Audio Recording with OpenAl
BlitzMax Forums/BlitzMax Tutorials/Audio Recording with OpenAl
| ||
In this tutorial you will learn to use the AUDIO-IN port of your computer. After this you are able to record sounds from microphone or also grab sounds from other applications and how to save it as a WAV-File. Lesson I: Preparation of OpenAl This line tests, whether OpenAl is already installed on your computer: If OpenALInstalled()=False Then RuntimeError "Please install OpenAl" EndIf OpenAl is avaiable for free on Creative Laps homepage: OpenAl-Homepage: http://connect.creativelabs.com/openal/default.aspx Download-Link: http://connect.creativelabs.com/openal/Downloads/oalinst.zip The ZIP-file only contains the installer, which installs the OpenAl32.dll. After the installation OpenAl can be used immediately. For OSX you need no installation, because OpenAl is already part of the system The next lines select OpenAl as the driver: EnableOpenALAudio() SetAudioDriver("OpenAL") The next step is to open the device to call OpenAl later: Global Device% Device=alcOpenDevice(Null) If Device=Null Then RuntimeError "No Access to Open-AL-Device possible" EndIf Inside OpenAl there is another "special device" for recording. Now we test, whether the computer is able to useOpenAl Recording: Global CaptureDevice% If (alcIsExtensionPresent(Device, "ALC_EXT_CAPTURE") = AL_FALSE) Then RuntimeError "No Recording possible!" EndIf CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, 44100*2*1*5); If Not(CaptureDevice) Then RuntimeError "No Access to the Open-AL-CAPTURE-Device" EndIf Now the OpenAL-Recording device is opened. Therefore we had to pass 5 parameters: Null=always Null 44100=sampling rate. 44.100 mean 44.100 samples per second (CD quality). You can also use 22050. Or you may use 11025, but you can hear the loose of quality at trebles. AL_FORMAT_MONO16=A special constant descripes two further quality characteristics. Here: a Mono-Recording on one track, also avaiable STEREO. The second part is 16bit resolution (2 bytes per sample). As an alternative you may use 8bit (1Byte per sample) 44100*2*1*5 defines the size of the ring buffer. rule of thumb: Sampling-Rate * Resolution * Trackse * Seconds With this parameter OpenAl creates a ring buffer in which the incoming datas will flew. The insertion of the data runs later in the background completely not depending on your application in a second thread. the samples will reach the buffer continuously, even if you program or other applications slowdown the computer. This guarantees that there will be no dropouts in the audio material. OpenAl analyses the Audio In port and saves all events into the ring buffer. Every sample has the same size of 1 (MONO8), 2 (MONO16 or STEREO8) or 4 bytes (STEREO16). As long as the buffer is not filled, OpenAL will fill it. If it might overflow older samples will be overwritten. You can imagine this buffer like a ring, where the writting pointer write the datas running in the circle. The reading pointer alway runs behind the writting pointer. If the writting pointer is faster than the reading pointer, it overruns it, but it will nir write datas outside of the ring. So you dont get any overflow. In lesson II you will learn how to fetch datas from the ring. But now it is very important to learn, how to close both devices, before quit your application: alcCloseDevice(CaptureDevice) alcCloseDevice(Device) Now here is the complete code for start and end of the OpenAL-Recording engine: If OpenALInstalled()=False Then RuntimeError "Please install OpenAL!" EndIf EnableOpenALAudio() SetAudioDriver("OpenAL") Global Device% Device=alcOpenDevice(Null) If Device=Null Then RuntimeError "No Access to Open-AL-Device possible!" EndIf Global CaptureDevice% If (alcIsExtensionPresent(Device, "ALC_EXT_CAPTURE") = AL_FALSE) Then RuntimeError "No Recording possible!" EndIf CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, 44100*2*1*5); If Not(CaptureDevice) Then RuntimeError "No Access to the Open-AL-CAPTURE-Device" EndIf '-------------------------------------------- ' Main-Recording Loop here: ........ '-------------------------------------------- alcCloseDevice(CaptureDevice) alcCloseDevice(Device) End Last edited 2011 |
| ||
Lesson II: Grab the Samples The device is now prepared for the recording. But is still does not work, the ring buffer is reserved, but no datas are filled in. So we start the background process of writting into the ring buffer: alcCaptureStart(CaptureDevice) And this is how to stop it again: alcCaptureStop(CaptureDevice) Now we reach the most important part. We ask OpenAL, how many samples we can fetch: Global Number% alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Number)) DebugLog Number + " Samples avaiable" Therefor we pass a pointer to a 4 byte Integer variable, here Number%. After the function call Number contains the number of avaiable samples In a last Step we fetch the samples by let them copy into a BMax TAudioSample-Type Global TestSample:TAudioSample=CreateAudioSample(44100*5*2 , 44100 , SF_MONO16LE) alcCaptureSamples(CaptureDevice, TestSample.Samples, Number) The pointer TestSample.Samples points to the first Byte of the TAudioSample memory. Now we can play the sound: Global Music:TSound=LoadSound(TestSample) PlaySound Music Here is the complete recording loop for a 2 second recording: '-------------------------------------------- ' Main-Recording Schleife: alcCaptureStart(CaptureDevice) Delay 2*1000 alcCaptureStop(CaptureDevice) Global Number% alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Number)) DebugLog Number + " Samples avaiable" Global TestSample:TAudioSample=CreateAudioSample(44100*5*2 , 44100 , SF_MONO16LE) alcCaptureSamples(CaptureDevice, TestSample.Samples, Number) Global Music:TSound=LoadSound(TestSample) PlaySound Music '-------------------------------------------- |
| ||
Lesson III: Continuous Fetching In the last lesson you learned how to fetch a single piece of recorded audio. Of course it is possible to fetch the datas from the ring buffer while it is running. The code of lesson I keeps valid. We only change the recoring loop code: alcCaptureStart(CaptureDevice) Global Number%, Sum%, RecTime% Global Tracks%=1 Global Resolution%=2 Global WholeMemorySpace%= 44100 * Tracks * Resolution% * 240 Global TestSample:TAudioSample=CreateAudioSample(WholeMemorySpace, 44100 , SF_MONO16LE) We need some new variables: Tracks% contains 1 for MONO or 2 for STEREO Resolution% contains 1 for 8bit or 2 for 16bit Sum% will contain the amount memory already used for recording And we create a TAudoSample, which is big enough for 240 seconds of recording We call the main function every 500msec: Repeat If RecTime<MilliSecs() RecTime=MilliSecs() +500 alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Number)) .... and do not pass the beginning of the TAudioSample-Memory, but add the bytes already fetched: ..... If (Sum + (Number*Tracks*Resolution)) < WholeMemorySpace Then alcCaptureSamples(CaptureDevice, TestSample.Samples+Sum, Number) Sum= Sum + (Number * Tracks* Resolution) EndIf EndIf .... It is very important, that you do not add the amount of samples, but the number of bytes. Because a sample can have the size of 1, 2 or 4 Bytes, you have to calculate the sum with regard of resolution and tracks. IMPORTANT: TAKE CARE ABOUT THE SUM +NEW BYTES IS NEVER BIGGER THAN THE TAUDIOSAMPLE SIZE. Here is the code for the whole recording application. You can record with <R>, stop with <S> and Play with <P> and switch as often as you want: If OpenALInstalled()=False Then RuntimeError "Please install OpenAL!" EndIf EnableOpenALAudio() SetAudioDriver("OpenAL") Global Device% Device=alcOpenDevice(Null) If Device=Null Then RuntimeError "No Access to Open-AL-Device possible!" EndIf Global CaptureDevice% If (alcIsExtensionPresent(Device, "ALC_EXT_CAPTURE") = AL_FALSE) Then RuntimeError "No Recording possible!" EndIf CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, 44100*2*1*5); If Not(CaptureDevice) Then RuntimeError "No Access to the Open-AL-CAPTURE-Device" EndIf alcCaptureStart(CaptureDevice) Global Number%, Sum%, RecTime%, RecordNow%=1 Global Tracks%=1 Global Resolution%=2 Global WholeMemorySpace%= 44100 * Tracks * Resolution% * 240 Global TestSample:TAudioSample=CreateAudioSample(WholeMemorySpace, 44100 , SF_MONO16LE) '-------------------------------------------- ' Main-Recording Loop here: Repeat If RecTime<MilliSecs() RecTime=MilliSecs() +500 alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Number)) If (Sum + (Number*Tracks*Resolution)) < WholeMemorySpace Then alcCaptureSamples(CaptureDevice, TestSample.Samples+Sum, Number) Sum= Sum + (Number * Tracks* Resolution) EndIf EndIf If KeyHit(Key_R) Then If RecordNow=0 RecordNow=1 alcCaptureStart(CaptureDevice) Sum=0 EndIf Else If KeyHit(Key_P) Then If RecordNow=0 Music = LoadSound(TestSample) PlaySound Music EndIf Else If KeyHit(Key_S) Then If RecordNow=1 RecordNow=0 alcCaptureStop(CaptureDevice) EndIf EndIf ' Draw Graphics , etc... Flip 0 Until KeyHit(Key_Escape) '-------------------------------------------- alcCloseDevice(CaptureDevice) alcCloseDevice(Device) End Last edited 2011 |
| ||
Lesson III: Continuous Fetching In the last lesson you learned how to fetch a single piece of recorded audio. Of course it is possible to fetch the datas from the ring buffer while it is running. The code of lesson I keeps valid. We only change the recoring loop code: alcCaptureStart(CaptureDevice) Global Number%, Sum%, RecTime% Global Tracks%=1 Global Resolution%=2 Global WholeMemorySpace%= 44100 * Tracks * Resolution% * 240 Global TestSample:TAudioSample=CreateAudioSample(WholeMemorySpace, 44100 , SF_MONO16LE) We need some new variables: Tracks% contains 1 for MONO or 2 for STEREO Resolution% contains 1 for 8bit or 2 for 16bit Sum% will contain the amount memory already used for recording And we create a TAudoSample, which is big enough for 240 seconds of recording We call the main function every 500msec: Repeat If RecTime<MilliSecs() RecTime=MilliSecs() +500 alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Number)) .... and do not pass the beginning of the TAudioSample-Memory, but add the bytes already fetched: ..... If (Sum + (Number*Tracks*Resolution)) < WholeMemorySpace Then alcCaptureSamples(CaptureDevice, TestSample.Samples+Sum, Number) Sum= Sum + (Number * Tracks* Resolution) EndIf EndIf .... It is very important, that you do not add the amount of samples, but the number of bytes. Because a sample can have the size of 1, 2 or 4 Bytes, you have to calculate the sum with regard of resolution and tracks. IMPORTANT: TAKE CARE ABOUT THE SUM +NEW BYTES IS NEVER BIGGER THAN THE TAUDIOSAMPLE SIZE. Here is the code for the whole recording application. You can record with <R>, stop with <S> and Play with <P> and switch as often as you want: If OpenALInstalled()=False Then RuntimeError "Please install OpenAL!" EndIf EnableOpenALAudio() SetAudioDriver("OpenAL") Global Device% Device=alcOpenDevice(Null) If Device=Null Then RuntimeError "No Access to Open-AL-Device possible!" EndIf Global CaptureDevice% If (alcIsExtensionPresent(Device, "ALC_EXT_CAPTURE") = AL_FALSE) Then RuntimeError "No Recording possible!" EndIf CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, 44100*2*1*5); If Not(CaptureDevice) Then RuntimeError "No Access to the Open-AL-CAPTURE-Device" EndIf alcCaptureStart(CaptureDevice) Global Number%, Sum%, RecTime%, RecordNow%=1 Global Tracks%=1 Global Resolution%=2 Global WholeMemorySpace%= 44100 * Tracks * Resolution% * 240 Global TestSample:TAudioSample=CreateAudioSample(WholeMemorySpace, 44100 , SF_MONO16LE) '-------------------------------------------- ' Main-Recording Loop here: Repeat If RecTime<MilliSecs() RecTime=MilliSecs() +500 alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Number)) If (Sum + (Number*Tracks*Resolution)) < WholeMemorySpace Then alcCaptureSamples(CaptureDevice, TestSample.Samples+Sum, Number) Sum= Sum + (Number * Tracks* Resolution) EndIf EndIf If KeyHit(Key_R) Then If RecordNow=0 RecordNow=1 alcCaptureStart(CaptureDevice) Sum=0 EndIf Else If KeyHit(Key_P) Then If RecordNow=0 Music = LoadSound(TestSample) PlaySound Music EndIf Else If KeyHit(Key_S) Then If RecordNow=1 RecordNow=0 alcCaptureStop(CaptureDevice) EndIf EndIf ' Draw Graphics , etc... Flip 0 Until KeyHit(Key_Escape) '-------------------------------------------- alcCloseDevice(CaptureDevice) alcCloseDevice(Device) End Last edited 2011 |
| ||
Great tutorial !! There´s just a small typo in the example. alcCaptureSamples(CaptureDevice, testsample.Samples + Sum, Number) should be alcCaptureSamples(CaptureDevice, Klang.Samples + Sum, Number) If OpenALInstalled()=False Then RuntimeError "Please install OpenAL!" EndIf EnableOpenALAudio() SetAudioDriver("OpenAL") Global Device% Device=alcOpenDevice(Null) If Device=Null Then RuntimeError "No Access to Open-AL-Device possible!" EndIf Global CaptureDevice% If (alcIsExtensionPresent(Device, "ALC_EXT_CAPTURE") = AL_FALSE) Then RuntimeError "No Recording possible!" EndIf CaptureDevice = alcCaptureOpenDevice(Null, 44100, AL_FORMAT_MONO16, 44100*2*1*5); If Not(CaptureDevice) Then RuntimeError "No Access to the Open-AL-CAPTURE-Device" EndIf alcCaptureStart(CaptureDevice) Global Number%, Sum%, RecTime%, RecordNow%=1 Global Tracks%=1 Global Resolution%=2 Global WholeMemorySpace%= 44100 * Tracks * Resolution% * 240 Global Klang:TAudioSample=CreateAudioSample(WholeMemorySpace, 44100 , SF_MONO16LE) Graphics 640,480 '-------------------------------------------- ' Main-Recording Loop here: Repeat If RecTime<MilliSecs() RecTime=MilliSecs() +500 alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(Number)) If (Sum + (Number*Tracks*Resolution)) < WholeMemorySpace Then alcCaptureSamples(CaptureDevice, Klang.Samples + Sum, Number) Sum= Sum + (Number * Tracks* Resolution) EndIf EndIf If KeyHit(Key_R) Then If RecordNow=0 RecordNow=1 alcCaptureStart(CaptureDevice) Sum=0 EndIf Else If KeyHit(Key_P) Then If RecordNow=0 Music = LoadSound(Klang) PlaySound Music EndIf Else If KeyHit(Key_S) Then If RecordNow=1 RecordNow=0 alcCaptureStop(CaptureDevice) EndIf EndIf ' Draw Graphics , etc... Flip 0 Until KeyHit(Key_Escape) '-------------------------------------------- alcCloseDevice(CaptureDevice) alcCloseDevice(Device) End |
| ||
Thank you for making me aware of this. I immediately changed this in my old posts. "KLANG" was a little bit "german". When building the tutorial I tried to change all german expressions in my source code to english ones, but forgot this. Now all the tutorial chapters only use "TestSample", and not "Klang" anymore. |
| ||
hi. what about capturing midi data and saving it? |