Created
November 14, 2021 05:01
-
-
Save trentgill/bf930d4cb3f7f9e4a4c981432bf02356 to your computer and use it in GitHub Desktop.
sequins_arithmetic
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- base sequins | |
s1 = s{0,4,7} | |
s1(),s1(),s1() --> 0, 4, 7 | |
-- apply a pattern 'transformer' function to maybe add an octave | |
s1:fn(function(n) return (n>5) and n or n+12 end) | |
s1(),s1(),s1() --> 12, 16, 7 | |
-- we can cancel the transformer with an empty fn | |
s1:fn() | |
s1(),s1(),s1() --> 0, 4, 7 | |
--[[ motivation | |
why do it? why not just apply the function at the consumer? | |
i think the reason is for better encapsulation of concerns. | |
when applying a transformer, you are thinking in terms of the data. | |
it feels ideal to wrap that data-manipulation up with the data | |
storage. it's very similar to mapping over an table in a non-destructive | |
way. | |
also means sequins can use values that the consumer won't understand | |
natively. insead you can deal with values in an intermediate format | |
(eg note-numbers like above) then wrap the output-conversion into | |
the sequins itself. without this feature, we end up building trivial | |
anonymous functions throughout our code. and we all know anon fns in | |
lua are not exactly terse. | |
]] | |
-- when using these transformations, imagine we want to capture the | |
-- transform at a given moment (we found a nice algo). there are a few | |
-- approaches to capturing it: | |
-- 1) capture the transformer function | |
mytransform = s1.fn -- can pass this around (shoutout ryan!) | |
-- 2) copy the sequins (including the transformer) | |
s2 = s1:copy() | |
s1:fn() -- clear the transformer in the original | |
-- s2 is now a duplicate sequins with the old transformer function | |
-- 3) bake the transformer into a new sequins | |
s2 = s1:bake() -- copies & executes the function per value (recursive) | |
s1:fn() -- clear the transformer in the original | |
-- query why we ever need to bake? | |
-- my assumption is to incrementally approach pattern transformation | |
-- you could just write a big function, but maybe you want to generate | |
-- multiple different patterns iteratively... | |
--[[ operator shortcut | |
one of the main use cases in mind is the classic conversion from n-style | |
note numbers to volts. this use case is so common that there's a shortcut | |
available for using operator syntax over the sequins table directly. | |
]] | |
-- division shortcut | |
s1 = s{0,3,6,9}/12 | |
-- equivalent to: | |
s1 = s{0,3,6,9}:fn(function(n) return n/12 end) | |
--[[ operator limitations | |
due to lua's grammar, this syntax is only available when assigning | |
(operators are expressions), so it's really just a shortcut at | |
initialization. | |
only a single operator can be applied, and any subsequent operations will | |
override the previous one (perhaps with an error message?) | |
]] | |
-- ERROR case | |
s1 = s{0,3,6,9}/12 + 1 -- +1 will override /12 | |
--> 1,4,7,10 -- note that the /12 is abandoned | |
--[[ shortcut alternative | |
because there's really only 1 use-case i'm thinking of for the operator | |
shortcut, perhaps we should just have a pre-def'd function for notes-to-volts | |
ironically, this is the classic (much derided) `n2v` function. | |
]] | |
-- using n2v | |
s1 = s{0,3,6,9}:fn(n2v) | |
--[[ don't abandon ship yet! | |
one of the cool things about operator syntax is the ability to do math | |
between multiple sequins. especially when their lengths are unequal. | |
take for example an octave-jumping modifier: | |
]] | |
s1 = s{0,4,5,7,8} + s{0,0,12,0} | |
-- equivalent to | |
s1 = s{0,4,5,7,8}:fn( | |
function(n,...) -- additional args to :fn are passed in as var args | |
return n + {...}[1]() -- annoying unwrap of varargs | |
end, s{0,0,12,0}) -- the sequins will be stored in the sequins | |
-- above it's clear that the 2nd sequins will be captured inside the 1st's | |
-- transformer function, and perhaps the explicit behaviour is a good thing. | |
-- but there is something about the former that makes me want to generate | |
-- a bunch of sequences with long lengths, then print out the good bits. | |
-- that said, the 'equivalent' syntax is remarkably close to the clock | |
-- libraries additional argument passing (so it's ugly but consistent). | |
-- what if the :bake method took an argument for number of steps to capture | |
s2 = s1:bake(32) -- duplicates s1 (temporarily) | |
-- create a new empty sequins (return value) | |
-- fill it up by calling the temp copy 32 times. | |
-- so a pattern generator could look like | |
patt = (s{0,7,2,9,4} + s{0,12}):bake(32) | |
-- this alternate usage of :bake could potentially use a new name | |
-- maybe 'flatten' makes more sense, or perhaps we could flip them? | |
-- while the patterns aren't *that* complex, the truncation to a known | |
-- number of elements leads to more rhythmically grounded patterns as well | |
-- as a tertiary phasing quality. eg: the previous example repeats every | |
-- 10 elements, but producing 32 vals leaves an additional 2 steps on the | |
-- end that rapidly repeat afterward. | |
-- often polyrhythmic patterns are fascinating, and hearing the patterns | |
-- shift over each other is very rewarding, but after a prolonged time | |
-- there can be a loss of a rhythmic centre which is negative (in some | |
-- circumstances). having the ability to truncate to a fixed length leads | |
-- to more controllable patterns. | |
--[[ sequins in transformers | |
speaking of the above usage of a sequins operation, it should be noted | |
that in the regular function-style transformer config, you can fill the | |
transformation with arbitrarily many sequins. it is just a regular old | |
first-class function, which can close over state. | |
but honestly once you start to get into complex multi-variate transformer | |
functions, you probably want to be writing them as a separate chunk of | |
lua, and just passing them in as functions to :fn. anything short of that | |
will eventually just lead to much confusion. | |
]] |
also thinking about some fun stuff you can do with built-in functions:
s{1,2,3}:map(math.random)
--> math.random(1)
--> math.random(2)
--> math.random(3)
--> math.random(1)
this line will generate a random number from 1
up to the sequins value. so it's a probability sequencer.
because any additional args to map
are passed in as subsequent args, you can create an inverse range (random from sequins value up to max)
s{1,2,3}:map(math.random, 5)
--> math.random(1, 5)
--> math.random(2, 5)
--> math.random(3, 5)
--> math.random(1, 5)
i'm sure there are many more standard functions (or crow/norns specific functions) that could very naturally consume sequins values. it would be a worthwhile (and fun) exercise to comb the function lists to identify other interesting fns to use out of the box.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@dndrks
thanks for the thoughts!
indeed easy cancellation of a transformer was focused on bc of your thoughts in that direction!
i'm excited about the
:bake(n)
method too! seems like a great way to rapidly generate musical ideas that aren't random, but quickly get more complicated that i can hold in my head.there is a difference with
copy
andbake
, specifically that a lonecopy
will duplicate the whole sequins (including the transformer function). you could do this to have 2 identical sequins that don't share an index.bake
on the other hand destructively changes the data in the table (optionally returning a copy, as discussed above). so they are different, but i'm not really sure how they will get used, so it's hard to commit without thinking further ahead...s1 = s{0,3,6,9}/12 + 1
could be implemented 2 different ways:lastly, i'm considering changing
:fn
to:map
as the behaviour is exactly mapping the function over the data (lazily). i appreciate that this reference is perhaps not known to new scripters, but i think encourage people to learn about functional programming techniques is a good thing. plus everyone loves the wordmap
:)