inline void VCR::onAudioCB(AudioIOData& io) {
	// cached, because it may be being used in a different thread
	sampletime r = mAudioReadIndex;
	sampletime w = mAudioWriteIndex;

	unsigned channels = io.channelsOut();
	if (channels > mAudioChans) channels = mAudioChans;

	unsigned numAudioFrames = io.framesPerBuffer();
	unsigned numSamples = numAudioFrames * channels;
	unsigned length = mAudioRing.size();

	sampletime ahead = w - r;	// how much writer is ahead of reader
	if (ahead > (length - numSamples)) {
		// not enough space left in ringbuffer!
		fprintf(stderr, "audio underflow\n");
		mAudioUnderflows++;
		return;
	}

	sampletime wnext = w + numSamples;
	unsigned wu = (unsigned)(w % length);

	//printf("writing %u samples to %llu\n", numSamples, wnext);

	for (unsigned c=0; c < channels; c++) {
		float * src = io.outBuffer(c);
		float * dst = &mAudioRing[0];

		unsigned wc = wu + c;
		unsigned srcIdx = 0;

		while (srcIdx < numAudioFrames) {
			dst[wc] = src[srcIdx];
			srcIdx++;
			wc += mAudioChans;
			if (wc >= length) wc -= length;
		}
	}

	// update write head position
	mAudioWriteIndex = wnext;
}