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:
-
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 ")
-
how to change the output from norns sc engine to crow done! This was way easier than I thought it would be
-
how to quantize output done! crow.output[n].scale
-
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.
-
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.
-
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
- 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