Audio Recording with OpenAl

BlitzMax Forums/BlitzMax Tutorials/Audio Recording with OpenAl

Midimaster(Posted 2010) [#1]
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


Midimaster(Posted 2010) [#2]
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
'--------------------------------------------



Midimaster(Posted 2010) [#3]
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


Midimaster(Posted 2010) [#4]
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


Casaber(Posted 2015) [#5]
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



Midimaster(Posted 2015) [#6]
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.


Hardcoal(Posted May) [#7]
hi. what about capturing midi data and saving it?