-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Release Starmidi - microtonal scales using lanes as piano roll v1.0-b…
…eta-1 (#1421)
- Loading branch information
1 parent
deebe5c
commit 3908802
Showing
8 changed files
with
978 additions
and
0 deletions.
There are no files selected for viewing
56 changes: 56 additions & 0 deletions
56
Various/starshine_Starmidi - microtonal scales using lanes as piano roll.lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
-- @description Starmidi - microtonal scales using lanes as piano roll | ||
-- @author Starshine | ||
-- @version 1.0-beta-1 | ||
-- @metapackage | ||
-- @provides | ||
-- [main] starshine_Starmidi - microtonal scales using lanes as piano roll/starshine_Starmidi - Flatten.eel | ||
-- [main] starshine_Starmidi - microtonal scales using lanes as piano roll/starshine_Starmidi - Sharpen.eel | ||
-- [main] starshine_Starmidi - microtonal scales using lanes as piano roll/starshine_Starmidi - Small step down.eel | ||
-- [main] starshine_Starmidi - microtonal scales using lanes as piano roll/starshine_Starmidi - Small step up.eel | ||
-- [main] starshine_Starmidi - microtonal scales using lanes as piano roll/starshine_Starmidi - Parser.eel | ||
-- [main] starshine_Starmidi - microtonal scales using lanes as piano roll/starshine_Starmidi - UI.eel | ||
-- [effect] starshine_Starmidi - microtonal scales using lanes as piano roll/starmidi | ||
-- @about | ||
-- # Starmidi - microtonal scales using lanes as piano roll | ||
-- | ||
-- This program seeks to solve several problems for microtonal music producers while streamlining the workflow: | ||
-- | ||
-- 1. Microtonal scales may contain far more than 12 notes per octave, and this quickly makes working in the conventional chromatic piano roll cumbersome or even limiting to the instrument's range. | ||
-- | ||
-- *This script package and plugin solve this by using an adjustable number of "fixed item lanes" that act as the "piano white key notes" for the given microtonal scale, with other pitches accessible by means of accidentals* | ||
-- | ||
-- 2. Different synthesizers and samplers have different ways of loading microtonal scales, sometimes requiring different file formats, inconsistent behavior, timbre warping, and more. | ||
-- | ||
-- *These scripts solve this issue by finding the closest MIDI note and applying a pitchbend amount of no more than 50 cents. It's tested working with Vital, Surge XT, and Pianoteq and should work with most synths and samplers. | ||
-- | ||
-- To achieve microtonal polyphony, the jsfx plugin (which handles generating the MIDI events from data in gmem) dynamically assigns new notes to free channels. Up to 16 simultaneous and independently tuned notes can be played this way.* | ||
-- | ||
-- ### Setup/Usage | ||
-- | ||
-- 1. ReaImGui is required | ||
-- | ||
-- 2. Preferences > Track Send Defaults > Fixed Lane Defaults > **Uncheck "Automatically delete empty lanes at bottom of track"** | ||
-- | ||
-- I strongly recommend selecting "Small lanes" here as well. | ||
-- | ||
-- 3. Launch the script "Starshine_starmidi_UI.eel". | ||
-- | ||
-- 4. Drag the # of Lanes slider to add 15+ lanes to a track. | ||
-- | ||
-- 5. It is beyond the scope of this package documentation to explain microtonal scale theory. If you are interested in learning more, please consider joining the Xenharmonic Alliance discord at https://discord.gg/uxvw5Vzj | ||
-- | ||
-- Setting the following combinations for [# of Scale Steps], [Equal Divisions], and [n\EDX as Generator] should yield interesting results. 7\16edo means to select 16 [Equal Divisions] and 7 for [n\EDX as Generator] | ||
-- | ||
-- * 7 steps, 7\16edo | ||
-- * 8 steps, 10\27edo | ||
-- * 9 steps, 4\19edo | ||
-- * 7 steps, 13\31edo, mode 5 (this is like a regular major scale but more in tune. roughly "quarter-comma meantone") | ||
-- | ||
-- 6. Insert/draw empty MIDI items in lanes. These function as notes. Optionally, you may set a mouse modifier to draw MIDI items so that it functions like the standard piano roll for inserting notes. | ||
-- | ||
-- 7. Add a synth to the FX chain; do not enable MPE. Leave pitch bend range at 2 semitones. | ||
-- | ||
-- 8. You must click Play from this interface; this runs the parsing script prior to playback. (The script will also auto-add and configure the jsfx.) | ||
-- | ||
-- *Optionally, you may create a custom action that runs "starshine_starmidi_parser.eel" and then the Transport: Play/Stop action* | ||
|
156 changes: 156 additions & 0 deletions
156
Various/starshine_Starmidi - microtonal scales using lanes as piano roll/starmidi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
desc:starmidi | ||
options:gmem=fakemidi | ||
//@gmem=fakemidi | ||
// reads "midi events" from gmem by partner reascript | ||
// partner reascript reads items in lanes into gmem | ||
// TODO: handle incoming events on the same bus? for purpose of reassigning channel | ||
|
||
slider1:1<1,32,1>track num | ||
// TODO: handle dynamic scale change -- bigger jump, let's get it working the easy way first. | ||
|
||
@init | ||
|
||
// MIDI command constants | ||
NOTE_OFF_MSG = 0x80; | ||
NOTE_ON_MSG = 0x90; | ||
CHANNEL_MSG = 0xB0; | ||
CHANNEL_STFU = 0x7B; | ||
PITCHBEND = 0xE0; | ||
|
||
// gmem data offsets | ||
MEM_PER_TRACK = 196608; | ||
_TR_GMEM_START = 65536; | ||
EVT_WIDTH = 8; | ||
TRACK_COUNT = 32; // about as many tracks as we can support at once | ||
EVT_LENGTH = 16384; | ||
_track_evt_counter = 8192; | ||
PLAYBACK_OKAY = 8000000; | ||
|
||
// local memory offsets | ||
_MIDI_EVENTS = 65536; // must match _source_array in reascript | ||
_CHANNELS = 0; | ||
|
||
// column reference constants for 2D array _MIDI_EVENTS | ||
EventTime = 0; | ||
EventType = 1; | ||
MidiNote = 2; | ||
Velocity = 3; | ||
PitchbendAmount = 4; | ||
ChannelPointer = 5; | ||
|
||
last_play_pos=0; | ||
last_play_state=0; | ||
|
||
function _track_evts_gmem_ptr(track_num)(track_num*MEM_PER_TRACK+_TR_GMEM_START); | ||
|
||
// the 2D array structure used, array[row][col], depends on doing this | ||
function copy_gmem_to_local()( | ||
_gmem_ptr = _track_evts_gmem_ptr(slider1-1)-1; | ||
_local_ptr = -1; | ||
Loop(EVT_WIDTH*EVT_LENGTH, | ||
_MIDI_EVENTS[_local_ptr+=1] = gmem[_gmem_ptr+=1]; | ||
); | ||
); | ||
|
||
|
||
// unreserve playing channels and turn off every note | ||
function clear_channels()( | ||
ch=-1; | ||
Loop(16, | ||
_CHANNELS[ch+=1]=0; | ||
midisend(0,CHANNEL_MSG|ch, CHANNEL_STFU); | ||
); | ||
); | ||
|
||
// scan through sorted events to catch up to current play position | ||
function scan_to_start()( | ||
_play_ptr=-1; t=0; | ||
while( (t<play_position)&&(t>-1) )( | ||
t = _MIDI_EVENTS[_play_ptr+=1][EventTime]; | ||
); | ||
); | ||
|
||
copy_gmem_to_local(); | ||
clear_channels(); | ||
scan_to_start(); | ||
|
||
@slider | ||
slider_show(slider1,0); | ||
|
||
@block | ||
|
||
@sample | ||
|
||
function ANO_if_stopped_or_looping()( | ||
!play_state && last_play_state ? (clear_channels(); scan_to_start(); _play_ptr-=1); | ||
(play_position < last_play_pos) && play_state ? (clear_channels(); scan_to_start(); _play_ptr-=1); | ||
last_play_state = play_state; | ||
last_play_pos = play_position; | ||
); | ||
|
||
function scan_to_start_on_play()( | ||
play_state && !last_play_state2 ? (clear_channels(); scan_to_start()); | ||
last_play_state2 = play_state; | ||
); | ||
|
||
// dynamically obtain an available channel and mark as in-use | ||
function get_free_channel()( | ||
ch=-1; return_ch=-1; | ||
while((return_ch<0) && (ch < 16)) | ||
( _CHANNELS[ch+=1] == 0 | ||
? ( return_ch = ch; | ||
_CHANNELS[ch] = 1; | ||
); | ||
); | ||
return_ch); | ||
|
||
|
||
// sends midi pitchbend and note-on. also writes channel for note-off event to use later | ||
function note_on(_ptr)( | ||
chan = get_free_channel(); | ||
_chan_ptr = _MIDI_EVENTS[_ptr][ChannelPointer]; | ||
_MIDI_EVENTS[_chan_ptr] = chan; | ||
midisend(0, PITCHBEND|chan, _MIDI_EVENTS[_ptr][PitchbendAmount]); | ||
midisend(0, NOTE_ON_MSG|chan, _MIDI_EVENTS[_ptr][MidiNote], _MIDI_EVENTS[_ptr][Velocity]); | ||
); | ||
|
||
|
||
// sends note-off and frees channel | ||
function note_off(_ptr)( | ||
_chan_ptr = _MIDI_EVENTS[_ptr][ChannelPointer]; | ||
chan = _MIDI_EVENTS[_chan_ptr]; | ||
_CHANNELS[chan] = 0; | ||
midisend(0, NOTE_OFF_MSG|chan, _MIDI_EVENTS[_ptr][MidiNote], 0); | ||
); | ||
|
||
|
||
function play_scan()( | ||
while( (t<play_position)&&(t>-1) )( | ||
event = _MIDI_EVENTS[_play_ptr][EventType]; | ||
event == NOTE_ON_MSG ? note_on(_play_ptr); | ||
event == NOTE_OFF_MSG ? note_off(_play_ptr); | ||
t = _MIDI_EVENTS[_play_ptr+=1][EventTime]; | ||
); | ||
); | ||
|
||
function playback_okay()(gmem[PLAYBACK_OKAY+slider1-1]); | ||
|
||
ANO_if_stopped_or_looping(); | ||
|
||
scan_to_start_on_play(); | ||
|
||
play_state && playback_okay() ? play_scan(); | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
46 changes: 46 additions & 0 deletions
46
...e_Starmidi - microtonal scales using lanes as piano roll/starshine_Starmidi - Flatten.eel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// @noindex | ||
|
||
// helper script for starmidi tools | ||
// modifies item title by adding or removing # or b symbols | ||
|
||
function apply_accidental(take)( | ||
GetSetMediaItemTakeInfo_String(take, "P_NAME", #text, 0); | ||
|
||
//todo: check if item is midi | ||
|
||
(len=strlen(#text))==0 ? #text ="b" : //case 1: empty. just add a b | ||
match("*b*",#text) ? #text+="b" : //case 2: has one or more b. add another | ||
match("*#*",#text) ? str_setlen(#text,len-1); //case 3: has one or more #. remove one. | ||
|
||
GetSetMediaItemTakeInfo_String(take, "P_NAME", #text, 1); | ||
|
||
// count the number of b or # to apply colors | ||
pos=-1; r=1; g=1; b=1; | ||
|
||
char = str_getchar(#text,0); | ||
(char == 'b') ? (r-=0.125; g-=0.2;); | ||
(char == '#') ? (b-=0.2; g-=0.125;); | ||
|
||
Loop(strlen(#text), | ||
char = str_getchar(#text,pos+=1); | ||
char == 'b' ? (r-=0.125; g-=0.2;); | ||
char == '#' ? (b-=0.2; g-=0.125;); | ||
); | ||
|
||
r*=255; b*=255; g*=255; | ||
SetMediaItemTakeInfo_Value(take, "I_CUSTOMCOLOR", ColorToNative(r,g,b)|0x1000000); | ||
); | ||
|
||
function loop_selected()( | ||
item_idx = -1; | ||
Loop(CountSelectedMediaItems(0), | ||
item = GetSelectedMediaItem(0,item_idx+=1); | ||
take = GetMediaItemTake(item,0); | ||
apply_accidental(take); | ||
); | ||
); | ||
|
||
GetMousePosition(x,y); | ||
GetItemFromPoint(x,y,0,take); | ||
!take ? loop_selected() : apply_accidental(take); | ||
|
Oops, something went wrong.