Skip to content

Instantly share code, notes, and snippets.

@goonzoid
Last active January 26, 2023 03:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goonzoid/afaaee42dddcc0ceab65c27a05dccb3f to your computer and use it in GitHub Desktop.
Save goonzoid/afaaee42dddcc0ceab65c27a05dccb3f to your computer and use it in GitHub Desktop.
First attempt at porting https://gist.github.com/schollz/e2cc49425d54336b422e144e7eeb34cc to crow. WARNING: this may lock up your Crow / Just Friends! Needs work still!
-- melody generator
-- based on https://gist.github.com/schollz/e2cc49425d54336b422e144e7eeb34cc
function init()
ii.jf.mode(1)
local chords={"I","vi","IV","iii"} -- change to any chords
local move_left=6 -- change to 0-12
local move_right=6 -- change to 0-12
local stay_on_chord=0.95 -- change to 0-1
play(chords,1,move_left,move_right,stay_on_chord)
end
function clamp(v, min, max)
if v < min then return min
elseif v > max then return max
else return v end
end
function play(chord_structure,factor,move_left,move_right,stay_scale)
stay_scale=clamp(stay_scale,0,1)
local notes_to_play={}
local chords={}
for i,v in ipairs(chord_structure) do
local scale=generate_scale(12,1,8) -- select scale here
local chord_notes=generate_chord_roman(12,1,v)
local notes_in_chord={}
for _,u in ipairs(chord_notes) do
notes_in_chord[u]=true
for j=1,8 do
notes_in_chord[u+(12*j)]=true
end
end
local note_start=4
print(note_start) -- this works, it prints 4!
for jj=1,4*factor do
local notes_to_choose={}
for _,note in ipairs(scale) do
if note > note_start - move_left and note < note_start + move_right then
table.insert(notes_to_choose,note)
end
end
local weights={}
local scale_size=#notes_to_choose
for i,note in ipairs(notes_to_choose) do
weights[i]=notes_in_chord[note]~=nil and scale_size or scale_size*(1-stay_scale)
end
local note_next=choose_with_weights(notes_to_choose,weights)
table.insert(notes_to_play,note_next)
note_start=note_next
end
end
local notei=0
local note_last=0
clock.run(function()
while true do
clock.sync(1/factor)
notei=(notei)%#notes_to_play+1
local note_next=notes_to_play[notei]
if note_next~=note_last then
print(note_next)
ii.jf.play_note(note_next/12, 2)
end
note_last=note_next
end
end)
end
function choose_with_weights(choices,weights)
local totalWeight=0
for _,weight in pairs(weights) do
totalWeight=totalWeight+weight
end
local rand=math.random()*totalWeight
local choice=nil
for i,weight in pairs(weights) do
if rand<weight then
choice=choices[i]
break
else
rand=rand-weight
end
end
return choice
end
-- adapted from norns/musicutil.lua:
SCALES = {
{name = "Dorian", intervals = {0, 2, 3, 5, 7, 9, 10, 12}, chords = {{14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19}, {1, 2, 3, 4, 5}, {1, 2, 4, 8, 9, 10, 11, 14, 15}, {14, 15, 17, 19, 21, 22}, {24, 26}, {1, 2, 3, 4, 5, 6, 7, 14}, {14, 15, 17, 18, 19, 20, 21, 22, 23}}}
}
CHORDS = {
{name = "Maj", intervals = {0, 4, 7}},
{name = "Maj6", intervals = {0, 4, 7, 9}},
{name = "Maj7", intervals = {0, 4, 7, 11}},
{name = "Maj69", intervals = {0, 4, 7, 9, 14}},
{name = "Maj9", intervals = {0, 4, 7, 11, 14}},
{name = "Maj11", intervals = {0, 4, 7, 11, 14, 17}},
{name = "Maj13", intervals = {0, 4, 7, 11, 14, 17, 21}},
{name = "Dom7", intervals = {0, 4, 7, 10}},
{name = "Ninth", intervals = {0, 4, 7, 10, 14}},
{name = "Eleventh", intervals = {0, 4, 7, 10, 14, 17}},
{name = "Thirteenth", intervals = {0, 4, 7, 10, 14, 17, 21}},
{name = "Aug", intervals = {0, 4, 8}},
{name = "Aug7", intervals = {0, 4, 8, 10}},
{name = "Sus4", intervals = {0, 5, 7}},
{name = "SeventhSus4", intervals = {0, 5, 7, 10}},
{name = "MinMaj7", intervals = {0, 3, 7, 11}},
{name = "Min", intervals = {0, 3, 7}},
{name = "Min6", intervals = {0, 3, 7, 9}},
{name = "Min7", intervals = {0, 3, 7, 10}},
{name = "Min69", intervals = {0, 3, 7, 9, 14}},
{name = "Min9", intervals = {0, 3, 7, 10, 14}},
{name = "Min11", intervals = {0, 3, 7, 10, 14, 17}},
{name = "Min13", intervals = {0, 3, 7, 10, 14, 17, 21}},
{name = "Dim", intervals = {0, 3, 6}},
{name = "Dim7", intervals = {0, 3, 6, 9}},
{name = "HalfDim7", intervals = {0, 3, 6, 10}}
}
function generate_scale(root_num, scale_type, octaves)
local scale_data = lookup_data(SCALES, scale_type)
local length = octaves * #scale_data.intervals -- (math.floor(octaves) - 1) -- TODO round?
return generate_scale_array(root_num, scale_data, length)
end
function generate_scale_array(root_num, scale_data, length)
local out_array = {}
local scale_len = #scale_data.intervals
local note_num
local i = 0
while #out_array < length do
if i > 0 and i % scale_len == 0 then
root_num = root_num + scale_data.intervals[scale_len]
else
note_num = root_num + scale_data.intervals[i % scale_len + 1]
if note_num > 127 then break
else table.insert(out_array, note_num) end
end
i = i + 1
end
return out_array
end
function generate_chord_roman(root_num, scale_type, rct)
local scale_data = lookup_data(SCALES, scale_type)
local degree_string, augdim_string, added_string, bass_string, inv_string =
string.match(rct, "([ivxIVX]+)([+*]?)([0-9]*)-?([0-9]?)([bcdefg]?)")
local d = string.lower(degree_string)
local major = degree_string ~= d
local augmented = augdim_string == "+"
local diminished = augdim_string == "*"
local seventh = added_string == "7"
local chord_type = nil
if major then
if augmented then
if seventh then
chord_type = "Aug7"
else
chord_type = "Aug"
end
elseif diminished then
if seventh then
chord_type = "Dim7"
else
chord_type = "Dim"
end
elseif added_string == "6" then
if bass_string == "9" then
chord_type = "Maj69"
else
chord_type = "Maj6"
end
elseif seventh then
chord_type = "Maj7"
elseif added_string == "9" then
chord_type = "Maj9"
elseif added_string == "11" then
chord_type = "Maj11"
elseif added_string == "13" then
chord_type = "Maj13"
else
chord_type = "Maj"
end
else
if augmented then
if seventh then
chord_type = "Aug7"
else
chord_type = "Aug"
end
elseif diminished then
if seventh then
chord_type = "Dim7"
else
chord_type = "Dim"
end
elseif added_string == "6" then
if bass_string == "9" then
chord_type = "Min69"
else
chord_type = "Min6"
end
elseif seventh then
chord_type = "Min7"
elseif added_string == "9" then
chord_type = "Min9"
elseif added_string == "11" then
chord_type = "Min11"
elseif added_string == "13" then
chord_type = "Min13"
else
chord_type = "Min"
end
end
local degree = nil
for i,v in pairs({"i","ii","iii","iv","v","vi","vii"}) do
if(v == d) then
degree = i
break
end
end
local inv = string.lower(inv_string)
local inversion = 0
for i,v in pairs({"b","c","d","e","f","g"}) do
if(v == inv) then
inversion = i
break
end
end
local degree_note = root_num + scale_data.intervals[degree]
return generate_chord(degree_note, chord_type, inversion)
end
function generate_chord(root_num, chord_type, inversion)
if type(root_num) ~= "number" or root_num < 0 or root_num > 127 then return nil end
local chord_data = lookup_data(CHORDS, chord_type)
local out_array = {}
for i = 1, #chord_data.intervals do
local note_num = root_num + chord_data.intervals[i]
if note_num > 127 then break end
table.insert(out_array, note_num)
end
for i = 1, clamp(inversion, 0, #out_array - 1) do
local head = table.remove(out_array, 1)
table.insert(out_array, head + 12)
end
return out_array
end
function lookup_data(lookup_table, search)
if type(search) == "string" then
for i = 1, #lookup_table do
if lookup_table[i].name == search then
search = i
break
end
end
end
return lookup_table[search]
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment