crow - interpreting plain text in lua

This is my first post here! I picked up a crow recently and have been having a lot of fun playing around with druid and throwing in some simple scripts of my own as well as the ones available in bowery. But I haven’t been able to figure out how to do the thing I was most excited about when I first read about this module, and I can’t seem to find anyone else who’s attempted something similar with crow. What I’d like to do is create a Lua script that can interpret plain text and translate it into musical information. I’ve made something like this in Supercollider, and I’ll include that code here for reference. With crow, I’m imagining turning plain text into cv on outputs 1 and 3 and envelopes on outputs 2 and 4, with input 1 to receive clock and input 2 to receive cv to do … something? I took a look at some of the Lua references on the monome site, and it seems like the way to accomplish this would be through creating tables that would function in much the same way intervMap, durMap, susMap, and ampMap do in the attached scLang. But I don’t know how to do that in Lua, so I thought I’d ask here. Has anyone tried something like this before? Any ideas about how to approach it, where to start, or where to look to learn what I’ll need to know?

Here’s the above-mentioned scLang. The input is the first bit of the screenplay for Star Trek IV: The Voyage Home. It’s just what happened to be in there when I pulled it up.

(
var noteFunc, sinInst, midiInst, channel = 0, port = 0, prog = 0,
intervMap, count = 0, ifNilInt = 0, midin = 0, ifNilDur = 1,
durMap, durFunc, ifNilSus = 1, susMap, susFunc, ifNilAmp = 0.5,
curAmp = 0.5, ampMap, ampFunc, inputString;
//The input stream.
inputString = "Star Trek IV
                                       The Voyage Home










                                         Screenplay by
                                HARVE BENNETT & NICHOLAS MEYER

                                            Story by
                                 STEVE MEERSON & PETER KRIKES


                                                              REV.  SHOOTING SCRIPT

                                                                     March 11, 1986





                                        PRODUCTION NOTES

              1.   Please note that the character of Starfleet Commander
                   is Admiral Cartwright, not Admiral Morrow. Please
                   modify all scripts and production boards accordingly.

              2.   Please note that Master Chief Petty Officer Rand now
                   first appears in Scene 25 and in all
                   subsequent Starfleet Command
                   scenes.

              3.   Please note that Commander Chapel appears in Scene 25
                   and in all subsequent Starfleet
                   Command Scenes.

              4.   Please note that a new Scene 188A has been included
                   and Scotty's side of the dialogue will be photographed
                   in the Cargo Bay Area.

              5.   Please note the readmission of Scenes 132 and
                   133.

              6.   Uhura is now present in Scene 132.

              7.   Please note that in a new scene 158A, we are covering Kirk
                   in the Transporter area of the Bird of Prey during the
                   plexiglass lowering sequence.

                   Also note, we will require Video playback from production
                   footage of Gillian's image outside. Existing video monitor
                   in Cargo Bay will be playback source for this image.

              8.   Please note that in Scene 188A, Scotty will be covered
                   in the Cargo Bay.

              9.   Please note that Admiral Morrow (Cartwright) does not
                   appear in Scenes 265, 266, 267.


                                          DEDICATION


                   IN BLACK

                   In the silence preceding the Paramount Logo, in simple
                   white letters which FADE IN is the following legend:

                                  THE CAST AND CREW OF STAR TREK

                                   WISH TO DEDICATE THIS FILM

                                   TO THE MEN AND WOMEN OF

                                   THE STARSHIP CHALLENGER

                                   WHOSE COURAGEOUS SPIRIT SHALL

                                   LIVE TO THE 23RD CENTURY AND

                                   BEYOND.


                   THE LEGEND FADES OUT.


            .......................................................................


                   FADE IN:

               1   SPACE - A STARFIELD - ILM                                      1

                   A HORN sounds the STAR TREK FANFARE, and we begin to
                   MOVE FORWARD. MAIN TITLES begin. And to the delight
                   of Star Trek lovers everywhere, the MUSIC OVER the
                   early credits is the SERIES THEME by Alexander Courage.
                   We're in for a classic, good old Star Trek time. But
                   hold on...

                   After the first 16 bars and early CREDITS, the music
                   trails off ominously into silence -- and a faint new
                   SOUND. At the same time, we pick up a speck of light
                   coming toward us from deep space. As the SOUND GROWS,
                   the speck begins to assume shape and form.";
//intervMap is filled with arrays containing a collection of
//characters and a value. In the functions below the character
//strings are associated with the numbers.
intervMap = [
["abcdef", 5], ["ghijkl", 3], ["mnopqr", -2], ["stuvwxyz", -3],
["ABCDEF", 9], ["GHIJKL", -7], ["MNOPQR", -5], ["STUVWXYZ", 1], [" .:;~!@#$%^&*,.", 0]
];
durMap = [
["aeiouy", 1.125], ["bcdfghojkl,", 1], ["mnpqrstvwxz", 0.25],
["AEIOUY", 2], ["BCDFGHIJKLMN", 1.5], ["PQRSTVWXZ", 1.25], [" .:;~!@#$%^&*,.", 0.125]
];
susMap = [
["aeiouy", 1.125], ["bcdfghojkl,", 1], ["mnpqrstvwxz", 0.25],
["AEIOUY", 2], ["BCDFGHIJKLMN", 1.5], ["PQRSTVWXZ", 1.25], [" .:;~!@#$%^&*,.", 0]
];
ampMap = [
["abcdef", 0.8], ["ghijkl", 1.25], ["mnopqrstuvwxyz", 0.9],
["ABCDEF", 1.20], ["GHIJKL", 0.7], ["MNOPQR", 0.75], ["STUVWXYZ", 0.85], [" .:;~!@#$%^&*,.", 0]
];
noteFunc = Pfunc({var parseInt, octave = 32;
//Each array in the intervMap is checked to see if the
//character (inputString.wrapAt(count)) is included. If
//it is then parseInt is set to the value at item.at(1)
intervMap.do({arg item;
if(item.at(0).includes(inputString.wrapAt(count)),
{parseInt = item.at(1)})
});
//If parseInt is notNil, midin is set to that plus previous
//midin. ifNilInt is for storing each parseInt to be used if
//no match is found and parseInt is nil.
if(parseInt.notNil,
{midin = parseInt},
);
[inputString.wrapAt(count)].post;
["pitch", parseInt, midin, octave/12, midin + octave].post;
midin + octave
});
durFunc = Pfunc({var parseDur, nextDur;
durMap.do({arg item;
if(item.at(0).includes(inputString.wrapAt(count)),
{parseDur = item.at(1)})
});
if(parseDur.notNil,
{nextDur = parseDur; ifNilDur = parseDur},
{nextDur = ifNilDur}
);
["dur", nextDur].post;
nextDur
});
susFunc = Pfunc({var parseSus, nextSus;
susMap.do({arg item;
if(item.at(0).includes(inputString.wrapAt(count)),
{parseSus = item.at(1)})
});
if(parseSus.notNil,
{nextSus = parseSus; ifNilSus = parseSus},
{nextSus = ifNilSus}
);
["sustain", nextSus.round(0.01)].post;
nextSus
});
ampFunc = Pfunc({var parseAmp;
ampMap.do({arg item;
if(item.at(0).includes(inputString.wrapAt(count)),
{parseAmp = item.at(1)})
});
if(parseAmp.notNil,
{curAmp = curAmp*parseAmp; ifNilAmp = parseAmp},
{curAmp = curAmp*ifNilAmp}
);
count = count + 1;
if(0.5.coin, {curAmp = rrand(0.2, 0.7)});
["amp", curAmp.round(0.01)].postln;
curAmp.wrap(0.4, 0.7)
	});
Pbind(
\midinote, noteFunc,
\dur, durFunc,
\legato, susFunc,
\amp, ampFunc,
).play;
)

1 Like

I’m not to familiar with sclang, but it seems like you could accomplish something similar using Lua patterns. Have a look at the String library (for iterating over the text in many sophisticated ways), and specifically the pattern matching mechanism. This will allow you to create similar mappings between ranges of characters and different values.

I’d recommend building it as a plain lua script on your computer so you don’t have to wait for the crow upload cycle, plus desktop lua has more in-depth error messaging and debug capabilities.

2 Likes

I’m looking at the string library now, and this seems super helpful! Thank you so much for the tip.

1 Like

5 months later, I’ve read a bunch of the Lua manual and picked up a norns shield, and I think I have something along these lines starting to work. I didn’t really know how to make a sequencer, so I borrowed @Galapagoose’s sequins demo. Right now, I’m able to turn a string of characters (i.e., a poem) into a string of numbers and then turn the string into a table which can in turn serve as a reference for a sequin. Some questions remain:

  1. right now, the only way I was able to get from letter to number and from string to table was by separating each character in the plain text with a space. There must be a more elegant way to do this, even if it’s just adding a function that says “insert a space between every character in this plain text.” But I don’t know what that more elegant solution is. Ideas welcome. This was a little counterintuitive, but I’ve got it working now. The breakthrough was that I needed to eliminate all spaces in the text (in this case, my rather inelegant solution was to replace all spaces with the letter “a”) and then reintroduce one space between each character, like so:
titlenospace = string.gsub(title, "%s", "a")
textnospace = string.gsub(text, "%s", "a")
titlewithspace = string.gsub(titlenospace, "(%a)", "%1 ")
textwithspace = string.gsub(textnospace, "(%a)", "%1 ")
  1. how to change the output from norns sc engine to crow done! This was way easier than I thought it would be
  2. how to quantize output done! crow.output[n].scale
  3. how to get rests into the mix. Silence is important. The current workaround I have in mind for this, since I’ll be using this with crow in a modular system, is to derive triggers (and/or envelopes) from somewhere else in the system entirely and just to send pitch through crow outputs 1 and 3. But I’d like to be able to use outputs 2 and 4 for triggers in the long run. Splicer (and @zebra and @zbs) had the answers here. This is working now.
  4. how to make this part of a “combo script.” I think mlr would be a good candidate for this, but when I tried just plopping this code onto the beginning or end of mlr, it wouldn’t run. I assume this has something to do with clocks. But I could use some advice on mashing them together without breaking things. I know @tehn is very busy, but perhaps your would have some insight? Or @dan_derks? I’m more interested in running this script in the background of another script handling softcut duties than I am in treating it as a stand-alone thing. This is because, as an initial use-case, I plan on using it for a sort of multimedia form of poetry reading, in which I’ll be reading the text, manipulating my voice in real time using softcut, and playing a modular for accompaniment with pitch (and perhaps trigger) information derived from the text I’m reading. Thanks to a great deal of assistance from Dan, I’ve got this working with mlr! I might try for otis next, for gridless operation, but for right now, I’m very pleased with how this is working.
  5. how to introduce more poems, more tables, more sequins, etc. got it! This took me a while, but I realized that it’s not so much about making more sequins as it is about making more arps to parse the existing tables in various ways
  6. UI?

If anyone has other suggestions, I would really love to hear them. I also just wanted to share a progress update here, since so many of you have been so helpful to me. The progress is slow, but it’s happening. Here’s what I’m working with so far, if anyone’s curious.

--- krahenLied.

local lattice = require "lattice"
local sequins = require "sequins"
local MusicUtil = require "musicutil"
local TabUtil = require "tabutil"

title = "Christabel"
text = "'Tis the middle of night by the castle clock, And the owls have awakened the crowing cock; Tu—whit! Tu—whoo! And hark, again! the crowing cock, How drowsily it crew. Sir Leoline, the Baron rich, Hath a toothless mastiff bitch; From her kennel beneath the rock She maketh answer to the clock, Four for the quarters, and twelve for the hour; Ever and aye, by shine and shower, Sixteen short howls, not over loud; Some say, she sees my lady's shroud."
print(title)
print(text)

local ExtMin = 0
local ExtMax = 10
local ExtRange = ExtMax - ExtMin

scale_names = {}
for i = 1, #MusicUtil.SCALES do
  table.insert(scale_names, MusicUtil.SCALES[i].name)
end

function init()
  
function build_scale()
  note_nums = MusicUtil.generate_scale_of_length(params:get("root_note"), params:get("scale"), params:get("pool_size")) -- builds scale
end

  params:add_separator()
      -- setting root notes using params
  params:add{type = "number", id = "root_note", name = "root note",
    min = 0, max = 127, default = 60, formatter = function(param) return MusicUtil.note_num_to_name(param:get(), true) end,
    action = function() build_scale() end} -- by employing build_scale() here, we update the scale

  -- setting scale type using params
  params:add{type = "option", id = "scale", name = "scale",
    options = scale_names, default = 41,
    action = function() build_scale() end} -- by employing build_scale() here, we update the scale
  
  -- setting how many notes from the scale can be played
  params:add{type = "number", id = "pool_size", name = "note pool size",
    min = 1, max = 32, default = 32,
    action = function() build_scale() end}
  
 build_scale() -- builds initial scale
  
function quantize(v)
  local v = v/12
  local octs = math.floor(v)
  local rem = v - octs
  local index = math.floor(rem * #note_nums) + 1
  return note_nums[index]/12 + octs
end

titlenospace = string.gsub(title, "%s", "a")
textnospace = string.gsub(text, "%s", "a")
titlewithspace = string.gsub(titlenospace, "(%a)", "%1 ")
textwithspace = string.gsub(textnospace, "(%a)", "%1 ")

  anumbs = string.gsub(titlewithspace, "a", 32)
  bnumbs = string.gsub(anumbs, "b", 31)
  cnumbs = string.gsub(bnumbs, "c", 30)
  dnumbs = string.gsub(cnumbs, "d", 29)
  enumbs = string.gsub(dnumbs, "e", 28)
  fnumbs = string.gsub(enumbs, "f", 27)
  gnumbs = string.gsub(fnumbs, "g", 26)
  hnumbs = string.gsub(gnumbs, "h", 25)
  inumbs = string.gsub(hnumbs, "i", 24)
  jnumbs = string.gsub(inumbs, "j", 23)
  knumbs = string.gsub(jnumbs, "k", 22)
  lnumbs = string.gsub(knumbs, "l", 21)
  mnumbs = string.gsub(lnumbs, "m", 20)
  nnumbs = string.gsub(mnumbs, "n", 19)
  onumbs = string.gsub(nnumbs, "o", 18)
  pnumbs = string.gsub(onumbs, "p", 17)
  qnumbs = string.gsub(pnumbs, "q", 16)
  rnumbs = string.gsub(qnumbs, "r", 15)
  snumbs = string.gsub(rnumbs, "s", 14)
  tnumbs = string.gsub(snumbs, "t", 13)
  unumbs = string.gsub(tnumbs, "u", 12)
  vnumbs = string.gsub(unumbs, "v", 11)
  wnumbs = string.gsub(vnumbs, "w", 10)
  xnumbs = string.gsub(wnumbs, "x", 9)
  ynumbs = string.gsub(xnumbs, "y", 8)
  znumbs = string.gsub(ynumbs, "z", 7)
  Anumbs = string.gsub(znumbs, "A", 6)
  Bnumbs = string.gsub(Anumbs, "B", 5)
  Cnumbs = string.gsub(Bnumbs, "C", 4)
  Dnumbs = string.gsub(Cnumbs, "D", 3)
  Enumbs = string.gsub(Dnumbs, "E", 2)
  Fnumbs = string.gsub(Enumbs, "F", 1)
  Gnumbs = string.gsub(Fnumbs, "G", 1)
  Hnumbs = string.gsub(Gnumbs, "H", 2)
  Inumbs = string.gsub(Hnumbs, "I", 3)
  Jnumbs = string.gsub(Inumbs, "J", 4)
  Knumbs = string.gsub(Jnumbs, "K", 5)
  Lnumbs = string.gsub(Knumbs, "L", 6)
  Mnumbs = string.gsub(Lnumbs, "M", 7)
  Nnumbs = string.gsub(Mnumbs, "N", 8)
  Onumbs = string.gsub(Nnumbs, "O", 9)
  Pnumbs = string.gsub(Onumbs, "P", 10)
  Qnumbs = string.gsub(Pnumbs, "Q", 11)
  Rnumbs = string.gsub(Qnumbs, "R", 12)
  Snumbs = string.gsub(Rnumbs, "S", 13)
  Tnumbs = string.gsub(Snumbs, "T", 14)
  Unumbs = string.gsub(Tnumbs, "U", 15)
  Vnumbs = string.gsub(Unumbs, "V", 16)
  Wnumbs = string.gsub(Vnumbs, "W", 17)
  Xnumbs = string.gsub(Wnumbs, "X", 18)
  Ynumbs = string.gsub(Xnumbs, "Y", 19)
  Znumbs = string.gsub(Ynumbs, "Z", 20)
  Periodnumbs = string.gsub(Znumbs, "%.", 21)
  Colonnumbs = string.gsub(Periodnumbs, "%:", 22)
  Semicolonnumbs = string.gsub(Colonnumbs, "%;", 23)
  Commanumbs = string.gsub(Semicolonnumbs, "%,", 24)
  Hyphennumbs = string.gsub(Commanumbs, "%-", 25)
  Exclamationnumbs = string.gsub(Hyphennumbs, "%!", 26)
  Atnumbs = string.gsub(Exclamationnumbs, "%@", 27)
  Poundnumbs = string.gsub(Atnumbs, "%#", 28)
  Dollarnumbs = string.gsub(Poundnumbs, "%$", 29)
  Percentnumbs = string.gsub(Dollarnumbs, "%%", 30)
  Carotnumbs = string.gsub(Percentnumbs, "%^", 31)
  Andnumbs = string.gsub(Carotnumbs, "%&", 32)
  Starnumbs = string.gsub(Andnumbs, "%*", 31)
  Parenthesisnumbs = string.gsub(Starnumbs, "%(", 30)
  Parentheticalnumbs = string.gsub(Parenthesisnumbs, "%)", 29)
  Equalnumbs = string.gsub(Parentheticalnumbs, "%=", 28)
  Addnumbs = string.gsub(Equalnumbs, "%+", 27)
  Apostrophenumbs = string.gsub(Addnumbs, "%'", 26)
  Accentnumbs = string.gsub(Apostrophenumbs, "%`", 25)
  Tildenumbs = string.gsub(Accentnumbs, "%~", 24)
  Slashnumbs = string.gsub(Tildenumbs, "%/", 23)
  Undernumbs = string.gsub(Slashnumbs, "%_", 22)
  Bracketnumbs = string.gsub(Undernumbs, "%[", 21)
  Closebracketnumbs = string.gsub(Bracketnumbs, "%]", 20)
  Curlynumbs = string.gsub(Closebracketnumbs, "%{", 19)
  Closecurlynumbs = string.gsub(Curlynumbs, "%}", 18)
  Lessthannumbs = string.gsub(Closecurlynumbs, "%<", 17)
  Greaterthannumbs = string.gsub(Lessthannumbs, "%>", 16)
  Questionnumbs = string.gsub(Greaterthannumbs, "%?", 15)
  Endashnumbs = string.gsub(Questionnumbs, "%–", 14)
  Emdashnumbs = string.gsub(Endashnumbs, "%—", 13)
  anombs = string.gsub(textwithspace, "a", 1)
  bnombs = string.gsub(anombs, "b", 2)
  cnombs = string.gsub(bnombs, "c", 3)
  dnombs = string.gsub(cnombs, "d", 4)
  enombs = string.gsub(dnombs, "e", 5)
  fnombs = string.gsub(enombs, "f", 6)
  gnombs = string.gsub(fnombs, "g", 7)
  hnombs = string.gsub(gnombs, "h", 8)
  inombs = string.gsub(hnombs, "i", 9)
  jnombs = string.gsub(inombs, "j", 10)
  knombs = string.gsub(jnombs, "k", 11)
  lnombs = string.gsub(knombs, "l", 12)
  mnombs = string.gsub(lnombs, "m", 13)
  nnombs = string.gsub(mnombs, "n", 13)
  onombs = string.gsub(nnombs, "o", 14)
  pnombs = string.gsub(onombs, "p", 15)
  qnombs = string.gsub(pnombs, "q", 16)
  rnombs = string.gsub(qnombs, "r", 17)
  snombs = string.gsub(rnombs, "s", 18)
  tnombs = string.gsub(snombs, "t", 19)
  unombs = string.gsub(tnombs, "u", 20)
  vnombs = string.gsub(unombs, "v", 21)
  wnombs = string.gsub(vnombs, "w", 22)
  xnombs = string.gsub(wnombs, "x", 23)
  ynombs = string.gsub(xnombs, "y", 24)
  znombs = string.gsub(ynombs, "z", 25)
  Anombs = string.gsub(znombs, "A", 26)
  Bnombs = string.gsub(Anombs, "B", 27)
  Cnombs = string.gsub(Bnombs, "C", 28)
  Dnombs = string.gsub(Cnombs, "D", 29)
  Enombs = string.gsub(Dnombs, "E", 30)
  Fnombs = string.gsub(Enombs, "F", 31)
  Gnombs = string.gsub(Fnombs, "G", 32)
  Hnombs = string.gsub(Gnombs, "H", 31)
  Inombs = string.gsub(Hnombs, "I", 30)
  Jnombs = string.gsub(Inombs, "J", 29)
  Knombs = string.gsub(Jnombs, "K", 28)
  Lnombs = string.gsub(Knombs, "L", 27)
  Mnombs = string.gsub(Lnombs, "M", 26)
  Nnombs = string.gsub(Mnombs, "N", 25)
  Onombs = string.gsub(Nnombs, "O", 24)
  Pnombs = string.gsub(Onombs, "P", 23)
  Qnombs = string.gsub(Pnombs, "Q", 22)
  Rnombs = string.gsub(Qnombs, "R", 21)
  Snombs = string.gsub(Rnombs, "S", 20)
  Tnombs = string.gsub(Snombs, "T", 19)
  Unombs = string.gsub(Tnombs, "U", 18)
  Vnombs = string.gsub(Unombs, "V", 17)
  Wnombs = string.gsub(Vnombs, "W", 16)
  Xnombs = string.gsub(Wnombs, "X", 15)
  Ynombs = string.gsub(Xnombs, "Y", 14)
  Znombs = string.gsub(Ynombs, "Z", 13)
  Periodnombs = string.gsub(Znombs, "%.", 12)
  Colonnombs = string.gsub(Periodnombs, "%:", 11)
  Semicolonnombs = string.gsub(Colonnombs, "%;", 10)
  Commanombs = string.gsub(Semicolonnombs, "%,", 9)
  Hyphennombs = string.gsub(Commanombs, "%-", 8)
  Exclamationnombs = string.gsub(Hyphennombs, "%!", 7)
  Atnombs = string.gsub(Exclamationnombs, "%@", 6)
  Poundnombs = string.gsub(Atnombs, "%#", 5)
  Dollarnombs = string.gsub(Poundnombs, "%$", 4)
  Percentnombs = string.gsub(Dollarnombs, "%%", 3)
  Carotnombs = string.gsub(Percentnombs, "%^", 2)
  Andnombs = string.gsub(Carotnombs, "%&", 1)
  Starnombs = string.gsub(Andnombs, "%*", 2)
  Parenthesisnombs = string.gsub(Starnombs, "%(", 3)
  Parentheticalnombs = string.gsub(Parenthesisnombs, "%)", 4)
  Equalnombs = string.gsub(Parentheticalnombs, "%=", 5)
  Addnombs = string.gsub(Equalnombs, "%+", 6)
  Apostrophenombs = string.gsub(Addnombs, "%'", 7)
  Accentnombs = string.gsub(Apostrophenombs, "%`", 8)
  Tildenombs = string.gsub(Accentnombs, "%~", 9)
  Slashnombs = string.gsub(Tildenombs, "%/", 10)
  Undernombs = string.gsub(Slashnombs, "%_", 11)
  Bracketnombs = string.gsub(Undernombs, "%[", 12)
  Closebracketnombs = string.gsub(Bracketnombs, "%]", 13)
  Curlynombs = string.gsub(Closebracketnombs, "%{", 114)
  Closecurlynombs = string.gsub(Curlynombs, "%}", 15)
  Lessthannombs = string.gsub(Closecurlynombs, "%<", 16)
  Greaterthannombs = string.gsub(Lessthannombs, "%>", 17)
  Questionnombs = string.gsub(Greaterthannombs, "%?", 18)
  Endashnombs = string.gsub(Questionnombs, "%–", 17)
  Emdashnombs = string.gsub(Endashnombs, "%—", 16)

  crow.input[1].mode('clock')  
  crow.output[2].action = "{to(5,0),to(0,0.025)}"
  crow.output[4].action = "{to(5,0),to(0,0.025)}"
  crow.ii.wsyn.ar_mode(1)
  crow.ii.wsyn.patch(1, 1)
  crow.ii.wsyn.patch(2, 2)
  
function Split(s, delimiter)
  result = {};
  for match in (s..delimiter):gmatch("(.-)"..delimiter) do
    table.insert(result, match);
  end
  return result;
end

lied = Split(Emdashnumbs, "%s")
lieder = Split(Emdashnombs, "%s")
notetrig = sequins.new( {1, 0}, 'next' )
othernotetrig = sequins.new( {1, 0}, 'next' )
withtrig = sequins.new( {1, 0}, 'next' )
with2trig = sequins.new( {1, 0}, 'next' )
with3trig = sequins.new( {1, 0}, 'next' )
with4trig = sequins.new( {1, 0}, 'next' )
notediv = sequins.new( { sequins.new( lieder, 'next' )
            , sequins.every( 2, sequins.new( lied, 'prev' ) )
            }
            , 'next' )
othernotediv = sequins.new( { sequins.new( lied, 'drunk' )
            , sequins.every( 3, sequins.new( lieder, 'next' ) )
            }
            , 'prev' )
withdiv = sequins.new( { sequins.new( lieder, 'prev' )
            , sequins.every( 5, sequins.new( lied, 'drunk' ) )
            }
            , 'drunk' )
with2div = sequins.new( { sequins.new( lied, 'next' )
            , sequins.every( 8, sequins.new( lieder, 'prev' ) )
            }
            , 'next' )
with3div = sequins.new( { sequins.new( lieder, 'drunk' )
            , sequins.every( 13, sequins.new( lied, 'next' ) )
            }
            , 'prev' )
with4div = sequins.new( { sequins.new( lied, 'prev' )
            , sequins.every( 21, sequins.new( lieder, 'drunk' ) )
            }
            , 'drunk' )
notenote = sequins.new( { sequins.new( lieder, 'next' )
            , sequins.every( 34, sequins.new( lied, 'prev' ) )
            }
            , 'next' )
othernotenote = sequins.new( { sequins.new( lied, 'drunk' )
            , sequins.every( 55, sequins.new( lieder, 'next' ) )
            }
            , 'prev' )
withnote = sequins.new( { sequins.new( lieder, 'prev' )
            , sequins.every( 89, sequins.new( lied, 'drunk' ) )
            }
            , 'drunk' )
withsecondnote = sequins.new( { sequins.new( lied, 'next' )
            , sequins.every( 144, sequins.new( lieder, 'prev' ) )
            }
            , 'next' )
withthirdnote = sequins.new( { sequins.new( lieder, 'drunk' )
            , sequins.every( 233, sequins.new( lied, 'next' ) )
            }
            , 'prev' )
withfourthnote = sequins.new( { sequins.new( lied, 'prev' )
            , sequins.every( 377, sequins.new( lieder, 'drunk' ) )
            }
            , 'drunk' )

  splicer_lattice = lattice:new()
  
  note_pattern = splicer_lattice:new_pattern{
    action = function() play_note() end,
    division = notediv()
  }
  othernote_pattern = splicer_lattice:new_pattern{
    action = function() play_other_note() end,
    division = othernotediv()
  }
  with_pattern = splicer_lattice:new_pattern{
    action = function() play_with() end,
    division = withdiv()
  }
  with2_pattern = splicer_lattice:new_pattern{
    action = function() play_with_2() end,
    division = with2div()
  }
  with3_pattern = splicer_lattice:new_pattern{
    action = function() play_with_3() end,
    division = with3div()
  }
  with4_pattern = splicer_lattice:new_pattern{
    action = function() play_with_4() end,
    division = with4div()
  }
  
  -- create a utility pattern for housekeeping
  utility_pattern = splicer_lattice:new_pattern{
    action = function() utility() end,
    division = 1/16
  }
  
  -- start the lattice
  splicer_lattice:start()

end

print("LIED INITIALIZED")

function play_note(v)
  local v = notenote()
  local freq = v/12 - 5
  build_scale() -- by employing build_scale() here, we update the scale
  crow.output[1].scale( note_nums, 12, 1.0 )
  if notetrig() == 1 then
  crow.output[1].volts = freq
  crow.output[2].execute()
  print("playing note")
  local nonuna = MusicUtil.note_num_to_name(v)
  print(nonuna)
  end
end

function play_other_note(v)
  local v = othernotenote()
  local freq = v/12 - 5
  build_scale() -- by employing build_scale() here, we update the scale
  crow.output[3].scale( note_nums, 12, 1.0 )
  if othernotetrig() == 1 then
  crow.output[3].volts = freq
  crow.output[4].execute()
  print("playing other note")
  local otnonuna = MusicUtil.note_num_to_name(v)
  print(otnonuna)
  end
end

function play_with(v)
  local v = withnote()
  local freq = v/12 - 60
  build_scale() -- by employing build_scale() here, we update the scale
  if withtrig() == 1 then
  crow.ii.wsyn.play_voice(1, quantize(freq, note_nums), 1)
  print("playing with first voice")
  local withnonuna = MusicUtil.note_num_to_name(v)
  print(withnonuna)
  end
end

function play_with_2(v)
  local v = withsecondnote()
  local freq = v/12 - 60
  build_scale() -- by employing build_scale() here, we update the scale
  if with2trig() == 1 then
  crow.ii.wsyn.play_voice(2, quantize(freq, note_nums), 1)
  print("playing with second voice")
  local withtwonuna = MusicUtil.note_num_to_name(v)
  print(withtwonuna)
  end
end

function play_with_3(v)
  local v = withthirdnote()
  local freq = v/12 - 60
  build_scale() -- by employing build_scale() here, we update the scale
  if with3trig() == 1 then
  crow.ii.wsyn.play_voice(3, quantize(freq, note_nums), 1)
  print("playing with third voice")
  local withthreenuna = MusicUtil.note_num_to_name(v)
  print(withthreenuna)
  end
end

function play_with_4(v)
  local v = withfourthnote()
  local freq = v/12 - 60
  build_scale() -- by employing build_scale() here, we update the scale
  if with4trig() == 1 then
  crow.ii.wsyn.play_voice(4, quantize(freq, note_nums), 1)
  print("playing with fourth voice")
  local withfournuna = MusicUtil.note_num_to_name(v)
  print(withfournuna)
  end
end

function utility()
  local notediv_current = notediv()
  if (note_pattern.division ~= notediv_current) then
    note_pattern:set_division(notediv_current)
  end
  local othernotediv_current = othernotediv()
  if (othernote_pattern.division ~= othernotediv_current) then
    othernote_pattern:set_division(othernotediv_current)
  end
  local withdiv_current = withdiv()
  if (with_pattern.division ~= withdiv_current) then
    with_pattern:set_division(withdiv_current)
  end
  local with2div_current = with2div()
  if (with2_pattern.division ~= with2div_current) then
    with2_pattern:set_division(with2div_current)
  end
  local with3div_current = with3div()
  if (with3_pattern.division ~= with3div_current) then
    with3_pattern:set_division(with3div_current)
  end
  local with4div_current = with4div()
  if (with4_pattern.division ~= with4div_current) then
    with4_pattern:set_division(with4div_current)
  end
end

--sequins = {}

-- arg1: value table eg{0,2,4,7,9}
-- arg2: behaviour enum:{'next','prev',rand','drunk'} -- n as next#
-- retv: function that generates the next note
function sequins.new( vals, behaviour )
  local ix = 1

  local function generate()
    local newix = 0
    if behaviour == 'next' then
      newix = ix + 1
    elseif behaviour == 'prev' then
      newix = ix - 1
    elseif behaviour == 'rand' then
      newix = math.random(#vals)
    elseif behaviour == 'drunk' then
      newix = ix + math.random(-1,1)
    end
    -- clamp to vals table length
    while newix < 1 do newix = newix + #vals end
    while newix > #vals do newix = newix - #vals end

    local val = vals[newix]

    -- currently we explictly step the index forward in some cases but not others
    -- ideally this would be cleaned up & have a single return point
    if type(val) == 'function' then
      local v, action = val() -- allows nested tables
      if action == 'skip' then
        ix = newix -- nb: doesn't matter if it's before/after recursion
        return generate( vals, behaviour )
      elseif action == 'again' then
        -- ix stays the same!
      else -- was a nested table, but just returned a value (no action)
        ix = newix
      end
      return v
    else -- assume a number
      ix = newix
      return val
    end
  end

  return generate -- named function so we can use recursion inside
end

-- returns a value on everyth call. otherwise instructs the caller to skip it.
function sequins.every( everyth, sequin )
  local e = everyth
  return
    function()
      e = e%everyth +1
      if e == 1 then
        return sequin()
      else
        return 0, 'skip'
      end
    end
end

-- returns count values in a row, stopping the calling sequin from moving forward until count is exhausted
function sequins.count( count, sequin )
  local cou = count
  return
    function()
      cou = cou%count +1
     if cou == 1 then
        print'match'
        return sequin()
      else
        return sequin(), 'again'
      end
    end
end
4 Likes