From 34b0b6ec5a6e6dd5149ae9c95ab197f41ad8755e Mon Sep 17 00:00:00 2001 From: subvertnormality Date: Wed, 8 Jan 2025 20:50:50 +0000 Subject: [PATCH 1/2] Adding integration test to check shuffle across patterns --- lib/clock/m_lattice.lua | 3 + .../lib/integration_tests/clock_tests.lua | 88 ++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/lib/clock/m_lattice.lua b/lib/clock/m_lattice.lua index 6f16ec7..0260b21 100644 --- a/lib/clock/m_lattice.lua +++ b/lib/clock/m_lattice.lua @@ -205,6 +205,9 @@ function Lattice:pulse() sprocket.step = sprocket.step + 1 if sprocket.step > sprocket.lattice.pattern_length then sprocket.step = 1 + -- -- Reset shuffle state for new pattern + -- sprocket.ppqn_error = 0.5 -- Reset to initial error value + -- sprocket:update_shuffle(1) -- Force recalculation for first step end end sprocket.transport = sprocket.transport + 1 diff --git a/lib/tests/lib/integration_tests/clock_tests.lua b/lib/tests/lib/integration_tests/clock_tests.lua index ec0944c..06f3de2 100644 --- a/lib/tests/lib/integration_tests/clock_tests.lua +++ b/lib/tests/lib/integration_tests/clock_tests.lua @@ -705,8 +705,6 @@ function test_drunk_shuffle_amount_0() end --- TODO test 75 amount drunk 7 for note lengths - function test_drunk_shuffle_amount_75() local test_pattern @@ -774,13 +772,18 @@ function test_drunk_shuffle_amount_75() clock_setup() + -- Store the timing values from first pattern for comparison + local first_pattern_timings = {} + local note_on_event = table.remove(midi_note_on_events, 1) + first_pattern_timings[1] = 0 -- First note happens immediately luaunit.assert_equals(note_on_event[1], 60) luaunit.assert_equals(note_on_event[2], 20) luaunit.assert_equals(note_on_event[3], 1) progress_clock_by_pulses(30) + first_pattern_timings[2] = 30 local note_on_event = table.remove(midi_note_on_events, 1) @@ -790,6 +793,7 @@ function test_drunk_shuffle_amount_75() progress_clock_by_pulses(24) + first_pattern_timings[3] = 24 local note_on_event = table.remove(midi_note_on_events, 1) @@ -798,6 +802,7 @@ function test_drunk_shuffle_amount_75() luaunit.assert_equals(note_on_event[3], 1) progress_clock_by_pulses(20) + first_pattern_timings[4] = 20 local note_on_event = table.remove(midi_note_on_events, 1) @@ -806,6 +811,7 @@ function test_drunk_shuffle_amount_75() luaunit.assert_equals(note_on_event[3], 1) progress_clock_by_pulses(22) + first_pattern_timings[5] = 22 local note_on_event = table.remove(midi_note_on_events, 1) @@ -814,6 +820,7 @@ function test_drunk_shuffle_amount_75() luaunit.assert_equals(note_on_event[3], 1) progress_clock_by_pulses(30) + first_pattern_timings[6] = 30 local note_on_event = table.remove(midi_note_on_events, 1) @@ -822,6 +829,7 @@ function test_drunk_shuffle_amount_75() luaunit.assert_equals(note_on_event[3], 1) progress_clock_by_pulses(22) + first_pattern_timings[7] = 22 local note_on_event = table.remove(midi_note_on_events, 1) @@ -830,6 +838,7 @@ function test_drunk_shuffle_amount_75() luaunit.assert_equals(note_on_event[3], 1) progress_clock_by_pulses(22) + first_pattern_timings[8] = 22 local note_on_event = table.remove(midi_note_on_events, 1) @@ -838,6 +847,7 @@ function test_drunk_shuffle_amount_75() luaunit.assert_equals(note_on_event[3], 1) progress_clock_by_pulses(22) + first_pattern_timings[9] = 22 local note_on_event = table.remove(midi_note_on_events, 1) @@ -845,4 +855,78 @@ function test_drunk_shuffle_amount_75() luaunit.assert_equals(note_on_event[2], 28) luaunit.assert_equals(note_on_event[3], 1) + -- Progress through remaining steps to get back to step 1 + -- We're at step 9, need to progress through steps 10-64 and get to step 1 + -- Each step is 24 pulses (default ppqn) + progress_clock_by_pulses(24 * ((64 - 9) + 1)) + + -- Second pattern iteration - verify timing matches first pattern + local note_on_event = table.remove(midi_note_on_events, 1) + luaunit.assert_equals(note_on_event[1], 60) + luaunit.assert_equals(note_on_event[2], 20) + luaunit.assert_equals(note_on_event[3], 1) + + progress_clock_by_pulses(30) + luaunit.assert_equals(30, first_pattern_timings[2], "Second pattern timing mismatch at step 2") + + local note_on_event = table.remove(midi_note_on_events, 1) + luaunit.assert_equals(note_on_event[1], 62) + luaunit.assert_equals(note_on_event[2], 21) + luaunit.assert_equals(note_on_event[3], 1) + + progress_clock_by_pulses(24) + luaunit.assert_equals(24, first_pattern_timings[3], "Second pattern timing mismatch at step 3") + + local note_on_event = table.remove(midi_note_on_events, 1) + luaunit.assert_equals(note_on_event[1], 64) + luaunit.assert_equals(note_on_event[2], 22) + luaunit.assert_equals(note_on_event[3], 1) + + progress_clock_by_pulses(20) + luaunit.assert_equals(20, first_pattern_timings[4], "Second pattern timing mismatch at step 4") + + local note_on_event = table.remove(midi_note_on_events, 1) + luaunit.assert_equals(note_on_event[1], 65) + luaunit.assert_equals(note_on_event[2], 23) + luaunit.assert_equals(note_on_event[3], 1) + + progress_clock_by_pulses(22) + luaunit.assert_equals(22, first_pattern_timings[5], "Second pattern timing mismatch at step 5") + + local note_on_event = table.remove(midi_note_on_events, 1) + luaunit.assert_equals(note_on_event[1], 60) + luaunit.assert_equals(note_on_event[2], 24) + luaunit.assert_equals(note_on_event[3], 1) + + progress_clock_by_pulses(30) + luaunit.assert_equals(30, first_pattern_timings[6], "Second pattern timing mismatch at step 6") + + local note_on_event = table.remove(midi_note_on_events, 1) + luaunit.assert_equals(note_on_event[1], 62) + luaunit.assert_equals(note_on_event[2], 25) + luaunit.assert_equals(note_on_event[3], 1) + + progress_clock_by_pulses(22) + luaunit.assert_equals(22, first_pattern_timings[7], "Second pattern timing mismatch at step 7") + + local note_on_event = table.remove(midi_note_on_events, 1) + luaunit.assert_equals(note_on_event[1], 64) + luaunit.assert_equals(note_on_event[2], 26) + luaunit.assert_equals(note_on_event[3], 1) + + progress_clock_by_pulses(22) + luaunit.assert_equals(22, first_pattern_timings[8], "Second pattern timing mismatch at step 8") + + local note_on_event = table.remove(midi_note_on_events, 1) + luaunit.assert_equals(note_on_event[1], 65) + luaunit.assert_equals(note_on_event[2], 27) + luaunit.assert_equals(note_on_event[3], 1) + + progress_clock_by_pulses(22) + luaunit.assert_equals(22, first_pattern_timings[9], "Second pattern timing mismatch at step 9") + + local note_on_event = table.remove(midi_note_on_events, 1) + luaunit.assert_equals(note_on_event[1], 60) + luaunit.assert_equals(note_on_event[2], 28) + luaunit.assert_equals(note_on_event[3], 1) end From dc54ec9582476588dd4ad4da26ace2bc67f934b8 Mon Sep 17 00:00:00 2001 From: Andrew Hillel Date: Thu, 9 Jan 2025 18:09:35 +0000 Subject: [PATCH 2/2] Fixing various bugs --- CHANGELOG.md | 9 +- README.md | 4 + lib/clock/m_clock.lua | 22 ++-- lib/clock/m_lattice.lua | 21 +--- lib/m_midi.lua | 2 + lib/models/program.lua | 24 ++-- .../channel_edit_page_ui.lua | 104 ++++++++---------- .../channel_edit_page_ui_handlers.lua | 20 ++-- .../channel_edit_page_ui_refreshers.lua | 12 +- lib/quantiser.lua | 46 +++++--- mosaic.lua | 5 +- 11 files changed, 146 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4392073..dcb9f5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,17 @@ + + +## 1.2.1 + +- Fixed regression that stopped shuffle from being applied to channels on pattern reset. +- Added global option to disable honouring scale transpose when using midi input devices. +- Fixed regression that stopped scale degree and rotation from being honoured properly when using midi input devices. + ## 1.2.0 - Added mute_root_note trig param to silence the root note of chords while allowing other chord notes to play. - Added global option to fully quantize note masks (quantiser_fully_act_on_note_masks) to make note masks follow scale degree and rotation changes. - Added fully_quantise_mask trig param to control whether note masks are fully quantised to the current scale, including scale degree and rotation on a per-channel or per-step basis. - ## 1.1.2 - Chord masks now follow the root note after random note shifts have been applied. diff --git a/README.md b/README.md index 11a07f3..1ac1ac2 100644 --- a/README.md +++ b/README.md @@ -1110,6 +1110,10 @@ Similarly, "Lock merged to pent." is on by default and ensures notes modified by "Honor scale degree" is off by default. Enabling this setting means the current scale's degree option will affect the MIDI keyboard mapping. +##### Honour Scale Transpose + +"Honour scale transpose" is off by default. Enabling this setting means the current scale's transpose option will affect the MIDI keyboard mapping. + ### Sinfonion Connect You can sync up your Eurorack Sinfonion module to Mosaic using a DIY device called [norns2sinfonion](https://github.com/subvertnormality/norns2sinfonion). diff --git a/lib/clock/m_clock.lua b/lib/clock/m_clock.lua index feb32fa..5bdd0ea 100644 --- a/lib/clock/m_clock.lua +++ b/lib/clock/m_clock.lua @@ -227,11 +227,11 @@ end local function get_shuffle_values(channel) local shuffle_values = { - swing = (channel.swing ~= -51) and channel.swing or params:get("global_swing") or 0, - swing_or_shuffle = (channel.swing_shuffle_type and channel.swing_shuffle_type > 0) and (channel.swing_shuffle_type) or params:get("global_swing_shuffle_type"), - shuffle_basis = (channel.shuffle_basis and channel.shuffle_basis > 0) and (channel.shuffle_basis) or params:get("global_shuffle_basis"), - shuffle_feel = (channel.shuffle_feel and channel.shuffle_feel > 0) and (channel.shuffle_feel) or params:get("global_shuffle_feel"), - shuffle_amount = channel.shuffle_amount or params:get("global_shuffle_amount") + swing = program.get_effective_swing(channel), + swing_or_shuffle = program.get_effective_swing_shuffle_type(channel), + shuffle_basis = program.get_effective_shuffle_basis(channel), + shuffle_feel = program.get_effective_shuffle_feel(channel), + shuffle_amount = program.get_effective_shuffle_amount(channel) } if channel.number == 17 then @@ -480,8 +480,8 @@ end function m_clock.set_swing_shuffle_type(channel_number, swing_or_shuffle) local clock = m_clock["channel_" .. channel_number .. "_clock"] - clock:set_swing_or_shuffle((swing_or_shuffle or 2) - 1) - clock.end_of_clock_processor:set_swing_or_shuffle((swing_or_shuffle or 2) - 1) + clock:set_swing_or_shuffle((swing_or_shuffle or 0)) + clock.end_of_clock_processor:set_swing_or_shuffle((swing_or_shuffle or 0)) end function m_clock.set_channel_swing(channel_number, swing) @@ -492,14 +492,14 @@ end function m_clock.set_channel_shuffle_feel(channel_number, shuffle_feel) local clock = m_clock["channel_" .. channel_number .. "_clock"] - clock:set_shuffle_feel((shuffle_feel or 2) - 1) - clock.end_of_clock_processor:set_shuffle_feel((shuffle_feel or 2) - 1) + clock:set_shuffle_feel((shuffle_feel or 0)) + clock.end_of_clock_processor:set_shuffle_feel((shuffle_feel or 0)) end function m_clock.set_channel_shuffle_basis(channel_number, shuffle_basis) local clock = m_clock["channel_" .. channel_number .. "_clock"] - clock:set_shuffle_basis((shuffle_basis or 2) - 1) - clock.end_of_clock_processor:set_shuffle_basis((shuffle_basis or 2) - 1) + clock:set_shuffle_basis((shuffle_basis or 0)) + clock.end_of_clock_processor:set_shuffle_basis((shuffle_basis or 0)) end function m_clock.set_channel_shuffle_amount(channel_number, shuffle_amount) diff --git a/lib/clock/m_lattice.lua b/lib/clock/m_lattice.lua index 0260b21..99c01bd 100644 --- a/lib/clock/m_lattice.lua +++ b/lib/clock/m_lattice.lua @@ -205,9 +205,6 @@ function Lattice:pulse() sprocket.step = sprocket.step + 1 if sprocket.step > sprocket.lattice.pattern_length then sprocket.step = 1 - -- -- Reset shuffle state for new pattern - -- sprocket.ppqn_error = 0.5 -- Reset to initial error value - -- sprocket:update_shuffle(1) -- Force recalculation for first step end end sprocket.transport = sprocket.transport + 1 @@ -250,8 +247,8 @@ function Lattice:new_sprocket(args) args.delay = args.delay == nil and 0 or util.clamp(args.delay,0,1) args.swing = args.swing == nil and 0 or util.clamp(args.swing,-50,50) args.swing_or_shuffle = args.swing_or_shuffle == nil and 1 or util.clamp(args.swing_or_shuffle,1,2) - args.shuffle_basis = args.shuffle_basis and util.clamp(args.shuffle_basis, 0, 6) or 0 - args.shuffle_feel = args.shuffle_feel and util.clamp(args.shuffle_feel, 0, 3) or 0 + args.shuffle_basis = args.shuffle_basis and util.clamp(args.shuffle_basis, 1, 6) or 0 + args.shuffle_feel = args.shuffle_feel and util.clamp(args.shuffle_feel, 1, 4) or 0 args.shuffle_amount = args.shuffle_amount and util.clamp(args.shuffle_amount, 0, 100) or 0 args.step = 1 args.lattice = self @@ -300,8 +297,8 @@ function Sprocket:new(args) p.phase = args.phase or 1 p.ppqn = args.ppqn p.swing_or_shuffle = args.swing_or_shuffle - p.shuffle_basis = util.clamp(args.shuffle_basis, 0, 6) - p.shuffle_feel = util.clamp(args.shuffle_feel, 0, 3) + p.shuffle_basis = util.clamp(args.shuffle_basis, 1, 6) + p.shuffle_feel = util.clamp(args.shuffle_feel, 1, 4) p.shuffle_amount = (args.shuffle_amount == nil) and 1.0 or util.clamp(args.shuffle_amount, 0, 100) / 100 p.current_ppqn = args.ppqn p.ppqn_error = 0.5 @@ -392,18 +389,12 @@ function Sprocket:set_shuffle_amount(shuffle_amount) end function Sprocket:set_shuffle_basis(basis) - self.shuffle_basis = util.clamp(basis, 0, 6) + self.shuffle_basis = util.clamp(basis, 1, 6) self:update_shuffle(self.step) end function Sprocket:set_shuffle_feel(feel) - self.shuffle_feel = util.clamp(feel, 0, 3) - self:update_shuffle(self.step) -end - -function Sprocket:set_shuffle_or_swing(swing_or_shuffle) - self.swing_or_shuffle = util.clamp(swing_or_shuffle, 1, 2) - self:update_swing() + self.shuffle_feel = util.clamp(feel, 1, 4) self:update_shuffle(self.step) end diff --git a/lib/m_midi.lua b/lib/m_midi.lua index 9b69a3c..f459e22 100644 --- a/lib/m_midi.lua +++ b/lib/m_midi.lua @@ -63,6 +63,8 @@ function handle_midi_event_data(data, midi_device) s = fn.calc_grid_count(pressed_keys[1][1], pressed_keys[1][2]) step_scale_number = step.manually_calculate_step_scale_number(channel.number, s) end + else + step_scale_number = step.manually_calculate_step_scale_number(channel.number, s) end local note = quantiser.process_with_global_params(midi_tables[data[2] + 1][1], midi_tables[data[2] + 1][2], transpose, step_scale_number) diff --git a/lib/models/program.lua b/lib/models/program.lua index 5447578..cb9233c 100644 --- a/lib/models/program.lua +++ b/lib/models/program.lua @@ -761,34 +761,42 @@ function program.get_effective_swing_shuffle_type(channel) end function program.get_effective_swing(channel) - if channel.swing ~= nil then + if channel.swing ~= nil and channel.swing > -51 then return channel.swing - else + elseif params:get("global_swing_shuffle_type") == 1 then return params:get("global_swing") + else + return 0 end end function program.get_effective_shuffle_feel(channel) - if channel.shuffle_feel ~= nil then + if channel.shuffle_feel ~= nil and channel.shuffle_feel > 0 then return channel.shuffle_feel - else + elseif params:get("global_swing_shuffle_type") == 2 then return params:get("global_shuffle_feel") + else + return 0 end end function program.get_effective_shuffle_basis(channel) - if channel.shuffle_basis ~= nil then + if channel.shuffle_basis ~= nil and channel.shuffle_basis > 0 then return channel.shuffle_basis - else + elseif params:get("global_swing_shuffle_type") == 2 then return params:get("global_shuffle_basis") + else + return 0 end end function program.get_effective_shuffle_amount(channel) - if channel.shuffle_amount ~= nil then + if channel.shuffle_amount ~= nil and channel.shuffle_amount > 0 then return channel.shuffle_amount - else + elseif params:get("global_swing_shuffle_type") == 2 then return params:get("global_shuffle_amount") + else + return 0 end end diff --git a/lib/pages/channel_edit_page/channel_edit_page_ui.lua b/lib/pages/channel_edit_page/channel_edit_page_ui.lua index 6fb4eaa..b4773ec 100644 --- a/lib/pages/channel_edit_page/channel_edit_page_ui.lua +++ b/lib/pages/channel_edit_page/channel_edit_page_ui.lua @@ -29,11 +29,34 @@ local shuffle_feel_selector = list_selector:new(0, 40, "Feel", {{name = "X", val local shuffle_basis_selector = list_selector:new(40, 40, "Basis", {{name = "X", value = 1}, {name = "9", value = 2}, {name = "7", value = 3}, {name = "5", value = 4}, {name = "6", value = 5}, {name = "8??", value = 6}, {name = "9??", value = 7}}) local shuffle_amount_selector = value_selector:new(70, 40, "Amount", 0, 100) - local memory_state = { events = {} } +function channel_edit_page_ui.get_swing_shuffle_type_selector_value() + return swing_shuffle_type_selector:get_selected().value - 1 +end + +function channel_edit_page_ui.get_shuffle_feel_selector_value() + return shuffle_feel_selector:get_selected().value - 1 +end + +function channel_edit_page_ui.get_shuffle_basis_selector_value() + return shuffle_basis_selector:get_selected().value - 1 +end + +function channel_edit_page_ui.set_swing_shuffle_type_selector_value(value) + swing_shuffle_type_selector:set_selected_value(value + 1) +end + +function channel_edit_page_ui.set_shuffle_feel_selector_value(value) + shuffle_feel_selector:set_selected_value(value + 1) +end + +function channel_edit_page_ui.set_shuffle_basis_selector_value(value) + shuffle_basis_selector:set_selected_value(value + 1) +end + -- Value selectors with initial values local mask_selectors = { trig = value_selector:new(0 + (1 - 1) % 5 * 25, 18 + math.floor((1 - 1) / 5) * 22, "Trig", -1, 1), @@ -219,13 +242,13 @@ end) local clock_mods_page = page:new("Clocks", function() swing_shuffle_type_selector:draw() - local value = swing_shuffle_type_selector:get_selected().value - if value == 1 then - value = params:get("global_swing_shuffle_type") + 1 + local value = channel_edit_page_ui.get_swing_shuffle_type_selector_value() + if value == 0 then + value = params:get("global_swing_shuffle_type") end - if value == 2 then + if value == 1 then swing_selector:draw() - elseif value == 3 then + elseif value == 2 then shuffle_feel_selector:draw() shuffle_basis_selector:draw() shuffle_amount_selector:draw() @@ -344,10 +367,10 @@ function channel_edit_page_ui.init() clock_mod_list_selector:select() swing_selector:set_value(0) - swing_shuffle_type_selector:set_selected_value(params:get("global_swing_shuffle_type")) + channel_edit_page_ui.set_swing_shuffle_type_selector_value(params:get("global_swing_shuffle_type")) swing_selector:set_value(params:get("global_swing")) - shuffle_feel_selector:set_selected_value(params:get("global_shuffle_feel")) - shuffle_basis_selector:set_selected_value(params:get("global_shuffle_basis")) + channel_edit_page_ui.set_shuffle_feel_selector_value(params:get("global_shuffle_feel")) + channel_edit_page_ui.set_shuffle_basis_selector_value(params:get("global_shuffle_basis")) shuffle_amount_selector:set_value(params:get("global_shuffle_amount")) memory_controls.navigator:set_event_state(memory_state) @@ -368,11 +391,11 @@ end -- Update functions function channel_edit_page_ui.update_swing_shuffle_type() local channel = program.get_selected_channel() - local value = swing_shuffle_type_selector:get_selected().value + local value = channel_edit_page_ui.get_swing_shuffle_type_selector_value() channel.swing_shuffle_type = value - if value == 1 or nil then - value = params:get("global_swing_shuffle_type") + 1 or 1 + if value == 0 or nil then + value = program.get_effective_swing_shuffle_type(channel) end if m_clock.is_playing() then @@ -389,13 +412,7 @@ end function channel_edit_page_ui.align_global_and_local_swing_shuffle_type_values(c) local channel = program.get_channel(program.get().selected_song_pattern, c) - local channel_value = channel.swing_shuffle_type - local value = channel_value - if channel_value == 1 or nil then - value = params:get("global_swing_shuffle_type") + 1 or 1 - end - - m_clock.set_swing_shuffle_type(channel.number, value) + m_clock.set_swing_shuffle_type(channel.number, program.get_effective_swing_shuffle_type(channel)) end @@ -405,7 +422,7 @@ function channel_edit_page_ui.update_swing() local value = swing_selector:get_value() channel.swing = value if value == -51 or nil then - value = params:get("global_swing") + value = program.get_effective_swing(channel) end if m_clock.is_playing() then @@ -421,22 +438,16 @@ end function channel_edit_page_ui.align_global_and_local_swing_values(c) local channel = program.get_channel(program.get().selected_song_pattern, c) - local channel_value = channel.swing - local value = channel_value - if channel_value == -51 or nil then - value = params:get("global_swing") - end - - m_clock.set_channel_swing(channel.number, value) + m_clock.set_channel_swing(channel.number, program.get_effective_swing(channel)) end function channel_edit_page_ui.update_shuffle_feel() local channel = program.get_selected_channel() - local shuffle_feel = shuffle_feel_selector:get_selected().value + local shuffle_feel = channel_edit_page_ui.get_shuffle_feel_selector_value() channel.shuffle_feel = shuffle_feel - if shuffle_feel == 1 or nil then - shuffle_feel = params:get("global_shuffle_feel") + 1 or 0 + if shuffle_feel == 0 or nil then + shuffle_feel = program.get_effective_shuffle_feel(channel) end if m_clock.is_playing() then @@ -452,24 +463,18 @@ end function channel_edit_page_ui.align_global_and_local_shuffle_feel_values(c) local channel = program.get_channel(program.get().selected_song_pattern, c) - local channel_value = channel.shuffle_feel - local value = channel_value - if channel_value == 1 or nil then - value = params:get("global_shuffle_feel") + 1 or 0 - end - - m_clock.set_channel_shuffle_feel(channel.number, value) + m_clock.set_channel_shuffle_feel(channel.number, program.get_effective_shuffle_feel(channel)) end function channel_edit_page_ui.update_shuffle_basis() local channel = program.get_selected_channel() - local shuffle_basis = shuffle_basis_selector:get_selected().value + local shuffle_basis = channel_edit_page_ui.get_shuffle_basis_selector_value() channel.shuffle_basis = shuffle_basis - if shuffle_basis == 1 or nil then - shuffle_basis = params:get("global_shuffle_basis") + 1 or 0 + if shuffle_basis == 0 or nil then + shuffle_basis = program.get_effective_shuffle_basis(channel) end if m_clock.is_playing() then @@ -485,14 +490,7 @@ end function channel_edit_page_ui.align_global_and_local_shuffle_basis_values(c) local channel = program.get_channel(program.get().selected_song_pattern, c) - local channel_value = channel.shuffle_basis - local value = channel_value - if channel_value == 1 or nil then - value = params:get("global_shuffle_basis") + 1 or 0 - end - - m_clock.set_channel_shuffle_basis(channel.number, value) - + m_clock.set_channel_shuffle_basis(channel.number, program.get_effective_shuffle_basis(channel)) end function channel_edit_page_ui.update_shuffle_amount() @@ -501,7 +499,7 @@ function channel_edit_page_ui.update_shuffle_amount() channel.shuffle_amount = shuffle_amount if shuffle_amount == 0 or nil then - shuffle_amount = params:get("global_shuffle_amount") or 0 + shuffle_amount = program.get_effective_shuffle_amount(channel) end if m_clock.is_playing() then @@ -517,13 +515,7 @@ end function channel_edit_page_ui.align_global_and_local_shuffle_amount_values(c) local channel = program.get_channel(program.get().selected_song_pattern, c) - local channel_value = channel.shuffle_amount - local value = channel_value - if channel_value == 0 or nil then - value = params:get("global_shuffle_amount") or 0 - end - - m_clock.set_channel_shuffle_amount(channel.number, value) + m_clock.set_channel_shuffle_amount(channel.number, program.get_effective_shuffle_amount(channel)) end diff --git a/lib/pages/channel_edit_page/channel_edit_page_ui_handlers.lua b/lib/pages/channel_edit_page/channel_edit_page_ui_handlers.lua index f3798f7..ea0bae9 100644 --- a/lib/pages/channel_edit_page/channel_edit_page_ui_handlers.lua +++ b/lib/pages/channel_edit_page/channel_edit_page_ui_handlers.lua @@ -47,13 +47,13 @@ function channel_edit_page_ui_handlers.handle_encoder_two_positive(pages, select -- Adjusted navigation for Clock Mods page local function get_visible_clock_mod_selectors() local selectors = {clock_mod_list_selector, swing_shuffle_type_selector} - local value = swing_shuffle_type_selector:get_selected().value - if value == 1 then - value = params:get("global_swing_shuffle_type") + 1 + local value = channel_edit_page_ui.get_swing_shuffle_type_selector_value() + if value == 0 then + value = params:get("global_swing_shuffle_type") end - if value == 2 then + if value == 1 then table.insert(selectors, swing_selector) - elseif value == 3 then + elseif value == 2 then table.insert(selectors, shuffle_feel_selector) table.insert(selectors, shuffle_basis_selector) table.insert(selectors, shuffle_amount_selector) @@ -148,13 +148,13 @@ function channel_edit_page_ui_handlers.handle_encoder_two_negative(pages, select -- Adjusted navigation for Clock Mods page local function get_visible_clock_mod_selectors() local selectors = {clock_mod_list_selector, swing_shuffle_type_selector} - local value = swing_shuffle_type_selector:get_selected().value - if value == 1 then - value = params:get("global_swing_shuffle_type") + 1 + local value = channel_edit_page_ui.get_swing_shuffle_type_selector_value() + if value == 0 then + value = params:get("global_swing_shuffle_type") end - if value == 2 then + if value == 1 then table.insert(selectors, swing_selector) - elseif value == 3 then + elseif value == 2 then table.insert(selectors, shuffle_feel_selector) table.insert(selectors, shuffle_basis_selector) table.insert(selectors, shuffle_amount_selector) diff --git a/lib/pages/channel_edit_page/channel_edit_page_ui_refreshers.lua b/lib/pages/channel_edit_page/channel_edit_page_ui_refreshers.lua index 9f0a24e..804fe67 100644 --- a/lib/pages/channel_edit_page/channel_edit_page_ui_refreshers.lua +++ b/lib/pages/channel_edit_page/channel_edit_page_ui_refreshers.lua @@ -87,21 +87,21 @@ end, throttle_time) channel_edit_page_ui_refreshers.refresh_swing_shuffle_type = scheduler.debounce(function(swing_shuffle_type_selector) local channel = program.get_selected_channel() - local value = channel.swing_shuffle_type or 1 - swing_shuffle_type_selector:set_selected_value(value) + local value = channel.swing_shuffle_type or 0 + channel_edit_page_ui.set_swing_shuffle_type_selector_value(value) end, throttle_time) channel_edit_page_ui_refreshers.refresh_shuffle_feel = scheduler.debounce(function(shuffle_feel_selector) local channel = program.get_selected_channel() - local value = channel.shuffle_feel or 1 - shuffle_feel_selector:set_selected_value(value) + local value = channel.shuffle_feel or 0 + channel_edit_page_ui.set_shuffle_feel_selector_value(value) end, throttle_time) channel_edit_page_ui_refreshers.refresh_shuffle_basis = scheduler.debounce(function(shuffle_basis_selector) local channel = program.get_selected_channel() - local value = channel.shuffle_basis or 1 - shuffle_basis_selector:set_selected_value(value) + local value = channel.shuffle_basis or 0 + channel_edit_page_ui.set_shuffle_basis_selector_value(value) end, throttle_time) channel_edit_page_ui_refreshers.refresh_shuffle_amount = scheduler.debounce(function(shuffle_amount_selector) diff --git a/lib/quantiser.lua b/lib/quantiser.lua index 789c049..5d6fc5c 100644 --- a/lib/quantiser.lua +++ b/lib/quantiser.lua @@ -219,26 +219,40 @@ local function cleanup_old_cache_entries() quantiser._scale_cache_size = quantiser._scale_cache_size - entries_to_remove end -local function make_cache_key(root_note, chord_rotation, scale_number, transpose, do_rotation, do_degree, do_pentatonic, degree_rotation, version) +local function hash_scale(scale) + local hash = 0 + for i, v in ipairs(scale) do + -- Use XOR operation to combine values in a way that's sensitive to order + -- Multiply by i to make position matter + hash = hash ~ (v * i) + end + return hash +end + +local function make_cache_key(root_note, chord_rotation, scale_number, transpose, do_rotation, do_degree, do_transpose, do_pentatonic, scale_container) -- Use bit operations to pack booleans into a single number local flags = (do_rotation and 1 or 0) + (do_degree and 2 or 0) + - (do_pentatonic and 4 or 0) + (do_transpose and 4 or 0) + + (do_pentatonic and 8 or 0) + -- Hash the scale table + scale_hash = hash_scale(scale_container.scale) + -- Create a more efficient key using string format - -- %d for integers, %x for hex (flags) - return string.format("%d:%d:%d:%d:%x:%d:%d", + return string.format("%d:%d:%d:%d:%x:%d:%d:%d", root_note, chord_rotation, scale_number, transpose, flags, - degree_rotation or 0, - version or 0 + scale_container.chord_degree_rotation or 0, + scale_container.version or 0, + scale_hash ) end -local function process_handler(note_number, octave_mod, transpose, scale_number, do_rotation, do_degree, do_pentatonic) +local function process_handler(note_number, octave_mod, transpose, scale_number, do_rotation, do_degree, do_transpose, do_pentatonic) local root_note = program.get().root_note + 60 @@ -261,11 +275,12 @@ local function process_handler(note_number, octave_mod, transpose, scale_number, transpose, do_rotation, do_degree, + do_transpose, do_pentatonic, - scale_container.chord_degree_rotation, - scale_container.version + scale_container ) + local cache_entry = quantiser._scale_cache[cache_key] local scale, pentatonic @@ -292,9 +307,11 @@ local function process_handler(note_number, octave_mod, transpose, scale_number, end end end - - scale = fn.transpose_scale(scale, transpose) - pentatonic = fn.transpose_scale(pentatonic, transpose) + + if do_transpose then + scale = fn.transpose_scale(scale, transpose) + pentatonic = fn.transpose_scale(pentatonic, transpose) + end -- Store processed scales in cache quantiser._scale_cache[cache_key] = { @@ -332,7 +349,7 @@ local function process_handler(note_number, octave_mod, transpose, scale_number, end function quantiser.process(note_number, octave_mod, transpose, scale_number, do_pentatonic) - return process_handler(note_number, octave_mod, transpose, scale_number, true, true, do_pentatonic) + return process_handler(note_number, octave_mod, transpose, scale_number, true, true, true, do_pentatonic) end function quantiser.translate_note_mask_to_relative_scale_position(note_mask_value, scale_number) @@ -382,9 +399,10 @@ end function quantiser.process_with_global_params(note_number, octave_mod, transpose, scale_number) local do_rotation = params:get("midi_honour_rotation") ~= 1 local do_degree = params:get("midi_honour_degree") ~= 1 + local do_transpose = params:get("midi_honour_transpose") ~= 1 - return process_handler(note_number, octave_mod, transpose, scale_number, do_rotation, do_degree) + return process_handler(note_number, octave_mod, transpose, scale_number, do_rotation, do_degree, do_transpose) end function quantiser.snap_to_scale(note_num, scale_number, transpose) diff --git a/mosaic.lua b/mosaic.lua index 3c42607..cc9539c 100644 --- a/mosaic.lua +++ b/mosaic.lua @@ -1,4 +1,4 @@ --- mosaic v1.2.0 +-- mosaic v1.2.1 -- grid-first rhythm and -- harmony sequencer. -- @@ -249,7 +249,7 @@ function init() blink() - params:add_group("mosaic", "MOSAIC", 33) + params:add_group("mosaic", "MOSAIC", 34) params:add_separator("Pattern project management") params:add_trigger("save_p", "< Save project") params:set_action( @@ -354,6 +354,7 @@ function init() params:add_option("midi_scale_mapped_to_white_keys", "Map scale to white keys", {"Off", "On"}, 1) params:add_option("midi_honour_rotation", "Honour scale rotations", {"Off", "On"}, 1) params:add_option("midi_honour_degree", "Honour scale degree", {"Off", "On"}, 1) + params:add_option("midi_honour_transpose", "Honour scale transpose", {"Off", "On"}, 1) param_manager.init()