Lazarus

Programming => General => Topic started by: AnthonyTekatch on March 27, 2018, 02:01:57 pm

Title: fpwavwriter or creating a .wav file
Post by: AnthonyTekatch on March 27, 2018, 02:01:57 pm
I would like to create a .wav file from my own signal formula.

I could not find any documentation, examples, or tutorial on how to use fpwavwriter.pas from here: https://github.com/graemeg/freepascal/tree/master/packages/fcl-sound/src

or is there another documented method to create a .wav file ?
Title: Re: fpwavwriter or creating a .wav file
Post by: taazz on March 27, 2018, 02:52:47 pm
something along the lines of
Code: Pascal  [Select][+][-]
  1. function SaveToWav(const aFilename:String; const aBuffer; const aBufferSize; aBitsPerSample, aSampleRate, aChannelCount :Integer):Boolean;
  2. var
  3.   vTmp :TWavWriter;
  4. begin
  5.   Result := False;
  6.   vTmp := TWavWriter.Create;
  7.   try
  8.     vTmp.StoreToFile(aFilename);
  9.     vTmp.fmt.BitsPerSample := aBitsPerSample;
  10.     vTmp.fmt.SampleRate    := aSampleRate;
  11.     vTmp.fmt.Channels      := aChannelCount;
  12.     vTmp.WriteBuf(aBuffer, aBufferSize);
  13.     vTmp.FlushHeader;
  14.     Result := true;
  15.   finally
  16.     vTmp.Free;
  17.   end;
  18. end;
  19.  
There is a writeln in the writebuffer method that needs to be deleted to work properly on windows. ee
Code: Pascal  [Select][+][-]
  1. function TWavWriter.WriteBuf(var Buffer; BufferSize: Integer): Integer;
  2. var
  3.   sz: Integer;
  4. begin
  5.   //WriteLn('[TWavWriter.WriteBuf] BufferSize = ', BufferSize); <-- comment this line its a debug pollution.
  6.   Result := 0;
  7.   with fStream do begin
  8.     sz := Write(Buffer, BufferSize);
  9.     if sz < 0 then Exit;
  10.     Inc(Result, sz);
  11.   end;
  12. end;
  13.  
Title: Re: fpwavwriter or creating a .wav file
Post by: AnthonyTekatch on March 27, 2018, 08:03:55 pm
Thank you.

What should the buffer look like?

I think it should be in bytes according to the fstream.write definition  (https://www.freepascal.org/docs-html/rtl/classes/tstream.write.html), but an aBitsPerSample of 16 or 32 messes things up. And, aBufferSize must be in bytes even if the wav data is 16 or 32 bits per sample.

Your example throws these errors in FPC 3.04:
Error: Can't assign values to const variable
Error: Incompatible type for arg no. 2: Got "<Formal type>", expected "LongInt"

I think the SaveToWav function parameter definitions should match the parameter types of the TWavWriter parameters as follows:

Code: Pascal  [Select][+][-]
  1. function SaveToWav(const aFilename:String; var aBuffer; aBufferSize:LongInt; aBitsPerSample, aSampleRate, aChannelCount :Integer):Boolean;
Title: Re: fpwavwriter or creating a .wav file
Post by: wp on March 27, 2018, 08:13:23 pm
In the attachment, there's my wavegenerator. I did not know of the fpwavwriter those days and used my own wav routines. Also, fpwavwriter is not very helpful because it does not tell what the buffer should look like.
Title: Re: fpwavwriter or creating a .wav file
Post by: AnthonyTekatch on March 27, 2018, 08:53:33 pm
In the attachment, there's my wavegenerator. I did not know of the fpwavwriter those days and used my own wav routines. Also, fpwavwriter is not very helpful because it does not tell what the buffer should look like.

Ah, thank you for that!

It works, and is well commented. I will be able to use it.
Title: Re: fpwavwriter or creating a .wav file
Post by: jipété on November 30, 2022, 05:47:19 pm
Hello,
In the attachment, there's my wavegenerator. I did not know of the fpwavwriter those days and used my own wav routines.
Near 5 years later, I've found something strange, using your tool :
if I create 2 identical files except for the frequency and with 2 channels and a length of 2 seconds, if I try to concatenate them using sox like that :
sox file1.wav file2.wav output.wav, I get two times the line :
"WARN wav: premature EOF on wav imput file"

So I looked more closely with sox --i file1.wav :
Code: Pascal  [Select][+][-]
  1. $ sox --i file1.wav   352844 octets
  2. Duration       : 00:00:16.00 = 705600 samples = 1200 CDDA sectors --> WRONG !
  3. File Size      : 353k OK
  4. Bit Rate       : 176k --> WRONG !
Same bad result with file2.wav.
 
But the output.wav is OK :
Code: Pascal  [Select][+][-]
  1. $ sox --i output.wav   705644 octets
  2. Duration       : 00:00:04.00 = 176400 samples = 300 CDDA sectors OK
  3. File Size      : 706k OK
  4. Bit Rate       : 1.41M

For me, the problem is solved using the following changes at the bottom of the procedure WriteWavStream :
Code: Pascal  [Select][+][-]
  1.   // Complete missing header data
  2.   AStream.Position := p - 4;
  3. //AStream.WriteDWord(n * numch * BITS_PER_SAMPLE);  // Size of data part
  4.   // added "div 8" et no more problem with sox
  5.   AStream.WriteDWord(n * numch * BITS_PER_SAMPLE div 8);  // Size of data part
  6.   AStream.Position := 4;
  7.   AStream.WriteDWord(AStream.Size - 8);  // File size - RIFF chunk size
  8. // look at http://soundfile.sapp.org/doc/WaveFormat/ : Subchunk2Size (Size of data part) == NumSamples * NumChannels * BitsPerSample/8

Creating a new file with "div 8" gives the following good data :
Code: Pascal  [Select][+][-]
  1. $ sox --i goodfile.wav
  2. Duration       : 00:00:02.00 = 88200 samples = 150 CDDA sectors
  3. File Size      : 353k
  4. Bit Rate       : 1.41M

A last word : the Audacious player cannot play (don't remember error message) a file created without "div 8" and plays it perfectly if created with "div 8".

Thanks for your work, best regards from South of France,
--
jp
Title: Re: fpwavwriter or creating a .wav file
Post by: wp on November 30, 2022, 07:49:21 pm
Yes, of course. "Size of data part" means bytes, and the div 8 converts "BITS_PER_SAMPLE" to "BYTES_PER_SAMPLE". Thanks for notifying me.
TinyPortal © 2005-2018