diff --git a/MIDI Editor/talagan_OneSmallStep Change note len param source.lua b/MIDI Editor/talagan_OneSmallStep Change note len param source.lua index 8f3a68327..069a0671e 100644 --- a/MIDI Editor/talagan_OneSmallStep Change note len param source.lua +++ b/MIDI Editor/talagan_OneSmallStep Change note len param source.lua @@ -9,11 +9,10 @@ local mode = select(2, reaper.get_action_context()):match("%- ([^%s]*)%.lua$") local engine_lib = require "talagan_OneSmallStep/talagan_OneSmallStep Engine lib"; - if mode == 'OSS' then - engine_lib.setNoteLenMode(engine_lib.NoteLenMode.OSS); + engine_lib.setNoteLenParamSource(engine_lib.NoteLenParamSource.OSS); elseif mode == 'ItemConf' then - engine_lib.setNoteLenMode(engine_lib.NoteLenMode.ItemConf); + engine_lib.setNoteLenParamSource(engine_lib.NoteLenParamSource.ItemConf); elseif mode == 'ProjectGrid' then - engine_lib.setNoteLenMode(engine_lib.NoteLenMode.ProjectGrid); + engine_lib.setNoteLenParamSource(engine_lib.NoteLenParamSource.ProjectGrid); end \ No newline at end of file diff --git a/MIDI Editor/talagan_OneSmallStep Playback.lua b/MIDI Editor/talagan_OneSmallStep Playback.lua new file mode 100644 index 000000000..901ec2307 --- /dev/null +++ b/MIDI Editor/talagan_OneSmallStep Playback.lua @@ -0,0 +1,109 @@ +-- @noindex +-- @author Ben 'Talagan' Babut +-- @license MIT +-- @description This is part of One Small Step. Will replay the n last measures. + +package.path = debug.getinfo(1,"S").source:match[[^@?(.*[\/])[^\/]-$]] .."?.lua;".. package.path; +local engine_lib = require "talagan_OneSmallStep/talagan_OneSmallStep Engine lib"; + +-- Give the possibility to this script to be duplicated and called +-- With a param at the end of the lua file name (it overrides OSS config) +local param = select(2, reaper.get_action_context()):match("%- ([^%s]*)%.lua$"); + +if reaper.set_action_options ~= nil then + reaper.set_action_options(1); +end + +-- Reaper must be in stalled state +if not (reaper.GetPlayState() == 0) then + return; +end + +local rewindMeasureCount = ((param == nil) and engine_lib.getPlaybackMeasureCount() or tonumber(param)); + +local pos = reaper.GetCursorPosition(); +local posqn = reaper.TimeMap2_timeToQN(0, pos); +local posm = reaper.TimeMap_QNToMeasures(0, posqn); + +local timeStart = 0; + +if rewindMeasureCount == -1 then + local mkid, mkpos = engine_lib.findPlaybackMarker(); + if mkid == nil then + rewindMeasureCount = 0 + else + timeStart = mkpos; + end +end + +if rewindMeasureCount >= 0 then + -- Determine the right measure start + local _, thisMeasureStart, thisMeasureEnd = reaper.TimeMap_GetMeasureInfo(0, posm - 1); + + if math.abs(thisMeasureStart - posqn) < 0.01 and rewindMeasureCount == 0 then + -- If the cursor is on the start of one measure, move 1 one more measure backward + rewindMeasureCount = 1; + end + + local _, measureStart, measureEnd = reaper.TimeMap_GetMeasureInfo(0, posm - 1 - rewindMeasureCount); + + timeStart = reaper.TimeMap2_QNToTime(0, measureStart); +end + + + +-- In OSS manual, I encourage users to a tick the option that +-- creates an undo point whenever the Edit cursor is moved +-- This ensures that OSS undo works well (notes are cancelled and the edit cursor moves back to its previous position) +-- Hovever, for the playback action, it may create unwanted undo points + +-- That's why, at the end we check if undo points were created during playback and we cancel them. + +local SPBA = "OneSmallStep - Start Playback"; +local EPBA = "OneSmallStep - End Playback"; + +function startPlayback() + -- Move the cursor back and hit play + reaper.Undo_BeginBlock(); + reaper.SetEditCurPos(timeStart, true, true); + reaper.Undo_EndBlock(SPBA,0); + reaper.OnPlayButton(); + reaper.defer(waitEndOfPlayback); +end + +function onPlaybackEnd() + reaper.Undo_BeginBlock(); + reaper.SetEditCurPos(pos, false, false); + reaper.Undo_EndBlock(EPBA,0); + + -- We cannot prevent reaper from creating undo points + local last_action = reaper.Undo_CanUndo2(0); + while last_action == SPBA or last_action == EPBA do + reaper.Undo_DoUndo2(0); + last_action = reaper.Undo_CanUndo2(0); + end + +end + +function waitEndOfPlayback() + + local ps = reaper.GetPlayState(); + local curtime = reaper.GetPlayPosition(); + local antiglitch = 0.05; + + if curtime < pos - antiglitch and ps == 1 then + reaper.defer(waitEndOfPlayback); + else + return; + end + +end + +function stopPlayback() + reaper.OnStopButton(); + onPlaybackEnd(); +end + +reaper.defer(startPlayback); +reaper.atexit(stopPlayback); + diff --git a/MIDI Editor/talagan_OneSmallStep Set or remove playback marker.lua b/MIDI Editor/talagan_OneSmallStep Set or remove playback marker.lua new file mode 100644 index 000000000..ce0ee7df6 --- /dev/null +++ b/MIDI Editor/talagan_OneSmallStep Set or remove playback marker.lua @@ -0,0 +1,9 @@ +-- @noindex +-- @author Ben 'Talagan' Babut +-- @license MIT +-- @description This is part of One Small Step. Will replay the n last measures. + +package.path = debug.getinfo(1,"S").source:match[[^@?(.*[\/])[^\/]-$]] .."?.lua;".. package.path; +local engine_lib = require "talagan_OneSmallStep/talagan_OneSmallStep Engine lib"; + +engine_lib.setPlaybackMarkerAtCurrentPos(); diff --git a/MIDI Editor/talagan_OneSmallStep.lua b/MIDI Editor/talagan_OneSmallStep.lua index 7358ec5cd..4fe570885 100644 --- a/MIDI Editor/talagan_OneSmallStep.lua +++ b/MIDI Editor/talagan_OneSmallStep.lua @@ -1,6 +1,6 @@ --[[ @description One Small Step : Alternative Step Input -@version 0.9 +@version 0.9.1 @author Ben 'Talagan' Babut @license MIT @metapackage @@ -25,6 +25,7 @@ [main=main,midi_editor] talagan_OneSmallStep Change note len.lua > talagan_OneSmallStep Change note len - 1.lua [main=main,midi_editor] talagan_OneSmallStep Cleanup helper JSFXs.lua [main=main,midi_editor] talagan_OneSmallStep Commit.lua + [nomain] talagan_OneSmallStep/classes/*.lua [nomain] talagan_OneSmallStep/images/*.lua [nomain] talagan_OneSmallStep/talagan_OneSmallStep Engine lib.lua [nomain] talagan_OneSmallStep/talagan_OneSmallStep Helper lib.lua @@ -32,13 +33,11 @@ [data] talagan_OneSmallStep/toolbar_icons/toolbar_one_small_step.png > toolbar_icons/toolbar_one_small_step.png [data] talagan_OneSmallStep/toolbar_icons/toolbar_one_small_step_cleanup.png > toolbar_icons/toolbar_one_small_step_cleanup.png @screenshot - https://stash.reaper.fm/48161/One%20Small%20Step%200.1.png + https://stash.reaper.fm/48222/OSS%200.9.1.png @changelog - - Added support for complex note length modification (+/- fractions between 0 and 1) - - More compact UI (save space) - - Removed OFF mode : Redundant with closing OSS - - Bug Fix : "change note len param source" actions where called "change note len modifier" instead - - Big code refactoring + - [Feature] Added playback (rewind and play) action (n measures) + - [Feature] Added playback marker support + - [Feature] Added Keyboard Press mode @about # Purpose @@ -52,7 +51,7 @@ # Install Notes - This script also needs the JS_ReaScriptAPI api by Julian Sander and the ReaImGui library by Christian Fillion to work. Please install them alongside (OSS will remind you to do so anyway). A restart of Reaper is needed after install. + This script also needs the JS_ReaScriptAPI api by Julian Sader and the ReaImGui library by Christian Fillion to work. Please install them alongside (OSS will remind you to do so anyway). A restart of Reaper is needed after install. # Reaper forum thread @@ -66,21 +65,43 @@ You can then select your input mode between Keyboard / Sustain Pedal / Action. For each Input Mode, two triggers may be used to validate notes and rests : the sustain pedal and the 'OneSmallStep Commit' Action, to which you may consider giving a shortcut. Inserting held notes and/or rests depends on the chosen mode. You can use the tooltip by hovering over each mode's button as a reminder of their role. - ### Keyboard + ### Keyboard Release (Grope Mode) Notes are added to the MIDI item at the current position, when the keys are released. - Rests can also be inserted in this mode, by calling the 'OneSmallStep Commit' action or pressing the sustain pedal. + + Suitable for inputing notes at a low pace, correcting things by ear, especially for chords. This mode is error tolerant, but tends to aggregate and skip notes easily when playing fast. + + This is pretty much the same as Reaper's default step input mode. + + - The sustain pedal advances (=inserts rests) + - The Commit action advances (=inserts rests) + + ### Keyboard Press (Fast Mode) + + Notes are added on keyboard key press events. + + Suitable for inputing notes at a high pace. It is not error tolerant (you get what you play), but will only aggregate chords if keys are pressed simultaneously. + + - The sustain pedal advances (=inserts rests) + - The Commit action advances (=inserts rests) ### Sustain Pedal - Hold keys on your MIDI controller, then press the sustain pedal to validate them. This is convenient when playing with chords for example. - In this mode, the 'OneSmallStep Commit' action will behave like the sustain pedal. + Hold some keyboard keys, and then press the sustain pedal to validate and add notes. + + Useful when testing chords. + + - The sustain pedal commits held notes (or advances) + - The Commit action commits held notes (or advances) ### Action - It's the same thing as with the sustain pedal, except that held notes are validated with the REAPER action. In this mode, the sustain pedal will only insert rests. + Hold some keyboard keys, and then call the Commit action from Reaper to validate and add notes. - ## Note length + - The sustain pedal advances (=inserts rests) + - The Commit action commits held notes (or advances) + + ## Note length parameter source Three sources for determining the input note length are proposed. @@ -96,7 +117,11 @@ One Small Step will use the note parameters specific to the edited MIDI item to determine the length of the notes to insert. Those parameters are located at the bottom of the MIDI Editor (the combo boxes right of the 'Notes' label). - ## Other actions + ## Step input playback + + One Small Step provides a convenient playback widget, which is a way to ear what you've just written, without losing the position of the edit cursor, so that you can work faster. The playback button will replay the last N measures (N is settable, and the result is rounded to the start of the matching measure). You can chose Mk instead of a number of measures, and instead, the start point will be the 'OSS Playback' marker (if it is set, else, only the current measure will be played as when N=0). You can set/remove it using the marker button on the right. + + ## Other Reaper actions To speed up your flow, multiple actions are provided to quickly change OSS parameters, so that you can assign shortcuts to them. Those are the "Change note len", "Decrease/Increase note len", "Change note len modifier", "Change note len param source" actions, whose names should be self explanatory. The "Cleanup helper JSFXs" is here for cleaniness, to remove the Helper JSFXs that are installed automatically on the input FX chain of your tracks when OSS is running (it could have been done automatically when closing the tool, but it adds an entry in the undo stack, which is annoying, and I don't have a solution for this yet). @@ -130,6 +155,12 @@ png_to_lua("triplet.png") --]] +-- Tell the script to be terminated if relaunched. +-- Check the existence of the function for sanity (added in v 7.03) +if reaper.set_action_options ~= nil then + reaper.set_action_options(1); +end + ------------------------------- -- Path and modules @@ -174,41 +205,17 @@ local ctx = reaper.ImGui_CreateContext('One Small Step'); ------------------------------- -function ButtonGroupImageButton(image_name, is_on, callback, corner) - reaper.ImGui_SetCursorPosY(ctx, reaper.ImGui_GetCursorPosY(ctx)); - reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Button(), is_on and 0x5080FFFF or 0x203040FF); - - if corner == nil then - corner = 0.1 - end - - if reaper.ImGui_ImageButton(ctx, image_name, getImage(image_name), 20, 20, corner, corner, 1 - corner, 1 - corner, 0, 0xFFFFFFFF) then - callback(); - end - - reaper.ImGui_PopStyleColor(ctx); +function SL() reaper.ImGui_SameLine(ctx); end -function ImGui_NoteLenImg(context, image_name, triplet, divider) - reaper.ImGui_SetCursorPosY(ctx,reaper.ImGui_GetCursorPosY(ctx)); - reaper.ImGui_Image(ctx, getImage(image_name), 20, 20, 0.1, 0.1, 0.9, 0.9); - - if triplet then - reaper.ImGui_SameLine(ctx); - reaper.ImGui_SetCursorPosX(ctx, reaper.ImGui_GetCursorPosX(ctx) - 20); - reaper.ImGui_SetCursorPosY(ctx, reaper.ImGui_GetCursorPosY(ctx) - 10); - ImGui_NoteLenImg(ctx, "note_triplet"); - end - - if divider then - reaper.ImGui_SameLine(ctx); - reaper.ImGui_SetCursorPosY(ctx,reaper.ImGui_GetCursorPosY(ctx) + 3); - reaper.ImGui_TextColored(ctx, 0xC0C0C0FF, divider); +function TT(str) + if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then + reaper.ImGui_SetTooltip(ctx, str) end end -local function to_frac(num) +function to_frac(num) local W = math.floor(num) local F = num - W local pn, n, N = 0, 1 @@ -309,16 +316,105 @@ function ImGui_ItemGridLabel(ctx,take) reaper.ImGui_PopStyleVar(ctx,1); end +function ButtonGroupImageButton(image_name, is_on, callback, corner, is_green) + + reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_FramePadding(), 0, 0); + + if is_green then + PushGreenButtonColors(is_on); + else + PushBlueButtonColors(is_on); + end + + if corner == nil then + corner = 0.1 + end + + if reaper.ImGui_ImageButton(ctx, image_name, getImage(image_name), 20, 20, corner, corner, 1 - corner, 1 - corner, 0, 0xFFFFFFFF) then + callback(); + end + + if is_green then + PopGreenButtonColors(); + else + PopBlueButtonColors(); + end + + reaper.ImGui_PopStyleVar(ctx,1); +end + +function PushBlueButtonColors(is_on) + local on_col = 0x5080FFFF; + local off_col = 0x203040FF; + local hov_col = 0x60A0FFFF; + local act_col = 0x60A0FFFF; + + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Button(), is_on and on_col or off_col); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_ButtonHovered(),hov_col ); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_ButtonActive(), act_col ); +end + +function PopBlueButtonColors() + reaper.ImGui_PopStyleColor(ctx,3); +end + +function PushGreenButtonColors(is_on) + + local on_col = 0x008000FF; + local off_col = 0x006000FF; + local hov_col = 0x00C000FF; + local act_col = 0x00C000FF; + + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Button(), is_on and on_col or off_col); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_ButtonHovered(),hov_col ); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_ButtonActive(), act_col ); +end + +function PopGreenButtonColors() + reaper.ImGui_PopStyleColor(ctx,3); +end + +function ButtonGroupTextButton(text, is_on, callback) + + if reaper.ImGui_Button(ctx, text) then + callback(); + end; + +end + +function ImGui_NoteLenImg(context, image_name, triplet, divider) + reaper.ImGui_SetCursorPosY(ctx,reaper.ImGui_GetCursorPosY(ctx)); + reaper.ImGui_Image(ctx, getImage(image_name), 20, 20, 0.1, 0.1, 0.9, 0.9); + + if triplet then + SL(); + reaper.ImGui_SetCursorPosX(ctx, reaper.ImGui_GetCursorPosX(ctx) - 20); + reaper.ImGui_SetCursorPosY(ctx, reaper.ImGui_GetCursorPosY(ctx) - 10); + ImGui_NoteLenImg(ctx, "note_triplet"); + end + + if divider then + SL(); + reaper.ImGui_SetCursorPosY(ctx,reaper.ImGui_GetCursorPosY(ctx) + 3); + reaper.ImGui_TextColored(ctx, 0xC0C0C0FF, divider); + end +end + function ImGui_VerticalSpacer(context, height) reaper.ImGui_PushStyleVar(context, reaper.ImGui_StyleVar_ItemSpacing(),0,0) reaper.ImGui_Dummy(context, 10, height); reaper.ImGui_PopStyleVar(context,1); end -function MiniBarSeparator() - reaper.ImGui_SameLine(ctx); - reaper.ImGui_Dummy(ctx,10,0); - reaper.ImGui_SameLine(ctx); +function MiniBarSeparator(dst) + dst = ((dst == nil) and 6 or dst); + + reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_FramePadding(),0,0); + reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_ItemSpacing(),0,0); + reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_ItemInnerSpacing(),0,0) + reaper.ImGui_Dummy(ctx, dst, 0); + reaper.ImGui_PopStyleVar(ctx,3); + end -- Current take info label and indicators @@ -329,11 +425,11 @@ function TakeInfo(take) local recarmed = reaper.GetMediaTrackInfo_Value(track, "I_RECARM"); local playState = reaper.GetPlayState(); - reaper.ImGui_TextColored(ctx, 0xA0A0FFFF, track_name .. " / " .. take_name) + reaper.ImGui_TextColored(ctx, 0xA0A0FFFF, track_name .. " / " .. take_name); -- Glowing indicator - reaper.ImGui_SameLine(ctx); - reaper.ImGui_SetCursorPosY(ctx, reaper.ImGui_GetCursorPosY(ctx) - 3); + SL(); + reaper.ImGui_SetCursorPosY(ctx, reaper.ImGui_GetCursorPosY(ctx)+1); if (recarmed == 1) and not (engine_lib.getInputMode() == engine_lib.InputMode.None) and playState == 0 then local alpha = math.sin(reaper.time_precise()*4); @@ -355,8 +451,9 @@ function TakeInfo(take) reaper.ImGui_PopStyleColor(ctx, 4); -- Target issues debug text - reaper.ImGui_SameLine(ctx); - reaper.ImGui_SetCursorPosY(ctx, reaper.ImGui_GetCursorPosY(ctx) - 3); + SL(); + + reaper.ImGui_SetCursorPosY(ctx, reaper.ImGui_GetCursorPosY(ctx)); if not (recarmed == 1) then reaper.ImGui_TextColored(ctx, 0x808080FF, '[Track not armed]'); elseif engine_lib.getInputMode() == engine_lib.InputMode.None then @@ -372,46 +469,75 @@ end function InputModeMiniBar() local mode = engine_lib.getInputMode(); - ButtonGroupImageButton('input_mode_keyboard', mode == engine_lib.InputMode.Keyboard, function()engine_lib.setInputMode(engine_lib.InputMode.Keyboard); end); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, 'Input Mode : Keyboard\n\nNotes are added on keyboard key\nrelease events.\n\nThis is pretty much the same as\nReaper\'s default step input mode.\n\n- The sustain pedal inserts rests\n- The Commit action inserts rests') - end + ButtonGroupImageButton('input_mode_keyboard_release', mode == engine_lib.InputMode.Keyboard, function()engine_lib.setInputMode(engine_lib.InputMode.Keyboard); end, 0); + TT("Input Mode : Keyboard Release (Grope mode)\n\z + \n\z + Notes are added on keyboard key release events.\n\z + \n\z + Suitable for inputing notes at a low pace, correcting\n\z + things by ear, especially for chords. This mode is error\n\z + tolerant, but tends to aggregate and skip notes easily\n\z + when playing fast.\n\z + \n\z + This is pretty much the same as Reaper\'s default\nstep input mode.\n\n\z + - The sustain pedal advances (=inserts rests)\n\z + - The Commit action advances (=inserts rests)"); + SL(); + + ButtonGroupImageButton('input_mode_keyboard_press', mode == engine_lib.InputMode.KeyboardMelodic, function()engine_lib.setInputMode(engine_lib.InputMode.KeyboardMelodic); end, 0); + TT("Input Mode : Keyboard Press (Fast mode)\n\z + \n\z + Notes are added on keyboard key press events.\n\z + \n\z + Suitable for inputing notes at a high pace. It is not error\n\z + tolerant (you get what you play), but will only aggregate \n\z + chords if keys are pressed simultaneously.\n\z + \n\z + - The sustain pedal advances (=inserts rests)\n\z + - The Commit action advances (=inserts rests)"); + SL(); ButtonGroupImageButton('input_mode_pedal', mode == engine_lib.InputMode.Pedal, function()engine_lib.setInputMode(engine_lib.InputMode.Pedal); end,0); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, 'Input Mode : Pedal\n\nHold some keyboard keys, and\nthen press the sustain pedal\nto validate and add notes.\n\nUseful when testing chords.\n\n- The sustain pedal commits held notes\n- The Commit action commits held notes') - end - - ButtonGroupImageButton('input_mode_action', mode == engine_lib.InputMode.Action, function()engine_lib.setInputMode(engine_lib.InputMode.Action); end); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, 'Input Mode : Action\n\nHold some keyboard keys, and then\ncall the \'OneSmallStep Commit\'\naction from Reaper to validate\nand add notes.\n\n- The sustain pedal inserts rests\n- The Commit action commits held notes') - end + TT('Input Mode : Pedal\n\z + \n\z + Hold some keyboard keys, and then press the sustain\n\z + pedal to validate and add notes.\n\z + \n\z + Useful when testing chords.\n\n\z + - The sustain pedal commits held notes (or advances)\n\z + - The Commit action commits held notes (or advances)'); + SL(); + + ButtonGroupImageButton('input_mode_action', mode == engine_lib.InputMode.Action, function()engine_lib.setInputMode(engine_lib.InputMode.Action); end,0); + TT('Input Mode : Action\n\z + \n\z + Hold some keyboard keys, and then call the Commit\n\z + action from Reaper to validate and add notes.\n\z + \n\z + - The sustain pedal advances (=inserts rests)\n\z + - The Commit action commits held notes (or advances)'); end -- MINIBAR : Conf source function ConfSourceMiniBar() - local nlm = engine_lib.getNoteLenMode(); - - ButtonGroupImageButton('note_len_mode_oss', nlm == engine_lib.NoteLenMode.OSS, function() engine_lib.setNoteLenMode(engine_lib.NoteLenMode.OSS) end, 0); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, 'Note Length conf : One Small Step\n\nUse the params aside.') - end + local nlm = engine_lib.getNoteLenParamSource(); - ButtonGroupImageButton('note_len_mode_pgrid', nlm == engine_lib.NoteLenMode.ProjectGrid, function() engine_lib.setNoteLenMode(engine_lib.NoteLenMode.ProjectGrid) end); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, "Note Length conf : Project\n\nUse the project's grid conf.") - end + ButtonGroupImageButton('note_len_mode_oss', nlm == engine_lib.NoteLenParamSource.OSS, function() engine_lib.setNoteLenParamSource(engine_lib.NoteLenParamSource.OSS) end, 0); + TT('Note Length conf : One Small Step\n\nUse the params aside.'); + SL(); + ButtonGroupImageButton('note_len_mode_pgrid', nlm == engine_lib.NoteLenParamSource.ProjectGrid, function() engine_lib.setNoteLenParamSource(engine_lib.NoteLenParamSource.ProjectGrid) end); + TT( "Note Length conf : Project\n\nUse the project's grid conf."); + SL(); + ButtonGroupImageButton('note_len_mode_igrid', nlm == engine_lib.NoteLenParamSource.ItemConf, function() engine_lib.setNoteLenParamSource(engine_lib.NoteLenParamSource.ItemConf) end); + TT( "Note Length conf : MIDI Item\n\nUse the MIDI item's own conf.\n\n('Notes' at the bottom of the MIDI editor)"); - ButtonGroupImageButton('note_len_mode_igrid', nlm == engine_lib.NoteLenMode.ItemConf, function() engine_lib.setNoteLenMode(engine_lib.NoteLenMode.ItemConf) end); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, "Note Length conf : MIDI Item\n\nUse the MIDI item's own conf.\n\n('Notes' at the bottom of the MIDI editor)") - end end -- MINIBAR : Note length function NoteLenMiniBar() local nl = engine_lib.getNoteLen(); for i,v in ipairs(engine_lib.NoteLenDefs) do + SL(); ButtonGroupImageButton('note_'.. v.id , nl == v.id, function() engine_lib.setNoteLen(v.id) @@ -433,10 +559,8 @@ function NoteLenModifierMiniBar() end end ); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, "Dotted") - end - + TT("Dotted"); + SL(); ButtonGroupImageButton('note_triplet', nmod == engine_lib.NoteLenModifier.Triplet, function() if nmod == engine_lib.NoteLenModifier.Triplet then engine_lib.setNoteLenModifier(engine_lib.NoteLenModifier.Straight); @@ -445,10 +569,8 @@ function NoteLenModifierMiniBar() end end ); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, "Triplet") - end - + TT("Triplet"); + SL(); ButtonGroupImageButton('note_modified', nmod == engine_lib.NoteLenModifier.Modified, function() if nmod == engine_lib.NoteLenModifier.Modified then engine_lib.setNoteLenModifier(engine_lib.NoteLenModifier.Straight); @@ -457,10 +579,8 @@ function NoteLenModifierMiniBar() end end ); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, "Modified length") - end - + TT("Modified length"); + SL(); ButtonGroupImageButton('note_tuplet', nmod == engine_lib.NoteLenModifier.Tuplet, function() if nmod == engine_lib.NoteLenModifier.Tuplet then engine_lib.setNoteLenModifier(engine_lib.NoteLenModifier.Straight); @@ -469,9 +589,7 @@ function NoteLenModifierMiniBar() end end ); - if reaper.ImGui_IsItemHovered(ctx, reaper.ImGui_HoveredFlags_DelayNormal()) then - reaper.ImGui_SetTooltip(ctx, "N-tuplet") - end + TT("N-tuplet"); end -- Sub-params : N-tuplet @@ -557,15 +675,115 @@ end -- Note AD function AugmentedDiminishedMiniBars() NoteADSignComboBox(); + SL(); MiniBarSeparator(); + SL(); NoteADFactorComboBox(); end +function PlayBackMeasureCountComboBox() + + reaper.ImGui_PushID(ctx, "playback_measure_count"); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_FrameBg(), 0x006000FF); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_FrameBgHovered(), 0x00A000FF); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_FrameBgActive(), 0x00C000FF); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Button(), 0x008000FF); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_ButtonHovered(), 0x008000FF); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_PopupBg(), 0x006000FF); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_Header(), 0x00C000FF); + reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_HeaderHovered(), 0x00C000FF); + + reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_FramePadding(), 5, 4); + local curm = engine_lib.getPlaybackMeasureCount(); + + local function label(mnum) + return ((mnum == -1) and "Mk" or mnum); + end + + reaper.ImGui_SetNextItemWidth(ctx,45); + if reaper.ImGui_BeginCombo(ctx, '', label(curm)) then + for i=-1,16,1 do + local is_selected = (curm == i); + + if reaper.ImGui_Selectable(ctx, label(i), is_selected) then + engine_lib.setPlaybackMeasureCount(i); + end + if is_selected then + reaper.ImGui_SetItemDefaultFocus(ctx) + end + if i == -1 then + TT("Use OSS marker as start point for playback"); + end + end + reaper.ImGui_EndCombo(ctx) + end + reaper.ImGui_PopStyleVar(ctx,1); + reaper.ImGui_PopStyleColor(ctx,8); + reaper.ImGui_PopID(ctx); + + TT("Number of measures to rewind, rounded at measure start.\n\n\z + 'Mk' stands for Marker mode, the playback will start at the\n\z + 'OSS Playback' marker instead. you can set/move/remove it\n\z + it with the button on the right.\z + "); + +end + +function PlaybackButton() + reaper.ImGui_PushID(ctx, "playback"); + ButtonGroupImageButton("playback", false, function() + local id = reaper.NamedCommandLookup("_RSb38bb99e06254b3b6e60fc7755e7af02d54341b4"); + reaper.Main_OnCommand(id, 0); + end, 0, true + ); + reaper.ImGui_PopID(ctx); + TT("Playback"); +end + +function PlaybackSetMarkerButton() + reaper.ImGui_PushID(ctx, "playback_marker"); + reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_FramePadding(), 8, 4); + ButtonGroupImageButton("marker", false, function() + engine_lib.setPlaybackMarkerAtCurrentPos(); + end, 0, true, false + ); + reaper.ImGui_PopStyleVar(ctx,1); + reaper.ImGui_PopID(ctx); + TT("Sets/Moves/Removes the playback marker"); +end + +function PlaybackWidget() + + reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_ItemSpacing(), 2, 4); + reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_ItemInnerSpacing(), 0, 0); + + PlaybackButton(); + SL(); + PlayBackMeasureCountComboBox(); + SL(); + PlaybackSetMarkerButton(); + + reaper.ImGui_PopStyleVar(ctx,2); +end + +function TargetLine(take) + PlaybackWidget(); + SL(); + MiniBarSeparator(0); + SL(); + + if not take then + reaper.ImGui_TextColored(ctx, 0xA0A0A0FF, "No target item. Please select one."); + ImGui_VerticalSpacer(ctx,0); + else + TakeInfo(take); + end +end function ui_loop() reaper.ImGui_PushStyleVar(ctx,reaper.ImGui_StyleVar_WindowPadding(),10,10); - -- + local flags = reaper.ImGui_WindowFlags_NoDocking() | reaper.ImGui_WindowFlags_NoCollapse() | reaper.ImGui_WindowFlags_AlwaysAutoResize() | @@ -573,7 +791,7 @@ function ui_loop() -- Since we use a trick to give back the focus to reaper, we don't want the window to glitch. reaper.ImGui_PushStyleColor(ctx, reaper.ImGui_Col_TitleBgActive(), 0x0A0A0AFF); - local visible, open = reaper.ImGui_Begin(ctx, 'One Small Step v0.9', true, flags); + local visible, open = reaper.ImGui_Begin(ctx, 'One Small Step v0.9.1', true, flags); reaper.ImGui_PopStyleColor(ctx,1); if visible then @@ -581,12 +799,8 @@ function ui_loop() -- Target display line local take = engine_lib.TakeForEdition(); - if not take then - reaper.ImGui_TextColored(ctx, 0xA0A0A0FF, "No target item. Please select one."); - ImGui_VerticalSpacer(ctx,3); - else - TakeInfo(take); - end + + TargetLine(take); -- Separator ImGui_VerticalSpacer(ctx,10); @@ -595,31 +809,42 @@ function ui_loop() reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_ItemSpacing(), 2, 4); reaper.ImGui_PushStyleVar(ctx, reaper.ImGui_StyleVar_ItemInnerSpacing(), 0, 0); - local nlm = engine_lib.getNoteLenMode(); + local nlm = engine_lib.getNoteLenParamSource(); local nlmod = engine_lib.getNoteLenModifier(); InputModeMiniBar(); + SL(); MiniBarSeparator(); + SL(); ConfSourceMiniBar(); + SL(); MiniBarSeparator(); - if nlm == engine_lib.NoteLenMode.OSS then + if nlm == engine_lib.NoteLenParamSource.OSS then NoteLenMiniBar(); + SL(); MiniBarSeparator(); + SL(); NoteLenModifierMiniBar(); if nlmod == engine_lib.NoteLenModifier.Tuplet then + SL(); MiniBarSeparator(); + SL(); NTupletComboBox(); elseif nlmod == engine_lib.NoteLenModifier.Modified then + SL(); MiniBarSeparator(); + SL(); AugmentedDiminishedMiniBars(); end - elseif nlm == engine_lib.NoteLenMode.ProjectGrid then + elseif nlm == engine_lib.NoteLenParamSource.ProjectGrid then + SL(); ImGui_ProjectGridLabel(ctx); - elseif nlm == engine_lib.NoteLenMode.ItemConf then + elseif nlm == engine_lib.NoteLenParamSource.ItemConf then + SL(); ImGui_ItemGridLabel(ctx,take); end @@ -647,20 +872,19 @@ function ui_loop() reaper.ImGui_PopStyleVar(ctx); if open then - reaper.defer(loop) + reaper.defer(main_loop) else stop(); end end - function updateToolbarButtonState(v) local _,_,sectionID,cmdID,_,_,_ = reaper.get_action_context(); reaper.SetToggleCommandState(sectionID,cmdID,v); reaper.RefreshToolbar2(sectionID, cmdID); end -function loop() +function main_loop() local engine_ret = engine_lib.atLoop(); @@ -685,7 +909,7 @@ function start() updateToolbarButtonState(1); engine_lib.atStart(); reaper.atexit(onReaperExit); - reaper.defer(loop); + reaper.defer(main_loop); end start(); diff --git a/MIDI Editor/talagan_OneSmallStep/One Small Step Helper.jsfx b/MIDI Editor/talagan_OneSmallStep/One Small Step Helper.jsfx index d0d8fba47..9301960be 100644 --- a/MIDI Editor/talagan_OneSmallStep/One Small Step Helper.jsfx +++ b/MIDI Editor/talagan_OneSmallStep/One Small Step Helper.jsfx @@ -1,20 +1,28 @@ noindex: true desc: One Small Step Helper -version: 0.1 +version: 0.2 author: Ben 'Talagan' Babut -slider1:0<0,3153600000,0.001>Sustain Pedal Activity -slider2:0<0,127,1>Notes in buffer + -// Generate sliders for held notes %sSustain Pedal Activity\n", dbg); + printf("slider2:0<0,127,1> %sHeld note count\n", dbg); + ni = 0; while(ni < note_buffer_size) ( - printf("slider%d:<0,127,1>Note %d Pitch\n", note_slider_offset + 3*ni + 0, ni); - printf("slider%d:<0,15,1> Note %d Channel\n", note_slider_offset + 3*ni + 1, ni); - printf("slider%d:<0,127,1>Note %d Velocity\n", note_slider_offset + 3*ni + 2, ni); + + // Generate sliders for held notes + printf("slider%d:<0,127,1> %sNote %d Pitch\n", note_slider_offset + 4*ni + 0, dbg, ni); + printf("slider%d:<0,15,1> %sNote %d Channel\n", note_slider_offset + 4*ni + 1, dbg, ni); + printf("slider%d:<0,127,1> %sNote %d Velocity\n", note_slider_offset + 4*ni + 2, dbg, ni); + printf("slider%d:<0,1,0.001> %sNote %d Timestamp\n", note_slider_offset + 4*ni + 3, dbg, ni); ni += 1; ); @@ -22,23 +30,36 @@ slider2:0<0,127,1>Notes in buffer @init -last_slider_update = 0; +MAX_NOTE = ; +NOTE_SLIDER_OFFSET = ; +note_cnt = 0; -// Use a buffer to retain note states, and update sliders in @block -// Instead of updating sliders directly. -// This avoid glitches due to undo operations that could restore FX params -// And we don't want that +function spitch(note_num) ( (NOTE_SLIDER_OFFSET + 4*note_num + 0) ); +function schan(note_num) ( (NOTE_SLIDER_OFFSET + 4*note_num + 1) ); +function svel(note_num) ( (NOTE_SLIDER_OFFSET + 4*note_num + 2) ); +function stimestamp(note_num) ( (NOTE_SLIDER_OFFSET + 4*note_num + 3) ); -MAX_NOTE = 32; -note_buf = 0; -note_cnt = 0; +// Only note_buf is used so free the memory after it. +freembuf(0); -function spitch(note_num) ( (10 + 3*note_num + 0) ); -function schan(note_num) ( (10 + 3*note_num + 1) ); -function svel(note_num) ( (10 + 3*note_num + 2) ); +function setNoteSliders(i, n, c, v, ts) +( + slider(spitch(i)) = n; + slider(schan(i)) = c; + slider(svel(i)) = v; + slider(stimestamp(i)) = ts; +); + +function copyNoteSliders(dst, src) +( + setNoteSliders(dst, + slider(spitch(src)), + slider(schan(src)), + slider(svel(src)), + slider(stimestamp(src)) + ); +); -// Only note_buf is used so free the memory after it. -freembuf(32 * 3 + 1); function addRemoveNoteFromBuffer(m1,m2,m3) local(s,c,n,v,t,i,j,init_buflen) @@ -49,30 +70,31 @@ function addRemoveNoteFromBuffer(m1,m2,m3) n = m2; // note c = m1&$xF; // channel v = m3; // velocity + ts = time_precise(); i = -1; - while // look for this note|channel already in the buffer + while ( + // look for this note|channel already in ON state among the sliders i = i+1; - i < note_cnt && i < MAX_NOTE && (notebuf[3*i]|0 != n || notebuf[3*i + 1]|0 != c); + i < note_cnt && i < MAX_NOTE && (slider(spitch(i))|0 != n || slider(schan(i))|0 != c); ); (i == MAX_NOTE)?( + // Overflow -1; ):( - (s == $x90 && v > 0) ? - ( + (s == $x90 && v > 0) ? ( // note-on, add to buffer - notebuf[3*i+0] = n; - notebuf[3*i+1] = c; - notebuf[3*i+2] = v; + setNoteSliders(i,n,c,v,ts); + // New slot ? i == note_cnt ? note_cnt = note_cnt+1; - ): - ( - + + ):( + // note-off, remove from buffer i < note_cnt ? ( @@ -80,16 +102,13 @@ function addRemoveNoteFromBuffer(m1,m2,m3) while(j < note_cnt) ( // Shift all sliders to delete current entry - notebuf[3*j+0] = notebuf[3*(j+1)+0]; - notebuf[3*j+1] = notebuf[3*(j+1)+1]; - notebuf[3*j+2] = notebuf[3*(j+1)+2]; + copyNoteSliders(j, j+1); j = j + 1; ); while(j < MAX_NOTE) ( - notebuf[3*j+0] = 0; - notebuf[3*j+1] = 0; - notebuf[3*j+2] = 0; + // Clear remaining sliders + setNoteSliders(j,0,0,0,0); j = j + 1; ); @@ -98,22 +117,12 @@ function addRemoveNoteFromBuffer(m1,m2,m3) ); ); - note_cnt==init_buflen ? -1; //return value for nothing added/removed + // Update note count + slider2 = note_cnt; ); @block -function updateSliders() ( - i = 0; - while(i < MAX_NOTE) ( - slider(spitch(i)) = note_buf[3*i+0]; - slider(schan(i)) = note_buf[3*i+1]; - slider(svel(i)) = note_buf[3*i+2]; - i = i+1; - ); - slider2 = note_cnt; -); - function main() local(offset,msg1,msg2,msg3) ( @@ -136,8 +145,3 @@ function main() ); main(); -t = time_precise(); -(t - last_slider_update > 0.05)?( - updateSliders(); - last_slider_update = t; -); diff --git a/MIDI Editor/talagan_OneSmallStep/classes/KeyPressActivityManager.lua b/MIDI Editor/talagan_OneSmallStep/classes/KeyPressActivityManager.lua new file mode 100644 index 000000000..542d6ea42 --- /dev/null +++ b/MIDI Editor/talagan_OneSmallStep/classes/KeyPressActivityManager.lua @@ -0,0 +1,127 @@ +-- @noindex +-- @author Ben 'Talagan' Babut +-- @license MIT +-- @description This is part of One Small Step + +-- A manager to keep track of key activities +-- When releasing keys +-- With inertia, to avoid losing events for chords +-- (the release events may not be totally synchronized) + +KeyPressActivityManager = { activity = {} }; + +function KeyPressActivityManager:new() + local o = o or {} + setmetatable(o, self) + self.__index = self + self.activity = {}; + return o +end + +function KeyPressActivityManager:inertia() + return 0.050; +end + +function KeyPressActivityManager:keepTrackOfKeysForTrack(track, pressed_keys) + local trackid = reaper.GetTrackGUID(track); + local t = reaper.time_precise(); + + if self.activity[trackid] == nil then + self.activity[trackid] = {} + end + + local track_activity = self.activity[trackid]; + + for _, v in pairs(pressed_keys) do + + if v.timestamp == 0 then + -- Hack... sometimes we read null events from JSFX, need to investiagate + goto continue + end + + local k = tostring(math.floor(v.chan+0.5)) .. "," .. tostring(math.floor(v.note+0.5)) + + track_activity[k] = track_activity[k] or {}; + + if track_activity[k].first_ts ~= v.timestamp then + -- We use the ts (note on date) as ID + -- Since the timestamp does not match our pending/remaining note + track_activity[k].committed = nil; + track_activity[k].released = nil; + end + + track_activity[k].note = v.note; + track_activity[k].chan = v.chan; + track_activity[k].velocity = v.velocity; + track_activity[k].first_ts = v.timestamp; + track_activity[k].latest_ts = t; + + ::continue:: + end + + -- Update released key states + for k, vals in pairs(track_activity) do + if vals.latest_ts ~= t then + -- The key is released because it was not updated this time + vals.released = true + end + end +end + +function KeyPressActivityManager:pullNotesToCommitForTrack(track) + local trackid = reaper.GetTrackGUID(track); + local track_activity = self.activity[trackid]; + + if track_activity == nil then + return {} + end + + local candidates = {} + + local most_recent_ts = nil; + for _, v in pairs(track_activity) do + if (v.committed == nil) then + -- Everything that is not committed yet is a candidate + candidates[#candidates+1] = v; + if (most_recent_ts == nil) or (most_recent_ts < v.first_ts) then + most_recent_ts = v.first_ts; + end + end + end + + if (#candidates > 0) and ((reaper.time_precise() - most_recent_ts) > self:inertia()) then + for i, candidate in ipairs(candidates) do + candidate.committed = true; + end + return candidates; + end + + return {}; +end + +function KeyPressActivityManager:clearTrackActivityForTrack(track) + local trackid = reaper.GetTrackGUID(track); + self.activity[trackid] = {}; +end + +function KeyPressActivityManager:clearOutdatedTrackActivity() + + for guid, track_activity in pairs(self.activity) do + -- Clear outdated keys + local torem = {}; + + for k, note_info in pairs(track_activity) do + if note_info.committed and note_info.released then + -- Check for the committed flag (we don't want to re-add the event, so be sure it wont reappear) + torem[#torem+1] = k; + end + end + + for i, v in ipairs(torem) do + track_activity[v] = nil; + end + end + +end + +return KeyPressActivityManager; \ No newline at end of file diff --git a/MIDI Editor/talagan_OneSmallStep/classes/KeyReleaseActivityManager.lua b/MIDI Editor/talagan_OneSmallStep/classes/KeyReleaseActivityManager.lua new file mode 100644 index 000000000..6bc291085 --- /dev/null +++ b/MIDI Editor/talagan_OneSmallStep/classes/KeyReleaseActivityManager.lua @@ -0,0 +1,93 @@ +-- @noindex +-- @author Ben 'Talagan' Babut +-- @license MIT +-- @description This is part of One Small Step + +-- A manager to keep track of key activities +-- When releasing keys +-- With inertia, to avoid losing events for chords +-- (the release events may not be totally synchronized) + +KeyReleaseActivityManager = { activity = {} } + +function KeyReleaseActivityManager:new() + local o = o or {} + setmetatable(o, self) + self.__index = self + self.activity = {}; + return o +end + +function KeyReleaseActivityManager:inertia() + return 0.2; +end + +-- +function KeyReleaseActivityManager:keepTrackOfKeysForTrack(track, pressed_keys) + local trackid = reaper.GetTrackGUID(track); + local t = reaper.time_precise(); + + if self.activity[trackid] == nil then + self.activity[trackid] = {} + end + + for _, v in pairs(pressed_keys) do + local k = tostring(math.floor(v.chan+0.5)) .. "," .. tostring(math.floor(v.note+0.5)) + local track_activity = self.activity[trackid]; + + track_activity[k] = track_activity[k] or { + note = v.note, + chan = v.chan, + velocity = v.velocity, + first_ts = v.timestamp, + latest_ts = t + }; + + -- Update activity ts + track_activity[k].latest_ts = t; + end +end + +function KeyReleaseActivityManager:keyActivityForTrack(track) + local trackid = reaper.GetTrackGUID(track); + local track_activity = self.activity[trackid]; + + if track_activity == nil then + return {} + end + + local ret = {}; + for _, v in pairs(track_activity) do + ret[#ret+1] = v; + end + + return ret; +end + +function KeyReleaseActivityManager:clearTrackActivityForTrack(track) + local trackid = reaper.GetTrackGUID(track); + self.activity[trackid] = {}; +end + +function KeyReleaseActivityManager:clearOutdatedTrackActivity() + local t = reaper.time_precise(); + + -- Do some cleanup + for guid, track_activity in pairs(self.activity) do + + local torem = {}; + for k, note_info in pairs(track_activity) do + -- If the key is not held anymore, for more than the inertia time, remove it + if t - note_info.latest_ts > self:inertia() then + torem[#torem+1] = k; + end + end + + for k,v in pairs(torem) do + track_activity[v] = nil; + end + end + +end + +return KeyReleaseActivityManager; \ No newline at end of file diff --git a/MIDI Editor/talagan_OneSmallStep/images/input_mode_action.lua b/MIDI Editor/talagan_OneSmallStep/images/input_mode_action.lua index 7fec53014..8259a24ae 100644 --- a/MIDI Editor/talagan_OneSmallStep/images/input_mode_action.lua +++ b/MIDI Editor/talagan_OneSmallStep/images/input_mode_action.lua @@ -4,12 +4,12 @@ -- @description This is part of One Small Step return "\z -\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x1E\x00\x00\x00\x1E\x08\x06\x00\x00\x00\x3B\x30\xAE\xA2\x00\x00\x00\x09\x70\x48\x59\z -\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x00\xF1\x49\x44\x41\x54\x48\x89\xED\x96\xDD\x6D\xC2\x30\x14\x85\xBF\x54\xBC\x9E\x87\xAE\x10\x46\z -\x60\x05\x3A\x02\x2B\xB0\x42\x33\x4B\x47\xE8\x0C\xCC\xC2\x0A\x3C\x9C\x01\xCC\x43\x4D\x95\xA6\xC4\xB1\x43\x22\x10\xE4\x48\x56\x24\xFF\xE4\xCB\xBD\xB9\xC7\x76\x15\z -\x42\xE0\x1E\x7A\xBB\x0B\x75\x01\x2F\xE0\x39\xB5\x4A\x0D\xDA\xBE\xC9\x6B\x92\xAA\x51\xE0\x96\x7A\x5F\xD0\xA3\xC1\x0F\xCE\x01\x57\x92\x92\x13\x6C\xFF\x5B\x33\x04\z -\x2F\xFA\xC7\xB6\x6B\xDB\xC1\xF6\xAE\xDD\x2F\xE9\x4F\xCB\x51\x69\x71\xED\x80\x23\xB0\x4F\x4D\xCA\x81\x97\x82\x3F\x81\x06\xD8\xD8\xAE\x2F\x9D\x57\x52\x3D\x1D\x38\z -\xA6\xF7\x1D\xF8\x8E\xCF\xDF\x74\x4B\x2A\x86\x97\x44\xBC\x07\x9A\x68\x91\x0F\x7E\xA2\x1F\xAD\x2C\x70\x4C\xEB\x16\xF8\x02\x90\x74\x00\x4E\xDD\x22\x2B\x51\x96\x8F\z -\x25\x1D\xE9\x78\x59\xD2\x7A\x2C\x14\x1E\x75\xCB\xEC\x53\xB7\x90\x72\xBD\x7B\x13\xF8\x02\x6D\xC3\x66\xB5\xD3\xD4\xAA\x52\x97\xBD\xD6\xE9\x34\xB8\x5F\x5F\x5B\x37\z -\xC5\xE9\x14\xC6\xA4\x33\xA5\x64\xC4\x73\xEA\xF5\xAE\x3E\x0B\xF8\xF9\xC1\x67\xBD\x40\x4D\x7E\x16\x4F\xA5\x13\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82" +\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8D\x89\x1D\x0D\x00\x00\x00\x09\x70\x48\x59\z +\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x00\xD7\x49\x44\x41\x54\x38\x8D\xAD\x94\xC1\x0D\x83\x30\x14\x43\x9D\xAA\x57\x1F\xBA\x02\x1D\xA1\z +\x2B\xB4\x23\xB0\x02\x2B\x94\x59\x3A\x42\x67\xE8\x2C\xAC\xD0\x83\x07\x48\x2F\x09\x4A\x02\x7C\x28\xC4\x52\x14\x84\x94\x27\xFF\x7C\xE7\x3B\xEF\x3D\x6A\xEA\x54\x95\z +\x06\xE0\x1C\x3F\x24\x1D\xB2\x4A\xD2\x65\xC0\x44\xEE\x4F\x56\x66\xA4\x04\x3A\x92\xE6\x69\x49\x73\x06\x46\xE8\xE2\x1D\x4A\x6A\x24\x79\x49\x6D\xFA\x9F\x64\xB6\x4A\z +\x59\x4D\x69\x01\x0C\x00\x3A\xCB\x71\x09\xB5\x80\x4F\x00\x3D\x80\x9B\xA4\x26\x71\x6E\xF1\xE7\x81\xA1\xCC\x0B\x80\x77\xD8\xC7\xB2\x49\x9A\xD0\x25\x87\x1D\x80\x3E\z +\x44\xE1\x11\xDC\x6E\xD2\x04\x18\xCA\xBB\x03\x78\x05\x47\x1F\x00\xDF\xB2\x39\x4B\x9A\xE4\x90\xE4\x80\x22\x8B\x24\xAF\xBB\x1D\x1E\xD5\xDC\x4B\xC9\x54\x36\x60\x2D\z +\xF8\x26\x30\xC2\x52\xC8\xAE\xD8\x1C\x91\x8B\xF3\x30\x99\x36\xAB\xEF\x39\x55\x3C\x67\x4D\x1B\xBF\x56\xD6\x26\x87\xB5\x54\xFD\x0E\x7F\x35\xBB\x4D\x6A\x45\x43\xC0\z +\xDD\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82" ; diff --git a/MIDI Editor/talagan_OneSmallStep/images/input_mode_keyboard.lua b/MIDI Editor/talagan_OneSmallStep/images/input_mode_keyboard.lua deleted file mode 100644 index c5881deec..000000000 --- a/MIDI Editor/talagan_OneSmallStep/images/input_mode_keyboard.lua +++ /dev/null @@ -1,12 +0,0 @@ --- @noindex --- @author Ben 'Talagan' Babut --- @license MIT --- @description This is part of One Small Step - -return "\z -\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x1E\x00\x00\x00\x1E\x08\x06\x00\x00\x00\x3B\x30\xAE\xA2\x00\x00\x00\x09\x70\x48\x59\z -\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x00\x72\x49\x44\x41\x54\x48\x89\xED\xD6\x31\x0A\xC0\x20\x0C\x05\xD0\xA6\x74\xFD\xF7\x3F\x68\x0E\z -\x90\x2E\x1D\x4A\x3B\x7C\x83\x89\x19\xCC\x07\x11\x25\xF0\x10\x45\x22\x66\x76\x54\xE4\x2C\x51\x1B\x5E\x99\x8B\x15\xA8\xAA\xFB\xF5\x01\x10\x56\x33\x7A\x62\x79\xC6\z -\x77\x8F\xD5\x4C\xC3\xE1\x19\x86\x01\x84\xD4\xB8\xE1\xE8\x34\xDC\x70\xC3\x65\xB0\x90\x39\x0D\x0E\x8B\x0B\x7E\x7F\x89\x00\x7E\xEB\x34\x38\x32\x0D\x2F\x8B\xB0\xBE\z -\x3A\xAB\xF5\xA1\x70\x56\xF6\xBB\xE3\xFD\xE0\x1B\x8E\x65\x12\x26\xB0\xF6\xFA\x14\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82" -; diff --git a/MIDI Editor/talagan_OneSmallStep/images/input_mode_keyboard_press.lua b/MIDI Editor/talagan_OneSmallStep/images/input_mode_keyboard_press.lua new file mode 100644 index 000000000..bce591263 --- /dev/null +++ b/MIDI Editor/talagan_OneSmallStep/images/input_mode_keyboard_press.lua @@ -0,0 +1,13 @@ +-- @noindex +-- @author Ben 'Talagan' Babut +-- @license MIT +-- @description This is part of One Small Step + +return "\z +\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8D\x89\x1D\x0D\x00\x00\x00\x09\x70\x48\x59\z +\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x00\x7E\x49\x44\x41\x54\x38\x8D\xE5\x55\x41\x0E\x80\x20\x0C\x6B\x8D\xD7\xFE\xFF\xA1\x3C\xA0\x5E\z +\x04\x11\x35\x84\x38\x8D\x89\xBD\x90\x35\xA3\x6B\xB6\x30\x68\x1B\x91\x98\x01\x20\xA5\x34\xAC\x2A\x89\x55\x68\x00\x04\x80\xA9\x22\x99\xC9\x86\x6B\x63\xAE\x26\x4E\z +\x0B\x4D\xA7\xEC\x38\x4A\xE1\x9D\xA0\xA4\xEE\xCD\x9C\x73\x95\x1B\xE5\xB0\xCC\x20\x4A\xB0\xE0\xD9\x1E\x46\xE0\x3F\x3D\x7C\x57\x90\x9D\x73\x58\xF0\x16\x0E\x82\xF5\z +\x93\x92\x74\x88\x1B\xB4\x5B\xCA\x9F\x1B\x0A\xB1\xB9\x34\x00\xD2\x76\xE8\x82\x65\xF4\x17\xB0\x00\x17\x85\x1D\x10\xD7\x01\x48\x93\x00\x00\x00\x00\x49\x45\x4E\x44\z +\xAE\x42\x60\x82" +; diff --git a/MIDI Editor/talagan_OneSmallStep/images/input_mode_keyboard_release.lua b/MIDI Editor/talagan_OneSmallStep/images/input_mode_keyboard_release.lua new file mode 100644 index 000000000..1a08651de --- /dev/null +++ b/MIDI Editor/talagan_OneSmallStep/images/input_mode_keyboard_release.lua @@ -0,0 +1,13 @@ +-- @noindex +-- @author Ben 'Talagan' Babut +-- @license MIT +-- @description This is part of One Small Step + +return "\z +\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8D\x89\x1D\x0D\x00\x00\x00\x09\x70\x48\x59\z +\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x00\x7D\x49\x44\x41\x54\x38\x8D\xED\x95\xC1\x0A\xC0\x20\x0C\x43\x13\xD9\x35\xFF\xFF\xA1\x7E\x40\z +\x77\xD1\xE2\xE6\x44\x36\x9D\xEC\xB0\x5C\x4A\x4B\x79\x84\x16\x2B\xCD\x0C\x33\xB5\x01\x40\x8C\xF1\x36\x55\x12\x8B\xD4\x00\x10\x00\x42\x51\x64\x2E\x9E\x6A\xE7\x9C\z +\xC9\x44\x09\xF3\x18\x30\x26\x77\x96\xA2\x1D\x80\x92\xBA\x84\xDC\xD3\xEA\x1D\x75\x58\x8D\x64\x14\x58\xE9\x07\x7E\x07\xE8\xDB\x5E\xE2\x90\x9D\x78\x25\xBF\x05\xEF\z +\x3B\x2C\x9F\x94\xA4\x2A\x6F\x68\xED\x0C\x9F\xC8\x67\x48\x33\x9B\x7A\x60\x39\xFB\x0B\xD8\x01\x1E\xED\x1D\x12\x47\x00\xB4\x0A\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\z +\x42\x60\x82" +; diff --git a/MIDI Editor/talagan_OneSmallStep/images/input_mode_pedal.lua b/MIDI Editor/talagan_OneSmallStep/images/input_mode_pedal.lua index 8ef197186..5e1d3dfbf 100644 --- a/MIDI Editor/talagan_OneSmallStep/images/input_mode_pedal.lua +++ b/MIDI Editor/talagan_OneSmallStep/images/input_mode_pedal.lua @@ -4,10 +4,10 @@ -- @description This is part of One Small Step return "\z -\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x1E\x00\x00\x00\x1E\x08\x06\x00\x00\x00\x3B\x30\xAE\xA2\x00\x00\x00\x09\x70\x48\x59\z -\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x00\x8B\x49\x44\x41\x54\x48\x89\xED\x96\xDD\x0A\xC0\x20\x08\x85\x6D\xEC\xD6\xF7\x7F\x50\x1F\xC0\z -\xDD\x34\x08\x96\xF9\xB3\xB6\xC6\xF0\x40\x37\x91\x7C\x9E\x32\xB1\x30\x33\xAC\xD0\xB6\x84\x9A\xE0\x04\xFF\x12\xBC\xF7\x36\x89\x68\xEA\xE7\x46\xC4\x62\x02\x57\x5D\z -\x0E\x07\x24\x1A\x18\x81\xDB\x40\x6F\x12\x6A\x9C\x06\xF6\xCA\x9C\xA8\x06\xB6\x3A\x75\xDF\xCC\x1D\xC7\xED\xFB\xB9\xEB\x61\xF8\x9D\x10\x51\x02\x72\x85\x9D\xCB\x2D\z -\x8F\xE3\x68\xA1\x85\xC1\x53\x81\x16\x30\x13\x51\x2F\x81\x88\x5E\x6F\x20\x00\x42\xC2\xDF\xEA\xD5\x55\x8F\x0E\x63\x25\x87\xBD\x04\x27\x38\xC1\x51\x1D\xA7\xD7\x1B\z -\x4B\x21\x43\x68\xA5\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82" +\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8D\x89\x1D\x0D\x00\x00\x00\x09\x70\x48\x59\z +\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x00\x83\x49\x44\x41\x54\x38\x8D\xED\x94\x4B\x0E\x80\x20\x0C\x44\xA7\x86\xED\xDC\xFF\xA0\x3D\x40\z +\x5D\x68\x49\xC5\x94\x48\xC0\x85\x89\x93\xB0\xE0\xF7\x3A\x2D\x1F\x31\x33\xAC\xD4\xB6\x94\xF6\x09\x60\x01\x00\x55\x5D\x52\x48\x92\x52\x42\x5F\x26\x58\xD5\x50\x49\z +\x26\x9E\xC2\x0D\x38\x9C\x65\xC0\x51\x47\xB7\xC0\x2D\xB0\xE7\x2C\xD6\xF9\xB2\x4E\x55\x01\x00\x24\xAF\xA7\x4C\x32\x03\xD9\x09\xF1\x96\xAA\x97\xF2\x68\x3D\x53\xE0\z +\x10\xC8\xB3\xF2\xB4\x23\xD0\x7C\xB0\x01\x77\xD5\xEC\x59\x76\x0F\xAB\x81\x77\x9E\x5E\x8C\x30\x2B\xF9\xFF\xC3\x69\xED\xE0\xEE\x23\x35\x1C\xD9\xAE\x52\x00\x00\x00\z +\x00\x49\x45\x4E\x44\xAE\x42\x60\x82" ; diff --git a/MIDI Editor/talagan_OneSmallStep/images/marker.lua b/MIDI Editor/talagan_OneSmallStep/images/marker.lua new file mode 100644 index 000000000..e13fa3645 --- /dev/null +++ b/MIDI Editor/talagan_OneSmallStep/images/marker.lua @@ -0,0 +1,12 @@ +-- @noindex +-- @author Ben 'Talagan' Babut +-- @license MIT +-- @description This is part of One Small Step + +return "\z +\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8D\x89\x1D\x0D\x00\x00\x00\x09\x70\x48\x59\z +\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x00\x63\x49\x44\x41\x54\x38\x8D\xDD\xD4\xC1\x0E\x00\x11\x0C\x04\x50\xF5\xFF\xFF\x3C\x2E\x2B\x82\z +\xED\xEE\x0C\x3D\x88\x9E\xA4\x87\x37\xA2\x30\x00\x29\xB2\x72\xA8\xE6\x80\x70\xD6\xCB\xE0\x57\x40\x08\x28\xA1\xCA\x19\x52\xA8\x02\x5A\x24\x48\x61\x2C\x48\x63\x0C\z +\x28\x61\x0C\x28\xD7\x1D\xE0\x78\xFF\xE0\xF4\x69\xF0\x17\x59\x01\xED\xC1\xDE\xA6\xDE\x85\xEC\xBE\x94\x29\x64\x77\x28\x75\xE7\xAD\x71\xFC\x8F\x5D\x00\x0F\xFC\x14\z +\x1C\x3B\x36\xB9\x12\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82" +; diff --git a/MIDI Editor/talagan_OneSmallStep/images/playback.lua b/MIDI Editor/talagan_OneSmallStep/images/playback.lua new file mode 100644 index 000000000..5ebea5164 --- /dev/null +++ b/MIDI Editor/talagan_OneSmallStep/images/playback.lua @@ -0,0 +1,12 @@ +-- @noindex +-- @author Ben 'Talagan' Babut +-- @license MIT +-- @description This is part of One Small Step + +return "\z +\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x14\x00\x00\x00\x14\x08\x06\x00\x00\x00\x8D\x89\x1D\x0D\x00\x00\x00\x09\x70\x48\x59\z +\x73\x00\x00\x0B\x13\x00\x00\x0B\x13\x01\x00\x9A\x9C\x18\x00\x00\x00\x6E\x49\x44\x41\x54\x38\x8D\xE5\x94\x49\x0A\x00\x20\x08\x45\x35\xBA\xFF\x95\x6D\x13\x51\xF0\z +\x95\x1C\xA0\x45\x6D\xA4\x8F\x3C\x9C\x59\x44\xA8\xF2\xB5\x52\xDA\x9F\xC0\x3E\x6D\x55\x67\xB8\xEF\x9F\x24\x4C\x88\x1E\xD7\xF0\xAA\x2C\xDE\x08\x11\xF4\xD0\x22\x29\z +\x9B\x91\x46\x6B\xA8\x42\x33\x4D\x81\xD0\x06\x1C\x34\x6B\x41\x97\x0F\x3B\xAE\x0D\x72\xE4\xA9\xAF\x19\xCE\xA4\x0C\x17\x21\x0A\x54\xB7\x2A\x02\x34\x57\xD4\x0B\x44\z +\xB0\x43\xF3\x00\xAF\x8E\xC7\x00\xA4\x35\x16\x1F\x3F\xFC\x38\x64\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82" +; diff --git a/MIDI Editor/talagan_OneSmallStep/talagan_OneSmallStep Engine lib.lua b/MIDI Editor/talagan_OneSmallStep/talagan_OneSmallStep Engine lib.lua index a098dab28..81212b543 100644 --- a/MIDI Editor/talagan_OneSmallStep/talagan_OneSmallStep Engine lib.lua +++ b/MIDI Editor/talagan_OneSmallStep/talagan_OneSmallStep Engine lib.lua @@ -8,7 +8,9 @@ local upperDir = scriptDir:match( "((.*)[\\/](.+)[\\/])(.+)$" ); package.path = scriptDir .."?.lua;".. package.path -local helper_lib = require "talagan_OneSmallStep Helper lib"; +local helper_lib = require "talagan_OneSmallStep Helper lib"; +local KeyReleaseActivityManager = require "classes/KeyReleaseActivityManager"; +local KeyPressActivityManager = require "classes/KeyPressActivityManager"; -- Defines local NoteLenDefs = { @@ -60,7 +62,7 @@ for i,v in ipairs(AugmentedDiminishedDefs) do AugmentedDiminishedLookup[v.id] = v; end -local NoteLenMode = { +local NoteLenParamSource = { OSS=0, ProjectGrid=1, ItemConf=2 @@ -70,7 +72,8 @@ local InputMode = { None=0, Pedal=1, Keyboard=2, - Action=3 + Action=3, + KeyboardMelodic=4 } local NoteLenModifier = { @@ -81,8 +84,23 @@ local NoteLenModifier = { Modified=4 } +------------ + +-- Our manager for the Key Release input mode +local KRActivityManager = KeyReleaseActivityManager:new(); + +-- Our manager for the Key Press input mode +local KPActivityManager = KeyPressActivityManager:new(); + ----------- +local function setPlaybackMeasureCount(c) + reaper.SetExtState("OneSmallStep", "PlaybackMeasureCount", c, true); +end +local function getPlaybackMeasureCount() + return tonumber(reaper.GetExtState("OneSmallStep", "PlaybackMeasureCount")) or -1; -- -1 is marker mode +end + local function setInputMode(m) reaper.SetExtState("OneSmallStep", "Mode", tostring(m), true) end @@ -90,17 +108,13 @@ local function getInputMode() return tonumber(reaper.GetExtState("OneSmallStep", "Mode")) or InputMode.Keyboard; end ------------ - -local function setNoteLenMode(m) - reaper.SetExtState("OneSmallStep", "NoteLenMode", tostring(m), true) +local function setNoteLenParamSource(m) + reaper.SetExtState("OneSmallStep", "NoteLenParamSource", tostring(m), true) end -local function getNoteLenMode() - return tonumber(reaper.GetExtState("OneSmallStep", "NoteLenMode")) or 0; +local function getNoteLenParamSource() + return tonumber(reaper.GetExtState("OneSmallStep", "NoteLenParamSource")) or 0; end ------------ - local function setNoteADSign(plus_or_minus) reaper.SetExtState("OneSmallStep", "NoteLenADSign", plus_or_minus, true) end @@ -108,8 +122,6 @@ local function getNoteADSign() return reaper.GetExtState("OneSmallStep", "NoteLenADSign") or "+"; end ------------ - local function setNoteADFactor(fraction_string) reaper.SetExtState("OneSmallStep", "NoteADFactor", fraction_string, true) end @@ -117,8 +129,6 @@ local function getNoteADFactor() return reaper.GetExtState("OneSmallStep", "NoteADFactor") or "1/2"; end ------------ - local function setTupletDivision(m) reaper.SetExtState("OneSmallStep", "TupletDivision", tostring(m), true) end @@ -126,8 +136,6 @@ local function getTupletDivision() return tonumber(reaper.GetExtState("OneSmallStep", "TupletDivision")) or 4; end ------------ - local function setNoteLen(str) reaper.SetExtState("OneSmallStep", "NoteLen", str, true); end @@ -139,8 +147,6 @@ local function getNoteLen() return nl; end ------------ - local function setNoteLenModifier(m) reaper.SetExtState("OneSmallStep", "NoteLenModifier", tostring(m), true); end @@ -150,6 +156,35 @@ end ----------- +function findPlaybackMarker() + local mc = reaper.CountProjectMarkers(0); + for i=0, mc, 1 do + local retval, isrgn, pos, rgnend, name, markrgnindexnumber, color = reaper.EnumProjectMarkers3(0, i); + if name == "OSS Playback" then + return i, pos; + end + end + return nil; +end + +function setPlaybackMarkerAtCurrentPos() + + local pos = reaper.GetCursorPosition(); + + local id, mpos = findPlaybackMarker(); + + reaper.Undo_BeginBlock(); + if not (id == nil) then + reaper.DeleteProjectMarkerByIndex(0, id); + end + + if (mpos == nil) or math.abs(pos - mpos) > 0.001 then + reaper.AddProjectMarker2(0, false, pos, 0, "OSS Playback", -1, reaper.ColorToNative(0,200,255)|0x1000000); + end + reaper.Undo_EndBlock("One Small Step - Set playback cursor", -1); + +end + local function getNoteLenModifierFactor() local m = getNoteLenModifier(); @@ -269,11 +304,11 @@ end local function resolveNoteLenQN(take) - local nlm = getNoteLenMode(); + local nlm = getNoteLenParamSource(); - if nlm == NoteLenMode.OSS then + if nlm == NoteLenParamSource.OSS then return getNoteLenQN() * getNoteLenModifierFactor(); - elseif nlm == NoteLenMode.ProjectGrid then + elseif nlm == NoteLenParamSource.ProjectGrid then local _, qn, swing, _ = reaper.GetSetProjectGrid(0, false); @@ -326,7 +361,7 @@ local function commit(take, notes) reaper.SetEditCurPos(noteEndTime, false, false); reaper.MarkTrackItemsDirty(track, mediaItem) - -- Grow the midi item + -- Grow the midi item if needed local itemStartTime = reaper.GetMediaItemInfo_Value(mediaItem, "D_POSITION") local itemLength = reaper.GetMediaItemInfo_Value(mediaItem, "D_LENGTH") local itemEndTime = itemStartTime + itemLength; @@ -341,78 +376,6 @@ local function commit(take, notes) reaper.MIDI_SetItemExtents(mediaItem, itemStartQN, itemEndQN) end --------------------------------------------------------------------- ---------------------------------------------------------------------- - --- The next functions are used to manage a --- Table to keep track of key activities --- With inertia, to avoid losing events for chords --- When releasing keys (the release events may not be totally synchronized) -local trackKeyActivity = {}; -- trackid -> { "chan,note" -> { :note, :chan, :velocity, :ts } } -local keyInertia = 0.2; - -local function keepTrackOfKeysForTrack(track, pressed_keys) - local trackid = reaper.GetTrackGUID(track); - local t = reaper.time_precise(); - - if trackKeyActivity[trackid] == nil then - trackKeyActivity[trackid] = { } - end - - for _, v in pairs(pressed_keys) do - local k = tostring(math.floor(v.chan+0.5)) .. "," .. tostring(math.floor(v.note+0.5)) - trackKeyActivity[trackid][k] = { - note = v.note, - chan = v.chan, - velocity = v.velocity, - ts = t - }; - end -end - -local function keyActivityForTrack(track) - local trackid = reaper.GetTrackGUID(track); - local track_activity = trackKeyActivity[trackid]; - - if track_activity == nil then - return {} - end - - local ret = {}; - for _, v in pairs(track_activity) do - ret[#ret+1] = v; - end - - return ret -end - -local function clearTrackActivityForTrack(track) - local trackid = reaper.GetTrackGUID(track); - trackKeyActivity[trackid] = {}; -end - -local function clearOutdatedTrackActivity() - local t = reaper.time_precise(); - - -- Do some cleanup - for guid, track_activity in pairs(trackKeyActivity) do - local torem = {}; - for k, note_info in pairs(track_activity) do - -- The key is not held anymore, for more than the inertia time - -- Remove it - if t - note_info.ts > keyInertia then - torem[#torem+1] = k - end - end - - for k,v in pairs(torem) do - track_activity[v] = nil; - end - end -end - --------------------------------------------------------------------- ---------------------------------------------------------------------- -- Listen to events from instrumented tracks that have the JSFX companion effect installed (or install it if not present) local function listenToEvents(called_from_action) @@ -462,17 +425,20 @@ local function listenToEvents(called_from_action) end elseif mode == InputMode.Action then + if called_from_action then reaper.Undo_BeginBlock(); commit(take, oss_state.pitches); reaper.Undo_EndBlock("One Small Step - Add notes on action",-1); end + elseif mode == InputMode.Keyboard then - clearOutdatedTrackActivity(); - keepTrackOfKeysForTrack(track, oss_state.pitches) + -- Remove old events that are not relevant (cleanup) + KRActivityManager:clearOutdatedTrackActivity(); + -- Commit new key state + KRActivityManager:keepTrackOfKeysForTrack(track, oss_state.pitches) - local trackid = reaper.GetTrackGUID(track); - local lastKnown = keyActivityForTrack(track); + local lastKnown = KRActivityManager:keyActivityForTrack(track); if lastKnown then -- We had some notes in our memory @@ -481,12 +447,35 @@ local function listenToEvents(called_from_action) if #lastKnown ~= 0 and #oss_state.pitches == 0 then reaper.Undo_BeginBlock(); commit(take, lastKnown); - -- Acknowledge note activity. - clearTrackActivityForTrack(track); + -- Acknowledge note activity, full cleanup. + KRActivityManager:clearTrackActivityForTrack(track); reaper.Undo_EndBlock("One Small Step - Add notes on key(s) release",-1); end end + -- Allow the use of the action or pedal, but only insert rests + if oss_state.pedalActivity > 0 or called_from_action then + reaper.Undo_BeginBlock(); + helper_lib.resetPedalActivity(track); + commit(take, {}) ; + reaper.Undo_EndBlock("One Small Step - Add rest",-1); + end + + elseif mode == InputMode.KeyboardMelodic then + -- Remove old events that are not relevant (cleanup) + KPActivityManager:clearOutdatedTrackActivity(); + + -- Commit new key state + KPActivityManager:keepTrackOfKeysForTrack(track, oss_state.pitches); + + local toCommit = KPActivityManager:pullNotesToCommitForTrack(track); + + if #toCommit > 0 then + reaper.Undo_BeginBlock(); + commit(take, toCommit); + reaper.Undo_EndBlock("One Small Step - Add notes on key(s) press",-1); + end + -- Allow the use of the action or pedal, but only insert rests if oss_state.pedalActivity > 0 or called_from_action then reaper.Undo_BeginBlock(); @@ -530,7 +519,7 @@ end return { -- Enums InputMode = InputMode, - NoteLenMode = NoteLenMode, + NoteLenParamSource = NoteLenParamSource, NoteLenModifier = NoteLenModifier, NoteLenDefs = NoteLenDefs, @@ -540,8 +529,11 @@ return { setInputMode = setInputMode, getInputMode = getInputMode, - setNoteLenMode = setNoteLenMode, - getNoteLenMode = getNoteLenMode, + setNoteLenParamSource = setNoteLenParamSource, + getNoteLenParamSource = getNoteLenParamSource, + + setPlaybackMeasureCount = setPlaybackMeasureCount, + getPlaybackMeasureCount = getPlaybackMeasureCount, setTupletDivision = setTupletDivision, getTupletDivision = getTupletDivision, @@ -563,6 +555,9 @@ return { increaseNoteLen = increaseNoteLen, decreaseNoteLen = decreaseNoteLen, + findPlaybackMarker = findPlaybackMarker, + setPlaybackMarkerAtCurrentPos = setPlaybackMarkerAtCurrentPos, + atStart = atStart, atExit = atExit, atLoop = atLoop, diff --git a/MIDI Editor/talagan_OneSmallStep/talagan_OneSmallStep Helper lib.lua b/MIDI Editor/talagan_OneSmallStep/talagan_OneSmallStep Helper lib.lua index 1e2ccc934..e8110dad9 100644 --- a/MIDI Editor/talagan_OneSmallStep/talagan_OneSmallStep Helper lib.lua +++ b/MIDI Editor/talagan_OneSmallStep/talagan_OneSmallStep Helper lib.lua @@ -67,6 +67,7 @@ local function cleanupAllTrackFXs() end end + -- Poll the JSFX state from a track local function oneSmallStepState(track) @@ -85,14 +86,18 @@ local function oneSmallStepState(track) local pitches = {} local pedalActivity = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_PedalActivity); - local heldNoteCount = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_NotesInBuffer) + local heldNoteCount = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_NotesInBuffer); for i = 1, heldNoteCount, 1 do -- now the plugin updates its sliders to give us the values for this index. - pitches[#pitches+1] = {} - pitches[#pitches].note = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_NoteStart + 3*(i-1) + 0) - pitches[#pitches].chan = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_NoteStart + 3*(i-1) + 1) - pitches[#pitches].velocity = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_NoteStart + 3*(i-1) + 2) + local evt = { + note = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_NoteStart + 4*(i-1) + 0), + chan = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_NoteStart + 4*(i-1) + 1), + velocity = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_NoteStart + 4*(i-1) + 2), + timestamp = reaper.TrackFX_GetParam(track, iHelper, jsfx.paramIndex_NoteStart + 4*(i-1) + 3) + } + + pitches[#pitches+1] = evt; end return {