More MIDI data
-
To run any sort of sequencer or tempo linked modulations etc. we are going to need MIDI Song Position Pointer data, we will need to know where we are in the song and the bar as well as Transport Start and Transport stop. I cant see this info anywhere in the current function calls.
-
This data is accessible through the common plugin-wrapper from JUCE (copied from their API):
@3a2jqf1l:
double bpm
The tempo in BPM. More…int timeSigNumerator
Time signature numerator, e.g. More...int timeSigDenominator
Time signature denominator, e.g. More...int64 timeInSamples
The current play position, in samples from the start of the edit. More...double timeInSeconds
The current play position, in seconds from the start of the edit. More...double editOriginTime
For timecode, the position of the start of the edit, in seconds from 00:00:00:00. More...double ppqPosition
The current play position, in pulses-per-quarter-note. More...double ppqPositionOfLastBarStart
The position of the start of the last bar, in pulses-per-quarter-note. More...FrameRateType frameRate
The video frame rate, if applicable. More...bool isPlaying
True if the transport is currently playing. More...bool isRecording
True if the transport is currently recording. More...double ppqLoopStart
The current cycle start position in pulses-per-quarter-note. More...double ppqLoopEnd
The current cycle end position in pulses-per-quarter-note. More...bool isLooping
True if the transport is currently looping. More...I could wrap them up into an Object which could be retrieved by this scripting call:
Engine.getPlayHead().ppqLoopStart; // contains the current value for this property
Although this will be a thing that is heavily dependant on the host DAW and needs some broad testing. I can check Cubase (5 + 8), Reaper 5 and Ableton 9 on both OSX and Windows.
-
Well I dont think we need all of these, I think we need the following:
bool isPlaying
… but more importantly we need a call back that happens when this value is changed, in KSP its on Listener
bool ppqPosition
assuming this is 96ppq resolution, which is a bit too fine I imagine, it will add a lot of overhead into the engine, KSP allows me to set the resolution here from 1 to 24 , and again when this happens we need a call back
So in some on-listener-type call back we need to interrogate some variable to see what type it is:
SIGNAL_TYPE would need to be
TRANSPORT_START
TRANSPORT_STOP
POSITIONthe following would be useful to get from a javascript call as you suggest:
double bpm
int timeSigNumerator
int timeSigDenominator
double ppqPositionOfLastBarStart
double ppqLoopStart
double ppqLoopEnd
bool isLoopingAs you say not all DAWs in all situations will return these, but my experience is that all DAWs on all platforms return the vital transport and position data.
-
Well, I added all properties (there's almost no overhead and some other properties may get interesting for other use cases).
Alright, I added two callbacks (although you have to write them manually, I don't want to clutter the editor interface with too many callback buttons):
// Callback that is called periodically and synchronized to the host (only called if the host is playing) function onClock(timeStamp); // Callback that indicates the transport change (isStart is 1 if the host is playing) function onTransport(isStart);
Getting the timing right was rather complicated and the timing goes funky when the audio buffer size is bigger than the clock interval - but this should not be relevant in the real world.
Every sound generator has its own clock and can be set from one bar (=4 quarters) to 32th notes (faster doesn't make sense). You can change the clock speed with the API call
Synth.setClockSpeed(16); // would be 16th notes.. Synth.setClockSpeed(0); // stops the clock (this is also the default).
You can use the host info API call to write a sequencer like this (make sure you use this on a sound generator with fast decay and no sustain!):
// A fast C-minor arpeggiator synched to the host Synth.setClockSpeed(32); var x = [48,51,55,60,63,67,72,75]; var index = 0; function onClock() { // You need to round up the ppqPosition because it is the value at the start of the buffer index = parseInt(Math.ceil(Engine.getPlayHead().ppqPosition*8.0)); Synth.playNote(x[index%x.length],127); }
This should be everything you need to write a sequencer. If you need more, let me know.
-
yeah this should work, where would I write the onClock and onTransport functions, anywhere special?
… you wont believe how long I've been asking UVI for this functionality in their engine... nice turn around, well done.
-
Thanks. I wanted to add this feature for a long time because the onTimer callback is not perfectly accurate (it gets executed at the start of the buffer, so you don't have sample accurate timing).
You can write them anywhere (outside of the other callbacks of course), but I would suggest adding them in the onInit page - the script processor checks if the other callback pages are empty and skips the callback to save overhead and if you add the onClock callback on the onNoteOn page, an empty callback will be executed for every incoming note on event.