sequins v2
The sequins
library is designed for building sequencers, arpeggiators and all sorts of other pattern systems. This new version adds to the original with transformers for simple pattern synthesis, and also streamlines some syntax for character-patterns.
For a quick refresher on v1, see the scripting reference and its entry in the norns reference.
Now, onto the new!
sections
sequins-strings
This feature is inspired by foxdot’s sample patterns, as used in a Flash Crash performance by mgs. The core idea is a string which assigns different events to different characters.
For sequins
, this just leads to a simple syntax shortcut where you replace the normal table-of-values (eg. sequins{x,y,z}
) with a string. This turns each character of the string into an element of the sequins
:
char_sequence = sequins"+ - + . "
char_sequence() --> '+'
char_sequence() --> ' '
char_sequence() --> '-'
-- etc.
When using these sequins
-strings, you’ll need to build a function that takes a character & does something special with it. Using spaces in a sequins
string is a great way to clearly add sonic whitespace to a pattern.
function make_sound(char)
if char == '+' then output[1](pulse())
elseif char == '-' then output[2](ar())
elseif char == '~' then output[1](lfo())
elseif char == '.' then output[1].volts = 0
end -- note that spaces are ignored!
end
char_sequence = sequins"+ -~ ."
make_sound(char_sequence()) -- + -> pulses output 1
make_sound(char_sequence()) -- space -> does nothing
make_sound(char_sequence()) -- - -> creates envelope on output 2
make_sound(char_sequence()) -- ~ -> starts an lfo on output 1
make_sound(char_sequence()) -- space -> does nothing
make_sound(char_sequence()) -- . -> sets output 1 to 0V, thus stopping the lfo
Of course, the sequins
v1 flow modifiers are still available so you can manipulate your pattern in interesting ways:
-- the following 2 lines are the same! this is special Lua function-call syntax.
chs = sequins"A a!"
chs = sequins("A a!")
-- calling chs() repeatedly produces the sequence:
-- 'A', ' ', ' ', 'a', '!', 'A', etc
-- use the :step method to step forward two elements at a time:
chs = sequins "A a!":step(2)
chs() --> 'A', ' ', '!', ' ', 'a', 'A', etc
sequins
offers other methods for flow-modification, but only when multiple patterns are nested. While this is absolutely possible with sequins
-strings, you’ll lose the primary benefit of having one character space equal to one event. Instead, try deploying multiple parallel sequins
that interleave elements, or phase against each other in polyrhythms, as in this example:
-- two sequins with length 7 and 8.
seq_a = sequins"+ + +0"
seq_b = sequins"R + ."
-- add your own make_sound function to handle the above characters!
-- execute both sequences at once, every beat
-- the 2 sequences will phase against one another
clock.run(function()
while true do
clock.sync(1)
make_sound(seq_a())
make_sound(seq_b())
end
end)
Transformers
While sequins
has always been about taking a pattern and manipulating how you move through it, transformers are all about changing the data inside of the pattern. This allows you to write your data in a legible way, but then manipulate it into a different form for some other use (eg. note values -> volts). Or it can be used for more drastic manipulation of the data itself!
Transformers are applied with the new :map
method chained onto your sequins
. Whenever your sequins
is called, the value produced is transformed by the map
function before being returned.
Utilitarian Usage
We’ll start with some simple arithmetic operations. One very common usage is to convert 12TET note values (à la {0,4,7} for a major triad) into their voltage equivalents. Here, we convert note-numbers into voltages for use with ii
:
my_seq = sequins{0,3,0,2,7}:map(function(n) return n/12 end)
-- now we can just use the `sequins` directly
ii.jf.play_note(my_seq(), 2)
In this case it might seem just as easy to apply the division when creating the note on just friends, but the benefit here is that we can now think about my_seq
as being a sequins
of voltages, rather than notes.
In a similar way, we might want to express our sequins
as just-intonation ratios. Here we can change my_seq
without having to touch the code that plays the notes, by using our justvolts
global:
my_seq = sequins{1/1, 3/2, 8/5, 4/3}:map(justvolts)
-- note how this next line is identical to above
-- thus we can freely change the note data without modifying our playback code
ii.jf.play_note(my_seq(), 2)
Arithmetic Operators
When you want to apply a single arithmetic operation to your sequence (like our note values -> volts conversion) you can simply use the arithmetic operators shortcut syntax.
The available arithmetic operations are +
, -
, *
, /
and %
.
It might look a little weird, but just remember that behind the scenes, the math operation is being applied via the :map
method:
my_seq = sequins{0,3,0,2,7}/12 -- divide-by-12 will be applied as the transformer
ii.jf.play_note(my_seq(), 2)
This syntax is also useful if you want to apply a simple transposition:
my_seq = sequins{0,3,0,2,7}+2 -- shift up by 2 semi-tones
ii.jf.play_note(my_seq()/12, 2) -- note we had to put the /12 down here though...
Using the new hotswap
library will let you change this more rapidly (see hotswap.
Pattern Manipulation
Let’s get into more involved transformations. The :map
function is just a regular Lua function with one argument: the value to be transformed. This means that the entire Lua language is available to you. Here, we inject some randomness to probabilistically add an octave to the notes:
my_seq = sequins{0,3,0,2,7}:map(function(n) return n + (math.random() > 0.5) and 12 or 0 end)
This line is a little lengthy, so let’s break our transformer out into its own function:
function maybe_octave(n)
if math.random() > 0.5 then
return n + 12
else return n end
end
my_seq = sequins{0,3,0,2,7}:map(maybe_octave) -- this clarifies our intent
You could also use conditionals to change values based on an external signal through one of crow’s inputs:
function major_minor(n)
if input[1].volts > 2 then -- switch to minor tonality if input 1 above 2V
if n == 4 then return 3
elseif n == 11 then return 10
end
else return n end -- otherwise leave untouched
end
my_seq = sequins{0,4,7,11,14}:map(major_minor)
We can also use external storage to do some statistical analysis of a sequins
. This example returns the median of the last 3 values which might be… useful?
local previous = {0,0,0} -- store the last 3 values
function median(n)
table.remove(previous, 3) -- remove the last element
table.insert(previous, n) -- add the new element
-- copy the table so we can sort it
local t = {}
for i=1,3 do t[i] = previous[i] end
table.sort(t) -- sort the table
-- return the middle element which will be the median
return t[2]
end
my_seq = sequins{0,10,4,3,7,11,3,4,14}:map(median)
sequins As Operators
Extending the arithmetic operators from above, we can create some very interesting combinations by using a second sequins
on the right-hand-side of the operation:
my_seq = sequins{0,7,2,9,4} + sequins{0,0,-12,12}
my_seq() --> this would be a nice long pattern jumping up and down octaves
Above, the second sequins
is placed inside of the first’s :map
thanks to our arithmetic operators. The equivalent non-operator version is:
my_seq = sequins{0,7,2,9,4}
:map(function(n, sq) -- we can pass additional args!
return n + sq()
end, sequins{0,0,-12,12})
You may be tempted to chain multiple sequins
together with +
, but remember that we can only apply one arithmetic operation with this syntax. If you want to delve more deeply into this, you’ll need to write the full :map
method as shown immediately above.
Cancelling a Transformer
At some point you may want to remove the current transformer. To do so, you can simply call :map
with no argument to unset the transformer:
my_seq = sequins{0,3,0,2,7}:map(function(n) return n + (math.random() > 0.5) and 12 or 0 end)
my_seq() --> provides some fun octave switching
my_seq:map() -- remove the map transformer
my_seq() --> provides the original sequence again
Save Your Work
sequins
transformers are designed to be used to gradually build up ideas over time. By incrementally modifying the sequins
data, flow-modifiers, and map-transformer, you’ll hopefully get to a new place that you’d like to capture in its own right.
As of v2, there are 3 ways to “copy” a sequins
, each having a different result.
The first option is to simply copy around the name of the sequins
(eg. seq
in seq = sequins{1,2,3}
). This doesn’t actually copy anything, but just passes around a reference to the sequins
object. Wherever you call the name, the sequins
will advance a step forward and return a new value. This lets you define a sequins
in one place, and use it in another.
:copy
To make an independent copy of sequins
, we can use the :copy
method:
my_seq = sequins{1,4,9,16,25}
my_copy = my_seq:copy()
my_seq() --> 1
my_seq() --> 4
my_seq() --> 9
my_copy() --> 1
my_copy() --> 4
Here we can see that the original and copy have their own indices that are independent of each other. This could be useful if you want to use the same melody played by two instruments with different rhythm or tempo.
When using :copy
, everything is copied into the new variable. this includes flow-modifiers (eg :every
), and transformers. This is useful if you have found a nice configuration that you want to keep around: make a copy of it and set it to the side, then keep working on your original. This is similar to the “Save a Copy” feature in many PC applications.
:bake
At some point you’ll likely find a sequins
that you love, but perhaps it’s already using a transformer and you want to add another without adding a bunch of text. Or maybe the sequins
sounds great for a little while, but becomes weird or loses a sense of rhythm after some time. This is where the :bake
method comes in as a new way to copy sequins
.
Baking a sequins
captures the output data for a defined number of steps, and places it into a new, independent sequins
.
patt = sequins{0,7,2,9,4} + sequins{0,12}
patt() --> creates a nice sequence of notes, looping every 10 elements
cake = patt:bake(32) -- capture the first 32 elements of patt
cake() --> same as patt() but loops after 32 elements
Now cake
can be used just like patt
but because it’s been baked, it will loop after 32 steps. This will truncate the fourth repetition of the note sequence which might be just the amount of poly-rhythm you’re looking for.
You can now add a new transformer to cake
to further take it in a new direction!
Other New Goodies
Along with the sequins
-strings and :map
-transformers, v2 also has some nice quality-of-life improvements to make working with sequins
a little easier:
Visualizing
Previously, calling print(my_sequins)
would return something like table 0x38746373
. We’ve updated this behavior so that print
ing a sequins
will give you information about both the values and the index state(s) of the sequins
:
s1 = sequins{1,2,sequins{3,4}}
for i=1,5 do s1() end -- step forward 5 steps
print(s1) --> s[2]{1,2,s[2]{3,4}}
We can break down this printout into the following elements:
s
: this is asequins
[2]
: we’re at index 2{1,2,...}
these are the values in thesequins
s[2]{3,4}
the state of the nestedsequins
When you get deeply nested the printout can be a little hard to read, but it should bear a close resemblance to your original sequins
, useful for reference.
If you have flow modifiers or transformers applied, they will be shown.
Here, :every
is shortened to :e
:
patt = sequins{0,7,2,9,4}:every(3)
print(patt) --> s[1]{0,7,2,9,4}:e[0](3)
And :map
is shown as the underlying transformer:
patt = sequins{0,7,2,9,4} + sequins{0,0,12,12}
s[1]{0,7,2,9,4}:map(fn,s[1]{0,0,12,12})
Length
If you call the #
(length) operator on a sequins
, it will now return the number of elements at the top-layer of the sequins
. If you have nested sequins
, each nest will count as one element:
s1 = sequins{1,2,3}
print(#s1) --> 3
s2 = sequins{1,2,sequins{3,4}} -- nested sequins only count as a single element
print(#s2) --> 3
:peek
If you’re using a sequins
in multiple places but only want it to be advanced in one location, you can use the :peek
method (thanks to @tyleretters
). This is especially useful if you want to find the current value to print on screen (eg. on norns):
s1 = sequins{0,3,9,7}
s1() -- start with first index
s1() -- move to second index
print(s1:peek()) --> 3