# VBForums UtilityBank > UtilityBank - Tutorials >  VB6.0  Sound and DirectXSound Tutorial

## CVMichael

*Please DO NOT post in this thread.

If you have any comments about this thread, please post here: Sugestions or Comments about my Sound Tutorial

AGAIN, PLEASE DO NOT POST HERE
Or you will get in trouble  See this: http://www.vbforums.com/showthread.php?t=388557*


*My Objectives*:
*Basic*
 A small explanation about sound, and sound format.
 How to read/write to a wave file.
 How to play short length sounds with DirectXSound buffers (loading the entire sound file at once)
*Medium difficulty*
 How to record/play with DirectXSound using split buffer for very long sounds, (for streaming).
 How to display the wave data (oscilloscope).
 How to convert from one format to another (like 8 bit to 16 bit, or 11000 Hz to 8000 Hz, Stereo to Mono, etc.)
 How to split a stereo sound to 2 mono sounds.
 How to mix sounds, change the volume digitally.

*What is sound ?*
In computers, sound is digital. That means that sound is represented by a long array of numbers. (That is not the official definition by the way). Therefore if you want to modify the sound (the volume for example), you have to change those numbers in a way or another (I’ll explain how to do that later).

*Sound format: What is ... ?*
*Bit Rate:* In a wave file, the bit rate can be of 8 bit and 16 bit. 8 bit is one byte, therefore the sound is stored in arrays of Byte type, and 16 bit waves (2 bytes) are store in Integer array type. Though you can also store a 16 bit wave in a Byte array also (as long as the length is always divisible by 2). Don’t get me wrong, this does not mean that you can’t store sound in any other data type, in fact, sound can be stored in almost any data type, even strings, as long as you know how to handle the data buffer it really does not matter. But preferably, it is better to store the sound in simple data types like Byte and Integer, corresponding to 8 bit and 16 bit waves. Another reason why you should store the sound in Byte and Integer data type is that it is easier to manipulate the sound (i.e. change the volume, etc.)
*Stereo, Mono:* Everyone who has any clue about music should know this. It is how many channels the wave file has. Mono means one channel, Stereo means two channels.
*Samples Per Second:* When the sound card converts from analog to digital, it takes “samples” of the wave, and it does it really fast, as in thousands of times per second. Usually sample rates range from 8,000 Hz to 44,100 Hz, though I’ve seen sample rates from 4,000 Hz to 110,000 Hz. When the sound is mono, it reads 1 number per sample, when the sample is stereo, then it reads 2 numbers per sample. So a sample includes either one channel or two channels of data.
*Block Align:* This is the most complicated one. Block Align is a number that tells the program how much it should read (how many bytes) to get a complete sample. The sample can be 8 or 16 bit, and also Stereo or Mono.
It is calculated using this formula: BlockAlign = BitsPerSample * Channels / 8
So, if you have a sound that is 8 bits, and mono, then the block align should be 1 byte, if your sound is 16 bits, and stereo then it should be 4 bytes.
*Average Bytes Per Second:* Yes you guessed it, this tells the program how many bytes are in one second for this sound format.
The easiest way to calculate this number is with this formula: AvgBytesPerSec = SamplesPerSec * BlockAlign
*Frequency:* A frequency is how many impulses are in one second. A frequency is represented by hertz (Hz). For example if the frequency is 1000 Hz, then it means that the sound has 1000 impulses per second.
*Impulse:* An impulse looks like a sine (or cosine) when represented graphically, it looks like this:

An impulse consists of un upper "bump" and a lower "bump".

----------


## CVMichael

*How to read or write to a wave file ?*

A wave file has a header, that is composed of 3 individual small headers, and the wave data (raw data).
These are the headers:

VB Code:
Private Type FileHeader
    lRiff As Long
    lFileSize As Long
    lWave As Long
    lFormat As Long
    lFormatLength As Long
End Type
 Private Type WaveFormat
    wFormatTag As Integer
    nChannels As Integer
    nSamplesPerSec As Long
    nAvgBytesPerSec As Long
    nBlockAlign As Integer
    wBitsPerSample As Integer
End Type
 Private Type ChunkHeader
    lType As Long
    lLen As Long
End Type
They are written in the wave file in the same order it is in the code above.
First is the File Header, witch tells the program reading the file that this file IS a wave. You have the total file size, wave format identifier, the format identifier, the length of the wave format structure, following the wave structure itself.
The last part of the header is the Chunk Header. The wave data (raw data) does not have to be in one big chunk, it can be in multiple chunks, as long as the data is preceded by the chunk header.

Writing to a wave file is the easiest ever, you just fill in the values in the structures, then write them in the file one by one, like here:

VB Code:
Private Sub WaveWriteHeader(ByVal OutFileNum As Integer, WaveFmt As WaveFormat)
    Dim header As FileHeader
    Dim chunk As ChunkHeader
    
    With header
        .lRiff = &H46464952 ' "RIFF"
        .lFileSize = 0
        .lWave = &H45564157 ' "WAVE"
        .lFormat = &H20746D66 ' "fmt "
        .lFormatLength = Len(WaveFmt)
    End With
     chunk.lType = &H61746164 ' "data"
    chunk.lLen = 0
    
    Put #OutFileNum, 1, header
    Put #OutFileNum, , WaveFmt
    Put #OutFileNum, , chunk
End Sub
If you look at the code closely, you will see that the variable lFileSize of the FileHeader structure is 0, and the lLen of ChunkHeader is 0. This is because you did not write the wave data yet, so you don't know the sizes yet.
Right after you write the header information (having the 2 variables 0), you have to write the wave data, and when you are done, then you will know the exact length of the file, and length of the wave data.

I created this small sub that calculates those values, and updates the sizes. All you have to do is give the file number to it. This function should be called when you are done saving the wave data into the file, and right before closing the file.

VB Code:
Private Sub WaveWriteHeaderEnd(ByVal OutFileNum As Integer)
    Dim header As FileHeader
    Dim HdrFormat As WaveFormat
    Dim chunk As ChunkHeader
    Dim Lng As Long
    
    Lng = LOF(OutFileNum)
    Put #OutFileNum, 5, Lng ' write the FileHeader.lFileSize value
    
    Lng = LOF(OutFileNum) - (Len(header) + Len(HdrFormat) + Len(chunk))
    Put #OutFileNum, Len(header) + Len(HdrFormat) + 5, Lng ' write the ChunkHeader.lLen value
End Sub
And the function to read from a wave file:

VB Code:
Private Function WaveReadFormat(ByVal InFileNum As Integer, ByRef lDataLength As Long) As WaveFormat
    Dim header As FileHeader
    Dim HdrFormat As WaveFormat
    Dim chunk As ChunkHeader
    Dim by As Byte
    Dim i As Long
    
    Get #InFileNum, 1, header
    
    If header.lRiff <> &H46464952 Then Exit Function   ' Check for "RIFF" tag and exit if not found.
    If header.lWave <> &H45564157 Then Exit Function   ' Check for "WAVE" tag and exit if not found.
    If header.lFormat <> &H20746D66 Then Exit Function ' Check for "fmt " tag and exit if not found.
    
    ' Check format chunk length; if less than 16, it's not PCM data so we can't use it.
    If header.lFormatLength < 16 Then Exit Function
    
    Get #InFileNum, , HdrFormat ' Retrieve format.
    
    ' Seek to next chunk by discarding any format bytes.
    For i = 1 To header.lFormatLength - 16
        Get #InFileNum, , by
    Next
    
    ' Ignore chunks until we get to the "data" chunk.
    Get #InFileNum, , chunk
    Do While chunk.lType <> &H61746164
        For i = 1 To chunk.lLen
            Get #InFileNum, , by
        Next
        Get #InFileNum, , chunk
    Loop
    
    lDataLength = chunk.lLen ' Retrieve the size of the data.
    
    WaveReadFormat = HdrFormat
End Function
I will attach a project that shows the previous 3 functions in use later on in the tutorial.
Right now you won't be able to use the functions because you are missing something: the wave data...

But if you want to see a small example on how it's done:

VB Code:
'  Sample how to write to a wave file
 Private Sub Create_Wave_File_Example()
    Dim Buffer() As Integer
    Dim WaveFmt As WaveFormat
    Dim FileNum As Integer
    
    ' make a 1 second buffer (knowing that the sample rate is 22050)
    ' VB initializes the array with 0 (zero)'s, 0 means silence (for 16 bit wave file)
    ReDim Buffer(22050 * 1) As Integer
    
    ' fill in the Wave Format
    With WaveFmt
        .wFormatTag = 1 ' PCM
        
        .nChannels = 1
        .nSamplesPerSec = 22050
        .wBitsPerSample = 16
        
        .nBlockAlign = .wBitsPerSample * .nChannels / 8
        .nAvgBytesPerSec = .nBlockAlign * .nSamplesPerSec
    End With
    
    ' Create the wave tile
    FileNum = FreeFile
    Open "C:\My Wave File.WAV" For Binary Access Write Lock Write As FileNum
    
    WaveWriteHeader FileNum, WaveFmt ' write the wave headers
    
    Put FileNum, , Buffer ' write 1 second
    Put FileNum, , Buffer ' write another second (to simulate streaming)
    
    ' in this case we will have a wave file with 2 seconds of silence
    
    ' complete the header values (file length, and chunk length)
    WaveWriteHeaderEnd FileNum
    
    ' close the file
    Close FileNum
End Sub
 ' Sample how to read a wave file
 Private Sub Read_Wave_File_Example()
    Dim WaveFmt As WaveFormat
    Dim FileNum As Integer
    Dim DataLength As Long
    Dim Buffer() As Integer
    
    ' open the wave file
    FileNum = FreeFile
    Open "C:\My Wave File.WAV" For Binary Access Read Lock Write As FileNum
    
    ' read the header, and get the chunk size (data length)
    WaveFmt = WaveReadFormat(FileNum, DataLength)
    
    ' resize our buffer to hold the entire wave data (DataLength is in Bytes)
    ReDim Buffer((DataLength \ 2) - 1)
    
    ' read the wave data from the file into our buffer
    Get FileNum, , Buffer
    
    Close FileNum ' close the file
End Sub

----------


## CVMichael

Before you start using DirectXSound, guess what you have to do first ?
Yup, you guessed it, you have to add a reference to DirectX in your project.
So, go to the "Project" menu, and click on "References", you will see a screen like this:

Make sure your latest DirectX is selected. Everyone should have DirectX 8 by now.

Here is the simplest and shortest possible way to play a wave file in DirectXSound:

VB Code:
Private DX As New DirectX8Private DSEnum As DirectSoundEnum8Private DIS As DirectSound8 Private DSSecBuffer As DirectSoundSecondaryBuffer8 Private Sub Form_Load()    Dim BuffDesc As DSBUFFERDESC    ' get enumeration object    Set DSEnum = DX.GetDSEnum        ' select the first sound device, and create the Direct Sound object    Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))        ' Set the Cooperative Level to normal    DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL        ' load the wave file, and create the buffer for it    Set DSSecBuffer = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc)        DSSecBuffer.Play DSBPLAY_DEFAULTEnd Sub
In DirectX 7 you do it the same, except you have to also give a structure of WAVEFORMATEX to CreateSoundBufferFromFile.

VB Code:
Private DX As New DirectX7Private DSEnum As DirectSoundEnumPrivate DIS As DirectSound Private DSSecBuffer As DirectSoundBuffer Private Sub Form_Load()    Dim BuffDesc As DSBUFFERDESC, WaveFormat As WAVEFORMATEX        ' get enumeration object    Set DSEnum = DX.GetDSEnum        ' select the first sound device, and create the Direct Sound object    Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))        ' Set the Cooperative Level to normal    DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL        ' load the wave file, and create the buffer for it    Set DSSecBuffer = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc, WaveFormat)        DSSecBuffer.Play DSBPLAY_DEFAULTEnd Sub
Note that the buffer was declared in the global section. That is because if you put it in the Form_Load, the sound buffer will be destroyed when it will go out of scope. In other words, you *should not* create the buffer in the same function where you play it, preferably it should be created one level below the play statement. OR... make a loop right after the play statement, that will loop continuously while the buffer is playing, and stop when the buffer is done, but this is not recommended.
The buffer description structure that you pass to CreateSoundBufferFromFile function, will be filled with the sound format while it loads the file. In our case we will not use the resulting structure, but it has to be passed to the function. You can use this structure if you want to do more advanced things where you need to know the sound format of the file.

NOTE: This will be the last time I will refer to DirectX 7. The tutorial will get way to big if I have to post DirectX 7 code also. If you understand how DirectX 8 works, it is easy to convert to DirectX 7. It is the same theory for both, there are only small differences between them. And on top of that, everyone should be using DirectX 8 by now, it's been out for a long time.

So, back to the code.
You are probably wondering, "How can I change the volume ?", or "How can I make it play faster or slower ?"
Well, easy... all you have to do is tell the buffer what you are planning to do, and then do it.
Here's an example:

VB Code:
Private Sub Form_Load()    Dim BuffDesc As DSBUFFERDESC    ' get enumeration object    Set DSEnum = DX.GetDSEnum        ' select the first sound device, and create the Direct Sound object    Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))        ' Set the Cooperative Level to normal    DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL        ' allow frequency changes and volume changes    BuffDesc.lFlags = DSBCAPS_CTRLFREQUENCY Or DSBCAPS_CTRLVOLUME        ' load the wave file, and create the buffer for it    Set DSSecBuffer = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc)        ' frequency can range from 100 Hz to ~ 100,000 (depending on your sound card)    DSSecBuffer.SetFrequency 22050 * 1.8        ' volume is from 0 to -10,000 (where 0 is the lowdest, and -10,000 is silence)    DSSecBuffer.SetVolume -1500        DSSecBuffer.Play DSBPLAY_DEFAULTEnd Sub
So, first modify the BuffDesc.lFlags to the constant for frequency or sound volume (or both as in the example), then call the appropriate functions to set the values after you load the file, and before you play the sound.

How to play more than one sound at the same time ?

VB Code:
Dim DX As New DirectX8Dim DSEnum As DirectSoundEnum8Dim DIS As DirectSound8 Dim DSSecBuffer As DirectSoundSecondaryBuffer8Dim DSSecBuffer2 As DirectSoundSecondaryBuffer8 Private Sub Form_Load()    Dim BuffDesc As DSBUFFERDESC        ' get enumeration object    Set DSEnum = DX.GetDSEnum        ' select the first sound device, and create the Direct Sound object    Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))        ' Set the Cooperative Level to normal    DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL        ' allow frequency changes and volume changes    BuffDesc.lFlags = DSBCAPS_CTRLFREQUENCY Or DSBCAPS_CTRLVOLUME        ' load the wave file, and create the buffer for it    Set DSSecBuffer = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc)    Set DSSecBuffer2 = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\notify.wav", BuffDesc)        ' volume is from 0 to -10,000 (where 0 is the lowdest, and -10,000 is silence)    DSSecBuffer.SetVolume -500    DSSecBuffer.Play DSBPLAY_DEFAULT        DSSecBuffer2.SetFrequency 22050 * 0.7    DSSecBuffer2.Play DSBPLAY_DEFAULTEnd Sub
I used the same buffer description on both buffers, this is because in this case we don't need to know the sound format of the sounds, and I passed the structure to the CreateSoundBufferFromFile function just to make DirectSound happy, since the function was designed to take 2 parameters.

Just keep in mind that when using CreateSoundBufferFromFile, Direct Sound will load the *entire* wave file into memory, and play it from there. It is NOT recommended to load large files this way as it will use a lot of memory, I will talk about loading large wave files later in the tutorial.

Now if you are wondering what is CreateSoundBufferFromFile really doing ?
Well, this:

VB Code:
Private DX As New DirectX8Private DSEnum As DirectSoundEnum8Private DIS As DirectSound8 Private DSSecBuffer As DirectSoundSecondaryBuffer8 Private Sub Form_Load()    Dim BuffDesc As DSBUFFERDESC        ' get enumeration object    Set DSEnum = DX.GetDSEnum        ' select the first sound device, and create the Direct Sound object    Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))        ' Set the Cooperative Level to normal    DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL        ' load the wave file, and create the buffer for it    Set DSSecBuffer = CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc)        DSSecBuffer.Play DSBPLAY_DEFAULTEnd Sub Private Function CreateSoundBufferFromFile(ByVal FileName As String, ByRef BuffDesc As DSBUFFERDESC) As DirectSoundSecondaryBuffer8    Dim SecBuff As DirectSoundSecondaryBuffer8    Dim WaveFmt As WaveFormat    Dim FileNum As Integer, DataLength As Long    Dim WaveData() As Byte        FileNum = FreeFile    Open FileName For Binary Access Read Lock Write As FileNum ' open file    WaveFmt = WaveReadFormat(FileNum, DataLength) ' read format        ' copy the wave format values into the WAVEFORMATEX of the DSBUFFERDESC structure    With BuffDesc.fxFormat        .nFormatTag = WaveFmt.wFormatTag                .nChannels = WaveFmt.nChannels        .nBitsPerSample = WaveFmt.wBitsPerSample        .lSamplesPerSec = WaveFmt.nSamplesPerSec                .nBlockAlign = WaveFmt.nBlockAlign        .lAvgBytesPerSec = WaveFmt.nAvgBytesPerSec    End With        ' the next line is not really needed    ' DSBCAPS_STICKYFOCUS means it will keep playing even if our application does not have focus    ' DSBCAPS_STATIC simply means that the buffer is static otherwise DirectX Sound will think that this is a streamming buffer    BuffDesc.lFlags = DSBCAPS_STICKYFOCUS Or DSBCAPS_STATIC        ' tell the buffer how long it should be, in this case we are loading the entire wave file    BuffDesc.lBufferBytes = DataLength        ' create the buffer    Set SecBuff = DIS.CreateSoundBuffer(BuffDesc)        ' resize our array to fit the whole sound file    ReDim WaveData(DataLength - 1)        ' read the wave data    Get FileNum, , WaveData        ' copy our array to the DirectX Sound buffer    SecBuff.WriteBuffer 0, DataLength, WaveData(0), DSBLOCK_DEFAULT        Close FileNum ' close file        ' return the DirectX Sound Buffer    Set CreateSoundBufferFromFile = SecBuffEnd Function
Here is the project to see it working.

Yes, that's right... with everything that we learned until now, we can re-create the CreateSoundBufferFromFile function of the DirectX Sound, easy isn't it ?

----------


## CVMichael

*How to record/play with DirectXSound using split buffer for very long sounds ?*
If you want to play or record for a long time, instead of using a very big buffer that holds all the data, you must use a small buffer where DirectSound has to write the sound, and you to read it from. Especially if you use streaming, if you record for minutes or hours, there is not enough memory to hold that much data.

This is best done with a split buffer.
What happens is when DirectSound writes to the buffer, and you read the buffer at the same time DirectSound writes, the sound won't be clear.

Here is an example of a single buffer:

Since DirectSound writes continuously to the same portion of the buffer, you won't get a chance to read the data.

In order to read the data properly, you have to divide the buffer in 2, and read the section where DirectSound is not writing.

So when Direct Sound is writing on the left side of the buffer, you have to read the right side of the buffer, and vice versa.

This means, that if you have a buffer of 1 second (for example), you have to read from the buffer only 500 Ms of sound at a time, since it's divided in 2.
Also, reading the sound this way, gives you plenty of time to process the sound, convert to other formats, and save it.

When you play sound, you still have to use a split buffer, but the reading/writing will be the opposite; you have to write to the buffer in the half of the buffer that DirectSound does not read.

*Recording:*
Initializing DirectSound for Recording
*1)* Choose the sound device to record from.
*2)* Fill in the buffer description structure the format that you will use to record.
*3)* Calculate the buffer size, and half buffer size (buffer size / 2), and *make sure* that the buffer is aligned.
Making sure the buffer is aligned is *very* important because if you read data and the buffer is not aligned the sound won't be clear (very distorted).
Aligning the buffer simply means that is it divisible by the format Block Align number, or simply, make sure it's aligned by 4 (since 4 is the largest block align number).
*4)* Create the DirectSound buffer.
*5)* Lastly, create the events (Notification Positions) for the buffer.

Here is the code to initialize (and UnInitialize) the Direct Sound for recording:

VB Code:
' Direct Sound objectsPrivate DX As New DirectX8Private SEnum As DirectSoundEnum8Private DISCap As DirectSoundCapture8 ' buffer, and buffer descriptionPrivate Buff As DirectSoundCaptureBuffer8Private BuffDesc As DSCBUFFERDESC ' For the eventsPrivate EventsNotify() As DSBPOSITIONNOTIFYPrivate EndEvent As Long, MidEvent As Long, StartEvent As Long ' to know the buffer sizePrivate BuffLen As Long, HalfBuffLen As Long Public Function Initialize(Optional ByVal SamplesPerSec As Long = 44100, _                            Optional ByVal BitsPerSample As Integer = 16, _                            Optional ByVal Channels As Integer = 2, _                            Optional ByVal HalfBufferLen As Long = 0, _                            Optional ByVal GUID As String = "") As String        ' if there is any error go to ReturnError    On Error GoTo ReturnError        Set SEnum = DX.GetDSCaptureEnum ' get the device enumeration object        ' if GUID is empty, then assign the first sound device    If Len(GUID) = 0 Then GUID = SEnum.GetGuid(1)        ' choose the sound device, and create the Direct Sound object    Set DISCap = DX.DirectSoundCaptureCreate(GUID)        ' set the format to use for recording    With BuffDesc.fxFormat        .nFormatTag = WAVE_FORMAT_PCM        .nChannels = Channels        .nBitsPerSample = BitsPerSample        .lSamplesPerSec = SamplesPerSec                .nBlockAlign = (.nBitsPerSample * .nChannels) \ 8        .lAvgBytesPerSec = .lSamplesPerSec * .nBlockAlign                If HalfBufferLen <= 0 Then            ' make half of the buffer to be 100 ms            HalfBuffLen = .lAvgBytesPerSec / 10        Else            ' using a "custom" size buffer            HalfBuffLen = HalfBufferLen        End If                ' make sure the buffer is aligned        HalfBuffLen = HalfBuffLen - (HalfBuffLen Mod .nBlockAlign)    End With        ' calculate the total size of the buffer    BuffLen = HalfBuffLen * 2        BuffDesc.lBufferBytes = BuffLen    BuffDesc.lFlags = DSCBCAPS_DEFAULT        ' create the buffer object    Set Buff = DISCap.CreateCaptureBuffer(BuffDesc)        ' Create 3 event notifications    ReDim EventsNotify(0 To 2) As DSBPOSITIONNOTIFY        ' create event to signal that DirectSound write cursor    ' is at the beginning of the buffer    StartEvent = DX.CreateEvent(Me)    EventsNotify(0).hEventNotify = StartEvent    EventsNotify(0).lOffset = 1        ' create event to signal that DirectSound write cursor    ' is at half of the buffer    MidEvent = DX.CreateEvent(Me)    EventsNotify(1).hEventNotify = MidEvent    EventsNotify(1).lOffset = HalfBuffLen        ' create the event to signal the sound has stopped    EndEvent = DX.CreateEvent(Me)    EventsNotify(2).hEventNotify = EndEvent    EventsNotify(2).lOffset = DSBPN_OFFSETSTOP        ' Assign the notification points to the buffer    Buff.SetNotificationPositions 3, EventsNotify()        Initialize = ""    Exit FunctionReturnError:    ' return error number, description and source    Initialize = "Error: " & Err.Number & vbNewLine & _        "Desription: " & Err.Description & vbNewLine & _        "Source: " & Err.Source        Err.Clear    UninitializeSound    Exit FunctionEnd Function Public Sub UninitializeSound()    ' distroy all events    DX.DestroyEvent EventsNotify(0).hEventNotify    DX.DestroyEvent EventsNotify(1).hEventNotify    DX.DestroyEvent EventsNotify(2).hEventNotify        Erase EventsNotify        Set Buff = Nothing    Set DISCap = Nothing    Set SEnum = NothingEnd Sub
Now you have to read the data from the DirectSound buffer.
This is done in the events that come from DirectSound.
To receive the events you have to implement the DirectXEvent8 interface. Once implemented you will receive the events through the DirectXEvent8_DXCallback event.

This is how it's done:

VB Code:
Implements DirectXEvent8 Private Sub DirectXEvent8_DXCallback(ByVal eventid As Long)    Dim WaveBuffer() As Byte        ' make sure that Buff object is actually initialized to a buffer instance    If Not (Buff Is Nothing) Then        ReDim WaveBuffer(HalfBuffLen - 1)            Select Case eventid        Case StartEvent            ' we got the event that the write cursor is at the beginning of the buffer            ' therefore read from the middle of the buffer to the end            Buff.ReadBuffer HalfBuffLen, HalfBuffLen, WaveBuffer(0), DSCBLOCK_DEFAULT        Case MidEvent            ' we got an event that the write cursor is at the middle of the buffer            ' threfore read from the beginning of the buffer to the middle            Buff.ReadBuffer 0, HalfBuffLen, WaveBuffer(0), DSCBLOCK_DEFAULT        Case EndEvent            ' not used right now        End Select                ' use the wave buffer here    End IfEnd Sub
Now there are 2 more things to make it complete:
1) Functions to Start/Stop the actual recording.
2) Use the wave data in a way or another...

So, applying everything that we learned until now, I've made a project that records the audio from the main sound device, and saves the wave data into a wave file.

Please see the following project: DirectSound, Split Buffer Record

You will see that there are 2 forms. I placed all the DirectSound initialization, buffer event, and start and stop recording in one form. This way I can use that form in multiple projects (same as you use a module file).
When DirectSound raises the event to read from it's buffer, a byte array buffer is created and the wave data is read into it.
I placed an event in the DirectSound form. The event is used to transfer the wave data to the parent form.
The parent form creates a wave file, and saves all the wave data that comes from the other form (DirectSound form) into that file.

*Playing:*
When playing sound, initializing the sound is somewhat similar, but reading/writing to the buffer is exactly the opposite as recording.
When playing, you have to write to the buffer in the half that DirectSound is *not* reading, and vice versa.

Because playing sound is so similar to recording, I'm not going to explain in detail how it's done. Just open the following project, and read the comments in the project to understand how playing sound works:
DirectSound, Split Buffer Play
This project I made it similar to the recording one, I've put the DirectSound in a separate form, and the main form opens the wave file, and sends the wave data to DirectSound form.

----------


## CVMichael

*How to display the wave data (oscilloscope):*

This is very easily done because you simply display the wave data "as is".

The only problem is that you have to write separate code for 8 and 16 bit sound formats, and also for mono and stereo.

In the following code, you will see 3 functions:
DisplayWaveData8 is for displaying 8 bit data, stereo and mono.
DisplayWaveData16_8 is for displaying 16 bit data, but the data is recorded in byte arrays (instead of integer arrays), therefore, the function simply copies the array to an integer array.
DisplayWaveData16 is for displaying 16 bit data, stereo and mono.

VB Code:
Private Sub DisplayWaveData8(DataBuff() As Byte, Pic As PictureBox, Stereo As Boolean)
    Dim Stp As Single, HBuffer As Long, Q As Single
    Dim LX As Single, LY As Single, RX As Single, RY As Single
    Dim LVal As Single, RVal As Single, K As Long
    
    If Not Stereo Then
        HBuffer = UBound(DataBuff)
        Stp = HBuffer / (Pic.Width / 15)
        
        Pic.Scale (0, 127)-(HBuffer, -127)
        Pic.PSet (0, 0)
        
        Pic.Cls
        
        For Q = 0 To HBuffer - 2 Step Stp
            Pic.Line -(Fix(Q), DataBuff(Fix(Q)) - 127)
        Next Q
    Else
        HBuffer = UBound(DataBuff) \ 2
        Stp = HBuffer / (Pic.Width / 15)
        
        Pic.Scale (0, 256)-(HBuffer, -256)
        Pic.PSet (0, 0)
        
        Pic.Cls
        
        LX = 0
        LY = -127
        RX = 0
        RY = 127
        
        For Q = 0 To HBuffer - 2 Step Stp
            K = Q
            K = K - (K Mod 2)
            
            LVal = DataBuff(K + 1) - 255
            RVal = DataBuff(K)
            
            Pic.Line (LX, LY)-(K, LVal)
            Pic.Line (RX, RY)-(K, RVal)
            
            LX = K
            LY = LVal
            
            RX = K
            RY = RVal
        Next Q
    End If
End Sub
 ' the sound is 16 bit, but it comes in Bytes, not Integer
Private Sub DisplayWaveData16_8(DataBuff() As Byte, Pic As PictureBox, Stereo As Boolean)
    Dim Buff() As Integer
    
    ReDim Buff(UBound(DataBuff) \ 2 - 1)
    
    CopyMemory Buff(0), DataBuff(0), UBound(DataBuff) + 1
    
    DisplayWaveData16 Buff, Pic, Stereo
End Sub
 Private Sub DisplayWaveData16(DataBuff() As Integer, Pic As PictureBox, Stereo As Boolean)
    Dim Stp As Single, HBuffer As Long, Q As Single
    Dim LX As Single, LY As Single, RX As Single, RY As Single
    Dim LVal As Single, RVal As Single, K As Long
    
    If Not Stereo Then
        HBuffer = UBound(DataBuff)
        Stp = HBuffer / (Pic.Width / 15)
        
        Pic.Scale (0, 0.5)-(HBuffer, -0.5)
        Pic.PSet (0, 0)
        
        Pic.Cls
        For Q = 0 To HBuffer - 2 Step Stp
            Pic.Line -(Fix(Q), DataBuff(Fix(Q)) / 65536#)
        Next Q
    Else
        HBuffer = UBound(DataBuff) \ 2
        Stp = HBuffer / (Pic.Width / 15)
        
        Pic.Scale (0, 1)-(HBuffer, -1)
        Pic.PSet (0, 0)
        
        Pic.Cls
        
        LX = 0
        LY = -0.5
        RX = 0
        RY = 0.5
        
        For Q = 0 To HBuffer - 2 Step Stp
            K = Q
            K = K - (K Mod 2)
            
            LVal = DataBuff(K + 1) / 65536# - 0.5
            RVal = DataBuff(K) / 65536# + 0.5
            
            Pic.Line (LX, LY)-(K, LVal)
            Pic.Line (RX, RY)-(K, RVal)
            
            LX = K
            LY = LVal
            
            RX = K
            RY = RVal
        Next Q
    End If
End Sub
To see the previous functions in use, see this project: Recording, and displaying the wave data

Note: The functions are made assuming that the picture box is sitting on a form with ScaleMode = vbTwips (the default).

----------


## CVMichael

*How to convert from one format to another:*

First things first, as in the previous code (displaying the wave data), sometimes you need to convert a 16 bit sound in a Byte array to a Integer array. This is done by simply copying the data from the Byte array to a new Integer array.

VB Code:
' convert a 16 bit sound from a Byte array to Integer arrayPublic Function Convert16_8To16(Buffer() As Byte) As Integer()    Dim Buff() As Integer        ReDim Buff((UBound(Buffer) + 1) / 2 - 1 + 0.1)        CopyMemory Buff(0), Buffer(0), UBound(Buffer) + 1        Convert16_8To16 = BuffEnd Function
Converting from 16 bit wave to 8 bit wave is just a simple matter of division.
But since the 8 bit wave (byte) is unsigned, we have to make the 16 bit wave data unsigned first, then divide.

VB Code:
' convert 16 bit to 8 bitPublic Function ConvertWave16to8(Buffer() As Integer) As Byte()    Dim K As Long, Val As Long    Dim RetBuff() As Byte        ReDim RetBuff(UBound(Buffer))        For K = 0 To UBound(Buffer)        Val = Buffer(K)        Val = (Val + 32768) \ 256                RetBuff(K) = Val    Next K        ConvertWave16to8 = RetBuffEnd Function
Converting from 8 bit wave to 16 bit wave is the opposite.
You have to subtract to make it signed, then multiply to fit the Integer variable.

VB Code:
' convert 8 bit to 16 bitPublic Function ConvertWave8to16(Buffer() As Byte) As Integer()    Dim K As Long, Val As Long    Dim RetBuff() As Integer        ReDim RetBuff(UBound(Buffer))        For K = 0 To UBound(Buffer)        Val = (Buffer(K) - 127) * 256                RetBuff(K) = Val    Next K        ConvertWave8to16 = RetBuffEnd Function
When converting from Mono to Stereo, you just copy the same value to left channel and right channel.

VB Code:
' convert mono to stereo for 16 bit bufferPublic Function ConvertWaveMonoToStereo16(Buffer() As Integer) As Integer()    Dim K As Long    Dim RetBuff() As Integer        ReDim RetBuff(UBound(Buffer) * 2)        For K = 0 To UBound(Buffer)        RetBuff(K * 2 + 0) = Buffer(K)        RetBuff(K * 2 + 1) = Buffer(K)    Next K        ConvertWaveMonoToStereo16 = RetBuffEnd Function ' convert mono to stereo for 8 bit bufferPublic Function ConvertWaveMonoToStereo8(Buffer() As Byte) As Byte()    Dim K As Long    Dim RetBuff() As Byte        ReDim RetBuff(UBound(Buffer) * 2)        For K = 0 To UBound(Buffer)        RetBuff(K * 2 + 0) = Buffer(K)        RetBuff(K * 2 + 1) = Buffer(K)    Next K        ConvertWaveMonoToStereo8 = RetBuffEnd Function
When converting from Stereo to Mono, is a little tricky. You have to mix the 2 channels. So you first add the 2 channels together, then divide by 2 (making an average of the 2).

VB Code:
' convert stereo to mono for 16 bit bufferPublic Function ConvertWaveStereoToMono16(Buffer() As Integer) As Integer()    Dim K As Long, Val As Long    Dim RetBuff() As Integer        ReDim Buff((UBound(Buffer) + 1) \ 2 - 1)        For K = 0 To UBound(RetBuff)        Val = Buffer(K * 2)        Val = (Val + Buffer(K * 2 + 1)) \ 2                RetBuff(K) = Val    Next K        ConvertWaveStereoToMono16 = RetBuffEnd Function ' convert stereo to mono for 8 bit bufferPublic Function ConvertWaveStereoToMono8(Buffer() As Byte) As Byte()    Dim K As Long, Val As Long    Dim RetBuff() As Byte        ReDim Buff((UBound(Buffer) + 1) \ 2 - 1)        For K = 0 To UBound(RetBuff)        Val = Buffer(K * 2)        Val = (Val + Buffer(K * 2 + 1)) \ 2                RetBuff(K) = Val    Next K        ConvertWaveStereoToMono8 = RetBuffEnd Function
*Converting between sample rates:*

The *standard* sample rates are:
8000
11025
16000
22050
32000
44100

Now when you look at the numbers carefully, you will notice that for some of them when you divide one by another, you get an Integer result (2 or 4), and for some you get a decimal number.
For example if you divide 16000 Hz by 8000 Hz, you get 2, or 44100 Hz by 11025 Hz you get 4, but when you divide 44100 Hz by 32000 Hz you get 1.378125 (decimal number).

These are the 2 groups that are divisible by one another:
8000
16000
32000

11025
22050
44100

This means, and it is easy to convert between 8000 Hz and 16000 Hz and 32000 Hz, or 11025 Hz and 22050 Hz and 44100, but not as easy when you convert from 8000 Hz to 11025 Hz (for example).

The next functions are for converting by multiplication or dividing by 2 for 16 bit per sample and 8 bits per sample wave buffers.
For example if you want to convert from 11025 Hz to 22050 Hz at 16 bits per sample, then you would call the function ConvertWave16MultiplySamplesBy2.

VB Code:
' convert a 16 bit wave, multiply samples by 2Public Function ConvertWave16MultiplySamplesBy2(Buffer() As Integer, ByVal Stereo As Boolean) As Integer()    Dim K As Long    Dim RetBuff() As Integer        ReDim RetBuff(UBound(Buffer) * 2)        If Not Stereo Then        For K = 0 To UBound(Buffer) - 1            RetBuff(K * 2) = Buffer(K)            RetBuff(K * 2 + 1) = (CLng(Buffer(K)) + Buffer(K + 1)) \ 2        Next K    Else        For K = 0 To UBound(Buffer) - 3 Step 2            RetBuff(K * 2 + 0) = Buffer(K + 0)            RetBuff(K * 2 + 2) = (CLng(Buffer(K)) + Buffer(K + 2)) \ 2                        RetBuff(K * 2 + 1) = Buffer(K + 1)            RetBuff(K * 2 + 3) = (CLng(Buffer(K + 1)) + Buffer(K + 3)) \ 2        Next K    End If        ConvertWave16MultiplySamplesBy2 = RetBuffEnd Function ' convert a 8 bit wave, multiply samples by 2Public Function ConvertWave8MultiplySamplesBy2(Buffer() As Byte, ByVal Stereo As Boolean) As Byte()    Dim K As Long    Dim RetBuff() As Byte        ReDim RetBuff(UBound(Buffer) * 2)        If Not Stereo Then        For K = 0 To UBound(Buffer) - 1            RetBuff(K * 2) = Buffer(K)            RetBuff(K * 2 + 1) = (CLng(Buffer(K)) + Buffer(K + 1)) \ 2        Next K    Else        For K = 0 To UBound(Buffer) - 3 Step 2            RetBuff(K * 2 + 0) = Buffer(K + 0)            RetBuff(K * 2 + 2) = (CLng(Buffer(K)) + Buffer(K + 2)) \ 2                        RetBuff(K * 2 + 1) = Buffer(K + 1)            RetBuff(K * 2 + 3) = (CLng(Buffer(K + 1)) + Buffer(K + 3)) \ 2        Next K    End If        ConvertWave8MultiplySamplesBy2 = RetBuffEnd Function ' convert a 16 bit wave, divide samples by 2Public Function ConvertWave16DivideSamplesBy2(Buffer() As Integer, ByVal Stereo As Boolean) As Integer()    Dim K As Long    Dim RetBuff() As Integer        ReDim RetBuff((UBound(Buffer) + 1) \ 2 - 1)        If Not Stereo Then        For K = 0 To UBound(Buffer) Step 2            RetBuff(K \ 2) = (CLng(Buffer(K)) + Buffer(K + 1)) \ 2        Next K    Else        For K = 0 To UBound(Buffer) - 4 Step 4            RetBuff(K \ 2 + 0) = (CLng(Buffer(K + 0)) + Buffer(K + 2)) \ 2            RetBuff(K \ 2 + 1) = (CLng(Buffer(K + 1)) + Buffer(K + 3)) \ 2        Next K    End If        ConvertWave16DivideSamplesBy2 = RetBuffEnd Function ' convert a 8 bit wave, divide samples by 2Public Function ConvertWave8DivideSamplesBy2(Buffer() As Byte, ByVal Stereo As Boolean) As Byte()    Dim K As Long    Dim RetBuff() As Byte        ReDim RetBuff((UBound(Buffer) + 1) \ 2 - 1)        If Not Stereo Then        For K = 0 To UBound(Buffer) Step 2            RetBuff(K \ 2) = (CLng(Buffer(K)) + Buffer(K + 1)) \ 2        Next K    Else        For K = 0 To UBound(Buffer) - 4 Step 4            RetBuff(K \ 2 + 0) = (CLng(Buffer(K + 0)) + Buffer(K + 2)) \ 2            RetBuff(K \ 2 + 1) = (CLng(Buffer(K + 1)) + Buffer(K + 3)) \ 2        Next K    End If    ConvertWave8DivideSamplesBy2 = RetBuffEnd Function

To convert from one sample format to another where they are not divisible by 2 (for example: 8000 to 11025), it is kinda tricky.
There are 2 ways to do it.
The easy way is to convert and assign new values by the nearest index.
Here is a sample code for 16 bit:

VB Code:
Private Function ReSample16(Buff() As Integer, FromSample As Long, ToSample As Long) As Integer()    Dim K As Long, BuffSZ As Long    Dim Ret() As Integer, Per As Double        BuffSZ = UBound(Buff) + 1        ReDim Ret(Fix(BuffSZ * ToSample / FromSample + 0.5))        For K = 0 To UBound(Ret)        Per = K / UBound(Ret)                Ret(K) = Buff(UBound(Buff) * Per)    Next K        ReSample16 = RetEnd Function
_Continued in the next post_

----------


## CVMichael

Here is an example on how it looks like when you convert by the nearest index:

In the previous image it's converting from 8000 Hz sample rate to 22050 Hz sample rate.
The black line (with black dots) is the original sound that is at 8000 Hz, and the yellow is the converted sound at 22050 Hz.

The better way to do it, is by calculating the value by using the line intersection formula, like this:

Using the line intersection formula, you can find the exact value that it should be even when the destination sample position does not match the source sample position.

Here is a sample image on how it looks like when you use line intersection formula:


And here is the code to convert using the line intersection formula:

VB Code:
Public Function FindYForX(ByVal X As Double, ByVal X1 As Double, ByVal Y1 As Double, _
        ByVal X2 As Double, ByVal Y2 As Double) As Double
    
    Dim M As Double, B As Double
    
    M = (Y1 - Y2) / (X1 - X2)
    B = Y1 - M * X1
    
    FindYForX = M * X + B
End Function
 Public Function ConvertWave16ReSample(Buff() As Integer, ByVal FromSample As Long, ByVal ToSample As Long, ByVal Stereo As Boolean) As Integer()
    Dim K As Long, Lx As Long, RX As Long
    Dim Ret() As Integer, Per As Double, NewSize As Long
    
    If Not Stereo Then
        NewSize = Fix((UBound(Buff) + 1) * ToSample / FromSample + 0.5)
        ReDim Ret(NewSize - 1)
        
        For K = 0 To UBound(Ret) - 1
            Per = K / UBound(Ret)
            
            Lx = Fix(UBound(Buff) * Per)
            
            Ret(K) = FindYForX(UBound(Buff) * Per, Lx, Buff(Lx), Lx + 1, Buff(Lx + 1))
        Next K
        
        Ret(UBound(Ret)) = Buff(UBound(Buff))
    Else
        NewSize = Fix((UBound(Buff) + 1) * ToSample / FromSample + 0.5)
        NewSize = NewSize - (NewSize Mod 2)
        ReDim Ret(NewSize - 1)
        
        For K = 0 To UBound(Ret) Step 2
            Per = K / (UBound(Ret) + 2)
            
            ' Left channel
            Lx = Fix(UBound(Buff) * Per / 2#) * 2
            Ret(K + 0) = FindYForX(UBound(Buff) * Per, Lx, Buff(Lx), Lx + 2, Buff(Lx + 2))
            
            ' Right channel
            RX = Lx + 1
            Ret(K + 1) = FindYForX(UBound(Buff) * Per + 1, RX, Buff(RX), RX + 2, Buff(RX + 2))
        Next K
        
        Ret(UBound(Ret) - 1) = Buff(UBound(Buff) - 1)
        Ret(UBound(Ret)) = Buff(UBound(Buff))
    End If
    
    ConvertWave16ReSample = Ret
End Function
 Public Function ConvertWave8ReSample(Buff() As Byte, ByVal FromSample As Long, ByVal ToSample As Long, ByVal Stereo As Boolean) As Byte()
    Dim K As Long, Lx As Long, RX As Long
    Dim Ret() As Byte, Per As Double, NewSize As Long
    
    If Not Stereo Then
        NewSize = Fix((UBound(Buff) + 1) * ToSample / FromSample + 0.5)
        ReDim Ret(NewSize - 1)
        
        For K = 0 To UBound(Ret) - 1
            Per = K / UBound(Ret)
            
            Lx = Fix(UBound(Buff) * Per)
            
            Ret(K) = FindYForX(UBound(Buff) * Per, Lx, Buff(Lx), Lx + 1, Buff(Lx + 1))
        Next K
        
        Ret(UBound(Ret)) = Buff(UBound(Buff))
    Else
        NewSize = Fix((UBound(Buff) + 1) * ToSample / FromSample + 0.5)
        NewSize = NewSize - (NewSize Mod 2)
        ReDim Ret(NewSize - 1)
        
        For K = 0 To UBound(Ret) Step 2
            Per = K / (UBound(Ret) + 2)
            
            ' Left channel
            Lx = Fix(UBound(Buff) * Per / 2#) * 2
            Ret(K + 0) = FindYForX(UBound(Buff) * Per, Lx, Buff(Lx), Lx + 2, Buff(Lx + 2))
            
            ' Right channel
            RX = Lx + 1
            Ret(K + 1) = FindYForX(UBound(Buff) * Per + 1, RX, Buff(RX), RX + 2, Buff(RX + 2))
        Next K
        
        Ret(UBound(Ret) - 1) = Buff(UBound(Buff) - 1)
        Ret(UBound(Ret)) = Buff(UBound(Buff))
    End If
    
    ConvertWave8ReSample = Ret
End Function

*Converting from any format to any other format*
With all the functions together from this post and previous post, you can convert from any format to any other format. They work fine if the format is static, but if the format changes often, it is difficult to make a lot of if statements to choose what function to call in each case.

That's why I made things easier. The following function converts to all formats possible using all the previous functions.
The input must be a Byte Array to Integer Array regardless of the Bits Per Sample for the sound. The return array will always be Byte Array for 8 bit return sound, and Integer Array for 16 bit return sound.

VB Code:
Public Function ConvertWave(Buffer As Variant, FromFormat As WAVEFORMATEX, ToFormat As WAVEFORMATEX) As Variant
    Dim Buffer16() As Integer
    Dim Buffer8() As Byte
    
    Dim RetBuff16() As Integer
    Dim RetBuff8() As Byte
    
    If FromFormat.nBitsPerSample = 16 Then
        Select Case VarType(Buffer)
        Case (vbArray Or vbByte)
            Buffer8 = Buffer
            Buffer16 = Convert16_8To16(Buffer8)
            Erase Buffer8
        Case (vbArray Or vbInteger)
            Buffer16 = Buffer
        Case Else
            ConvertWave = vbEmpty
            Exit Function
        End Select
        
        If ToFormat.nBitsPerSample = 8 Then
            RetBuff8 = ConvertWave16to8(Buffer16)
            Erase Buffer16
            
            GoTo To8Bit ' JUMP TO 8 BIT
        ElseIf ToFormat.nBitsPerSample = 16 Then
            RetBuff16 = Buffer16
            Erase Buffer16
        Else
            ConvertWave = vbEmpty
            Exit Function
        End If
To16Bit:
        
        If FromFormat.nChannels = 1 And ToFormat.nChannels = 2 Then
            RetBuff16 = ConvertWaveMonoToStereo16(RetBuff16)
        ElseIf FromFormat.nChannels = 2 And ToFormat.nChannels = 1 Then
            RetBuff16 = ConvertWaveStereoToMono16(RetBuff16)
        ElseIf FromFormat.nChannels <> ToFormat.nChannels Then
            ConvertWave = vbEmpty
            Exit Function
        End If
        
        If FromFormat.lSamplesPerSec <> ToFormat.lSamplesPerSec Then
            Select Case FromFormat.lSamplesPerSec / ToFormat.lSamplesPerSec
            Case 0.25
                RetBuff16 = ConvertWave16MultiplySamplesBy2(RetBuff16, ToFormat.nChannels = 2)
                RetBuff16 = ConvertWave16MultiplySamplesBy2(RetBuff16, ToFormat.nChannels = 2)
            Case 0.5
                RetBuff16 = ConvertWave16MultiplySamplesBy2(RetBuff16, ToFormat.nChannels = 2)
            Case 2
                RetBuff16 = ConvertWave16DivideSamplesBy2(RetBuff16, ToFormat.nChannels = 2)
            Case 4
                RetBuff16 = ConvertWave16DivideSamplesBy2(RetBuff16, ToFormat.nChannels = 2)
                RetBuff16 = ConvertWave16DivideSamplesBy2(RetBuff16, ToFormat.nChannels = 2)
            Case Else
                RetBuff16 = ConvertWave16ReSample(RetBuff16, FromFormat.lSamplesPerSec, ToFormat.lSamplesPerSec, ToFormat.nChannels = 2)
            End Select
        End If
        
        ConvertWave = RetBuff16
    ElseIf FromFormat.nBitsPerSample = 8 Then
        Select Case VarType(Buffer)
        Case (vbArray Or vbByte)
            Buffer8 = Buffer
        Case (vbArray Or vbInteger)
            Buffer16 = Buffer
            
            ReDim Buffer8((UBound(Buffer16) + 1) * 2 - 1)
            CopyMemory Buffer8(0), Buffer(16), UBound(Buffer8) + 1
            
            Erase Buffer16
        Case Else
            ConvertWave = vbEmpty
            Exit Function
        End Select
        
        If ToFormat.nBitsPerSample = 16 Then
            RetBuff16 = ConvertWave8to16(Buffer8)
            Erase Buffer8
            
            GoTo To16Bit ' JUMP TO 16 BIT
        ElseIf ToFormat.nBitsPerSample = 8 Then
            RetBuff8 = Buffer8
            Erase Buffer8
        Else
            ConvertWave = vbEmpty
            Exit Function
        End If
To8Bit:
        
        If FromFormat.nChannels = 1 And ToFormat.nChannels = 2 Then
            RetBuff8 = ConvertWaveMonoToStereo8(RetBuff8)
        ElseIf FromFormat.nChannels = 2 And ToFormat.nChannels = 1 Then
            RetBuff8 = ConvertWaveStereoToMono8(RetBuff8)
        ElseIf FromFormat.nChannels <> ToFormat.nChannels Then
            ConvertWave = vbEmpty
            Exit Function
        End If
        
        If FromFormat.lSamplesPerSec <> ToFormat.lSamplesPerSec Then
            Select Case FromFormat.lSamplesPerSec / ToFormat.lSamplesPerSec
            Case 0.25
                RetBuff8 = ConvertWave8MultiplySamplesBy2(RetBuff8, ToFormat.nChannels = 2)
                RetBuff8 = ConvertWave8MultiplySamplesBy2(RetBuff8, ToFormat.nChannels = 2)
            Case 0.5
                RetBuff8 = ConvertWave8MultiplySamplesBy2(RetBuff8, ToFormat.nChannels = 2)
            Case 2
                RetBuff8 = ConvertWave8DivideSamplesBy2(RetBuff8, ToFormat.nChannels = 2)
            Case 4
                RetBuff8 = ConvertWave8DivideSamplesBy2(RetBuff8, ToFormat.nChannels = 2)
                RetBuff8 = ConvertWave8DivideSamplesBy2(RetBuff8, ToFormat.nChannels = 2)
            Case Else
                RetBuff8 = ConvertWave8ReSample(RetBuff8, FromFormat.lSamplesPerSec, ToFormat.lSamplesPerSec, ToFormat.nChannels = 2)
            End Select
        End If
        
        ConvertWave = RetBuff8
    Else
        ConvertWave = vbEmpty
        Exit Function
    End If
End Function

----------


## CVMichael

*Changing the buffer volume.*

When you change the volume of the sound, you change the amplitude of the waves within the sound. If the amplitude is big, the sound is loud, and if the amplitude is small, the sound is low..

To change the volume you basically have to multiply the values within the array sound, to make the amplitude bigger or smaller.

When changing volume for 8 Bit sound, since the value is stored in a Byte is from 0 to 255, and silence is 127, you have to first make the value signed ie. from -128 to 127, then multiply, and then bring it back to range 0 to 255.

Here is how it's done for 8 Bit Mono and Stereo sound:

vb Code:
' Change volume for 8 Bit Mono soundPublic Sub ChangeVolume8Mono(Buffer8() As Byte, Percent As Single)    Dim K As Long    Dim Sample As Long        For K = LBound(Buffer8) To UBound(Buffer8)        Sample = ((CSng(Buffer8(K)) - 127) * Percent) + 127                If Sample < 0 Then Sample = 0        If Sample > 255 Then Sample = 255                Buffer8(K) = Sample    Next KEnd Sub ' Change volume for 8 Bit Stereo soundPublic Sub ChangeVolume8Stereo(Buffer8() As Byte, LPercent As Single, RPercent As Single)    Dim K As Long    Dim LSample As Long    Dim RSample As Long        For K = LBound(Buffer8) To UBound(Buffer8) Step 2        LSample = ((CSng(Buffer8(K)) - 127) * LPercent) + 127        RSample = ((CSng(Buffer8(K + 1)) - 127) * RPercent) + 127                If LSample < 0 Then LSample = 0        If LSample > 255 Then LSample = 255                If RSample < 0 Then RSample = 0        If RSample > 255 Then RSample = 255                Buffer8(K) = LSample        Buffer8(K + 1) = RSample    Next KEnd Sub
For 16 bit sound, it's even easier because the sound is already signed, so all you have to do, is multiply it's value.
Here's how it's done for 16 Bit Mono and Stereo sound:

vb Code:
' Change volume for 16 Bit Mono soundPublic Sub ChangeVolume16Mono(Buffer16() As Integer, Percent As Single)    Dim K As Long    Dim Sample As Long        For K = LBound(Buffer16) To UBound(Buffer16)        Sample = Buffer16(K) * Percent                If Sample < -32768 Then Sample = -32768        If Sample > 32767 Then Sample = 32767                Buffer16(K) = Sample    Next KEnd Sub ' Change volume for 16 Bit Stereo soundPublic Sub ChangeVolume16Stereo(Buffer16() As Integer, LPercent As Single, RPercent As Single)    Dim K As Long    Dim LSample As Long    Dim RSample As Long        For K = LBound(Buffer16) To UBound(Buffer16) Step 2        LSample = Buffer16(K) * LPercent        RSample = Buffer16(K + 1) * RPercent                If LSample < -32768 Then LSample = -32768        If LSample > 32767 Then LSample = 32767                If RSample < -32768 Then RSample = -32768        If RSample > 32767 Then RSample = 32767                Buffer16(K) = LSample        Buffer16(K + 1) = RSample    Next KEnd Sub

----------


## CVMichael

*Splitting a Stereo buffer to 2 Mono buffers*

In a Stereo buffer, the left channel is stored first, then the right channel.
So, at position 1, you will find the sample of the left channel, position 2 is right channel, position 3 is left channel again, and so on.

Here is code to split Stereo buffer to 2 Mono buffers, for 8 and 16 Bit:

vb Code:
Public Sub SoundStereoToMono16(InStereo() As Integer, OutLeftChannel() As Integer, OutRightChannel() As Integer)
    Dim InPos As Long
    Dim OutPos As Long
    
    ReDim OutLeftChannel((UBound(InStereo) + 1) \ 2 - 1)
    ReDim OutRightChannel(UBound(OutLeftChannel))
    
    For InPos = 0 To UBound(InStereo) Step 2
        OutLeftChannel(OutPos) = InStereo(InPos)
        OutRightChannel(OutPos) = InStereo(InPos + 1)
        
        OutPos = OutPos + 1
    Next InPos
End Sub
 Public Sub SoundStereoToMono8(InStereo() As Byte, OutLeftChannel() As Byte, OutRightChannel() As Byte)
    Dim InPos As Long
    Dim OutPos As Long
    
    ReDim OutLeftChannel((UBound(InStereo) + 1) \ 2 - 1)
    ReDim OutRightChannel(UBound(OutLeftChannel))
    
    For InPos = 0 To UBound(InStereo) Step 2
        OutLeftChannel(OutPos) = InStereo(InPos)
        OutRightChannel(OutPos) = InStereo(InPos + 1)
        
        OutPos = OutPos + 1
    Next InPos
End Sub

----------


## CVMichael

*Mixing buffers*

Mixing sound is actually very easy, you simply add the value from the first buffer to the value from the second buffer (and so on...).

When mixing sounds, since you add 2 (or more) sounds into one, the volume might get too loud, therefore I also added a parameter to the function to lower the volume.

I posted 2 functions, one mix 2 sounds, and another to mix 3 sounds. As you can see the only difference is where it's adding the buffers together, so it's easy to make a function to mix 4 sounds (and more), because all you do is to add all of them together.

The last parameter is the "Divisor" (Div), if you mix 2 sounds you might want to put the divisor to 1/2 (0.5), or if you mix 3 sounds, 1/3 (0.33), and so on. I put the Div value equal to 1, because this is optional.

vb Code:
Public Function WaveMIX2(Buffer1() As Integer, Buffer2() As Integer, Optional Div As Single = 1) As Integer()    Dim K As Long, Sample As Single    Dim Ret() As Integer        ReDim Ret(UBound(Buffer1))        For K = 0 To UBound(Buffer1)        Sample = (CSng(Buffer1(K)) + Buffer2(K)) * Div                If Sample < -32768 Then Sample = -32768        If Sample > 32767 Then Sample = 32767                Ret(K) = Sample    Next K        WaveMIX2 = RetEnd Function Public Function WaveMIX3(Buffer1() As Integer, Buffer2() As Integer, Buffer3() As Integer, Optional Div As Single = 1) As Integer()    Dim K As Long, Sample As Single    Dim Ret() As Integer        ReDim Ret(UBound(Buffer1))        For K = 0 To UBound(Buffer1)        Sample = (CSng(Buffer1(K)) + Buffer2(K) + Buffer3(K)) * Div                If Sample < -32768 Then Sample = -32768        If Sample > 32767 Then Sample = 32767                Ret(K) = Sample    Next K        WaveMIX3 = RetEnd Function

----------

