More Audio Woes

TL;DR at end of post if you want to skip my nonsense.

Here’s something that almost made me tear my laptop in two like a phone book.

I’ve moved to a lower-level sound model so I can have sample-accurate control. I’m still using two bar phrases which need to be connected together seamlessly; the overhead in the typical Sound.play() model was causing a several ms hiccup which was unnoticeable to the human ear but threw timing off disastrously. With the lower level model, the algorithm looks something like this:

function processSound(event:SampleDataEvent):void {
	var bytes:ByteArray = new ByteArray();
	var numSamples:int = 0;
	var startSample:int = -1;
	while (numSamples <= BUFFER_SIZE){
 		var samplesRequest:int = BUFFER_SIZE - numSamples;
 		numSamples += _currentTrack.extract(bytes, samplesRequest, startSample);
 		startSample = 0;
 	}
 	event.data.writeBytes(bytes);
 }

Note first of all that the actual .as file has an additional 42 lines of explanatory, questioning, soul-searching comments and debug statements, excised here for your benefit. The idea is simple: we fill a ByteArray with up to BUFFER_SIZE samples (8 bytes each: 2 x 32-bits representing left and right channel), then go back to the beginning if we need more, and so on.

Problem Number 1: First of all, let's say you read the last remaining 1987 samples in the Sound. You loop, and ask for an additional 6205 samples. Flash gives you 6204 for some reason. So you loop again and ask for 1 measly sample. Yet stingy Flash gives you 0. And you loop again and again and again and again okay we get it, that's great, we get it. Why?

After some debugging I found that Sound.extract() would only return a number of samples that was a multiple of four. So 8192, 8188, 6204, 4, etc. Okay, the fix for that is simple, if stupid: just buffer a little less than BUFFER_SIZE if you don't get within 4 samples of it. That means your buffer isn't quite up to size, but that shouldn't really affect anything, especially since we're working with a large buffer. So you can do

while (numSamples < (BUFFER_SIZE - MINIMUM_SAMPLE_REQUEST)){...}

and you should be fine.

But you're not fine, you're still screwed, because of:

Problem Number 2: when you add your 6204 bytes to your 1987 bytes and write that ByteArray to the sound stream, you get "RangeError: Error #2004: One of the parameters is invalid", which error message, of course, is enthroned in the great pantheon of Really Helpful Error Messages Thank You Very Much. Like, not even maybe a stack trace? Or, like, maybe mention who didn't like the parameters? Or, get this, which parameters? If I had a little more time I would point out that there are thousands of parameters being passed around the program, only some of which I'm privy to, and in fact I might, if I had the time, calculate the efficiency at which one could approach figuring out which parameter is invalid and that it is, in fact, rather poor, and also could then extrapolate that to estimate the time spent by your average programmer trying to figure it out and show with some rigor that that time is measured in hours, not minutes, and in fact may bleed into the lives of other good-hearted programmers on various StackExchange-like message boards who unwittingly may try to help, and could furthermore argue, if one thinks about it, that all of these man-hours have a cost, financial and otherwise, inasmuch as some involved may be gainfully employed but more importantly time spent on this earth is sadly finite and has an innate value of its own, and that all lives born up in this travesty of an error message are irrevocably shortened and made worse by it, and I might conclude that the true cost of this error message is not measured in money, nor man-hours, nor inefficiency, but rather human tears and despair, but no, there is unfortunately no time for that analysis to be done, I'm afraid.

Instead I determined the error was coming from event.writeBytes(), which was receiving way way too many bytes of samples. Like, four times as many. Exactly four times. But it's stranger. If you read BUFFER_SIZE samples correctly, the ByteArray ends up being correctly sized (BUFFER_SIZE * 8 bytes). But if you reach the end, and Sound.extract() reports it wrote 1987 samples, if you check the ByteArray it really received 63584 bytes, which, if we were in sane-happy-land, would be 7948 samples. But we're not in that fabled land where things work like you expect them to and it's safe to make certain assumptions, so we don't know which it is: did we actually get more samples, or did all of the samples take up 32-bytes (!!) each, or is it a misreporting, or is this all just a dream and when I wake up I'll be in sane-happy-land, or is the land you wake up in even worse? The possibilities are too fearsome to contemplate, and getting to the bottom of it - I supposed by looking through the byte array, or analyzing the sound produced - are beyond the scope of my life.

Things were looking grim for our hero. On a whim I thought, what if there's something wrong with the file? I produced it with professional hardware and software but maybe there's some kind of error that's killing Flash and me slow. But before checking the source I went into Flash Professional and messed around with the encoding of the file, changing it first from "Default Encoding" to RAW, stereo, no compression. And it worked, flawlessly. It even extracted odd, non-multiples of four numbers of samples. So I tried choosing another encoding: MP3 160kb/s 44.1 stereo, and that worked as well. Then I started writing this blog post, later still you started reading it, and here we are.

I don't know exactly why the encoding might cause such a strange thing: generally correct extraction until the end of the file when you get QUAD DAMAGE or something. And that's one of those things I'm just going to file under things-i-don't-even-want-to-know-the-answer-to-i-just-want-them-to-go-away and hopefully there it shall remain evermore.

TL;DR: Sound.extract() may report an incorrect number of samples read as its return value if you reach the end of a sound file that is encoded in a certain way for yet undiscovered reasons. Solution: re-encode the file.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>