Solved

Creating an empty wave file...

Posted on 1998-08-10
11
724 Views
Last Modified: 2010-04-03
Hi

I am interested to know how to create a blank wave file with just the header. I can do this using Sound Recorder but how do i do it programatically, ie writing the wave header to a new file.

Thanks
keith
0
Comment
Question by:keithcsl
  • 5
  • 2
  • 2
  • +2
11 Comments
 
LVL 4

Expert Comment

by:erajoj
Comment Utility
Hi,
What kind of formats do you want to support (except RIFF)?

/// John
0
 
LVL 3

Expert Comment

by:Matvey
Comment Utility
0
 
LVL 8

Expert Comment

by:ZifNab
Comment Utility
read this, and then I'm sure you know how to make it :

Playing and Recording Sound in Delphi

by Dr Darryl Gove - D.J.Gove@open.ac.uk

http://www.maths.soton.ac.uk/~djg/djgmain.htm

Recently I was wondering about how to play and record sound, up until then my experience had been with using the TMediaPlayer to play a
WAV or a .AVI file. Its a nice component, but I was thinking perhaps I could record sound in my own format and play it back. Id looked at the
Windows documentation and only found the play sound command - not very useful, however tucked away in the Delphi/Bin directory there is a
file called mmsystem.hlp; the file tells you everything you need to know, or at least it does if you already know what youre doing. I didnt, but I
was fortunate enough to find a web-site ( http://www.relisoft.com/recorder.html) which had the code for a sound recorder in C++, this gave me
sufficient details to get the program working.

Now, I use Delphi 1.0, so thats my excuse for avoiding multi-threaded applications. Ill leave the translation as an exercise to the reader.

Actually, when it comes down to it, playing and recording sound is very similar, so rather than write a long article treating both separately, Im
going to write about both at the same time - and hope that Im clear enough to make it easy for you to follow.

Do you have a soundcard?

The first thing that youll need to do when writing an audio application is to handle the possibility that the computer the application is running on
does not have a soundcard.

To do this we need to use the calls WaveOutGetNumDevs and WaveInGetNumDevs - they return the number of audio playing devices (Out)
and the number of recording devices (In). Most of the time there will be one of each - one soundcard.

if WaveOutGetNumDevs=0 then application.messagebox('Error', 'No sound playing card', mb_OK);
if waveInGetNumDevs=0 then application.messagebox('Error','No recording sound card',mb_ok);


The best place for this code would probably be in the On Create handler for the form, however before you compile the code, make sure that you
have included mmsystem in the uses list.

What kind of sound do you want?

You should be aware that there is a variety of options for sound quality - whether it is mono or stereo, 8 or 16 bit, and the sampling frequency.
You need to ask the soundcard whether it supports the format.

The basic wave format information is handled by the TWaveFormat block

TWaveFormat = record
    wFormatTag: Word;         {format type}
    nChannels: Word;          {number of channels 1 for mono 2 for stereo}
    nSamplesPerSec: Longint;  {sample rate}
    nAvgBytesPerSec: Longint; {number of bytes per second recorded}
    nBlockAlign: Word;        {size of a single sample}
  end;


However, you wont directly use this, since you need to use a wrapper which relates to the particular format that you want to store the data in -
basically you dont only ask Do you support this sample rate etc? but ask Do you support this way of saving the data?. The only really supported
format is PCM, but potentially there could be other formats supported by the multimedia subsystem, and you as a programmer would not need
to worry about them.

In order to ask about PCM data, you need to use the TPCMWaveFormat block:

TPCMWaveFormat = record
   wf: TWaveFormat;
   wBitsPerSample: Word;
end;


Which is just the TWaveFormat block with an additional word telling the computer the bits per sample (8,16, or 32). Each sample (the number
of samples per second is the sampling frequency) is either 8 bit or 16 bit, and either stereo or mono - so the smallest size is 8 bit mono or 8 bits
per sample, and the largest size is 16 bit stereo or 32 bits per sample. This calculation follows directly from the data you specify in the
TWaveFormat block - so besides questions of why do you need to tell the computer again, well have a look at setting up the TWaveFormat
block:

WaveFormat:=new(PPCMwaveFormat);
with WaveFormat^.wf do
  begin
    WFormatTag := WAVE_FORMAT_PCM; {PCM format - the only option!}
    NChannels:=1; {mono}
    NSamplesPerSec:=11000; {11kHz sampling}
    NAvgBytesPerSec:=11000; {we aim to use 8 bit sound so only 11k per second}
    NBlockAlign:=1; {only one byte in each sample}
    waveformat^.wBitsPerSample:=8; {8 bits in each sample}
  end;


So weve set up the type of audio we want to record, the next thing to do is to ask the soundcard if it can do it.

i:=waveOutOpen(nil,0,PWaveFormat(WaveFormat),0,0,WAVE_FORMAT_QUERY);
if i<>0 then
  application.messagebox('Error', 'Play format not supported', mb_OK);

i:=waveInOpen(nil,0,PWaveFormat(WaveFormat),0,0,WAVE_FORMAT_QUERY);
if i<>0 then
  application.messagebox('Error', 'Record format not supported', mb_OK);


Getting a handle on it.

Like most things in Windows, we end up referring to the soundcard using a handle; we need one handle to record and one to playback.

Having set up our WaveFormat block, we can ask for a handle to a device that can either play or record that format.

HwaveOut:=new(PHwaveOut);
i:=waveOutOpen(HWaveOut,0,Pwaveformat(WaveFormat),form1.handle,0,CALLBACK_WINDOW);
if i<>0 then
  application.messagebox('Error', 'Problem creating play handle', mb_OK);

HwaveIn:=new(PHwaveIn);
i:=waveInOpen(HWaveIn,0,Pwaveformat(WaveFormat),form1.handle,0,CALLBACK_WINDOW);
if i<>0 then
  application.messagebox('Error', 'Problem creating record handle', mb_OK);


In this instance, were going to use the messages to handle the playback and recording of audio. We could use a callback function. To use
messages, we need to pass the handle of a window that will receive the messages, and the CALLBACK_WINDOW value to tell the multimedia
subsystem that were passing a handle to it.

Being prepared

The final thing to do is to start either playing sound or recording sound. To do this we need to send packets of memory to the sound card either
to play or to record on.

When you send data out to be played, the playing starts immediately you add a packet of data, extra packets of data are added to a queue, and
played in sequence. If youre recording then the blocks of memory are once again added to a queue - but they are not recorded on until you tell
the computer to start recording. If the computer runs out of packets to record on then the recording stops.

So the first thing to do is to get a block of memory and to set up the data block that will tell the multimedia subsystem about it.

Tmemblock=array[0..memblocklength] of byte;
memBlock:=new(PmemBlock);


Nows a good time to put your audio data into the memory block - if youre playing audio.

Header:=new(PwaveHdr);
with header^ do
  begin
    lpdata:=pointer(memBlock);
    dwbufferlength:=memblocklength;
    dwbytesrecorded:=0;
    dwUser:=0;
    dwflags:=0;
    dwloops:=0;
  end;


Except for setting the pointer to a block of memory, and the length of the block of memory, all the other fields should be set to zero - unless you
want to play the same block of data multiple times.

The next step is to prepare the data, why this is necessary, I dont know!

i:=waveOutPrepareHeader(HWaveOut^,Header,sizeof(TWavehdr));
if i<>0 then
  application.messagebox('Out Prepare error','error',mb_ok);

i:=waveInPrepareHeader(HWaveIn^,Header,sizeof(TWavehdr));
if i<>0 then
  application.messagebox('In Prepare error','error',mb_ok);


Then we need to send the new block of data to the audio device - either to be played or to be recorded on.

i:=waveOutWrite(HWaveOut^,Header,sizeof(TWaveHdr));
if i<>0 then
  application.messagebox('Wave out error','error',mb_ok);

i:=waveInAddBuffer(HWaveIn^,Header,sizeof(TWaveHdr));
if i<>0 then
  application.messagebox('Add buffer error','error',mb_ok);


The final thing to do when recording sound is to start!

i:=waveInStart(HwaveIn^);
if i<>0 then
   application.messagebox('Start error','error',mb_ok);


There are also commands to stop and pause recording.

Messages

If youre using messages to control the recording an playback of audio, then you need to have some handlers for the messages. The handlers
should be something like

procedure

 MMOutOpen(var msg: Tmessage); message MM_WOM_OPEN;
procedure MMOutClose(var msg: Tmessage); message MM_WOM_CLOSE;
procedure MMOutDone(var msg: Tmessage); message MM_WOM_DONE;
procedure MMInOpen(var msg: Tmessage); message MM_WIM_OPEN;
procedure MMInClose(var msg: Tmessage); message MM_WIM_CLOSE;
procedure MMInDone(var msg: Tmessage); message MM_WIM_DATA;


WOM messages are send by audio out devices, and WIM messages are sent by audio in devices.

The open and close messages are sent when the device is either opened or closed (closing is covered in the next section) - these are not really
very useful messages to trap. The important messages are the DONE and DATA.

MM_WOM_DONE tells you that the block of data that you were playing has been played, and you should now get rid of it.
MM_WIM_DATA tells you that the block of data has been recorded on and you should now deal with it as appropriate. For both messages,
youll probably want to send some more data to the audio device.

The first thing to do with your returned data is to unprepare it, a pointer to the header that identifies the block is passed as the lparam of the
message.

Header:=PWaveHdr(msg.lparam);
i:=waveOutUnPrepareHeader(HWaveOut^,Header,sizeof(TWavehdr));
if i<>0 then
  application.messagebox('Out Un Prepare error','error',mb_ok);

Header:=PWaveHdr(msg.lparam);
i:=waveInUnPrepareHeader(HWaveIn^,Header,sizeof(TWavehdr));
if i<>0 then
  application.messagebox('In Un Prepare error','error',mb_ok);


You can then do as you will with the block of data, but remember to dispose of any memory that you dont want any more.

dispose(Header^.lpdata);
dispose(Header);


Disposing of the evidence.

Once weve finished with the soundcard, we need to get rid of the handle to audio device. Before we can do that, we need to reset the device so
that any unused buffers are returned to the application for disposal.

if HWaveOut<>nil then WaveOutReset(HWaveOut^);
if HwaveOut<>nil then WaveOutClose(HWaveOut^);

if HwaveIn<>nil then WaveInReset(HWaveIn^);
if HwaveIn<>nil then WaveInClose(HWaveIn^);


Notice that the multimedia subsystem sometimes requires a pointer to a handler, and sometimes a handler - just one of those things.

A problem youll find if you try this code, is that the reset is asynchronous so you reset the audio device, and close it - then your handler gets
called with a block of data that needs to be unprepared, of course the handle you had is invalid now, and so you get a GPF if you use it (well you
dont seem to but Im not prepared to take that kind of a risk). So to get around this count the number of packets sent to the audio device, and
only execute the close when there are no remaining packets - you can do this in the handler that deals with returned packets of data.

The example program

The listing is a complete program for recording audio and playing it directly back to the speaker - an echo! To use it make a blank form, with an
On Create and a Close Query handler, then replace the entire unit with the code in the listing. Its written for Delphi 1, but I would expect it to
work with Delphi 2.

Note: The code given uses a sampling rate of 11,000 samples per second; not all sound cards can support this rate. If your sound card does not
support it, then you will need to adjust the rate to 11,025 samples per second, which should be supported.

Conclusion

Using the sound card at a low level to record and playback audio, feels like it should be really complex, but in fact it isnt thats the power of the
Windows API. However there are a number of quirks in it, but its fun!

Zif.
0
 
LVL 3

Expert Comment

by:Matvey
Comment Utility
Easy killer! Zif, it's rather simple...

The simplest way to create an empty PCM 8 bit mono file:

const
emptyfile: Array[1..58] of Char = (
  #82, #73, #70, #70, #50, #0, #0, #0,
  #87, #65, #86, #69, #102, #109, #116,
  #32, #18, #0, #0, #0, #1, #0, #1, #0,
  #64, #31, #0, #0, #64, #31, #0, #0, #1,
  #0, #8, #0, #0, #0, #102, #97, #99,
  #116, #4, #0, #0, #0, #0, #0, #0, #0,
  #100, #97, #116, #97, #0, #0, #0, #0);
var
  f: file;
begin
  assignfile(f, 'D:\Empty1.wav');
  Rewrite(f, 1);
  BlockWrite(f, emptyfile, SizeOf(emptyfile));
  Closefile(f);
end;  

Post specifics (like 8-16 bit, stereo-mono...), and I'll post an exact emptyfile array...

c u, Matvey
0
 
LVL 10

Expert Comment

by:viktornet
Comment Utility
Here is an easy way to create an empty .wav file.,,,,

var WavFile : TFileStream;
begin
   WavFile := TFileStream.Create('C:\windows\desktop\Empty.wav',fmCreate);
   WavFile.free;
end;
0
Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

 
LVL 8

Expert Comment

by:ZifNab
Comment Utility
yep, that should be a way too. Much better then my idea, i must say ...
0
 
LVL 3

Expert Comment

by:Matvey
Comment Utility
Cool approach! ;-)

"I am interested to know how to create a blank wave file with just the HEADER..."
0
 
LVL 1

Author Comment

by:keithcsl
Comment Utility
I am sorry viktornet, but i wanted to know how to create the wave file with just the header in it, not a empty file..

Matvey, I think your appraoch of hard coding the header in my program is the best. To get the characters, all i need to do it to create one using Sound Recorder and read the bytes huh? yeah, i think that's good enough for me...

could you please post your comment as an answer??

thanks
Keith

0
 
LVL 3

Accepted Solution

by:
Matvey earned 50 total points
Comment Utility
Yup, you guessed right, these were my thoughs. This way is only useful for programs that don't really mess up with file structures, and just use this pice once and forget about it. You want to record sound to this file later or something, right?
Whatever, but if you want to deal later with the file yourself and not through MCI, then you'll need some more universal approach. You could use the ACM, or not use it and just learn the file structure or something. You could do the same thing using a header record (I can even post the specifics), and some record type for data strypes, and then write them to the file, but if you just need to write a very curtain file, like an empty one for example, so it will be just more code for nothing...

All the best, --Matvey
0
 
LVL 1

Author Comment

by:keithcsl
Comment Utility
Thanks Matvey for your comments.

At the moment, I am just wanting a 8000 Hz, 16 bit mono PCM empty file for recording voice from the telephone. yes, i will explore the option of other formats and also play around with the header record method (could you post an example if possible??) to examine all the possibilities i can use...

Thanks
Keith
0
 
LVL 3

Expert Comment

by:Matvey
Comment Utility
The header record is something like this:

  TWAVHDR = record
    riff: array[1..4] of char;    //* Must be "RIFF" */
    length: longint;         //* Total length of file */
    wavefmt: array[1..8] of char;     //* Must be "WAVEfmt " */
    hdr_len: longint;        //* length of header */
    style: integer;           //* Style: 1=unsigned */
    channels: Integer;        //* channels: mono or stereo */
    rate: Longint;           //* samples per second */
    bytesps: LongInt;        //* average bytes per second */
    align: Integer;           //* bytes per clock tick */
    size: Integer;            //* bits per sample */
  end;

If you write just this record to a file (it will be an empty file with only the header), you won't get a real wav file. It'll be something like corrupt. You have to add something else to it beyond the header area.
Please examin these format descriptions to understand the format correctly:

http://www.wotsit.org/wmusic/wav.zip
http://www.wotsit.org/wmusic/wave.zip
http://www.wotsit.org/wmusic/wavecomp.zip
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
It is a freely distributed piece of software for such tasks as photo retouching, image composition and image authoring. It works on many operating systems, in many languages.
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…

763 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now