├── .github └── workflows │ ├── check.yml │ └── deploy.yml ├── .reapack-index.conf ├── Item Editing ├── RCJacH_Delete content under mouse (contextual).lua ├── RCJacH_Glue Selected Items Preserve First Item Name.lua ├── RCJacH_Quick add or edit take marker under mouse cursor.lua ├── RCJacH_Select Tracks with Regex.lua ├── RCJacH_Split items under mouse cursor (obey snapping and selection).lua └── RCJacH_Spread Items Horizontally.lua ├── JSFX ├── Audio │ ├── AB Comparison - 1INnOUT Stereo.jsfx │ ├── AB Comparison.jsfx │ ├── NoiseBuzz.jsfx │ ├── RCBitRangeGain.jsfx │ ├── RCEveryGain.jsfx │ ├── RCInflator.jsfx │ └── RCNoiseBuzz.jsfx ├── Game │ └── RCMShip Perfect Pitch.jsfx ├── MIDI │ ├── RC MIDI Channel Router.jsfx │ ├── RC MIDI Circular Note Generator.jsfx │ ├── RC MIDI Harmonizer.jsfx │ ├── midi_cc_eater.jsfx │ └── midi_note_beat_repeater.jsfx └── Synth │ ├── ReaModular │ └── RCAutoPanner.jsfx │ └── Tone Sweep.jsfx ├── MIDI Editor ├── RCJacH_MIDI Humanizer.lua └── RCJacH_Split notes at mouse cursor (obey snapping and selection).lua ├── README.md ├── Templating └── RCJacH_Set Airwindows Console TrackFX Pin Mapping.lua ├── Track Properties ├── RCJacH_Set Parent Send Channel Offset of Selected Tracks.lua └── RCJacH_Set Parent Send Channel Offset to Sequentially Stereos for Selected Tracks.lua ├── Various ├── RCJacH_Generate LRC Lyrics and Export to Clipboard.lua └── Vimper Solo │ ├── Bindings.lua │ ├── Engine.lua │ ├── RCJacH_Vimper Solo Repeat Action.lua │ ├── RCJacH_Vimper Solo.lua │ ├── README.md │ ├── fn.lua │ └── last_action.ini └── index.xml /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: check 2 | on: [push, pull_request] 3 | jobs: 4 | reapack-index: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Fetch repository 8 | uses: actions/checkout@v2 9 | - name: Install Pandoc 10 | run: sudo apt-get install -yy pandoc 11 | - name: Set up Ruby 12 | uses: ruby/setup-ruby@v1 13 | with: 14 | ruby-version: '3.0' 15 | - name: Install reapack-index 16 | run: gem install reapack-index 17 | - name: Validate packages 18 | run: reapack-index --check 19 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | on: 3 | push: 4 | branches: [master] 5 | jobs: 6 | reapack-index: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Fetch repository 10 | uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | - name: Configure git 14 | run: |- 15 | git config user.name 'ReaTeam Bot' 16 | git config user.email 'reateam-bot@cfillion.ca' 17 | - name: Install Pandoc 18 | run: sudo apt-get install -yy pandoc 19 | - name: Set up Ruby 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: '3.0' 23 | - name: Install reapack-index 24 | run: gem install reapack-index 25 | - name: Update index.xml 26 | run: reapack-index --commit 27 | - name: Push changes 28 | run: git push 29 | -------------------------------------------------------------------------------- /.reapack-index.conf: -------------------------------------------------------------------------------- 1 | --about README.md 2 | --commit-template "index: $changelog 3 | 4 | [ci skip]" 5 | -------------------------------------------------------------------------------- /Item Editing/RCJacH_Delete content under mouse (contextual).lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ReaScript Name: Delete Content Under Mouse (Contextual) 3 | Author: RCJacH 4 | Link: https://github.com/RCJacH/ReaScripts 5 | Version: 1.0 6 | About: 7 | Delete track/item/take/envelope depending on mouse position. 8 | ]] 9 | 10 | -- Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 11 | 12 | function Main() 13 | reaper.Undo_BeginBlock() 14 | local window, segment, details 15 | window, segment, details = reaper.BR_GetMouseCursorContext() -- get mouse context 16 | if segment == "track" and not details:match('env_') then 17 | local track = reaper.BR_GetMouseCursorContext_Track() 18 | if details == "" then 19 | reaper.DeleteTrack(track) 20 | elseif details == "item" then 21 | local item = reaper.BR_GetMouseCursorContext_Item() 22 | if reaper.CountTakes(item) > 1 then 23 | reaper.Main_OnCommandEx(reaper.NamedCommandLookup("_BR_DELETE_TAKE_MOUSE", 0), 0, 0 ) 24 | else 25 | reaper.DeleteTrackMediaItem(track, item) 26 | end 27 | end 28 | elseif segment == "envelope" or details:match('env_') then 29 | reaper.Main_OnCommandEx(reaper.NamedCommandLookup("_BR_DEL_ENV_PT_MOUSE", 0), 0, 0 ) 30 | end 31 | reaper.UpdateArrange() 32 | reaper.Undo_EndBlock("Delete contextual content under mouse", 0) 33 | end 34 | Main() 35 | -------------------------------------------------------------------------------- /Item Editing/RCJacH_Glue Selected Items Preserve First Item Name.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ReaScript Name: Glue selected items preserving first item name 3 | Author: RCJacH 4 | Link: https://github.com/RCJacH/ReaScripts 5 | Version: 1.0 6 | About: 7 | Glue selected items, and rename result to: 8 | 1. The name of the first item If all item names are identical; 9 | 2. The name of each nonidentical items. 10 | 3. Remove the "Glued" in item name (but not the file name unfortunately) 11 | ]] 12 | 13 | -- Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 14 | 15 | function merge_track_take_names(track_pointer, apply_to_first_take) 16 | local first_take, item, take, take_name, b_exist 17 | local merged_name = "" 18 | local a_names = {} 19 | if track_pointer then 20 | for i = 0, reaper.CountTrackMediaItems(track_pointer) do 21 | item = reaper.GetTrackMediaItem(track_pointer, i) 22 | if item then 23 | if reaper.IsMediaItemSelected(item) then 24 | take = reaper.GetActiveTake(item) 25 | if take then 26 | take_name = reaper.GetTakeName(take) 27 | if #a_names > 0 then 28 | b_exist = false 29 | for k, v in ipairs(a_names) do 30 | b_exist = take_name == v and true 31 | end 32 | if not b_exist then 33 | a_names[#a_names + 1] = take_name 34 | end 35 | else 36 | first_take = take 37 | a_names[#a_names + 1] = take_name 38 | end 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -- apply merged_name to first selected item in track (if "apply_to_first_take" is 1) 45 | if #a_names > 0 then 46 | merged_name = table.concat(a_names, " + ") 47 | reaper.GetSetMediaItemTakeInfo_String(first_take, "P_NAME", merged_name, 1) 48 | end 49 | return merged_name 50 | end 51 | 52 | 53 | -- This function applies "merged take names" to the first takes of tracks 54 | -- 55 | function glue() 56 | local merged_name, item, take, take_name, source, filenamebuf 57 | reaper.Undo_BeginBlock() 58 | for i = 0, reaper.CountTracks(0) do 59 | merged_name = merge_track_take_names(reaper.GetTrack(0, i), 1) 60 | end 61 | 62 | -- GLUE ITEMS WITHOUT TIME SELECTION 63 | reaper.Main_OnCommand(40362, 0) 64 | 65 | -- remove "glued" string from take names 66 | for i = 0, reaper.CountSelectedMediaItems(0) do 67 | item = reaper.GetSelectedMediaItem(0, i) 68 | if item then 69 | take = reaper.GetActiveTake(item) 70 | if take then 71 | take_name = reaper.GetTakeName(take) 72 | if take_name:match(".glued") then 73 | reaper.GetSetMediaItemTakeInfo_String(take, "P_NAME", take_name:gsub(".glued",""), 1) 74 | end 75 | end 76 | end 77 | end 78 | reaper.Undo_EndBlock("Glue Named", -1) 79 | end 80 | 81 | glue() -------------------------------------------------------------------------------- /Item Editing/RCJacH_Quick add or edit take marker under mouse cursor.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | @author RCJacH 3 | @description Quick add or edit take marker under mouse cursor 4 | @link 5 | Github Repository https://github.com/RCJacH/ReaScript 6 | @version 1.0 7 | 8 | @about 9 | Opens the Edit Take Marker window if a take marker exists under the mouse cursor, else quick add a new one. 10 | ]] 11 | 12 | local CMD_ADD_TAKE_MARKER = 42391 13 | local CMD_EDIT_TAKE_MARKER = 42388 14 | 15 | function Main() 16 | local item, pos = reaper.BR_ItemAtMouseCursor() 17 | local take = reaper.GetActiveTake(item) 18 | local take_marker_count = reaper.GetNumTakeMarkers(take) 19 | reaper.Main_OnCommand(CMD_ADD_TAKE_MARKER, 0) 20 | local take_marker_count_after = reaper.GetNumTakeMarkers(take) 21 | if take_marker_count_after == take_marker_count then 22 | reaper.Main_OnCommand(CMD_EDIT_TAKE_MARKER, 0) 23 | end 24 | end 25 | 26 | reaper.Undo_BeginBlock() 27 | reaper.PreventUIRefresh(1) 28 | Main() 29 | reaper.PreventUIRefresh(-1) 30 | reaper.UpdateArrange() 31 | reaper.Undo_EndBlock("Quick add or edit take marker under mouse cursor", 0) 32 | -------------------------------------------------------------------------------- /Item Editing/RCJacH_Select Tracks with Regex.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Description: Select Tracks with Regex 3 | Version: 1.0a 4 | Author: RCJacH 5 | Reference: 6 | Changelog: 7 | * v1.0a (2021-05-05) 8 | + Initial Release. 9 | --]] 10 | 11 | local track_count = reaper.CountTracks(0) 12 | if track_count == 0 then return end 13 | 14 | local retval, regex = reaper.GetUserInputs("Search Pattern for Selecting Tracks", 1, "Lua Regex", "") 15 | if not retval then return end 16 | 17 | 18 | 19 | reaper.Undo_BeginBlock() 20 | 21 | for i = 0, track_count - 1 do 22 | local track = reaper.GetTrack(0, i) 23 | local retval, name = reaper.GetSetMediaTrackInfo_String(track, "P_NAME", "", false) 24 | local result = name:match(regex) 25 | reaper.SetTrackSelected(track, result ~= nil) 26 | end 27 | 28 | reaper.UpdateArrange() 29 | reaper.Undo_EndBlock2(0, "Select Tracks with Regex", -1) 30 | -------------------------------------------------------------------------------- /Item Editing/RCJacH_Split items under mouse cursor (obey snapping and selection).lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | @author RCJacH 3 | @description Split items under mouse (obey snapping and selection) 4 | @link 5 | Github Repository https://github.com/RCJacH/ReaScript 6 | @version 1.0 7 | 8 | @about 9 | Split selected items at mouse cursor (obey snapping), if no items are selected 10 | split only the item under mouse cursor, if there is no item under mouse cursor, 11 | split all items with confirmation. 12 | ]] 13 | 14 | local CMD_SPLIT_SELECTED_ITEM = reaper.NamedCommandLookup("_S&M_SPLIT10") 15 | 16 | 17 | function split_all_items(cursor_pos) 18 | local item_count = reaper.CountMediaItems(-1) 19 | reaper.Main_OnCommand(40513, 0) 20 | local confirm = reaper.ShowMessageBox("WARNING: This will split all items at mouse position!", "SPLIT ALL ITEMS?", 1) 21 | if confirm == 1 then 22 | reaper.Main_OnCommand(40757, 0) 23 | end 24 | end 25 | 26 | function split_item_under_mouse(item, cursor_pos) 27 | reaper.SetMediaItemSelected(item, true) 28 | reaper.Main_OnCommand(CMD_SPLIT_SELECTED_ITEM, 0) 29 | reaper.SetMediaItemSelected(item, false) 30 | reaper.SetMediaItemSelected(reaper.GetSelectedMediaItem(-1, 0), false) 31 | end 32 | 33 | function split_item(cursor_pos) 34 | local item, pos 35 | local screen_x, screen_y = reaper.GetMousePosition() 36 | local sel_item_count = reaper.CountSelectedMediaItems(-1) 37 | 38 | item, pos = reaper.BR_ItemAtMouseCursor() 39 | if sel_item_count > 0 then 40 | if reaper.IsMediaItemSelected(item) then 41 | reaper.Main_OnCommand(CMD_SPLIT_SELECTED_ITEM, 0) 42 | end 43 | return 44 | end 45 | 46 | if not item then 47 | item = reaper.GetItemFromPoint(screen_x, screen_y, true) 48 | end 49 | if not item then 50 | split_all_items(cursor_pos) 51 | return 52 | end 53 | 54 | split_item_under_mouse(item, cursor_pos) 55 | end 56 | 57 | function Main() 58 | local cursor_pos = reaper.GetCursorPosition() 59 | split_item(cursor_pos) 60 | reaper.SetEditCurPos(cursor_pos, 0, 0) 61 | end 62 | 63 | reaper.Undo_BeginBlock() 64 | reaper.PreventUIRefresh(1) 65 | Main() 66 | reaper.PreventUIRefresh(-1) 67 | reaper.UpdateArrange() 68 | reaper.Undo_EndBlock("Split items under mouse (obey snapping and selection", 0) 69 | -------------------------------------------------------------------------------- /Item Editing/RCJacH_Spread Items Horizontally.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ReaScript Name: Spread items horizontally 3 | Author: RCJacH 4 | Website: https://github.com/RCJacH/ReaScripts 5 | Version: 1.0 6 | 7 | Description: 8 | ------ 9 | 10 | ]] 11 | 12 | 13 | -- 14 | 15 | 16 | function main() 17 | reaper.Undo_BeginBlock() 18 | local place, pos, length 19 | -- Get selected item count 20 | num_items = reaper.CountSelectedMediaItems(0) 21 | 22 | -- Get base location 23 | 24 | -- Get selected items 25 | 26 | for i = 0, num_items - 1 do 27 | local item = reaper.GetSelectedMediaItem(0, i) 28 | pos = reaper.GetMediaItemInfo_Value(item, "D_POSITION") 29 | length = reaper.GetMediaItemInfo_Value(item, "D_LENGTH") 30 | if i == 0 then 31 | place = pos + length 32 | else 33 | reaper.SetMediaItemPosition(item, place, false) 34 | place = place + length 35 | end 36 | end 37 | 38 | 39 | reaper.Undo_EndBlock("Spread items horizontally", -1) 40 | end 41 | 42 | main() 43 | reaper.UpdateArrange() 44 | -------------------------------------------------------------------------------- /JSFX/Audio/AB Comparison - 1INnOUT Stereo.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: AB Comparison - 1INnOUT Stereo 3 | Author: RCJacH 4 | Release Date: Dec 2017 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.2 7 | Reference: 8 | Jonas Eriksson BeatFinder JSFX 9 | Audio Vitamin Contra 10 | About: 11 | This JSFX alternates between signals from different channels for objective 12 | comparison purposes. 13 | 14 | Instruction: 15 | 1. Set *# of Outputs* to number of stereo channels to compare. 16 | 2. Link plugin output to different track or hardware output. 17 | 3. Set desired switching beat length using *Switch Beat length*. 18 | Alternatively, set *Millisecond Mode* to > 0 for switching based on time 19 | 4. Set switching mode using *Mode*: 20 | Manual = Switch by pressing channel trigger; 21 | Sequential = Switch to the next channel; 22 | Random = Switch to a random different channel; 23 | Guess = Switch to a random channel, press trigger to show answer. 24 | 25 | Changelog: 26 | * v1.2 (2017-12-01) 27 | + Added a multiple output stereo version, reversed random input into random output. 28 | * v1.1 (2017-01-19) 29 | + Millisecond Mode. 30 | + Beat Mode works while not playing. 31 | + Guess Mode added Answer Display Time. 32 | + Limitation for user input. 33 | + Default channel initialization. 34 | # Triggers in Sequential/Random Mode changes to Manual Mode. 35 | # Clicking on time-line do not reset to output 1-2. 36 | # Guess Mode switches by triggering, rather than time. 37 | * v1.0 (2017-01-19) 38 | + Initial Release 39 | */ 40 | 41 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 42 | 43 | desc: AB Comparison - 1INnOut Stereo 44 | 45 | slider1: 2<1,8,1> # of Outputs 46 | slider2: 8<1,16,1> Switch Beat Length 47 | slider3: 0<0,5000,10> Millisecond Mode (when > 0) 48 | 49 | slider6: 0<0,2,1{Manual,Sequential,Random,Guessing}> Mode 50 | slider7: 2<0.5,5,0.1> Answer Displaying Time (s) 51 | 52 | slider11: 0<0,8,1{1-2,3-4,5-6,7-8,9-10,11-12,13-14,15-16,Guess}> OutChannel 53 | slider12: 0<0,8,1> -_OutCh 54 | 55 | in_pin:input L 56 | in_pin:input R 57 | out_pin:output 1 L 58 | out_pin:output 1 R 59 | out_pin:output 2 L 60 | out_pin:output 2 R 61 | out_pin:output 3 L 62 | out_pin:output 3 R 63 | out_pin:output 4 L 64 | out_pin:output 4 R 65 | out_pin:output 5 L 66 | out_pin:output 5 R 67 | out_pin:output 6 L 68 | out_pin:output 6 R 69 | out_pin:output 7 L 70 | out_pin:output 7 R 71 | out_pin:output 8 L 72 | out_pin:output 8 R 73 | 74 | @init 75 | i_diff = 0; 76 | i_result = 0; 77 | i_answerTime = 0; 78 | msCnt = 0; 79 | 80 | @slider 81 | i_maxchs = max(1, min(8, slider1)); 82 | i_beats = max(1, min(16, slider2)); 83 | i_ms = max(0, slider3 * srate * 0.001); 84 | mode = slider6; 85 | msCnt = 0; 86 | 87 | //Disable User Selection of *OutChannel* 88 | slider11 = i_result = mode == 3 ? 8 : slider12; 89 | 90 | @block 91 | 92 | beats_per_sample = ( tempo / 60 ) / srate; 93 | offset = 0; 94 | 95 | // Switch automatically only in sequential/random mode 96 | mode > 0 && mode < 3 ? ( 97 | // Check whether in millisecond mode or beat mode 98 | i_ms ? ( // ms Mode 99 | // Calculate the samplesblock approaching next switch 100 | msCnt += samplesblock; 101 | i_diff = msCnt + samplesblock >= i_ms ? i_ms - msCnt : 0; 102 | msCnt > i_ms ? msCnt = 0; 103 | // i_diff = how many samples left until next beat 104 | ) : ( // Beat Mode 105 | //WhyAddThis is Jonas's magic number. 106 | WhyAddThis = 0.97902 / (srate * ( 60 / tempo ) ); 107 | // Transport Playing 108 | play_state & 1 ? ( 109 | // Calculate the samplesblock approaching next switch 110 | curBeat = beat_position; 111 | nextBeat = ceil(curBeat); 112 | bp = (curBeat + beats_per_sample * samplesblock); 113 | i_diff = curBeat % i_beats == i_beats - 1 && 114 | bp >= nextBeat ? nextBeat - curBeat : 0; 115 | ) : ( 116 | msCnt += samplesblock * beats_per_sample; 117 | i_diff = msCnt + samplesblock * beats_per_sample >= i_beats ? 118 | i_beats - msCnt : 0; 119 | msCnt > i_beats ? msCnt = 0; 120 | ); 121 | // i_diff = how many beats left until next beat. 122 | ); 123 | ); 124 | 125 | // When a trigger button is pressed 126 | trigger ? ( 127 | // In Sequential/Random Mode, change to Manual Mode 128 | // In Manual Mode, switch output directly. 129 | mode < 3 ? ( 130 | mode ? slider6 = mode = 0; 131 | // Get button number 132 | i_trig = log10(trigger)/ log10(2); 133 | slider11 = slider12 = i_trig; 134 | ); 135 | // In Guess Mode, press any trigger to reveal answer. 136 | mode == 3 ? ( 137 | slider12 = floor(rand(i_maxchs - 1) + 0.5); 138 | slider11 = i_result = slider12; 139 | i_answerTime = slider7 * srate; 140 | ); 141 | trigger = 0; 142 | ); 143 | 144 | // When an answer is shown in Guess Mode 145 | i_answerTime ? ( 146 | // Check to see when to conceal *Output* to "Guess". 147 | i_answerTime = i_answerTime > 0 ? i_answerTime - samplesblock : 0; 148 | !i_answerTime ? slider11 = i_result = 8; 149 | ); 150 | 151 | @sample 152 | // Only calculate samples when beat changes within a samplesblock. 153 | i_diff ? ( 154 | (i_ms && i_diff - offset == 0) || 155 | (!i_ms && i_diff - WhyAddThis - offset * beats_per_sample <= 0) ? ( 156 | mode == 1 ? ( 157 | // Sequential Mode adds one to output. 158 | slider12 = (slider12 + 1) % i_maxchs; 159 | ):( 160 | // Random Mode generate a random and different output. 161 | while (i = floor(rand(i_maxchs - 1) + 0.5); i == slider12; ); 162 | slider12 = i; 163 | ); 164 | slider11 = i_result = slider12; 165 | i_diff = 0; 166 | ); 167 | offset += 1; 168 | ); 169 | s0 = spl0; s1 = spl1; 170 | spl0=spl1=0; 171 | sn = slider12 * 2; 172 | spl(sn) = s0; 173 | spl(sn + 1) = s1; 174 | -------------------------------------------------------------------------------- /JSFX/Audio/AB Comparison.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: AB Comparison 3 | Author: RCJacH 4 | Release Date: Jan 2017 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.1 7 | Reference: 8 | Jonas Eriksson BeatFinder JSFX 9 | Audio Vitamin Contra 10 | About: 11 | This JSFX alternates between signals from different channels for objective 12 | comparison purposes. 13 | 14 | Instruction: 15 | 1. Set different comparing items to sequential stereo channels. 16 | 2. Set **# of Inputs** to number of stereo channels to compare. 17 | 3. Set desired switching beat length using **Switch Beat length**. 18 | Alternatively, set **Millisecond Mode** to > 0 for switching based on time 19 | 4. Set switching mode using **Mode**: 20 | Manual = Switch by pressing channel trigger; 21 | Sequential = Switch to the next channel; 22 | Random = Switch to a random different channel; 23 | Guess = Switch to a random channel, press trigger to show answer. 24 | **Output Channel Display**s the current output channel. 25 | Changelog: 26 | * v1.1 (2017-01-19) 27 | + Millisecond Mode. 28 | + Beat Mode works while not playing. 29 | + Guess Mode added Answer Display Time. 30 | + Limitation for user input. 31 | + Default channel initialization. 32 | # Triggers in Sequential/Random Mode changes to Manual Mode. 33 | # Clicking on time-line do not reset to output 1-2. 34 | # Guess Mode switches by triggering, rather than time. 35 | * v1.0 (2017-01-19) 36 | + Initial Release 37 | */ 38 | 39 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 40 | 41 | desc: AB Comparison 42 | 43 | slider1: 2<1,8,1> # of Inputs 44 | slider2: 8<1,16,1> Switch Beat Length 45 | slider3: 0<0,5000,10> Millisecond Mode (when > 0) 46 | 47 | slider6: 0<0,2,1{Manual,Sequential,Random,Guessing}> Mode 48 | slider7: 2<0.5,5,0.1> Answer Displaying Time (s) 49 | 50 | slider11: 0<0,8,1{1-2,3-4,5-6,7-8,9-10,11-12,13-14,15-16,Guess}> Output Channel Display 51 | slider12: 0<0,8,1> -_OutCh 52 | 53 | in_pin:input 1 L 54 | in_pin:input 1 R 55 | in_pin:input 2 L 56 | in_pin:input 2 R 57 | in_pin:input 3 L 58 | in_pin:input 3 R 59 | in_pin:input 4 L 60 | in_pin:input 4 R 61 | in_pin:input 5 L 62 | in_pin:input 5 R 63 | in_pin:input 6 L 64 | in_pin:input 6 R 65 | in_pin:input 7 L 66 | in_pin:input 7 R 67 | in_pin:input 8 L 68 | in_pin:input 8 R 69 | out_pin:output L 70 | out_pin:output R 71 | 72 | @init 73 | i_diff = 0; 74 | i_result = 0; 75 | i_answerTime = 0; 76 | msCnt = 0; 77 | 78 | @slider 79 | i_maxchs = max(1, min(8, slider1)); 80 | i_beats = max(1, min(16, slider2)); 81 | i_ms = max(0, slider3 * srate * 0.001); 82 | mode = slider6; 83 | msCnt = 0; 84 | 85 | //Disable User Selection of *OutChannel* 86 | slider11 = i_result = mode == 3 ? 8 : slider12; 87 | 88 | @block 89 | 90 | beats_per_sample = ( tempo / 60 ) / srate; 91 | offset = 0; 92 | 93 | // Switch automatically only in sequential/random mode 94 | mode > 0 && mode < 3 ? ( 95 | // Check whether in millisecond mode or beat mode 96 | i_ms ? ( // ms Mode 97 | // Calculate the samplesblock approaching next switch 98 | msCnt += samplesblock; 99 | i_diff = msCnt + samplesblock >= i_ms ? i_ms - msCnt : 0; 100 | msCnt > i_ms ? msCnt = 0; 101 | // i_diff = how many samples left until next beat 102 | ) : ( // Beat Mode 103 | //WhyAddThis is Jonas's magic number. 104 | WhyAddThis = 0.97902 / (srate * ( 60 / tempo ) ); 105 | // Transport Playing 106 | play_state & 1 ? ( 107 | // Calculate the samplesblock approaching next switch 108 | curBeat = beat_position; 109 | nextBeat = ceil(curBeat); 110 | bp = (curBeat + beats_per_sample * samplesblock); 111 | i_diff = curBeat % i_beats == i_beats - 1 && 112 | bp >= nextBeat ? nextBeat - curBeat : 0; 113 | ) : ( 114 | msCnt += samplesblock * beats_per_sample; 115 | i_diff = msCnt + samplesblock * beats_per_sample >= i_beats ? 116 | i_beats - msCnt : 0; 117 | msCnt > i_beats ? msCnt = 0; 118 | ); 119 | // i_diff = how many beats left until next beat. 120 | ); 121 | ); 122 | 123 | // When a trigger button is pressed 124 | trigger ? ( 125 | // In Sequential/Random Mode, change to Manual Mode 126 | // In Manual Mode, switch output directly. 127 | mode < 3 ? ( 128 | mode ? slider6 = mode = 0; 129 | // Get button number 130 | i_trig = log10(trigger)/ log10(2); 131 | slider11 = slider12 = i_trig; 132 | ); 133 | // In Guess Mode, press any trigger to reveal answer. 134 | mode == 3 ? ( 135 | slider12 = floor(rand(i_maxchs - 1) + 0.5); 136 | slider11 = i_result = slider12; 137 | i_answerTime = slider7 * srate; 138 | ); 139 | trigger = 0; 140 | ); 141 | 142 | // When an answer is shown in Guess Mode 143 | i_answerTime ? ( 144 | // Check to see when to conceal *Output* to "Guess". 145 | i_answerTime = i_answerTime > 0 ? i_answerTime - samplesblock : 0; 146 | !i_answerTime ? slider11 = i_result = 8; 147 | ); 148 | 149 | @sample 150 | // Only calculate samples when beat changes within a samplesblock. 151 | i_diff ? ( 152 | (i_ms && i_diff - offset == 0) || 153 | (!i_ms && i_diff - WhyAddThis - offset * beats_per_sample <= 0) ? ( 154 | mode == 1 ? ( 155 | // Sequential Mode adds one to output. 156 | slider12 = (slider12 + 1) % i_maxchs; 157 | ):( 158 | // Random Mode generate a random and different output. 159 | while (i = floor(rand(i_maxchs - 1) + 0.5); i == slider12; ); 160 | slider12 = i; 161 | ); 162 | slider11 = i_result = slider12; 163 | i_diff = 0; 164 | ); 165 | offset += 1; 166 | ); 167 | 168 | spl0 = spl(slider12 * 2); 169 | spl1 = spl(slider12 * 2 + 1); 170 | -------------------------------------------------------------------------------- /JSFX/Audio/NoiseBuzz.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: NoiseBuzz 3 | Author: RCJacH 4 | Release Date: Jan 2017 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.0 7 | Reference: 8 | Wavesfactory SnareBuzz 9 | http://www.firstpr.com.au/dsp/pink-noise/ 10 | ReaRack Filter 11 | About: 12 | Audio triggered dynamic noise generator with ASR and filter. 13 | 14 | Instruction: 15 | 1. Set **Mix** slider to taste, use **Noise Gain** for additional volume control. 16 | 2. Select desired noise type. 17 | 3. (Optional) Use **Threshold**, **Attack**, **Release** to shape the envelope of 18 | the noise, with AR triggered by input audio exceed or fall under the threshold. 19 | 4. (Optional) Use **HPF**, **LPF**, and their **Resonance** controls to limit the 20 | frequency range of the generated noise. 21 | 5. (Optional) Adjust the detector **RMS** of the input level to smooth out the level 22 | of the generated noise. 23 | 6. You can also route the noise audio to channel 3-4 to further shape the noise, 24 | voiding the **Mix** control. 25 | Changelog: 26 | * v1.0a (2017-01-31) 27 | + Renamed to NoiseBuzz 28 | * v1.0 (2017-01-22) 29 | + Initial Release 30 | */ 31 | 32 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 33 | 34 | desc: NoiseBuzz 35 | 36 | slider1: 50<0, 100, 1> Mix (%) 37 | slider2: -6<-24,0,0.1> Noise Gain (dB) 38 | slider5: 0<0,1,1{White,Pink}> Noise Type 39 | slider6: -60<-120,0,0.1> Threshold 40 | slider7: 3<0,50,1>Attack (ms) 41 | slider8: 50<0,500,1>Release (ms) 42 | slider12: 20<0,22000,1> HPF 43 | slider13: 0<0,1> HPF Resonance 44 | slider14: 20000<0,22000,1> LPF 45 | slider15: 0<0,1> LPF Resonance 46 | slider20: 3<0,20,1>RMS (ms) 47 | slider22: 0<0,1,1{Mix to 1+2, 3+4}> Output Channel 48 | 49 | options: no_meter 50 | 51 | @init 52 | env = 0; 53 | 54 | @slider 55 | // Input Limiting 56 | slider6 = min(0, slider6); 57 | slider7 = max(0, slider7); 58 | slider8 = max(0, slider8); 59 | slider12 = max(0, min(22000, slider12)); 60 | slider13 = max(0, min(1, slider13)); 61 | slider14 = max(0, min(22000, slider14)); 62 | slider15 = max(0, min(1, slider15)); 63 | slider12 > slider14 ? slider12 = slider14; 64 | slider14 < slider12 ? slider14 = slider12; 65 | 66 | // Dynamic variables 67 | threshold = 10^(slider6/20); 68 | attack = slider7 ? 1/(srate * slider7 * 0.001) : 1; 69 | release = slider8 ? 1/(srate * slider8 * 0.001) : 1; 70 | 71 | // Filter variables 72 | hpcut = min(((slider12 * 2) / srate), 0.99); 73 | hpfb = slider13 + slider13 / (1 - hpcut); 74 | lpcut = min(((slider14 * 2) / srate), 0.99); 75 | lpfb = slider15 + slider15 / (1 - lpcut); 76 | 77 | // Level variables 78 | pink = slider5; 79 | rms = exp(-1/(max(min(slider20 * 0.001, 0.02), 0.00004) * srate)); 80 | vn = 10^((pink ? slider2-9 : slider2) / 20); 81 | org = 1 - (mix = (slider22 ? 1 : sqr(slider1) * 0.0001;)); 82 | mix *= vn; 83 | 84 | @sample 85 | in0 = spl0; 86 | in1 = spl1; 87 | 88 | // Generate random float level between -1 and 1. 89 | noise=rand(2) - 1; 90 | pink ? ( 91 | b0 = 0.99886 * b0 + noise * 0.0555179; 92 | b1 = 0.99332 * b1 + noise * 0.0750759; 93 | b2 = 0.96900 * b2 + noise * 0.1538520; 94 | b3 = 0.86650 * b3 + noise * 0.3104856; 95 | b4 = 0.55000 * b4 + noise * 0.5329522; 96 | b5 = -0.7616 * b5 - noise * 0.0168980; 97 | tmp = b0 + b1 + b2 + b3 + b4 + b5 + b6 + noise * 0.5362; 98 | b6 = noise * 0.115926; 99 | noise = tmp; 100 | ); 101 | vNoise = noise * mix; 102 | 103 | // Get input peak/rms level. 104 | slider1 ? ( 105 | ave = in0 * in0 + in1 * in1; 106 | runave = ave + rms * (runave - ave); 107 | inV = sqrt(runave); 108 | ):( 109 | ave = 0; 110 | inV = max(abs(in0), abs(in1)); 111 | ); 112 | 113 | // Is it attack or release phase? 114 | inV >= threshold ? ( 115 | stage == 0 ? ( 116 | env += attack; 117 | env >= 1 ? stage = 1; 118 | ) : 119 | stage == 1 ? ( 120 | env -= decay * (1 - sustain); 121 | env <= sustain ? stage == 2; 122 | ) : 123 | stage == 2 ? ( 124 | env = sustain; 125 | ); 126 | ) : ( 127 | env > 0 ? env -= release; 128 | stage = 0; 129 | ); 130 | env = max(0,min(1,env)); 131 | 132 | // Calculate Filters. 133 | hpn3 = hpn3 + hpcut * (vNoise - hpn3 + hpfb * (hpn3 - hpn4)); 134 | hpn4 = hpn4 + hpcut * (hpn3 - hpn4); 135 | lpn3 = lpn3 + lpcut * (vNoise - lpn3 + lpfb * (lpn3 - lpn4)); 136 | lpn4 = lpn4 + lpcut * (lpn3 - lpn4); 137 | vNoise = lpn4 - hpn4; 138 | 139 | // Noise Volume. 140 | outV = inV >= threshold ? env * (inV - threshold) : env * threshold; 141 | vNoise = vNoise * outV; 142 | 143 | // Output based on channel selection. 144 | slider22 == 0 ? ( 145 | spl0 = in0 * org + vNoise; 146 | spl1 = in1 * org + vNoise; 147 | ); 148 | slider22 == 1 ? ( 149 | spl0 = in0; 150 | spl1 = in1; 151 | spl2 = spl3 = vNoise; 152 | ); 153 | -------------------------------------------------------------------------------- /JSFX/Audio/RCBitRangeGain.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: RCBitRangeGain 3 | Author: RCJacH 4 | Release Date: Feb 2018 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.3 7 | Reference: 8 | AirWindows BitShiftGain 9 | AirWindows PurestGain 10 | About: 11 | Suedo bit based gain adjustment for float point audio. 12 | 1 bit ~= 6.0206 dB (doubling of distance). 13 | 14 | RCBitRangeGain solves two issues of the traditional dB-based gain plugin: 15 | 1. When doing static gain alternation (meaning no automation) by twice/half of the sound pressure, 16 | we often adjust the signal by 6dB. However 6dB is around 1.9952623149688795, rather than the 17 | integer 2 which we desired, thus the signal will expand in number of digits, and truncate when 18 | it reaches the length of limit of the bit depth used for calculation, cause some sort of distortion, 19 | although likely inaudible. This is resolved by the AirWindows BitShiftGain algorithm, which adjusts 20 | volume by the exact number that represents bit shifting rather than dB. 21 | 2. When doing dynamic gain alternation (with automation), there might occur a zipper noise, a sudden 22 | high frequency distortion like aliasing (like that of the digital square wave), when moving the 23 | volume slider too quickly. This is resolved by the AirWindows PurestGain algorithm, which does 24 | the volume change with smoothing effect. The result is no aliasing, but a bit slower reaction to 25 | changes. The original PurestGain fader has 4000 samples window of smooth effect, and I reduced it 26 | to 800, which in my opinion is a better balance between smoothness and reaction speed. 27 | 28 | By combining the two AirWindows algorithms, RCBitRangeGain can be used both statically and dynamically. 29 | Also I included other controls related to the basic concept so users can still break away from only 30 | doing 6dB gains, although when adjusting gains by anything other than bits will cause truncation. 31 | 32 | Instruction: 33 | * Use **Macro Shift** to increase or decrease audio input by 6.0206 dB per bit. 34 | * Use **Micro Shift** to fine adjust audio within 1 bit. 35 | * Use **Bit Ratio** to adjust the dB representation of one bit. 36 | * **Fader** is used as a controller of output volume in percentage after bit shifting. 37 | * Use **Fader Curve** to adjust the behavior of **Fader** slider. 38 | * Increase **Smooth Fader** if gain adjustment causes zipper noise. 39 | * **Pan** is used to balanced the volume of L-R channels. 40 | * Use **Pan Law (dB)** to determine whether the audio is attenuated or amplified when approaching center. 41 | * Increase **Smooth Pan** if pan adjustment causes zipper noise. 42 | Changelog: 43 | * v1.3 (2018-09-27) 44 | + Using PurestGain Fader algorithm to change pan, so panning should be smoother now. 45 | * v1.2 (2018-04-12) 46 | + Added Pan controls 47 | * v1.1 (2018-02-13) 48 | + Implemented the actual algorithm of AirWindows OpenSourced PurestGain with variables. 49 | * Changed fader action, NOT BACKWARD COMPATIBLE if you used fader 50 | * Renamed to RCBitRangeGain 51 | * v1.0a (2017-01-31) 52 | + Renamed to BitRangeGain 53 | * v1.0 (2017-01-11) 54 | + Initial Release 55 | */ 56 | 57 | // License: GPL- http://www.gnu.org/licenses/gpl.html 58 | 59 | desc: RCBitRangeGain 60 | 61 | slider1: 0<-16,16,1> Macro Shift (Bit ~= 6.0206 dB) 62 | slider2: 0<-100,100,0.000001> Micro Shift (% of a Bit) 63 | slider3: 1<0,3,0.25> Bit Ratio 64 | 65 | slider11: 100<0, 100, 0.0001> Fader (% of total) 66 | slider12: 2<0,10,0.1> Fader Curve 67 | slider13: 2<0.1,10,0.1> Smooth Fader 68 | 69 | slider21: 0<-100,100,0.1>Pan 70 | slider22: 0<-6,6,1.5>Pan Law (dB) 71 | slider23: 2<0.1,10,0.1> Smooth Pan 72 | 73 | in_pin: Input L 74 | in_pin: Input R 75 | out_pin: Output L 76 | out_pin: Output R 77 | 78 | @init 79 | vFade = 1; 80 | faderTgt = faderCrnt = 2 ^ ((slider11 * 0.01) ^ slider12) - 1; 81 | panCrnt = panTgt = 0.005 * slider21 + 0.5; 82 | 83 | @slider 84 | slider1 = min(max(-16, slider1),16); 85 | slider2 = min(max(-100, slider2),100); 86 | slider11 = min(max(0, slider11),100); 87 | shift = 2 ^ ((slider1 + slider2 * 0.01) * slider3); 88 | faderTgt = 2 ^ ((slider11 * 0.01) ^ slider12) - 1; 89 | faderSmooth = 400 * slider13; 90 | _faderSmooth = 1 / (faderSmooth + 1); 91 | 92 | panTgt = 0.005 * slider21 + 0.5; 93 | panlaw = 2 ^ ((slider22 + 6)/6); 94 | pancomp = (panlaw > 1.0 ? 1.0/panlaw : panlaw); 95 | panSmooth = 400 * slider23; 96 | _panSmooth = 1 / (panSmooth + 1); 97 | 98 | @sample 99 | faderCrnt = (((faderCrnt * faderSmooth) + faderTgt)) * _faderSmooth; 100 | vFade = faderCrnt; 101 | shift && vFade ? vFade *= shift; 102 | 103 | panCrnt = (((panCrnt * panSmooth) + panTgt)) * _panSmooth; 104 | 105 | adj = vFade; 106 | 107 | panlaw != 1.0 ? ( 108 | panlaw > 1.0 ? adj *= panlaw; 109 | panatt = 1.0 - abs(panCrnt * 2 - 1.0); 110 | adj *= pancomp+(1.0-pancomp)*(2.0/(2.0-panatt)-1.0); 111 | ); 112 | 113 | adj0 = adj1 = adj; 114 | adj1 *= panCrnt; 115 | adj0 *= (1.0 - panCrnt); 116 | 117 | spl0 *= adj0; spl1 *= adj1; 118 | -------------------------------------------------------------------------------- /JSFX/Audio/RCEveryGain.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: RCEveryGain 3 | Author: RCJacH 4 | Release Date: Feb 2018 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.5 7 | Reference: 8 | AirWindows BitShiftGain 9 | AirWindows PurestGain 10 | AirWindows EveryTrim 11 | About: 12 | Suedo bit based gain adjustment for float point audio. 13 | 1 bit ~= 6.0206 dB (doubling of distance). 14 | 15 | RCEveryGain solves two issues of the traditional dB-based gain plugin: 16 | 1. When doing static gain alternation (meaning no automation) by twice/half of the sound pressure, 17 | we often adjust the signal by 6dB. However 6dB is around 1.9952623149688795, rather than the 18 | integer 2 which we desired, thus the signal will expand in number of digits, and truncate when 19 | it reaches the length of limit of the bit depth used for calculation, cause some sort of distortion, 20 | although likely inaudible. This is resolved by the AirWindows BitShiftGain algorithm, which adjusts 21 | volume by the exact number that represents bit shifting rather than dB. 22 | 2. When doing dynamic gain alternation (with automation), there might occur a zipper noise, a sudden 23 | high frequency distortion like aliasing (like that of the digital square wave), when moving the 24 | volume slider too quickly. This is resolved by the AirWindows PurestGain algorithm, which does 25 | the volume change with smoothing effect. The result is no aliasing, but a bit slower reaction to 26 | changes. The original PurestGain fader has 4000 samples window of smooth effect, and I reduced it 27 | to 800, which in my opinion is a better balance between smoothness and reaction speed. 28 | 29 | By combining the two AirWindows algorithms, RCBitRangeGain can be used both statically and dynamically. 30 | Also I included other controls related to the basic concept so users can still break away from only 31 | doing 6dB gains, although when adjusting gains by anything other than bits will cause truncation. 32 | 33 | Instruction: 34 | * Use **Macro Shift** to increase or decrease audio input by 6.0206 dB per bit. 35 | * Use **Micro Shift** to fine adjust audio within 1 bit. 36 | * Use **Bit Ratio** to adjust the dB representation of one bit. 37 | * **Fader** is used as a controller of output volume in percentage after bit shifting. 38 | * Use **Fader Curve** to adjust the behavior of **Fader** slider. 39 | * Increase **Smooth Fader** if gain adjustment causes zipper noise. 40 | * Use Gain sliders for rough dialing. 41 | * Use Trim sliders for fine toning. 42 | Changelog: 43 | * v1.5 (2022-03-04) 44 | + Add Trim controls 45 | + Renamed to RCEveryGain 46 | * v1.3 (2018-09-27) 47 | + Using PurestGain Fader algorithm to change pan, so panning should be smoother now. 48 | * v1.2 (2018-04-12) 49 | + Added Pan controls 50 | * v1.1 (2018-02-13) 51 | + Implemented the actual algorithm of AirWindows OpenSourced PurestGain with variables. 52 | * Changed fader action, NOT BACKWARD COMPATIBLE if you used fader 53 | * Renamed to RCBitRangeGain 54 | * v1.0a (2017-01-31) 55 | + Renamed to BitRangeGain 56 | * v1.0 (2017-01-11) 57 | + Initial Release 58 | */ 59 | 60 | // License: GPL- http://www.gnu.org/licenses/gpl.html 61 | 62 | desc: RCEveryGain 63 | 64 | slider1: 0<-16,16,1>Macro Shift (Bit ~= 6.0206 dB) 65 | slider2: 0<-100,100,0.000001>Micro Shift (% of a Bit) 66 | slider3: 1<0,3,0.25>Bit Ratio 67 | 68 | slider11: 100<0, 100, 0.0001>Fader (% of total) 69 | slider12: 2<0,10,0.1>Fader Curve 70 | slider13: 2<0.1,10,0.1>Smooth Fader 71 | 72 | slider21: master_rough_db=0<-60, 60, 0.01>Master Gain 73 | slider22: left_rough_db=0<-60, 60, 0.01>Left Gain 74 | slider23: right_rough_db=0<-60, 60, 0.01>Right Gain 75 | slider24: center_rough_db=0<-60, 60, 0.01>Center Gain 76 | slider25: side_rough_db=0<-60, 60, 0.01>Side Gain 77 | 78 | slider31: 0<-6, 6, 0.000001>Master Trim 79 | slider32: 0<-6, 6, 0.000001>Left Trim 80 | slider33: 0<-6, 6, 0.000001>Right Trim 81 | slider34: 0<-6, 6, 0.000001>Center Trim 82 | slider35: 0<-6, 6, 0.000001>Side Trim 83 | 84 | in_pin: Input L 85 | in_pin: Input R 86 | out_pin: Output L 87 | out_pin: Output R 88 | 89 | 90 | @init 91 | gain = 1.0; 92 | fader_tgt = fader_gain = 2.0 ^ ((slider11 * 0.01) ^ slider12) - 1.0; 93 | 94 | AMP_dB_i = 0.11512925464970229; 95 | 96 | function block_splpos(cur, tgt*, i, slider, d*, splpos*) ( 97 | splpos = slider_next_chg(i, tgt); 98 | splpos > 0 ? ( 99 | cur = slider; 100 | ) : ( 101 | tgt = slider; 102 | splpos = samplesblock; 103 | ); 104 | d = (tgt - cur) / splpos; 105 | ); 106 | 107 | function sample_splpos(cur, tgt, i, d*, splpos*) 108 | global(cnt) 109 | instance() 110 | local() 111 | ( 112 | cnt == splpos ? ( 113 | d = 0.0; 114 | splpos = slider_next_chg(i, tgt); 115 | splpos > cnt ? ( 116 | d = (tgt - cur) / (splpos - cnt); 117 | ); 118 | ); 119 | ); 120 | 121 | @slider 122 | slider1 = min(max(-16.0, slider1),16.0); 123 | slider2 = min(max(-100.0, slider2),100.0); 124 | slider11 = min(max(0.0, slider11),100.0); 125 | shift = 2.0 ^ ((slider1 + slider2 * 0.01) * slider3); 126 | fader_tgt = 2.0 ^ ((slider11 * 0.01) ^ slider12) - 1.0; 127 | faderSmooth = 400.0 * slider13; 128 | _faderSmooth = 1.0 / (faderSmooth + 1.0); 129 | 130 | d_master_db = 0.0; 131 | d_left_db = 0.0; 132 | d_right_db = 0.0; 133 | d_center_db = 0.0; 134 | d_side_db = 0.0; 135 | 136 | @block 137 | 138 | cnt=0; 139 | d_master_db=0.0; 140 | d_left_db=0.0; 141 | d_right_db=0.0; 142 | d_center_db=0.0; 143 | d_side_db=0.0; 144 | 145 | block_splpos(master_db, master_tgt, 31, slider31, d_master_db, master_chg_splpos); 146 | block_splpos(left_db, left_tgt, 32, slider32, d_left_db, left_chg_splpos); 147 | block_splpos(right_db, right_tgt, 33, slider33, d_right_db, right_chg_splpos); 148 | block_splpos(center_db, center_tgt, 34, slider34, d_center_db, center_chg_splpos); 149 | block_splpos(side_db, side_tgt, 35, slider35, d_side_db, side_chg_splpos); 150 | 151 | @sample 152 | fader_gain = ((fader_gain * faderSmooth) + fader_tgt) * _faderSmooth; 153 | gain = fader_gain * shift; 154 | 155 | sample_splpos(master_db, master_tgt, 31, d_master_db, master_chg_splpos); 156 | sample_splpos(left_db, left_tgt, 32, d_left_db, left_chg_splpos); 157 | sample_splpos(right_db, right_tgt, 33, d_right_db, right_chg_splpos); 158 | sample_splpos(center_db, center_tgt, 34, d_center_db, center_chg_splpos); 159 | sample_splpos(side_db, side_tgt, 35, d_side_db, side_chg_splpos); 160 | 161 | center = (spl0 + spl1) * 0.5 * exp((center_rough_db + center_db) * AMP_dB_i); 162 | side = (spl0 - spl1) * 0.5 * exp((side_rough_db + side_db) * AMP_dB_i); 163 | 164 | l_gain = r_gain = gain * exp((master_rough_db + master_db) * AMP_dB_i); 165 | r_gain *= exp((right_rough_db + right_db) * AMP_dB_i); 166 | l_gain *= exp((left_rough_db + left_db) * AMP_dB_i); 167 | 168 | spl0 = (center + side) * l_gain; 169 | spl1 = (center - side) * r_gain; 170 | 171 | master_db += d_master_db; 172 | left_db += d_left_db; 173 | right_db += d_right_db; 174 | center_db += d_center_db; 175 | side_db += d_side_db; 176 | cnt += 1; 177 | -------------------------------------------------------------------------------- /JSFX/Audio/RCInflator.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: RCInflator 3 | Author: RCJacH 4 | Release Date: Aug 2021 5 | Link: 6 | https://github.com/RCJacH/ReaScripts 7 | https://forum.cockos.com/showthread.php?t=256286 8 | Version: 0.6 9 | Reference: 10 | tviler 11 | sai'ke 12 | Sony Oxford 13 | samu4199 14 | About: 15 | JSFX implementation of Sony Oxford Inflator algorithm, 16 | found on Gearspace. 17 | Changelog: 18 | * v0.6 (2021-08-15) 19 | + Refactor integers to floats 20 | + Clip dry signal before summing 21 | * v0.5.4 (2021-08-15) 22 | + Match slider range and steps to the original 23 | * v0.5.3 (2021-08-15) 24 | + Applies second clipping before output gain 25 | * v0.5.2 (2021-08-13) 26 | + Applies in/output gain to dry signal 27 | + Refactor for less CPU usage 28 | * v0.5.1 (2021-08-13) 29 | * Fix unsymmetrical waveshaping 30 | * v0.5 (2021-08-13) 31 | + Refactor code 32 | + Add absolute clipping at 6dB. 33 | + Add waveshapeOvershoot 34 | * v0.4 (2021-08-13) 35 | + Accurate decibel to float conversion. 36 | + Wrap waveshaper above 0dB. 37 | + Refactor waveshaper into function. 38 | * v0.3 (2021-08-11) 39 | + Apply waveshaper symmetrically. 40 | * Fix sign Error. 41 | * v0.2 (2021-08-09) 42 | * Cleaned up code. 43 | * v0.1 (2021-02-06) 44 | + Initial alpha release. 45 | */ 46 | 47 | desc:RCInflator 48 | 49 | slider1:0<-6, 12, 0.01>Input (dB) 50 | slider2:100<0, 100, 0.1>Effect (%) 51 | slider3:curve=0<-50, 50, 0.1>Curve 52 | slider4:clip=0<0,1,1{Off,On}>Clip 53 | slider5:0<-12, 0, 0.01>Output (dB) 54 | 55 | @init 56 | function process(in) 57 | local(s, s_2, s_3, out) 58 | global(curveA, curveB, curveC, curveD, 59 | clip, dry, dry2, in_db, out_db, wet) 60 | ( 61 | in *= in_db; 62 | s = abs(in); 63 | clip && s > 1.0 ? s = 1.0; 64 | s_2 = s * s; 65 | s_3 = s_2 * s; 66 | s = (s >= 2.0) ? ( 67 | 0.0 68 | ) : s > 1.0 ? ( 69 | 2.0 * s - s_2 70 | ) : ( 71 | curveA * s + curveB * s_2 + curveC * s_3 - curveD * (s_2 - 2.0 * s_3 + s_2 * s_2) 72 | ); 73 | 74 | out = sign(in) * s * wet + min(max(in * dry, -dry2), dry2); 75 | clip ? out = max(-1.0, min(1.0, out)); 76 | out * out_db 77 | ); 78 | 79 | @slider 80 | in_db = exp(0.11512925464970229 * slider1); 81 | wet = slider2 * 0.01; 82 | dry = 1.0 - wet; 83 | dry2 = dry * 2.0; 84 | out_db = exp(0.11512925464970229 * slider5); 85 | 86 | curvepct = curve * 0.01; 87 | // 1 + (curve + 50) / 100 88 | curveA = 1.5 + curvepct; 89 | // - curve / 50 90 | curveB = -(curvepct + curvepct); 91 | // (curve - 50) / 100 92 | curveC = curvepct - 0.5; 93 | // 1 / 16 - curve / 400 + curve ^ 2 / (4 * 10 ^ 4) 94 | curveD = 0.0625 - curve * 0.0025 + (curve * curve) * 0.000025; 95 | 96 | @sample 97 | 98 | spl0 = process(spl0); 99 | spl1 = process(spl1); 100 | -------------------------------------------------------------------------------- /JSFX/Audio/RCNoiseBuzz.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: RCNoiseBuzz 3 | Author: RCJacH 4 | Release Date: Jan 2017 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.3 7 | Reference: 8 | Wavesfactory SnareBuzz 9 | http://www.firstpr.com.au/dsp/pink-noise/ 10 | ReaRack Filter 11 | State Variable (Morphing) filter 12 | About: 13 | Audio triggered dynamic noise generator with ADSR and filter. 14 | 15 | Instruction: 16 | 1. Set **Mix** slider to taste, use **Noise Gain** for additional volume control. 17 | 2. Select desired noise type. 18 | 3. (Optional) Use **Threshold**, **Attack**, **Decay**, **Sustain**, **Release** 19 | to shape the envelope of the noise, with AR triggered by input audio exceed or 20 | fall under the threshold. 21 | 4. (Optional) Use **HPF**, **LPF**, and their **Resonance** controls to limit the 22 | frequency range of the generated noise. 23 | 5. (Optional) Adjust the detector **RMS** of the input level to smooth out the level 24 | of the generated noise. 25 | 6. You can also route the noise audio to channel 3-4 to further shape the noise, 26 | voiding the **Mix** control. 27 | Changelog: 28 | * v1.3 (2018-03-08) 29 | + Brown Noise Type. 30 | + MIDI Trigger. 31 | * Fixed sustain. 32 | * Renamed to RCNoiseBuzz since the update broke backward compatibility. 33 | * v1.2 (2017-04-08) 34 | + Width Control. 35 | * v1.1 (2017-03-11) 36 | + Decay & Sustain Control. 37 | * v1.0a (2017-01-31) 38 | * Renamed to NoiseBuzz. 39 | * v1.0 (2017-01-22) 40 | + Initial Release. 41 | */ 42 | 43 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 44 | 45 | desc: RCNoiseBuzz 46 | 47 | slider1: 50<0, 100, 1> Mix (%) 48 | slider2: -12<-24,24,0.1> Noise Gain (dB) 49 | slider3: 0<0,1,0.1> Width (Mono > Stereo) 50 | slider4: 2<0,2,1{Brown,Pink,White}>Color 51 | slider5: 1<0,1,0.001>Density 52 | slider6: 0<0,1>Smoother 53 | slider11: -60<-144,0,0.1> Threshold 54 | slider12: 3<0,50,1>Attack (ms) 55 | slider13: 0<0,500,1>Decay (ms) 56 | slider14: 100<0,100,1>Sustain (%) 57 | slider15: 50<0,500,1>Release (ms) 58 | slider21: 0<0,100>High Pass Frequency (Scale) 59 | slider22: 6<0,24>High Pass Resonance (dB) 60 | slider23: 0<0,100>High Pass % 61 | slider24: 100<0,100>Low Pass Frequency (Scale) 62 | slider25: 6<-12,24>Low Pass Resonance (dB) 63 | slider26: 0<0,100>Low Pass % 64 | slider31: 1<0,1,1{Static,Dynamic}> Dynamic Envelope 65 | slider32: 3<0,20,1>RMS (ms) 66 | slider41:-1<-1,127,1> MIDI Note (Set to -1 for Audio Triggered) 67 | slider42: 0<0,1,1{Mix to 1+2, 3+4}> Output Channel 68 | 69 | // options: no_meter 70 | 71 | @init 72 | 73 | env = 0; 74 | stage = 0; 75 | perSample = 1/srate; 76 | ms2sample = 0.001 * srate; 77 | min_inf = -150.0; 78 | pi = $pi; 79 | freqlog = log(1.059); 80 | 81 | rms[0] = 0.075; 82 | rms[1] = 0.25; 83 | rms[2] = 1; 84 | rms[3] = 0.56; 85 | rms[4] = sqrt(0.5); 86 | rms[5] = 0.125; 87 | 88 | ratio = 6; 89 | ratio[3] = srate / 44100; 90 | ratio[5] = 91 | ratio[2] = sqrt(ratio[3]); 92 | ratio[4] = ratio[2] * ratio[3]; 93 | ratio[0] = 1/ratio[2]; 94 | ratio[1] = 1; 95 | 96 | function gain(db, inf) ( db <= inf ? 0 : 10^(0.05 * db) ); 97 | 98 | function getOutput(input) ( 99 | input * dryPct + this.gN * wetPct; 100 | ); 101 | 102 | function genNoise(cur)( 103 | abs(cur) < smoothHi ? cur = rand(2) - 1: 104 | cur -= sign(cur) * rand(1 + abs(cur)) * smoothHiSqt; 105 | cur; 106 | ); 107 | 108 | // "Pinking" filter (Paul Kellet's economy method) from "DSP generation of 109 | // Pink (1/f) Noise". 110 | // http://www.firstpr.com.au/dsp/pink-noise/ 111 | function genPink(cur) 112 | instance(b0,b1,b2,b3,b4,b5,b6) 113 | local(pN) 114 | ( 115 | b0 = 0.99886 * b0 + cur * 0.0555179; 116 | b1 = 0.99332 * b1 + cur * 0.0750759; 117 | b2 = 0.96900 * b2 + cur * 0.1538520; 118 | b3 = 0.86650 * b3 + cur * 0.3104856; 119 | b4 = 0.55000 * b4 + cur * 0.5329522; 120 | b5 = -0.7616 * b5 - cur * 0.0168980; 121 | pN = b0 + b1 + b2 + b3 + b4 + b5 + b6 + cur * 0.5362; 122 | b6 = cur * 0.115926; 123 | pN; 124 | ); 125 | 126 | function colorNoise(color) instance(white, N, gN) local(tmp,a,lp)( 127 | white = genNoise(white); 128 | color == 0 ? ( //Brown 129 | !a ? a = 1 - 1 / ( 0.0082 * srate + 1); 130 | lp = a * (white + lp); 131 | N = lp; 132 | ): 133 | color == 1 ? ( //Pink 134 | N = this.genPink(white); 135 | ): 136 | color == 2 ? ( //White 137 | N = white; 138 | ); 139 | gN = N * gain; 140 | gN = abs(gN) >= (1 - slider5) ? gN : 0; //Density 141 | gN; 142 | ); 143 | 144 | function runFilter() instance(a0,a0.d,a1,a1.d,a2,a2.d,b1,b1.d,b2,b2.d,)( 145 | a0 += a0.d; a1 += a1.d; a2 += a2.d; b1 += b1.d; b2 += b2.d; ); 146 | 147 | function getFilter(input) instance(a0,a1,a2,b1,b2,mem1,mem2,mem3,mem4) 148 | local(out) ( 149 | out = a0*input+a1*mem1+a2*mem2-b1*mem3-b2*mem4; 150 | mem2 = mem1; mem1 = input; mem4 = mem3; mem3 = out; 151 | out; 152 | ); 153 | 154 | function applyFilter() instance(gN) ( 155 | hp.wet ? gN = hp.getFilter(gN) * hp.wet + (hp.dry ? gN * hp.dry); 156 | lp.wet ? gN = lp.getFilter(gN) * lp.wet + (lp.dry ? gN * lp.dry); 157 | ); 158 | 159 | @slider 160 | function fSlider(freq,res,pct,hp) 161 | instance(wet,dry,a0.tgt,a1.tgt,a2.tgt,b1.tgt,b2.tgt,f) 162 | local(sx,c,k,c1,c2,c3) 163 | ( 164 | sx = 16+freq*1.20103; 165 | f = floor(exp(sx*freqlog)*8.17742); 166 | (wet = pct) ? ( 167 | dry = 1 - wet; 168 | c = 2*f*perSample; 169 | res = f < 50 ? 1 : 10^(0.05*(-res+1.5)); 170 | //lp 171 | k = 0.5*res*sin(pi*c); 172 | c1 = 0.5*(1-k)/(1+k); 173 | c2 = (0.5+c1)*cos(pi*c); 174 | c3 = (0.5+c1+(hp?c2:-c2))*0.25; 175 | a0.tgt = 2*c3; 176 | a1.tgt = (hp?-4:4)*c3; 177 | a2.tgt = 2*c3; 178 | b1.tgt = -2*c2; 179 | b2.tgt = 2*c1; 180 | ); 181 | ); 182 | // Dynamic variables 183 | threshold = gain(slider11, min_inf); 184 | dAttack = slider12 ? 1/(slider12 * ms2sample) : 1; 185 | dDecay = slider13 ? 1/(slider13 * ms2sample) : 1; 186 | sustain = slider14 * 0.01; 187 | invSustain = 1 - sustain; 188 | dRelease = slider15 ? 1/(slider15 * ms2sample) : 1; 189 | 190 | // Filter variables 191 | bFilter = slider23 || slider26; 192 | hp.fSlider(slider21,slider22,slider23 * 0.01,1); 193 | lp.fSlider(slider24,slider25,slider26 * 0.01,0); 194 | 195 | // Level variables 196 | gain = rms[slider4] * ratio[slider4]; 197 | volume = gain(slider2, min_inf); 198 | smoothHi = max(1 - slider6, 0.5); 199 | smoothHiSqt = smoothHi ^ 2; 200 | 201 | rms = exp(-1/(max(min(slider32 * 0.001, 0.02), 0.00004) * srate)); 202 | dryPct = 1 - (wetPct = (slider42 ? 1 : sqr(slider1) * 0.0001;)); 203 | 204 | slider4 != color? ( 205 | s0.N = s0.gN = s1.N = s1.gN = 0; 206 | color = slider4; 207 | ); 208 | 209 | @block 210 | function fNode() instance(d,tgt,src) 211 | ( 212 | d = (tgt - src)/samplesblock; 213 | this = src; 214 | src = tgt; 215 | ); 216 | function fBlock() 217 | instance(a0,a1,a2,b1,b2) 218 | (a0.fNode(); a1.fNode(); a2.fNode(); b1.fNode(); b2.fNode(); ); 219 | bFilter ? (hp.wet ? hp.fBlock();lp.wet ? lp.fBlock();); 220 | 221 | slider41 > -1 ? ( 222 | while(midirecv(offset, msg1, msg2, msg3)) ( 223 | in_status = msg1 & $xF0; //Get incoming STATUS 224 | msg2 == slider41 ? ( 225 | iVel = in_status == $x90 ? msg3 : in_status == $x80 ? 0; 226 | invVel = iVel / 127; 227 | ); //msg2 228 | ); //while 229 | ); //slider41 230 | 231 | @sample 232 | 233 | // Get input peak/rms level. 234 | slider32 ? ( //RMS 235 | ave = spl0 * spl0 + spl1 * spl1; 236 | runave = ave + rms * (runave - ave); 237 | inV = sqrt(runave); 238 | ):( //Peak 239 | ave = 0; 240 | inV = max(abs(spl0), abs(spl1)); 241 | ); 242 | 243 | // Is it attack or release phase? 244 | (slider41 < 0 && inV >= threshold) || (slider41 >= 0 && iVel) ? ( 245 | !stage ? ( 246 | env += dAttack; 247 | env >= 1 ? stage = 1; 248 | ) : 249 | stage == 1 ? ( 250 | env -= dDecay * invSustain; 251 | env <= sustain ? stage = 2; 252 | ) : 253 | stage == 2 ? ( 254 | env = sustain; 255 | ); 256 | ) : ( 257 | env > 0 ? env -= dRelease; 258 | stage = 0; 259 | ); 260 | env = max(0, min(1, env)); 261 | 262 | // Noise Volume. 263 | slider41 < 0 && slider31 ? ( //Audio triggered and envelope following? 264 | outV = inV > threshold ? threshold + env * (inV - threshold) : env * threshold; 265 | ) : ( 266 | outV = env * (iVel ? invVel : 0.1); 267 | ); 268 | outV *= volume; 269 | 270 | // Generate random float level between -1 and 1. 271 | s0.colorNoise(slider4); 272 | slider3 ? s1.colorNoise(slider4); 273 | 274 | // Calculate Filters. 275 | bFilter ? (hp.wet ? hp.runFilter(); lp.wet ? lp.runFilter(); ); 276 | 277 | s0.gN *= outV; 278 | s0.applyFilter(); 279 | slider3 ? ( //Stereo 280 | s1.gN *= outV; 281 | s1.applyFilter(); 282 | mono=(s0.gN+s1.gN)*0.5; 283 | stereo=(s0.gN-s1.gN)*slider3*0.5; 284 | 285 | s0.gN = (mono + stereo) / max(slider3,1); 286 | s1.gN = (mono - stereo) / max(slider3,1); 287 | ) : ( //Mono 288 | s1.gN = s0.gN; 289 | ); 290 | 291 | // Output based on channel selection. 292 | slider42 == 0 ? ( 293 | spl0 = s0.getOutput(spl0); 294 | spl1 = s1.getOutput(spl1); 295 | ); 296 | slider42 == 1 ? ( 297 | spl0 = spl0; 298 | spl1 = spl1; 299 | spl2 = s0.gN; 300 | spl3 = s1.gN; 301 | ); 302 | 303 | 304 | @gfx 100 16 305 | gfx_y = 5; 306 | gfx_x = 32; 307 | gfx_r=gfx_b=0; 308 | gfx_g=gfx_a=1; 309 | gfx_drawstr("HPF = "); 310 | gfx_drawnumber(hp.f,0); 311 | gfx_drawstr(" Hz"); 312 | 313 | gfx_x=230; 314 | gfx_drawstr("LPF = "); 315 | gfx_drawnumber(lp.f,0); 316 | gfx_drawstr(" Hz"); 317 | -------------------------------------------------------------------------------- /JSFX/Game/RCMShip Perfect Pitch.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: RCMShip Perfect Pitch 3 | Author: RCJacH 4 | Release Date: Mar 2018 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.0rc1 7 | About: 8 | ## Description 9 | 10 | RCMShip (MShip stands for musicianship) Perfect Pitch is a little game for exercising Perfect Pitch, the ability to recognize notes without any reference, using adaptive method of randomization. 11 | 12 | ## How to Play 13 | 1. Click **START**, the synth will play a tone. 14 | 2. Guess the name of the tone by clicking on one of the note names. 15 | 3. Upon answering, you will be shown the correct answer, and your own answer if it differs. 16 | 4. Click **Next** to continue to next round. 17 | 18 | ## Features 19 | 20 | ### Adaptive 21 | Upon answering, the chance of appearance for the current note will be changed based on whether you've answered correctly and the win stream of the note. 22 | 23 | ### Selective 24 | Click on **Setup** to redirect to note setup page, where you can either manually select which note(s) to include in the game, or randomly generate a set number of notes based on currently selected ones. 25 | 26 | ### Configurative 27 | Click on **Config** for further preferences, such as Volume, note length, base octave, and octave span (octaves above base octave for note generation). You can also switch to MIDI mode so you can use samples or other synth as sound source. Right click on a button to return it to default value. 28 | 29 | 30 | Changelog: 31 | * v1.0pre2 (2018-04-05) 32 | # Fixed saving progress, but you have to save the project. 33 | # I will do a rewrite of the script later thus going back to pre-release. 34 | * v1.0rc2 (2018-03-28) 35 | # Fixed number on Random button not showing 36 | # Changed None/All button to red since it's somewhat destructive. 37 | * v1.0rc1 (2018-03-28) 38 | + GUI 39 | + Note Selection 40 | + Octaves 41 | + Winstream 42 | * v1.0pre1 (2018-03-12) 43 | + Initial Algorithm 44 | */ 45 | 46 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 47 | 48 | desc: RCMShip Perfect Pitch 49 | 50 | out_pin:output L 51 | out_pin:output R 52 | 53 | @init 54 | 55 | defChance = 256; 56 | maxChance = 128; 57 | minChance = 1; 58 | i_guess = -1; 59 | i_rnd = -1; 60 | 61 | !f_active ? f_active = $xFFF; 62 | smooth = 200; 63 | 64 | twoPi = $pi * 2; 65 | incBase = twoPi * 1 / srate; 66 | 67 | uix.init = 0; 68 | uix_stopped = 1; 69 | inc = 1; 70 | uix_settings = inc; memset(uix_settings, 0, 32); inc += 32; 71 | a_midi = inc; memset(a_midi, 0, 128); inc += 128; 72 | a_chance = inc; inc += 12; 73 | a_correct = inc; inc += 12; 74 | a_wrong = inc; inc += 12; 75 | a_winstream = inc; inc += 12; 76 | 77 | function fn_reset() ( 78 | // Reset All status 79 | memset(a_chance, defChance, 12); 80 | memset(a_correct, 0, 12); 81 | memset(a_wrong, 0, 12); 82 | memset(a_winstream, 0, 12); 83 | ); 84 | !a_chance[0] ? fn_reset(); 85 | 86 | function fn_stopGame() ( 87 | uix_stopped = 1; 88 | bt_start.status = 0; 89 | i_guess = i_rnd = -1; 90 | ); 91 | 92 | function fn_altChance(n,bCorrect) local(i)( 93 | // Write each guess to memory. 94 | bCorrect? ( 95 | a_correct[n] += 1; a_winstream[n] < 0 ? a_winstream[n] = 1 : a_winstream[n] += 1; 96 | ) : ( 97 | a_wrong[n] +=1; a_winstream[n] > 0 ? a_winstream[n] = 0 : a_winstream -= 1; 98 | ); 99 | 100 | // Increase or decrease chance of the played note by the square of winstream 101 | i = 2 ^ a_winstream[n]; 102 | a_chance[n] += bCorrect ? -i : i; 103 | a_chance[n] = max(minChance,min(maxChance, a_chance[n])); 104 | ); 105 | 106 | // Generate a random tone index 107 | function fn_rndTone() 108 | local(i, r, t, b) 109 | ( 110 | // Add up all tone chances 111 | i = t = 0; 112 | loop(12, 113 | f_active & 2 ^ i ? t += a_chance[i]; 114 | i += 1; 115 | ); 116 | // Generate random chance and find the matching index 117 | i = -1; 118 | b = 1; 119 | r = rand(t)|0; 120 | while( 121 | i += 1; 122 | f_active & 2 ^ i ? ( // If note is active 123 | r >= a_chance[i] ? r -= a_chance[i] : b = 0; 124 | ); 125 | // Break loop only if random chance is less than the play chance of an active tone index 126 | b; 127 | ); 128 | i; 129 | ); 130 | 131 | // Set the flags of manually clicked pitches for randomization 132 | function fn_setManual() local(i) ( 133 | f_manual = f_active; 134 | // Get number of notes of f_manual 135 | i = i_manualCnt = 0; 136 | loop(12, 137 | (f_active >> i)&1 ? i_manualCnt += 1; 138 | i += 1; 139 | ); 140 | ); 141 | 142 | // Generate f_active based on f_manual 143 | function fn_genRndflag() local(f_activeo) ( 144 | f_activeo = f_active = f_manual; 145 | while( 146 | loop(i_rndNum - i_manualCnt, 147 | while( 148 | n = rand(12)|0; 149 | f_active&(2 ^ n); 150 | ); 151 | f_active |= 2 ^ n; 152 | ); 153 | // Make sure to differenciate from the last set 154 | f_active == f_activeo; 155 | ); 156 | ); 157 | 158 | function pit2freq(pitch) ( 159 | 440 * (2 ^ ((pitch - 57) / 12)); 160 | ); 161 | 162 | function incEnv(tgt) instance(env) ( 163 | env != tgt ? env = (env * smooth + tgt) / (smooth + 1); 164 | ); 165 | 166 | function incPhase() instance(f,phase) ( 167 | phase += incBase * f; 168 | phase >= twoPi ? phase -= twoPi; 169 | ); 170 | 171 | function setFloat() ( 172 | this.env <= 0.00001 ? (this.env = 0; this.state = 0); 173 | this.value = sin(this.phase) * this.env * volume; 174 | ); 175 | 176 | function progress() ( 177 | this.cnt += 1; 178 | this.incPhase(note.f); 179 | this.incEnv(this.cnt <= this.len ? this.state : 0); 180 | ); 181 | 182 | function newNote(pitch, len) ( 183 | this.cnt = 0; 184 | this.phase = this.phase ? this.phase : 0; 185 | this.len = ceil(len * srate); 186 | this.env = this.env ? this.env : 0; 187 | this.state = 1; 188 | this.f = pit2freq(pitch); 189 | ); 190 | 191 | @block 192 | uix.midi ? ( 193 | i = 0; 194 | loop(128, 195 | a_midi[i] ? ( 196 | // Send note off to keys longer than set length 197 | a_midi[i] == 1 ? midisend(0,$x90,i,127); 198 | a_midi[i] > ceil(vb_nLen.value * srate) ? (midisend(0,$x80,i,0); a_midi[i] = 0;) 199 | : a_midi[i] += samplesblock; 200 | ); 201 | i += 1; 202 | ); 203 | ); 204 | 205 | @sample 206 | 207 | !uix.midi && (note.state || note.env) ? ( 208 | note.progress(); 209 | note.setFloat(); 210 | spl1 = spl0 = note.value; 211 | ); 212 | 213 | @gfx 600 600 214 | 215 | // Rescale keeping square shape 216 | uix.scale = min(gfx_w, gfx_h) / 600; 217 | uix.bt = 128 * uix.scale; 218 | uix.side = 32 * uix.scale; 219 | uix.grid = 8 * uix.scale; 220 | uix.g1 = uix.side; 221 | uix.g2 = uix.g1 + uix.bt + uix.grid; 222 | uix.g3 = uix.g2 + uix.bt + uix.grid; 223 | uix.g4 = uix.g3 + uix.bt + uix.grid; 224 | 225 | function uix_getPitName(pit) ( 226 | pit = pit == 0 ? "C" : 227 | pit == 1 ? (uix.pitName&2 ? "Db" : "C#" ) : 228 | pit == 2 ? "D" : 229 | pit == 3 ? (uix.pitName&2 ? "Eb" : "D#" ) : 230 | pit == 4 ? "E" : 231 | pit == 5 ? "F" : 232 | pit == 6 ? (uix.pitName&2 ? "Gb" : "F#" ) : 233 | pit == 7 ? "G" : 234 | pit == 8 ? (uix.pitName&2 ? "Ab" : "G#" ) : 235 | pit == 9 ? "A" : 236 | pit == 10 ? (uix.pitName&1 ? "A#" : "Bb" ) : 237 | pit == 11 ? "B" ; 238 | strcpy(#,pit); 239 | ); 240 | 241 | function uix_Hue2RGB(p, q, t) local(o, h) ( 242 | t < 0 ? t += 360; t > 360 ? t -= 360; 243 | o = t < 60 ? p + (q - p) * t / 60: 244 | t < 180 ? q: 245 | t < 240 ? p + (q - p) * (240 - t) / 60: p; 246 | o; 247 | ); 248 | 249 | function uix_HSL2RGB(h,s,l) local(q,p,h,r,g,b)( 250 | s = min(max(s,0),1); 251 | l = min(max(l,0),1); 252 | !s ? r=g=b=l : !l ? r=g=b=0 : l == 1 ? r=g=b=1 :( 253 | q = l < 0.5 ? l * (1 + s) : l + s - l * s; 254 | p = 2 * l - q; 255 | r = uix_Hue2RGB(p, q, h + 120); 256 | g = uix_Hue2RGB(p, q, h); 257 | b = uix_Hue2RGB(p, q, h - 120); 258 | ); 259 | gfx_r = r; gfx_g = g; gfx_b = b; 260 | ); 261 | 262 | function uix_getColor(x, a) local(h, s, l) ( 263 | !x ? ( // BG 264 | h = s = l = 0; 265 | ) : x == 1 ? ( // Button Inactive 266 | h = 146; s = 0.03; l = 0.30; 267 | ) : x == 2 ? ( // Button Active 268 | h = 146; s = 0.03; l = 0.78; 269 | ) : x == 3 ? ( // Button Correct 270 | h = 84; s = 1; l = 0.65; 271 | ) : x == 4 ? ( // Button Wrong 272 | h = 348; s = 1; l = 0.81; 273 | ) : x == 5 ? ( // Button Appending 274 | h = 54; s = 1; l = 0.83; 275 | ) : x == 6 ? ( // Button Setup 276 | h = 215; s = 0.68; l = 0.78; 277 | ); 278 | 279 | a < 0 ? l *= abs(a) : a > 0 ? l *= 1 + a; 280 | l = min(max(l,0),1); 281 | uix_settings[7] = h; 282 | uix_settings[8] = s; 283 | uix_settings[9] = l; 284 | ); 285 | 286 | function uix_setFont(m) ( 287 | gfx_setfont(1, "Arial", m * uix.scale, 'b'); 288 | ); 289 | 290 | function ui_drawText(x,y,w,h,s,m) local(sw,sh, l) ( 291 | uix_setFont(m); 292 | gfx_measurestr(s, sw, sh); 293 | // Set Alignment 294 | gfx_x = x + w * uix_settings[5/*xAlign*/] - sw * uix_settings[5/*xAlign*/]; 295 | gfx_y = y + h * uix_settings[6/*yAlign*/] - sh * uix_settings[6/*yAlign*/]; 296 | // Text Color based on BG color 297 | l = uix_settings[9]; 298 | uix_HSL2RGB(uix_settings[7], uix_settings[8], l > 0.5 ? l * 0.5 : l * 1.8);// * (uix_settings[8] < 0.7 && uix_settings[8] > 0.2 ? 0.5 : 1.6 )); 299 | gfx_drawstr(s); 300 | ); 301 | 302 | function ui_drawInit(x,y,w,h) local(color, i, hue, s, l, b) ( 303 | // Desaturate while LClicking 304 | b = this.status==2 || this.status == 8; 305 | hue = uix_settings[7]; s = uix_settings[8]; l = uix_settings[9]; 306 | b ? (hue -= 6; s < 0.5 ? l *= 0.9 : s *= 0.7; ); 307 | // Add border with darker color 308 | uix_HSL2RGB(hue,s,l*0.8); 309 | i = 0; 310 | loop(uix.grid >> (b ? 3 : 2), 311 | gfx_rect(x + i, y + i, w - i << 1, h - i << 1); 312 | i += 1; 313 | ); 314 | // Body 315 | uix_HSL2RGB(hue,s,l); 316 | gfx_rect(x + i, y + i, w - i << 1, h - i << 1); 317 | ); 318 | 319 | function ui_ctrlInit(x,y,w,h) instance(f_mouse, status) ( 320 | /* f_mouse 321 | 1 = mouse within rng 322 | 2 = LClick Down 323 | 4 = Single LClick Up 324 | 8 = Double LClick Down 325 | 16 = Double LClick Up 326 | 32 = RClick Down 327 | 64 = RClick Up 328 | */ 329 | /* status 330 | 0 = Active 331 | 1 = Inactive 332 | 2 = triggering 333 | 4 = triggered 334 | 8 = dragging 335 | */ 336 | mouse_x >= x && mouse_x <= x+w && mouse_y >= y && mouse_y <= y+h ? ( // Within border 337 | !mouse_cap || f_mouse&2 ? f_mouse |= 1; // Within range only when not dragging from another object 338 | f_mouse&1 && mouse_cap&1 ? ( 339 | f_mouse |= 2; status = 2; // LClick Down 340 | // For disabling click when dragging 341 | !uix_settings[23] ? uix_settings[23] = mouse_x; 342 | !uix_settings[24] ? uix_settings[24] = mouse_y; 343 | ); 344 | f_mouse&1 && mouse_cap&2 ? f_mouse |= 32; // RClick Down 345 | ) : f_mouse&1 ? f_mouse ~= 1; 346 | f_mouse&2 && !mouse_cap ? ( // LClick Up 347 | f_mouse ~= 2; 348 | !(f_mouse&1) ? status = 0; // Reset status if mouse goes off border of the object 349 | f_mouse&1 ? f_mouse |= 4; 350 | ); 351 | f_mouse&4 ? (status&2 ? status = 4;f_mouse ~= 4;); // LClick Up, used to trigger action 352 | f_mouse&32 && !mouse_cap ? ( // RClick Up 353 | f_mouse~=32; 354 | f_mouse&1 ? f_mouse|=64; 355 | ); 356 | !mouse_cap ? (init_pos = uix_settings[23] = uix_settings[24] = 0; status&8 ? status = 0;); 357 | ); 358 | 359 | 360 | // Get value by mouse position in relation to the object 361 | function ui_getCtrlValue(vStart,vEnd,stepsize,default,dir) local(mouse, input, tmp, step, axis) 362 | instance(rng, init_pos, f_mouse, status, value) ( 363 | rng = abs(vStart - vEnd); 364 | input = 0; 365 | axis = sign(dir); 366 | // If mouse pos changed while clicking, do not trigger click action 367 | status > 1 && f_mouse&2 && (mouse_x != uix_settings[23] || mouse_y != uix_settings[24]) ? status = 8; 368 | f_mouse&2 ? ( 369 | mouse = axis < 0 ? mouse_y : mouse_x; 370 | !init_pos ? init_pos = mouse; // Initialize position 371 | init_pos != mouse ? ( // If change of mouse position 372 | tmp = (mouse - init_pos); 373 | tmp *= mouse_cap&8/*Shift*/ ? 0.01 : 0.1; 374 | input += axis < 0 ? tmp : -tmp; // Add up positional difference in ratio 375 | init_pos = mouse; 376 | ); 377 | status&8 ? ( // Only when dragging 378 | step += input; 379 | // Add to value only when positional difference is greater than stepsize 380 | abs(step) > stepsize ? ( 381 | tmp = step < 0 ? ceil(step / stepsize) * stepsize : floor(step / stepsize) * stepsize; 382 | value -= tmp; 383 | step -= tmp; 384 | ); 385 | ); 386 | ): 387 | f_mouse&64/*Right*/ ? (value = default; f_mouse ~= 64;); // Reset upon releasing RClick 388 | value = max(min(value,max(vStart,vEnd)),min(vStart,vEnd)); //Limit value within rng 389 | value; 390 | ); 391 | 392 | // Draw a rectangular button 393 | function ui_drawButton(x,y,w,h,cBG,s,f) local(sw, sh) instance(click,triggered)( 394 | s != "" || this.status != 1 ? this.ui_ctrlInit(x,y,w,h); 395 | uix_getColor(cBG, 0); // Get HSL from color presets 396 | this.ui_drawInit(x,y,w,h); 397 | // Draw text in the center 398 | uix_settings[5/*xAlign*/] = uix_settings[6/*yAlign*/] = 0.5; 399 | ui_drawText(x,y,w,h,s,f); 400 | ); 401 | 402 | // Draw a Note variation of a button 403 | function ui_drawNote(i,m) local(bGuess, cBG, x, y, r, active) ( 404 | // Check Note status: Active? Guessed? 405 | active = f_active & 2 ^ i; 406 | bGuess = i_guess == i_rnd; 407 | // Get button color based on status 408 | cBG = m == 0 ? ( 409 | active ? ( 410 | (bGuess && i_guess == i) || (i_guess >=0 && !bGuess && i_rnd == i) ? 3 : // Made a guess and show Correct 411 | !bGuess && i_guess == i ? 4 : i_rnd != -1 && i_guess == -1 ? 5 : 2; 412 | ) : 1; 413 | ): m == 1 ? active ? 3 : 1; 414 | 415 | // Get position 416 | x = i % 4; y = (i / 4); 417 | x = !(x~0) ? uix.g1 : !(x~1) ? uix.g2 : !(x~2) ? uix.g3 : !(x~3) ? uix.g4; 418 | y = !(y~0) ? uix.g1 : !(y~1) ? uix.g2 : !(y~2) ? uix.g3 : !(y~3) ? uix.g4; 419 | this.ui_drawButton( 420 | x, y, uix.bt, uix.bt, cBG, 421 | uix_getPitName(i),36 422 | ); 423 | 424 | // Trigger actions 425 | m < 2 ? ( 426 | m == 0 && (uix_stopped || (i_guess != -1 && (i == i_guess || i == i_rnd))) ? ( 427 | this.status&2 ? ( 428 | !note.env ? note.newNote(vb_oct.value + (vb_span.value ? rand(vb_span.value)&0 * 12), vb_nLen.value); 429 | ): this.status&4 ? ( 430 | !note.env ? note.cnt = note.len; 431 | ); 432 | ); 433 | this.status&4 ? ( 434 | m == 0 ? ( 435 | !uix_stopped && f_active & 2 ^ i && i_guess == -1 ? ( 436 | i_guess = i; 437 | fn_altChance(i_rnd, i_guess == i_rnd); 438 | bt_start.status = 0; 439 | ); 440 | ): m == 1 ? ( 441 | f_active ~= 2 ^ i; 442 | fn_setManual(); 443 | fn_stopGame(); 444 | ); 445 | this.status = 0; 446 | ); 447 | ); 448 | 449 | // Show stats 450 | uix.noteDisplay ? ( 451 | r = a_correct[i] / (a_correct[i] + a_wrong[i]) * 100; 452 | s = uix.noteDisplay == 1 ? 453 | strcat(strcat(sprintf(#,"%d",a_correct[i]),":"), sprintf(#,"%d",a_wrong[i])) : uix.noteDisplay == 2 ? 454 | strcat(sprintf(#,"%.2f", r),"%"); 455 | uix_settings[5/*xAlign*/] = 0.5; uix_settings[6/*yAlign*/] = 0.85; 456 | ui_drawText(x,y,uix.bt,uix.bt,s,24); 457 | ); 458 | ); 459 | 460 | // A static variation of button (no triggered action) 461 | function ui_drawLabel(x,y,w,h,s)( 462 | this.status = 1; 463 | this.ui_drawButton(x, y, w, h, 1, s, 24); 464 | ); 465 | 466 | // A minimalistic control 467 | function ui_drawDragBox(x,y,w,h,vStart,vEnd,stepsize,default) local (knobsize, tmp, digit, s, qh, knob.pos) 468 | instance(rng, value) ( 469 | this.ui_ctrlInit(x,y,w,h); 470 | this.ui_getCtrlValue(vStart, vEnd, stepsize, default, -1); 471 | knobsize = 4; 472 | knob.pos = (rng + value - vEnd) / (rng) * (h - knobsize << 2); 473 | 474 | // Handle 475 | uix_HSL2RGB(uix_settings[7] - 52, 1 - uix_settings[8], uix_settings[9]); 476 | gfx_circle(x + w * 0.1, y + knobsize << 1 + (h - knobsize << 2) - knob.pos,knobsize,1,1); 477 | 478 | value; 479 | ); 480 | 481 | // A dragbox with value showing 482 | function ui_drawValueBox(x,y,w,h,vStart,vEnd,stepsize,default,unit) local (knobsize, tmp, digit, s, qh) 483 | instance(rng, value, knob.pos) ( 484 | // Draw Box 485 | uix_getColor(3, 0); 486 | this.ui_drawInit(x,y,w,h); 487 | uix_setFont(30); 488 | this.ui_drawDragBox(x,y,w,h,vStart,vEnd,stepsize,default); 489 | 490 | // Draw Value Text 491 | digit = stepsize < 0.1 ? "%.2f" : stepsize < 1 ? "%.1f": "%d"; 492 | s = strcat(sprintf(#, digit, max(vStart,vEnd)), unit); 493 | gfx_measurestr(s, sw, sh); 494 | 495 | s = strcat(sprintf(#, digit, value), unit); 496 | uix_settings[5/*xAlign*/] = uix_settings[6/*yAlign*/] = 0.5; 497 | uix_getColor(3, 0); 498 | ui_drawText(x,y,w,h,s,24); 499 | 500 | // No action triggered upon releasing LClick 501 | this.status&4 ?(this.status = 0;); 502 | value; 503 | ); 504 | 505 | //************************* 506 | // GFX 507 | //************************* 508 | 509 | // Initiate default values 510 | !b_init ? ( 511 | uix.noteDisplay = 1; 512 | vb_nLen.value = 1.5; 513 | vb_volume.value = -6; 514 | vb_oct.value = 4; 515 | vb_span.value = 0; 516 | b_init = 1; 517 | ); 518 | 519 | // Background 520 | gfx_r = gfx_g = gfx_b = 0; 521 | gfx_rect(0,0,gfx_w,gfx_h); 522 | 523 | uix_getColor(1, 0); 524 | uix_HSL2RGB(uix_settings[7],uix_settings[8],uix_settings[9]); 525 | uix_settings[5/*xAlign*/] = 1; uix_settings[6/*yAlign*/] = 0.8; 526 | ui_drawText(gfx_w - uix.side - uix.bt, 0, uix.bt, uix.side, "© RCJacH", 18); 527 | 528 | 529 | // Draw notes 530 | uix_settings[0] < 2 ? ( 531 | bt_note0.ui_drawNote(0,uix_settings[0/*Page ID*/]); 532 | bt_note1.ui_drawNote(1,uix_settings[0/*Page ID*/]); 533 | bt_note2.ui_drawNote(2,uix_settings[0/*Page ID*/]); 534 | bt_note3.ui_drawNote(3,uix_settings[0/*Page ID*/]); 535 | bt_note4.ui_drawNote(4,uix_settings[0/*Page ID*/]); 536 | bt_note5.ui_drawNote(5,uix_settings[0/*Page ID*/]); 537 | bt_note6.ui_drawNote(6,uix_settings[0/*Page ID*/]); 538 | bt_note7.ui_drawNote(7,uix_settings[0/*Page ID*/]); 539 | bt_note8.ui_drawNote(8,uix_settings[0/*Page ID*/]); 540 | bt_note9.ui_drawNote(9,uix_settings[0/*Page ID*/]); 541 | bt_note10.ui_drawNote(10,uix_settings[0/*Page ID*/]); 542 | bt_note11.ui_drawNote(11,uix_settings[0/*Page ID*/]); 543 | ); 544 | 545 | bt_setup.ui_drawButton(uix.g1, uix.g4, uix.bt, uix.bt, 6, uix_settings[0] ? "<" : "Setup",36); 546 | bt_setup.status&4 ? ( 547 | uix.init = 0; 548 | fn_setManual(); 549 | uix_settings[0] = uix_settings[0] != 1 ? 1 : 0; 550 | bt_setup.status = 0; 551 | ); 552 | 553 | uix_settings[0/*Page ID*/] == 0 ? ( 554 | bt_display.ui_drawButton(uix.g2, uix.g4, uix.bt, uix.bt, 2, uix.noteDisplay&1?"Ratio":uix.noteDisplay&2?"Percent":"Score",36); 555 | bt_display.status&4 ? ( 556 | uix.noteDisplay += 1; 557 | uix.noteDisplay > 2 ? uix.noteDisplay = 0; 558 | bt_display.status = 0; 559 | ); 560 | 561 | uix_stopped ? bt_play.status = 1; 562 | bt_play.ui_drawButton(uix.g3, uix.g4, uix.bt, uix.bt, bt_play.status != 1 ? 3 : 1, "Replay",36); 563 | bt_play.status&4 ? ( 564 | note.newNote(i_rndMIDI, vb_nLen.value); 565 | bt_play.status = 0; 566 | ); 567 | 568 | bt_start.ui_drawButton(uix.g4, uix.g4, uix.bt, uix.bt, bt_start.status != 1 ? 5 : 1, uix_stopped ? "START" : "Next",36); 569 | bt_start.status&4 ? ( 570 | uix_stopped || i_guess != -1 ? ( 571 | // uix.fanfare ? ( 572 | // bt_setup.status = 1; 573 | // i = 0; 574 | // loop(16, 575 | // uix.fanfare == 2 ? ( 576 | // note.newNote(rand(12) + 48, 0.3); 577 | // while( 578 | // note.env || note.state; 579 | // ); 580 | // ) : 581 | // uix.fanfare == 1 ? ( 582 | // i < 13 ? !note.env ? note.newNote( 583 | // 24 + rand(12)&0 + 584 | // i == 0 ? 2 : 585 | // i == 1 ? 4 : 586 | // i == 2 ? 7 : 587 | // i == 3 ? 12 : 588 | // i == 4 ? 16 : 589 | // i == 5 ? 12 : 590 | // i == 6 ? 14 : 591 | // i == 7 ? 16 : 592 | // i == 8 ? 19 : 593 | // i == 9 ? 14 : 594 | // i == 10 ? 16 : 595 | // i == 11 ? 19 : 596 | // i == 12 ? 23 : 597 | // i == 13 ? 24 598 | // , i < 13 ? 0.1: 0.4 599 | // ); 600 | // ); 601 | // i += 1; 602 | // ); 603 | // bt_setup.status = 0; 604 | // ); 605 | i_rnd = fn_rndTone(); 606 | i_rndMIDI = max(0,min(127,i_rnd + (vb_oct.value + (vb_span.value ? rand(vb_span.value)&0 : 0)) * 12)); 607 | i_guess = -1; 608 | uix.midi ? a_midi[i_rndMIDI] = 1 : note.newNote(i_rndMIDI, vb_nLen.value); 609 | uix_stopped ? (uix_stopped = 0; bt_play.status = 0;); 610 | ); 611 | bt_start.status = 1; 612 | ); 613 | 614 | ): uix_settings[0/*Page ID*/] == 1 ? ( 615 | bt_all.ui_drawButton(uix.g2, uix.g4, uix.bt, uix.bt, 4, f_active == $xFFF ? "None" : "All",36); 616 | bt_all.status&4 ? ( 617 | fn_stopGame(); 618 | f_active = f_active == $xFFF ? 0 : $xFFF; 619 | fn_setManual(); 620 | bt_all.status = 0; 621 | ); 622 | lb_rnd.ui_drawButton(uix.g3, uix.g4, uix.bt, uix.bt, f_active == $xFFF ? 1:3, "Random",36); 623 | i_rndNum = db_rndNum.ui_drawDragBox(uix.g3, uix.g4, uix.bt, uix.bt, 2, 12, 1, i_manualCnt + 2); 624 | db_rndNum.status&4 ? ( 625 | fn_stopGame(); 626 | fn_genRndflag(); 627 | db_rndNum.status = 0; 628 | ); 629 | uix_settings[5/*xAlign*/] = 0.5; uix_settings[6/*yAlign*/] = 0.85; 630 | ui_drawText(uix.g3, uix.g4, uix.bt, uix.bt, sprintf(#,"%d", i_rndNum),24); 631 | bt_config.ui_drawButton(uix.g4, uix.g4, uix.bt, uix.bt, 6, "Config",36); 632 | bt_config.status&4 ? ( 633 | uix_settings[0] = 2; 634 | bt_config.status = 0; 635 | ); 636 | ): uix_settings[0/*Page ID*/] == 2 ? ( 637 | lb_volume.ui_drawLabel(uix.g1, uix.g1, uix.bt, uix.bt, "Volume:"); 638 | vb_volume.ui_drawValueBox(uix.g2, uix.g1, uix.bt, uix.bt, -60, 0, 0.1, -6, " dB"); 639 | lb_nLen.ui_drawLabel(uix.g1, uix.g2, uix.bt, uix.bt, "Note Length:"); 640 | vb_nLen.ui_drawValueBox(uix.g2, uix.g2, uix.bt, uix.bt, 0.5, 5, 0.5, 1.5, " ms"); 641 | 642 | 643 | lb_oct.ui_drawLabel(uix.g3, uix.g1, uix.bt, uix.bt, "Base Octave:"); 644 | vb_oct.ui_drawValueBox(uix.g4, uix.g1, uix.bt, uix.bt, 0, 10, 1, 4, " Oct"); 645 | lb_span.ui_drawLabel(uix.g3, uix.g2, uix.bt, uix.bt, "Octave Span:"); 646 | vb_span.ui_drawValueBox(uix.g4, uix.g2, uix.bt, uix.bt, 0, 4, 1, 0, " Octs"); 647 | 648 | lb_fanfare.ui_drawLabel(uix.g1, uix.g3, uix.bt, uix.bt, ""); 649 | bt_fanfare.ui_drawButton(uix.g2, uix.g3, uix.bt, uix.bt, !uix.fanfare ? 1:(2), uix.fanfare == 1 ? "On":uix.fanfare == 2 ? "Random" : "",36); 650 | // bt_fanfare.status&4 ? ( 651 | // uix.fanfare += 1; 652 | // uix.fanfare > 2 ? uix.fanfare = 0; 653 | // bt_fanfare.status = 0; 654 | // ); 655 | 656 | lb_pitName.ui_drawLabel(uix.g3, uix.g3, uix.bt, uix.bt, "Pitch Name"); 657 | bt_pitName.ui_drawButton(uix.g4, uix.g3, uix.bt, uix.bt, 2, uix.pitName == 1 ? "Sharp":uix.pitName == 2 ? "Flat" : "C",36); 658 | bt_pitName.status&4 ? ( 659 | uix.pitName += 1; 660 | uix.pitName > 2 ? uix.pitName = 0; 661 | bt_pitName.status = 0; 662 | ); 663 | 664 | bt_reset.ui_drawButton(uix.g2, uix.g4, uix.bt, uix.bt, 4, "Reset",36); 665 | bt_reset.status&4 ? ( 666 | fn_reset(); 667 | bt_reset.status = 0; 668 | ); 669 | 670 | bt_btStyle.ui_drawButton(uix.g4, uix.g4, uix.bt, uix.bt, 1, uix.btStyle ? "Flash":"",36); 671 | // bt_btStyle.status&4 ? ( 672 | // uix.btStyle = !uix.btStyle; 673 | // bt_btStyle.status = 0; 674 | // ); 675 | 676 | bt_trig.ui_drawButton(uix.g3, uix.g4, uix.bt, uix.bt, 2, uix.midi ? "MIDI":"Sine",36); 677 | bt_trig.status&4 ? ( 678 | uix.midi = !uix.midi; 679 | bt_trig.status = 0; 680 | ); 681 | 682 | 683 | ); 684 | 685 | volume = 2^(vb_volume.value/6); 686 | uix.init = 1; -------------------------------------------------------------------------------- /JSFX/MIDI/RC MIDI Channel Router.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: RC MIDI Channel Router 3 | Author: RCJacH 4 | Release Date: Jun 2019 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.1.3 7 | About: 8 | ## Description 9 | 10 | RC MIDI Channel Router is a MIDI plugin to send each Note-On/Note-Off/CC event to assigned Channel(s) accordingly. 11 | 12 | ## How to Use 13 | 1. Use Left Mouse Button to work with MIDI channel assignments; 14 | Use Right Mouse Button to work with MIDI Bus. 15 | 2. Select channel using channel select buttons number 1-16. 16 | 3. Press (and drag) Note button below to toggle note send state. 17 | Green means note will be send to selected Channel upon receiving input. 18 | Red means not send. 19 | 4. You can switch to separate CCs assignment window by pressing the CC button at the top-right position. 20 | 21 | There are four extra buttons located at the top-center of the plugin. 22 | Reset: Reseting all notes and ccs of all channels to default setting. 23 | All: Turn all notes/cc of the current channel on (green). 24 | None: Turn all notes/cc of the current channel off (red). 25 | Flip: Invert all notes/cc of the current channel. 26 | 27 | Changelog: 28 | * v1.1.3 (2022-12-04) 29 | * Fix some settings not saved in serialize 30 | * Fix wrong reaction of some controls in cc mode 31 | * v1.1.2 (2022-11-01) 32 | * Fix flag overflow on bus 15 and 16 33 | * Fix note slipping through bus 1 when no flag is on 34 | * Fix sliders not updating when clicking on GUI 35 | * v1.1.1 (2022-03-07) 36 | * Fix Note button not working 37 | * v1.1 (2022-03-07) 38 | + Allow MIDI Bus output assignment 39 | * v1.0 (2019-06-30) 40 | + Initial Release 41 | */ 42 | 43 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 44 | 45 | desc: RC MIDI Channel Router 46 | 47 | slider1: cur_ch=0<0,15,1>-Channel 48 | slider2: select_mode=0<0,1,1>-Note/CC 49 | slider3: select_bus=0<0,1,1>-Channel/Bus 50 | slider10: i_note=0<0,127,1>-Select Note 51 | slider11: ch1=0<0,1,1>-Channel 1 52 | slider12: ch2=0<0,1,1>-Channel 2 53 | slider13: ch3=0<0,1,1>-Channel 3 54 | slider14: ch4=0<0,1,1>-Channel 4 55 | slider15: ch5=0<0,1,1>-Channel 5 56 | slider16: ch6=0<0,1,1>-Channel 6 57 | slider17: ch7=0<0,1,1>-Channel 7 58 | slider18: ch8=0<0,1,1>-Channel 8 59 | slider19: ch9=0<0,1,1>-Channel 9 60 | slider20: ch10=0<0,1,1>-Channel 10 61 | slider21: ch11=0<0,1,1>-Channel 11 62 | slider22: ch12=0<0,1,1>-Channel 12 63 | slider23: ch13=0<0,1,1>-Channel 13 64 | slider24: ch14=0<0,1,1>-Channel 14 65 | slider25: ch15=0<0,1,1>-Channel 15 66 | slider26: ch16=0<0,1,1>-Channel 16 67 | 68 | @init 69 | 70 | ext_midi_bus = 1.0; 71 | 72 | STATUS_NOTE_ON = $x90; // Note On Message 73 | STATUS_NOTE_OFF = $x80; // Note Off Message 74 | STATUS_CC = $xB0; // CC Message 75 | 76 | ALL_FLAGS = 65535; 77 | INITIAL_VALUE = 1; 78 | click = -1; 79 | flip_mode = -1; 80 | 81 | inc = 0; 82 | a_notes_ch = inc; inc += 128; 83 | a_notes_bus = inc; inc += 128; 84 | a_ccs_ch = inc; inc += 128; 85 | a_ccs_bus = inc; inc += 128; 86 | a_gridx = inc; inc += 12; 87 | a_gridy = inc; inc += 14; 88 | 89 | w = 48; 90 | h = 24; 91 | br = 2; 92 | br2 = br*2; 93 | 94 | function setup_grid(inc) 95 | local(i, tmp) 96 | global(w, h, br, br2, a_gridx, a_gridy) 97 | ( 98 | i = 0; 99 | tmp = br2; 100 | loop(12, 101 | a_gridx[i] = tmp; 102 | tmp += w + br; 103 | i += 1; 104 | ); 105 | i = 0; 106 | tmp = br2; 107 | loop(14, 108 | a_gridy[i] = tmp; 109 | tmp += (i==2 ? br2:h + br); 110 | i += 1; 111 | ); 112 | ); 113 | 114 | setup_grid(inc); 115 | a = a_gridx[1]; 116 | a2 = a_gridy[1]; 117 | 118 | function get_midi_channel_value(i,t) 119 | ( 120 | (t == 0) ? a_notes_ch[i] : 121 | (t == 1) ? a_ccs_ch[i]; 122 | ); 123 | 124 | function get_midi_bus_value(i,t) 125 | ( 126 | (t == 0) ? a_notes_bus[i] : 127 | (t == 1) ? a_ccs_bus[i]; 128 | ); 129 | 130 | function set_default(is_cc) ( 131 | is_cc ? ( 132 | memset(a_ccs_ch, INITIAL_VALUE, 128); 133 | memset(a_ccs_bus, INITIAL_VALUE, 128); 134 | ) : ( 135 | memset(a_notes_ch, INITIAL_VALUE, 128); 136 | memset(a_notes_bus, INITIAL_VALUE, 128); 137 | ); 138 | ); 139 | 140 | function get_flag(v) 141 | global() 142 | local() 143 | ( 144 | 2.0^v 145 | ); 146 | 147 | function set_all_values(is_all, is_bus) 148 | global(select_mode, cur_ch, cur_bus, a_notes_bus, a_notes_ch, a_ccs_bus, a_ccs_ch) 149 | local(i, flag, mem) 150 | ( 151 | is_bus ? ( 152 | flag = get_flag(cur_bus); 153 | mem = select_mode ? a_ccs_bus : a_notes_bus; 154 | ) : ( 155 | flag = get_flag(cur_ch); 156 | mem = select_mode ? a_ccs_ch : a_notes_ch; 157 | ); 158 | i=0; 159 | loop(128, 160 | is_all ? ( 161 | mem[i] |= flag; 162 | ) : ( 163 | mem[i] |= flag; 164 | mem[i] ~= flag; 165 | ); 166 | i += 1; 167 | ); 168 | ); 169 | 170 | function set_value(is_cc, is_bus, i, v, is_off) 171 | local(mem, flag) 172 | global(a_notes_bus, a_notes_ch, a_ccs_bus, a_ccs_ch) 173 | ( 174 | is_cc ? ( 175 | mem = is_bus ? a_ccs_bus : a_ccs_ch; 176 | ) : ( 177 | mem = is_bus ? a_notes_bus : a_notes_ch; 178 | ); 179 | flag = get_flag(v); 180 | is_off ? ( 181 | mem[i] |= flag; 182 | mem[i] ~= flag; 183 | ) : ( 184 | mem[i] |= flag; 185 | ); 186 | ); 187 | 188 | function flip_value(is_bus) 189 | local(i, flag, mem) 190 | global(notev, cur_ch, cur_bus, a_notes_bus, a_notes_ch) ( 191 | i = 0; 192 | is_bus ? ( 193 | mem = a_notes_bus; 194 | flag = get_flag(cur_bus); 195 | ) : ( 196 | mem = a_notes_ch; 197 | flag = get_flag(cur_ch); 198 | ); 199 | loop(128, 200 | mem[i] ~= flag; 201 | i += 1; 202 | ); 203 | ); 204 | 205 | function update_sliders() 206 | local(memn) 207 | ( 208 | memn = select_mode ? ( 209 | select_bus ? a_ccs_bus : a_ccs_ch; 210 | ) : ( 211 | select_bus ? a_notes_bus : a_notes_ch; 212 | ); 213 | ch1=memn&1; 214 | ch2=memn&(1<<1)?1; 215 | ch3=memn&(1<<2)?1; 216 | ch4=memn&(1<<3)?1; 217 | ch5=memn&(1<<4)?1; 218 | ch6=memn&(1<<5)?1; 219 | ch7=memn&(1<<6)?1; 220 | ch8=memn&(1<<7)?1; 221 | ch9=memn&(1<<8)?1; 222 | ch10=memn&(1<<9)?1; 223 | ch11=memn&(1<<10)?1; 224 | ch12=memn&(1<<11)?1; 225 | ch13=memn&(1<<12)?1; 226 | ch14=memn&(1<<13)?1; 227 | ch15=memn&(1<<14)?1; 228 | ch16=memn&(1<<15)?1; 229 | ); 230 | 231 | !inited ? ( 232 | set_default(0); 233 | notev = INITIAL_VALUE; 234 | inited = 1; 235 | ); 236 | 237 | update_sliders(); 238 | 239 | @serialize 240 | file_mem(0, a_notes_ch, 128); 241 | file_mem(0, a_notes_bus, 128); 242 | file_mem(0, a_ccs_ch, 128); 243 | file_mem(0, a_ccs_bus, 128); 244 | 245 | @slider 246 | function sliderV() ( 247 | (ch1?1) + (ch2?2) + (ch3?4) + (ch4?8) + (ch5?16) + (ch6?32) + 248 | (ch7?64) + (ch8?128) + (ch9?256) + (ch10?512) + (ch11?1024) + 249 | (ch12?2048) + (ch13?4096) + (ch14?8192) + (ch15?16384) + (ch16?32768); 250 | ); 251 | 252 | mem = select_mode ? ( 253 | select_bus ? a_ccs_bus : a_ccs_ch 254 | ) : ( 255 | select_bus ? a_notes_bus : a_ccs_ch 256 | ); 257 | 258 | update_sliders(); 259 | 260 | @block 261 | 262 | while (midirecv(offset, msg1, msg2, msg3)) ( 263 | in_ch = msg1&$x0F; 264 | in_type = msg1&$xF0; 265 | type = (in_type == STATUS_NOTE_ON) || (in_type == STATUS_NOTE_OFF) ? 0 : 266 | (in_type == STATUS_CC) ? 1 : -1; 267 | type != -1 ? ( 268 | chNoteV = get_midi_channel_value(msg2, type); 269 | busNoteV = get_midi_bus_value(msg2, type); 270 | chNoteV && busNoteV ? ( 271 | i = 0; 272 | loop(16, 273 | chNoteV&get_flag(i) ? ( 274 | msg1 = in_type + i; 275 | j = 0; 276 | loop(16, 277 | busNoteV&get_flag(j) ? ( 278 | midi_bus = j; 279 | midisend(offset, msg1, msg2, msg3); 280 | ); 281 | j += 1; 282 | ); 283 | ); 284 | i += 1; 285 | ); 286 | ); 287 | ) : midisend(offset, msg1, msg2, msg3); 288 | ); 289 | 290 | @gfx 604 348 291 | 292 | function uix_getPitName(pit) ( 293 | pit = pit == 0 ? "C" : 294 | pit == 1 ? "C#" : 295 | pit == 2 ? "D" : 296 | pit == 3 ? "D#" : 297 | pit == 4 ? "E" : 298 | pit == 5 ? "F" : 299 | pit == 6 ? "F#" : 300 | pit == 7 ? "G" : 301 | pit == 8 ? "G#" : 302 | pit == 9 ? "A" : 303 | pit == 10 ? "Bb" : 304 | pit == 11 ? "B"; 305 | strcpy(#,pit); 306 | ); 307 | 308 | function uix_setColor(n) ( 309 | !n ? (gfx_r = 1; gfx_g = .94; gfx_b = 0.83;) : 310 | n == 10 ? (gfx_r = .06; gfx_g = .19; gfx_b = 0.36;) : 311 | n == 11 ? (gfx_r = .08; gfx_g = .45; gfx_b = 0.50;) : 312 | n == 20 ? (gfx_r = .55; gfx_g = .35; gfx_b = .35;) : 313 | n == 30 ? (gfx_r = .64; gfx_g = .87; gfx_b = .61;); 314 | ); 315 | 316 | function ui_drawTitle() ( 317 | gfx_setfont(1, "Arial", 24, 'b'); 318 | uix_setColor(10); 319 | gfx_x = gfx_y = br2; 320 | gfx_drawstr("MIDI Channel Router"); 321 | gfx_setfont(1, "Arial", 16, 'b'); 322 | gfx_x = w*2.8; gfx_y = br+h; 323 | gfx_drawstr("@RCJacH"); 324 | ); 325 | 326 | function ui_drawButton(x,y,w,h,c,c2,s) local(sw,sh) ( 327 | uix_setColor(c ? 30 : 20); 328 | gfx_rect(x, y, w, h); 329 | uix_setColor(c2 == c ? ( c2 ? 11 : 0) : (c2 ? (c ? 30: 30) : (c ? 20 : 0))); 330 | gfx_measurestr(s, sw, sh); 331 | gfx_x = x + w * 0.5 - sw * 0.5; 332 | gfx_y = y + h * 0.5 - sh * 0.5; 333 | gfx_drawstr(s); 334 | ); 335 | 336 | function ui_drawNote(i) 337 | global(a_gridx, a_gridy, w, h, select_mode, a_ccs_ch, a_notes_ch, a_ccs_bus, a_notes_bus, cur_ch, cur_bus) 338 | ( 339 | this.ui_drawButton(a_gridx[i%12], a_gridy[floor(i/12)+3], w, h, 340 | (select_mode ? a_ccs_ch[i]:a_notes_ch[i])&get_flag(cur_ch)!=0, 341 | (select_mode ? a_ccs_bus[i]:a_notes_bus[i])&get_flag(cur_bus)!=0, 342 | select_mode ? sprintf(#, "%i", i):strcat(strcpy(#,uix_getPitName(i%12)),sprintf(#,"%i",floor(i/12))); 343 | ); 344 | ); 345 | 346 | function ui_drawNotes() 347 | global(click, mouse_x, mouse_y, mouse_cap, a_gridx, a_gridy, w, h, cur_ch, cur_bus, select_mode, a_ccs_ch, a_ccs_bus, a_notes_ch, a_notes_bus) 348 | local(i,hi,vi,hold_first,tmp, chmem, busmem) 349 | ( 350 | select_mode ? ( 351 | chmem = a_ccs_ch; 352 | busmem = a_ccs_bus; 353 | ) : ( 354 | chmem = a_notes_ch; 355 | busmem = a_notes_bus; 356 | ); 357 | i = 0; 358 | loop(128, 359 | hi = i % 12; vi = floor(i / 12) + 3; 360 | ui_drawNote(i); 361 | mouse_x >= a_gridx[hi] && mouse_x <= a_gridx[hi]+w && mouse_y >= a_gridy[vi] && mouse_y <= a_gridy[vi]+h ? ( 362 | mouse_cap&1 ? ( 363 | hold_first == -1 ? hold_first = chmem[i]&get_flag(cur_ch); 364 | click != i ? set_value(select_mode, 0, i, cur_ch, hold_first); 365 | click = i; 366 | update_sliders(); 367 | ); 368 | mouse_cap&2 ? ( 369 | hold_first == -1 ? hold_first = busmem[i]&get_flag(cur_bus); 370 | click != i ? set_value(select_mode, 1, i, cur_bus, hold_first); 371 | click = i; 372 | update_sliders(); 373 | ); 374 | mouse_cap == 0 ? ( 375 | hold_first = -1; 376 | click = -1; 377 | update_sliders(); 378 | ); 379 | ); 380 | i += 1; 381 | ); 382 | 383 | ); 384 | 385 | function ui_drawClickableButton(x,y,w,h,c,s) 386 | instance(lclick,rclick,l_activated,r_activated) 387 | ( 388 | this.ui_drawButton(x, y, w, h, c, c, s); 389 | mouse_x >= x && mouse_x <= x+w && mouse_y >= y && mouse_y <= y+h ? ( 390 | mouse_cap&1 ? lclick = 1; 391 | lclick && mouse_cap == 0 ? lclick = 2; 392 | lclick == 2 ? (l_activated = 1; lclick = 0); 393 | mouse_cap&2 ? rclick = 1; 394 | rclick && mouse_cap == 0 ? rclick = 2; 395 | rclick == 2 ? (r_activated = 1; rclick = 0); 396 | ); 397 | ); 398 | 399 | function ui_drawChannelSwitch(x,y) local(i,x1,click,rclick) instance() ( 400 | i = 0; 401 | loop(16, 402 | x1 = x + h*i + br*i*0.5; 403 | ui_drawButton(x1,y,i==15?h-1:h,h,cur_ch==i,cur_bus==i,sprintf(#, "%i", i+1)); 404 | mouse_x >= x1 && mouse_x <= x1+h && mouse_y >= y && mouse_y <= y+h ? ( 405 | mouse_cap&1 ? click = i + 1; 406 | (click == i + 1) && !mouse_cap ? ( 407 | cur_ch = i; 408 | update_sliders(); 409 | ); 410 | mouse_cap&2 ? rclick = i + 1; 411 | (rclick == i + 1) && !mouse_cap ? ( 412 | cur_bus = i; 413 | update_sliders(); 414 | ); 415 | !mouse_cap ? click = rclick = 0; 416 | ); 417 | i += 1; 418 | ); 419 | ); 420 | 421 | uix_setColor(0); 422 | gfx_rect(0,0,gfx_w,gfx_h); 423 | 424 | ui_drawTitle(); 425 | 426 | gfx_setfont(1, "Arial", 20, 'b'); 427 | 428 | ui_drawNotes(); 429 | 430 | bt_reset.ui_drawClickableButton(a_gridx[4], br2, w, h, bt_reset.lclick, "Reset"); 431 | bt_reset.l_activated ? (set_default(select_mode); bt_reset.l_activated = 0; update_sliders();); 432 | bt_all.ui_drawClickableButton(a_gridx[5], br2, w, h, bt_all.lclick, "All"); 433 | bt_all.l_activated ? (set_all_values(1, 0); bt_all.l_activated = 0; update_sliders();); 434 | bt_all.r_activated ? (set_all_values(1, 1); bt_all.r_activated = 0; update_sliders();); 435 | bt_none.ui_drawClickableButton(a_gridx[6], br2, w, h, bt_none.lclick, "None"); 436 | bt_none.l_activated ? (set_all_values(0, 0); bt_none.l_activated = 0; update_sliders();); 437 | bt_none.r_activated ? (set_all_values(0, 1); bt_none.r_activated = 0; update_sliders();); 438 | bt_flip.ui_drawClickableButton(a_gridx[7], br2, w, h, bt_flip.lclick, "Flip"); 439 | bt_flip.l_activated ? (flip_value(0); bt_flip.l_activated = 0; update_sliders();); 440 | bt_flip.r_activated ? (flip_value(1); bt_flip.r_activated = 0; update_sliders();); 441 | 442 | bt_modeNote.ui_drawClickableButton(a_gridx[10], br2, w, h, !select_mode, "Note"); 443 | bt_modeNote.l_activated ? (select_mode = 0; mem = a_notes; bt_modeNote.l_activated = 0; update_sliders();); 444 | bt_modeCC.ui_drawClickableButton(a_gridx[11], br2, w, h, select_mode, "CC"); 445 | bt_modeCC.l_activated ? (select_mode = 1; mem = a_ccs; bt_modeCC.l_activated = 0; update_sliders();); 446 | 447 | sw_channel.ui_drawChannelSwitch(a_gridx[4],br2+br+h); 448 | -------------------------------------------------------------------------------- /JSFX/MIDI/RC MIDI Harmonizer.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: RC MIDI Harmonizer 3 | Author: RCJacH 4 | Release Date: Jul 2023 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.0.0 7 | About: 8 | ## Description 9 | 10 | RC MIDI Harmonizer is a simple MIDI effect that transposes the input MIDI note at a 11 | fixed harmonic interval. 12 | 13 | Changelog: 14 | * v1.0 (2023-07-22) 15 | + Initial Release 16 | */ 17 | 18 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 19 | 20 | desc: RC MIDI Harmonizer 21 | 22 | slider11: dbl1=0<-127,127,1> Double 1 23 | slider12: dbl2=0<-127,127,1> Double 2 24 | slider13: dbl3=0<-127,127,1> Double 3 25 | slider14: dbl4=0<-127,127,1> Double 4 26 | slider15: dbl5=0<-127,127,1> Double 5 27 | slider16: dbl6=0<-127,127,1> Double 6 28 | slider17: dbl7=0<-127,127,1> Double 7 29 | slider18: dbl8=0<-127,127,1> Double 8 30 | slider19: dbl9=0<-127,127,1> Double 9 31 | slider20: dbl10=0<-127,127,1> Double 10 32 | 33 | @init 34 | 35 | ext_midi_bus = 1.0; 36 | 37 | STATUS_NOTE_ON = $x90; // Note On Message 38 | STATUS_NOTE_OFF = $x80; // Note Off Message 39 | 40 | @block 41 | 42 | while (midirecv(offset, msg1, msg2, msg3)) ( 43 | in_type = msg1&$xF0; 44 | (in_type == STATUS_NOTE_ON) || (in_type == STATUS_NOTE_OFF) ? ( 45 | (dbl1 != 0) ? ( 46 | new_note = msg2 + dbl1; 47 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, new_note, msg3); 48 | ); 49 | (dbl2 != 0) ? ( 50 | new_note = msg2 + dbl2; 51 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, new_note, msg3); 52 | ); 53 | (dbl3 != 0) ? ( 54 | new_note = msg2 + dbl3; 55 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, new_note, msg3); 56 | ); 57 | (dbl4 != 0) ? ( 58 | new_note = msg2 + dbl4; 59 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, new_note, msg3); 60 | ); 61 | (dbl5 != 0) ? ( 62 | new_note = msg2 + dbl5; 63 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, new_note, msg3); 64 | ); 65 | (dbl6 != 0) ? ( 66 | new_note = msg2 + dbl6; 67 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, new_note, msg3); 68 | ); 69 | (dbl7 != 0) ? ( 70 | new_note = msg2 + dbl7; 71 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, new_note, msg3); 72 | ); 73 | (dbl8 != 0) ? ( 74 | new_note = msg2 + dbl8; 75 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, new_note, msg3); 76 | ); 77 | (dbl9 != 0) ? ( 78 | new_note = msg2 + dbl9; 79 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, new_note, msg3); 80 | ); 81 | (dbl10 != 0) ? ( 82 | new_note = msg2 + dbl10; 83 | (new_note <= 127) && (new_note >= 0) ? midisend(offset, msg1, msg2+new_note, msg3); 84 | ); 85 | ); 86 | midisend(offset, msg1, msg2, msg3); 87 | ); 88 | -------------------------------------------------------------------------------- /JSFX/MIDI/midi_cc_eater.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: Midi CC Eater 3 | Author: RCJacH 4 | Release Date: 18.02.2015 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.0 7 | Reference: MIDI Eater 8 | About: 9 | This JS plugin filters all inputs of selected CC. 10 | Instruction: 11 | 1. Add as an input FX. 12 | 2. Select specific CC or all CCs to filter. 13 | 3. Win. 14 | */ 15 | 16 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 17 | 18 | desc:MIDI CC Eater 19 | slider1:0<0,128,1{0 All,Pitch Wheel,1 Mod Wheel M,2 Breath M,3,4 Foot P M,5 Porta M,6 Data Entry M,7 Vol M,8 Balance M,9,10 Pan M,11 Expression M,12 Ctrl 1 M,13 Ctrl 2 M,14,15,16 GP Slider 1,17 GP Slider 2,18 GP Slider 3,19 GP Slider 4,20,21,22,23,24,25,26,27,28,29,30,31,32 Bank Sel L,33 Mod Wheel L,34 Breath L,35,36 Foot P L,37 Porta L,38 Data Entry L,39 Vol L,40 Balance L,41,42 Pan L,43 Expression L,44 Ctrl 1 L,45 Ctrl 2 L,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64 Hold P sw,65 Porta sw,66 Sustenuto sw,67 Soft P sw,68 Legato P sw,69 Hold 2 P sw,70 S.Variation,71 S.Timbre,72 Release,73 Attack,74 Cutoff,75 Decay,76 S.Ctrl 7,77 S.Ctrl 8,78 S.Ctrl 9,79 S.Ctrl 10,80 GP B.1 sw,81 GP B.2 sw,82 GP B.3 sw,83 GP B.4 sw,84,85,86,87,88,89,90,91 Effects Lv,92 Trem Lv,93 Chorus Lv,94 Celeste Lv,95 Phaser Lv,96 Data B. Inc,97 Data B. Dec,98 NRP L,99 NRP M,100 RP L,101 RP M,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127}>CC 20 | in_pin:none 21 | out_pin:none 22 | 23 | @init 24 | STATUS_CC = $xB0; 25 | STATUS_PW = $xE0; 26 | 27 | @slider 28 | CC_select = slider1; 29 | 30 | @block 31 | while(midirecv(mpos, msg1, msg23)) ( 32 | // get message components 33 | in_type = msg1 & $xF0; 34 | in_CCN = msg23 & $xff; //Get incoming CC number 35 | eat = 0; 36 | (in_type == STATUS_PW) && (CC_select <= 1) ? eat = 1; 37 | 38 | (in_type == STATUS_CC) ? ( 39 | (CC_select == 0) || (in_CCN == CC_select - 1) ? eat = 1; 40 | ); //in_type 41 | !eat ? midisend(mpos, msg1, msg23); 42 | ); 43 | -------------------------------------------------------------------------------- /JSFX/MIDI/midi_note_beat_repeater.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: MIDI Note Beat Repeater 3 | Author: RCJacH 4 | Release Date: Oct 2016 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.1 7 | Reference: 8 | MIDI Note Repeater 9 | Native Instrument Maschine 10 | About: 11 | This script is made to emulate the note repeating function in Native Instrument Maschine hardware. 12 | I have never used Maschine myself, thus I do not know how it really works. 13 | However, I did watch some video that demonstrated great workflow for inputting hihat patterns. 14 | Instruction: 15 | You can use this script in two ways: 16 | 1. Insert as an input FX of your track. Assign MIDI CCs to all of the parameters, trigger slider11(On/Off) and press a note, 17 | the pressed note will repeat itself at an interval set using slider1(Rate), while slider2(Type) determines the type of 18 | rhythm it generates. 19 | 2. Insert as a normal FX before your sampler. Write the trigger note for the desired sample, and use envelop to control 20 | the parameters. You can even insert another track and record MIDI output of the original track, which allows you to 21 | edit the generated pattern. 22 | The Normal Type Lock slider(3) is great for drill type patterns where you have a steady 8th note groove with occasional 23 | 8th or 16th note triplet variations. This setting will free you from constantly alternating the type slider from triplet 24 | to normal. 25 | Channel Pressure is used to vary the velocity of repeated notes: output Velocity = Channel Pressure. 26 | Changelog: 27 | * v1.1 (2016-10-09) 28 | + Renamed 29 | + Added Gate Function (Note Length) 30 | + Added Sync to grid function 31 | # Fixed Note On accuracy 32 | * v1.0 (2016-07-24) 33 | + Initial Release 34 | Potential Addition: 35 | 1. Velocity and Timing Humanization 36 | 2. Aftertouch %. 37 | 3. Native CC sliders. 38 | 4. Work with pause play_state. 39 | */ 40 | 41 | // License: GPL - http://www.gnu.org/licenses/gpl.html 42 | 43 | desc:MIDI Note Beat Repeater 44 | //tags: MIDI processing 45 | 46 | slider1:5<0,9,1{4,2,1,1/2,1/4,1/8,1/16,1/32,1/64,1/128}>Rate (fraction of whole note) 47 | slider2:0<0,2,1{NORMAL,TRIPLET,DOTTED}>Type 48 | slider3:2<0,4,1{off,1/2,1/4,1/8,1/16}> Normal Type lock 49 | slider4:0<0,127,1> Aftertouch 50 | slider5:100<10,100,10> Gate 51 | slider10:1<0,1,1{Off,On}> Sync to Beat 52 | slider11:1<0,1,1{Off,On}> On/Off 53 | 54 | 55 | in_pin:none 56 | out_pin:none 57 | 58 | @init 59 | // Constants from the MIDI specification: 60 | NUM_CHANNELS = 16; 61 | NUM_NOTES = 128; 62 | 63 | STATUS_NOTE_ON = $x90; // Note On Message 64 | STATUS_NOTE_OFF = $x80; // Note Off Message 65 | STATUS_AFTERTOUCH = $xA0; // AfterTouch Message 66 | STATUS_CC = $xB0; // Control Change Message 67 | STATUS_CHANNEL_PRESSURE = $xD0; // Channel Pressure Message 68 | STATUS_PITCH_WHEEL = $xE0; // Pitch Wheel Message 69 | 70 | i_noteCnt = 0; 71 | i_AT = 0; 72 | active = 0; 73 | 74 | // List of notes that are pressed 75 | i_NotesPressedBit = 2; //Channel, Vel 76 | a_NotesPressed = 0; 77 | memset(a_NotesPressed,0, NUM_NOTES * i_NotesPressedBit); 78 | 79 | // List of notes that have sent Note On MIDI information 80 | i_NotesOutBit = 3; //Channel, Pit, Vel 81 | a_NotesOut = a_NotesPressed + NUM_NOTES * i_NotesPressedBit; 82 | memset(a_NotesOut, 0, NUM_NOTES * i_NotesOutBit); 83 | 84 | @slider 85 | // rate as integer of a beat 86 | i_div = (16/(2 ^ slider1)); 87 | // Return to even notes from triplet if division is equal to slider3 value 88 | slider3 ? i_divlock = 2 / (2 ^ (slider3 - 1)):16; 89 | // Triplet or Normal 90 | slider2 == 1 ? i_div < i_divlock ? i_div *= 2/3; 91 | // Dotted 92 | slider2 == 2 ? i_div *= 3/2; 93 | i_AT = slider4; 94 | i_gate = slider5 / 100; 95 | i_divGate = i_div * i_gate; 96 | b_sync = slider10; 97 | on = slider11; 98 | 99 | @block 100 | 101 | while(midirecv(offset,msg1,msg2,msg3)) ( 102 | // Break up the MIDI message into its component parameters. 103 | in_ch = msg1 & $x0F; 104 | in_type = msg1 & $xF0; 105 | in_pit = msg2; 106 | in_vel = msg3; 107 | block = 0; 108 | 109 | (in_type == STATUS_NOTE_ON || in_type == STATUS_NOTE_OFF) ? ( 110 | in_type == STATUS_NOTE_ON ? samplesTrigger = offset; 111 | i_noteCnt += (in_type == STATUS_NOTE_ON && in_vel > 0) ? 1:-1; 112 | a_NotesPressed[in_pit*i_NotesPressedBit] = in_ch; 113 | a_NotesPressed[in_pit*i_NotesPressedBit + 1] = in_vel; 114 | block = on ? 1:0; 115 | // Pressed Note are blocked if script is active. 116 | ); // Note ON/OFF 117 | 118 | (in_type == STATUS_CHANNEL_PRESSURE) ? ( 119 | i_AT = in_pit; 120 | slider4 = i_AT; 121 | block = 1; // Do not output Channel Pressure 122 | ); 123 | // Pass Original MIDI Data 124 | !block?midisend(offset, msg1, msg2, msg3); 125 | 126 | ); //while MIDI in 127 | 128 | // Calculate Repeated Notes 129 | i_noteCnt && on ? ( 130 | // If keys are pressed while triggered 131 | b_syncChk = b_sync && (play_state&1 || play_state&5) ? 1 : 0; 132 | // rate to sample 133 | samples_per_beat = srate * 60 / tempo; 134 | beats_per_sample = ( tempo / 60 ) / srate; 135 | beats_per_block = samplesblock * beats_per_sample; 136 | div_next_block = floor((beat_position + beats_per_block) / i_div); 137 | div_this_block = floor((beat_position) / i_div); 138 | i_divSample = i_div * samples_per_beat; 139 | 140 | // Check if Note-On trigger happens in this samplesblock based on sync/play setting 141 | b_onTrigger = b_syncChk ? ( 142 | beat_position == 0 || div_next_block > div_this_block ? 1 : 0; 143 | ) : ( 144 | samplesTrigger < samplesblock || floor((samplesTrigger + samplesblock) / i_divSample) > floor(samplesTrigger / i_divSample) ? 1 : 0; 145 | ); 146 | 147 | // If note(s) is already being triggered 148 | active ? ( 149 | b_offTrigger = b_syncChk ? ( 150 | beat_position + beats_per_block > active_pos + i_divGate ? 1 : 0; 151 | ):( 152 | (samplesTrigger + samplesblock) % i_divSample > i_divSample * i_gate ? 1 : 0; 153 | ); 154 | b_offTrigger ? ( 155 | ofs = b_syncChk ? (active_pos + i_divGate - beat_position) * samples_per_beat : 156 | i_divSample * i_gate - samplesTrigger % i_divSample; 157 | // Note-Off all pressed notes 158 | loopOut = 0; loop(active, 159 | a_NotesOut[loopOut + 2] ? ( 160 | outCh = a_NotesOut[loopOut]; 161 | outPit = a_NotesOut[loopOut + 1]; 162 | midisend(ofs, STATUS_NOTE_OFF|outCh, outPit); 163 | ); 164 | loopOut += i_NotesOutBit; 165 | ); // loopOut 166 | active = 0; 167 | ); //offTrigger 168 | ); //active? 169 | 170 | // If Note-On triggers 171 | b_onTrigger ? ( 172 | ofs = b_syncChk ? (ceil(beat_position / i_div) * i_div - beat_position) * samples_per_beat : 173 | ceil(samplesTrigger / i_divSample) * samples_per_beat - samplesTrigger; 174 | // Note-On all pressed notes 175 | loopPit = 0; loopOut = 0; loop(NUM_NOTES, 176 | outCh = a_NotesPressed[loopPit]; 177 | outPit = loopPit/i_NotesPressedBit; 178 | outVel = a_NotesPressed[loopPit + 1]; 179 | outVel ? ( 180 | i_AT ? outVel = i_AT; 181 | midisend(ofs, STATUS_NOTE_ON|outCh, outPit, outVel); 182 | a_NotesOut[loopOut] = outCh; 183 | a_NotesOut[loopOut + 1] = outPit; 184 | a_NotesOut[loopOut + 2] = outVel; 185 | loopOut += i_NotesOutBit; 186 | active += 1; 187 | ); //outVel 188 | loopPit += i_NotesPressedBit; 189 | ); //loopPit 190 | // Mark position of last Note-On 191 | active_pos = beat_position + ofs * beats_per_sample; 192 | ); // b_onTrigger 193 | samplesTrigger += samplesblock; 194 | ); //on 195 | 196 | 197 | active && (!i_noteCnt || !on) ? ( 198 | loopOut = 0; loop(active, 199 | a_NotesOut[loopOut + 2] ? ( 200 | outCh = a_NotesOut[loopOut]; 201 | outPit = a_NotesOut[loopOut + 1]; 202 | midisend(0, STATUS_NOTE_OFF|outCh, outPit); 203 | ); 204 | loopOut += i_NotesOutBit; 205 | ); // loopOut 206 | memset(a_NotesOut, 0, NUM_NOTES * i_NotesOutBit); 207 | active = 0; 208 | ); //active 209 | -------------------------------------------------------------------------------- /JSFX/Synth/ReaModular/RCAutoPanner.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: ReaModular - RCAutoPanner 3 | Author: RCJacH 4 | Release Date: April 2018 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.0pre1 7 | About: 8 | ## Description 9 | PRERELEASE, USE ONLY FOR TESTING PURPOSES. I might change the slider numbering later on. 10 | 11 | ReaModular - RCAutoPanner is an auto panning JSFX effect, supporting 6 types of sync method, also panning curves, cv input and other effects. 12 | 13 | Changelog: 14 | * v1.0pre2 (2018-04-05) 15 | # Renamed **Delay %** to **Sync Offset %** 16 | # Changed Panlaw to dB for easier reference 17 | # CV/Audio Mode audio goes through curve setting, and thus spread and offset. 18 | + Smoother Automation for: Rate (Non-Sync), Curve, Rotate, Width, Spread, Offset 19 | + Ducking Mode 20 | * v1.0pre1 (2018-04-04) 21 | + Initial Algorithm 22 | */ 23 | 24 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 25 | 26 | desc: ReaModular - RCAutoPanner 27 | 28 | /* Rate */ 29 | slider1: 1<0,20,0.01>Rate (Hz) 30 | slider2: 1<0.1,4,0.01>Rate (Ms) 31 | slider3: 1<0.1,4,0.01>Rate (Beats) 32 | slider4: 5<0,9,1{4,2,1,1/2,1/4,1/8,1/16,1/32,1/64,1/128}>Rate (Measure) 33 | slider5: 0<0,2,1{NORMAL,TRIPLET,DOTTED}>Sync Type 34 | slider6: 1<0,2,1{Hz,ms,Beats,Measure,CV,Audio}>Sync Method 35 | 36 | slider8: 0<-100,100>Sync Offset % 37 | slider9: 1<0,2,1{None,Reverse,Replicate}>Mirror 38 | 39 | /* Shape */ 40 | slider11: 0<0,4,1{Linear,Exponential,Logarithmic,Sine,Cosine}>Curve Shape 41 | slider12: 0<-100,100,1>Curve 42 | slider13: 1<0,10,0.01>Curve Multiplier 43 | 44 | /* Random */ 45 | // slider31: 0<0,100,1>Random Amount 46 | // slider32: 0<0,10,1>Random Smoothing 47 | 48 | /* Ducking */ 49 | slider35: -100<-100,0,0.1>Ducking Threshold 50 | slider36: 0<0,10,1>Smooth Duck 51 | slider37: 3<0,5,1{Left,Right,Forward,Backward,Random}>Ducking Direction 52 | slider38: 0<0,1,1{Clip,Fold}>Ducking Border Behavior 53 | slider39: 0<0,100,1>Ducking Amount % 54 | 55 | /* Stage */ 56 | slider41: 0<-90,90,1>Rotate (°) 57 | slider42: 100<0,200,1>Width (%) 58 | slider43: 100<0,100,1>Spread 59 | slider44: 0<-100,100,1>Offset 60 | 61 | /* Settings */ 62 | 63 | slider60: 0<0,10,1>Smooth Pan 64 | slider61: 3<0,3,1{None,Left,Right,Stereo}>Channel 65 | slider62: 0<-12,12,1.5>Pan Law (dB) 66 | // slider62: <0,1,1{Low,High}>Quality 67 | 68 | slider64: 0.5<0,1> Pan CV 69 | 70 | 71 | in_pin:left input 72 | in_pin:right input 73 | in_pin:Pan CV input 74 | in_pin:Duck input 75 | out_pin:left output 76 | out_pin:right output 77 | out_pin:pan output 78 | 79 | @init 80 | pi = $pi; 81 | twoPi = pi * 2; 82 | halfPi = pi * 0.5; 83 | 84 | i_curPos = 0; 85 | d_perSample = 1/srate; 86 | 87 | inc = 0; 88 | i_aveMem = inc; inc += 1; 89 | i_aveMemG = inc; inc += 1; 90 | i_rndMem = inc; inc += 1; 91 | i_duckMem = inc; inc += 1; 92 | 93 | function gain(db) ( db <= -100 ? 0 : 10^(0.05 * db) ); 94 | 95 | function lerp(src, tgt, pct) ( 96 | !pct ? src : pct == 1 ? tgt : src * (1 - pct) + tgt * pct; 97 | ); 98 | 99 | function getCurve(shape, pos, t, v0, v1, curve) local(value) ( 100 | pos < 0 ? pos += t : pos > t ? pos -= t; 101 | !pos ? value = 0 : pos == t ? value = 1 : ( 102 | shape == 0 ? ( // Linear 103 | value = pos / t; 104 | ) : 105 | shape == 1 ? ( // Exponential 106 | value = 2 ^ (pos / t) - 1; 107 | ) : 108 | shape == 2 ? ( // Logarithmic 109 | value = log(pos + 1) / log(t + 1); 110 | ) : 111 | shape == 3 ? ( // Sine 112 | value = 0.5 * (1 - cos($pi * (pos / t))); 113 | ) : 114 | shape == 4 ? ( // Cosine 115 | value = sin(halfPi * pos / t); 116 | ); 117 | ); 118 | v0 + value ^ curve * (v1 - v0); 119 | ); 120 | 121 | function walkingAverage(inSample, len, mempos) local(ave) ( 122 | ave=inSample*inSample; 123 | mempos[] = ave + len * (mempos[] - ave); 124 | mempos[] ^ 0.5 + 0.000000000001; 125 | ); 126 | 127 | function fn_progress(b) ( 128 | b ? this += 1 : this = 0; 129 | this > i_totalSamples ? this -= i_totalSamples; 130 | ); 131 | 132 | function fn_updateTempo()( 133 | samples_per_beat = srate * 60 / tempo; 134 | beats_per_sample = (tempo / 60) * d_perSample; 135 | ); 136 | 137 | function fn_updateRate(rate)( 138 | i_cycleSamples = !panSync? srate / rate : panSync== 1 ? floor(srate * rate) /* ms */ : floor(rate * samples_per_beat)/* Synced */; 139 | i_totalSamples = i_cycleSamples; 140 | slider9 ? i_cycleSamples = i_cycleSamples * 0.5; 141 | i_delaySample = i_totalSamples * slider8 * 0.01; 142 | ); 143 | 144 | fn_updateTempo(); 145 | fn_updateRate(1); 146 | 147 | @slider 148 | !panSync? rateAppend = slider1 : panSync== 1 ? rateAppend = slider2 : ( 149 | rateAppend = panSync== 2 ? slider3 : panSync== 3 ? 16/(2 ^ slider4); 150 | slider5 ? rateAppend *= slider5&1 ? 2/3 : slider5&2 ? 3/2; 151 | ); 152 | panSync != slider6 ? (pan = d_drate = 0; rate = rateAppend; fn_updateRate(rate); panSync = slider6;); 153 | 154 | panCurveAppend = 2 ^ (slider12 * 0.01 * slider13); 155 | 156 | d_rotAppend=slider41*0.017453292; 157 | d_widthAppend = slider42 * 0.005; 158 | d_halfSpread = slider43 * 0.005; 159 | d_offset = 0.5 + slider44 * 0.005; 160 | d_leftAppend = d_offset - d_halfSpread; 161 | d_rightAppend = d_offset + d_halfSpread; 162 | 163 | rmstime = max(min(slider60/500,0.02),0.00004); 164 | d_panRMS = exp(-1/(rmstime * srate)); 165 | 166 | d_rndPct = slider31 * 0.01; 167 | rmstime = max(min(slider32/1000,0.02),0.00002); 168 | d_rndRMS = exp(-1/(rmstime * srate)); 169 | 170 | d_duckThreshold = gain(slider35); 171 | rmstime = max(min(slider36/1000,0.02),0.00002); 172 | d_duckRMS = exp(-1/(rmstime * srate)); 173 | d_duckPct = slider39 * 0.01; 174 | 175 | i_ch = slider61; 176 | panlaw = 2 ^ (slider62 / 6); 177 | 178 | @block 179 | prevTempo != tempo ? ( 180 | fn_updateTempo(); 181 | fn_updateRate(rate); 182 | prevTempo = tempo; 183 | ); 184 | 185 | per_samplesblock = 1 / samplesblock; 186 | 187 | play_state&1 && (panSync== 2 || panSync== 3) ? ( 188 | i_curPos = (play_position * srate) % i_totalSamples; 189 | ); 190 | b_calcPan = (play_state&0 && b_calcSig) || play_state&1; 191 | 192 | /* Smooth Faders */ 193 | d_drate = rate != rateAppend ? (rateAppend - rate) * per_samplesblock * 0.5; 194 | d_dpanCurve = panCurve != panCurveAppend ? (panCurveAppend - panCurve) * per_samplesblock; 195 | 196 | d_drot = d_rot != d_rotAppend ? (d_rotAppend - d_rot) * per_samplesblock; 197 | d_dwidth = d_width != d_widthAppend ? (d_widthAppend - d_width) * per_samplesblock; 198 | d_dleft = d_left != d_leftAppend ? (d_leftAppend - d_left) * per_samplesblock; 199 | d_dright = d_right != d_rightAppend ? (d_rightAppend - d_right) * per_samplesblock; 200 | 201 | 202 | @sample 203 | b_calcSig = (i_ch&1 && (spl0 || s0)) || (i_ch&2 && (spl1 || s0)); 204 | 205 | /* Update Values */ 206 | d_drate ? (rate += d_drate; fn_updateRate(rate)); 207 | d_dpanCurve ? panCurve += d_dpanCurve; 208 | d_drot ? d_rot += d_drot; 209 | d_dwidth ? d_width += d_dwidth; 210 | d_dleft ? d_left += d_dleft; 211 | d_dright ? d_right += d_dright; 212 | 213 | /* Setup Sound Stage */ 214 | b_calcSig ? ( 215 | /* Input Setup */ 216 | i_ch&1 ? s0 = spl0; 217 | i_ch&2 ? s1 = spl1; 218 | i_ch == 3 ? ( 219 | // Rotation 220 | d_rot ? ( 221 | s0 = sign(spl0); 222 | s1 = sign(spl1); 223 | angle = atan( spl0 / spl1 ); 224 | (s0 == 1 && s1 == -1) || (s0 == -1 && s1 == -1) ? angle += 3.141592654; 225 | s0 == -1 && s1 == 1 ? angle += 6.283185307; 226 | spl1 == 0 ? spl0 > 0 ? angle = 1.570796327 : angle = 4.71238898; 227 | spl0 == 0 ? spl1 > 0 ? angle = 0 : angle = 3.141592654; 228 | angle -= d_rot; 229 | radius = sqrt( sqr(spl0)+sqr(spl1) ) ; 230 | s0 = sin(angle)*radius; 231 | s1 = cos(angle)*radius; 232 | ); 233 | 234 | // Width 235 | mono = (s0 + s1) * 0.5; 236 | stereo = (s0 - s1) * d_width; 237 | s0 = mono + stereo; 238 | s1 = mono - stereo; 239 | ); 240 | ):( 241 | s0 = s1 = 0; 242 | ); 243 | 244 | b_calcPan ? ( 245 | 246 | /* Pan Position */ 247 | panSync< 4 ? ( // From Algorithm 248 | rate ? ( 249 | i_cyclePos = i_curPos + i_delaySample; 250 | /* Change Rate */ 251 | // To be implemented 252 | 253 | i_cyclePos < 0 ? i_cyclePos += i_totalSamples : i_cyclePos > i_totalSamples ? i_cyclePos -= i_totalSamples; 254 | i_cyclePos > i_cycleSamples ? ( 255 | slider9 == 1 ? tgtPan = getCurve(slider11, i_cycleSamples - (i_cyclePos - i_cycleSamples), i_cycleSamples, d_left, d_right, panCurve) : 256 | slider9 == 2 ? tgtPan = 1 - getCurve(slider11, i_cyclePos, i_cycleSamples, d_left, d_right, panCurve); 257 | ): tgtPan = getCurve(slider11, i_cyclePos, i_cycleSamples, d_left, d_right, panCurve); 258 | 259 | // Progress 260 | i_curPos.fn_progress((play_state&0 && b_calcSig) || play_state&1); 261 | ):( 262 | tgtPan = d_offset; 263 | ) 264 | ):( // From input 265 | tgtPan = panSync== 4 ? spl2 : panSync== 5 ? spl2 * 0.5 + 0.5; 266 | tgtPan = getCurve(slider11, max(0,min(1,tgtPan)), 1, d_left, d_right, panCurve); 267 | ); 268 | 269 | /* Duck */ 270 | slider39 ? ( 271 | spl3 /* Duck in */ >= d_duckThreshold ? ( 272 | d_duck = (abs(spl3) - d_duckThreshold) * d_duckPct; 273 | !slider37 || (slider37 == 4 && rand(1) < 0.5) || 274 | (i_totalSamples > i_cycleSamples && 275 | (slider37 == 2 && i_cyclePos > i_cycleSamples) || 276 | (slider37 == 2 && i_cyclePos <= i_cycleSamples)) ? 277 | d_duck = -d_duck; 278 | ); 279 | slider36 ? d_duck = walkingAverage(d_duck, d_duckRMS, i_duckMem); 280 | tgtPan += d_duck; 281 | tgtPan = !slider38 ? max(0,min(1, tgtPan)) : (tgtPan > 1 ? 1 - (tgtPan - 1) : tgtPan < 0 ? -tgtPan : tgtPan); 282 | ); 283 | 284 | pan = slider60 ? walkingAverage(tgtPan, d_panRMS, i_aveMem) : tgtPan; 285 | pan > 1 ? pan = 1 - (pan - 1); pan < 0 ? pan = -pan; 286 | slider64 = spl2 = pan; 287 | strpan = pan; 288 | invpan = 1 - pan; 289 | ); 290 | 291 | /* Output */ 292 | b_calcSig ? ( 293 | panlaw != 1 ? ( 294 | strpan ? strpan *= lerp(panlaw, 1, abs(0.5 - strpan) * 2); 295 | invpan ? invpan *= lerp(panlaw, 1, abs(0.5 - invpan) * 2); 296 | ); 297 | spl0 = (i_ch&1 ? s0 * invpan) + (i_ch&2 ? s1 * strpan); 298 | spl1 = (i_ch&1 ? s0 * strpan) + (i_ch&2 ? s1 * invpan); 299 | ); 300 | 301 | @gfx 400 100 302 | 303 | gfx_r = gfx_g = gfx_b = 0; 304 | gfx_rect(0, 0, gfx_w, gfx_h); 305 | gfx_r = gfx_g = gfx_b = 1; 306 | 307 | gfx_x = 0; 308 | halfH = gfx_h * 0.5; 309 | gfx_y = halfH; 310 | pos = 0; 311 | while ( 312 | prevX = pos; 313 | prevY = y; 314 | y = halfH - halfH * getCurve(slider11, gfx_w - pos, gfx_w, d_left, d_right, panCurve); 315 | gfx_line(prevX, prevY, pos, y, 1); 316 | pos += 1; 317 | pos <= gfx_w; 318 | ); 319 | slider9 ? 320 | while ( 321 | prevX = pos; 322 | prevY = y; 323 | slider9 == 1 ? ( 324 | y = getCurve(slider11, gfx_w - pos, gfx_w, d_left, d_right, panCurve); 325 | y = halfH + halfH * y; 326 | ):slider9 == 2 ?( 327 | y = getCurve(slider11, pos, gfx_w, d_left, d_right, panCurve); 328 | y = gfx_h - halfH * y; 329 | ); 330 | gfx_line(prevX, prevY, pos, y, 1); 331 | pos -= 1; 332 | pos >= 0; 333 | ); 334 | gfx_r = !gfx_g = gfx_b = 0; 335 | y = panSync< 4 ? i_cyclePos/i_totalSamples * gfx_h : halfH; 336 | gfx_circle(pan * gfx_w, y, 5, 1, 1); 337 | -------------------------------------------------------------------------------- /JSFX/Synth/Tone Sweep.jsfx: -------------------------------------------------------------------------------- 1 | /* 2 | JSFX Name: Tone Sweep 3 | Author: RCJacH 4 | Release Date: Mar 2017 5 | Link: https://github.com/RCJacH/ReaScripts 6 | Version: 1.0rc2a 7 | Reference: 8 | D16 Punchbox 9 | Tone Generator 10 | ToneJS 11 | About: 12 | This JS plugin generates a sweeping tone base on settings 13 | Instruction: 14 | 1. Set starting and ending note, fine tuning in cents if desired. 15 | 2. Set sweeping time and envelope time. 16 | 3. Adjust envelope curves 17 | 4. Select different wave shape if desired. 18 | 19 | Changelog: 20 | * v1.0rc2a (2017-05-05) 21 | # Fixed key filter, now triggered by all keys when set to midi note -1, rather than 0 (C0). 22 | * v1.0rc2 (2017-04-30) 23 | # Fixed controls on the last GUI page not saving 24 | * v1.0rc1 (2017-04-28) 25 | + Full GUI 26 | + Note off/Key track 27 | 28 | * v1.0pre2 (2017-04-13) 29 | + Curve Slection 30 | + GUI displaying waveform 31 | + Simple AM/FM modulation with sidechain capability 32 | # Using functions 33 | # Fixed curve algorithm 34 | # Move to different AHD stage at 0 phase 35 | * v1.0pre1 (2017-03.28) 36 | + Initial Algorithm 37 | */ 38 | 39 | // Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 40 | 41 | desc: Tone Sweep 42 | 43 | // pitch 44 | slider1: 440<400,480,0.01> -Pitch Standard 45 | slider2: 64<0,127,1> -Start Note 46 | slider3: 0<-100,100,1> -Fine Tune (cents) 47 | slider4: 21<0,127,1> -End Note 48 | slider5: 0<-100,100,1> -Fine Tune (cents) 49 | 50 | slider7: 125<1,1000,0.1> -Sweep Time 51 | slider8: -100<-100,100,1> -Sweep Curve 52 | slider9: 3<0,10,0.01> -Sweep Curve Multiplier 53 | slider10: 1<0,4,1{Linear,Exponential,Logarithmic,Sine,Cosine}> -Sweep Curve Shape 54 | 55 | // time 56 | slider12: 5<0,1000,0.01> -Attack Time 57 | slider13: 125<1,1000,0.01> -Hold Time 58 | slider14: 125<0,5000,0.01> -Decay Time 59 | 60 | // curve 61 | slider16: 100<-100,100,1> -Attack Curve 62 | slider17: 1<0,10,0.01> -Attack Curve Multiplier 63 | slider18: 0<0,4,1{Linear,Exponential,Logarithmic,Sine,Cosine}> -Attack Curve Shape 64 | slider19: -100<-100,100,1> -Decay Curve 65 | slider20: 2<0,10,0.01> -Decay Curve Multiplier 66 | slider21: 0<0,4,1{Linear,Exponential,Logarithmic,Sine,Cosine}> -Decay Curve Shape 67 | 68 | slider23: 0<0, 20000, 0.01> -AM Frequency 69 | slider24: 0<-100,100,1> -AM Depth 70 | slider25: 0<-100,100,1> -AM Env Slant (/ - \) 71 | slider26: 0<0,3,1{Sine,Triangle,Square,Saw}> -AM Waveform 72 | 73 | slider31: 0<0,3,1{Sine,Triangle,Square,Saw}> -Waveform 74 | 75 | slider34: 0<0,20000,0.01> -FM Frequency 76 | slider35: 0<-100,100,1> -FM Depth 77 | slider36: 0<-100,100,1> -FM Env Slant (/ - \) 78 | slider37: 0<0,3,1{Sine,Triangle,Square,Saw}> -FM Waveform 79 | slider38: 0<0,1,1{Static, Variable}> -FM Frequency Domain 80 | 81 | slider41: -18<-120,0,1> -Volume 82 | slider58: 0<0,1,1{Off,On}> -Key Track 83 | slider59: 0<0,1,1{Off,On}> -Note Off 84 | slider60: 0<0,1,0.01> -Velocity to Pitch 85 | slider61: 0<0,1,0.01> -Velocity to Volume 86 | slider62: -1<0,127,1> -keyFilter 87 | slider63: 0<0,8000,0.01> -Output Frequency 88 | slider64: 0<0,4,1> GUI Page 89 | 90 | in_pin:Input L 91 | in_pin:Input R 92 | in_pin:AM Input 93 | in_pin:FM Input 94 | out_pin:output L 95 | out_pin:output R 96 | 97 | @init 98 | STATUS_NOTE_ON = $x90; // Note On Message 99 | STATUS_NOTE_OFF = $x80; // Note Off Message 100 | 101 | halfPi = $pi * 0.5; 102 | onehalfPi = halfPi * 3; 103 | twoPi = $pi * 2; 104 | perSample = 1/srate; 105 | incBase = twoPi * perSample; 106 | ms2sample = 0.001 * srate; 107 | 108 | function onepole(fc) instance(cutoff, a0, b1, z1) ( 109 | fc != cutoff ? ( 110 | cutoff = fc; 111 | b1 = exp(-twoPi * cutoff); 112 | a0 = 1 - b1; 113 | ); 114 | this = z1 = this * a0 + z1 * b1; 115 | ); 116 | 117 | function pit2freq(pitch) ( 118 | (2 ^ ((pitch - 57) / 12)); 119 | ); 120 | 121 | function getCurve(shape, pos, t0, t1, v0, v1, curve) local(tExt, tLen, tmp, value) ( 122 | pos = max(t0, min(pos, t1)); 123 | tExt = pos - t0; 124 | tLen = t1 - t0; 125 | shape == 0 ? ( // Linear 126 | value = tExt / tLen; 127 | ) : 128 | shape == 1 ? ( // Exponential 129 | value = 2 ^ (tExt / tLen) - 1; 130 | ) : 131 | shape == 2 ? ( // Logarithmic 132 | value = log(tExt + 1) / log(tLen + 1); 133 | ) : 134 | shape == 3 ? ( // Sine 135 | value = 0.5 * (1 - cos($pi * (tExt / tLen))); 136 | ) : 137 | shape == 4 ? ( // Cosine 138 | value = sin(halfPi * tExt / tLen); 139 | ); 140 | v0 + value ^ curve * (v1 - v0); 141 | ); 142 | 143 | function getEnv() local(b, Fc) ( 144 | b = (this.phase + incBase * this.freq) % $pi == 0; 145 | this.state == 1 ? ( //Attack 146 | Fc = this.atkFc; 147 | this.env = getCurve(this.sA, this.AHDcnt, 0, this.tA, 0, 1, this.vA); 148 | this.AHDcnt >= this.tA && b ? this.state = 2; 149 | ): 150 | this.state == 2 ? ( //Hold 151 | Fc = this.holdFc; 152 | this.env = 1; 153 | b_noteoff.value ? ( 154 | !this.cnt && b ? this.state = 3; 155 | ):( 156 | this.AHDcnt >= this.tAH && b ? this.state = 3; 157 | ); 158 | ): 159 | this.state == 3 ? ( //Decay 160 | Fc = this.decFc; 161 | this.env = getCurve(this.sD, this.AHDcnt, this.tAH, this.tAHD, 1, 0, this.vD); 162 | this.AHDcnt >= this.tAHD && b ? this.state = 0; 163 | ); 164 | this.env.onepole(Fc); 165 | ); 166 | 167 | function getTone(shape) local(tone) ( 168 | shape == 0 ? ( // Sine 169 | tone = sin(this.phase); 170 | ) : 171 | shape == 1 ? ( // Triangle 172 | tone = this.phase / halfPi - 1; 173 | (tone > 1) ? tone = 2 - tone; 174 | ): 175 | shape == 2 ? ( // Square 176 | tone = this.phase > halfPi && this.phase < onehalfPi ? 1 : -1; 177 | ): 178 | shape == 3 ? ( // Saw 179 | tone = 1 - this.phase / $pi; 180 | ); 181 | this.tone = tone; 182 | ); 183 | 184 | function incPhase(freq) ( 185 | this.phase += incBase * freq; 186 | this.phase >= twoPi ? this.phase -= twoPi; 187 | ); 188 | 189 | function getFreq(cnt) ( 190 | this.freq = getCurve(AHD.sSweep, cnt, 0, AHD.tSweep, this.fStart, this.bKeytrack ? this.fEnd : fEnd, AHD.vSweep); 191 | ); 192 | 193 | function progress() ( 194 | this.AHDcnt += 1; 195 | LFO.AM.bActive ? this.AM.incPhase(LFO.AM.freq); 196 | LFO.FM.depth != 0 ? ( 197 | !LFO.FM.bSidechain ? ( 198 | this.FM.incPhase(LFO.FM.freq); 199 | this.FM.getTone(LFO.FM.shape); 200 | ); 201 | this.freq += this.FM.tone * LFO.FM.domain * LFO.FM.depth * getCurve(0, this.AHDcnt, 0, AHD.tSweep, LFO.FM.vStart, LFO.FM.vEnd, 1); 202 | ); 203 | this.incPhase(this.freq); 204 | ); 205 | 206 | function setStatus() ( 207 | this.getFreq(this.AHDcnt); 208 | this.getEnv(); 209 | ); 210 | 211 | function setFloat() local(AMvalue, balance) ( 212 | this.setStatus(); 213 | this.getTone(this.toneShape); 214 | LFO.AM.bActive ? ( 215 | !LFO.AM.bSidechain ? this.AM.getTone(LFO.AM.shape); 216 | AMvalue = this.AM.tone * LFO.AM.depth * getCurve(0, this.AHDcnt, 0, this.tAHD, LFO.AM.vStart, LFO.AM.vEnd, 1); 217 | balance = (1 - abs(LFO.AM.depth) * 0.5 ^ abs(LFO.AM.depth)); 218 | ) : ( 219 | AMvalue = 0; 220 | balance = 1; 221 | ); 222 | this.value = this.tone * this.env * (this.envPct + AMvalue) * balance; 223 | ); 224 | 225 | function newNote(velPct) ( 226 | this.envPct = i_vel2vol.value > 0 ? (1 - i_vel2vol.value) + velPct * i_vel2vol.value : 1; 227 | this.pitPct = i_vel2pit.value > 0 ? (1 - i_vel2pit.value) + velPct * i_vel2pit.value : 1; 228 | this.fStart = (this.bKeytrack ? this.fEnd : fEnd) + fDif * this.pitPct; 229 | this.phase = this.state > 1 ? this.phase : 0; 230 | this.AM.phase = 0; 231 | this.FM.phase = fDif > 0 ? $pi : 0; 232 | this.tA = AHD.tA; this.tH = AHD.tH; this.tD = AHD.tD; this.tAH = AHD.tAH; this.tAHD = AHD.tAHD; 233 | this.vA = AHD.vA; this.vD = AHD.vD; 234 | this.sA = AHD.sA; this.sD = AHD.sD; 235 | this.atkFc = atkFc; this.holdFc = holdFc; this.decFc = decFc; 236 | this.AHDcnt = 0; 237 | this.state = 1; 238 | this.toneShape = toneShape; 239 | ); 240 | 241 | function setNoteOff(b) local(tmp) ( 242 | tmp != b ? ( 243 | tmp = b; 244 | note.cnt = 0; 245 | ); 246 | ); 247 | 248 | function setSlant(slant) ( 249 | this.vStart = min(1, 1 + slant); 250 | this.vEnd = min(1, 1 - slant); 251 | ); 252 | 253 | function processSliders() ( 254 | // Input Limiting 255 | slider7 = max(0, slider7); 256 | slider9 = max(0, slider9); 257 | slider12 = max(0, slider12); 258 | slider13 = max(0, slider13); 259 | 260 | // Frequency variables 261 | pStart = slider2 + (slider3 * 0.01); 262 | fStart = slider1 * pit2freq(pStart); 263 | pEnd = slider4 + (slider5 * 0.01); 264 | fEnd = slider1 * pit2freq(pEnd); 265 | fDif = fStart - fEnd; 266 | fDifHalf = fDif / 2; 267 | 268 | // Time variables 269 | AHD.tA = ceil(slider12*ms2sample); 270 | AHD.tH = ceil(slider13*ms2sample); 271 | AHD.tD = ceil(slider14*ms2sample); 272 | AHD.tAH = AHD.tA + AHD.tH; 273 | AHD.tAHD = AHD.tAH + AHD.tD; 274 | AHD.tSweep = ceil(slider7*ms2sample); 275 | 276 | // Curve variables 277 | AHD.vA = (2 ^ (slider16 * 0.01 * slider17)); 278 | atkFc = (100 + AHD.vA * 20) * perSample; 279 | AHD.vA = 1 / AHD.vA; 280 | AHD.sA = slider18; 281 | holdFc = (200 + slider12 * 0.5) * perSample; 282 | AHD.vD = 2 ^ (slider19 * 0.01 * slider20); 283 | AHD.sD = slider21; 284 | decFc = (100 + AHD.vD * 20) * perSample; 285 | 286 | AHD.vSweep = 2 ^ (slider8 * 0.01 * slider9); 287 | AHD.sSweep = slider10; 288 | 289 | LFO.AM.freq = slider23; 290 | LFO.AM.depth = slider24 * 0.01; 291 | LFO.AM.setSlant(slider25 * 0.01); 292 | LFO.AM.shape = slider26; 293 | LFO.AM.bActive = LFO.AM.freq > 0 && LFO.AM.depth != 0 ? 1 : 0; 294 | 295 | toneShape = slider31; 296 | 297 | LFO.FM.freq = slider34; 298 | LFO.FM.depth = slider35 * 0.01; 299 | LFO.FM.setSlant(slider36 * 0.01); 300 | LFO.FM.shape = slider37; 301 | LFO.FM.domain = slider38 == 0 ? LFO.FM.freq : fDif; 302 | 303 | volume = 2^(slider41/6); 304 | b_keytrack.value = slider58; 305 | b_noteoff.value = slider59; 306 | i_vel2pit.value = slider60; 307 | i_vel2vol.value = slider61; 308 | keyFilter.value = slider62; 309 | ); 310 | 311 | processSliders(); 312 | 313 | @slider 314 | 315 | s1.value = slider1; 316 | s2.value = slider2; 317 | s3.value = slider3; 318 | s4.value = slider4; 319 | s5.value = slider5; 320 | s7.value = gPit.x.value = slider7; 321 | s8.value = gPit.y.value = slider8; 322 | s9.value = slider9; 323 | s10.value = slider10; 324 | 325 | s12.value = gEnv.A.x.value = slider12; 326 | s13.value = gEnv.H.x.value = slider13; 327 | s14.value = gEnv.D.x.value = slider14; 328 | 329 | s16.value = gEnv.A.y.value = slider16; 330 | s17.value = slider17; 331 | s18.value = slider18; 332 | s19.value = gEnv.D.y.value = slider19; 333 | s20.value = slider20; 334 | s21.value = slider21; 335 | 336 | s23.value = slider23; 337 | s24.value = slider24; 338 | s25.value = slider25; 339 | s26.value = slider26; 340 | 341 | s31.value = slider31; 342 | 343 | s34.value = slider34; 344 | s35.value = slider35; 345 | s36.value = slider36; 346 | s37.value = slider37; 347 | s38.value = slider38; 348 | 349 | s41.value = slider41; 350 | 351 | uix.page = slider64; 352 | 353 | @block 354 | while ( 355 | midirecv(offset,msg1,msg2,msg3) ? ( 356 | // in_ch = msg1 & $x1F; //Get incoming channel 357 | in_status = msg1 & $xF0; //Get incoming STATUS 358 | in_pit = msg2; //Get incoming Pitch 359 | in_vel = msg3; //Get incoming Velocity 360 | // note on 361 | keyFilter.value < 0 || in_pit == keyFilter.value ? ( 362 | (in_status == STATUS_NOTE_ON) ? ( 363 | note.bKeytrack = b_keytrack.value ? 1 : 0; 364 | note.fEnd = b_keytrack.value ? slider1 * pit2freq(in_pit); 365 | note.newNote(in_vel/127); 366 | b_noteoff.value ? note.cnt += 1; 367 | ): 368 | (in_status == STATUS_NOTE_OFF) ? ( 369 | b_noteoff.value && note.cnt > 0 ? ( 370 | note.state == 2 ? ( 371 | note.cnt -= 1; 372 | note.AHDcnt = AHD.tAH; 373 | ); 374 | ):note.cnt = 0; 375 | ); 376 | ); 377 | midisend(offset,msg1,msg2,msg3); 378 | ); 379 | ); 380 | 381 | @sample 382 | LFO.AM.bSidechain ? note.AM.tone = spl2; 383 | LFO.FM.bSidechain ? note.FM.tone = spl3; 384 | note.state > 0 ? ( 385 | note.setFloat(); 386 | output = note.value * volume; 387 | spl0 += output; 388 | spl1 += output; 389 | slider63 = note.freq; 390 | note.progress(); 391 | ); 392 | 393 | @gfx 750 400 394 | // GUI settings 395 | function uix_setColor(type) ( 396 | type == 0 ? ( //Background 397 | uix.r = uix.g = uix.b = 20; 398 | ): 399 | type == 1 ? ( //Border 400 | uix.r = uix.g = 81; uix.b = 82; 401 | ): 402 | type == 2 ? ( //Text0 403 | uix.r = 249; uix.g = 239; uix.b = 201; 404 | ): 405 | type == 3 ? ( //Text1 406 | uix.r = 185; uix.g = 85; uix.b = 24; 407 | ): 408 | type == 4 ? ( //Slider 409 | uix.r = 148; uix.g = 149; uix.b = 150; 410 | ): 411 | type == 5 ? ( //Button Dark 412 | uix.r = 0.24; uix.g = 0; uix.b = 0; 413 | ): 414 | type == 6 ? ( //Button Light 415 | uix.r = 255; uix.g = 0.15; uix.b = 0; 416 | ): 417 | type == 7 ? ( //Red 418 | uix.r = 135; uix.g = 13; uix.b = 11; 419 | ): 420 | type == 8 ? ( //Orange 421 | uix.r = 220; uix.g = 107; uix.b = 20; 422 | ): 423 | type == 9 ? ( //Yellow 424 | uix.r = 205; uix.g = 159; uix.b = 20; 425 | ); 426 | uix.r = uix.r <= 1 ? uix.r : uix.r/255; 427 | uix.g = uix.g <= 1 ? uix.g : uix.g/255; 428 | uix.b = uix.b <= 1 ? uix.b : uix.b/255; 429 | gfx_r = uix.r; gfx_g = uix.g; gfx_b = uix.b; 430 | ); 431 | 432 | function uix_setFont(type) local(resize) ( 433 | resize = min(gfx_h * 0.0025, gfx_w * 0.0017); 434 | type == 0 ? ( 435 | gfx_setfont(1,"Arial", resize * 16); 436 | ): 437 | type == 1 ? ( 438 | gfx_setfont(1,"Arial", resize * 36, 'b'); 439 | ): 440 | type == 2 ? ( 441 | gfx_setfont(1,"Arial", resize * 30, 'b'); 442 | ): 443 | type == 3 ? ( 444 | gfx_setfont(1,"Arial", resize * 24, 'b'); 445 | ): 446 | type == 4 ? ( 447 | gfx_setfont(1,"Arial", resize * 20, 'b'); 448 | ): 449 | type == 5 ? ( 450 | gfx_setfont(1,"Arial", resize * 14, 'b'); 451 | ); 452 | ); 453 | 454 | function uix_setAlign(wAlign, hAlign) ( 455 | uix.wAlign = max(0, min(1, wAlign)); 456 | uix.hAlign = max(0, min(1, hAlign)); 457 | ); 458 | 459 | function uix_setStrokeWidth(sWidth) ( 460 | uix.sWidth = sWidth; 461 | uix.sWidth2 = sWidth * 2; 462 | ); 463 | 464 | function uix_setPos(x,y) ( 465 | gfx_x = x; gfx_y = y; 466 | ); 467 | 468 | function uix_getPitName(midinum) local(pit) ( 469 | pit = midinum % 12; 470 | pit = pit == 0 ? "C" : 471 | pit == 1 ? "C#" : 472 | pit == 2 ? "D" : 473 | pit == 3 ? "D#" : 474 | pit == 4 ? "E" : 475 | pit == 5 ? "F" : 476 | pit == 6 ? "F#" : 477 | pit == 7 ? "G" : 478 | pit == 8 ? "G#" : 479 | pit == 9 ? "A" : 480 | pit == 10 ? "Bb" : 481 | pit == 11 ? "B"; 482 | strcat(strcpy(#,pit),sprintf(oct,"%i",floor(midinum / 12))); 483 | ); 484 | 485 | function uix_tmp(set) ( 486 | !set ? ( 487 | uix.tmp.sWidth = uix.sWidth; 488 | uix.tmp.wAlign = uix.wAlign; 489 | uix.tmp.hAlign = uix.hAlign; 490 | uix.tmp.fColor = uix.fColor; 491 | uix.tmp.sColor = uix.sColor; 492 | uix.tmp.tColor = uix.tColor; 493 | ) : ( 494 | uix.wAlign = uix.tmp.wAlign; 495 | uix.hAlign = uix.tmp.hAlign; 496 | uix.fColor = uix.tmp.fColor; 497 | uix.sColor = uix.tmp.sColor; 498 | uix.tColor = uix.tmp.tColor; 499 | uix_setStrokeWidth(uix.tmp.sWidth); 500 | ); 501 | ); 502 | 503 | // GUI Draw 504 | function ui_rect(x,y,w,h)( 505 | x -= w * uix.wAlign; 506 | y -= h * uix.hAlign; 507 | uix.sWidth > 0 ? ( 508 | uix_setColor(uix.sColor); 509 | gfx_rect(x,y,w,h); 510 | uix_setColor(uix.fColor); 511 | gfx_rect(x + uix.sWidth,y + uix.sWidth,w - uix.sWidth2, h - uix.sWidth2); 512 | ):( 513 | uix_setColor(uix.fColor); 514 | gfx_rect(x,y,w,h); 515 | ); 516 | ); 517 | 518 | function ui_text(x,y,text,type) 519 | local(w,h) 520 | ( 521 | uix_setFont(type); 522 | gfx_measurestr(text, w, h); 523 | uix_setPos(x - w * uix.wAlign, y - h * uix.hAlign); 524 | uix_setColor(uix.tColor); 525 | gfx_drawstr(text); 526 | ); 527 | 528 | function ui_textCurveName(x,y,type) local(str)( 529 | type == 0 ? str = "Linear" : 530 | type == 1 ? str = "Exponential" : 531 | type == 2 ? str = "Logarithmic" : 532 | type == 3 ? str = "Sine" : 533 | type == 4 ? str = "Cosine"; 534 | ui_text(x, y, str, 0); 535 | ); 536 | 537 | function ui_textWaveformName(x,y,type) local(str)( 538 | type == 0 ? str = "Sine" : 539 | type == 1 ? str = "Triangle" : 540 | type == 2 ? str = "Square" : 541 | type == 3 ? str = "Saw"; 542 | ui_text(x, y, str, 0); 543 | ); 544 | 545 | function ui_handle(x,y,w,h) 546 | local(kh, hw, grad) 547 | ( 548 | uix_setAlign(0,0); 549 | kh = h - 2; 550 | hw = w * 0.5; 551 | grad = 1/w*0.5; 552 | uix_setColor(uix.kColor); 553 | gfx_gradrect(x-3, y+1, hw+4, kh, uix.r*0.5, uix.g*0.5, uix.b*0.5, 1, uix.r/hw, uix.g/hw, uix.b/hw); 554 | gfx_gradrect(x+1+hw, y+1, hw+4, kh, uix.r, uix.g, uix.b, 1, -grad, -grad, -grad); 555 | uix_setColor(2); 556 | gfx_rect(x + hw, y + 1, 2, kh - 1); 557 | ); 558 | 559 | function ui_drawInit(x,y,w,h) ( 560 | // Mouse Logic 561 | mouse_x >= x && mouse_x <= x+w && mouse_y >= y && mouse_y <= y+h && !this.disabled ? ( 562 | !mouse_cap ? this.hasEntered = 1; 563 | mouse_cap ? this.hasClicked = 1; 564 | this.hasEntered && this.hasClicked ? this.canChange = 1; 565 | ) : ( 566 | this.hasEntered = this.hasClicked = 0; 567 | ); 568 | !mouse_cap ? this.canChange = this.init_pos = 0; 569 | ); 570 | 571 | function ui_drawGetControlValue(vStart,vEnd,stepsize,default,dir,length,type) local(tmp,mouse,step,axis,pos) ( 572 | this.range = abs(vStart - vEnd); 573 | this.input = 0; 574 | axis = sign(dir); 575 | pos = abs(dir); 576 | this.canChange ? ( 577 | mouse = axis < 0 ? mouse_y : mouse_x; 578 | !this.init_pos ? this.init_pos = mouse; 579 | type == 2 ? (// Check Box 580 | this.value = !this.value; 581 | this.hasEntered = this.canChange = 0; 582 | ):( 583 | type == 1 /*Dragbox*/ || (mouse_cap & 8 || mouse_cap & 16) ? ( // Holding Shift or Alt = Drag to Fine tune by 0.01 or 1 584 | this.init_pos != mouse ? ( 585 | tmp = mouse - this.init_pos; 586 | mouse_cap & 8 ? tmp = tmp * 0.01; 587 | this.input += axis < 0 ? tmp : -tmp; 588 | this.init_pos = mouse; 589 | ); 590 | step += this.input; 591 | abs(step) > stepsize ? ( 592 | tmp = step < 0 ? ceil(step / stepsize) * stepsize : floor(step / stepsize) * stepsize; 593 | this.value -= tmp; 594 | step -= tmp; 595 | ); 596 | ):( // Direct left click = set by clicked position 597 | this.input = mouse - pos; 598 | tmp = 1 / stepsize; 599 | this.value = ceil(this.range * this.input / length * tmp) / tmp; 600 | this.value = vStart + (axis < 0 ? this.range - this.value : this.value); 601 | ); 602 | ); 603 | mouse_cap & 4 ? this.value = default; 604 | ); 605 | this.value = max(min(this.value,vEnd),vStart); 606 | this.value; 607 | ); 608 | 609 | function ui_drawVisual(x,y,w,h,title) ( 610 | // Knob parameters 611 | this.knob.size = uix.gridx * 2; 612 | this.knob.halfsize = this.knob.size * 0.5; 613 | 614 | this.frame > 0 ? ( 615 | // Frame 616 | uix_setAlign(0,0); 617 | uix_setStrokeWidth(uix.sWidth); 618 | ui_rect(x,y,w,h); 619 | ); 620 | 621 | // Title 622 | title != "" ? ( 623 | uix_setAlign(1,0.5); 624 | ui_text(x - uix.gridx, y + h * 0.5, title, 0); 625 | ); 626 | ); 627 | 628 | function ui_drawSlider(x,y,w,h,vStart,vEnd,stepsize,default,unit,title) local (digit) ( 629 | uix_tmp(0); 630 | x -= w * uix.wAlign; y -= h * uix.hAlign; 631 | this.frame = 1; 632 | this.ui_drawInit(x,y,w,h); 633 | this.ui_drawVisual(x,y,w,h,title); 634 | this.ui_drawGetControlValue(vStart, vEnd, stepsize, default,x,w,0); 635 | 636 | this.knob.pos = (this.range + this.value - vEnd) / (this.range) * (w-this.knob.size - uix.sWidth2); 637 | 638 | // Handle 639 | ui_handle(x+this.knob.pos,y,this.knob.size,h); 640 | // Readout 641 | !this.hidereadout ? ( 642 | digit = stepsize < 1 ? "%.2f" : stepsize < 0.1 ? "%.1f": "%d"; 643 | uix_setAlign(0,0); 644 | ui_text(x + w + uix.gridx, y, strcat(sprintf(#,digit,this.value),unit), 0); 645 | ); 646 | uix_tmp(1); 647 | this.value; 648 | ); 649 | 650 | function ui_drawSelector(x,y,w,h,vStart,vEnd,stepsize,default,title) 651 | ( 652 | x -= w * uix.wAlign; y -= h * uix.hAlign; 653 | this.frame = 1; 654 | this.ui_drawInit(x,y,w,h); 655 | this.ui_drawVisual(x,y,w,h,title); 656 | this.ui_drawGetControlValue(vStart,vEnd,stepsize,default,x,w,0); 657 | 658 | this.knob.pos = (this.range + this.value - vEnd) / (this.range) * (w-this.knob.size-this.knob.halfsize+uix.sWidth2); 659 | 660 | // Handle 661 | ui_handle(x+this.knob.pos,y,this.knob.size,h); 662 | 663 | this.value; 664 | ); 665 | 666 | function ui_drawChkbox(x,y,s,title) ( 667 | uix_tmp(0); 668 | x -= s * uix.wAlign; y -= s * uix.hAlign; 669 | this.frame = 1; 670 | uix.fColor = 5; uix_setStrokeWidth(0); 671 | this.ui_drawInit(x,y,s,s); 672 | this.ui_drawVisual(x,y,s,s,title); 673 | this.ui_drawGetControlValue(0,1,1,0,0,0,2); 674 | // Checked 675 | this.value ? ( 676 | uix_setColor(6); 677 | uix_setStrokeWidth(1); 678 | gfx_rect(x+uix.sWidth2,y+uix.sWidth2,s-uix.sWidth2*2,s-uix.sWidth2*2); 679 | ); 680 | 681 | uix_tmp(1); 682 | this.value; 683 | ); 684 | 685 | function ui_drawValueBox(x,y,vStart,vEnd,stepsize,default,unit,title) local (tmp, digit, w, h, qh) ( 686 | uix_tmp(0); 687 | ui_text(0,0,"",0); 688 | gfx_measurestr(strcat(sprintf(#,"%.2f",vEnd),unit), w, h); 689 | x -= w * uix.wAlign; y -= h * uix.hAlign; 690 | this.frame = 0; 691 | this.ui_drawInit(x,y,w + uix.gridx,h); 692 | this.ui_drawVisual(x,y,w + uix.gridx,h,title); 693 | 694 | // Knob parameters 695 | this.knob.halfsize = 2; 696 | 697 | this.ui_drawGetControlValue(vStart, vEnd, stepsize, default, -y,h,1); 698 | this.knob.pos = (this.range + this.value - vEnd) / (this.range) * (h - this.knob.halfsize); 699 | 700 | // Handle 701 | uix_setColor(6); 702 | gfx_circle(x,y+h-this.knob.pos,this.knob.halfsize,1,1); 703 | 704 | // Readout 705 | !this.hidereadout ? ( 706 | digit = stepsize < 1 ? "%.2f" : stepsize < 0.1 ? "%.1f": "%d"; 707 | uix_setAlign(0,0); 708 | ui_text(x + uix.gridx, y, strcat(sprintf(#,digit,this.value),unit), 0); 709 | ); 710 | 711 | uix_tmp(1); 712 | this.value; 713 | ); 714 | 715 | function ui_drawDragBox(x,y,w,h,vStart,vEnd,stepsize,default,dir)( 716 | this.ui_drawInit(x,y,w,h); 717 | this.ui_drawGetControlValue(vStart,vEnd,stepsize,default,dir,0,1); 718 | ); 719 | 720 | function ui_drawGraph(x,y,w,h,type) local(step, pos, a, b, dir, draw) ( 721 | uix_setAlign(0,0); 722 | uix_setStrokeWidth(1); 723 | uix.fColor = uix.bColor; 724 | ui_rect(x,y,w,h); 725 | uix_setColor(uix.vColor); 726 | x += uix.sWidth; 727 | y += uix.sWidth; 728 | h -= uix.sWidth2; 729 | w -= uix.sWidth2; 730 | uix_setPos(x, y); 731 | draw.newNote(1); 732 | type == 0 ? (//Waveform 733 | step = w / AHD.tAHD; 734 | dir = h * 0.5; 735 | while (draw.state > 0) ( 736 | a = draw.AHDcnt * step; 737 | draw.setFloat(); 738 | b = dir - draw.value * dir; 739 | gfx_line(x + a, y + dir, x + a, y + b, 1); 740 | draw.progress(); 741 | ); 742 | ): 743 | type == 1 ? (//Line Curve 744 | this.vStart == this.vEnd ? ( 745 | pos = h * this.vEnd; 746 | gfx_rect(x, y + h - pos, w, pos, 1); 747 | ) : ( 748 | pos = 0; 749 | step = w / this.tCurve; 750 | while (pos <= this.tCurve) ( 751 | a = pos * step; 752 | b = getCurve(this.sCurve, pos, 0, this.tCurve, this.vStart, this.vEnd, this.vCurve); 753 | b *= h; 754 | gfx_rect(x + a, y + h - b, step + 1, b); 755 | pos += 1; 756 | ); 757 | ); 758 | ): 759 | type == 2 ? (//Envelope Curve 760 | step = w / AHD.tAHD; 761 | while (draw.state > 0) ( 762 | a = draw.AHDcnt * step; 763 | draw.setStatus(); 764 | b = h - draw.env * h; 765 | gfx_line(x + a, y + h, x + a, y + b, 1); 766 | draw.progress(); 767 | ); 768 | ); 769 | ); 770 | 771 | function ui_drawPitchGraph(x,y,w,h) local(tmp) ( 772 | uix_tmp(0); 773 | 774 | // Draw Graph 775 | uix_setAlign(0.5,1); 776 | uix.tColor = 9; 777 | ui_text(x + w * 0.5, y, "Pitch Curve", 4); 778 | this.tCurve = AHD.tSweep; this.sCurve = AHD.sSweep; this.vCurve = AHD.vSweep; 779 | this.vStart = fStart - fEnd; 780 | this.vStart == 0 ? this.vStart = this.vEnd = 0.5 :( 781 | this.vStart = (this.vStart / abs(this.vStart) + 1) * 0.5; 782 | this.vEnd = 1 - this.vStart; 783 | ); 784 | this.ui_drawGraph(x, y, w, h, 1); 785 | uix.tColor = 2; 786 | !this.hidefrequency ? ( 787 | uix_setAlign(0,0); 788 | tmp = this.vStart > 0 ? fStart : fEnd; 789 | ui_text(x + w + uix.gridx, y, strcat(sprintf(#,"%.2f",tmp), "Hz"), 0); 790 | uix_setAlign(0,1); 791 | tmp = this.vEnd > 0 ? fStart : fEnd; 792 | ui_text(x + w + uix.gridx, y + h, strcat(sprintf(#,"%.2f",tmp), "Hz"), 0); 793 | ); 794 | !this.hidelength ? ( 795 | uix_setAlign(0.5,0); 796 | ui_text(x + w * 0.5, y + h, strcat(sprintf(#,"%.2f",s7.value), "ms"), 0); 797 | ); 798 | 799 | this.x.ui_drawDragBox(x, y, w, h, 0, 1000, 0.01, 125, x); 800 | this.y.ui_drawDragBox(x, y, w, h, -100, 100, 0.01, -100, -y); 801 | 802 | uix_tmp(1); 803 | ); 804 | 805 | function ui_drawEnvGraph(x,y,w,h) local(tmp) ( 806 | uix_tmp(0); 807 | 808 | // Draw Graph 809 | uix_setAlign(0.5,1); 810 | uix.tColor = 9; 811 | ui_text(x + w * 0.5, y, "Envelope", 4); 812 | this.ui_drawGraph(x, y, w, h, 2); 813 | 814 | tmp = w/AHD.tAHD; 815 | this.A.x.ui_drawDragBox(x, y, tmp * AHD.tA, h, 0, 1000, 1, 5, x); 816 | this.A.y.ui_drawDragBox(x, y, tmp * AHD.tA, h, -100, 100, 1, -100, -y); 817 | this.H.x.ui_drawDragBox(x+tmp*AHD.tA, y, tmp * AHD.tH, h, 1, 1000, 1, 125, x); 818 | this.D.x.ui_drawDragBox(x+tmp*AHD.tAH, y, tmp * AHD.tD, h, 0, 5000, 1, 125, x); 819 | this.D.y.ui_drawDragBox(x+tmp*AHD.tAH, y, tmp * AHD.tD, h, -100, 100, 1, -100, -y); 820 | uix_tmp(1); 821 | ); 822 | 823 | function ui_initiation() ( 824 | uix.sColor = 1; //Stroke Color 825 | uix.fColor = 0; //Fill Color 826 | uix.tColor = 2; //Text Color 827 | uix.kColor = 7; //Knob Color 828 | uix.vColor = 2; //Curve Color 829 | uix.bColor = 8; //Box Color 830 | uix_setStrokeWidth(1); 831 | uix_setAlign(0,0); 832 | ui_rect(0, 0, gfx_w, gfx_h); 833 | uix.gridx = gfx_w * 0.01; 834 | uix.gridy = gfx_h * 0.01; 835 | uix.sliderh = uix.gridy * 3; 836 | uix.sliderHPad = uix.gridy * 5; 837 | uix.sliderw = uix.gridx * 75; 838 | uix.sliderw2 = uix.gridx * 30; 839 | uix.x = uix.gridx * 15; // Section L 840 | uix.x2 = uix.x * 4; // Section R 841 | ); 842 | 843 | // Initiation 844 | ui_initiation(); 845 | 846 | 847 | uix.page == 0 ? ( 848 | uix.x = gfx_w * 0.5; 849 | uix.tColor = 3; 850 | uix_setAlign(1,0); 851 | ui_text(uix.x, 0, "Tone ", 1); 852 | uix.tColor = 4; 853 | uix_setAlign(0,0); 854 | ui_text(uix.x, 0, "Sweep", 1); 855 | gfx_measurestr("Sweep", uix.x2, #); 856 | uix.y = gfx_texth; 857 | uix.tColor = 8; 858 | uix_setAlign(1,0.25); 859 | ui_text(uix.x + uix.x2, uix.y, "© RCJacH", 5); 860 | 861 | uix.y = uix.gridy * 20; 862 | // Pitch Curve 863 | uix.tColor = 2; 864 | uix_setAlign(1,0); 865 | slider2 = s2.ui_drawValueBox(uix.gridx * 20, uix.y, 0,127,1,64,uix_getPitName(s2.value),"Start Note"); 866 | uix_setAlign(1,1); 867 | slider4 = s4.ui_drawValueBox(uix.gridx * 20, uix.y+uix.gridy*20, 0,127,1,21,uix_getPitName(s4.value),"End Note"); 868 | gPit.ui_drawPitchGraph(uix.gridx * 20, uix.y, uix.gridx * 60, uix.gridy * 20); 869 | slider7 = s7.value = gPit.x.value; 870 | slider8 = s8.value = gPit.y.value; 871 | 872 | uix.y = uix.gridy * 70; 873 | // Envelope 874 | uix_setAlign(0,0); 875 | slider12 = gEnv.A.x.value = s12.ui_drawValueBox(uix.gridx * 20, uix.y, 0,1000,0.01,5,"ms","Attack"); 876 | slider13 = gEnv.H.x.value = s13.ui_drawValueBox(uix.gridx * 50, uix.y, 1,1000,0.01,125,"ms","Hold"); 877 | slider14 = gEnv.D.x.value = s14.ui_drawValueBox(uix.gridx * 80, uix.y, 0,5000,0.01,125,"ms","Decay"); 878 | uix.y += uix.sliderHPad; 879 | slider18 = s18.ui_drawValueBox(uix.gridx * 20, uix.y, 0,4,1,0,"","Shape"); 880 | s18.hidereadout = 1; 881 | slider21 = s21.ui_drawValueBox(uix.gridx * 80, uix.y, 0,4,1,0,"","Shape"); 882 | s21.hidereadout = 1; 883 | uix_setAlign(0,0.5); 884 | ui_textCurveName(uix.gridx * 20 + uix.gridx, uix.y + uix.sliderh * 0.5, s18.value); 885 | ui_textCurveName(uix.gridx * 80 + uix.gridx, uix.y + uix.sliderh * 0.5, s21.value); 886 | 887 | uix_setAlign(0,0); 888 | uix.y -= uix.sliderHPad + uix.gridy * 20; 889 | gEnv.ui_drawEnvGraph(0, uix.y, gfx_w, uix.gridy * 20); 890 | s12.value = slider12 = gEnv.A.x.value; 891 | s13.value = slider13 = gEnv.H.x.value; 892 | s14.value = slider14 = gEnv.D.x.value; 893 | s16.value = slider16 = gEnv.A.y.value; 894 | s19.value = slider19 = gEnv.D.y.value; 895 | ): 896 | uix.page == 1 ? ( 897 | 898 | // Section 1 899 | uix.y = uix.gridy * 5; 900 | uix.tColor = 3; 901 | uix_setAlign(0,0); 902 | ui_text(uix.gridx, uix.gridy * 5, "Pitch", 2); 903 | 904 | uix.y += gfx_texth + uix.gridy * 2; 905 | uix.tColor = 2; 906 | slider1 = s1.ui_drawSlider(uix.x, uix.y, uix.sliderw, uix.sliderh,400,480,0.01,440,"Hz","A4"); 907 | slider2 = s2.ui_drawSlider(uix.x, uix.y + uix.sliderHPad, uix.sliderw, uix.sliderh,0,127,1,64,uix_getPitName(s2.value),"Start Note"); 908 | slider3 = s3.ui_drawSlider(uix.x, uix.y + uix.sliderHPad * 2, uix.sliderw, uix.sliderh,-100,100,1,0,"Cents","Fine Tune"); 909 | slider4 = s4.ui_drawSlider(uix.x, uix.y + uix.sliderHPad * 3, uix.sliderw, uix.sliderh,0,127,1,21,uix_getPitName(s4.value),"End Note"); 910 | slider5 = s5.ui_drawSlider(uix.x, uix.y + uix.sliderHPad * 4, uix.sliderw, uix.sliderh,-100,100,1,0,"Cents","Fine Tune"); 911 | 912 | // Section 2 913 | uix.y = uix.gridy * 40; 914 | uix.tColor = 3; 915 | uix_setAlign(0,0); 916 | ui_text(uix.gridx, uix.y, "Sweep", 2); 917 | uix.y += gfx_texth + uix.gridy * 2; 918 | uix.tColor = 2; 919 | slider7 = gPit.x.value = s7.ui_drawSlider(uix.x, uix.y, uix.sliderw2, uix.sliderh,0,1000,0.01,125,"ms","Time"); 920 | slider8 = gPit.y.value = s8.ui_drawSlider(uix.x, uix.y + uix.sliderHPad, uix.sliderw2, uix.sliderh,-100,100,0.01,-100,"%","Curve"); 921 | slider9 = s9.ui_drawSlider(uix.x, uix.y + uix.sliderHPad * 2, uix.sliderw2, uix.sliderh,0,10,0.01,3,"x","Multiplier"); 922 | slider10 = s10.ui_drawSelector(uix.x, uix.y + uix.sliderHPad * 3, uix.sliderw2, uix.sliderh,0,4,1,1,"Shape"); 923 | uix_setAlign(0,0.5); 924 | ui_textCurveName(uix.x + uix.sliderw2 + uix.gridx, uix.y + uix.sliderHPad * 3 + uix.sliderh * 0.5, s10.value); 925 | 926 | // Draw Graph 927 | gPit.hidefrequency = gPit.hidelength = 0; 928 | gPit.ui_drawPitchGraph(uix.x2, uix.y, uix.sliderw2, uix.gridy * 20); 929 | slider7 = s7.value = gPit.x.value; 930 | slider8 = s8.value = gPit.y.value; 931 | ): 932 | uix.page == 2 ? ( 933 | uix.y = uix.gridy * 5; 934 | 935 | // Section 1 936 | uix.tColor = 3; 937 | uix_setAlign(0,0); 938 | ui_text(uix.gridx, uix.y, "Time", 2); 939 | 940 | uix.y += gfx_texth + uix.gridy * 2; 941 | uix.tColor = 2; 942 | uix_setAlign(0,0); 943 | slider12 = gEnv.A.x.value = s12.ui_drawSlider(uix.x, uix.y, uix.sliderw, uix.sliderh,0,1000,0.01,5,"ms","Attack"); 944 | slider13 = gEnv.H.x.value = s13.ui_drawSlider(uix.x, uix.y + uix.sliderHPad, uix.sliderw, uix.sliderh,1,1000,0.01,125,"ms","Hold"); 945 | slider14 = gEnv.D.x.value = s14.ui_drawSlider(uix.x, uix.y + uix.sliderHPad * 2, uix.sliderw, uix.sliderh,0,5000,0.01,125,"ms","Decay"); 946 | 947 | // Section 2 948 | uix.y = uix.gridy * 30; 949 | uix.tColor = 3; 950 | uix_setAlign(0,0); 951 | ui_text(uix.gridx, uix.y, "Curve", 2); 952 | uix.y += gfx_texth + uix.gridy * 2; 953 | uix.tColor = 2; 954 | uix_setAlign(0.5,1); 955 | ui_text(uix.x + uix.sliderw2 * 0.5, uix.y, "Attack", 3); 956 | ui_text(uix.x2 + uix.sliderw2 * 0.5, uix.y, "Decay", 3); 957 | 958 | uix_setAlign(0,0); 959 | slider16 = gEnv.A.y.value = s16.ui_drawSlider(uix.x, uix.y, uix.sliderw2, uix.sliderh,-100,100,1,100,"%","Curve"); 960 | slider19 = gEnv.D.y.value = s19.ui_drawSlider(uix.x2, uix.y, uix.sliderw2, uix.sliderh,-100,100,1,-100,"%",""); 961 | uix.y += uix.sliderHPad; 962 | slider17 = s17.ui_drawSlider(uix.x, uix.y, uix.sliderw2, uix.sliderh,0,10,0.01,1,"x","Multiplier"); 963 | slider20 = s20.ui_drawSlider(uix.x2, uix.y, uix.sliderw2, uix.sliderh,0,10,0.01,2,"x",""); 964 | uix.y += uix.sliderHPad; 965 | slider18 = s18.ui_drawSelector(uix.x, uix.y, uix.sliderw2, uix.sliderh,0,4,1,0,"Shape"); 966 | slider21 = s21.ui_drawSelector(uix.x2, uix.y, uix.sliderw2, uix.sliderh,0,4,1,0,""); 967 | uix_setAlign(0,0.5); 968 | ui_textCurveName(uix.x + uix.sliderw2 + uix.gridx, uix.y + uix.sliderh * 0.5, s18.value); 969 | ui_textCurveName(uix.x2 + uix.sliderw2 + uix.gridx, uix.y + uix.sliderh * 0.5, s21.value); 970 | 971 | // Draw Graph 972 | uix_setAlign(0,1); 973 | gEnv.ui_drawEnvGraph(0, uix.gridy * 60, gfx_w, uix.gridy * 20); 974 | s12.value = slider12 = gEnv.A.x.value; 975 | s13.value = slider13 = gEnv.H.x.value; 976 | s14.value = slider14 = gEnv.D.x.value; 977 | s16.value = slider16 = gEnv.A.y.value; 978 | s19.value = slider19 = gEnv.D.y.value; 979 | ): 980 | uix.page == 3 ? ( 981 | 982 | // Section 1 983 | uix.y = uix.gridy * 5; 984 | uix.tColor = 3; 985 | uix_setAlign(0,0); 986 | ui_text(uix.gridx, uix.y, "Waveform", 2); 987 | 988 | uix.tColor = 2; 989 | uix.y += gfx_texth + uix.gridy * 2; 990 | slider31 = s31.ui_drawSelector(uix.x, uix.y, uix.sliderw, uix.sliderh,0,3,1,0,"Waveform"); 991 | uix_setAlign(0,0.5); 992 | ui_textWaveformName(uix.x + uix.sliderw + uix.gridx, uix.y + uix.sliderh * 0.5, s31.value); 993 | 994 | // Section 2 995 | uix.y = uix.gridy * 25; 996 | uix.tColor = 3; 997 | uix_setAlign(0,0); 998 | ui_text(uix.gridx, uix.y, "Modulation", 2); 999 | uix.y += gfx_texth + uix.gridy * 2; 1000 | 1001 | uix.tColor = 2; 1002 | uix_setAlign(0.5,1); 1003 | ui_text(uix.x + uix.sliderw2 * 0.5, uix.y, "AM", 3); 1004 | ui_text(uix.x * 4 + uix.sliderw2 * 0.5, uix.y, "FM", 3); 1005 | uix_setAlign(0, 0); 1006 | LFO.AM.bSidechain = b_AMsc.ui_drawchkbox(uix.x * 3 - uix.sliderh, uix.y - uix.sliderHPad, uix.sliderh, "Sidechain"); 1007 | LFO.FM.bSidechain = b_FMsc.ui_drawchkbox(uix.x * 6 - uix.sliderh, uix.y - uix.sliderHPad, uix.sliderh, "Sidechain"); 1008 | slider38 = !b_FMstatic.ui_drawchkbox(uix.x2 + uix.gridx * 5, uix.y - uix.sliderHPad, uix.sliderh, "Static"); 1009 | slider23 = s23.ui_drawSlider(uix.x, uix.y, uix.sliderw2, uix.sliderh,0,20000,0.001,0,"Hz","Frequency"); 1010 | slider34 = s34.ui_drawSlider(uix.x2, uix.y, uix.sliderw2, uix.sliderh,0,20000,0.001,0,"Hz",""); 1011 | 1012 | uix.y += uix.sliderHPad; 1013 | slider24 = s24.ui_drawSlider(uix.x, uix.y, uix.sliderw2, uix.sliderh,-100,100,1,0,"%","Depth"); 1014 | slider35 = s35.ui_drawSlider(uix.x2, uix.y, uix.sliderw2, uix.sliderh,-100,100,1,0,"%",""); 1015 | uix.y += uix.sliderHPad; 1016 | slider25 = s25.ui_drawSlider(uix.x, uix.y, uix.sliderw2, uix.sliderh,-100,100,1,0,"%","Env Slant"); 1017 | slider36 = s36.ui_drawSlider(uix.x2, uix.y, uix.sliderw2, uix.sliderh,-100,100,1,0,"%",""); 1018 | uix.y += uix.sliderHPad; 1019 | slider26 = s26.ui_drawSelector(uix.x, uix.y, uix.sliderw2, uix.sliderh,0,3,1,1,"Waveform"); 1020 | slider37 = s37.ui_drawSelector(uix.x2, uix.y, uix.sliderw2, uix.sliderh,0,3,1,1,""); 1021 | uix_setAlign(0,0.5); 1022 | uix.y += uix.sliderh * 0.5; 1023 | ui_textWaveformName(uix.x + uix.sliderw2 + uix.gridx, uix.y, s26.value); 1024 | ui_textWaveformName(uix.x2 + uix.sliderw2 + uix.gridx, uix.y, s37.value); 1025 | 1026 | // Draw Graph 1027 | uix.tColor = 3; 1028 | uix_setAlign(0,0); 1029 | uix.y += uix.sliderHPad; 1030 | ui_text(uix.gridx, uix.y, "Slant", 2); 1031 | gAM.tCurve = uix.sliderw2; gAM.vCurve = 1; gAM.sCurve = 0; 1032 | gAM.vStart = LFO.AM.vStart; gAM.vEnd = LFO.AM.vEnd; 1033 | gAM.ui_drawGraph(uix.x, uix.y, uix.sliderw2, uix.gridy * 20, 1); 1034 | gFM.tCurve = uix.sliderw2; gFM.vCurve = 1; gFM.sCurve = 0; 1035 | gFM.vStart = LFO.FM.vStart; gFM.vEnd = LFO.FM.vEnd; 1036 | gFM.ui_drawGraph(uix.x2, uix.y, uix.sliderw2, uix.gridy * 20, 1); 1037 | ): 1038 | uix.page == 4 ? ( 1039 | uix.y = uix.gridy * 5; 1040 | uix.tColor = 3; 1041 | uix_setAlign(0,0); 1042 | ui_text(uix.gridx, uix.y, "Settings", 2); 1043 | uix.y += gfx_texth + uix.gridy * 2; 1044 | uix.tColor = 2; 1045 | slider41 = s41.ui_drawSlider(uix.x, uix.y, uix.sliderw, uix.sliderh,-120,0,1,-18,"dB","Volume"); 1046 | slider62 = keyFilter.ui_drawSlider(uix.x, uix.y + uix.sliderHPad, uix.sliderw, uix.sliderh,-1,127,1,24,"","Key Filter"); 1047 | 1048 | // Section 2 1049 | uix.y = uix.gridy * 25; 1050 | uix.tColor = 3; 1051 | uix_setAlign(0,0); 1052 | ui_text(uix.gridx, uix.y, "Velocity", 2); 1053 | uix.y += gfx_texth + uix.gridy * 2; 1054 | uix.tColor = 2; 1055 | slider61 = i_vel2vol.ui_drawSlider(uix.x, uix.y,uix.sliderw,uix.sliderh,0,1,0.01,0,"","Volume"); 1056 | uix.y += uix.sliderHPad; 1057 | slider60 = i_vel2pit.ui_drawSlider(uix.x, uix.y,uix.sliderw,uix.sliderh,0,1,0.01,0,"","Pitch"); 1058 | 1059 | // Section 3 1060 | uix.y = uix.gridy * 45; 1061 | uix.tColor = 3; 1062 | uix_setAlign(0,0); 1063 | ui_text(uix.gridx, uix.y, "Others", 2); 1064 | uix.y += gfx_texth + uix.gridy * 2; 1065 | uix.tColor = 2; 1066 | slider59 = b_noteoff.ui_drawchkbox(uix.x, uix.y, uix.sliderh, "Note Off"); 1067 | setNoteOff(b_noteoff.value); 1068 | slider58 = b_keytrack.ui_drawchkbox(uix.x2, uix.y, uix.sliderh, "Key Track"); 1069 | ); 1070 | 1071 | processSliders(); 1072 | // Waveform 1073 | ui_drawGraph(0, uix.gridy * 80, gfx_w, uix.gridy * 20,0); -------------------------------------------------------------------------------- /MIDI Editor/RCJacH_MIDI Humanizer.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Author: RCJacH 3 | Description: Humanize MIDI notes 4 | About: 5 | Humanize selected notes, or all notes if none selected, in the active 6 | MIDI editor, based on how a player would, i.e. timing and velocity 7 | drifting based on 1/f noise, and realizing that drift and trying 8 | to compensate. This script also keeps legato or non-legato between notes. 9 | Plus, timing variation is based on the current grid setting, it mimics 10 | how a player feels the subdivision while playing. 11 | 12 | Parameters: 13 | Experience: 14 | A more experienced player drifts less and makes less over-compensation. 15 | Tightness: 16 | Tighter means putting more effort to compensate when drift happens. 17 | Links: 18 | Github Repository https://github.com/RCJacH/ReaScript 19 | Version: 0.1 20 | Changelog: 21 | * v0.1 (2023-04-22) 22 | + Initial release 23 | --]] 24 | 25 | ------------------------------------- 26 | -- Global 27 | ------------------------------------- 28 | 29 | local PLUGIN_NAME = "MIDI Humanizer" 30 | 31 | if not reaper.CF_GetSWSVersion then 32 | reaper.ShowConsoleMsg("SWS extension required to get closest grid.") 33 | end 34 | 35 | if not reaper.JS_ReaScriptAPI_Version then 36 | reaper.ShowConsoleMsg("js_ReaScriptAPI extension required to remove window frames.\n") 37 | end 38 | 39 | ------------------------------------- 40 | -- Debug 41 | ------------------------------------- 42 | 43 | DEBUG = false 44 | function dbg(...) 45 | if DEBUG == true then 46 | local s = '' 47 | for _, v in pairs({...}) do 48 | s = s .. ' ' .. tostring(v) 49 | end 50 | reaper.ShowConsoleMsg(s .. '\n') 51 | end 52 | end 53 | 54 | ------------------------------------- 55 | -- Note 56 | ------------------------------------- 57 | local Note = {} 58 | Note.__index = Note 59 | 60 | function Note.new(take, noteidx) 61 | local retval, _, _, startppqpos, endppqpos, _, pitch, velocity = reaper.MIDI_GetNote(take, noteidx) 62 | local self = { 63 | take = take, 64 | noteidx = noteidx, 65 | startppqpos = startppqpos, 66 | endppqpos = endppqpos, 67 | pitch = pitch, 68 | velocity = velocity 69 | } 70 | setmetatable(self, Note) 71 | return self 72 | end 73 | 74 | function Note:set() 75 | reaper.MIDI_SetNote( 76 | self.take, 77 | self.noteidx, 78 | nil, 79 | nil, 80 | self.startppqpos, 81 | self.endppqpos, 82 | nil, 83 | self.pitch, 84 | self.velocity, 85 | true 86 | ) 87 | end 88 | 89 | function Note:start_qn() 90 | return reaper.MIDI_GetProjQNFromPPQPos(self.take, self.startppqpos) 91 | end 92 | 93 | function Note:end_qn() 94 | return reaper.MIDI_GetProjQNFromPPQPos(self.take, self.endppqpos) 95 | end 96 | 97 | function Note:nearest_start_grid() 98 | return reaper.MIDI_GetProjQNFromPPQPos( 99 | self.take, 100 | reaper.BR_GetClosestGridDivision(self.startppqpos) 101 | ) 102 | end 103 | 104 | ------------------------------------- 105 | -- White Noise 106 | ------------------------------------- 107 | 108 | local WhiteNoise = {} 109 | WhiteNoise.__index = WhiteNoise 110 | 111 | function WhiteNoise.new(max_octaves, seed) 112 | math.randomseed(seed or os.time()) 113 | local self = { 114 | octaves = {}, 115 | max_octaves = max_octaves or 16, 116 | } 117 | for i=0, self.max_octaves do 118 | self.octaves[i] = 0 119 | end 120 | 121 | setmetatable(self, WhiteNoise) 122 | return self 123 | end 124 | 125 | function WhiteNoise.generate() 126 | return math.random() * 2 - 1 127 | end 128 | 129 | function WhiteNoise:update(index) 130 | for octave = 1, self.max_octaves do 131 | if index % (1 << (octave - 1)) == 0 then 132 | self.octaves[octave] = self.generate() 133 | end 134 | end 135 | end 136 | 137 | local PinkNoise = {} 138 | PinkNoise.__index = PinkNoise 139 | 140 | function PinkNoise.new(...) 141 | local self = { 142 | white_noise = WhiteNoise.new(...), 143 | current_sample = 0, 144 | index = 0 145 | } 146 | setmetatable(self, PinkNoise) 147 | return self 148 | end 149 | 150 | function PinkNoise:next_sample() 151 | self.index = self.index + 1 152 | self.white_noise:update(self.index) 153 | local max_octaves = self.white_noise.max_octaves 154 | local sample = 0 155 | for octave = 1, max_octaves do 156 | sample = sample + self.white_noise.octaves[octave] 157 | end 158 | self.current_sample = sample / max_octaves 159 | return self.current_sample 160 | end 161 | 162 | ------------------------------------- 163 | -- Interpolation 164 | ------------------------------------- 165 | 166 | local easing = {} 167 | easing.__index = easing 168 | 169 | function easing.cosine(x, v) 170 | return (0.5 * (1 - math.cos(x ^ v * math.pi))) 171 | end 172 | 173 | function easing.sigmoid(x, v) 174 | return 1 / (1 + math.exp(-10 * x ^ v + 5)) 175 | end 176 | 177 | function easing.hermite(x, v) 178 | return 1 - (x ^ 2 * (2 * x - 3) + 1) ^ (1 / v) * math.sin(0.5 * math.pi) 179 | end 180 | 181 | function easing.equally_distributed(x, f, v, c) 182 | v = v or 1 183 | c = c or 0.5 184 | 185 | if x <= c then 186 | if c < 0.5 then 187 | x = (x + (1 - 2 * c)) / (1 - c) 188 | else 189 | x = x / c 190 | end 191 | return f(x, v) 192 | else 193 | if c > 0.5 then 194 | x = 1 - (x - c) / c 195 | else 196 | x = 1 - (x - c) / (1 - c) 197 | end 198 | return f(x, v) 199 | end 200 | end 201 | 202 | ------------------------------------- 203 | -- Player 204 | ------------------------------------- 205 | 206 | local Player = {} 207 | Player.__index = Player 208 | 209 | function Player.new(take) 210 | local note_placeholder = Note.new(take, -1) 211 | note_placeholder.startppqpos = 0.0 212 | note_placeholder.endppqpos = 0.0 213 | 214 | local self = { 215 | experience = 0.5, 216 | rev_exp = 0.5, 217 | tightness = 0.5, 218 | chord_threshold = 20, 219 | noise = PinkNoise.new(), 220 | beat_emphasis = 0.8, 221 | lastnote = note_placeholder, 222 | effort = { 223 | timing = 0, 224 | velocity = 0, 225 | }, 226 | drift = { 227 | timing = 0, 228 | velocity = 0, 229 | }, 230 | offset = { 231 | timing = 0, 232 | velocity = 0, 233 | } 234 | } 235 | setmetatable(self, Player) 236 | return self 237 | end 238 | 239 | function Player:set_experience(experience) 240 | self.experience = experience 241 | self.rev_exp = 1 - experience 242 | end 243 | 244 | function Player:set_tightness(tightness) 245 | self.tightness = tightness 246 | end 247 | 248 | function Player:get_emphasis(measure_start_qn, beat_qn, grid_qn) 249 | local emphasis = beat_qn == measure_start_qn and 4 or beat_qn == grid_qn and 2 or 1 250 | 251 | if emphasis > 1 then 252 | emphasis = emphasis * self.beat_emphasis 253 | end 254 | 255 | return 1 / emphasis 256 | end 257 | 258 | function Player:update_status(emphasis) 259 | self:update_effort(emphasis) 260 | self:update_drift(emphasis) 261 | end 262 | 263 | function Player:update_effort(emphasis) 264 | local do_drift_timing = math.abs(self.drift.timing) > 0.1 * self.rev_exp 265 | local do_drift_velocity = math.abs(self.drift.velocity) > 0.1 * self.rev_exp 266 | local timing_compensation = do_drift_timing and self.drift.timing * -self:get_random_effort(emphasis, true) or 0 267 | local velocity_compensation = do_drift_velocity and self.drift.velocity * -self:get_random_effort(emphasis, true) or 0 268 | 269 | local side_effect_timing = do_drift_velocity and velocity_compensation * self:get_random_effort(emphasis, false) ^ 2 * 0.25 or 0 270 | local side_effect_velocity = do_drift_timing and timing_compensation * self:get_random_effort(emphasis, false) ^ 2 * 0.25 or 0 271 | 272 | dbg("compensations: t", timing_compensation, '|', side_effect_timing, '|v', velocity_compensation, '|', side_effect_velocity) 273 | 274 | self.effort.timing = timing_compensation + side_effect_timing 275 | self.effort.velocity = velocity_compensation + side_effect_velocity 276 | 277 | dbg("effort:", self.effort.timing, '|', self.effort.velocity) 278 | end 279 | 280 | function Player:get_random_effort(emphasis, may_overshoot) 281 | local overshoot = may_overshoot and 0.125 * self.rev_exp ^ (0.8 / emphasis) or 0 282 | local effort = easing.equally_distributed(math.random(), easing.cosine, 2 ^ (2 * (self.tightness - 0.5)), 0) 283 | effort = 1 - effort * (1 + overshoot) 284 | effort = effort < 0 and 1 + -effort or effort 285 | return effort 286 | end 287 | 288 | function Player:update_drift(emphasis) 289 | local controlled_noise = self:get_controlled_noise_sample() 290 | dbg("controlled_noise:", controlled_noise) 291 | local compensation_factor = 1 - (self.effort.timing and 0.75 or 0.5) * self.experience 292 | self.drift.timing = self.drift.timing + self.effort.timing + controlled_noise * emphasis * compensation_factor 293 | 294 | compensation_factor = 1 - (self.effort.velocity and 0.75 or 0.5) * self.experience 295 | self.drift.velocity = self.drift.velocity + self.effort.velocity + controlled_noise * emphasis * compensation_factor 296 | 297 | dbg("drift:", self.drift.timing, '|', self.drift.velocity) 298 | 299 | end 300 | 301 | function Player:get_controlled_noise_sample() 302 | local noise_sample = self.noise:next_sample() 303 | return noise_sample 304 | end 305 | 306 | ------------------------------------- 307 | -- Process 308 | ------------------------------------- 309 | local Process = {} 310 | Process.__index = Process 311 | 312 | function Process.new() 313 | local editor = reaper.MIDIEditor_GetActive() 314 | if not editor then return end 315 | 316 | local take = reaper.MIDIEditor_GetTake(editor) 317 | if not take then return end 318 | 319 | local retval, note_count = reaper.MIDI_CountEvts(take) 320 | if not retval or not note_count then return end 321 | 322 | local self = { 323 | take = take, 324 | noteidx = -1, 325 | note_count = note_count, 326 | query = {}, 327 | } 328 | 329 | setmetatable(self, Process) 330 | return self 331 | end 332 | 333 | function Process:call(player) 334 | reaper.PreventUIRefresh(1) 335 | self.player = player 336 | reaper.MIDI_DisableSort(self.take) 337 | local cnt = 0 338 | while true do 339 | self.noteidx = reaper.MIDI_EnumSelNotes(self.take, self.noteidx) 340 | if self.noteidx == -1 then break end 341 | self:iterating_sel_notes() 342 | cnt = cnt + 1 343 | end 344 | if cnt == 0 then 345 | for i = 0, self.note_count - 1 do 346 | self.noteidx = i 347 | self:iterating_sel_notes() 348 | end 349 | end 350 | reaper.MIDI_Sort(self.take) 351 | reaper.PreventUIRefresh(-1) 352 | reaper.UpdateArrange() 353 | reaper.Undo_OnStateChange(PLUGIN_NAME) 354 | end 355 | 356 | function Process:iterating_sel_notes() 357 | local note = Note.new(self.take, self.noteidx) 358 | if #self.query == 0 then 359 | self.query[#self.query + 1] = note 360 | return 361 | end 362 | 363 | local lastnote = self.query[#self.query] 364 | if math.abs(note.startppqpos - lastnote.startppqpos) <= self.player.chord_threshold then 365 | self.query[#self.query + 1] = note 366 | return 367 | end 368 | 369 | self:process_query() 370 | self.player.lastnote = lastnote 371 | self.query = { note } 372 | end 373 | 374 | function Process:process_query() 375 | local first_note = self.query[1] 376 | local first_note_start_qn = first_note:start_qn() 377 | local beat_qn, offbeat_qn = math.modf(first_note_start_qn + 0.5) 378 | local grid_qn = first_note:nearest_start_grid() 379 | local retval, measure_start_qn, _ = reaper.TimeMap_QNToMeasures(-1, beat_qn) 380 | local emphasis = self.player:get_emphasis(measure_start_qn, beat_qn, grid_qn) 381 | 382 | self.player:update_status(emphasis) 383 | 384 | local timing_offset, vel_offset 385 | for _, note in ipairs(self.query) do 386 | timing_offset, vel_offset = self:process_note(note) 387 | end 388 | 389 | self.player.offset.timing = timing_offset 390 | self.player.offset.velocity = vel_offset 391 | end 392 | 393 | function Process:process_note(note) 394 | local lastnote = self.player.lastnote 395 | 396 | local was_legato = note.startppqpos < lastnote.endppqpos 397 | local grid_size, swing = reaper.MIDI_GetGrid(note.take) 398 | 399 | local raw_startppqpos = note.startppqpos 400 | local raw_velocity = note.velocity 401 | local raw_start_qn = note:start_qn() 402 | local new_start_qn = self:apply_start_qn_modulation(note:start_qn(), grid_size) 403 | note.startppqpos = reaper.MIDI_GetPPQPosFromProjQN(note.take, new_start_qn) 404 | 405 | local timing_diff = note.startppqpos - raw_startppqpos 406 | local is_now_legato = lastnote.endppqpos > note.startppqpos 407 | if (was_legato and not is_now_legato) or (not was_legato and is_now_legato) then 408 | lastnote.endppqpos = lastnote.endppqpos + timing_diff 409 | lastnote:set() 410 | end 411 | 412 | note.velocity = self:apply_velocity_modulation(note.velocity) 413 | note:set() 414 | 415 | return new_start_qn - raw_start_qn, note.velocity - raw_velocity 416 | end 417 | 418 | function Process:apply_start_qn_modulation(start_qn, grid_size) 419 | return start_qn + grid_size * -self.player.drift.timing 420 | end 421 | 422 | function Process:apply_velocity_modulation(velocity) 423 | local limit = 64 424 | local new_velocity = velocity + limit * self.player.drift.velocity 425 | new_velocity = math.max(1, math.min(new_velocity, 127)) 426 | return math.floor(new_velocity) 427 | end 428 | 429 | ------------------------------------- 430 | -- GUI 431 | ------------------------------------- 432 | 433 | local GUI = {} 434 | GUI.__index = GUI 435 | 436 | function GUI.new(grid_size, x_label, y_label) 437 | grid_size = grid_size or 160 438 | local x, y = reaper.GetMousePosition() 439 | local font_size = math.floor(grid_size / 10) 440 | local min_line_width = math.max(font_size / 16, 1) 441 | local w = grid_size + font_size * 1.5 442 | local h = grid_size + font_size * 1.5 443 | x = x - w * 0.5 444 | y = y - h * 0.5 445 | gfx.setfont(1, "Arial", font_size) 446 | gfx.init(PLUGIN_NAME, w, h, 0, x, y) 447 | local hwnd = reaper.JS_Window_Find(PLUGIN_NAME, true) 448 | if hwnd then 449 | local WS_POPUP = 0x80000000 450 | local WS_VISIBLE = 0x10000000 451 | local style = WS_POPUP | WS_VISIBLE 452 | reaper.JS_Window_SetStyle(hwnd, style) 453 | end 454 | local _, left, top, right, bottom = reaper.JS_Window_GetRect(hwnd) 455 | local self = { 456 | hwnd = hwnd, 457 | w = right - left, 458 | h = math.abs(bottom - top), 459 | gx = font_size * 1.5, 460 | gy = font_size * 2, 461 | grid_size = grid_size, 462 | font_size = font_size, 463 | x_label = x_label, 464 | y_label = y_label, 465 | left_mouse = 0, 466 | min_line_width = min_line_width, 467 | } 468 | setmetatable(self, GUI) 469 | return self 470 | end 471 | 472 | -- Check if the mouse is inside the button 473 | function GUI:is_mouse_inside_grid() 474 | local mx = gfx.mouse_x 475 | local my = gfx.mouse_y 476 | return ( 477 | mx >= self.gx 478 | and mx <= self.gx + self.grid_size 479 | and my >= self.gy 480 | and my <= self.gy + self.grid_size 481 | ) 482 | end 483 | 484 | function GUI:mouse_position_in_grid() 485 | local mx = gfx.mouse_x 486 | local my = gfx.mouse_y 487 | local gx = self.gx 488 | local gy = self.gy 489 | local grid_size = self.grid_size 490 | return (mx - gx) / grid_size, 1 - (my - gy) / grid_size 491 | end 492 | 493 | function GUI:draw_grid() 494 | local x = self.gx 495 | local y = self.gy 496 | local full_grid = self.grid_size 497 | local half_grid = full_grid * 0.5 498 | gfx.set(0.298039, 0.337255, 0.415686, 1) 499 | for i = 1, self.min_line_width * 3 do 500 | gfx.rect(x - i, y - i, full_grid + i * 2, full_grid + i * 2, 0) 501 | end 502 | gfx.set(0.298039, 0.337255, 0.415686, 0.5) 503 | for i = 1, self.min_line_width * 2 do 504 | local sign = i % 2 and -1 or 1 505 | local j = i // 2 506 | gfx.line(x, y + half_grid + sign * j, x + full_grid, y + half_grid + sign * j) 507 | gfx.line(x + half_grid + sign * j, y, x + half_grid + sign * j, y + full_grid) 508 | end 509 | 510 | -- Display current value 511 | local mx = gfx.mouse_x 512 | local my = gfx.mouse_y 513 | local x, y = self:mouse_position_in_grid() 514 | if x < 0 or x > 1 or y < 0 or y > 1 then return end 515 | 516 | local str = string.format("(%.2f, %.2f)", x, y) 517 | local str_len = gfx.measurestr(str) 518 | local my_grid = y * self.grid_size 519 | local font_size = self.font_size 520 | if my_grid < font_size then 521 | gfx.x = mx - str_len / 2 522 | gfx.y = my - font_size 523 | elseif (self.grid_size - my_grid) < font_size then 524 | gfx.x = mx - str_len / 2 525 | gfx.y = my + font_size 526 | elseif x <= 0.5 then 527 | gfx.x = mx 528 | gfx.y = my - font_size / 2 529 | else 530 | gfx.x = mx - str_len 531 | gfx.y = my - font_size / 2 532 | end 533 | gfx.set(0.847059, 0.870588, 0.913725, 1) 534 | gfx.drawstr(str) 535 | end 536 | 537 | function GUI:draw_axes_labels() 538 | gfx.set(0.847059, 0.870588, 0.913725, 1) 539 | gfx.x, gfx.y = (gfx.w - gfx.measurestr(self.x_label)) / 2, gfx.h - self.font_size * 1.5 540 | gfx.drawstr(self.x_label) 541 | 542 | gfx.y = (gfx.h - #self.y_label * self.font_size * 0.75) / 2 543 | for i = 1, #self.y_label do 544 | local char = self.y_label:byte(i) 545 | local char_w = gfx.measurechar(char) 546 | gfx.x = self.font_size * 0.7 - char_w / 2 547 | gfx.drawchar(char) 548 | gfx.y = gfx.y + self.font_size * 0.7 549 | end 550 | end 551 | 552 | function GUI:draw() 553 | gfx.set(0.180392, 0.203922, 0.25098, 1) 554 | gfx.rect(0, 0, self.w, self.h) 555 | gfx.set(0.505882, 0.631373, 0.756863, 1) 556 | gfx.x, gfx.y = (gfx.w - gfx.measurestr(PLUGIN_NAME)) / 2, self.font_size * 0.5 557 | gfx.drawstr(PLUGIN_NAME) 558 | self:draw_axes_labels() 559 | self:draw_grid() 560 | 561 | local key = gfx.getchar() 562 | if key == 27 then 563 | return -1 564 | end 565 | 566 | local mouse_clicked = gfx.mouse_cap&1 == 1 567 | if mouse_clicked then 568 | if self.left_mouse ~= -1 and self:is_mouse_inside_grid() then 569 | self.left_mouse = 1 570 | else 571 | self.left_mouse = -1 572 | end 573 | else 574 | if self.left_mouse == 1 and self:is_mouse_inside_grid() then 575 | return 1, self:mouse_position_in_grid() 576 | end 577 | self.left_mouse = 0 578 | end 579 | 580 | gfx.update() 581 | return 0 582 | end 583 | 584 | ------------------------------------- 585 | -- Main 586 | ------------------------------------- 587 | 588 | local function main() 589 | local window = reaper.JS_Window_GetForeground() 590 | local process = Process.new() 591 | if not process then return end 592 | local player = Player.new(process.take) 593 | local gui = GUI.new(160, 'Experience', 'Tightness') 594 | 595 | local open = 0 596 | local x, y 597 | local function loop() 598 | local focusedWindow = reaper.JS_Window_GetFocus() 599 | local parentWindow = reaper.JS_Window_GetParent(focusedWindow) 600 | if gui.hwnd ~= focusedWindow and gui.hwnd ~= parentWindow then 601 | gfx.quit() 602 | return 603 | end 604 | if open == 0 then 605 | open, x, y = gui:draw(player) 606 | reaper.defer(loop) 607 | else 608 | if open == 1 then 609 | player:set_experience(x) 610 | player:set_tightness(y) 611 | process:call(player) 612 | end 613 | gfx.quit() 614 | reaper.JS_Window_SetForeground(window) 615 | end 616 | end 617 | 618 | loop() 619 | end 620 | 621 | main() 622 | -------------------------------------------------------------------------------- /MIDI Editor/RCJacH_Split notes at mouse cursor (obey snapping and selection).lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | @author RCJacH 3 | @description Split notes at mouse cursor (obey snapping and selection) 4 | @link 5 | Github Repository https://github.com/RCJacH/ReaScript 6 | @version 1.2.2 7 | @changelog 8 | fix wrong splitting position with odd time signature and uncommon grid size 9 | 10 | @about 11 | Split selected notes at mouse cursor (obey snapping), if no notes are selected 12 | split only the note under mouse cursor, if there is no note under mouse cursor, 13 | split all notes. 14 | ]] 15 | 16 | function split_no_selection(take, mouse_pos, mouse_pitch) 17 | local pending_insert = {} 18 | local pending_set = {} 19 | local i = 0 20 | local retval, selected, muted, startppqpos, endppqpos, chan, pitch, vel = reaper.MIDI_GetNote(take, i) 21 | 22 | while retval do 23 | if startppqpos < mouse_pos and endppqpos > mouse_pos then 24 | local v = {selected, muted, mouse_pos, endppqpos, chan, pitch, vel, true} 25 | if pitch == mouse_pitch then 26 | reaper.MIDI_SetNote(take, i, selected, muted, startppqpos, mouse_pos, chan, pitch, vel, true) 27 | reaper.MIDI_InsertNote(take, table.unpack(v)) 28 | return 29 | end 30 | pending_set[#pending_set + 1] = {i, selected, muted, startppqpos, mouse_pos, chan, pitch, vel, true} 31 | pending_insert[#pending_insert + 1] = v 32 | end 33 | i = i + 1 34 | retval, selected, muted, startppqpos, endppqpos, chan, pitch, vel = reaper.MIDI_GetNote(take, i) 35 | end 36 | 37 | for _, v in ipairs(pending_set) do 38 | reaper.MIDI_SetNote(take, table.unpack(v)) 39 | end 40 | 41 | for _, v in ipairs(pending_insert) do 42 | reaper.MIDI_InsertNote(take, table.unpack(v)) 43 | end 44 | end 45 | 46 | function split_selected(take, cur_sel_note_idx, mouse_pos) 47 | local pending = {} 48 | while cur_sel_note_idx ~= -1 do 49 | local retval, selected, muted, startppqpos, endppqpos, chan, pitch, vel = reaper.MIDI_GetNote(take, cur_sel_note_idx) 50 | if startppqpos < mouse_pos and endppqpos > mouse_pos then 51 | reaper.MIDI_SetNote(take, cur_sel_note_idx, selected, muted, startppqpos, mouse_pos, chan, pitch, vel, true) 52 | pending[#pending + 1] = {take, selected, muted, mouse_pos, endppqpos, chan, pitch, vel, true} 53 | end 54 | cur_sel_note_idx = reaper.MIDI_EnumSelNotes(take, cur_sel_note_idx) 55 | end 56 | 57 | for _, v in pairs(pending) do 58 | reaper.MIDI_InsertNote(table.unpack(v)) 59 | end 60 | end 61 | 62 | function split(take, mouse_pos, mouse_pitch) 63 | local cur_sel_note_idx = reaper.MIDI_EnumSelNotes(take, -1) 64 | 65 | if cur_sel_note_idx == -1 then 66 | split_no_selection(take, mouse_pos, mouse_pitch) 67 | else 68 | split_selected(take, cur_sel_note_idx, mouse_pos) 69 | end 70 | 71 | end 72 | 73 | function main() 74 | local window, segment, details = reaper.BR_GetMouseCursorContext() 75 | 76 | if window ~= "midi_editor" then return end 77 | if segment == "piano" then return end 78 | 79 | local editor, _, mouse_pitch, _, _, _ = reaper.BR_GetMouseCursorContext_MIDI() 80 | local take = reaper.MIDIEditor_GetTake(editor) 81 | local mouse_time = reaper.BR_GetMouseCursorContext_Position() 82 | local mouse_pos 83 | if reaper.MIDIEditor_GetSetting_int(editor, "snap_enabled") == 1 then 84 | local _, measures = reaper.TimeMap2_timeToBeats(-1, mouse_time) 85 | local _, qn_start, qn_end, _, _, _ = reaper.TimeMap_GetMeasureInfo(-1, measures) 86 | local qn = reaper.TimeMap_timeToQN(mouse_time) 87 | local qn_in_measure = qn - qn_start 88 | local grid_size, swing = reaper.MIDI_GetGrid(take) 89 | local base_grid_size = grid_size * 2 90 | local grid, frac = math.modf(qn_in_measure / base_grid_size) 91 | local split_point = 0.5 + 0.25 * swing 92 | local diff = frac - split_point 93 | if diff < 0 then 94 | frac = math.abs(diff) < split_point / 2 and split_point or 0 95 | else 96 | frac = diff > (1 - split_point) / 2 and 1 or split_point 97 | end 98 | local final_measure_qn = math.min((grid + frac) * base_grid_size, qn_end) 99 | mouse_pos = reaper.MIDI_GetPPQPosFromProjQN(take, qn_start + final_measure_qn) 100 | else 101 | mouse_pos = reaper.MIDI_GetPPQPosFromProjTime(take, mouse_time) 102 | end 103 | reaper.MIDI_DisableSort(take) 104 | split(take, mouse_pos, mouse_pitch) 105 | reaper.MIDI_Sort(take) 106 | end 107 | 108 | reaper.Undo_BeginBlock() 109 | reaper.PreventUIRefresh(1) 110 | main() 111 | reaper.PreventUIRefresh(-1) 112 | 113 | reaper.Undo_EndBlock("Split notes at mouse cursor (obey snapping and selection)", 0) 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReaScripts 2 | 3 | This is a collection of JSFX and Lua Scripts that I have written for REAPER DAW. 4 | -------------------------------------------------------------------------------- /Templating/RCJacH_Set Airwindows Console TrackFX Pin Mapping.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Description: Setup the pin mapping of selected airwindows console summing track, with console bus as the last fx and console channel as all others. 3 | Version: 1.0a 4 | Author: RCJacH 5 | Reference: 6 | Changelog: 7 | * v1.0a (2021-05-06) 8 | + Initial Release. 9 | --]] 10 | 11 | local track_count = reaper.CountSelectedTracks(0) 12 | if not track_count or track_count == 0 then return end 13 | 14 | local function Msg(str) 15 | reaper.ShowConsoleMsg(tostring(str).."\n") 16 | end 17 | 18 | function bitstr2int(bitstr) 19 | local int = 0 20 | for i=1, #bitstr do 21 | local v = bitstr:sub(i, i) 22 | if v == "1" then int = int + (2 ^ i) end 23 | end 24 | return int 25 | end 26 | 27 | function parsePinBits(bitstr) 28 | local lo32bits = bitstr:sub(1, 32) 29 | local hi32bits = bitstr:sub(33) 30 | return bitstr2int(lo32bits), bitstr2int(hi32bits) 31 | end 32 | 33 | function calcBusInputPinBits(fx_total, is_pin_even) 34 | local bitstr = (string.rep("00", 32 - fx_total) .. string.rep("01", fx_total)):reverse() 35 | return parsePinBits(bitstr) 36 | end 37 | 38 | function calcChannelInputPinBits(fx_i) 39 | local bitstr = (string.rep("00", 31 - fx_i) .. "01" .. string.rep("00", fx_i)):reverse() 40 | return parsePinBits(bitstr) 41 | end 42 | 43 | reaper.Undo_BeginBlock() 44 | 45 | for tr_i = 0, track_count - 1 do 46 | local track = reaper.GetSelectedTrack(0, tr_i) 47 | local fx_count = reaper.TrackFX_GetCount(track) 48 | if fx_count then 49 | fx_totalx = fx_count - 1 50 | reaper.SetMediaTrackInfo_Value(track, "I_NCHAN", fx_totalx * 2) 51 | for fx_i=0, fx_totalx do 52 | if fx_i == fx_totalx then 53 | local lo32, hi32 = calcBusInputPinBits(fx_totalx) 54 | reaper.TrackFX_SetPinMappings(track, fx_i, 0, 0, lo32>>1, hi32>>1) 55 | reaper.TrackFX_SetPinMappings(track, fx_i, 0, 1, lo32, hi32) 56 | reaper.TrackFX_SetPinMappings(track, fx_i, 1, 0, 1, 0) 57 | reaper.TrackFX_SetPinMappings(track, fx_i, 1, 1, 2, 0) 58 | else 59 | local lo32, hi32 = calcChannelInputPinBits(fx_i) 60 | for isOutput=0, 1 do 61 | reaper.TrackFX_SetPinMappings(track, fx_i, isOutput, 0, lo32>>1, hi32>>1) 62 | reaper.TrackFX_SetPinMappings(track, fx_i, isOutput, 1, lo32, hi32) 63 | end 64 | end 65 | end 66 | end 67 | end 68 | 69 | reaper.UpdateArrange() 70 | reaper.Undo_EndBlock2(0, "Set Airwindows Console trackFX Pin Mapping", -1) 71 | -------------------------------------------------------------------------------- /Track Properties/RCJacH_Set Parent Send Channel Offset of Selected Tracks.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Description: Set Channel Offset of All Selected Tracks with prompt input 3 | Version: 1.0a 4 | Author: RCJacH 5 | Reference: 6 | Changelog: 7 | * v1.0a (2021-05-05) 8 | + Initial Release. 9 | --]] 10 | 11 | local track_count = reaper.CountSelectedTracks(0) 12 | if not track_count or track_count == 0 then return end 13 | 14 | local retval, s_channel = reaper.GetUserInputs("Set Start Channel for Channel Offsets to Parent", 1, "Starting Channel", "1") 15 | if not retval then return end 16 | 17 | assert(tonumber(s_channel), "invalid input: " .. s_channel .. " is not a number") 18 | 19 | local i_channel = tonumber(s_channel) 20 | 21 | assert((i_channel > 0) and (i_channel < 64), "invalid input: " .. s_channel .. " is out of range") 22 | 23 | reaper.Undo_BeginBlock() 24 | 25 | for i = 0, track_count - 1 do 26 | reaper.SetMediaTrackInfo_Value(reaper.GetSelectedTrack(0, i), "C_MAINSEND_OFFS", i_channel - 1) 27 | end 28 | 29 | reaper.UpdateArrange() 30 | reaper.Undo_EndBlock2(0, "Set Channel Offsets of Selected Tracks to " .. s_channel, -1) 31 | -------------------------------------------------------------------------------- /Track Properties/RCJacH_Set Parent Send Channel Offset to Sequentially Stereos for Selected Tracks.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Description: Set the parent send channel offset for each of all selected tracks to a sequential stereo pair 3 | Version: 1.0a 4 | Author: RCJacH 5 | Reference: 6 | Changelog: 7 | * v1.0a (2021-05-06) 8 | + Initial Release. 9 | --]] 10 | 11 | local track_count = reaper.CountSelectedTracks(0) 12 | if not track_count or track_count == 0 then return end 13 | 14 | assert(track_count < 32, "Only maximum of 32 tracks allowed") 15 | reaper.Undo_BeginBlock() 16 | 17 | local start_channel = reaper.GetMediaTrackInfo_Value(reaper.GetSelectedTrack(0, 0), "C_MAINSEND_OFFS") 18 | for i = 0, track_count - 1 do 19 | reaper.SetMediaTrackInfo_Value(reaper.GetSelectedTrack(0, i), "C_MAINSEND_OFFS", start_channel + i * 2) 20 | end 21 | 22 | reaper.UpdateArrange() 23 | reaper.Undo_EndBlock2(0, "Set Sequential parent Send Channel Offsets of Selected Tracks to ", -1) 24 | -------------------------------------------------------------------------------- /Various/RCJacH_Generate LRC Lyrics and Export to Clipboard.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | @author RCJacH 3 | @description Generate LRC Lyrics and Export to Clipboard 4 | @about 5 | # What Does This Do 6 | 7 | This script generates the lyrics in [LRC](https://en.wikipedia.org/wiki/LRC_(file_format)) format 8 | from the take name of all items on the first selected track, 9 | and export the result to the system clipboard. 10 | - Take name starting with # is cosidered as ID tag. 11 | - Empty take name is rendered with only time code, usually for spacing purposes. 12 | @link 13 | Github Repository https://github.com/RCJacH/ReaScripts 14 | @version 1.0 15 | ]]-- 16 | 17 | function format_time(seconds) 18 | local minus, mm, ss, xx 19 | mm = math.floor(seconds / 60) 20 | ss = seconds % 60 21 | ss, xx = tostring(ss):match('[?]-(%d+).(%d*)') 22 | return string.format("[%02d:%02s.%s]", mm, ss, xx:sub(1, 2)) 23 | end 24 | 25 | reaper.PreventUIRefresh(1) 26 | track = reaper.GetSelectedTrack(0, 0) 27 | itemsCount = reaper.CountTrackMediaItems(track) 28 | lyrics = "" 29 | for itemidx = 0, itemsCount - 1, 1 do 30 | local item, location, take 31 | --, takeName, time 32 | --, title, content 33 | item = reaper.GetTrackMediaItem(track, itemidx) 34 | location = reaper.GetMediaItemInfo_Value(item, "D_POSITION") 35 | take = reaper.GetActiveTake(item) 36 | takeName = reaper.GetTakeName(take) 37 | title, content = takeName:match("^(#*)(.*)") 38 | if title == "" then 39 | content = format_time(location) .. content 40 | else 41 | content = "[" .. content .. "]" 42 | end 43 | lyrics = lyrics .. content .. "\n" 44 | end 45 | reaper.CF_SetClipboard(lyrics) 46 | lyrics = "The following lyrics has been copied to the system clipboard:\n\n" .. lyrics 47 | reaper.ShowMessageBox(lyrics, "Lyrics", 0) 48 | reaper.PreventUIRefresh(-1) 49 | -------------------------------------------------------------------------------- /Various/Vimper Solo/Bindings.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This script is part of Vimper Solo Package 3 | NoIndex: true 4 | --]] 5 | local Transport, Navigation, MIDI, Layout, Track, Item, Take, Envelope, Windows 6 | 7 | 8 | Transport = { 9 | NAME = "Transport", 10 | p = {40073, "Pause/Play"}, 11 | ['['] = {40042, "Go to end of project"}, 12 | [']'] = {40043, "Go to end of project"}, 13 | RIGHT = {40085, "Fast forward a little bit"}, 14 | LEFT = {40085, "Rewind a little bit"}, 15 | r = {1068, "Toggle Repeat"}, 16 | SPACE = {40044, "Play/Stop"}, 17 | } 18 | 19 | Navigation = { 20 | NAME = "Navigation", 21 | t = { 22 | NAME = "Track Navigation", 23 | UP = {40286, "Go to previous track"}, 24 | LEFT = {40288, "Go to previous track (leave selection)"}, 25 | DOWN = {40285, "Go to next track"}, 26 | RIGHT = {40287, "Go to next track (leave selection)"}, 27 | }, 28 | i = { 29 | NAME = "Item Navigation", 30 | UP = {40418, "Select and move to item in previous track"}, 31 | LEFT = {40416, "Select and move to previous item"}, 32 | DOWN = {40419, "Select and move to item in next track"}, 33 | RIGHT = {40417, "Select and move to next item"}, 34 | }, 35 | e = { 36 | NAME = "Envelope Navigation", 37 | DOWN = {41864, "Select next envelope"}, 38 | LEFT = {"_BR_ENV_SEL_PREV_POINT", "Select previous point"}, 39 | RIGHT = {"_BR_ENV_SEL_NEXT_POINT", "Select next point"}, 40 | UP = {41863, "Select previous envelope"}, 41 | }, 42 | s = { 43 | NAME = "Scroll Navigation", 44 | LEFT = {40140, "Scroll view left"}, 45 | RIGHT = {40141, "Scroll view right"}, 46 | UP = {40138, "Scroll view up"}, 47 | DOWN = {40139, "Scroll view down"}, 48 | }, 49 | } 50 | 51 | MIDI = { 52 | NAME = "MIDI Editor", 53 | 54 | } 55 | 56 | Layout = { 57 | NAME = "Layout", 58 | d = {48500, "Default Layout"}, 59 | t = {40853, "Toggle visibility in TCP"}, 60 | m = {40250, "Toggle visibility in MCP"}, 61 | l = { 62 | NAME = "Load Track View", 63 | [1] = {40444, "Load track view #01"}, 64 | [2] = {40445, "Load track view #02"}, 65 | [3] = {40446, "Load track view #03"}, 66 | [4] = {40447, "Load track view #04"}, 67 | [5] = {40448, "Load track view #05"}, 68 | [6] = {40449, "Load track view #06"}, 69 | [7] = {40450, "Load track view #07"}, 70 | [8] = {40451, "Load track view #08"}, 71 | [9] = {40452, "Load track view #09"}, 72 | a = {40453, "Load track view #10"}, 73 | }, 74 | s = { 75 | NAME = "Save Track View", 76 | [1] = {40464, "Save track view #01"}, 77 | [2] = {40465, "Save track view #02"}, 78 | [3] = {40466, "Save track view #03"}, 79 | [4] = {40467, "Save track view #04"}, 80 | [5] = {40468, "Save track view #05"}, 81 | [6] = {40469, "Save track view #06"}, 82 | [7] = {40470, "Save track view #07"}, 83 | [8] = {40471, "Save track view #08"}, 84 | [9] = {40472, "Save track view #09"}, 85 | a = {40473, "Save track view #10"}, 86 | }, 87 | L = { 88 | NAME = "Load Window View", 89 | [1] = {40454, "Load window set #01"}, 90 | [2] = {40455, "Load window set #02"}, 91 | [3] = {40456, "Load window set #03"}, 92 | [4] = {40457, "Load window set #04"}, 93 | [5] = {40458, "Load window set #05"}, 94 | [6] = {40459, "Load window set #06"}, 95 | [7] = {40460, "Load window set #07"}, 96 | [8] = {40461, "Load window set #08"}, 97 | [9] = {40462, "Load window set #09"}, 98 | a = {40463, "Load window set #10"}, 99 | }, 100 | S = { 101 | NAME = "Save Window View", 102 | [1] = {40474, "Save window set #01"}, 103 | [2] = {40475, "Save window set #02"}, 104 | [3] = {40476, "Save window set #03"}, 105 | [4] = {40477, "Save window set #04"}, 106 | [5] = {40478, "Save window set #05"}, 107 | [6] = {40479, "Save window set #06"}, 108 | [7] = {40480, "Save window set #07"}, 109 | [8] = {40481, "Save window set #08"}, 110 | [9] = {40482, "Save window set #09"}, 111 | a = {40483, "Save window set #10"}, 112 | }, 113 | } 114 | 115 | Track = { 116 | NAME = "Track", 117 | a = {40296, "Select all tracks"}, 118 | n = {40001, "Add new track"}, 119 | t = {46000, "Add from track template"}, 120 | T = {40392, "Save tracks as track template..."}, 121 | x = {40337, "Cut tracks"}, 122 | c = {40210, "Copy tracks"}, 123 | v = {40058, "Paste items/tracks"}, 124 | d = {40062, "Duplicate tracks"}, 125 | m = {40280, "Mute tracks"}, 126 | M = {40341, "Mute all tracks"}, 127 | DELETE = {40005, "Remove tracks"}, 128 | s = {7, "Toggle solo"}, 129 | S = {41199, "Toggle solo defeat"}, 130 | -- = {41997, "Move tracks to subproject"}, 131 | g = {40771, "Toggle all track grouping enabled"}, 132 | b = {41223, "Freeze to stereo"}, 133 | B = {41644, "Unfreeze tracks"}, 134 | f = {40291, "View FX chain"}, 135 | F = {40844, "View input FX"}, 136 | i = {40293, "View track I/O"}, 137 | r = {9, "Toggle record arm"}, 138 | R = {40495, "Cycle track record monitor"}, 139 | p = {41321, "Set record path to primary"}, 140 | P = {41322, "Set record path to secondary"}, 141 | h = {40282, "Invert track phase"}, 142 | l = {41314, "Toggle lock track controls"}, 143 | } 144 | 145 | Item = { 146 | NAME = "Item", 147 | e = {41848, "Stretch at mouse"}, 148 | d = {41295, "Duplicate item"}, 149 | f = { 150 | NAME = "Item Fade", 151 | ['/'] = {"_RS5b9ffa0b6fb23c2342fdc9b5b329fde14130ecb0", "Fade in to mouse"}, 152 | ['\\'] = {"_RSda12211743f373b6890f510ea9e2277926005b46", "Fade out from mouse"}, 153 | ['['] = {41191, "Remove fade in"}, 154 | [']'] = {41192, "Remove fade out"}, 155 | 156 | }, 157 | r = {41051, "Reverse"}, 158 | R = {40270, "Reverse to new take"}, 159 | l = {40636, "Loop item source"}, 160 | f = {40641, "Toggle free positioning"}, 161 | o = {40688, "Lock"}, 162 | O = {41340, "Lock to active take"}, 163 | s = {41559, "Solo"}, 164 | m = {40719, "Mute"}, 165 | h = {40181, "Invert Phase"}, 166 | p = { 167 | NAME = "Item Pitch", 168 | s = {40204, "Pitch up one semitone"}, 169 | S = {40205, "Pitch down one semitone"}, 170 | o = {40515, "Pitch up one octave"}, 171 | O = {40516, "Pitch down one octave"}, 172 | c = {40206, "Pitch down one cent"}, 173 | C = {40207, "Pitch down one cent"}, 174 | r = {40653, "Reset item pitch"}, 175 | }, 176 | c = { 177 | NAME = "Item Channel", 178 | m = { 179 | NAME = "Mono", 180 | [0] = {40178, "Set to Left+Right"}, 181 | [1] = {40179, "Set to Left"}, 182 | [2] = {40180, "Set to Right"}, 183 | [3] = {41388, "Set to Channel 3"}, 184 | [4] = {41389, "Set to Channel 4"}, 185 | [5] = {41390, "Set to Channel 5"}, 186 | [6] = {41391, "Set to Channel 6"}, 187 | [7] = {41392, "Set to Channel 7"}, 188 | [8] = {41393, "Set to Channel 8"}, 189 | [9] = {41394, "Set to Channel 9"}, 190 | a = {41395, "Set to Channel 10"}, 191 | b = {41396, "Set to Channel 11"}, 192 | c = {41397, "Set to Channel 12"}, 193 | d = {41398, "Set to Channel 13"}, 194 | e = {41399, "Set to Channel 14"}, 195 | f = {41400, "Set to Channel 15"}, 196 | g = {41401, "Set to Channel 16"}, 197 | }, 198 | s = { 199 | NAME = "Stereo", 200 | [1] = {41450, "Set to Channel 1|2"}, 201 | [2] = {41452, "Set to Channel 3|4"}, 202 | [3] = {41454, "Set to Channel 5|6"}, 203 | [4] = {41456, "Set to Channel 7|8"}, 204 | [5] = {41458, "Set to Channel 9|10"}, 205 | [6] = {41460, "Set to Channel 11|12"}, 206 | [7] = {41462, "Set to Channel 13|14"}, 207 | [8] = {41464, "Set to Channel 15|16"}, 208 | [9] = {41466, "Set to Channel 17|18"}, 209 | a = {41468, "Set to Channel 19|20"}, 210 | b = {41470, "Set to Channel 21|22"}, 211 | c = {41472, "Set to Channel 23|24"}, 212 | d = {41474, "Set to Channel 25|26"}, 213 | e = {41476, "Set to Channel 27|28"}, 214 | f = {41478, "Set to Channel 29|30"}, 215 | g = {41480, "Set to Channel 31|32"}, 216 | }, 217 | n = {40176, "Normal"}, 218 | }, 219 | z = {41622, "Zoom to selected items"}, 220 | } 221 | 222 | Take = { 223 | NAME = "Take", 224 | UP = {40126, "Previous take"}, 225 | DOWN = {40125, "Next take"}, 226 | t = {40131, "Crop to active take"}, 227 | T = {41348, "Remove all empty takes"}, 228 | DELETE = {40129, "Delete active take"}, 229 | i = {40438, "Implode items across tracks"}, 230 | I = {40543, "Implode items same track"}, 231 | e = {40224, "Explode across tracks"}, 232 | E = {40642, "Explode in place"}, 233 | a = {41352, "Add empty lane after active take"}, 234 | A = {41351, "Add empty lane before active take"}, 235 | x = {"_S&M_CUT_TAKE", "Cut take"}, 236 | c = {"_S&M_COPY_TAKE", "Copy take"}, 237 | v = {"_S&M_PASTE_TAKE_AFTER", "Paste take after"}, 238 | V = {"_S&M_PASTE_TAKE", "Paste take into"}, 239 | d = {40639, "Duplicate active take"} -- Take: Duplicate active take 240 | } 241 | 242 | Envelope = { 243 | NAME = "Envelope", 244 | r = {40887, "Reduce number of points"}, 245 | d = {"_RSbdbdf040df097fd57022f539c4b62b8e297c12cd", "Duplicate selected points"}, 246 | DELETE = {40333, "Delete all selected points"}, 247 | k = {40065, "Clear envelope"}, 248 | x = {40336, "Cut selected points"}, 249 | c = {40335, "Copy selected points"}, 250 | v = {40884, "Toggle hide"}, 251 | V = {"_RS194a70f3ae2f5e9f8fe0c0695c02e34b1a95d47c", "Hide all except envelope under mouse"}, 252 | h = {41151, "Toggle show all envelopes"}, 253 | H = {41152, "Toggle show all envelopes for all tracks"}, 254 | i = {42031, "Create automation item"}, 255 | a = {41595, "Toggle select all points"}, 256 | r = {40863, "Toggle record arm"}, 257 | l = {40851, "Toggle display in separate lane"}, 258 | b = {40883, "Toggle bypass"}, 259 | DOWN = {41181, "Move points down a bit"}, 260 | LEFT = {41176, "Move points left a bit"}, 261 | RIGHT = {41177, "Move points right a bit"}, 262 | UP = {41180, "Move points up a bit"}, 263 | [","] = {41178, "Move points left by grid"}, 264 | ["."] = {41179, "Move points right by grid"}, 265 | e = {42030, "Add edge points when moving multiple points"}, 266 | E = {42082, "Add edge points when moving automation item"}, 267 | [")"] = {41122, "Increase curve by 5%"}, 268 | ["("] = {41123, "Decrease curve by 5%"}, 269 | ["|"] = {40189, "Set linear"}, 270 | } 271 | 272 | Windows = { 273 | NAME = "Windows", 274 | p = {40240, "Performance meter window"}, 275 | P = {42074, "Peaks display window"}, 276 | t = {40906, "Track Manager"}, 277 | c = {"_SWSAUTOCOLOR_OPEN", "Auto color/icon/layout window"}, 278 | r = {40251, "Routing matrix window"}, 279 | R = {40326, "Region/Marker manager window"}, 280 | d = {"_REAPACK_BROWSE", "ReaPack"}, 281 | m = {40078, "Mixer window"}, 282 | g = {40768, "Grouping matrix window"}, 283 | k = {40377, "Virtual MIDI keyboard"}, 284 | s = {40422, "Screen/track/item sets window"}, 285 | S = {40301, "Scale finder window"}, 286 | n = {40268, "Navigation window"}, 287 | f = {40271, "FX browser window"}, 288 | F = {41882, "FX monitoring chain"}, 289 | C = {40378, "Show big clock window"}, 290 | j = {40069, "Jump to time window"}, 291 | v = {50125, "Video window"}, 292 | z = {40072, "Undo history window"}, 293 | e = {40716, "MIDI editor window"}, 294 | i = {40847, "Open inline editor"}, 295 | I = {41887, "Close inline editor"}, 296 | } 297 | return { 298 | NAME = "Vimper Solo", 299 | r = Transport, 300 | n = Navigation, 301 | -- m = MIDI, 302 | l = Layout, 303 | t = Track, 304 | i = Item, 305 | a = Take, 306 | e = Envelope, 307 | w = Windows, 308 | } -------------------------------------------------------------------------------- /Various/Vimper Solo/Engine.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This script is part of Vimper Solo Package 3 | NoIndex: true 4 | --]] 5 | -- ====> System 6 | do 7 | local info = debug.getinfo(1,'S'); 8 | local base_path = info.source:match[[^@?(.*[\/])[^\/]-$]] 9 | package.path = package.path .. ";" .. base_path.. "?.lua" 10 | end 11 | 12 | local Bindings = require ("Bindings") 13 | local fn = require("fn") 14 | 15 | local function Msg(str) 16 | reaper.ShowConsoleMsg(str.."\n") 17 | end 18 | -- <==== System 19 | 20 | -- ====> Variables 21 | -- Strings 22 | 23 | -- Integers 24 | local i_gui_gNameX = 25 25 | local i_gui_gNameY = 10 26 | local i_gui_listX = 50 27 | local i_gui_gSz = 24 28 | local i_gui_lSz = 18 29 | local i_gui_font = "Calibri" 30 | local i_backKey = 8 31 | -- Booleans 32 | 33 | -- Tables 34 | local a_keyPressed 35 | local curGroup 36 | local lastGroup 37 | 38 | 39 | -- <==== Variables 40 | 41 | -- ====> Functions 42 | -- Private 43 | local function fn_getCmdID(inID) 44 | local retID = (type(inID) == "table" and not inID.NAME) and inID[1] or inID 45 | retID = string.sub(retID, 1 ,1) == "_" and reaper.NamedCommandLookup(retID) or retID 46 | return retID 47 | end 48 | 49 | local function fn_runCmd(inID, flag, proj) 50 | flag = flag or 0 51 | proj = proj or 0 52 | reaper.Main_OnCommandEx(inID, flag, proj) 53 | end 54 | 55 | 56 | local function fn_setting(inStr) 57 | end 58 | 59 | 60 | -- Checks associated commands in Bindings file. 61 | local function fn_chkBindings() 62 | local i, curLevel = 1, Bindings 63 | local s, n 64 | while i <= #a_keyPressed do 65 | n = a_keyPressed[i] 66 | if not curLevel[n] then 67 | a_keyPressed[#a_keyPressed] = nil -- Nullify last key 68 | break 69 | end 70 | curLevel = curLevel[n] 71 | i = i + 1 72 | end 73 | return curLevel 74 | end 75 | 76 | local function fn_triggerCmd() 77 | local b_triggered 78 | local cmdID = fn_chkBindings() 79 | if type(cmdID) == "table" and cmdID.NAME then -- Group 80 | curGroup = cmdID 81 | else 82 | cmdID = fn_getCmdID(cmdID) 83 | if type(cmdID) == "number" then -- Reaper command 84 | fn_runCmd(cmdID) 85 | b_triggered = not string.match(curGroup.NAME, "Navigation") and true 86 | fn.f.setLastAction(a_keyPressed) 87 | elseif type(cmdID) == "string" then -- Script setting 88 | fn_setting(cmdID) 89 | end 90 | a_keyPressed[#a_keyPressed] = nil 91 | end 92 | return b_triggered 93 | end 94 | 95 | -- GUI 96 | local function fn_gui_draw(inGroup) 97 | local a_keyList = {} 98 | -- Sort key list by their ASCII bytes 99 | for k, v in pairs(inGroup) do 100 | if k ~= "NAME" then 101 | local nv = fn.k.getKey(k) 102 | fn.t.add(a_keyList, {nv, k}) 103 | end 104 | end 105 | table.sort(a_keyList, function(a,b) return a[1] < b[1] end) 106 | lastGroup = inGroup 107 | 108 | -- Draw Group Name 109 | gfx.x = i_gui_gNameX 110 | gfx.y = i_gui_gNameY 111 | gfx.setfont(1, i_gui_font, i_gui_gSz) 112 | gfx.drawstr(inGroup.NAME.."\n") 113 | 114 | -- Draw Group Key Lists 115 | gfx.y = gfx.y + i_gui_gSz 116 | gfx.setfont(1, i_gui_font, i_gui_lSz) 117 | for k, v in ipairs(a_keyList) do 118 | v = v[2] 119 | local n = type(inGroup[v]) == "table" and 120 | (inGroup[v].NAME and inGroup[v].NAME or inGroup[v][2]) 121 | or inGroup[v] 122 | gfx.x = i_gui_listX 123 | gfx.y = gfx.y + i_gui_lSz 124 | gfx.drawstr(v..": "..n.."\n") 125 | end 126 | gfx.update() 127 | end 128 | 129 | -- Public 130 | 131 | function Main(isRepeat) 132 | -- Variable localization 133 | local i_keyChar, s_keyChar 134 | -- Input properties initialization 135 | isRepeat = isRepeat or 0 136 | if isRepeat == 1 then 137 | a_keyPressed = fn.f.getLastAction() 138 | fn_triggerCmd() 139 | return 1 140 | else 141 | a_keyPressed = a_keyPressed or fn.f.getLastGroup() 142 | curGroup = curGroup or fn_chkBindings() 143 | end 144 | i_keyChar = math.modf(gfx.getchar()) 145 | 146 | -- When pressed escape or closed GUI, exit 147 | if i_keyChar == 27 or i_keyChar == -1 then 148 | if #a_keyPressed == 0 then fn.f.setLastAction() end 149 | return 0 150 | end 151 | 152 | -- If a keypress is detected, build a table of inputed keys 153 | if i_keyChar ~= 0 then 154 | if i_keyChar == i_backKey then -- Backspace is used to return to last menu 155 | a_keyPressed[#a_keyPressed] = nil 156 | else 157 | s_keyChar = fn.k.getChar(i_keyChar) 158 | fn.t.add(a_keyPressed, s_keyChar) 159 | end 160 | if fn_triggerCmd() then return 1 end 161 | end 162 | 163 | if curGroup ~= lastGroup then fn_gui_draw(curGroup) end 164 | reaper.defer(Main) 165 | end 166 | -- <==== Functions 167 | 168 | return Main; 169 | -------------------------------------------------------------------------------- /Various/Vimper Solo/RCJacH_Vimper Solo Repeat Action.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This script is part of Vimper Solo Package 3 | NoIndex: true 4 | --]] 5 | 6 | do 7 | local info = debug.getinfo(1,'S'); 8 | local base_path = info.source:match[[^@?(.*[\/])[^\/]-$]] 9 | package.path = package.path .. ";" .. base_path.. "?.lua" 10 | end 11 | 12 | local Main = require ("Engine") 13 | 14 | Main(1) -------------------------------------------------------------------------------- /Various/Vimper Solo/RCJacH_Vimper Solo.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ReaScript Name: Vimper Solo 3 | Author: RCJacH 4 | Link: https://github.com/RCJacH/ReaScripts 5 | Version: 1.0 6 | Reference: 7 | Vimper 8 | Description: 9 | This script allows user to type in a series of keys to trigger categorized actions. 10 | Instruction: 11 | 1. Place all files in the same folder. 12 | 2. Load and assign a key shortcut to "Vimper Solo.lua". 13 | 3. Use default binding or change it to your liking. 14 | a. Create groups using lua table, and make sure to write a NAME key. 15 | b. Add the group to the return table with an unique key in the group. 16 | c. You can add infinite level of groups within groups. 17 | d. In each group, the key is the key you press (case sensitive). 18 | e. For value, you can put just a command ID or use a table with 19 | f. the reaper command id first, and the displaying label second. 20 | 4. Trigger the key shortcut in reaper to load the GUI. 21 | 5. GUI has to be in focus to detect further keyswitches. 22 | Changelog: 23 | v1.0 (2017-02-05) 24 | + Initial Release 25 | Potential Addition: 26 | 1. Separated setting file. 27 | 2. Sequencial input for each group rather than single key. 28 | 3. Modifiers: ctrl, alt 29 | 4. MIDI editor actions. 30 | Provides: 31 | [main] RCJacH_Vimper Solo Repeat Action.lua 32 | [nomain] Bindings.lua 33 | [nomain] Engine.lua 34 | [nomain] fn.lua 35 | [nomain] last_action.ini 36 | ]] 37 | 38 | -- Licensed under the GNU GPL - http://www.gnu.org/licenses/gpl.html 39 | 40 | do 41 | local info = debug.getinfo(1,'S'); 42 | local base_path = info.source:match[[^@?(.*[\/])[^\/]-$]] 43 | package.path = package.path .. ";" .. base_path.. "?.lua" 44 | end 45 | 46 | local Main = require ("Engine") 47 | 48 | local GUI = { 49 | name = "Vimper Solo", 50 | x = 200, 51 | y = 200, 52 | w = 500, 53 | h = 550, 54 | } 55 | 56 | gfx.init(GUI.name, GUI.w, GUI.h, 0, GUI.x, GUI.y) 57 | Main(0) -------------------------------------------------------------------------------- /Various/Vimper Solo/README.md: -------------------------------------------------------------------------------- 1 | ReaScript Name: Vimper Solo 2 | Author: RCJacH 3 | Website: https://github.com/RCJacH/ReaScripts 4 | License: GPL - http://www.gnu.org/licenses/gpl.html 5 | Version: 1.0 6 | Reference: 7 | Vimper 8 | 9 | 10 | ### Description: 11 | ------ 12 | This script allows user to type in a series of keys to trigger categorized actions. 13 | 14 | 15 | ### Instruction: 16 | ------ 17 | 1. Place all files in the same folder. 18 | 2. Load and assign a key shortcut to "Vimper Solo.lua". 19 | 3. Use default binding or change it to your liking. 20 | a. Create groups using lua table, and make sure to write a NAME key. 21 | b. Add the group to the return table with an unique key in the group. 22 | c. You can add infinite level of groups within groups. 23 | d. In each group, the key is the key you press (case sensitive). 24 | e. For value, you can put just a command ID or use a table with 25 | f. the reaper command id first, and the displaying label second. 26 | 4. Trigger the key shortcut in reaper to load the GUI. 27 | 5. GUI has to be in focus to detect further keyswitches. 28 | 29 | ### Changelog: 30 | v1.0 (2017-02-05) 31 | + Initial Release 32 | 33 | ### Potential Addition: 34 | 1. Separated setting file. 35 | 2. Sequencial input for each group rather than single key. 36 | 3. Modifiers: ctrl, alt 37 | 4. MIDI editor actions. -------------------------------------------------------------------------------- /Various/Vimper Solo/fn.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This script is part of Vimper Solo Package 3 | NoIndex: true 4 | --]] 5 | ------------------------------------------------------------------------- 6 | -- This file provides the additional functions used by this script 7 | ------------------------------------------------------------------------- 8 | local LastAction 9 | do 10 | local info = debug.getinfo(1,'S'); 11 | local base_path = info.source:match[[^@?(.*[\/])[^\/]-$]] 12 | LastAction = base_path.."last_action.ini" 13 | end 14 | 15 | local a_MULTIBYTE = { 16 | LEFT = 1818584692, 17 | RIGHT = 1919379572, 18 | UP = 30064, 19 | DOWN = 1685026670, 20 | TAB = 9, 21 | END = 6647396, 22 | HOME = 1752132965, 23 | PGUP = 1885828464, 24 | PGDN = 1885824110, 25 | ENTER = 13, 26 | SPACE = 32, 27 | DELETE = 6579564, 28 | INSERT = 6909555, 29 | RETURN = 8; 30 | F1 = 26161, 31 | F2 = 26162, 32 | F3 = 26163, 33 | F4 = 26164, 34 | F5 = 26165, 35 | F6 = 26166, 36 | F7 = 26167, 37 | F8 = 26168, 38 | F9 = 26169, 39 | F10 = 26170, 40 | F11 = 26171, 41 | F12 = 26172, 42 | } 43 | 44 | -- Table Functions 45 | local fn_add, fn_hasIndex, fn_hasValue, fn_index 46 | 47 | fn_add = function (table, value) 48 | -- This function adds a row of new value at the end of the table 49 | table[#table + 1] = value 50 | end 51 | 52 | fn_hasIndex = function (table, index) 53 | -- This function checks if an index exists in a table 54 | assert(type(table) == "table", "Table required for search table by index") 55 | assert(index, "Index required for search table by index") 56 | for k, _ in pairs(table) do 57 | if (k == index) then return true end 58 | end 59 | return false 60 | end 61 | 62 | fn_hasValue = function (table, value) 63 | -- This function checks if a value exists in a table 64 | assert(type(table) == "table", "Table required for search table by value") 65 | assert(value, "Value required for search table by value") 66 | for _,v in pairs(table) do 67 | if (v == value) then return true end 68 | end 69 | return false 70 | end 71 | 72 | fn_index = function (table, value) 73 | -- This function gets table index from a value 74 | assert(type(table) == "table", "Table required for search table by value") 75 | assert(value, "Value required for search table by value") 76 | for k, v in pairs(table) do 77 | if (v == value) then return k end 78 | end 79 | return nil 80 | end 81 | 82 | 83 | -- File Functions 84 | local fn_withFile, fn_setFile, fn_readFile 85 | local fn_getLastAction, fn_getLastGroup, fn_setLastAction 86 | 87 | fn_withFile = function (path, mode, fn) 88 | local f = io.open(path, mode) 89 | local ret 90 | if f then 91 | ret = fn(f) 92 | f:close() 93 | end 94 | return ret 95 | end 96 | 97 | fn_writeFile = function (file, c) 98 | local f = io.open(file, 'w') 99 | f:write(c) 100 | f:close() 101 | end 102 | 103 | fn_readFile = function (file) 104 | local f = io.open(file, 'r') 105 | local ret = f and f:read('*a') or nil 106 | f:close() 107 | return ret 108 | end 109 | 110 | fn_getLastAction = function() 111 | local s, t = fn_readFile(LastAction), {} 112 | for v in s:gmatch"%S+" do 113 | fn_add(t, tostring(v)) 114 | end 115 | return t 116 | end 117 | 118 | fn_getLastGroup = function() 119 | local t = fn_getLastAction() 120 | if #t then t[#t] = nil end 121 | return t 122 | end 123 | 124 | fn_setLastAction = function(c) 125 | local s ="" 126 | if c then 127 | for _, v in ipairs(c) do 128 | s = s..v.." " 129 | end 130 | end 131 | fn_writeFile(LastAction, s) 132 | end 133 | 134 | -- Key Functions 135 | fn_getChar = function(inChar) 136 | return fn_hasValue(a_MULTIBYTE, inChar) and fn_index(a_MULTIBYTE, inChar) 137 | or string.char(inChar) 138 | end 139 | 140 | fn_getKey = function(inKey) 141 | local i_keyByte, s_rawkey, mod 142 | local i_modByte = 0 143 | -- mod = inKey:match "[!^+]+" 144 | -- if mod then 145 | -- for w in mod:gmatch "[!^+]" do 146 | -- i_modByte = (w == "^" and i_modByte - 96) or 147 | -- (w == "!" and i_modByte + 224) or 148 | -- (w == "+" and i_modByte + 32) 149 | -- end 150 | -- local start = string.len(inKey) - string.len(mod) + 1 151 | -- inKey = inKey:sub(start, -1) 152 | -- end 153 | i_keyByte = fn_hasIndex(a_MULTIBYTE, inKey) and a_MULTIBYTE[inKey] 154 | or string.byte(inKey) 155 | return i_keyByte + i_modByte 156 | end 157 | 158 | 159 | -- Groups 160 | local a_table = { 161 | add = fn_add, 162 | hasIndex = fn_hasIndex, 163 | hasValue = fn_hasValue, 164 | index = fn_index, 165 | } 166 | 167 | local a_file = { 168 | r = fn_readFile, 169 | w = fn_writeFile, 170 | f = fn_withFile, 171 | getLastAction = fn_getLastAction, 172 | getLastGroup = fn_getLastGroup, 173 | setLastAction = fn_setLastAction, 174 | } 175 | 176 | local a_key = { 177 | getChar = fn_getChar, 178 | getKey = fn_getKey, 179 | } 180 | 181 | return { 182 | t = a_table, 183 | f = a_file, 184 | k = a_key, 185 | } -------------------------------------------------------------------------------- /Various/Vimper Solo/last_action.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RCJacH/ReaScripts/0835d3e595c5dfa341fbcbcb8b31f0d8473b27c4/Various/Vimper Solo/last_action.ini --------------------------------------------------------------------------------