Last active
June 12, 2022 01:53
-
-
Save williamthazard/fab14a897e2501b930ec2c303bec9b44 to your computer and use it in GitHub Desktop.
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
--- krahenLied. | |
input[1].mode('clock') | |
ii.jf.mode(1) | |
tab = function(t,e) | |
for index, value in ipairs(t) do | |
if value == e then return index end | |
end | |
return nil | |
end | |
Lattice, Pattern = {}, {} | |
function Lattice:new(args) | |
local l = setmetatable({}, { __index = Lattice }) | |
local args = args == nil and {} or args | |
l.auto = args.auto == nil and true or args.auto | |
l.meter = args.meter == nil and 4 or args.meter | |
l.ppqn = args.ppqn == nil and 96 or args.ppqn | |
l.enabled = false | |
l.transport = 0 | |
l.superclock_id = nil | |
l.pattern_id_counter = 100 | |
l.patterns = {} | |
return l | |
end | |
function Lattice:start() | |
self.enabled = true | |
if self.auto and self.superclock_id == nil then | |
self.superclock_id = clock.run(self.auto_pulse, self) | |
end | |
end | |
function Lattice:reset() | |
self:stop() | |
if self.superclock_id ~= nil then | |
clock.cancel(self.superclock_id) | |
self.superclock_id = nil | |
end | |
for i, pattern in pairs(self.patterns) do | |
pattern.phase = pattern.division * self.ppqn * self.meter | |
end | |
self.transport = 0 | |
params:set("clock_reset",1) | |
end | |
function Lattice:hard_restart() | |
self:reset() | |
self:start() | |
end | |
function Lattice:stop() | |
self.enabled = false | |
end | |
function Lattice:toggle() | |
self.enabled = not self.enabled | |
end | |
function Lattice:destroy() | |
self:stop() | |
if self.superclock_id ~= nil then | |
clock.cancel(self.superclock_id) | |
end | |
self.patterns = {} | |
end | |
function Lattice:set_meter(meter) | |
self.meter = meter | |
end | |
function Lattice.auto_pulse(s) | |
while true do | |
s:pulse() | |
clock.sync(1/s.ppqn) | |
end | |
end | |
function Lattice:pulse() | |
if self.enabled then | |
local ppm = self.ppqn * self.meter | |
for id, pattern in pairs(self.patterns) do | |
if pattern.enabled then | |
pattern.phase = pattern.phase + 1 | |
if pattern.phase > (pattern.division * ppm) then | |
pattern.phase = pattern.phase - (pattern.division * ppm) | |
pattern.action(self.transport) | |
end | |
elseif pattern.flag then | |
self.patterns[pattern.id] = nil | |
end | |
end | |
self.transport = self.transport + 1 | |
end | |
end | |
function Lattice:new_pattern(args) | |
self.pattern_id_counter = self.pattern_id_counter + 1 | |
local args = args == nil and {} or args | |
args.id = self.pattern_id_counter | |
args.action = args.action == nil and function(t) return end or args.action | |
args.division = args.division == nil and 1/4 or args.division | |
args.enabled = args.enabled == nil and true or args.enabled | |
args.phase = args.division * self.ppqn * self.meter | |
local pattern = Pattern:new(args) | |
self.patterns[self.pattern_id_counter] = pattern | |
return pattern | |
end | |
function Pattern:new(args) | |
local p = setmetatable({}, { __index = Pattern }) | |
p.id = args.id | |
p.division = args.division | |
p.action = args.action | |
p.enabled = args.enabled | |
p.phase = args.phase | |
p.flag = false | |
return p | |
end | |
function Pattern:start() | |
self.enabled = true | |
end | |
function Pattern:stop() | |
self.enabled = false | |
end | |
function Pattern:toggle() | |
self.enabled = not self.enabled | |
end | |
function Pattern:destroy() | |
self.enabled = false | |
self.flag = true | |
end | |
function Pattern:set_division(n) | |
self.division = n | |
end | |
function Pattern:set_action(fn) | |
self.action = fn | |
end | |
text = "aaaaaaaaaaaaaaaaaa" | |
function remap(ascii) | |
local offset | |
if ascii <= 32 then offset = 0 | |
elseif ascii > 32 and ascii <= 64 then offset = -32 | |
elseif ascii > 64 and ascii <= 96 then offset = -64 | |
elseif ascii > 96 and ascii <= 128 then offset = -96 | |
elseif ascii > 128 and ascii <= 160 then offset = -128 | |
elseif ascii > 160 and ascii <= 192 then offset = -160 | |
elseif ascii > 192 and ascii <= 224 then offset = -192 | |
elseif ascii > 224 and ascii <= 255 then offset = -224 | |
end | |
return ascii + offset | |
end | |
function processString(s) | |
local tempScalar = {} | |
for i = 1, #s do | |
table.insert(tempScalar,remap(s:byte(i))) | |
end | |
return tempScalar | |
end | |
function jfmap(ascii) | |
local map | |
if ascii <= 51 then map = 1 | |
elseif ascii > 51 and ascii <= 102 then map = 2 | |
elseif ascii > 102 and ascii <= 153 then map = 3 | |
elseif ascii > 153 and ascii <= 204 then map = 4 | |
elseif ascii > 204 and ascii <= 255 then map = 5 | |
end | |
return map | |
end | |
function jfscaling (j) | |
local tempScalar = {} | |
for i = 1, #j do | |
table.insert(tempScalar,jfmap(j:byte(i))) | |
end | |
return tempScalar | |
end | |
s = sequins(processString(text)) | |
j = sequins(jfscaling(text)) | |
function init() | |
liedclock = Lattice:new{ | |
auto = true, | |
meter = 4, | |
ppqn = 4 | |
} | |
notes_lattice = liedclock:new_pattern{ | |
action = function(t) notes_event() end, | |
division = 1, | |
enabled = true | |
} | |
other_lattice = liedclock:new_pattern{ | |
action = function(t) other_event() end, | |
division = 1, | |
enabled = true | |
} | |
jfa_lattice = liedclock:new_pattern{ | |
action = function(t) jfa_event() end, | |
division = 1/4, | |
enabled = true | |
} | |
jfb_lattice = liedclock:new_pattern{ | |
action = function(t) jfb_event() end, | |
division = 1/4, | |
enabled = true | |
} | |
jfc_lattice = liedclock:new_pattern{ | |
action = function(t) jfc_event() end, | |
division = 1/4, | |
enabled = true | |
} | |
jfd_lattice = liedclock:new_pattern{ | |
action = function(t) jfd_event() end, | |
division = 1/4, | |
enabled = true | |
} | |
liedclock:start() | |
end | |
function notes_event() | |
s:settable(processString(text)) | |
notes_pitch = s | |
notes_time = s:step(2) | |
local slew_step = s:step(3) | |
local slew = slew_step()/300 | |
local notes_slope = { to(5,dyn{attack=1}/20), to(0,dyn{release=1}/20) } | |
notes_lattice:set_division(notes_time()/32) | |
local v = notes_pitch() | |
local freq = v/12 | |
output[1].volts = freq | |
output[2].action = notes_slope | |
output[2].dyn.attack = s:step(4)() | |
output[2].dyn.release = s:step(5)() | |
output[1].slew = slew | |
output[2]() | |
end | |
function other_event() | |
s:settable(processString(text)) | |
other_pitch = s:step(6) | |
other_time = s:step(7) | |
local slew_step = s:step(8) | |
local slew = slew_step()/300 | |
local other_slope = { to(5,dyn{attack=1}/20), to(0,dyn{release=1}/20) } | |
other_lattice:set_division(other_time()/32) | |
local v = other_pitch() | |
local freq = v/12 | |
output[3].volts = freq | |
output[4].action = other_slope | |
output[4].dyn.attack = s:step(9)() | |
output[4].dyn.release = s:step(10)() | |
output[3].slew = slew | |
output[4]() | |
end | |
function jfa_event() | |
s:settable(processString(text)) | |
j:settable(jfscaling(text)) | |
jfa_pitch = s:step(11) | |
jfa_time = s:step(12) | |
jfa_lattice:set_division(jfa_time()/32) | |
local v = jfa_pitch() | |
local freq = v/12 | |
local level = j:step(13)() | |
ii.jf.play_note(freq, level) | |
end | |
function jfb_event() | |
s:settable(processString(text)) | |
j:settable(jfscaling(text)) | |
jfb_pitch = s:step(14) | |
jfb_time = s:step(15) | |
jfb_lattice:set_division(jfb_time()/32) | |
local v = jfb_pitch() | |
local freq = v/12 | |
local level = j:step(16)() | |
ii.jf.play_note(freq, level) | |
end | |
function jfc_event() | |
s:settable(processString(text)) | |
j:settable(jfscaling(text)) | |
jfc_pitch = s:step(17) | |
jfc_time = s:step(18) | |
jfc_lattice:set_division(jfc_time()/32) | |
local v = jfc_pitch() | |
local freq = v/12 | |
local level = j:step(19)() | |
ii.jf.play_note(freq, level) | |
end | |
function jfd_event() | |
s:settable(processString(text)) | |
j:settable(jfscaling(text)) | |
jfd_pitch = s:step(20) | |
jfd_time = s:step(21) | |
jfd_lattice:set_division(jfd_time()/32) | |
local v = jfd_pitch() | |
local freq = v/12 | |
local level = j:step(22)() | |
ii.jf.play_note(freq, level) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Apologies for delay. Here's some thoughts & review. From top to bottom:
lines 2&3: should probably be in the
init
function.line 4:
tab
function is unused and the name is confusing. i guess it should beif_contains
or something?I've never used Lattice on crow, so I can't really say whether or not your solution is a good one. Seems to me you could probably work with just the
clock
library, rather than adding the whole Lattice framework on top.line 127:
remap
function seems like it's equivalent to justreturn n % 32
? you're just wrapping everything into the (0..32) range i think.line 147:
jfmap
is justreturn math.floor(n / 51)+1
lines 140+157:
processString
&jfscaling
can be the same function and just pass injfmap
orremap
as a function argument to be applied.i have some misgivings about the way the
jf*_event
functions & lattice patterns are copy+paste-y, but it's unfortunately more than trivial to refactor this.that's my general code review for making it a little easier to work on the script.
one thing that i'm very confused about is that you're calling the
settable
method on thes
sequins every time there's an event.processString
is a pure function, andtext
is only changed manually it seems. i'd just make a simple functiontext
which takes a single string argument & callssettable
on boths
andj
. this will massively reduce the CPU usage assettable
and your string conversion functions are both expensive (and churn a bunch of RAM due to the gradual table buildup).ok! one big thing / misunderstanding i'm seeing. at (for eg) line 274ish. you're saving the result of
s:step(..)
into a variable, then doing it again with a different step value.firstly,
s:step()
returns the sequins table itself. it seems from your code that you're assuming this.secondly though, the
sequins
object is a mutable data-structure, so your variablesjfd_pitch
andjfd_time
are in fact equal tos
itself. what you probably want to do is set the step value and pull a value from the sequins into the named param. something like:ok, finally this leads me to the issue where
w/
is crashing the system. there's nothing in your code that should really be causing issues, but just to double-check it is w/ causing the problem (and not general system stability), try changingjfd_event
's call toii.jf.play_note
withii.wsyn.play_note
and leave everything else the same. this should work directly, and if crow+jf are stable, it's very likely that crow+w/ should be as well.if you run into issues by just changing that one line, i'd:
^^c
ii.wsyn.play_note(0,3)
messages (or other similar values) directly from druidthis should prove that the system is working, or help reveal instabilities in the system that are unrelated to the script you're running. when testing for stability i often use the
up
-arrow thenenter
in rapid succession to generate a bunch of notes (ie execute the same line of code again and again). often an unstable system will work fine with isolated messages, but repeatedly bashing messages into theii
system will reveal faults.you might need an i2c "bus board" to provide some extra pullup current. see the thread on lines for more info on that.
my primary concern (and the reason i went to the trouble of the code review above) is that the script is putting crow in a state where it's operating near the edge of its capacity & thus revealing instability in the Lua VM. of course we have been trying to improve this kind of reliability, but the system is not perfect. i think the above changes (especially not re-running the text converter in every event) will make a massive difference.
finally since you asked & the docs are lacking, the
delay
technique ininit
is like so (however, you are correct that it's not relevant here as you only have the singleii
message occuring at startup, so treat this just as an fyi):so
delay
is just a function that will call another function (it's first argument) at some time in the future. i'm just using an anonymous function inline here, so you don't need to separate the code from the caller. of course you can name the functions if it makes more sense though!nothing here is a hard & fast solution, but this should be plenty of territory to explore and improve your script!