Rather than just blog an experiment, and leave it at that, I thought I’d blog the story and thought behind the experiment. Hopefully it’s interesting to someone. 🙂
Friday morning I woke up with the random idea to try to simulate playing records in Flash. Don’t ask me where it came from, I just remember waking up and saying to my wife “I wonder if I could make MP3s sound like records with ActionScript?”. She smiled and nodded and said it sounded like a lovely idea (she’s used to humoring random thoughts first thing in the morning). If I were to take a guess, I’d guess the idea grew from a seed planted a month or so ago when my wife and I were discussing the idea of getting a new record player. Not sure why it took a month to turn into an idea, but apparently I think slowly.
I headed to the office and started thinking through the code. Beyond a cursory glance, I’ve never really played with any of the new sound APIs, so I managed to justify this experiment to my team as R&D for future projects.
I started by glancing through the API, and checking out a simple tutorial by Lee Brimelow on working with extractSound and the sampleData event. Then I switched over to designing a 2.5D vector record. I often start explorations this way – learn the core concepts I’ll need, then switch to design for a while. I’m not really a designer, but I think it helps turn on my creative facilities, and gives my subconscious a chance to fit the pieces together while I play with design.
For anyone interested in the design side, here are the more interesting bits: the grooves are dynamically drawn circles, offset from the center randomly by a tiny amount so that you can see them spinning; the highlights are just two layers of vector wedges with blur applied to them, they are counter rotated as the record spins; the whole record is slightly rotated on the Y axis and the highlights are slightly offset to make it wobble a little bit when it spins. Nothing too exciting, but it was kind of fun getting it to look right.
Once I had a decent looking record, my next step was getting basic playback working (again, no sound API experience). I wanted to use FileReference, so that users could play back their own music from their harddrive. Unfortunately, there is no Sound.loadBytes() method (you can vote on this feature here), which makes it difficult to get a sound from the ByteArray data that FileReference returns. Fortunately, Chris at FlexibleFactory wrote a handy class that extracts the MP3 data, wraps it in a SWF, reads it into a Loader, and then pulls it back out as a Sound object.
Once I had the Sound object, it was easy to use extractSound and the sampleData event to get simple playback working (see Lee’s tutorial for help on this).
Scratching and Seeking
Next, I wanted to tie the on screen record to the music playback. I wanted the record to spin when the music was playing, and I wanted you to be able to spin the record to change the music’s playback speed. To make it more complicated, I wanted it to be as physically realistic as possible.
I quickly realized that to do this, I would have to make the record’s rotation subservient to the position of the playback. This is to prevent problems if my sampling events get out of synch with the rotation (you’ll notice even the tiniest audio glitch, but not slight variations in the rotation speed). As my sampling position changes, I update the rotation of the record by using the formula: position/44100/(60/rpm)*360, where 44100 is the sampling rate, 60 is the number of seconds in a minute, rpm is the revolutions per minute (33.3 for an LP), and 360 is the number of degrees in a full rotation. And yes, that formula can be optimized.
When you click and drag the record, it isn’t rotated directly. Instead I calculate a new sound position based on the rotation change implied by the mouse x/y, then update the rotation based on that position.
To generate the sound when dragging the record, I extract the samples between the last position and current position from the source music, then use simple interpolation to generate enough sample data to fill my standard sample size. If you rotate it backwards, it reverses the sampling direction to play backwards.
I’m going to try enhancing the interpolation algorithm in the future. I’m actually planning to try a method I used for my old pattern recognition system, which should hopefully increase the fidelity a bit.
I also wanted to introduce the hissing, pops, and skipping of a real record player.
Hissing was easy. I just add a percentage of random noise to the sample. I determine this percentage using a sine wave based on the current rotation of the record. This way the hissing fades in and out in conjunction with the records wobble, which is inline with how I remember records sounding. (mental note, I probably should have gone back and listened / looked at a physical record at some point during this experiment)
Skipping started easily. Using a derivative of the formula above, it was pretty simple to adjust the position by exactly one revolution of the record, and repeat that a random number of times at the same position. The hard bit was that I wanted it to pop a bit when it skipped, as opposed to just cleanly rewinding. I got this working by simply writing a block of “1”‘s at the point that it skips. I’m sure there’s a smarter way to do it, but I haven’t had a chance to find one yet.
To get popping working, I just applied what I had learned from skipping. I add a random offset to a randomly length block of samples. This results in quiet pops and louder ones.
I quickly added a retro style volume monitor behind the record. It just uses the drawing API to draw out the bars based on the output SoundChannel’s leftPeak and rightPeak values. No need to touch computeSpectrum for something this simple.
This was the last thing I did before heading home for the day, but hey, I had a whole weekend ahead of me.
I didn’t have a lot of time to play on the weekend, but I did really want to get ID3 support in. Unfortunately, the Sound object that is generated from the FileReference data is stripped of ID3 tags, so I had to find another option.
After a bit of hunting, I found the Metaphile library which can read ID3 data from an MP3 in a ByteArray. It’s not well documented, and it has some minor dependencies on the Flex framework, but it was pretty easy to figure it out and get it working without Flex. This let me display the song title, artist, and album. More excitingly, I could pull out allbum art (usually), and display it on the record.
I also did a bit of clean up on the UI, and tweaked some of the effects a bit.
Here’s the current result. Fittingly, I think it sounds best with older tracks (the Beatles and Louis Armstrong sound pretty good). The effects get drowned out by louder tracks (this is true on real record players, too). Turn up your volume to hear the hissing and pops. If it skips, you can either wait for it to continue, or advance the record manually to stop the skipping.
Unfortunately, the ID3 library occasionally generates errors, and I haven’t got around to suppressing them yet. If an MP3 won’t play, try reloading and trying a different one.
); //end AC code
I still plan on improving the interpolation, finessing the effects, and making the scratching sound more realistic (I think I need to introduce some random sawtooth wave forms or something). I’d also love to build a multi-touch version with two turntables and a cross fader. For now, I’m not going to release the source – I’m still playing with it and it’s a horrible, stinky mess. I might release it in the future. I thought I should blog it now though, in case it falls off my radar.