PixelBender audio mixer with dynamic track count

Pixel Bender can be a great tool for number crunching, but has a couple of limitations when trying to create an audio mixer.

  • The number of inputs is limited when using PixelBender toolkit to compile .pbj files.
  • Even if one overcomes the limitation by writing Pixel Bender assembly, the track count is fixed to a predetermined number determined by the number of inputs you have in the .pbj.

In some cases you might want your application to act as a normal audio app where you can add as many tracks as your CPU can handle, but not use more CPU than needed for the current track count.

What we need to accomlish that is a way to dynamically create a shader with the desired number of inputs. James Ward has created pbjAS, a library that enables you to create Pixel Bender shaders at runtime, based on Nicolas Cannasees haXe library.

Also, Tinic Uro has posted Pixel Bender assembly code to create a mixer.

So I set out to use pbjAS to recreate Tinics code with a dynamic number of channels.

The result is this class:

package com.blixtsystems.audio

{

import flash.display.Shader;

import flash.display.ShaderJob;

import flash.utils.ByteArray;

import pbjAS.ops.OpAdd;

import pbjAS.ops.OpMul;

import pbjAS.ops.OpSampleNearest;

import pbjAS.params.Parameter;

import pbjAS.params.Texture;

import pbjAS.PBJ;

import pbjAS.PBJAssembler;

import pbjAS.PBJChannel;

import pbjAS.PBJParam;

import pbjAS.PBJType;

import pbjAS.regs.RFloat;

/**

* Shader to mix audio with a dynamic number of channels

* @author leo@blixtsystems.com

*/

public class MixerShader

{

private var _bufferSize:int;

private var _pbj:PBJ = new PBJ();

private var _shader:Shader;

private var _buffer:Vector.<ByteArray> = new Vector.<ByteArray>();

private var _numTracks:int;

/**

* Constructor

* @param numTracks track count

*/

public function MixerShader(numTracks:int, bufferSize:int=2048)

{

_numTracks = numTracks;

_bufferSize = bufferSize;

}

/*-----------------------------------------------------------

Public methods

-------------------------------------------------------------*/

/**

* Mix audio

* @param data ByteArray in which to store the result, probably SampleDataEvent.data

*/

public function mix(data:ByteArray):void

{

var mixerJob:ShaderJob = new ShaderJob(_shader, data, 1024, _bufferSize/1024);

mixerJob.start(true);

}

/*-----------------------------------------------------------

Private methods

-------------------------------------------------------------*/

private function assembleShader():void

{

var channels:Array = [PBJChannel.R, PBJChannel.G];

var chanStr:String = "rg";

_pbj.version = 1;

_pbj.name = "SoundMixer";

_pbj.parameters =

[

new PBJParam

(

"_OutCoord",

new Parameter

(

PBJType.TFloat2,

false,

new RFloat( 0, channels)

)

)

];

_pbj.code =

[

new OpSampleNearest

(

new RFloat(1, channels),

new RFloat(0, channels),

0

),

new OpMul

(

new RFloat(1, channels),

new RFloat(3, channels)

)

];

var i:int;

for (i = 0; i < _numTracks; i++)

{

_pbj.parameters.push

(

new PBJParam

(

"track" + i,

new Texture(2, i)

)

);

}

for (i = 0; i < _numTracks; i++)

{

_pbj.parameters.push

(

new PBJParam

(

"volume" + i,

new Parameter

(

PBJType.TFloat2,

false,

new RFloat(i + 3, channels)

)

)

);

}

for (i = 0; i < _numTracks-1; i++)

{

_pbj.code.push

(

new OpSampleNearest

(

new RFloat(2, channels),

new RFloat(0, channels),

i+1

),

new OpMul

(

new RFloat(2, channels),

new RFloat(i+4, channels)

),

new OpAdd

(

new RFloat(1, channels),

new RFloat(2, channels)

)

);

}

_pbj.parameters.push

(

new PBJParam

(

"output",

new Parameter

(

PBJType.TFloat2,

true,

new RFloat(1, channels)

)

)

);

var pbjBytes:ByteArray = PBJAssembler.assemble(_pbj);

_shader = new Shader(pbjBytes);

_buffer = new Vector.<ByteArray>(_numTracks);

// initialize the shader inputs

for (i = 0; i < _numTracks; i++) {

_buffer[i] = new ByteArray();

_buffer[i].length = _bufferSize * 4 * 2;

_shader.data["track" + i]["width"] = 1024;

_shader.data["track" + i]["height"] = _bufferSize / 1024;

_shader.data["track" + i]["input"] = _buffer[i];

_shader.data["volume" + i]["value"] = [1, 1];

}

}

/*-----------------------------------------------------------

Getters/Setters

-------------------------------------------------------------*/

public function get numTracks():int { return _numTracks; }

public function set numTracks(value:int):void

{

// needs to be at least one input, and no point reassembling pbj if track count has not changed

if (_numTracks < 1 && _numTracks == value) return;

_numTracks = value;

assembleShader();

}

public function get buffer():Vector.<ByteArray> { return _buffer; }

}

}

To use it you need to download pjbAS from James Ward and include the swc in your project.

Page 1 of 2 | Next page

Share/Bookmark