AudioFormatWriter::ThreadedWriter timing issue

I’ve implemented an AudioFormatWriter::ThreadWriter in my plug-in, and am capturing audio to a .wav file. But a strange thing happens. If I drag the resulting file back into my DAW’s timeline (in Cubase), it shows the audio signals delayed by about 380 samples. (I haven’t measured exactly, just an eyeball from looking at the waveforms.)

What I do is, I get my ThreadWriter ready, and then in processBlock(), as soon as the transport start is detected, I start writing, and stop when the transport stops. I checked the timeInSamples value at the point it first starts writing, and it says the sample position is 0, as expected. So, when I drag the resulting file back into another track in the host, why does the data not line up exactly? Where is that 380 samples coming from?

It’s not related to the latency I report (which is 2566 samples in this instance).

Update: just tested the same thing in Pro Tools, and it gave me 690 samples difference! I’m confused. I need to be able to read back specific data from the file I’ve written, and know exactly where in time it is in relation to the original audio. But how can I guarantee that if the resulting file doesn’t line up exactly with the original audio?

Update: trying it a second time in Pro Tools, the audio is delayed by 635 samples this time. So it doesn’t appear to be consistent, at least in Pro Tools. I know Pro Tools can start playing at varying sample positions, but still, why would that make my data have this extra (and varying length of) silence at the start of the file? And how can I know how to line up my data when I read it back in later?

Anyone have a clue here? I have verified that the data written out contains an apparently random number of samples of 0.0 prior to the actual data that is being processed. I added code to allow me to break on the first sample greater than 0.1, and it happens in my audio at sample 34096, every time. But similar code that checks for a value greater than 0.1 when reading back in those samples that were written to the file breaks (with the exact same value, 0.102172852) at varying points, always a few hundred samples later in the data stream. (I’ve seen differences in that sample’s location as low as 255 and as high as 461 samples later, so far, after several repeat runs.)

Can someone please tell me how I can line up the data read back in from the file I have written so that it is in the correct location relative to the original audio? Has anyone else even used the AudioFormatWriter::ThreadWriter to capture audio? If so, do you see this small but vitally important difference in the start of the audio in the file that is written? This simply makes no sense to me.

I suspect that I may have to do something with the metadataValues parameter, other than passing {} as the example code I found does. Anyone know what would be appropriate values to use there for a simple WAV file? I only find the one example, and don’t see anything in the WaveAudioFormat documentation that specifies what I should be using. However, I see that there is something called “bwav” that specifies certain metadata, including bwavTimeReference, which appears to have the purpose of specifying the starting time in the timeline for the data. Am I supposed to be using createBWAVMetadata and passing its result to my createWriteFor function? I have no idea what “bwav” means, but I’ll give that a shot and see if it resolves my problems. If that is the solution, it would help to have that information specified someplace in the documentation or header file.

Do you start writing when the pos of the transport pos is > 0 or when the current block will includes the ppq of 0 ? pos + numSamples > 0

There is no reason it could be related to bwav whatsoever imho

I start writing when the AudioPlayHead’s getCurrentPosition sets the CurrentPositionInfo.isPlaying member to true. At that point, its timeInSamples is 0, because the audio is at the start of the timeline.

At that point, Cubase seems to call the process function 5 times (in one test, at least) with a buffer size of 0 before it actually feeds audio data, but the ThreadedWriter’s write() function returns without doing anything in that case. When the audio actually starts coming though after those initial empty buffers, the write() function actually writes data. So the data should line up, unless something is causing there to be padding at the front of the .wav file that is generated. I have not been able to determine anything that removes that padding, or figured out a way to know how large it is so that I can skip over it when reading the data back in.

I did try adding bwav metdata, and saw a much smaller padding up front when I did that, varying between 30-50 samples or so, but I don’t know if that was just random results or if the metadata somehow actually affects the amount of padding that is getting added. And nothing I did caused the resulting data to actually line up properly on its own.

Given that you have latency, you should have at some point a negative timeInSamples.
Ar at least I have a seen a negative ppqPosition.

isPlaying should probably be true in that case as well.
The varying size you’re seeing is probably the way the DAW do some preroll.

Have you tried disabling your latency ?
I know you said it’s not related but maybe you only meant the numbers.

There is no issue with pre-roll. I have examined the data that is coming in to processBlock, and it is exactly the same every time, with an identifiable value always at the same sample position, and the actual writing to the stream always starts at the same time, and gets passed the exact same values. But the resulting file always has an apparently random number of 0 padding bytes added, prior to the data that is actually sent to the stream via the write() function. It’s in the creation of the WAV file that this is happening, apparently, not in the write() function. Something about writing the WAV file header also writes some number of bytes of 0s, always different, to the file.

maybe a bug un Juce then.

That’s my guess. But I need it to work exactly. The intent is to use a MemoryMappedAudioFormatReader to read back in the data, and align it with the original data precisely. But when I read back in the data using that class, I see this same result. The values as read in are offset by some apparently random amount from when I originally called write() with the sample data. I was hoping that JUCE’s own reading code, at least, would account for the difference automagically, but it doesn’t. It behaves just like the host does if I drag the resulting file back into the timeline.

Now, I can see that the WAV format requires that the various header items all need to get written, then the actual data gets streamed, and then the write goes back and writes the actual size. But I have not yet located where in the code it “skips” forward to the location where it actually starts streaming when calling write(). Something sets that position in the file, but what? The last thing I see written to the file, before streaming actually starts, is this:

writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame));

But then there are a bunch of 0s, which I cannot figure out the source of, with the actual data offset by some indeterminate amount.

How do I get MemoryMappedAudioFormatReader to account for that offset, so that the data I get back is aligned properly with the data I wrote out? Anyone know how to do this? Is anyone out there doing this?

It looks like I found a way to apply the same offset to my data after reading, but it’s kind of kludgy. If I remember the number of samples that I sent to the writer, and later query the number of samples in the reader, that difference is always the same as the offset of the data within the resulting file. It acts just as if the file space is allocated to the nearest 1024 samples greater than or equal to the amount of data I end up writing, and then the data is pushed to the back of that space (or written backwards, from the end towards the front). Very strange! But at least I can use the size difference to compute the offset and compensate for it after the fact.

But I’d still like to know why this is and if there is a better way to get (or remove) that offset.