- musicator: generation + ddr operational
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- TODO: special logic, code cleanup
This commit is contained in:
@@ -17,6 +17,7 @@ logic/logic.glitch.lua
|
||||
logic/logic.discussion.lua
|
||||
system/system.ui.lua
|
||||
audio/audio.manager.lua
|
||||
audio/audio.generator.lua
|
||||
audio/audio.songs.lua
|
||||
sprite/sprite.manager.lua
|
||||
sprite/sprite.norman.lua
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
unpack = unpack or table.unpack
|
||||
|
||||
musicator_markov_model = {
|
||||
model = {
|
||||
["...|..."] = {
|
||||
next = {
|
||||
@@ -640,3 +642,105 @@
|
||||
},
|
||||
order = 2
|
||||
}
|
||||
|
||||
local function musicator_unmake_key(k)
|
||||
local result = {}
|
||||
for t in string.gmatch(k, "[^|]+") do
|
||||
result[#result + 1] = t
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local function musicator_count_notes(sequence)
|
||||
local result = 0
|
||||
|
||||
for i,v in ipairs(sequence) do
|
||||
if not (v == "...") then
|
||||
result = result + 1
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function musicator_generate_sequence(model_data, length)
|
||||
local order = model_data.order
|
||||
local model = 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 = musicator_unmake_key(start_key)
|
||||
|
||||
-- generation loop
|
||||
while musicator_count_notes(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
|
||||
|
||||
function musicator_row_to_frame(row, bpm, spd)
|
||||
local frames_per_row = (150 * spd) / bpm
|
||||
return math.floor(row * frames_per_row)
|
||||
end
|
||||
|
||||
function musicator_note_to_direction(note)
|
||||
local subnote = note:sub(1,1)
|
||||
|
||||
local mapping = {
|
||||
C="left",
|
||||
D="up",
|
||||
E="up",
|
||||
F="right",
|
||||
G="right",
|
||||
A="down"
|
||||
}
|
||||
|
||||
return mapping[subnote] or "up"
|
||||
end
|
||||
|
||||
-- converts generated sequence to pattern that the ddr minigame can consume
|
||||
function musicator_sequence_to_pattern(sequence, bpm, spd)
|
||||
local result = {}
|
||||
|
||||
for i, note in ipairs(sequence) do
|
||||
if not (note == "...") then
|
||||
|
||||
local at_ms = musicator_row_to_frame(i, bpm, spd)
|
||||
local direction = musicator_note_to_direction(note)
|
||||
|
||||
result[#result + 1] = { frame=at_ms, dir=direction, note=note }
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function musicator_generate_pattern(length, bpm, spd)
|
||||
return musicator_sequence_to_pattern(musicator_generate_sequence(musicator_markov_model, length), bpm, spd)
|
||||
end
|
||||
@@ -105,6 +105,15 @@ Songs = {
|
||||
fps = 60,
|
||||
end_frame = nil, -- No end frame for random mode
|
||||
pattern = {} -- Empty, will spawn randomly in game
|
||||
},
|
||||
generated = {
|
||||
name = "Markov Mode",
|
||||
bpm = 150,
|
||||
spd = 6,
|
||||
fps = 60,
|
||||
end_frame = nil, -- calculated
|
||||
pattern = {}, -- generated
|
||||
generated = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,40 +171,3 @@ Songs.custom_song = {
|
||||
}, 130)
|
||||
}
|
||||
]]
|
||||
|
||||
--[[
|
||||
function generate_sequence(model_data, length)
|
||||
local order = model.order
|
||||
local model_data = model_data.model
|
||||
|
||||
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)]
|
||||
|
||||
local seq = unmake_key(start_key)
|
||||
|
||||
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
|
||||
|
||||
seq[#seq+1] = chosen
|
||||
end
|
||||
|
||||
return seq
|
||||
end
|
||||
]]
|
||||
|
||||
@@ -27,7 +27,7 @@ Context = {}
|
||||
function Context.initial_data()
|
||||
return {
|
||||
current_menu_item = 1,
|
||||
test_mode = false,
|
||||
test_mode = true,
|
||||
popup = {
|
||||
show = false,
|
||||
content = {}
|
||||
|
||||
@@ -433,8 +433,8 @@
|
||||
-- 000:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000008c10000000008c10000000000000000000000000000000000000000000000000000000000000000004008b50000000000000000001008c10000004008b50000001008c10000000008c1000000e008b30000004008b50000001008c10000000008c10000000008c10000000008c10000000008c10000000008c1000000000000000000000000000000
|
||||
-- 001:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007008b50000007008b50000001008c10000007008b50000001008c10000000008c10000007008b50000009008b50000001008c10000009008b50000001008c10000009008b50000009008b50000001008c10000009008b50000001008c1000000
|
||||
-- 003:4008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d9000000000000000000000000000000000000000000
|
||||
-- 004:49998d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000
|
||||
-- 005:400881000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000
|
||||
-- 004:43398d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000
|
||||
-- 005:455981000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000
|
||||
-- </PATTERNS>
|
||||
-- <TRACKS>
|
||||
-- 000:1000012000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff
|
||||
|
||||
@@ -90,7 +90,7 @@ end
|
||||
function MenuWindow.ddr_test()
|
||||
AudioTestWindow.init()
|
||||
GameWindow.set_state("minigame_ddr")
|
||||
MinigameDDRWindow.start("menu", nil)
|
||||
MinigameDDRWindow.start("menu", "generated")
|
||||
end
|
||||
|
||||
--- Refreshes menu items.
|
||||
|
||||
@@ -9,6 +9,7 @@ function MinigameDDRWindow.init_context()
|
||||
local total_width = (4 * arrow_size) + (3 * arrow_spacing)
|
||||
local start_x = (Config.screen.width - total_width) / 2
|
||||
return {
|
||||
special_type = "normal", -- "normal", "only_special", "only_left", "only_nothing"
|
||||
bar_fill = 0,
|
||||
max_fill = 100,
|
||||
fill_per_hit = 10,
|
||||
@@ -66,13 +67,23 @@ end
|
||||
--- @param[opt] params table Optional parameters for minigame configuration.</br>
|
||||
function MinigameDDRWindow.start(return_window, song_key, params)
|
||||
MinigameDDRWindow.init(params)
|
||||
|
||||
Audio.music_play_activity_work()
|
||||
|
||||
Context.minigame_ddr.return_window = return_window or "game"
|
||||
Context.minigame_ddr.debug_song_key = song_key
|
||||
if song_key and Songs and Songs[song_key] then
|
||||
Context.minigame_ddr.current_song = Songs[song_key]
|
||||
local current_song = Songs[song_key]
|
||||
Context.minigame_ddr.current_song = current_song
|
||||
Context.minigame_ddr.use_pattern = true
|
||||
Context.minigame_ddr.pattern_index = 1
|
||||
Context.minigame_ddr.debug_status = "Pattern loaded: " .. song_key
|
||||
|
||||
if current_song.generated then
|
||||
local pattern = musicator_generate_pattern(30, current_song.bpm, current_song.spd * 3)
|
||||
current_song.pattern = pattern
|
||||
current_song.end_frame = pattern[#pattern].frame
|
||||
end
|
||||
else
|
||||
Context.minigame_ddr.use_pattern = false
|
||||
if song_key then
|
||||
@@ -81,12 +92,19 @@ function MinigameDDRWindow.start(return_window, song_key, params)
|
||||
Context.minigame_ddr.debug_status = "Random mode"
|
||||
end
|
||||
end
|
||||
|
||||
if not Context.test_mode then
|
||||
Context.minigame_ddr.debug_status = ""
|
||||
end
|
||||
|
||||
Window.set_current("minigame_ddr")
|
||||
end
|
||||
|
||||
--- Spawns a random arrow.
|
||||
--- @within MinigameDDRWindow
|
||||
local function spawn_arrow()
|
||||
trace("random arrow")
|
||||
|
||||
local mg = Context.minigame_ddr
|
||||
local target = mg.target_arrows[math.random(1, 4)]
|
||||
table.insert(mg.arrows, {
|
||||
@@ -99,14 +117,15 @@ end
|
||||
--- Spawns an arrow in a specific direction.
|
||||
--- @within MinigameDDRWindow
|
||||
--- @param direction string The direction of the arrow ("left", "down", "up", "right").
|
||||
local function spawn_arrow_dir(direction)
|
||||
local function spawn_arrow_dir(direction, note)
|
||||
local mg = Context.minigame_ddr
|
||||
for _, target in ipairs(mg.target_arrows) do
|
||||
if target.dir == direction then
|
||||
table.insert(mg.arrows, {
|
||||
dir = direction,
|
||||
x = target.x,
|
||||
y = mg.bar_y + mg.bar_height + 10
|
||||
y = mg.bar_y + mg.bar_height + 10,
|
||||
note = note
|
||||
})
|
||||
break
|
||||
end
|
||||
@@ -165,6 +184,7 @@ function MinigameDDRWindow.update()
|
||||
mg.win_timer = mg.win_timer - 1
|
||||
if mg.win_timer == 0 then
|
||||
mg.special_condition_met = (mg.total_misses == 0)
|
||||
Audio.music_stop()
|
||||
Meter.on_minigame_complete()
|
||||
if mg.on_win then
|
||||
mg.on_win()
|
||||
@@ -180,19 +200,23 @@ function MinigameDDRWindow.update()
|
||||
mg.win_timer = Config.timing.minigame_win_duration
|
||||
return
|
||||
end
|
||||
|
||||
mg.frame_counter = mg.frame_counter + 1
|
||||
|
||||
if mg.use_pattern and mg.current_song and mg.current_song.end_frame then
|
||||
if mg.frame_counter > mg.current_song.end_frame and #mg.arrows == 0 then
|
||||
mg.win_timer = Config.timing.minigame_win_duration
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if mg.use_pattern and mg.current_song and mg.current_song.pattern then
|
||||
local pattern = mg.current_song.pattern
|
||||
while mg.pattern_index <= #pattern do
|
||||
local spawn_entry = pattern[mg.pattern_index]
|
||||
|
||||
if mg.frame_counter >= spawn_entry.frame then
|
||||
spawn_arrow_dir(spawn_entry.dir)
|
||||
spawn_arrow_dir(spawn_entry.dir, spawn_entry.note)
|
||||
mg.pattern_index = mg.pattern_index + 1
|
||||
else
|
||||
break
|
||||
@@ -205,6 +229,8 @@ function MinigameDDRWindow.update()
|
||||
mg.arrow_spawn_timer = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- move arrow downwards
|
||||
local arrows_to_remove = {}
|
||||
for i, arrow in ipairs(mg.arrows) do
|
||||
arrow.y = arrow.y + mg.arrow_fall_speed
|
||||
@@ -217,26 +243,31 @@ function MinigameDDRWindow.update()
|
||||
mg.total_misses = mg.total_misses + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- iterate backwards to avoid index shift issues
|
||||
for i = #arrows_to_remove, 1, -1 do
|
||||
table.remove(mg.arrows, arrows_to_remove[i])
|
||||
end
|
||||
|
||||
for dir, _ in pairs(mg.input_cooldowns) do
|
||||
if mg.input_cooldowns[dir] > 0 then
|
||||
mg.input_cooldowns[dir] = mg.input_cooldowns[dir] - 1
|
||||
end
|
||||
end
|
||||
|
||||
for dir, _ in pairs(mg.button_pressed_timers) do
|
||||
if mg.button_pressed_timers[dir] > 0 then
|
||||
mg.button_pressed_timers[dir] = mg.button_pressed_timers[dir] - 1
|
||||
end
|
||||
end
|
||||
|
||||
local input_map = {
|
||||
left = Input.left(),
|
||||
down = Input.down(),
|
||||
up = Input.up(),
|
||||
right = Input.right()
|
||||
}
|
||||
|
||||
for dir, pressed in pairs(input_map) do
|
||||
if pressed and mg.input_cooldowns[dir] == 0 then
|
||||
mg.input_cooldowns[dir] = mg.input_cooldown_duration
|
||||
@@ -244,6 +275,8 @@ function MinigameDDRWindow.update()
|
||||
local hit = false
|
||||
for i, arrow in ipairs(mg.arrows) do
|
||||
if arrow.dir == dir and check_hit(arrow) then
|
||||
sfx(56, arrow.note)
|
||||
|
||||
mg.bar_fill = mg.bar_fill + mg.fill_per_hit
|
||||
if mg.bar_fill > mg.max_fill then
|
||||
mg.bar_fill = mg.max_fill
|
||||
|
||||
@@ -87,7 +87,7 @@ function build_markov_model(sequence, order)
|
||||
end
|
||||
|
||||
function generate_sequence(model_data, length)
|
||||
local order = model.order
|
||||
local order = model_data.order
|
||||
local model_data = model_data.model
|
||||
|
||||
-- random start key
|
||||
|
||||
Reference in New Issue
Block a user