public
Created

tick for MIDI example (with issues)

  • Download Gist
tick_for_MIDI_30_09_12.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
// tick() function, originally by Chris Wilson
// Chris: "with a conformant sendMIDIMessage w/ timestamps, PREQUEUE could be set to a larger number like 200."
// James: Changes (as per 30th Sept. 2012)
// 1. use calls to nextMessage() instead of having a flat sequence as in the original function.
// 2. two (cosmetic) name changes:
// a) output -> midiOutputDevice.
// b) domhrtTimeAtStartOfPerformance to domhrtMsOffsetAtStartOfPerformance.
// domhrtMsOffsetAtStartOfPerformance takes starting later in a sequence into account.
// 3. Synchronization with the running cursor in the score
// a) reportTimestamp callback:
// 1. reportTimestamp is an optional callback, set when the performance starts.
// 2. msg moved outside tick(). The very first msg in the performance is loaded in another function.
// 3. currentMomentTimestamp added outside tick(). Initialized when the performance starts.
// b) need to synchronize with rest symbols. (Try playing a single track having simple notes and rests.)
// If a MIDIMoment contains no messages, nextMessage() returns a msg having timestamp and isARest
// attributes. MIDIMessages having an isARest attribute are not sent to the midiOutputDevice.
//
// Issue:
// This code depends on PREQUEUE being 0. If PREQUEUE were to be increased, exact synchronization with
// the score would deteriorate. There would be no way of knowing exactly when the MIDIMessage is really sent.
// Possible solutions:
// 1. Maybe MIDIMessages could have an optional callback, to be called at the actual send time. But that
// would not work for rests. Rests are never actually sent to the MIDI output device.
// Maybe we need a wrapper for MIDIMessages, which would include both the callback and the
// information that this is an empty message.
// 2. If MIDI programmers are allowed to set the value of PREQUEUE themselves, then different applications
// can be given an optimal value. What, precisely, are the pros and cons of having larger values...
//
PREQUEUE = 0,
maxDeviation, // for console.log, set to 0 when performance starts
midiOutputDevice, // set when performance starts
currentMomentTimestamp, // set when performance starts
performanceStart, // set to true when performance starts
domhrtMsOffsetAtStartOfPerformance, // set when performance starts
msg, // the very first message in a performance is loaded elsewhere (when the performance starts)
// msg is never null when tick() is called.
 
tick = function ()
{
var deviation,
domhrtRelativeTime = Math.round(window.performance.webkitNow() - domhrtMsOffsetAtStartOfPerformance),
delay = msg.timestamp - domhrtRelativeTime;
 
if (reportTimestamp !== null && msg.timestamp > currentMomentTimestamp)
{
//console.log("sequence.tick()1, calling reportTimestamp(msg.timestamp): currentMomentTimestamp=" +
// currentMomentTimestamp + ", msg.timestamp=" + msg.timestamp);
currentMomentTimestamp = msg.timestamp;
reportTimestamp(msg.timestamp); // updates the cursor position in the score
}
 
while (delay <= PREQUEUE)
{ // send all messages that are due now.
 
// running log
deviation = (domhrtRelativeTime - msg.timestamp);
maxDeviation = (deviation > maxDeviation) ? deviation : maxDeviation;
console.log("timestamp: " + msg.timestamp + ", domhrtTime: " + domhrtRelativeTime + ", deviation: " + deviation);
 
if (msg.isARest === undefined)
{
// sendMIDIMessage needs msg.timestamp to be absolute DOMHRT time.
msg.timestamp += domhrtMsOffsetAtStartOfPerformance;
midiOutputDevice.sendMIDIMessage(msg);
// subtract again, otherwise the sequence gets corrupted
msg.timestamp -= domhrtMsOffsetAtStartOfPerformance;
}
 
msg = nextMessage();
if (msg === null)
{
// we're pausing, or have hit the end of the sequence.
console.log("Pause, or end of sequence. maxDeviation is " + maxDeviation + "ms");
return;
}
delay = msg.timestamp - domhrtRelativeTime;
}
 
window.setTimeout(tick, delay); // this will schedule the next tick.
},

Sorry abut all the updates. This one was made on 30th Sept. 2012 at 21:14 local time.
The function has now been simplified by moving the code for loading the first message into another function (the one that starts the performance).
The synchronization issue is still there.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.