diff --git a/impostor.inc b/impostor.inc index 27b27fb..ab4c396 100644 --- a/impostor.inc +++ b/impostor.inc @@ -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 diff --git a/inc/audio/audio.generator_model.lua b/inc/audio/audio.generator.lua similarity index 83% rename from inc/audio/audio.generator_model.lua rename to inc/audio/audio.generator.lua index 11afe01..6143160 100644 --- a/inc/audio/audio.generator_model.lua +++ b/inc/audio/audio.generator.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 diff --git a/inc/audio/audio.songs.lua b/inc/audio/audio.songs.lua index 70e0589..de1fa8b 100644 --- a/inc/audio/audio.songs.lua +++ b/inc/audio/audio.songs.lua @@ -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 -]] diff --git a/inc/init/init.context.lua b/inc/init/init.context.lua index 4623d23..a2623b0 100644 --- a/inc/init/init.context.lua +++ b/inc/init/init.context.lua @@ -27,7 +27,7 @@ Context = {} function Context.initial_data() return { current_menu_item = 1, - test_mode = false, + test_mode = true, popup = { show = false, content = {} diff --git a/inc/meta/meta.assets.lua b/inc/meta/meta.assets.lua index a97fea2..35f67ab 100644 --- a/inc/meta/meta.assets.lua +++ b/inc/meta/meta.assets.lua @@ -433,8 +433,8 @@ -- 000:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000008c10000000008c10000000000000000000000000000000000000000000000000000000000000000004008b50000000000000000001008c10000004008b50000001008c10000000008c1000000e008b30000004008b50000001008c10000000008c10000000008c10000000008c10000000008c10000000008c1000000000000000000000000000000 -- 001:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007008b50000007008b50000001008c10000007008b50000001008c10000000008c10000007008b50000009008b50000001008c10000009008b50000001008c10000009008b50000009008b50000001008c10000009008b50000001008c1000000 -- 003:4008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d9000000000000000000000000000000000000000000 --- 004:49998d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000 --- 005:400881000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000 +-- 004:43398d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000 +-- 005:455981000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000 -- -- -- 000:1000012000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff diff --git a/inc/window/window.menu.lua b/inc/window/window.menu.lua index 15c6326..ebe1e49 100644 --- a/inc/window/window.menu.lua +++ b/inc/window/window.menu.lua @@ -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. diff --git a/inc/window/window.minigame.ddr.lua b/inc/window/window.minigame.ddr.lua index 318d7db..cd8ae3c 100644 --- a/inc/window/window.minigame.ddr.lua +++ b/inc/window/window.minigame.ddr.lua @@ -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.
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 diff --git a/tools/musicator/musicator.lua b/tools/musicator/musicator.lua index d9927e4..ed27fbb 100644 --- a/tools/musicator/musicator.lua +++ b/tools/musicator/musicator.lua @@ -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