- tools/musicator:\n - markov model generator and pattern generator operational\n- DDR sound generation in-progress
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
mr.one
2026-03-20 20:18:10 +01:00
parent d899a74411
commit 46d3ff2ebc
8 changed files with 1274 additions and 94 deletions

View File

@@ -30,5 +30,5 @@
"files.associations": { "files.associations": {
"*.conf": "bitbake", "*.conf": "bitbake",
"*.inc": "bitbake" "*.inc": "bitbake"
} },
} }

View 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
}

View File

@@ -162,3 +162,43 @@ Songs.custom_song = {
}, 130) }, 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

View 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("}")

View File

@@ -1,130 +1,127 @@
unpack = unpack or table.unpack -- key separator: |
-- empty note: "..."
function build_markov_model(sequence, order) math.randomseed(os.time())
local function make_key(tbl)
local unpack = unpack or table.unpack
local function make_key(tbl)
return table.concat(tbl, "|") return table.concat(tbl, "|")
end end
local function unmake_key(k) local function unmake_key(k)
local result = {} local result = {}
for t in string.gmatch(k, "[^|]+") do for t in string.gmatch(k, "[^|]+") do
result[#result + 1] = t result[#result + 1] = t
end end
return result 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 counts = {}
local totals = {}
-- 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 notes_full = add_key(notes, sequence[i + order])
counts[notes_full] = (counts[notes_full] or 0) + 1
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]
end
return {
order = order,
model = model,
counts = counts -- keep raw counts (useful!)
}
end end
function generate_sequence(model_data, length) local function add_key(str, value)
local model = model_data.model return str .. "|" .. value
local order = model_data.order end
-- helper: split key into parts local function split_last(full)
local function split(k) 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 false
end
-- helper: split key into parts
local function split(k)
local t = {} local t = {}
for part in string.gmatch(k, "[^|]+") do for part in string.gmatch(k, "[^|]+") do
t[#t+1] = part t[#t+1] = part
end end
return t return t
end
function build_markov_model(sequence, order)
-- TODO: add {"..." x order} to beginning?
local model = { }
-- count
for i = 1, #sequence - order do
local key = make_key({unpack(sequence, i, i + order - 1)})
local next_note = sequence[i + order]
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 end
-- pick random starting state -- normalize
local start_key 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
}
end
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 for k,_ in pairs(model) do
start_key = k model_keys[#model_keys + 1] = k
break
end end
local start_key = model_keys[math.ceil(math.random() * #model_keys)]
-- (optional: better random start) -- sequence starts with the start key
for k,_ in pairs(model) do local seq = unmake_key(start_key)
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
-- generation loop -- generation loop
while #seq < length do while #seq < length do
-- build current state key local current_key = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|")
local state = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|")
-- collect matching transitions local chosen = "..."
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}
end
end
if #matches == 0 then break end local key_data = model[current_key]
if key_data then
-- weighted pick
local r = math.random() local r = math.random()
local sum = 0 local prob_sum = 0.0
for new_note, new_prob in pairs(key_data.next) do
local chosen prob_sum = prob_sum + new_prob
for _,m in ipairs(matches) do if prob_sum < r then
sum = sum + m.prob chosen = new_note
if r <= sum then end
chosen = m.key
break
end end
end end
if not chosen then -- print(current_key .. " --> " .. chosen)
chosen = matches[#matches].key
end
-- extract next symbol (after last '|') seq[#seq+1] = chosen
local next_symbol = chosen:match("|([^|]+)$")
seq[#seq+1] = next_symbol
end end
return seq return seq
end end
-- todo: feed samples

View File

@@ -1 +0,0 @@
-- todo

View 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
View 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
--]]