The code does a few things that I've just learnt how to do this year (or even this week).
It uses:
an EventVariable
an EventVariable array
compound CapyTalk expression (the comma thing)
builds a compound CapyTalk expression using SmallTalk
The code:
"First we have to declare our variables"
| register registerSize registerValue shiftExpression initialiseExpression values |
"The size of the shift register"
registerSize := 8.
"The array of possible register values.
I'm using the values as multipliers of the sample frequency so they are all
ratios taken from a just scale."
values := #(1 {5/4} {4/3} {3/2} {9/5} 2 {18/8} {12/5}).
"Declaring an EventVariable. HotValues like !MyCoolHotValue are a type of EventVariable and are CapyTalk variables that can change at run-time. HotValues create a control on the VCS. All the other variables in this code like registerSize are SmallTalk variables which means they're only used at the start when the Sound is constructed. Declaring an EventVariable is a way to make a CapyTalk variable that can be used at run-time but doesn't create a VCS fader."
registerValue := EventVariable new initialValue: 0.
"Next I'm declaring an EventVariable array. This is an array we can change at run-time. This is the shift register and being a CapyTalk array means we can shuffle the values along."
register := EventVariable new size: registerSize.
"Now it gets a bit funky! You can have a compound CapyTalk expression if you separate each expression with commas. So you can do something like:
(!GiveMeARandomNumber nextRandom), (!GiveMeANormalNumber nextNormal)
The multiple expression I want to write shuffles each value of the register along one, which for a shift register with 8 values uses 8 CapyTalk expressions. Take the value from postion two and put it in position 1, Take the value from postion three and put it in position two etc etc. Yawn. So I'd like to write that using a SmallTalk loop to construct that expression. And first I need to save the first value of the shift register into a variable before I shuffle them all along one. This next line is that first part of the multiple expression (that saves the first value) and I'm putting the CapyTalk expression into a SmallTalk variable, shiftExpression.
To access a value from an EventVariable array you use @<. The line below is accessing the first element of the array (position 0). To assign a value to an EventVariable at run-time I have to get it off the stack. I'm using the <+ operation to pop it off the stack."
shiftExpression := registerValue <+ (register @< 0).
"Next is the iterative SmallTalk loop that constructs the large CapyTalk expression using inject: into:. I'm injecting shiftExpression, our CapyTalk expression so far, into the iterative loop and it becomes the :expr variable. On each iteration of the loop the inject: into: construct takes the previous output of the loop and puts it back in as :expr, so you can use it as a way of building up a structure, in this case a complext CapyTalk expression.
Inside the loop is:
(expr, ((register @< ...
which is where it takes the expression so far, adds the comma and then adds another CapyTalk expression on the end."
shiftExpression := (0 to: (registerSize - 2)) inject: shiftExpression into: [:expr :i|
(expr, ((register @< i) <+ (register @< (i + 1))))].
"I want to initialise the shift register with successive values from the values array. Again I'm doing this at run-time so I'm constructing another comma separated CapyTalk expression to assign all 8 values. Here I'm creating the first part of that expression:"
initialiseExpression := (register @< 0) <+ (0 of: values).
"And below I'm using inject: into: again to create the other parts of this CapyTalk expression:"
initialiseExpression := (1 to: (registerSize - 1)) inject: initialiseExpression into: [:expr :i|
(expr, ((register @< i) <+ ((i mod: (values size)) of: values)))].
"Below I'm stitching everything together into the final complex, multi-part CapyTalk expression, separated by commas."
"First the initialisation code which runs at start up"
((!localTime le: 1) true: (initialiseExpression) false: (nil)),
"This next bit will shift the shift register along one step every time !Gate is switched on"
(!Gate switchedOn
true:
(shiftExpression)
false:
(nil)),
"This bit copies the value that was at the head of the shift register back into the end. But randomly, depending on the setting of !MutationRate, it will select a random element of the values array and write that back into the end instead."
((((!Gate switchedOn nextRandom + 1) / 2) lt: !MutationRate)
true:
((register @< (registerSize -1)) <+ (!Gate switchedOn nextRandomElementOf: values))
false:
((register @< (registerSize -1)) <+ registerValue)),
"And finally I leave the value of the first position of the shift register on the stack. This is the value that the CapyTalk expression spits out and is what gets used as the value of the SoundToGlobalController that ultimately sets the frequency of the sample."
(register @< 0).
I'm sure there must be a simpler way of doing this.