Merge branch 'develop' into feature/imp-99-add-discussions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This commit is contained in:
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -30,5 +30,5 @@
|
||||
"files.associations": {
|
||||
"*.conf": "bitbake",
|
||||
"*.inc": "bitbake"
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
642
inc/audio/audio.generator_model.lua
Normal file
642
inc/audio/audio.generator_model.lua
Normal file
@@ -0,0 +1,642 @@
|
||||
{
|
||||
model = {
|
||||
["...|..."] = {
|
||||
next = {
|
||||
["..."] = 0.71111111111111,
|
||||
["A-4"] = 0.0074074074074074,
|
||||
["B-4"] = 0.0037037037037037,
|
||||
["C-3"] = 0.011111111111111,
|
||||
["C-4"] = 0.037037037037037,
|
||||
["C-5"] = 0.11111111111111,
|
||||
["C-6"] = 0.0037037037037037,
|
||||
["D-4"] = 0.011111111111111,
|
||||
["D-5"] = 0.018518518518519,
|
||||
["E-4"] = 0.0074074074074074,
|
||||
["E-5"] = 0.025925925925926,
|
||||
["F-5"] = 0.0074074074074074,
|
||||
["G-3"] = 0.0037037037037037,
|
||||
["G-4"] = 0.022222222222222,
|
||||
["G-5"] = 0.018518518518519
|
||||
},
|
||||
total = 270
|
||||
},
|
||||
["...|A-4"] = {
|
||||
next = {
|
||||
["..."] = 0.9,
|
||||
["C-5"] = 0.1
|
||||
},
|
||||
total = 10
|
||||
},
|
||||
["...|A-5"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 8
|
||||
},
|
||||
["...|B-4"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["...|B-5"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 5
|
||||
},
|
||||
["...|C-3"] = {
|
||||
next = {
|
||||
["..."] = 0.66666666666667,
|
||||
["C-5"] = 0.33333333333333
|
||||
},
|
||||
total = 3
|
||||
},
|
||||
["...|C-4"] = {
|
||||
next = {
|
||||
["..."] = 0.875,
|
||||
["D-4"] = 0.083333333333333,
|
||||
["E-4"] = 0.041666666666667
|
||||
},
|
||||
total = 24
|
||||
},
|
||||
["...|C-5"] = {
|
||||
next = {
|
||||
["..."] = 0.73333333333333,
|
||||
["B-4"] = 0.033333333333333,
|
||||
["C-5"] = 0.066666666666667,
|
||||
["D-5"] = 0.13333333333333,
|
||||
["E-5"] = 0.033333333333333
|
||||
},
|
||||
total = 60
|
||||
},
|
||||
["...|C-6"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["...|D-4"] = {
|
||||
next = {
|
||||
["..."] = 0.92857142857143,
|
||||
["D-4"] = 0.071428571428571
|
||||
},
|
||||
total = 14
|
||||
},
|
||||
["...|D-5"] = {
|
||||
next = {
|
||||
["..."] = 0.80645161290323,
|
||||
["C-5"] = 0.032258064516129,
|
||||
["D-5"] = 0.032258064516129,
|
||||
["E-5"] = 0.12903225806452
|
||||
},
|
||||
total = 31
|
||||
},
|
||||
["...|D-6"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 3
|
||||
},
|
||||
["...|E-4"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 19
|
||||
},
|
||||
["...|E-5"] = {
|
||||
next = {
|
||||
["..."] = 0.77777777777778,
|
||||
["C-5"] = 0.022222222222222,
|
||||
["D-5"] = 0.13333333333333,
|
||||
["F-5"] = 0.066666666666667
|
||||
},
|
||||
total = 45
|
||||
},
|
||||
["...|F-3"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 3
|
||||
},
|
||||
["...|F-4"] = {
|
||||
next = {
|
||||
["..."] = 0.8,
|
||||
["D-4"] = 0.1,
|
||||
["F-4"] = 0.1
|
||||
},
|
||||
total = 10
|
||||
},
|
||||
["...|F-5"] = {
|
||||
next = {
|
||||
["..."] = 0.66666666666667,
|
||||
["D-5"] = 0.066666666666667,
|
||||
["E-5"] = 0.066666666666667,
|
||||
["G-5"] = 0.2
|
||||
},
|
||||
total = 15
|
||||
},
|
||||
["...|G-3"] = {
|
||||
next = {
|
||||
["..."] = 0.8,
|
||||
["G-5"] = 0.2
|
||||
},
|
||||
total = 5
|
||||
},
|
||||
["...|G-4"] = {
|
||||
next = {
|
||||
["..."] = 0.95652173913043,
|
||||
["E-4"] = 0.043478260869565
|
||||
},
|
||||
total = 23
|
||||
},
|
||||
["...|G-5"] = {
|
||||
next = {
|
||||
["..."] = 0.875,
|
||||
["A-5"] = 0.0625,
|
||||
["E-5"] = 0.0625
|
||||
},
|
||||
total = 16
|
||||
},
|
||||
["...|G-6"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["A-4|..."] = {
|
||||
next = {
|
||||
["..."] = 0.55555555555556,
|
||||
["C-5"] = 0.33333333333333,
|
||||
["D-5"] = 0.11111111111111
|
||||
},
|
||||
total = 9
|
||||
},
|
||||
["A-4|B-4"] = {
|
||||
next = {
|
||||
["C-5"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["A-4|C-5"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["A-4|G-4"] = {
|
||||
next = {
|
||||
["F-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["A-5|..."] = {
|
||||
next = {
|
||||
["..."] = 0.2,
|
||||
["B-5"] = 0.1,
|
||||
["E-4"] = 0.1,
|
||||
["E-5"] = 0.4,
|
||||
["F-5"] = 0.1,
|
||||
["G-5"] = 0.1
|
||||
},
|
||||
total = 10
|
||||
},
|
||||
["A-5|G-5"] = {
|
||||
next = {
|
||||
["..."] = 0.33333333333333,
|
||||
["A-5"] = 0.66666666666667
|
||||
},
|
||||
total = 3
|
||||
},
|
||||
["B-4|..."] = {
|
||||
next = {
|
||||
["A-4"] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["B-4|A-4"] = {
|
||||
next = {
|
||||
["G-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["B-4|C-5"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["B-5|..."] = {
|
||||
next = {
|
||||
["..."] = 0.2,
|
||||
["A-5"] = 0.2,
|
||||
["D-6"] = 0.4,
|
||||
["G-5"] = 0.2
|
||||
},
|
||||
total = 5
|
||||
},
|
||||
["C-3|..."] = {
|
||||
next = {
|
||||
["C-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["C-3|C-5"] = {
|
||||
next = {
|
||||
["..."] = 0.33333333333333,
|
||||
["C-3"] = 0.66666666666667
|
||||
},
|
||||
total = 3
|
||||
},
|
||||
["C-4|..."] = {
|
||||
next = {
|
||||
["..."] = 0.5,
|
||||
["D-4"] = 0.125,
|
||||
["E-4"] = 0.041666666666667,
|
||||
["F-3"] = 0.041666666666667,
|
||||
["G-3"] = 0.16666666666667,
|
||||
["G-4"] = 0.083333333333333,
|
||||
["G-5"] = 0.041666666666667
|
||||
},
|
||||
total = 24
|
||||
},
|
||||
["C-4|D-4"] = {
|
||||
next = {
|
||||
["E-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["C-4|E-4"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["C-5|..."] = {
|
||||
next = {
|
||||
["..."] = 0.40677966101695,
|
||||
["A-4"] = 0.067796610169492,
|
||||
["C-5"] = 0.050847457627119,
|
||||
["D-5"] = 0.20338983050847,
|
||||
["E-5"] = 0.15254237288136,
|
||||
["F-5"] = 0.016949152542373,
|
||||
["G-4"] = 0.10169491525424
|
||||
},
|
||||
total = 59
|
||||
},
|
||||
["C-5|B-4"] = {
|
||||
next = {
|
||||
["A-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["C-5|C-3"] = {
|
||||
next = {
|
||||
["C-5"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["C-5|C-5"] = {
|
||||
next = {
|
||||
["..."] = 0.8,
|
||||
["C-5"] = 0.2
|
||||
},
|
||||
total = 5
|
||||
},
|
||||
["C-5|D-5"] = {
|
||||
next = {
|
||||
["..."] = 0.3,
|
||||
["C-5"] = 0.2,
|
||||
["D-5"] = 0.1,
|
||||
["E-5"] = 0.4
|
||||
},
|
||||
total = 10
|
||||
},
|
||||
["C-5|E-5"] = {
|
||||
next = {
|
||||
["..."] = 0.33333333333333,
|
||||
["D-5"] = 0.33333333333333,
|
||||
["G-5"] = 0.33333333333333
|
||||
},
|
||||
total = 3
|
||||
},
|
||||
["C-6|..."] = {
|
||||
next = {
|
||||
["A-5"] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["D-4|..."] = {
|
||||
next = {
|
||||
["..."] = 0.26666666666667,
|
||||
["A-5"] = 0.066666666666667,
|
||||
["C-4"] = 0.2,
|
||||
["E-4"] = 0.4,
|
||||
["F-3"] = 0.066666666666667
|
||||
},
|
||||
total = 15
|
||||
},
|
||||
["D-4|C-4"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["D-4|D-4"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["D-4|E-4"] = {
|
||||
next = {
|
||||
["F-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["D-5|..."] = {
|
||||
next = {
|
||||
["..."] = 0.2258064516129,
|
||||
["A-4"] = 0.032258064516129,
|
||||
["A-5"] = 0.032258064516129,
|
||||
["C-5"] = 0.2258064516129,
|
||||
["E-5"] = 0.29032258064516,
|
||||
["F-5"] = 0.096774193548387,
|
||||
["G-5"] = 0.096774193548387
|
||||
},
|
||||
total = 31
|
||||
},
|
||||
["D-5|C-5"] = {
|
||||
next = {
|
||||
["..."] = 0.77777777777778,
|
||||
["D-5"] = 0.22222222222222
|
||||
},
|
||||
total = 9
|
||||
},
|
||||
["D-5|D-5"] = {
|
||||
next = {
|
||||
["..."] = 0.5,
|
||||
["E-5"] = 0.5
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["D-5|E-5"] = {
|
||||
next = {
|
||||
["..."] = 0.33333333333333,
|
||||
["E-5"] = 0.11111111111111,
|
||||
["F-5"] = 0.55555555555556
|
||||
},
|
||||
total = 9
|
||||
},
|
||||
["D-5|F-5"] = {
|
||||
next = {
|
||||
["E-5"] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["D-6|..."] = {
|
||||
next = {
|
||||
["B-5"] = 0.66666666666667,
|
||||
["G-6"] = 0.33333333333333
|
||||
},
|
||||
total = 3
|
||||
},
|
||||
["E-4|..."] = {
|
||||
next = {
|
||||
["..."] = 0.19047619047619,
|
||||
["B-5"] = 0.047619047619048,
|
||||
["C-4"] = 0.14285714285714,
|
||||
["D-4"] = 0.23809523809524,
|
||||
["F-4"] = 0.19047619047619,
|
||||
["G-4"] = 0.19047619047619
|
||||
},
|
||||
total = 21
|
||||
},
|
||||
["E-4|D-4"] = {
|
||||
next = {
|
||||
["C-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["E-4|F-4"] = {
|
||||
next = {
|
||||
["G-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["E-5|..."] = {
|
||||
next = {
|
||||
["..."] = 0.18604651162791,
|
||||
["A-5"] = 0.046511627906977,
|
||||
["C-5"] = 0.27906976744186,
|
||||
["D-5"] = 0.2093023255814,
|
||||
["E-5"] = 0.069767441860465,
|
||||
["F-5"] = 0.093023255813953,
|
||||
["G-4"] = 0.023255813953488,
|
||||
["G-5"] = 0.093023255813953
|
||||
},
|
||||
total = 43
|
||||
},
|
||||
["E-5|C-5"] = {
|
||||
next = {
|
||||
["E-5"] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["E-5|D-5"] = {
|
||||
next = {
|
||||
["..."] = 0.125,
|
||||
["C-5"] = 0.75,
|
||||
["F-5"] = 0.125
|
||||
},
|
||||
total = 8
|
||||
},
|
||||
["E-5|E-5"] = {
|
||||
next = {
|
||||
["F-5"] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["E-5|F-5"] = {
|
||||
next = {
|
||||
["..."] = 0.5,
|
||||
["E-5"] = 0.4,
|
||||
["G-5"] = 0.1
|
||||
},
|
||||
total = 10
|
||||
},
|
||||
["E-5|G-5"] = {
|
||||
next = {
|
||||
["..."] = 0.5,
|
||||
["F-5"] = 0.5
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["F-3|..."] = {
|
||||
next = {
|
||||
["C-4"] = 1
|
||||
},
|
||||
total = 3
|
||||
},
|
||||
["F-4|..."] = {
|
||||
next = {
|
||||
["D-4"] = 0.11111111111111,
|
||||
["E-4"] = 0.44444444444444,
|
||||
["G-4"] = 0.44444444444444
|
||||
},
|
||||
total = 9
|
||||
},
|
||||
["F-4|D-4"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["F-4|E-4"] = {
|
||||
next = {
|
||||
["D-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["F-4|F-4"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["F-4|G-4"] = {
|
||||
next = {
|
||||
["A-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["F-5|..."] = {
|
||||
next = {
|
||||
["..."] = 0.2,
|
||||
["A-5"] = 0.066666666666667,
|
||||
["D-5"] = 0.2,
|
||||
["E-5"] = 0.46666666666667,
|
||||
["G-5"] = 0.066666666666667
|
||||
},
|
||||
total = 15
|
||||
},
|
||||
["F-5|A-5"] = {
|
||||
next = {
|
||||
["G-5"] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["F-5|D-5"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["F-5|E-5"] = {
|
||||
next = {
|
||||
["..."] = 0.5,
|
||||
["D-5"] = 0.16666666666667,
|
||||
["F-5"] = 0.16666666666667,
|
||||
["G-5"] = 0.16666666666667
|
||||
},
|
||||
total = 6
|
||||
},
|
||||
["F-5|G-5"] = {
|
||||
next = {
|
||||
["..."] = 0.75,
|
||||
["A-5"] = 0.25
|
||||
},
|
||||
total = 4
|
||||
},
|
||||
["G-3|..."] = {
|
||||
next = {
|
||||
["C-4"] = 0.5,
|
||||
["D-4"] = 0.25,
|
||||
["F-3"] = 0.25
|
||||
},
|
||||
total = 4
|
||||
},
|
||||
["G-3|G-5"] = {
|
||||
next = {
|
||||
["..."] = 0.5,
|
||||
["G-3"] = 0.5
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["G-4|..."] = {
|
||||
next = {
|
||||
["..."] = 0.090909090909091,
|
||||
["A-4"] = 0.090909090909091,
|
||||
["C-4"] = 0.045454545454545,
|
||||
["C-5"] = 0.18181818181818,
|
||||
["D-5"] = 0.045454545454545,
|
||||
["E-4"] = 0.22727272727273,
|
||||
["E-5"] = 0.045454545454545,
|
||||
["F-4"] = 0.27272727272727
|
||||
},
|
||||
total = 22
|
||||
},
|
||||
["G-4|A-4"] = {
|
||||
next = {
|
||||
["B-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["G-4|E-4"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["G-4|F-4"] = {
|
||||
next = {
|
||||
["E-4"] = 1
|
||||
},
|
||||
total = 2
|
||||
},
|
||||
["G-5|..."] = {
|
||||
next = {
|
||||
["..."] = 0.35,
|
||||
["A-5"] = 0.05,
|
||||
["B-5"] = 0.05,
|
||||
["C-5"] = 0.05,
|
||||
["D-4"] = 0.05,
|
||||
["E-5"] = 0.25,
|
||||
["F-5"] = 0.2
|
||||
},
|
||||
total = 20
|
||||
},
|
||||
["G-5|A-5"] = {
|
||||
next = {
|
||||
["..."] = 0.5,
|
||||
["G-5"] = 0.5
|
||||
},
|
||||
total = 4
|
||||
},
|
||||
["G-5|E-5"] = {
|
||||
next = {
|
||||
["..."] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["G-5|F-5"] = {
|
||||
next = {
|
||||
["A-5"] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["G-5|G-3"] = {
|
||||
next = {
|
||||
["G-5"] = 1
|
||||
},
|
||||
total = 1
|
||||
},
|
||||
["G-6|..."] = {
|
||||
next = {
|
||||
["D-6"] = 1
|
||||
},
|
||||
total = 1
|
||||
}
|
||||
},
|
||||
order = 2
|
||||
}
|
||||
@@ -162,3 +162,43 @@ Songs.custom_song = {
|
||||
}, 130)
|
||||
}
|
||||
]]
|
||||
|
||||
function generate_sequence(model_data, length)
|
||||
local order = model.order
|
||||
local model_data = model_data.model
|
||||
|
||||
-- random start key
|
||||
local model_keys = {}
|
||||
for k,_ in pairs(model) do
|
||||
model_keys[#model_keys + 1] = k
|
||||
end
|
||||
local start_key = model_keys[math.ceil(math.random() * #model_keys)]
|
||||
|
||||
-- sequence starts with the start key
|
||||
local seq = unmake_key(start_key)
|
||||
|
||||
-- generation loop
|
||||
while #seq < length do
|
||||
local current_key = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|")
|
||||
|
||||
local chosen = "..."
|
||||
|
||||
local key_data = model[current_key]
|
||||
if key_data then
|
||||
local r = math.random()
|
||||
local prob_sum = 0.0
|
||||
for new_note, new_prob in pairs(key_data.next) do
|
||||
prob_sum = prob_sum + new_prob
|
||||
if prob_sum < r then
|
||||
chosen = new_note
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- print(current_key .. " --> " .. chosen)
|
||||
|
||||
seq[#seq+1] = chosen
|
||||
end
|
||||
|
||||
return seq
|
||||
end
|
||||
|
||||
46
tools/musicator/midi_converter.py
Normal file
46
tools/musicator/midi_converter.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from mido import MidiFile
|
||||
|
||||
MIDI_FILE = "/tmp/teletype_impostor_musicator/maestro-v3.0.0/2018/MIDI-Unprocessed_Schubert7-9_MID--AUDIO_16_R2_2018_wav.midi"
|
||||
|
||||
# resolution: rows per beat (e.g. 4 = 16th notes)
|
||||
ROWS_PER_BEAT = 4
|
||||
|
||||
names = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]
|
||||
|
||||
def note_name(n):
|
||||
octave = n // 12 - 1
|
||||
return f"{names[n % 12]}-{octave}"
|
||||
|
||||
mid = MidiFile(MIDI_FILE)
|
||||
tpb = mid.ticks_per_beat
|
||||
|
||||
row_ticks = tpb // ROWS_PER_BEAT
|
||||
|
||||
time = 0
|
||||
rows = {}
|
||||
|
||||
for msg in mid:
|
||||
time += msg.time
|
||||
if msg.type == "note_on" and msg.velocity > 0:
|
||||
row = int(time // row_ticks)
|
||||
rows.setdefault(row, []).append(msg.note)
|
||||
|
||||
# build monophonic sequence (highest note wins)
|
||||
max_row = max(rows.keys())
|
||||
sequence = []
|
||||
|
||||
for r in range(max_row + 1):
|
||||
if r in rows:
|
||||
n = max(rows[r])
|
||||
sequence.append(note_name(n))
|
||||
else:
|
||||
sequence.append("...")
|
||||
|
||||
# trim (optional)
|
||||
sequence = sequence[:512]
|
||||
|
||||
# output as Lua
|
||||
print("sequence = {")
|
||||
for n in sequence:
|
||||
print(f' "{n}",')
|
||||
print("}")
|
||||
@@ -1,130 +1,127 @@
|
||||
unpack = unpack or table.unpack
|
||||
-- key separator: |
|
||||
-- empty note: "..."
|
||||
|
||||
function build_markov_model(sequence, order)
|
||||
local function make_key(tbl)
|
||||
return table.concat(tbl, "|")
|
||||
math.randomseed(os.time())
|
||||
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
local function make_key(tbl)
|
||||
return table.concat(tbl, "|")
|
||||
end
|
||||
|
||||
local function unmake_key(k)
|
||||
local result = {}
|
||||
for t in string.gmatch(k, "[^|]+") do
|
||||
result[#result + 1] = t
|
||||
end
|
||||
|
||||
local function unmake_key(k)
|
||||
local result = {}
|
||||
for t in string.gmatch(k, "[^|]+") do
|
||||
result[#result + 1] = t
|
||||
return result
|
||||
end
|
||||
|
||||
local function add_key(str, value)
|
||||
return str .. "|" .. value
|
||||
end
|
||||
|
||||
local function split_last(full)
|
||||
local i = full:match(".*()|")
|
||||
return full:sub(1, i-1), full:sub(i+1)
|
||||
end
|
||||
|
||||
local function has_value (tab, val)
|
||||
for index, value in ipairs(tab) do
|
||||
if value == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function add_key(str, value)
|
||||
return str .. "|" .. value
|
||||
-- helper: split key into parts
|
||||
local function split(k)
|
||||
local t = {}
|
||||
for part in string.gmatch(k, "[^|]+") do
|
||||
t[#t+1] = part
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local function split_last(full)
|
||||
local i = full:match(".*()|")
|
||||
return full:sub(1, i-1), full:sub(i+1)
|
||||
end
|
||||
function build_markov_model(sequence, order)
|
||||
-- TODO: add {"..." x order} to beginning?
|
||||
|
||||
local counts = {}
|
||||
local totals = {}
|
||||
local model = { }
|
||||
|
||||
-- count
|
||||
for i = 1, #sequence - order do
|
||||
local notes = make_key({unpack(sequence, i, i + order - 1)})
|
||||
totals[notes] = (totals[notes] or 0) + 1
|
||||
local key = make_key({unpack(sequence, i, i + order - 1)})
|
||||
local next_note = sequence[i + order]
|
||||
|
||||
local notes_full = add_key(notes, sequence[i + order])
|
||||
counts[notes_full] = (counts[notes_full] or 0) + 1
|
||||
local data = model[key] or { next={}, total=0 }
|
||||
|
||||
data.next[next_note] = (data.next[next_note] or 0) + 1
|
||||
data.total = data.total + 1
|
||||
|
||||
model[key] = data
|
||||
end
|
||||
|
||||
-- build model
|
||||
local model = {}
|
||||
|
||||
for notes_full,count in pairs(counts) do
|
||||
local notes, _ = split_last(notes_full)
|
||||
|
||||
model[notes_full] = count[notes_full] / total[notes]
|
||||
-- normalize
|
||||
for temp_key,temp_data in pairs(model) do
|
||||
for temp_note, temp_count in pairs(temp_data.next) do
|
||||
model[temp_key].next[temp_note] = temp_count / temp_data.total
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
for k,v in pairs(model) do
|
||||
print("-----" .. k)
|
||||
for k2,v2 in pairs(v.next) do
|
||||
print(k2, v2)
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
return {
|
||||
order = order,
|
||||
model = model,
|
||||
counts = counts -- keep raw counts (useful!)
|
||||
model = model
|
||||
}
|
||||
end
|
||||
|
||||
function generate_sequence(model_data, length)
|
||||
local model = model_data.model
|
||||
local order = model_data.order
|
||||
local order = model.order
|
||||
local model_data = model_data.model
|
||||
|
||||
-- helper: split key into parts
|
||||
local function split(k)
|
||||
local t = {}
|
||||
for part in string.gmatch(k, "[^|]+") do
|
||||
t[#t+1] = part
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- pick random starting state
|
||||
local start_key
|
||||
-- random start key
|
||||
local model_keys = {}
|
||||
for k,_ in pairs(model) do
|
||||
start_key = k
|
||||
break
|
||||
model_keys[#model_keys + 1] = k
|
||||
end
|
||||
local start_key = model_keys[math.ceil(math.random() * #model_keys)]
|
||||
|
||||
-- (optional: better random start)
|
||||
for k,_ in pairs(model) do
|
||||
if math.random() < 0.1 then
|
||||
start_key = k
|
||||
end
|
||||
end
|
||||
|
||||
local parts = split(start_key)
|
||||
|
||||
-- initial sequence = first `order` items
|
||||
local seq = {}
|
||||
for i = 1, order do
|
||||
seq[i] = parts[i]
|
||||
end
|
||||
-- sequence starts with the start key
|
||||
local seq = unmake_key(start_key)
|
||||
|
||||
-- generation loop
|
||||
while #seq < length do
|
||||
-- build current state key
|
||||
local state = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|")
|
||||
local current_key = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|")
|
||||
|
||||
-- collect matching transitions
|
||||
local matches = {}
|
||||
for full,prob in pairs(model) do
|
||||
if full:sub(1, #state) == state and full:sub(#state+1, #state+1) == "|" then
|
||||
matches[#matches+1] = {key=full, prob=prob}
|
||||
local chosen = "..."
|
||||
|
||||
local key_data = model[current_key]
|
||||
if key_data then
|
||||
local r = math.random()
|
||||
local prob_sum = 0.0
|
||||
for new_note, new_prob in pairs(key_data.next) do
|
||||
prob_sum = prob_sum + new_prob
|
||||
if prob_sum < r then
|
||||
chosen = new_note
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #matches == 0 then break end
|
||||
-- print(current_key .. " --> " .. chosen)
|
||||
|
||||
-- weighted pick
|
||||
local r = math.random()
|
||||
local sum = 0
|
||||
|
||||
local chosen
|
||||
for _,m in ipairs(matches) do
|
||||
sum = sum + m.prob
|
||||
if r <= sum then
|
||||
chosen = m.key
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not chosen then
|
||||
chosen = matches[#matches].key
|
||||
end
|
||||
|
||||
-- extract next symbol (after last '|')
|
||||
local next_symbol = chosen:match("|([^|]+)$")
|
||||
|
||||
seq[#seq+1] = next_symbol
|
||||
seq[#seq+1] = chosen
|
||||
end
|
||||
|
||||
return seq
|
||||
end
|
||||
|
||||
-- todo: feed samples
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
-- todo
|
||||
42
tools/musicator/sequence_to_strudel.py
Normal file
42
tools/musicator/sequence_to_strudel.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import sys
|
||||
import re
|
||||
|
||||
ROWS_PER_BEAT = 4 # keep consistent with your MIDI extraction
|
||||
SOUND = "piano"
|
||||
|
||||
def parse_sequence(text):
|
||||
return re.findall(r'"([^"]+)"', text)
|
||||
|
||||
def to_strudel_notes(seq):
|
||||
out = []
|
||||
for n in seq:
|
||||
if n == "..." or n == "---":
|
||||
out.append("~")
|
||||
else:
|
||||
# C-5 → c5, C#5 → c#5
|
||||
note = n.replace("-", "")
|
||||
out.append(note.lower())
|
||||
return out
|
||||
|
||||
def chunk(seq, size):
|
||||
for i in range(0, len(seq), size):
|
||||
yield seq[i:i+size]
|
||||
|
||||
# read from stdin
|
||||
text = sys.stdin.read()
|
||||
|
||||
sequence = parse_sequence(text)
|
||||
notes = to_strudel_notes(sequence)
|
||||
|
||||
# group into musical lines (4 beats)
|
||||
lines = []
|
||||
for group in chunk(notes, ROWS_PER_BEAT * 4):
|
||||
lines.append(" ".join(group))
|
||||
|
||||
pattern = "\n".join(lines)
|
||||
|
||||
print("note(`")
|
||||
print(pattern)
|
||||
print(f"`).sound(\"{SOUND}\")")
|
||||
|
||||
# npm install -g strudel-cli
|
||||
414
tools/musicator/teach.lua
Normal file
414
tools/musicator/teach.lua
Normal file
@@ -0,0 +1,414 @@
|
||||
-- teach the musicator
|
||||
-- uses samples from: https://magenta.tensorflow.org/datasets/maestro#v300
|
||||
|
||||
--require("luarocks.loader")
|
||||
--require("luamidi")
|
||||
|
||||
require("./musicator")
|
||||
local inspect = require("inspect")
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
||||
function flatten(v)
|
||||
local res = {}
|
||||
local function flatten(v)
|
||||
if type(v) ~= "table" then
|
||||
table.insert(res, v)
|
||||
return
|
||||
end
|
||||
for _, v in ipairs(v) do
|
||||
flatten(v)
|
||||
end
|
||||
end
|
||||
flatten(v)
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local training_data = {
|
||||
|
||||
-- simple ascending phrase
|
||||
{
|
||||
"C-4","...","D-4","...","E-4","...","G-4","...",
|
||||
"E-4","...","D-4","...","C-4","...","...","..."
|
||||
},
|
||||
|
||||
-- descending answer
|
||||
{
|
||||
"G-4","...","F-4","...","E-4","...","D-4","...",
|
||||
"C-4","...","D-4","...","E-4","...","...","..."
|
||||
},
|
||||
|
||||
-- arpeggio major
|
||||
{
|
||||
"C-4","...","E-4","...","G-4","...","C-5","...",
|
||||
"G-4","...","E-4","...","C-4","...","...","..."
|
||||
},
|
||||
|
||||
-- arpeggio minor
|
||||
{
|
||||
"A-4","...","C-5","...","E-5","...","A-5","...",
|
||||
"E-5","...","C-5","...","A-4","...","...","..."
|
||||
},
|
||||
|
||||
-- stepwise melody (folk-like)
|
||||
{
|
||||
"D-4","...","E-4","...","F-4","...","G-4","...",
|
||||
"F-4","...","E-4","...","D-4","...","...","..."
|
||||
},
|
||||
|
||||
-- repeated note rhythm
|
||||
{
|
||||
"C-5","C-5","...","C-5","...","C-5","C-5","...",
|
||||
"D-5","...","E-5","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- bounce pattern
|
||||
{
|
||||
"C-5","...","G-4","...","C-5","...","G-4","...",
|
||||
"D-5","...","A-4","...","D-5","...","...","..."
|
||||
},
|
||||
|
||||
-- scale run up
|
||||
{
|
||||
"C-4","D-4","E-4","F-4","G-4","A-4","B-4","C-5",
|
||||
"...","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- scale run down
|
||||
{
|
||||
"C-5","B-4","A-4","G-4","F-4","E-4","D-4","C-4",
|
||||
"...","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- syncopated feel
|
||||
{
|
||||
"C-5","...","...","D-5","...","...","E-5","...",
|
||||
"C-5","...","...","G-4","...","...","...","..."
|
||||
},
|
||||
|
||||
-- triplet-ish feel (simulated)
|
||||
{
|
||||
"E-5","D-5","C-5","...","E-5","D-5","C-5","...",
|
||||
"G-4","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- small jumps
|
||||
{
|
||||
"C-5","...","E-5","...","D-5","...","F-5","...",
|
||||
"E-5","...","C-5","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- call
|
||||
{
|
||||
"G-4","...","A-4","...","C-5","...","A-4","...",
|
||||
"...","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- response
|
||||
{
|
||||
"E-4","...","F-4","...","G-4","...","F-4","...",
|
||||
"D-4","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- denser pattern (DDR-like)
|
||||
{
|
||||
"C-5","D-5","E-5","...","D-5","E-5","F-5","...",
|
||||
"E-5","D-5","C-5","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- alternating pattern (good for gameplay)
|
||||
{
|
||||
"C-5","...","E-5","...","C-5","...","E-5","...",
|
||||
"D-5","...","F-5","...","D-5","...","...","..."
|
||||
},
|
||||
|
||||
-- higher register variant
|
||||
{
|
||||
"G-5","...","A-5","...","B-5","...","D-6","...",
|
||||
"B-5","...","A-5","...","G-5","...","...","..."
|
||||
},
|
||||
|
||||
-- low register grounding
|
||||
{
|
||||
"C-4","...","G-3","...","C-4","...","G-3","...",
|
||||
"F-3","...","C-4","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- variant of ascending with offset
|
||||
{
|
||||
"...","C-4","...","D-4","...","E-4","...","G-4",
|
||||
"...","E-4","...","D-4","...","C-4","...","..."
|
||||
},
|
||||
|
||||
-- staggered rhythm
|
||||
{
|
||||
"C-4","...","...","D-4","...","E-4","...","...",
|
||||
"G-4","...","E-4","...","D-4","...","...","..."
|
||||
},
|
||||
|
||||
-- broken arpeggio (different spacing)
|
||||
{
|
||||
"C-4","E-4","...","G-4","...","C-5","...",
|
||||
"G-4","E-4","...","C-4","...","...","...","..."
|
||||
},
|
||||
|
||||
-- minor variation (shifted)
|
||||
{
|
||||
"...","A-4","C-5","...","E-5","...","A-5","...",
|
||||
"E-5","...","C-5","...","A-4","...","...","..."
|
||||
},
|
||||
|
||||
-- repeated + variation
|
||||
{
|
||||
"D-4","D-4","...","E-4","...","F-4","F-4","...",
|
||||
"G-4","...","F-4","...","E-4","...","...","..."
|
||||
},
|
||||
|
||||
-- zig-zag motion
|
||||
{
|
||||
"C-5","...","E-5","...","D-5","...","F-5","...",
|
||||
"E-5","...","G-5","...","F-5","...","...","..."
|
||||
},
|
||||
|
||||
-- alternating step/jump
|
||||
{
|
||||
"C-5","...","D-5","...","G-5","...","F-5","...",
|
||||
"E-5","...","C-5","...","D-5","...","...","..."
|
||||
},
|
||||
|
||||
-- denser burst pattern
|
||||
{
|
||||
"C-5","D-5","E-5","F-5","...","E-5","D-5","C-5",
|
||||
"...","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- rolling pattern
|
||||
{
|
||||
"E-5","...","D-5","...","C-5","...","D-5","...",
|
||||
"E-5","...","G-5","...","E-5","...","...","..."
|
||||
},
|
||||
|
||||
-- syncopation variant
|
||||
{
|
||||
"...","C-5","...","...","E-5","...","...","G-5",
|
||||
"...","E-5","...","C-5","...","...","...","..."
|
||||
},
|
||||
|
||||
-- low-high interplay
|
||||
{
|
||||
"C-4","...","G-4","...","C-5","...","G-4","...",
|
||||
"E-4","...","C-4","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- descending but staggered
|
||||
{
|
||||
"C-5","...","...","B-4","...","A-4","...","...",
|
||||
"G-4","...","F-4","...","E-4","...","...","..."
|
||||
},
|
||||
|
||||
-- small trill-like feel
|
||||
{
|
||||
"E-5","F-5","E-5","...","E-5","F-5","E-5","...",
|
||||
"D-5","...","C-5","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- call variant (shifted timing)
|
||||
{
|
||||
"...","G-4","...","A-4","...","C-5","...","A-4",
|
||||
"...","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- response variant
|
||||
{
|
||||
"...","E-4","...","F-4","...","G-4","...","F-4",
|
||||
"D-4","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- dense DDR-ish alternating
|
||||
{
|
||||
"C-5","...","D-5","...","C-5","...","D-5","...",
|
||||
"E-5","...","F-5","...","E-5","...","...","..."
|
||||
},
|
||||
|
||||
-- higher variation arpeggio
|
||||
{
|
||||
"G-5","...","B-5","...","D-6","...","G-6","...",
|
||||
"D-6","...","B-5","...","G-5","...","...","..."
|
||||
},
|
||||
|
||||
-- low groove pattern
|
||||
{
|
||||
"C-3","...","C-4","...","G-3","...","C-4","...",
|
||||
"F-3","...","C-4","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- slightly chaotic (good for branching)
|
||||
{
|
||||
"C-5","...","E-5","D-5","...","G-5","...","F-5",
|
||||
"...","D-5","...","C-5","...","...","...","..."
|
||||
},
|
||||
|
||||
-- mixed density
|
||||
{
|
||||
"C-5","D-5","...","E-5","...","F-5","G-5","...",
|
||||
"E-5","...","D-5","C-5","...","...","...","..."
|
||||
},
|
||||
|
||||
-- offset staircase up
|
||||
{
|
||||
"...","C-4","D-4","E-4","F-4","G-4","A-4","B-4",
|
||||
"C-5","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- offset staircase down
|
||||
{
|
||||
"...","C-5","B-4","A-4","G-4","F-4","E-4","D-4",
|
||||
"C-4","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- dense zigzag
|
||||
{
|
||||
"C-5","E-5","D-5","F-5","E-5","G-5","F-5","A-5",
|
||||
"G-5","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- jack pattern (DDR classic)
|
||||
{
|
||||
"C-5","C-5","C-5","...","C-5","C-5","...","...",
|
||||
"D-5","D-5","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- alternating two-note burst
|
||||
{
|
||||
"C-5","D-5","C-5","D-5","C-5","D-5","...","...",
|
||||
"E-5","F-5","E-5","F-5","...","...","...","..."
|
||||
},
|
||||
|
||||
-- wide jumps
|
||||
{
|
||||
"C-4","...","G-5","...","D-4","...","A-5","...",
|
||||
"E-4","...","B-5","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- rolling triplet-ish
|
||||
{
|
||||
"C-5","E-5","G-5","...","E-5","C-5","E-5","...",
|
||||
"G-5","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- syncopated dense
|
||||
{
|
||||
"...","C-5","D-5","...","E-5","...","F-5","G-5",
|
||||
"...","E-5","...","C-5","...","...","...","..."
|
||||
},
|
||||
|
||||
-- mirrored pattern
|
||||
{
|
||||
"C-5","D-5","E-5","F-5","E-5","D-5","C-5","...",
|
||||
"...","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- chord-outline arpeggio feel
|
||||
{
|
||||
"C-4","...","G-4","...","E-5","...","G-4","...",
|
||||
"C-4","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- broken rhythm variant
|
||||
{
|
||||
"C-5","...","D-5","E-5","...","F-5","...","G-5",
|
||||
"E-5","...","D-5","...","C-5","...","...","..."
|
||||
},
|
||||
|
||||
-- fast burst then rest
|
||||
{
|
||||
"C-5","D-5","E-5","F-5","G-5","A-5","...","...",
|
||||
"...","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- low-high bounce fast
|
||||
{
|
||||
"C-3","C-5","C-3","C-5","C-3","C-5","...","...",
|
||||
"G-3","G-5","G-3","G-5","...","...","...","..."
|
||||
},
|
||||
|
||||
-- repeated with shift
|
||||
{
|
||||
"...","E-5","...","E-5","...","E-5","...","...",
|
||||
"D-5","...","C-5","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- clustered mid
|
||||
{
|
||||
"D-5","E-5","F-5","...","E-5","D-5","C-5","...",
|
||||
"D-5","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- broken descending
|
||||
{
|
||||
"C-6","...","A-5","...","F-5","...","D-5","...",
|
||||
"C-5","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- chaotic jumpy (important for branching)
|
||||
{
|
||||
"C-5","...","F-5","D-5","...","A-5","...","E-5",
|
||||
"...","G-5","...","C-5","...","...","...","..."
|
||||
},
|
||||
|
||||
-- double-step pattern
|
||||
{
|
||||
"C-5","D-5","D-5","E-5","E-5","F-5","...","...",
|
||||
"G-5","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- uneven spacing
|
||||
{
|
||||
"C-5","...","...","D-5","E-5","...","...","F-5",
|
||||
"G-5","...","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- fast alternating high
|
||||
{
|
||||
"G-5","A-5","G-5","A-5","G-5","A-5","...","...",
|
||||
"F-5","E-5","...","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- low groove with variation
|
||||
{
|
||||
"C-3","...","C-4","...","G-3","...","D-4","...",
|
||||
"F-3","...","C-4","...","...","...","...","..."
|
||||
},
|
||||
|
||||
-- semi-random filler (very useful)
|
||||
{
|
||||
"C-5","...","E-5","...","D-5","...","G-5","...",
|
||||
"F-5","...","A-5","...","E-5","...","...","..."
|
||||
},
|
||||
|
||||
-- near-repetition (state collision booster)
|
||||
{
|
||||
"C-5","...","D-5","...","E-5","...","C-5","...",
|
||||
"D-5","...","E-5","...","C-5","...","...","..."
|
||||
},
|
||||
|
||||
-- same but shifted (VERY important)
|
||||
{
|
||||
"...","C-5","...","D-5","...","E-5","...","C-5",
|
||||
"...","D-5","...","E-5","...","C-5","...","..."
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
local model = build_markov_model(flatten(training_data), 2)
|
||||
|
||||
print(inspect(model))
|
||||
|
||||
--[[
|
||||
local generated = generate_sequence(model, 100)
|
||||
|
||||
for i,v in ipairs(generated) do
|
||||
print(v)
|
||||
end
|
||||
--]]
|
||||
Reference in New Issue
Block a user