The DTM-2 is a 10-channel stereo audio mixer with a real-time cutoff filter. It was created for the 2010 redesign of the Japanese fan website I ran for the British dance group Underworld. Renouned for their energetic live performances, the band mixes their songs live, often adding in parts from other songs or tracks which don't appear on any of their albums. The DTM-2 gave fans the ability to mix one of a selection of Underworld tracks and experience a simpler version of what the band does in an actual live show.

DTM-2 Instructions

  • Use the "Select a song" drop-down menu at the top to select a track to remix.
  • Press the play button (Space key) to play the song.
  • Use the volume sliders to adjust the levels for each track.
  • Use the pan knobs to pan the sound for each track to the left or right in the stereo field.
  • Click on the mute button (number keys 1 - 10) to mute/unmute the sound for a praticular track.
  • To send the master audio mix through the cutoff filter click the green button in the upper right corner of the filter section.
  • To adjust the master volume of the audio after mixdown, use the MSTR volume slider.
  • To stop playback of a song use the stop button (Return key).

Technical Specifications

The DTM-2 was created in Flash 14 with ActionScript 3 and the Flash sound API (which was new at the time). I used a Pixel Bender shader for mixing down the 10 stereo channels. The audio samples were taken from the Underworld iDrum App and CD tracks and arranged/edited in Logic Audio 5.5. The fader buttons, pan knobs, chasis, and mixer labels were created in Photoshop CS2.

How It Works

When the Flash movie first loads, it imports an XML file that contains the information for each song. After a song is selected from the drop-down menu, the mixer loads a .swf file for that song which contains 10 stereo audio files (1 for each track), as well as the "mixer strip" that is displayed along the bottom of the mixer. To process the audio, the main audio processor class grabs 2,048 samples from the sound file for each track, along with the volume and pan data, and sends this off to the pixel bender shader for mixdown. After the data is returned from the shader, it is sent through the optional cutoff filter and then the "Master Volume" control before being sent off to the soundcard for playback.

The Pixel Bender shader is where the real magic lies, and is what allowed the mixer to even be possible with the processing power of most machines at the time. It is essentially a powerful number cruncher, and because it works outside the Flash Player, accessing the CPU directly, it is perfect for real-time sound manipulation and mixing.

The Pixel Bender shader is doing about 1.5 million calculations per second!

For those who are interested, here is the Pixel Bender code I wrote for mixing down the audio channels:

<languageVersion : 1.0;>

kernel AudioMixerFilter
<   namespace : "darktrain";
    vendor : "phaseblue";
    version : 1;
    description : "Audio mixer with 10 tracks";
>
{
    input image4 track1;
    input image4 track2;
    input image4 track3;
    input image4 track4;
    input image4 track5;
    input image4 track6;
    input image4 track7;
    input image4 track8;
    input image4 track9;
    input image4 track10;
    
    parameter float vol1;
    parameter float vol2;
    parameter float vol3;
    parameter float vol4;
    parameter float vol5;
    parameter float vol6;
    parameter float vol7;
    parameter float vol8;
    parameter float vol9;
    parameter float vol10;

    parameter float panR1;
    parameter float panR2;
    parameter float panR3;
    parameter float panR4;
    parameter float panR5;
    parameter float panR6;
    parameter float panR7;
    parameter float panR8;
    parameter float panR9;
    parameter float panR10;

    parameter float panL1;
    parameter float panL2;
    parameter float panL3;
    parameter float panL4;
    parameter float panL5;
    parameter float panL6;
    parameter float panL7;
    parameter float panL8;
    parameter float panL9;
    parameter float panL10;

    output pixel4 dst;

    void
    evaluatePixel()
    {
        pixel4 tmp1 = sampleNearest(track1,outCoord());
        float2 tmp1VolPan = float2(panL1, panR1)*vol1;
        tmp1 *= float4(tmp1VolPan.x, tmp1VolPan.y, tmp1VolPan.x, tmp1VolPan.y);

        pixel4 tmp2 = sampleNearest(track2,outCoord());
        float2 tmp2VolPan = float2(panL2, panR2)*vol2;
        tmp2 *= float4(tmp2VolPan.x, tmp2VolPan.y, tmp2VolPan.x, tmp2VolPan.y);

        pixel4 tmp3 = sampleNearest(track3,outCoord());
        float2 tmp3VolPan = float2(panL3, panR3)*vol3;
        tmp3 *= float4(tmp3VolPan.x, tmp3VolPan.y, tmp3VolPan.x, tmp3VolPan.y);

        pixel4 tmp4 = sampleNearest(track4,outCoord());
        float2 tmp4VolPan = float2(panL4, panR4)*vol4;
        tmp4 *= float4(tmp4VolPan.x, tmp4VolPan.y, tmp4VolPan.x, tmp4VolPan.y);

        pixel4 tmp5 = sampleNearest(track5,outCoord());
        float2 tmp5VolPan = float2(panL5, panR5)*vol5;
        tmp5 *= float4(tmp5VolPan.x, tmp5VolPan.y, tmp5VolPan.x, tmp5VolPan.y);

        pixel4 tmp6 = sampleNearest(track6,outCoord());
        float2 tmp6VolPan = float2(panL6, panR6)*vol6;
        tmp6 *= float4(tmp6VolPan.x, tmp6VolPan.y, tmp6VolPan.x, tmp6VolPan.y);

        pixel4 tmp7 = sampleNearest(track7,outCoord());
        float2 tmp7VolPan = float2(panL7, panR7)*vol7;
        tmp7 *= float4(tmp7VolPan.x, tmp7VolPan.y, tmp7VolPan.x, tmp7VolPan.y);

        pixel4 tmp8 = sampleNearest(track8,outCoord());
        float2 tmp8VolPan = float2(panL8, panR8)*vol8;
        tmp8 *= float4(tmp8VolPan.x, tmp8VolPan.y, tmp8VolPan.x, tmp8VolPan.y);

        pixel4 tmp9 = sampleNearest(track9,outCoord());
        float2 tmp9VolPan = float2(panL9, panR9)*vol9;
        tmp9 *= float4(tmp9VolPan.x, tmp9VolPan.y, tmp9VolPan.x, tmp9VolPan.y);

        pixel4 tmp10 = sampleNearest(track10,outCoord());
        float2 tmp10VolPan = float2(panL10, panR10)*vol10;
        tmp10 *= float4(tmp10VolPan.x, tmp10VolPan.y, tmp10VolPan.x, tmp10VolPan.y);
        
        pixel4 tmp_out = tmp1 + tmp2 + tmp3 + tmp4 + tmp5 + tmp6 + tmp7 + tmp8 + tmp9 + tmp10;
      
        dst = tmp_out;
    }
}

Lessons Learned

This was an extremely fun project, and really pushed my knowledge of how digital audio is stored and processed. I was not able to finish the colored beat meter at the bottom right because it requred a large modification to the audio processing code, however, I was very satisfied with the result. Eventually, I would like to try and recreate this in JavaScript!