I’m trying to learn SuperCollider alongside Sonic Pi and I’m making flashcards to quiz myself on common functionality between the two, and in SuperCollider you can create an object that represents an infinite series of numbers, i.e. Pseries().asStream.nextN(10); will give you [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] but there’s no limit to how high it goes. Does Sonic Pi have a data structure like that? Rings are similar-ish in that they wrap around when you use an index that’s larger than the ring, but in this case I don’t want a wrap around; I want it to keep incrementing according to some parameters (e.g. step size). Does such a data structure exist in Sonic Pi?
Edit: To be clear there are alternatives, such as using a method/function that takes an index and just computes the value in the infinite series, but is there a SonicPi-ish way to construct an object that fulfills the same interface as and can be used interchangeable with a ring?
Followup question: while it’s easy to find the docs for the ring method, are there API docs somewhere for all the methods you can call on a Ring (or other objects you interact with in Sonic Pi)?
tick key (symbol)
Increment the default tick by 1 and return value. Successive calls to tick will continue to increment the default tick. If a key is specified, increment that specific tick. If an increment value is specified, increment key by that value rather than 1. Ticks are in_thread and live_loop local, so incrementing a tick only affects the current thread’s version of that tick. See tick_reset and tick_set for directly manipulating the tick vals.
Options
step:
The amount to tick up by. Default is 1.
offset:
Offset to add to index returned. Useful when calling tick on lists, rings and vectors to offset the returned value. Default is 0.
That said, I don’t really know whether the boundaries are with Sonic Pi’s flavor of Ruby; I’d tried something with lazy enumeration / Fibers the other day and it seemed to break Sonic Pi. Would I need to worry hitting the boundaries with what you’re suggesting?
I haven’t hit any real boundaries with enumerations. The only boundaries of Sonic Pi I’ve come across is more related to timing and hickups when trying to process and play too many notes at a time.
That being said of course you have to be sure not to do crazy stuff like huge permutations and try to convert that to a list because that would crash any program using any programming language … but for that enum.size() is your friend
The boundaries are that only the functionality documented within Sonic Pi is officially supported. All other aspects of Ruby may or may not work - and even if they do, they may break without warning in a future version.
With respect to infinite series - this is not something we support at this point. Sonic Pi’s data structures are all concrete, immutable and persistable. As @perpetual_monday points out, tick is the closest we get.
Out of interest, what specifically are you unable to achieve with the functionality as described in the documentation?
Technically there’s nothing that can’t be programmed with the primitives exposed by Sonic Pi, but sometimes it seems like it’d be nice to have an iterator API that you could 1. layer over the persistent immutable data structures you have today and 2. implement for your own iterators that aren’t necessarily based on an immutable data structure.
Specifically, when I’m messing around with rhythms, i usually have something like
rhythm_a = spread(1, 4)
rhythm_b = spread(3, 8)
# ...
rhythm = rhythm_a # this can be changed live
melody = (scale :c3, :major)
tick_reset :melody
tick_reset :rhythm
64.times do
play melody.tick(:melody) if rhythm.tick(:rhythm)
end
Now, for the purposes of live coding (and really any coding), it’d be ideal if I could just swap the rhythm or melody by pointing it to another data structure / object / thing; this of course works if everything can be represented with rings or other indexable data structures, but what if I wanted to do something weird like generate a melody based on the fibonacci sequence or something that can be lazily constructed but isn’t really indexable?
I think maybe what’s missing from Sonic Pi (and I totally understand if it’s not worth adding for reasons of simplicity / ease / minimal API surface) is an iterator API. I get that you’re trying to avoid mutation and preserve the composability of immutable persistent data structures, but iterating through these structures already involves mutating this weird ambient mutable counter known as tick.
If Sonic Pi had an iterator API, I could rewrite the code above to always use iterators that internally “tick” whenever you call .next on them. Instead of tick_reset, you create a new iterator based on the same immutable datastructure. And then if on top of that you could define your own algorithm iterators that implement the .next interface, then there’d be no limit to the creativity.
But I totally understand the constraint to keep the API surface minimal; though I’m loving the design of SuperCollider’s Sequence APIs, the API surface of SC is preeeeeetty overwhelming.
I’m just having a play with this sample and trying to understand what’s happening vs what’s desired, as I think the goals are achievable.
So you have rings to iterate over, and another times loop that iterates.
On first run I think I get all notes played at once
So I replaced play with play_pattern_timed, but then the ticks don’t seem to work in the times loop, so I reverted back to original play method, and added a sleep, and the notes play, and the if condition seems to work (when omitted notes are played on each loop & tick)
I think you may be after some alternative syntax thing , intrigued to hear more if still pertinent