I recently wrapped up an audio project for OS X based on JUCE (a cross platform C++ library for applications with an emphasis on audio) and using Propellerhead's ReWire protocol to allow the application to feed audio with very low latency to another audio app (e.g., Logic Pro).
The ReWire protocol defines a host, a device and a GUI (or "panel") that controls the device. The ReWire ecosystem is divided up that way so a ReWire device can be run inside a host (in the host's address space) in a similar way to how Audio Units or VSTs run as "plugins" in a host application. In order to control the device, a GUI or panel communicates with it via inter-process communication. (For standard plugins, this GUI would be directly managed by the host application.)
ReWire speaks both MIDI and audio data. It also communicates transport position and control commands (play, stop, etc) in both directions, allowing the transports of two applications to run in sync. For transport position, it uses MIDI PPQ (also called ticks or just PPQ) to give an offset. If you're building an audio data only application, as I was in this case, your ReWire device will probably ignore the MIDI side of ReWire and therefore will have no reference to utilize PPQ with. That means you'll need to convert a PPQ offset to a sample offset.
Even if you're ignoring the MIDI side of ReWire, the host will still announce all of the information you need to determine the current sample offset of the transport.
A few definitions first:
- PPQ (pulses per quarter note) is expressed as its smallest subdivision, so PPQ 96 means a maximum resolution of 1/96th of a quarter note. ReWire uses PPQ 15360.
- BPM (beats per minute) as our tempo. In this case we can assume each beat is a quarter note. The actual value ReWire supplies is BPM * 1000.
- Sample rate is the number of samples per second (per channel) of our audio. In this case we need to use the sample rate as announced by the host, not by any of the audio files we're playing back (though we need to convert the sample rate of each audio file to that of the host for playback, so they'll end up being equivalent).
To convert PPQ to samples, we'll first convert our PPQ offset to milliseconds. The formula for that is milliseconds = milliseconds per minute / (BPM * PPQ) or in this case 60000 / ((120000 / 1000) * 15360) or 0.03255208333 ms per tick for a tempo of 120 BPM. Our offset in milliseconds is then PPQ offset * milliseconds per tick. To convert that value to a sample offset, we multiply by our sample rate (divided by 1000) or sample offset = (PPQ offset * milliseconds per tick) * (sample rate / 1000).
Putting it all together, if we have a tempo of 120 BPM, a sample rate of 44100, a PPQ value of 15360 and a PPQ offset of 1 million, we'll end up with ((60000 / ((120000 / 1000) * 15360)) * 1000000) * (44100 / 1000) or approximately 1435546 samples.
Since our PPQ value won't change (it's always 15360 for ReWire) we can simplify this quite a bit. Here is a simplified version in function form (as well as the complementary conversion from samples to PPQ):