Audio mixing with Pixel Bender
Time to have some advanced fun with Pixel Bender. Recently someone in the community complained to us that mixing 13 mp3 tracks using the dynamic sound playback feature in Flash Player 10 does not really work. Well, true with the sample project he gave us. Doing dynamic sound playback is generally tricky to get right. I can provide a few tips though.
1. Pick the right mp3 encoding format
It's important you pick a format which consumes the least amount of CPU time for decoding. Specifically you should always choose 44.1Khz as the sample rate for your mp3 files. Why? The Flash Player will otherwise have to re-sample and filter your audio which takes away precious CPU cycles.
The tricky part here is that mp3 encoders usually pick the sample rate automatically, including Adobe Audition. Especially at a bit rate of 64kb or less it will try to switch to 24Khz or 22Khz. You can override this at least for CBR in Audition using the advanced settings at export time if you need to.
2. Keep things simple
Do all your processing in one function if you can. Function calls are expensive generally. Try to read and write data only once. Ideally your mixing code should look something like this if you use pure ActionScript:
3. Use Pixel Bender to mix sounds
I have talked to some who have tried to use Pixel Bender for audio processing. They had little success most of the time. Truth is, our tools are not ready yet. But with some patience and using the assembler for creating .pbj files I posted recently you can make it happen today.
One problematic issue is that right now the Pixel Bender toolkit is designed to handle image data. What does that mean? The toolkit limits you to float3 and float4 output types right now which is not really what you want. Now you might think you could just use float4. Not so. You will notice that Flash Player 10 has a pretty bad bug which makes it not work when float4 types are used for output. I am really angry we did not catch this sooner, hopefully we can address this bug sooner than later. What you are left with is using pure Pixel Bender assembly code for now which allows you to use a float2 output type.
For my experiment I took the Adobe Audition theme sample project and exported all tracks as separate .mp3 files, 15 in total. Incidentally that is also the maximum amount of inputs you can use for a single shader. The goal was to mix all 15 tracks in real time using the dynamic sound playback feature.
Here is the Pixel Bender assembly code I used to create my .pbj file:
To use the shader I wrote this little piece (note that this is incomplete code, it will not compile):
1. Pick the right mp3 encoding format
It's important you pick a format which consumes the least amount of CPU time for decoding. Specifically you should always choose 44.1Khz as the sample rate for your mp3 files. Why? The Flash Player will otherwise have to re-sample and filter your audio which takes away precious CPU cycles.
The tricky part here is that mp3 encoders usually pick the sample rate automatically, including Adobe Audition. Especially at a bit rate of 64kb or less it will try to switch to 24Khz or 22Khz. You can override this at least for CBR in Audition using the advanced settings at export time if you need to.
2. Keep things simple
Do all your processing in one function if you can. Function calls are expensive generally. Try to read and write data only once. Ideally your mixing code should look something like this if you use pure ActionScript:
var buffer:Vector.<ByteArray> = new Vector.<ByteArray>(NUM_TRACKS);You will notice that you will spend a lot of time in this function. So...
var sound:Vector.<Sound> = new Vector.<Sound>(NUM_TRACKS);
function onSoundData(sampleDataEvent:SampleDataEvent) : void
{
for (var i:int = 0; i < NUM_TRACKS; i++) {
buffer[i].position = 0;
sound[i].extract(buffer[i], BUFFER_SIZE);
buffer[i].position = 0;
}
for (var j:int = 0; j < BUFFER_SIZE*2; j++)
{
var val:Number = 0;
for (var k:int = 0; k < NUM_TRACKS; k++)
{
val += buffer[k].readFloat();
}
sampleDataEvent.data.writeFloat(val);
}
}
3. Use Pixel Bender to mix sounds
I have talked to some who have tried to use Pixel Bender for audio processing. They had little success most of the time. Truth is, our tools are not ready yet. But with some patience and using the assembler for creating .pbj files I posted recently you can make it happen today.
One problematic issue is that right now the Pixel Bender toolkit is designed to handle image data. What does that mean? The toolkit limits you to float3 and float4 output types right now which is not really what you want. Now you might think you could just use float4. Not so. You will notice that Flash Player 10 has a pretty bad bug which makes it not work when float4 types are used for output. I am really angry we did not catch this sooner, hopefully we can address this bug sooner than later. What you are left with is using pure Pixel Bender assembly code for now which allows you to use a float2 output type.
For my experiment I took the Adobe Audition theme sample project and exported all tracks as separate .mp3 files, 15 in total. Incidentally that is also the maximum amount of inputs you can use for a single shader. The goal was to mix all 15 tracks in real time using the dynamic sound playback feature.
Here is the Pixel Bender assembly code I used to create my .pbj file:
Looks complicated, but in fact this does nothing more that the above ActionScript code, with unrolled loops. As an extra you can control the volume on each track.
version 1
name "SoundMixer"
kernel "namespace", "adobe"
kernel "vendor", "Adobe Systems"
kernel "version", 1
kernel "description", "A generic sound mixer with volume control"
parameter "_OutCoord", float2, f0.rg, in
texture "track0", t0.rg
texture "track1", t1.rg
texture "track2", t2.rg
texture "track3", t3.rg
texture "track4", t4.rg
texture "track5", t5.rg
texture "track6", t6.rg
texture "track7", t7.rg
texture "track8", t8.rg
texture "track9", t9.rg
texture "track10", t10.rg
texture "track11", t11.rg
texture "track12", t12.rg
texture "track13", t13.rg
texture "track14", t14.rg
parameter "volume0", float2, f3.rg, in
meta "defaultValue", 1, 1
parameter "volume1", float2, f4.rg, in
meta "defaultValue", 1, 1
parameter "volume2", float2, f5.rg, in
meta "defaultValue", 1, 1
parameter "volume3", float2, f6.rg, in
meta "defaultValue", 1, 1
parameter "volume4", float2, f7.rg, in
meta "defaultValue", 1, 1
parameter "volume5", float2, f8.rg, in
meta "defaultValue", 1, 1
parameter "volume6", float2, f9.rg, in
meta "defaultValue", 1, 1
parameter "volume7", float2, f10.rg, in
meta "defaultValue", 1, 1
parameter "volume8", float2, f11.rg, in
meta "defaultValue", 1, 1
parameter "volume9", float2, f12.rg, in
meta "defaultValue", 1, 1
parameter "volume10", float2, f13.rg, in
meta "defaultValue", 1, 1
parameter "volume11", float2, f14.rg, in
meta "defaultValue", 1, 1
parameter "volume12", float2, f15.rg, in
meta "defaultValue", 1, 1
parameter "volume13", float2, f16.rg, in
meta "defaultValue", 1, 1
parameter "volume14", float2, f17.rg, in
meta "defaultValue", 1, 1
parameter "output", float2, f1.rg, out
;----------------------------------------------------------
texn f1.rg, f0.rg, t0
mul f1.rg, f3.rg
texn f2.rg, f0.rg, t1
mul f2.rg, f4.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t2
mul f2.rg, f5.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t3
mul f2.rg, f6.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t4
mul f2.rg, f7.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t5
mul f2.rg, f8.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t6
mul f2.rg, f9.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t7
mul f2.rg, f10.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t8
mul f2.rg, f11.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t9
mul f2.rg, f12.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t10
mul f2.rg, f13.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t11
mul f2.rg, f14.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t12
mul f2.rg, f15.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t13
mul f2.rg, f16.rg
add f1.rg, f2.rg
texn f2.rg, f0.rg, t14
mul f2.rg, f17.rg
add f1.rg, f2.rg
To use the shader I wrote this little piece (note that this is incomplete code, it will not compile):
Compared to the pure AS3 version this runs twice as fast overall. On my Core 2 Mac mixing the 15 tracks consumes about 24% of one CPU. So if you are doing simple audio mixing like this Pixel Bender is a good choice. YMMV depending on what application we are talking about and how much processing you need to do on the audio.
// Create shader
[Embed(source="mixer.pbj", mimeType="application/octet-stream")]
var MixerShader:Class;
var mixerShader:Shader = new Shader(new MixerShader());
// buffers will become shader inputs
var buffer:Vector.<ByteArray> = new Vector.<ByteArray>(15);
// volume control volume on each track, 1.0 is full volume
var volume:Vector.<Number> = new Vector.<Number>(15);
// initialize the shader inputs and volume values
for (var j:int = 0; j < 15; j++) {
volume[j]=1.0;
buffer[j] = new ByteArray();
// set so shader will always work even we have not enough tracks
buffer[j].length = BUFFER_SIZE*4*2;
mixerShader.data["track"+j]["width"] = 1024;
mixerShader.data["track"+j]["height"] = BUFFER_SIZE/1024;
mixerShader.data["track"+j]["input"] = buffer[j];
}
function onSoundData(e:SampleDataEvent) : void
{
// extract the mp3 data into our shader inputs
for (var i:int = 0; i < NUM_TRACKS; i++) {
buffer[i].position = 0;
sounds[i].extract(buffer[i], BUFFER_SIZE);
buffer[i].position = 0;
}
// update the volume value in the shader
for (var k:int = 0; k < NUM_TRACKS; k++) {
mixerShader.data["volume"+k]["value"] = [ volume[k], volume[k] ];
}
// mix!
var mixerJob:ShaderJob = new ShaderJob(mixerShader, e.data, 1024, BUFFER_SIZE/1024);
mixerJob.start(true);
}


12 Comments:
Thanks for demonstrating this. I have it working with PB using pixel3, and float3, but of course with only 2 inputs.
Yes, this is definitely the demo many of us have been aching for. Thanks!
Thank you, this post is right on time for one of our projects.
However, I was wondering why you provided a width of 1024 and a height of BUFFER_SIZE/1024 when you setup the shader's data. I made a test with a width of BUFFER_SIZE and a height of 1 and everything seems to work just fine.
Is there any performance impact related to the width/height properties?
Olivier,
yes, there is a reason for using a height other than 1. The player splits splits rendering tasks into horizontal stripes on multi-core machines. If the height is 1 only one CPU will be used.
Is there any documentation yet on how you can create pixel blender kernels for audio mixing? You have provided only assembly code.
Umm, teh pimP? Very nice. Fair to say I have been waiting for this for sometime. I am wondering if this is the right place to ask if people would be interested in putting together a Pure Data style interface (both GUI and extensible with externals) written as an AS3 creative commons library. Please contact me if so.
Please could you share the complete source? I can't compile the SampleDataEvent examples in my flex builder environment... I have downloaded the 3.2.0.3643 and 4.0.x SDKs but it does not compile: SampleDataEvent is not recognized.
Sorry if it is obvious...
Are there plans to implement FOR loops in pixel bender in the near future? The unrolling technique works fine for some situations, but I would love to have the number of passes on my filters be dynamic.
Tinic, to me it looks like the code you have provided would create a race condition where the ShaderJob has to process e.data before it gets consumed by the audio system. What safeguards are in place to ensure that the ShaderJob operation comes first, given that we're dealing with two asynchronous operations?
Tinic,
We're currently doing a project which needs us to output a float1 from pixel bender code. (We're processing vectors of data). Currently the pixel bender toolkit won't let us do this. Is this a limitation of the toolkit or the flash implementation of pixel bender?
I'm hoping its the toolkit and there's a fix coming soon. Any information would be great.
Cheers
Joe
Please please change the default font you use on your blog - it is extremely difficult to read!
Thanks for this!! This is really powerful stuff!! I am actually going to release an audio mixer with this in it. It worked great and we needed it to fix some major sync issues. The only problem I have found is when any playing is started on IE on pc, the player throws a Stack Overflow error. Have you seen this behavior? Even stranger...it is only on the release version of my swf published online. On pc ie running locally all is fine.
Post a Comment
<< Home