delphiaudioasio24-bit

Deinterleave 24 bit audio buffer for ASIO output


I'm using an ASIO component (in Delphi 7) which supports only 16-bit and 32-bit output when it deinterleaves the left/right channels into 2 separate ASIO buffers. Some ASIO drivers only support 24-bit output so I want to add a deinterleaving function for this bit depth, but the bitwise logic required is beyond me. Below are the DeInterleave32 and DeInterleave16 functions. Can someone please help create a DeInterleave24 function.

The PBufferInfoArray is basically an array of pointers.

type
  TBuffer32 = array[0..0] of LongWord;
  PBuffer32 = ^TBuffer32;

  TBuffer16 = array[0..0] of SmallInt;
  PBuffer16 = ^TBuffer16;

procedure Deinterleave32(InputBuffer : PBuffer32; OutputBuffer : PBufferInfoArray; Samples, BufferIndex : LongWord);
var
  i, j : LongWord;
  Dest : array[0..1] of PBuffer32;
begin
  for i := 0 to 1 do // always 2 channels
    Dest[i] := OutputBuffer[i].buffers[BufferIndex];
  i := 0;
  while i < Samples do
  begin
    j := i shl 1;
    Dest[0][i] := InputBuffer[j];
    Dest[1][i] := InputBuffer[j+1];
    Inc(i);
  end;
end;

procedure Deinterleave16(InputBuffer : PBuffer16; OutputBuffer : PBufferInfoArray; Samples, BufferIndex : LongWord);
var
  i, j : LongWord;
  Dest : array[0..1] of PBuffer16;
begin
  for i := 0 to 1 do // always 2 channels
    Dest[i] := OutputBuffer[i].buffers[BufferIndex];
  for i := 0 to Samples - 1 do
    for j := 0 to 1 do
      Dest[j][i] := InputBuffer[i*2 + j];
end;

Most appreciated if you can help.


Solution

  • I had very little information to go on. Some types were missing, as well as any information about the layout of the data. I therefore took myself a few liberties.

    1. I rewrote both of your old procedures from scratch, this time using pointers instead of array indices. This improves performance, because every pointer variable access is 1 memory read, and every array variable access is 2 (one for the array variable itself and one for the index variable). The new procedures are over twice as fast as your old ones (Timings are embedded in the source snippets).

    2. The 24 bit buffer needed to be written up, so I rewrote both of your old buffer structures as well. For consistency, more granular sub element access and performance reasons.

    The new 16 bit and 32 bit procedures have the same signature as the old ones. That is, they still expect the old buffer types. Note that this is not a problem, because the new types have the same memory footprint as the old ones. The new 24 bit procedure does for obvious reasons use the new 24 bit type.

    All the new types and procedures are prefixed with an underscore (_) to differentiate them from your old code.

    I didn't have access to PBufferInfoArray, so I gleaned its structure from analyzing your old procedures. This is what I concluded it had to look like:

    type
      PBufferInfoArray = ^TBufferInfoArray;
      TBufferInfoArray = array [0..1] of record
        buffers: array [0..0] of Pointer;
      end;
    

    Here is all the new stuff:

    Warning: No argument validation or error checking in these functions!

    type
      _PBuffer32 = ^_TBuffer32;
      _TBuffer32 = packed record // SizeOf(_TBuffer32) = 4
        case Integer of
          0: (d: Cardinal);
          1: (w0, w1: Word);
          2: (b0, b1, b2, b3: Byte);
      end;
    
      _PBuffer24 = ^_TBuffer24;
      _TBuffer24 = packed record // SizeOf(_TBuffer24) = 3
        case Integer of
          0: (w0: Word; w1: Byte); // !
          1: (b0, b1, b2: Byte);
      end;
    
      _PBuffer16 = ^_TBuffer16;
      _TBuffer16 = packed record // SizeOf(_TBuffer16) = 2
        case Integer of
          0: (w: Word);
          1: (b0, b1: Byte);
      end;
    
    // ~92.07 ms (67108864 SAMPLES)
    function _Deinterleave32(InputBuffer: PBuffer32; OutputBuffer: PBufferInfoArray; Samples, BufferIndex: LongWord): Boolean;
    var
      psrc, esrc: _PBuffer32;
      pdst0, pdst1: _PBuffer32;
    begin
      psrc := Pointer(InputBuffer);
      esrc := psrc;
      Inc(esrc, Samples * 2); // The InputBuffer is twice as long as the OutputBuffers.
    
      pdst0 := OutputBuffer[0].buffers[BufferIndex];
      pdst1 := OutputBuffer[1].buffers[BufferIndex];
    
      while (NativeInt(psrc) < NativeInt(esrc)) do
      begin
        pdst0.d := psrc.d;
    {
        pdst0.b0 := psrc.b0;
        pdst0.b1 := psrc.b1;
        pdst0.b2 := psrc.b2;
        pdst0.b3 := psrc.b3;
    }
        Inc(psrc);
        pdst1.d := psrc.d;
    {
        pdst1.b0 := psrc.b0;
        pdst1.b1 := psrc.b1;
        pdst1.b2 := psrc.b2;
        pdst1.b3 := psrc.b3;
    }
        Inc(psrc);
    
        Inc(pdst0);
        Inc(pdst1);
      end;
    
      Result := True;
    end;
    
    // ~99.70 ms (67108864 SAMPLES)
    function _Deinterleave24(InputBuffer: _PBuffer24; OutputBuffer: PBufferInfoArray; Samples, BufferIndex: LongWord): Boolean;
    var
      psrc, esrc: _PBuffer24;
      pdst0, pdst1: _PBuffer32;
    begin
      psrc := Pointer(InputBuffer);
      esrc := psrc;
      Inc(esrc, Samples * 2); // The InputBuffer is twice as long as the OutputBuffers.
    
      pdst0 := OutputBuffer[0].buffers[BufferIndex];
      pdst1 := OutputBuffer[1].buffers[BufferIndex];
    
      while (NativeInt(psrc) < NativeInt(esrc)) do
      begin
        // Data shifted 8 bits to the left to keep the volume.
        pdst0.d := (psrc.w0 or (psrc.w1 shl 16)) shl 8;
    {
        pdst0.b0 := 0;
        pdst0.b1 := psrc.b0;
        pdst0.b2 := psrc.b1;
        pdst0.b3 := psrc.b2;
    }
        Inc(psrc);
        pdst1.d := (psrc.w0 or (psrc.w1 shl 16)) shl 8;
    {
        pdst1.b0 := 0;
        pdst1.b1 := psrc.b0;
        pdst1.b2 := psrc.b1;
        pdst1.b3 := psrc.b2;
    }
        Inc(psrc);
    
        Inc(pdst0);
        Inc(pdst1);
      end;
    
      Result := True;
    end;
    
    // ~99.74 ms (67108864 SAMPLES)
    function _Deinterleave16(InputBuffer: PBuffer16; OutputBuffer: PBufferInfoArray; Samples, BufferIndex: LongWord): Boolean;
    var
      psrc, esrc: _PBuffer16;
      pdst0, pdst1: _PBuffer32;
    begin
      psrc := Pointer(InputBuffer);
      esrc := psrc;
      Inc(esrc, Samples * 2); // The InputBuffer is twice as long as the OutputBuffers.
    
      pdst0 := OutputBuffer[0].buffers[BufferIndex];
      pdst1 := OutputBuffer[1].buffers[BufferIndex];
    
      while (NativeInt(psrc) < NativeInt(esrc)) do
      begin
        // Data shifted 16 bits to the left to keep the volume.
        pdst0.d := psrc.w shl 16;
    {
        pdst0.b0 := 0;
        pdst0.b1 := 0;
        pdst0.b2 := psrc.b0;
        pdst0.b3 := psrc.b1;
    }
        Inc(psrc);
        pdst1.d := psrc.w shl 16;
    {
        pdst1.b0 := 0;
        pdst1.b1 := 0;
        pdst1.b2 := psrc.b0;
        pdst1.b3 := psrc.b1;
    }
        Inc(psrc);
    
        Inc(pdst0);
        Inc(pdst1);
      end;
    
      Result := True;
    end;
    

    Here are your old untouched procedures (with timings):

    // ~232.12 ms (67108864 SAMPLES)
    procedure Deinterleave32(InputBuffer : PBuffer32; OutputBuffer : PBufferInfoArray; Samples, BufferIndex : LongWord);
    var
      i, j : LongWord;
      Dest : array[0..1] of PBuffer32;
    begin
      for i := 0 to 1 do // always 2 channels
        Dest[i] := OutputBuffer[i].buffers[BufferIndex];
      i := 0;
      while i < Samples do
      begin
        j := i shl 1;
        Dest[0][i] := InputBuffer[j];
        Dest[1][i] := InputBuffer[j+1];
        Inc(i);
      end;
    end;
    
    // ~264.79 ms (67108864 SAMPLES)
    procedure Deinterleave16(InputBuffer : PBuffer16; OutputBuffer : PBufferInfoArray; Samples, BufferIndex : LongWord);
    var
      i, j : LongWord;
      Dest : array[0..1] of PBuffer16;
    begin
      for i := 0 to 1 do // always 2 channels
        Dest[i] := OutputBuffer[i].buffers[BufferIndex];
      for i := 0 to Samples - 1 do
        for j := 0 to 1 do
          Dest[j][i] := InputBuffer[i*2 + j];
    end;
    

    Bonus: Here are your old procedures, but cleaned up a little:

    // ~220.23 ms (67108864 SAMPLES)
    procedure Deinterleave32_clean(InputBuffer: PBuffer32; OutputBuffer: PBufferInfoArray; Samples, BufferIndex: LongWord);
    var
      i   : LongWord;
      Dest: array [0..1] of PBuffer32;
    begin
      Dest[0] := OutputBuffer[0].buffers[BufferIndex];
      Dest[1] := OutputBuffer[1].buffers[BufferIndex];
    
      for i := 0 to (Samples - 1) do
      begin
        Dest[0][i] := InputBuffer[i*2];
        Dest[1][i] := InputBuffer[i*2 + 1];
      end;
    end;
    
    // ~143.78 ms (67108864 SAMPLES)
    procedure Deinterleave16_clean(InputBuffer: PBuffer16; OutputBuffer: PBufferInfoArray; Samples, BufferIndex: LongWord);
    var
      i   : LongWord;
      Dest: array [0..1] of PBuffer16;
    begin
      Dest[0] := OutputBuffer[0].buffers[BufferIndex];
      Dest[1] := OutputBuffer[1].buffers[BufferIndex];
    
      for i := 0 to (Samples - 1) do
      begin
        Dest[0][i] := InputBuffer[i*2];
        Dest[1][i] := InputBuffer[i*2 + 1];
      end;
    end;