Author Topic: BassEnc EncodeProc doesn't work with BASS_ENCODE_PCM  (Read 6257 times)

The Mask

  • Posts: 97
Hi,

Is there a way to get BassEnc to send the output with the flag BASS_ENCODE_PCM to the EncodeProc callback function? I want to write my own header for raw pcm files so that they can be opened in for example TsMuxer. Also I maybe want to write my own aiff header.

When I use the following code:

Code: [Select]
BASS_Encode_Start(chan, pchar(OutPath + FileName + '.pcm'), BASS_ENCODE_PCM or BASS_ENCODE_FP_24BIT or BASS_ENCODE_NOHEAD or BASS_UNICODE or BASS_ENCODE_AUTOFREE, @Proc, self)
I get error 33 (BASS_ERROR_CREATE) because I already opened the file to write my own header before the call to BASS_Encode_Start. If I leave the second argument empty ('') then it also doesn't work.

When I use the following code:

Code: [Select]
BASS_Encode_Start(chan, '', BASS_ENCODE_FP_24BIT or BASS_ENCODE_NOHEAD or BASS_UNICODE or BASS_ENCODE_AUTOFREE, @Proc, self)

I get error 2 (BASS_ERROR_FILEOPEN) because the encoder cmdline is empty because I don't use a commandline

Can bassenc be modified so that it also can be used with the EncodeProc callback function when the flag BASS_ENCODE_PCM is used and that it ignores the pcm output name (second argument) and just write its output to the callback function?

I like BassEnc for it output possibilities (16,24,32 bit/32 bit float and little- or big-endian).

The Mask

  • Posts: 97
Re: BassEnc EncodeProc doesn't work with BASS_ENCODE_PCM
« Reply #1 on: 31 May '10 - 20:11 »
No one has a solution?  Ian maybe?

radio42

  • Posts: 4613
I guess this is currently not possible, because there is no real need for an ENCODEPROC, since you can at any time always get the raw PCM sample data via a DSPPROC.

The Mask

  • Posts: 97
You are right but the advantage to do it through bassenc is that it does the conversions (16, 24, 32 bit and the little and big endian swap) and I don't have to do it (don't know how in delphi). So for some of us it would be easier to do it through Bassenc.

Or maybe you can tell me how to convert 32 bit float to 32 bit/24 bit integer and how to do the big endian swap in delphi.

Ian @ un4seen

  • Administrator
  • Posts: 21024
Is there a way to get BassEnc to send the output with the flag BASS_ENCODE_PCM to the EncodeProc callback function?

No, I'm afraid the BASS_Encode_Start "proc" parameter is currently ignored when the BASS_ENCODE_PCM flag is in effect. The problem is that the WAVE header needs to be updated (to contain the correct length) at the end of the file writing, which isn't possible via an ENCODEPROC.

If it is just the float/int conversion that you want, you could do that without BASSenc. 32-bit integer conversion could be done something like this...

Code: [Select]
float *src=...;
int *dst=...;
for (int a=0; a<samples; a++) {
// convert to integer (64-bit for headroom)
__int64 s=src[a]*2147483648.0;
// clip it
if (s>2147483647) s=2147483647;
else if (s<-2147483648) s=-2147483648;
// store it (native endian)
dst[a]=(int)s;
}

24-bit is slightly more complex due to there being no 24-bit data type...

Code: [Select]
float *src=...;
BYTE *dst=...;
for (int a=0; a<samples; a++) {
// convert to integer
int s=src[a]*8388608.0;
// clip it
if (s>8388607) s=8388607;
else if (s<-8388608) s=-8388608;
// store it (little-endian)
dst[0]=(BYTE)s;
dst[1]=(BYTE)(s>>8);
dst[2]=(BYTE)(s>>16);
dst+=3;
}

Big-endian is a matter of reversing the order of the bytes...

Code: [Select]
float *src=...;
BYTE *dst=...;
for (int a=0; a<samples; a++) {
// convert to integer (64-bit for headroom)
__int64 s=src[a]*2147483648.0;
// clip it
if (s>2147483647) s=2147483647;
else if (s<-2147483648) s=-2147483648;
// store it (big-endian)
dst[0]=(BYTE)(s>>24);
dst[1]=(BYTE)(s>>16);
dst[2]=(BYTE)(s>>8);
dst[3]=(BYTE)s;
dst+=4;
}

Code: [Select]
float *src=...;
BYTE *dst=...;
for (int a=0; a<samples; a++) {
// convert to integer
int s=src[a]*8388608.0;
// clip it
if (s>8388607) s=8388607;
else if (s<-8388608) s=-8388608;
// store it (big-endian)
dst[0]=(BYTE)(s>>16);
dst[1]=(BYTE)(s>>8);
dst[2]=(BYTE)s;
dst+=3;
}
« Last Edit: 3 Jun '10 - 14:51 by Ian @ un4seen »

The Mask

  • Posts: 97
But I don't need the EncodeProc when writing wave files but only when using the BASS_ENCODE_PCM in combination with the  BASS_ENCODE_NOHEAD flag. So when no header is written by bassenc then EncodeProc should be available because after writing the end of the file no header update is needed.

But if this is not possible I will try the conversion functions you posted.

Thanks Ian and consider the possibility with the BASS_ENCODE_NOHEAD flag. :D

« Last Edit: 2 Jun '10 - 23:46 by The Mask »

The Mask

  • Posts: 97
I tried your conversion routines Ian but I have some questions:


The conversion from float to 16bit goes fine, here is my dsp:

Code: [Select]
procedure DSP16bit(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
var
  src : array of Single;
  dst : array of SmallInt;
  i : Integer;
  SaveValue : SmallInt;
begin
  SetLength(src, length);
  SetLength(dst, length div 2);

  move(buffer^, src[0], length);

  for I := 0 to (length div 4) do
    begin
      // convert to integer
      SaveValue := Trunc(src[I]*32768);
      // clip it
      if (SaveValue > 32767) then
        SaveValue := 32767
      else
        if (SaveValue < -32768) then
          SaveValue := -32768;
      // store it (native endian)
      dst[I] := SmallInt(SaveValue);
    end;

  Form1.DSPFile.Write(dst[0], length div 2);
end;

The output file compared to bassenc is almost the same (the waveform is identical but hex compare isn't).

For the float to 32 bit conversion I get a different waveform compared to the bassenc output, here is my dsp:

Code: [Select]
procedure DSP32bit(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
var
  src : array of Single;
  dst : array of integer;
  i : Integer;
  SaveValue : Int64;
begin
  SetLength(src, length);
  SetLength(dst, length);

  move(buffer^, src[0], length);

  for I := 0 to (length div 4) do
    begin
      // convert to integer (64-bit for headroom)
      SaveValue := Trunc(src[I]*4294967296);
      // clip it
      if (SaveValue > 4294967295) then
        SaveValue := 4294967295
      else
        if (SaveValue < -4294967296) then
          SaveValue := -4294967296;
      // store it (native endian)
      dst[I] := Integer(SaveValue);
    end;

  Form1.DSPFile.Write(dst[0], length);
end;

And the waveforms look like this:



Does Bassenc do some other things while writing PCM files because the waveform is not the same. My dsp is different from the one from BassEnc. What could that be? Or should the value be 2147483647 instead of 4294967296?


Also can you tell me how the declaration of BYTE *dst=... looks like in the float to 24 bit conversion? Is it dst := Array of Byte or something else and how do I translate the following to Delphi code:

Code: [Select]
BYTE *dst=...;

dst[0]=(BYTE)(s>>24);
dst[1]=(BYTE)(s>>16);
dst[2]=(BYTE)(s>>8);
dst[3]=(BYTE)s;
dst+=4;

and

Code: [Select]
dst[0]=(BYTE)s;
dst[1]=(BYTE)(s>>8);
dst[2]=(BYTE)(s>>16);
dst+=3;
« Last Edit: 3 Jun '10 - 11:41 by The Mask »

Ian @ un4seen

  • Administrator
  • Posts: 21024
The conversion from float to 16bit goes fine, here is my dsp:

Code: [Select]
procedure DSP16bit(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
var
  src : array of Single;
  dst : array of SmallInt;
  i : Integer;
  SaveValue : SmallInt;
begin
  SetLength(src, length);
  SetLength(dst, length div 2);

  move(buffer^, src[0], length);

  for I := 0 to (length div 4) do
    begin
      // convert to integer
      SaveValue := Trunc(src[I]*32768);

I'm not sure that is entirely fine :)

I believe "SmallInt" is only 16-bit, which means "SaveValue" has no headroom for larger values; it should probably be "Integer" instead.

For the float to 32 bit conversion I get a different waveform compared to the bassenc output...

Ah yes, sorry, I made a mistake in the code above; the numbers were 2x what they should have been. I have corrected them now, so please check it again.

Also can you tell me how the declaration of BYTE *dst=... looks like in the float to 24 bit conversion? Is it dst := Array of Byte or something else and how do I translate the following to Delphi code:

Yes, it would be an array of bytes. I'm afraid I can't give a Delphi translation, but the ">>" is bit-shifting right; I think the Delphi equivalent is "SHR".

The Mask

  • Posts: 97
Thanks Ian got the little-endian conversions working now.

Here is my implementation of the float to 24 bit conversion (little-endian):

Code: [Select]
procedure DSP24bitLE(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
type
  T24BitSample = record
    a0 : Byte;
    a1 : Byte;
    a2 : ShortInt;
  end;
var
  src : array of Single;
  dst : array of T24BitSample;
  i : Integer;
  SaveValue : Integer;
  NumSamples : integer;
begin
  SetLength(src, length);
  SetLength(dst, length * 24 div 32); // Size * 24 bit (output bitrate) div 32 bit (input bitrate)

  move(buffer^, src[0],  length);

  NumSamples := Length div (32 div 8);  // 32 bit float div 8
  for I := 0 to NumSamples - 1 do
    begin
      // convert to integer
      SaveValue := Trunc(src[I]*8388608);
      // clip it
      if (SaveValue > 8388607) then
        SaveValue := 8388607
      else
        if (SaveValue < -8388608) then
          SaveValue := -8388608;
      // store it (little-endian)
      dst[I].a0 := SaveValue;
      dst[I].a1 := SaveValue shr 8;
      dst[I].a2 := SaveValue shr 16;
    end;

  Form1.DSPFile.Write(dst[0], length * 24 div 32);
end;



I also have the big-endian conversion done, can you check if I did it right:

16 bit integer:

Code: [Select]
procedure DSP16bitBE(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
type
  T16BitSample = record
    a0 : Byte;
    a1 : Byte;
  end;
var
  src : array of Single;
  dst : array of T16BitSample;
  i : Integer;
  SaveValue : integer;
  NumSamples : integer;
begin
  SetLength(src, length);
  SetLength(dst, length * 16 div 32); // Size * 16 bit (output bitrate) div 32 bit (input bitrate)

  move(buffer^, src[0], length);

  NumSamples := Length div (32 div 8);  // 32 bit float div 8
  for I := 0 to NumSamples - 1 do
    begin
      // convert to smallint (integer for headroom)
     SaveValue := Trunc(src[I]*32768);
      // clip it
      if (SaveValue > 32767) then
        SaveValue := 32767
      else
        if (SaveValue < -32768) then
          SaveValue := -32768;
      // store it (native endian)
      dst[I].a0 := SaveValue shr 8;
      dst[I].a1 := SaveValue;
    end;

  Form1.DSPFile.Write(dst[0], length * 16 div 32);
end;

24 bit integer:

Code: [Select]
procedure DSP24bitBE(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
type
  T24BitSample = record
    a0 : Byte;
    a1 : Byte;
    a2 : ShortInt;
  end;
var
  src : array of Single;
  dst : array of T24BitSample;
  i : Integer;
  SaveValue : Integer;
  NumSamples : integer;
begin
  SetLength(src, length);
  SetLength(dst, length * 24 div 32); // Size * 24 bit (output bitrate) div 32 bit (input bitrate)

  move(buffer^, src[0],  length);

  NumSamples := Length div (32 div 8);  // 32 bit float div 8
  for I := 0 to NumSamples - 1 do
    begin
      // convert to integer
      SaveValue := Trunc(src[I]*8388608);
      // clip it
      if (SaveValue > 8388607) then
        SaveValue := 8388607
      else
        if (SaveValue < -8388608) then
          SaveValue := -8388608;
      // store it (big-endian)
      dst[I].a0 := SaveValue shr 16;
      dst[I].a1 := SaveValue shr 8;
      dst[I].a2 := SaveValue;
    end;

  Form1.DSPFile.Write(dst[0], length * 24 div 32);
end;

32 bit integer:

Code: [Select]
procedure DSPProc32bitBE(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
type
  T32BitSample = record
    a0 : Byte;
    a1 : Byte;
    a2 : Byte;
    a3 : Byte;
  end;
var
  src : array of Single;
  dst : array of T32BitSample;
  i : Integer;
  SaveValue : Int64;
  NumSamples : integer;
begin
  SetLength(src, length);
  SetLength(dst, length);

  move(buffer^, src[0], length);

  NumSamples := Length div (32 div 8);  // 32 bit float div 8
  for I := 0 to NumSamples - 1 do
    begin
      // convert to integer (64-bit for headroom)
      SaveValue := Trunc(src[I]*2147483647);
      // clip it
      if (SaveValue > 2147483647) then
        SaveValue := 2147483647
      else
        if (SaveValue < -2147483647) then
          SaveValue := -2147483647;
      // store it (native endian)
      dst[I].a0 := SaveValue shr 24;
      dst[I].a1 := SaveValue shr 16;
      dst[I].a2 := SaveValue shr 8;
      dst[I].a3 := SaveValue;
    end;

  Form1.DSPFile.Write(dst[0], length);
end;


The only thing I can't figure out is how the conversion from 32 float little-endian to 32 bit float big-endian looks like. Can you show it to me  :)

Edit: I found the following on the web, seems to work but I don't know if it is correct:

Code: [Select]
 
type 
  //enumeration used in variant record
  BytePos = (EndVal, ByteVal);

  PEndianCnvRec = ^EndianCnvRec;

  EndianCnvRec = packed record
    case pos: BytePos of
       //The value we are trying to convert
      EndVal: (EndianVal: single);
       //Overlapping bytes of the single
      ByteVal: (Bytes: array[0..SizeOf(single)-1] of byte);
  end;

//A gets B's values swapped
procedure SwapBytes( A, B: PEndianCnvRec );
var
  i: integer;
begin
  for i := high(A.Bytes) downto low(A.Bytes) do
    A.Bytes[i] := B.Bytes[High(A.Bytes) - i];
end;

procedure DSPFloatBE(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
var
  a,b: EndianCnvRec;
  src : array of Single;
  dst : array of Single;
  NumSamples, I : Integer;
begin
  SetLength(src, length);
  SetLength(dst, length);

  move(buffer^, src[0], length);

  NumSamples := Length div (32 div 8);  // 32 bit float div 8
  for I := 0 to NumSamples - 1 do
    begin
      a.EndianVal := src[I];
      SwapBytes(@b, @a);
      dst[I] := b.EndianVal;
    end;

  Form1.DSPFile.Write(dst[0], length);
end;
« Last Edit: 3 Jun '10 - 22:34 by The Mask »

Ian @ un4seen

  • Administrator
  • Posts: 21024
Here is my implementation of the float to 24 bit conversion (little-endian):

Code: [Select]
procedure DSP24bitLE(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
type
  T24BitSample = record
    a0 : Byte;
    a1 : Byte;
    a2 : ShortInt;
  end;

That doesn't look quite right, shouldn't "a2" be "Byte" too? The record should probably also be "packed".

The rest seems fine :)

The Mask

  • Posts: 97
Re: BassEnc EncodeProc doesn't work with BASS_ENCODE_PCM
« Reply #10 on: 5 Jun '10 - 10:26 »
I don't know Ian,

What is Byte in C/C++? is it unsigned or signed?

Byte in Delphi is has a range of 0..255 and is unsigned 8-bit. Shortint has a range of -128..127 and is signed 8-bit.

What should it be, Byte or Shortint?

Ian @ un4seen

  • Administrator
  • Posts: 21024
Re: BassEnc EncodeProc doesn't work with BASS_ENCODE_PCM
« Reply #11 on: 7 Jun '10 - 15:05 »
Oops, I got "ShortInt" and "SmallInt" mixed-up; I thought it was 16-bit, like the C/C++ "short" :)

Either of "Byte" or "ShortInt" should work identically then; signage shouldn't really matter if the structure is just for storage/containment purposes, eg. you're not using the values in calculations.

"BYTE" is unsigned in C/C++.

The Mask

  • Posts: 97
Re: BassEnc EncodeProc doesn't work with BASS_ENCODE_PCM
« Reply #12 on: 7 Jun '10 - 15:16 »
I will use Byte then.

Thanks for your help.  :)

Wishmaster

  • Posts: 162
Re: BassEnc EncodeProc doesn't work with BASS_ENCODE_PCM
« Reply #13 on: 12 Aug '10 - 08:46 »
Hi

I tried to convert it to 8 bit Integer,  but it still does not sound right, can you see my mistake?

Code: [Select]

function Clip_8(Value: integer): ShortInt;
asm
        cmp       eax, 127
        jle       @@Lower
        mov       al, 127
        ret
@@Lower:
        cmp       eax, -128
        jge       @@Finished
        mov       al, -128
@@Finished:
end;



procedure DSP32f_8bit(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: Pointer); stdcall;
var
  src : array of Single;
  dst : array of Byte;
  i : Integer;
  SaveValue : ShortInt;
begin
  SetLength(src, length);
  SetLength(dst, length * 8 div 32);

  move(buffer^, src[0], length);

  for I := 0 to (length div (32 div 8)) do
    begin
      // convert to integer
      SaveValue := Trunc(src[I] * 128) + 127;
      // clip it
        SaveValue := Clip_8(SaveValue);
      // store it (native endian)
      dst[I] := SmallInt(SaveValue);
    end;

FMACEncoder.Encoder_Write(@dst[0], length * 8 div 32);


The Mask

  • Posts: 97
Re: BassEnc EncodeProc doesn't work with BASS_ENCODE_PCM
« Reply #14 on: 12 Aug '10 - 20:44 »
Try the following:

Code: [Select]
  for I := 0 to (length div (32 div 8)) do
    begin
      dst[i] := Clip_8(Round(src[i] * 256)) + 128;
    end;

Wishmaster

  • Posts: 162
Re: BassEnc EncodeProc doesn't work with BASS_ENCODE_PCM
« Reply #15 on: 12 Aug '10 - 23:35 »
Hi

thx. But i had already tried that and it is even worse than before. I just get noise  

never mind, it works!  I checked again the encoder and found  also a small error


Thx.
« Last Edit: 13 Aug '10 - 02:04 by Wishmaster »