Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sequins.lua library #387

Merged
merged 14 commits into from
May 11, 2021
Merged

Sequins.lua library #387

merged 14 commits into from
May 11, 2021

Conversation

trentgill
Copy link
Collaborator

@trentgill trentgill commented Apr 21, 2021

Fixes #359

Introduces a new core library to crow for imbuing tables of data with 'behaviours'. Particularly focused on note sequences, but flexible enough to be useful for many things (ideas: building markov chains, event sequencing, generative arrangement).

--- reference
-- data can be anything a lua table can store
words = {'hi', 'there'}
notes = {2, 9, -4, 0}
fns = {lfo, pulse, ramp, ar}
tabs = { {2,3}, {'even', 'different', 'types'}, {4,5} }

seq = sequins{words} -- *copies* table into a sequins and stores in seq
seq() --> returns the values from the sequins ('hi', 'there', 'hi', ...)

tab2 = {1, 2, sequins{3, 4} } -- creates a table where one element is a sequins
seq2 = sequins{tab2} -- note how tab2 contains nested sequins which will be resolved at runtime
seq2_ = sequins{1, 2, sequins{3, 4} } -- these nested sequins can be written inline
--> calling seq2() returns 1, 2, 3, 1, 2, 4, 1 ... -- note how the nested sequins vals are interleaved

seq = sequins{...}:step(-1) -- change increment step
seq = sequins{...}:select(2) -- set the table index which will be used for the next call
seq:select(3)() --> after selecting, can call the result to get the value immediately

--- method chains
-- these fns modify the state of the sequins destructively
sequins{fns}
  :step(-2) -- change increment value per call. indexes are wrapped to the table length
  :select(5) -- sets the index to be selected on the next call. value is wrapped into range
--- the following method chains are mainly for use inside nested sequins
-- they manipulate behaviour of the parent sequins
  :every(2) -- only returns a value every 2nd call. if no value returned, parent sequins will try to get the next value
  :times(3) -- forces the parent sequins to ask it for the next 3 elements
  :count(10) -- will only provide values the first 10 times it's requested, then returns nil.
  :all() -- like times(#sequins) where the nested sequins will return all of it's values once
  :once() -- like count(1)

Some examples to demonstrate use-cases:

s = sequins

--- sequencing lydian arpeggios with a wholetone run flourish
lydian = {0,2,4,6,7,9,11}
wholet = {0,2,4,6,8,10}
melody = s{ s(lydian):all():step(3)
          , s(wholet):all():every(3)
          }
metro[1].event = melody
metro[1]:start()

--- using a sequins inside a method chain modifier so behaviour changes over time
arp = s(lydian):step( s{2,2,-2} ) -- lydian scale in thirds

--- arranger can be built such that a single function can be called to get 'next note'
-- while changing sequence is done asynchronously
mel1 = s{0,7,2}
mel2 = s{3,6,9,13}:step(-1) -- runs right to left
mel3 = s{3,-3}:select(2) -- starts from the 2nd element
arr = s{mel1, mel2, mel3}:step(0) -- step(0) means we'll always stay on the given melody
metro[2].event = function() output[1].volts = arr()/12 end -- install arrangement into a metro
metro[2]:start() -- every metro tick will get a new note
-- now we can modify the sequence asychronously
arr:select(2) -- switch to the second sequence

@trentgill trentgill requested a review from tehn April 21, 2021 21:53
@trentgill
Copy link
Collaborator Author

Specifically I'd love feedback on naming & syntax. Use of every, count and times are all somewhat ambiguous but i'm also trying to keep them short so it's more readable.

The basic usage seems very clear & satisfying, but I'm not entirely sure I've approached the more complex ideas the best way. Having nested sequins is really powerful, I just want to make sure i'm providing the clearest interface into that complexity.

@tehn
Copy link
Member

tehn commented Apr 21, 2021

super exciting, i'll sit with it and let it sink in so i can give proper feedback on syntax.

fyi it looks like some public things got into this commit, not sure if you were trying to separate them

@trentgill
Copy link
Collaborator Author

thanks for the note re: public. i'd like to keep them separate for now, so i'll go do some git fu...

@tehn
Copy link
Member

tehn commented Apr 22, 2021

i really like the syntax-- that the simple things are straightforward and nesting allows for some weird meta-exploration.

a full tutorial/study on this would help people see the full potential. looking forward to playing!

@rbrt-fm
Copy link
Contributor

rbrt-fm commented Apr 23, 2021

i've been playing around with this for a bit this evening and it's super fun, but I've been getting a runtime error in druid when trying to use the select method
repl:1:1 attempt to call a nil value (method 'select')

@trentgill
Copy link
Collaborator Author

@rbrt-fm i just pushed a new commit fixing the select method. lmk if it fixes your problem and i'd love to hear your thoughts on using the library!

@trentgill trentgill merged commit ed8c5a1 into THREE May 11, 2021
trentgill added a commit that referenced this pull request Jun 30, 2021
* can now update output[n].scale(_) with .scale = {note list}, so it can be dynamically set like a regular table (#373)

* fix bug where aliasing an ii address before use could stack overflow (#374)

* querying output voltage now returns the shaped & scaled voltage (real voltage at jack)

* add just intonation helpers: justvolts(), just12()
* support input scales: input[n].mode( 'scale', {...}, 'ji')
* support output scales: output[n].scale({...}, 'ji')

* Python based bootloader support and dfureset (#377)
* `make pydfu` to use python3 dfu bootloader
* `make dfureset` jumps to bootloader from make
* `make zip` now builds dfu file as well as binary
* add instructions for using pydfu instead of dfu-util

* Frequency detect mode for input[1] (#382)
* `input[1].mode('freq', interval)` reports frequency of a signal connected to input 1 (no support for input 2)
* `hztovolts(hz, <ref>)` helper fn converts an input hz equivalent voltage (uses default reference to match Just-Friends & W/Syn)

* print an error to usb host if event queue overflows (#391)

* clock.lua module added (#393)

* New calibrate.lua & underlying engine (#397)
Breaking:
* `cal` lua module refactored with new API
* calibration no longer auto-runs if no vals saved (ie new factory modules)
* `util/recalibrate.lua` demonstrates usage, and can be used for factory calibration (or at home)

* double userscript storage size to 16kB (#396)

* add jacktest.lua

* strip debug info from crow lua libs for dramatic ram reduction (#395)

* Sequins.lua library (#387)
* add sequins library

* ASL2 (#399) ASL2 library. Runtime operates in C for greatly optimized performance & sample accuracy

* introduce Caw_printf

* Public.lua library for sharing parameters (#394)
* after calling init() crow sends '^^ready()'
* add 'public.view' for enabling live monitoring of in & out voltages

* Oscillate asllib fn (#407)
* add 'oscillate' asllib fn

* add note about not using `note` in asllib. kept only to avoid the breaking change

* add quote library & remove quotes global (#412)

* support ii getters from teletype (#403)

Co-authored-by: brian crabtree <tehn@monome.org>
@trentgill trentgill deleted the justsequins branch July 3, 2021 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants