Last active
January 26, 2023 03:31
-
-
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!
This file contains hidden or 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
-- 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