Results 1 to 10 of 10

Thread: VB6.0 – Sound and DirectXSound Tutorial

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    VB6.0 – Sound and DirectXSound Tutorial

    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".
    Last edited by CVMichael; Dec 23rd, 2008 at 09:29 AM.

  2. #2

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    Re: Tutorial under construction

    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:
    1. Private Type FileHeader
    2.     lRiff As Long
    3.     lFileSize As Long
    4.     lWave As Long
    5.     lFormat As Long
    6.     lFormatLength As Long
    7. End Type
    8.  
    9. Private Type WaveFormat
    10.     wFormatTag As Integer
    11.     nChannels As Integer
    12.     nSamplesPerSec As Long
    13.     nAvgBytesPerSec As Long
    14.     nBlockAlign As Integer
    15.     wBitsPerSample As Integer
    16. End Type
    17.  
    18. Private Type ChunkHeader
    19.     lType As Long
    20.     lLen As Long
    21. 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:
    1. Private Sub WaveWriteHeader(ByVal OutFileNum As Integer, WaveFmt As WaveFormat)
    2.     Dim header As FileHeader
    3.     Dim chunk As ChunkHeader
    4.    
    5.     With header
    6.         .lRiff = &H46464952 ' "RIFF"
    7.         .lFileSize = 0
    8.         .lWave = &H45564157 ' "WAVE"
    9.         .lFormat = &H20746D66 ' "fmt "
    10.         .lFormatLength = Len(WaveFmt)
    11.     End With
    12.  
    13.     chunk.lType = &H61746164 ' "data"
    14.     chunk.lLen = 0
    15.    
    16.     Put #OutFileNum, 1, header
    17.     Put #OutFileNum, , WaveFmt
    18.     Put #OutFileNum, , chunk
    19. 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:
    1. Private Sub WaveWriteHeaderEnd(ByVal OutFileNum As Integer)
    2.     Dim header As FileHeader
    3.     Dim HdrFormat As WaveFormat
    4.     Dim chunk As ChunkHeader
    5.     Dim Lng As Long
    6.    
    7.     Lng = LOF(OutFileNum)
    8.     Put #OutFileNum, 5, Lng ' write the FileHeader.lFileSize value
    9.    
    10.     Lng = LOF(OutFileNum) - (Len(header) + Len(HdrFormat) + Len(chunk))
    11.     Put #OutFileNum, Len(header) + Len(HdrFormat) + 5, Lng ' write the ChunkHeader.lLen value
    12. End Sub
    And the function to read from a wave file:
    VB Code:
    1. Private Function WaveReadFormat(ByVal InFileNum As Integer, ByRef lDataLength As Long) As WaveFormat
    2.     Dim header As FileHeader
    3.     Dim HdrFormat As WaveFormat
    4.     Dim chunk As ChunkHeader
    5.     Dim by As Byte
    6.     Dim i As Long
    7.    
    8.     Get #InFileNum, 1, header
    9.    
    10.     If header.lRiff <> &H46464952 Then Exit Function   ' Check for "RIFF" tag and exit if not found.
    11.     If header.lWave <> &H45564157 Then Exit Function   ' Check for "WAVE" tag and exit if not found.
    12.     If header.lFormat <> &H20746D66 Then Exit Function ' Check for "fmt " tag and exit if not found.
    13.    
    14.     ' Check format chunk length; if less than 16, it's not PCM data so we can't use it.
    15.     If header.lFormatLength < 16 Then Exit Function
    16.    
    17.     Get #InFileNum, , HdrFormat ' Retrieve format.
    18.    
    19.     ' Seek to next chunk by discarding any format bytes.
    20.     For i = 1 To header.lFormatLength - 16
    21.         Get #InFileNum, , by
    22.     Next
    23.    
    24.     ' Ignore chunks until we get to the "data" chunk.
    25.     Get #InFileNum, , chunk
    26.     Do While chunk.lType <> &H61746164
    27.         For i = 1 To chunk.lLen
    28.             Get #InFileNum, , by
    29.         Next
    30.         Get #InFileNum, , chunk
    31.     Loop
    32.    
    33.     lDataLength = chunk.lLen ' Retrieve the size of the data.
    34.    
    35.     WaveReadFormat = HdrFormat
    36. 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:
    1. '  Sample how to write to a wave file
    2.  
    3. Private Sub Create_Wave_File_Example()
    4.     Dim Buffer() As Integer
    5.     Dim WaveFmt As WaveFormat
    6.     Dim FileNum As Integer
    7.    
    8.     ' make a 1 second buffer (knowing that the sample rate is 22050)
    9.     ' VB initializes the array with 0 (zero)'s, 0 means silence (for 16 bit wave file)
    10.     ReDim Buffer(22050 * 1) As Integer
    11.    
    12.     ' fill in the Wave Format
    13.     With WaveFmt
    14.         .wFormatTag = 1 ' PCM
    15.        
    16.         .nChannels = 1
    17.         .nSamplesPerSec = 22050
    18.         .wBitsPerSample = 16
    19.        
    20.         .nBlockAlign = .wBitsPerSample * .nChannels / 8
    21.         .nAvgBytesPerSec = .nBlockAlign * .nSamplesPerSec
    22.     End With
    23.    
    24.     ' Create the wave tile
    25.     FileNum = FreeFile
    26.     Open "C:\My Wave File.WAV" For Binary Access Write Lock Write As FileNum
    27.    
    28.     WaveWriteHeader FileNum, WaveFmt ' write the wave headers
    29.    
    30.     Put FileNum, , Buffer ' write 1 second
    31.     Put FileNum, , Buffer ' write another second (to simulate streaming)
    32.    
    33.     ' in this case we will have a wave file with 2 seconds of silence
    34.    
    35.     ' complete the header values (file length, and chunk length)
    36.     WaveWriteHeaderEnd FileNum
    37.    
    38.     ' close the file
    39.     Close FileNum
    40. End Sub
    41.  
    42. ' Sample how to read a wave file
    43.  
    44. Private Sub Read_Wave_File_Example()
    45.     Dim WaveFmt As WaveFormat
    46.     Dim FileNum As Integer
    47.     Dim DataLength As Long
    48.     Dim Buffer() As Integer
    49.    
    50.     ' open the wave file
    51.     FileNum = FreeFile
    52.     Open "C:\My Wave File.WAV" For Binary Access Read Lock Write As FileNum
    53.    
    54.     ' read the header, and get the chunk size (data length)
    55.     WaveFmt = WaveReadFormat(FileNum, DataLength)
    56.    
    57.     ' resize our buffer to hold the entire wave data (DataLength is in Bytes)
    58.     ReDim Buffer((DataLength \ 2) - 1)
    59.    
    60.     ' read the wave data from the file into our buffer
    61.     Get FileNum, , Buffer
    62.    
    63.     Close FileNum ' close the file
    64. End Sub
    Last edited by CVMichael; Feb 24th, 2006 at 10:49 PM.

  3. #3

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    Re: Tutorial under construction

    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:
    1. Private DX As New DirectX8
    2. Private DSEnum As DirectSoundEnum8
    3. Private DIS As DirectSound8
    4.  
    5. Private DSSecBuffer As DirectSoundSecondaryBuffer8
    6.  
    7. Private Sub Form_Load()
    8.     Dim BuffDesc As DSBUFFERDESC
    9.     ' get enumeration object
    10.     Set DSEnum = DX.GetDSEnum
    11.    
    12.     ' select the first sound device, and create the Direct Sound object
    13.     Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))
    14.    
    15.     ' Set the Cooperative Level to normal
    16.     DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL
    17.    
    18.     ' load the wave file, and create the buffer for it
    19.     Set DSSecBuffer = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc)
    20.    
    21.     DSSecBuffer.Play DSBPLAY_DEFAULT
    22. End Sub
    In DirectX 7 you do it the same, except you have to also give a structure of WAVEFORMATEX to CreateSoundBufferFromFile.
    VB Code:
    1. Private DX As New DirectX7
    2. Private DSEnum As DirectSoundEnum
    3. Private DIS As DirectSound
    4.  
    5. Private DSSecBuffer As DirectSoundBuffer
    6.  
    7. Private Sub Form_Load()
    8.     Dim BuffDesc As DSBUFFERDESC, WaveFormat As WAVEFORMATEX
    9.    
    10.     ' get enumeration object
    11.     Set DSEnum = DX.GetDSEnum
    12.    
    13.     ' select the first sound device, and create the Direct Sound object
    14.     Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))
    15.    
    16.     ' Set the Cooperative Level to normal
    17.     DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL
    18.    
    19.     ' load the wave file, and create the buffer for it
    20.     Set DSSecBuffer = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc, WaveFormat)
    21.    
    22.     DSSecBuffer.Play DSBPLAY_DEFAULT
    23. End 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:
    1. Private Sub Form_Load()
    2.     Dim BuffDesc As DSBUFFERDESC
    3.     ' get enumeration object
    4.     Set DSEnum = DX.GetDSEnum
    5.    
    6.     ' select the first sound device, and create the Direct Sound object
    7.     Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))
    8.    
    9.     ' Set the Cooperative Level to normal
    10.     DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL
    11.    
    12.     ' allow frequency changes and volume changes
    13.     BuffDesc.lFlags = DSBCAPS_CTRLFREQUENCY Or DSBCAPS_CTRLVOLUME
    14.    
    15.     ' load the wave file, and create the buffer for it
    16.     Set DSSecBuffer = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc)
    17.    
    18.     ' frequency can range from 100 Hz to ~ 100,000 (depending on your sound card)
    19.     DSSecBuffer.SetFrequency 22050 * 1.8
    20.    
    21.     ' volume is from 0 to -10,000 (where 0 is the lowdest, and -10,000 is silence)
    22.     DSSecBuffer.SetVolume -1500
    23.    
    24.     DSSecBuffer.Play DSBPLAY_DEFAULT
    25. End 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:
    1. Dim DX As New DirectX8
    2. Dim DSEnum As DirectSoundEnum8
    3. Dim DIS As DirectSound8
    4.  
    5. Dim DSSecBuffer As DirectSoundSecondaryBuffer8
    6. Dim DSSecBuffer2 As DirectSoundSecondaryBuffer8
    7.  
    8. Private Sub Form_Load()
    9.     Dim BuffDesc As DSBUFFERDESC
    10.    
    11.     ' get enumeration object
    12.     Set DSEnum = DX.GetDSEnum
    13.    
    14.     ' select the first sound device, and create the Direct Sound object
    15.     Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))
    16.    
    17.     ' Set the Cooperative Level to normal
    18.     DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL
    19.    
    20.     ' allow frequency changes and volume changes
    21.     BuffDesc.lFlags = DSBCAPS_CTRLFREQUENCY Or DSBCAPS_CTRLVOLUME
    22.    
    23.     ' load the wave file, and create the buffer for it
    24.     Set DSSecBuffer = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc)
    25.     Set DSSecBuffer2 = DIS.CreateSoundBufferFromFile("C:\WINDOWS\Media\notify.wav", BuffDesc)
    26.    
    27.     ' volume is from 0 to -10,000 (where 0 is the lowdest, and -10,000 is silence)
    28.     DSSecBuffer.SetVolume -500
    29.     DSSecBuffer.Play DSBPLAY_DEFAULT
    30.    
    31.     DSSecBuffer2.SetFrequency 22050 * 0.7
    32.     DSSecBuffer2.Play DSBPLAY_DEFAULT
    33. End 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:
    1. Private DX As New DirectX8
    2. Private DSEnum As DirectSoundEnum8
    3. Private DIS As DirectSound8
    4.  
    5. Private DSSecBuffer As DirectSoundSecondaryBuffer8
    6.  
    7. Private Sub Form_Load()
    8.     Dim BuffDesc As DSBUFFERDESC
    9.    
    10.     ' get enumeration object
    11.     Set DSEnum = DX.GetDSEnum
    12.    
    13.     ' select the first sound device, and create the Direct Sound object
    14.     Set DIS = DX.DirectSoundCreate(DSEnum.GetGuid(1))
    15.    
    16.     ' Set the Cooperative Level to normal
    17.     DIS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL
    18.    
    19.     ' load the wave file, and create the buffer for it
    20.     Set DSSecBuffer = CreateSoundBufferFromFile("C:\WINDOWS\Media\tada.wav", BuffDesc)
    21.    
    22.     DSSecBuffer.Play DSBPLAY_DEFAULT
    23. End Sub
    24.  
    25. Private Function CreateSoundBufferFromFile(ByVal FileName As String, ByRef BuffDesc As DSBUFFERDESC) As DirectSoundSecondaryBuffer8
    26.     Dim SecBuff As DirectSoundSecondaryBuffer8
    27.     Dim WaveFmt As WaveFormat
    28.     Dim FileNum As Integer, DataLength As Long
    29.     Dim WaveData() As Byte
    30.    
    31.     FileNum = FreeFile
    32.     Open FileName For Binary Access Read Lock Write As FileNum ' open file
    33.     WaveFmt = WaveReadFormat(FileNum, DataLength) ' read format
    34.    
    35.     ' copy the wave format values into the WAVEFORMATEX of the DSBUFFERDESC structure
    36.     With BuffDesc.fxFormat
    37.         .nFormatTag = WaveFmt.wFormatTag
    38.        
    39.         .nChannels = WaveFmt.nChannels
    40.         .nBitsPerSample = WaveFmt.wBitsPerSample
    41.         .lSamplesPerSec = WaveFmt.nSamplesPerSec
    42.        
    43.         .nBlockAlign = WaveFmt.nBlockAlign
    44.         .lAvgBytesPerSec = WaveFmt.nAvgBytesPerSec
    45.     End With
    46.    
    47.     ' the next line is not really needed
    48.     ' DSBCAPS_STICKYFOCUS means it will keep playing even if our application does not have focus
    49.     ' DSBCAPS_STATIC simply means that the buffer is static otherwise DirectX Sound will think that this is a streamming buffer
    50.     BuffDesc.lFlags = DSBCAPS_STICKYFOCUS Or DSBCAPS_STATIC
    51.    
    52.     ' tell the buffer how long it should be, in this case we are loading the entire wave file
    53.     BuffDesc.lBufferBytes = DataLength
    54.    
    55.     ' create the buffer
    56.     Set SecBuff = DIS.CreateSoundBuffer(BuffDesc)
    57.    
    58.     ' resize our array to fit the whole sound file
    59.     ReDim WaveData(DataLength - 1)
    60.    
    61.     ' read the wave data
    62.     Get FileNum, , WaveData
    63.    
    64.     ' copy our array to the DirectX Sound buffer
    65.     SecBuff.WriteBuffer 0, DataLength, WaveData(0), DSBLOCK_DEFAULT
    66.    
    67.     Close FileNum ' close file
    68.    
    69.     ' return the DirectX Sound Buffer
    70.     Set CreateSoundBufferFromFile = SecBuff
    71. End 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 ?
    Attached Files Attached Files
    Last edited by CVMichael; Dec 23rd, 2008 at 09:31 AM.

  4. #4

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    Re: Tutorial under construction

    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:
    1. ' Direct Sound objects
    2. Private DX As New DirectX8
    3. Private SEnum As DirectSoundEnum8
    4. Private DISCap As DirectSoundCapture8
    5.  
    6. ' buffer, and buffer description
    7. Private Buff As DirectSoundCaptureBuffer8
    8. Private BuffDesc As DSCBUFFERDESC
    9.  
    10. ' For the events
    11. Private EventsNotify() As DSBPOSITIONNOTIFY
    12. Private EndEvent As Long, MidEvent As Long, StartEvent As Long
    13.  
    14. ' to know the buffer size
    15. Private BuffLen As Long, HalfBuffLen As Long
    16.  
    17. Public Function Initialize(Optional ByVal SamplesPerSec As Long = 44100, _
    18.                             Optional ByVal BitsPerSample As Integer = 16, _
    19.                             Optional ByVal Channels As Integer = 2, _
    20.                             Optional ByVal HalfBufferLen As Long = 0, _
    21.                             Optional ByVal GUID As String = "") As String
    22.    
    23.     ' if there is any error go to ReturnError
    24.     On Error GoTo ReturnError
    25.    
    26.     Set SEnum = DX.GetDSCaptureEnum ' get the device enumeration object
    27.    
    28.     ' if GUID is empty, then assign the first sound device
    29.     If Len(GUID) = 0 Then GUID = SEnum.GetGuid(1)
    30.    
    31.     ' choose the sound device, and create the Direct Sound object
    32.     Set DISCap = DX.DirectSoundCaptureCreate(GUID)
    33.    
    34.     ' set the format to use for recording
    35.     With BuffDesc.fxFormat
    36.         .nFormatTag = WAVE_FORMAT_PCM
    37.         .nChannels = Channels
    38.         .nBitsPerSample = BitsPerSample
    39.         .lSamplesPerSec = SamplesPerSec
    40.        
    41.         .nBlockAlign = (.nBitsPerSample * .nChannels) \ 8
    42.         .lAvgBytesPerSec = .lSamplesPerSec * .nBlockAlign
    43.        
    44.         If HalfBufferLen <= 0 Then
    45.             ' make half of the buffer to be 100 ms
    46.             HalfBuffLen = .lAvgBytesPerSec / 10
    47.         Else
    48.             ' using a "custom" size buffer
    49.             HalfBuffLen = HalfBufferLen
    50.         End If
    51.        
    52.         ' make sure the buffer is aligned
    53.         HalfBuffLen = HalfBuffLen - (HalfBuffLen Mod .nBlockAlign)
    54.     End With
    55.    
    56.     ' calculate the total size of the buffer
    57.     BuffLen = HalfBuffLen * 2
    58.    
    59.     BuffDesc.lBufferBytes = BuffLen
    60.     BuffDesc.lFlags = DSCBCAPS_DEFAULT
    61.    
    62.     ' create the buffer object
    63.     Set Buff = DISCap.CreateCaptureBuffer(BuffDesc)
    64.    
    65.     ' Create 3 event notifications
    66.     ReDim EventsNotify(0 To 2) As DSBPOSITIONNOTIFY
    67.    
    68.     ' create event to signal that DirectSound write cursor
    69.     ' is at the beginning of the buffer
    70.     StartEvent = DX.CreateEvent(Me)
    71.     EventsNotify(0).hEventNotify = StartEvent
    72.     EventsNotify(0).lOffset = 1
    73.    
    74.     ' create event to signal that DirectSound write cursor
    75.     ' is at half of the buffer
    76.     MidEvent = DX.CreateEvent(Me)
    77.     EventsNotify(1).hEventNotify = MidEvent
    78.     EventsNotify(1).lOffset = HalfBuffLen
    79.    
    80.     ' create the event to signal the sound has stopped
    81.     EndEvent = DX.CreateEvent(Me)
    82.     EventsNotify(2).hEventNotify = EndEvent
    83.     EventsNotify(2).lOffset = DSBPN_OFFSETSTOP
    84.    
    85.     ' Assign the notification points to the buffer
    86.     Buff.SetNotificationPositions 3, EventsNotify()
    87.    
    88.     Initialize = ""
    89.     Exit Function
    90. ReturnError:
    91.     ' return error number, description and source
    92.     Initialize = "Error: " & Err.Number & vbNewLine & _
    93.         "Desription: " & Err.Description & vbNewLine & _
    94.         "Source: " & Err.Source
    95.    
    96.     Err.Clear
    97.     UninitializeSound
    98.     Exit Function
    99. End Function
    100.  
    101. Public Sub UninitializeSound()
    102.     ' distroy all events
    103.     DX.DestroyEvent EventsNotify(0).hEventNotify
    104.     DX.DestroyEvent EventsNotify(1).hEventNotify
    105.     DX.DestroyEvent EventsNotify(2).hEventNotify
    106.    
    107.     Erase EventsNotify
    108.    
    109.     Set Buff = Nothing
    110.     Set DISCap = Nothing
    111.     Set SEnum = Nothing
    112. End 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:
    1. Implements DirectXEvent8
    2.  
    3. Private Sub DirectXEvent8_DXCallback(ByVal eventid As Long)
    4.     Dim WaveBuffer() As Byte
    5.    
    6.     ' make sure that Buff object is actually initialized to a buffer instance
    7.     If Not (Buff Is Nothing) Then
    8.         ReDim WaveBuffer(HalfBuffLen - 1)
    9.    
    10.         Select Case eventid
    11.         Case StartEvent
    12.             ' we got the event that the write cursor is at the beginning of the buffer
    13.             ' therefore read from the middle of the buffer to the end
    14.             Buff.ReadBuffer HalfBuffLen, HalfBuffLen, WaveBuffer(0), DSCBLOCK_DEFAULT
    15.         Case MidEvent
    16.             ' we got an event that the write cursor is at the middle of the buffer
    17.             ' threfore read from the beginning of the buffer to the middle
    18.             Buff.ReadBuffer 0, HalfBuffLen, WaveBuffer(0), DSCBLOCK_DEFAULT
    19.         Case EndEvent
    20.             ' not used right now
    21.         End Select
    22.        
    23.         ' use the wave buffer here
    24.     End If
    25. End 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.
    Attached Files Attached Files
    Last edited by CVMichael; Dec 23rd, 2008 at 09:33 AM.

  5. #5

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    Re: Tutorial under construction

    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:
    1. Private Sub DisplayWaveData8(DataBuff() As Byte, Pic As PictureBox, Stereo As Boolean)
    2.     Dim Stp As Single, HBuffer As Long, Q As Single
    3.     Dim LX As Single, LY As Single, RX As Single, RY As Single
    4.     Dim LVal As Single, RVal As Single, K As Long
    5.    
    6.     If Not Stereo Then
    7.         HBuffer = UBound(DataBuff)
    8.         Stp = HBuffer / (Pic.Width / 15)
    9.        
    10.         Pic.Scale (0, 127)-(HBuffer, -127)
    11.         Pic.PSet (0, 0)
    12.        
    13.         Pic.Cls
    14.        
    15.         For Q = 0 To HBuffer - 2 Step Stp
    16.             Pic.Line -(Fix(Q), DataBuff(Fix(Q)) - 127)
    17.         Next Q
    18.     Else
    19.         HBuffer = UBound(DataBuff) \ 2
    20.         Stp = HBuffer / (Pic.Width / 15)
    21.        
    22.         Pic.Scale (0, 256)-(HBuffer, -256)
    23.         Pic.PSet (0, 0)
    24.        
    25.         Pic.Cls
    26.        
    27.         LX = 0
    28.         LY = -127
    29.         RX = 0
    30.         RY = 127
    31.        
    32.         For Q = 0 To HBuffer - 2 Step Stp
    33.             K = Q
    34.             K = K - (K Mod 2)
    35.            
    36.             LVal = DataBuff(K + 1) - 255
    37.             RVal = DataBuff(K)
    38.            
    39.             Pic.Line (LX, LY)-(K, LVal)
    40.             Pic.Line (RX, RY)-(K, RVal)
    41.            
    42.             LX = K
    43.             LY = LVal
    44.            
    45.             RX = K
    46.             RY = RVal
    47.         Next Q
    48.     End If
    49. End Sub
    50.  
    51. ' the sound is 16 bit, but it comes in Bytes, not Integer
    52. Private Sub DisplayWaveData16_8(DataBuff() As Byte, Pic As PictureBox, Stereo As Boolean)
    53.     Dim Buff() As Integer
    54.    
    55.     ReDim Buff(UBound(DataBuff) \ 2 - 1)
    56.    
    57.     CopyMemory Buff(0), DataBuff(0), UBound(DataBuff) + 1
    58.    
    59.     DisplayWaveData16 Buff, Pic, Stereo
    60. End Sub
    61.  
    62. Private Sub DisplayWaveData16(DataBuff() As Integer, Pic As PictureBox, Stereo As Boolean)
    63.     Dim Stp As Single, HBuffer As Long, Q As Single
    64.     Dim LX As Single, LY As Single, RX As Single, RY As Single
    65.     Dim LVal As Single, RVal As Single, K As Long
    66.    
    67.     If Not Stereo Then
    68.         HBuffer = UBound(DataBuff)
    69.         Stp = HBuffer / (Pic.Width / 15)
    70.        
    71.         Pic.Scale (0, 0.5)-(HBuffer, -0.5)
    72.         Pic.PSet (0, 0)
    73.        
    74.         Pic.Cls
    75.         For Q = 0 To HBuffer - 2 Step Stp
    76.             Pic.Line -(Fix(Q), DataBuff(Fix(Q)) / 65536#)
    77.         Next Q
    78.     Else
    79.         HBuffer = UBound(DataBuff) \ 2
    80.         Stp = HBuffer / (Pic.Width / 15)
    81.        
    82.         Pic.Scale (0, 1)-(HBuffer, -1)
    83.         Pic.PSet (0, 0)
    84.        
    85.         Pic.Cls
    86.        
    87.         LX = 0
    88.         LY = -0.5
    89.         RX = 0
    90.         RY = 0.5
    91.        
    92.         For Q = 0 To HBuffer - 2 Step Stp
    93.             K = Q
    94.             K = K - (K Mod 2)
    95.            
    96.             LVal = DataBuff(K + 1) / 65536# - 0.5
    97.             RVal = DataBuff(K) / 65536# + 0.5
    98.            
    99.             Pic.Line (LX, LY)-(K, LVal)
    100.             Pic.Line (RX, RY)-(K, RVal)
    101.            
    102.             LX = K
    103.             LY = LVal
    104.            
    105.             RX = K
    106.             RY = RVal
    107.         Next Q
    108.     End If
    109. 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).
    Attached Files Attached Files
    Last edited by CVMichael; Mar 2nd, 2006 at 12:38 PM.

  6. #6

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    Re: Tutorial under construction

    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:
    1. ' convert a 16 bit sound from a Byte array to Integer array
    2. Public Function Convert16_8To16(Buffer() As Byte) As Integer()
    3.     Dim Buff() As Integer
    4.    
    5.     ReDim Buff((UBound(Buffer) + 1) / 2 - 1 + 0.1)
    6.    
    7.     CopyMemory Buff(0), Buffer(0), UBound(Buffer) + 1
    8.    
    9.     Convert16_8To16 = Buff
    10. End 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:
    1. ' convert 16 bit to 8 bit
    2. Public Function ConvertWave16to8(Buffer() As Integer) As Byte()
    3.     Dim K As Long, Val As Long
    4.     Dim RetBuff() As Byte
    5.    
    6.     ReDim RetBuff(UBound(Buffer))
    7.    
    8.     For K = 0 To UBound(Buffer)
    9.         Val = Buffer(K)
    10.         Val = (Val + 32768) \ 256
    11.        
    12.         RetBuff(K) = Val
    13.     Next K
    14.    
    15.     ConvertWave16to8 = RetBuff
    16. End 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:
    1. ' convert 8 bit to 16 bit
    2. Public Function ConvertWave8to16(Buffer() As Byte) As Integer()
    3.     Dim K As Long, Val As Long
    4.     Dim RetBuff() As Integer
    5.    
    6.     ReDim RetBuff(UBound(Buffer))
    7.    
    8.     For K = 0 To UBound(Buffer)
    9.         Val = (Buffer(K) - 127) * 256
    10.        
    11.         RetBuff(K) = Val
    12.     Next K
    13.    
    14.     ConvertWave8to16 = RetBuff
    15. End Function
    When converting from Mono to Stereo, you just copy the same value to left channel and right channel.
    VB Code:
    1. ' convert mono to stereo for 16 bit buffer
    2. Public Function ConvertWaveMonoToStereo16(Buffer() As Integer) As Integer()
    3.     Dim K As Long
    4.     Dim RetBuff() As Integer
    5.    
    6.     ReDim RetBuff(UBound(Buffer) * 2)
    7.    
    8.     For K = 0 To UBound(Buffer)
    9.         RetBuff(K * 2 + 0) = Buffer(K)
    10.         RetBuff(K * 2 + 1) = Buffer(K)
    11.     Next K
    12.    
    13.     ConvertWaveMonoToStereo16 = RetBuff
    14. End Function
    15.  
    16. ' convert mono to stereo for 8 bit buffer
    17. Public Function ConvertWaveMonoToStereo8(Buffer() As Byte) As Byte()
    18.     Dim K As Long
    19.     Dim RetBuff() As Byte
    20.    
    21.     ReDim RetBuff(UBound(Buffer) * 2)
    22.    
    23.     For K = 0 To UBound(Buffer)
    24.         RetBuff(K * 2 + 0) = Buffer(K)
    25.         RetBuff(K * 2 + 1) = Buffer(K)
    26.     Next K
    27.    
    28.     ConvertWaveMonoToStereo8 = RetBuff
    29. End 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:
    1. ' convert stereo to mono for 16 bit buffer
    2. Public Function ConvertWaveStereoToMono16(Buffer() As Integer) As Integer()
    3.     Dim K As Long, Val As Long
    4.     Dim RetBuff() As Integer
    5.    
    6.     ReDim Buff((UBound(Buffer) + 1) \ 2 - 1)
    7.    
    8.     For K = 0 To UBound(RetBuff)
    9.         Val = Buffer(K * 2)
    10.         Val = (Val + Buffer(K * 2 + 1)) \ 2
    11.        
    12.         RetBuff(K) = Val
    13.     Next K
    14.    
    15.     ConvertWaveStereoToMono16 = RetBuff
    16. End Function
    17.  
    18. ' convert stereo to mono for 8 bit buffer
    19. Public Function ConvertWaveStereoToMono8(Buffer() As Byte) As Byte()
    20.     Dim K As Long, Val As Long
    21.     Dim RetBuff() As Byte
    22.    
    23.     ReDim Buff((UBound(Buffer) + 1) \ 2 - 1)
    24.    
    25.     For K = 0 To UBound(RetBuff)
    26.         Val = Buffer(K * 2)
    27.         Val = (Val + Buffer(K * 2 + 1)) \ 2
    28.        
    29.         RetBuff(K) = Val
    30.     Next K
    31.    
    32.     ConvertWaveStereoToMono8 = RetBuff
    33. End 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:
    1. ' convert a 16 bit wave, multiply samples by 2
    2. Public Function ConvertWave16MultiplySamplesBy2(Buffer() As Integer, ByVal Stereo As Boolean) As Integer()
    3.     Dim K As Long
    4.     Dim RetBuff() As Integer
    5.    
    6.     ReDim RetBuff(UBound(Buffer) * 2)
    7.    
    8.     If Not Stereo Then
    9.         For K = 0 To UBound(Buffer) - 1
    10.             RetBuff(K * 2) = Buffer(K)
    11.             RetBuff(K * 2 + 1) = (CLng(Buffer(K)) + Buffer(K + 1)) \ 2
    12.         Next K
    13.     Else
    14.         For K = 0 To UBound(Buffer) - 3 Step 2
    15.             RetBuff(K * 2 + 0) = Buffer(K + 0)
    16.             RetBuff(K * 2 + 2) = (CLng(Buffer(K)) + Buffer(K + 2)) \ 2
    17.            
    18.             RetBuff(K * 2 + 1) = Buffer(K + 1)
    19.             RetBuff(K * 2 + 3) = (CLng(Buffer(K + 1)) + Buffer(K + 3)) \ 2
    20.         Next K
    21.     End If
    22.    
    23.     ConvertWave16MultiplySamplesBy2 = RetBuff
    24. End Function
    25.  
    26. ' convert a 8 bit wave, multiply samples by 2
    27. Public Function ConvertWave8MultiplySamplesBy2(Buffer() As Byte, ByVal Stereo As Boolean) As Byte()
    28.     Dim K As Long
    29.     Dim RetBuff() As Byte
    30.    
    31.     ReDim RetBuff(UBound(Buffer) * 2)
    32.    
    33.     If Not Stereo Then
    34.         For K = 0 To UBound(Buffer) - 1
    35.             RetBuff(K * 2) = Buffer(K)
    36.             RetBuff(K * 2 + 1) = (CLng(Buffer(K)) + Buffer(K + 1)) \ 2
    37.         Next K
    38.     Else
    39.         For K = 0 To UBound(Buffer) - 3 Step 2
    40.             RetBuff(K * 2 + 0) = Buffer(K + 0)
    41.             RetBuff(K * 2 + 2) = (CLng(Buffer(K)) + Buffer(K + 2)) \ 2
    42.            
    43.             RetBuff(K * 2 + 1) = Buffer(K + 1)
    44.             RetBuff(K * 2 + 3) = (CLng(Buffer(K + 1)) + Buffer(K + 3)) \ 2
    45.         Next K
    46.     End If
    47.    
    48.     ConvertWave8MultiplySamplesBy2 = RetBuff
    49. End Function
    50.  
    51. ' convert a 16 bit wave, divide samples by 2
    52. Public Function ConvertWave16DivideSamplesBy2(Buffer() As Integer, ByVal Stereo As Boolean) As Integer()
    53.     Dim K As Long
    54.     Dim RetBuff() As Integer
    55.    
    56.     ReDim RetBuff((UBound(Buffer) + 1) \ 2 - 1)
    57.    
    58.     If Not Stereo Then
    59.         For K = 0 To UBound(Buffer) Step 2
    60.             RetBuff(K \ 2) = (CLng(Buffer(K)) + Buffer(K + 1)) \ 2
    61.         Next K
    62.     Else
    63.         For K = 0 To UBound(Buffer) - 4 Step 4
    64.             RetBuff(K \ 2 + 0) = (CLng(Buffer(K + 0)) + Buffer(K + 2)) \ 2
    65.             RetBuff(K \ 2 + 1) = (CLng(Buffer(K + 1)) + Buffer(K + 3)) \ 2
    66.         Next K
    67.     End If
    68.    
    69.     ConvertWave16DivideSamplesBy2 = RetBuff
    70. End Function
    71.  
    72. ' convert a 8 bit wave, divide samples by 2
    73. Public Function ConvertWave8DivideSamplesBy2(Buffer() As Byte, ByVal Stereo As Boolean) As Byte()
    74.     Dim K As Long
    75.     Dim RetBuff() As Byte
    76.    
    77.     ReDim RetBuff((UBound(Buffer) + 1) \ 2 - 1)
    78.    
    79.     If Not Stereo Then
    80.         For K = 0 To UBound(Buffer) Step 2
    81.             RetBuff(K \ 2) = (CLng(Buffer(K)) + Buffer(K + 1)) \ 2
    82.         Next K
    83.     Else
    84.         For K = 0 To UBound(Buffer) - 4 Step 4
    85.             RetBuff(K \ 2 + 0) = (CLng(Buffer(K + 0)) + Buffer(K + 2)) \ 2
    86.             RetBuff(K \ 2 + 1) = (CLng(Buffer(K + 1)) + Buffer(K + 3)) \ 2
    87.         Next K
    88.     End If
    89.     ConvertWave8DivideSamplesBy2 = RetBuff
    90. End 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:
    1. Private Function ReSample16(Buff() As Integer, FromSample As Long, ToSample As Long) As Integer()
    2.     Dim K As Long, BuffSZ As Long
    3.     Dim Ret() As Integer, Per As Double
    4.    
    5.     BuffSZ = UBound(Buff) + 1
    6.    
    7.     ReDim Ret(Fix(BuffSZ * ToSample / FromSample + 0.5))
    8.    
    9.     For K = 0 To UBound(Ret)
    10.         Per = K / UBound(Ret)
    11.        
    12.         Ret(K) = Buff(UBound(Buff) * Per)
    13.     Next K
    14.    
    15.     ReSample16 = Ret
    16. End Function
    Continued in the next post
    Last edited by CVMichael; Dec 23rd, 2008 at 09:21 AM.

  7. #7

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    Re: Tutorial under construction

    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:
    1. Public Function FindYForX(ByVal X As Double, ByVal X1 As Double, ByVal Y1 As Double, _
    2.         ByVal X2 As Double, ByVal Y2 As Double) As Double
    3.    
    4.     Dim M As Double, B As Double
    5.    
    6.     M = (Y1 - Y2) / (X1 - X2)
    7.     B = Y1 - M * X1
    8.    
    9.     FindYForX = M * X + B
    10. End Function
    11.  
    12. Public Function ConvertWave16ReSample(Buff() As Integer, ByVal FromSample As Long, ByVal ToSample As Long, ByVal Stereo As Boolean) As Integer()
    13.     Dim K As Long, Lx As Long, RX As Long
    14.     Dim Ret() As Integer, Per As Double, NewSize As Long
    15.    
    16.     If Not Stereo Then
    17.         NewSize = Fix((UBound(Buff) + 1) * ToSample / FromSample + 0.5)
    18.         ReDim Ret(NewSize - 1)
    19.        
    20.         For K = 0 To UBound(Ret) - 1
    21.             Per = K / UBound(Ret)
    22.            
    23.             Lx = Fix(UBound(Buff) * Per)
    24.            
    25.             Ret(K) = FindYForX(UBound(Buff) * Per, Lx, Buff(Lx), Lx + 1, Buff(Lx + 1))
    26.         Next K
    27.        
    28.         Ret(UBound(Ret)) = Buff(UBound(Buff))
    29.     Else
    30.         NewSize = Fix((UBound(Buff) + 1) * ToSample / FromSample + 0.5)
    31.         NewSize = NewSize - (NewSize Mod 2)
    32.         ReDim Ret(NewSize - 1)
    33.        
    34.         For K = 0 To UBound(Ret) Step 2
    35.             Per = K / (UBound(Ret) + 2)
    36.            
    37.             ' Left channel
    38.             Lx = Fix(UBound(Buff) * Per / 2#) * 2
    39.             Ret(K + 0) = FindYForX(UBound(Buff) * Per, Lx, Buff(Lx), Lx + 2, Buff(Lx + 2))
    40.            
    41.             ' Right channel
    42.             RX = Lx + 1
    43.             Ret(K + 1) = FindYForX(UBound(Buff) * Per + 1, RX, Buff(RX), RX + 2, Buff(RX + 2))
    44.         Next K
    45.        
    46.         Ret(UBound(Ret) - 1) = Buff(UBound(Buff) - 1)
    47.         Ret(UBound(Ret)) = Buff(UBound(Buff))
    48.     End If
    49.    
    50.     ConvertWave16ReSample = Ret
    51. End Function
    52.  
    53. Public Function ConvertWave8ReSample(Buff() As Byte, ByVal FromSample As Long, ByVal ToSample As Long, ByVal Stereo As Boolean) As Byte()
    54.     Dim K As Long, Lx As Long, RX As Long
    55.     Dim Ret() As Byte, Per As Double, NewSize As Long
    56.    
    57.     If Not Stereo Then
    58.         NewSize = Fix((UBound(Buff) + 1) * ToSample / FromSample + 0.5)
    59.         ReDim Ret(NewSize - 1)
    60.        
    61.         For K = 0 To UBound(Ret) - 1
    62.             Per = K / UBound(Ret)
    63.            
    64.             Lx = Fix(UBound(Buff) * Per)
    65.            
    66.             Ret(K) = FindYForX(UBound(Buff) * Per, Lx, Buff(Lx), Lx + 1, Buff(Lx + 1))
    67.         Next K
    68.        
    69.         Ret(UBound(Ret)) = Buff(UBound(Buff))
    70.     Else
    71.         NewSize = Fix((UBound(Buff) + 1) * ToSample / FromSample + 0.5)
    72.         NewSize = NewSize - (NewSize Mod 2)
    73.         ReDim Ret(NewSize - 1)
    74.        
    75.         For K = 0 To UBound(Ret) Step 2
    76.             Per = K / (UBound(Ret) + 2)
    77.            
    78.             ' Left channel
    79.             Lx = Fix(UBound(Buff) * Per / 2#) * 2
    80.             Ret(K + 0) = FindYForX(UBound(Buff) * Per, Lx, Buff(Lx), Lx + 2, Buff(Lx + 2))
    81.            
    82.             ' Right channel
    83.             RX = Lx + 1
    84.             Ret(K + 1) = FindYForX(UBound(Buff) * Per + 1, RX, Buff(RX), RX + 2, Buff(RX + 2))
    85.         Next K
    86.        
    87.         Ret(UBound(Ret) - 1) = Buff(UBound(Buff) - 1)
    88.         Ret(UBound(Ret)) = Buff(UBound(Buff))
    89.     End If
    90.    
    91.     ConvertWave8ReSample = Ret
    92. 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:
    1. Public Function ConvertWave(Buffer As Variant, FromFormat As WAVEFORMATEX, ToFormat As WAVEFORMATEX) As Variant
    2.     Dim Buffer16() As Integer
    3.     Dim Buffer8() As Byte
    4.    
    5.     Dim RetBuff16() As Integer
    6.     Dim RetBuff8() As Byte
    7.    
    8.     If FromFormat.nBitsPerSample = 16 Then
    9.         Select Case VarType(Buffer)
    10.         Case (vbArray Or vbByte)
    11.             Buffer8 = Buffer
    12.             Buffer16 = Convert16_8To16(Buffer8)
    13.             Erase Buffer8
    14.         Case (vbArray Or vbInteger)
    15.             Buffer16 = Buffer
    16.         Case Else
    17.             ConvertWave = vbEmpty
    18.             Exit Function
    19.         End Select
    20.        
    21.         If ToFormat.nBitsPerSample = 8 Then
    22.             RetBuff8 = ConvertWave16to8(Buffer16)
    23.             Erase Buffer16
    24.            
    25.             GoTo To8Bit ' JUMP TO 8 BIT
    26.         ElseIf ToFormat.nBitsPerSample = 16 Then
    27.             RetBuff16 = Buffer16
    28.             Erase Buffer16
    29.         Else
    30.             ConvertWave = vbEmpty
    31.             Exit Function
    32.         End If
    33. To16Bit:
    34.        
    35.         If FromFormat.nChannels = 1 And ToFormat.nChannels = 2 Then
    36.             RetBuff16 = ConvertWaveMonoToStereo16(RetBuff16)
    37.         ElseIf FromFormat.nChannels = 2 And ToFormat.nChannels = 1 Then
    38.             RetBuff16 = ConvertWaveStereoToMono16(RetBuff16)
    39.         ElseIf FromFormat.nChannels <> ToFormat.nChannels Then
    40.             ConvertWave = vbEmpty
    41.             Exit Function
    42.         End If
    43.        
    44.         If FromFormat.lSamplesPerSec <> ToFormat.lSamplesPerSec Then
    45.             Select Case FromFormat.lSamplesPerSec / ToFormat.lSamplesPerSec
    46.             Case 0.25
    47.                 RetBuff16 = ConvertWave16MultiplySamplesBy2(RetBuff16, ToFormat.nChannels = 2)
    48.                 RetBuff16 = ConvertWave16MultiplySamplesBy2(RetBuff16, ToFormat.nChannels = 2)
    49.             Case 0.5
    50.                 RetBuff16 = ConvertWave16MultiplySamplesBy2(RetBuff16, ToFormat.nChannels = 2)
    51.             Case 2
    52.                 RetBuff16 = ConvertWave16DivideSamplesBy2(RetBuff16, ToFormat.nChannels = 2)
    53.             Case 4
    54.                 RetBuff16 = ConvertWave16DivideSamplesBy2(RetBuff16, ToFormat.nChannels = 2)
    55.                 RetBuff16 = ConvertWave16DivideSamplesBy2(RetBuff16, ToFormat.nChannels = 2)
    56.             Case Else
    57.                 RetBuff16 = ConvertWave16ReSample(RetBuff16, FromFormat.lSamplesPerSec, ToFormat.lSamplesPerSec, ToFormat.nChannels = 2)
    58.             End Select
    59.         End If
    60.        
    61.         ConvertWave = RetBuff16
    62.     ElseIf FromFormat.nBitsPerSample = 8 Then
    63.         Select Case VarType(Buffer)
    64.         Case (vbArray Or vbByte)
    65.             Buffer8 = Buffer
    66.         Case (vbArray Or vbInteger)
    67.             Buffer16 = Buffer
    68.            
    69.             ReDim Buffer8((UBound(Buffer16) + 1) * 2 - 1)
    70.             CopyMemory Buffer8(0), Buffer(16), UBound(Buffer8) + 1
    71.            
    72.             Erase Buffer16
    73.         Case Else
    74.             ConvertWave = vbEmpty
    75.             Exit Function
    76.         End Select
    77.        
    78.         If ToFormat.nBitsPerSample = 16 Then
    79.             RetBuff16 = ConvertWave8to16(Buffer8)
    80.             Erase Buffer8
    81.            
    82.             GoTo To16Bit ' JUMP TO 16 BIT
    83.         ElseIf ToFormat.nBitsPerSample = 8 Then
    84.             RetBuff8 = Buffer8
    85.             Erase Buffer8
    86.         Else
    87.             ConvertWave = vbEmpty
    88.             Exit Function
    89.         End If
    90. To8Bit:
    91.        
    92.         If FromFormat.nChannels = 1 And ToFormat.nChannels = 2 Then
    93.             RetBuff8 = ConvertWaveMonoToStereo8(RetBuff8)
    94.         ElseIf FromFormat.nChannels = 2 And ToFormat.nChannels = 1 Then
    95.             RetBuff8 = ConvertWaveStereoToMono8(RetBuff8)
    96.         ElseIf FromFormat.nChannels <> ToFormat.nChannels Then
    97.             ConvertWave = vbEmpty
    98.             Exit Function
    99.         End If
    100.        
    101.         If FromFormat.lSamplesPerSec <> ToFormat.lSamplesPerSec Then
    102.             Select Case FromFormat.lSamplesPerSec / ToFormat.lSamplesPerSec
    103.             Case 0.25
    104.                 RetBuff8 = ConvertWave8MultiplySamplesBy2(RetBuff8, ToFormat.nChannels = 2)
    105.                 RetBuff8 = ConvertWave8MultiplySamplesBy2(RetBuff8, ToFormat.nChannels = 2)
    106.             Case 0.5
    107.                 RetBuff8 = ConvertWave8MultiplySamplesBy2(RetBuff8, ToFormat.nChannels = 2)
    108.             Case 2
    109.                 RetBuff8 = ConvertWave8DivideSamplesBy2(RetBuff8, ToFormat.nChannels = 2)
    110.             Case 4
    111.                 RetBuff8 = ConvertWave8DivideSamplesBy2(RetBuff8, ToFormat.nChannels = 2)
    112.                 RetBuff8 = ConvertWave8DivideSamplesBy2(RetBuff8, ToFormat.nChannels = 2)
    113.             Case Else
    114.                 RetBuff8 = ConvertWave8ReSample(RetBuff8, FromFormat.lSamplesPerSec, ToFormat.lSamplesPerSec, ToFormat.nChannels = 2)
    115.             End Select
    116.         End If
    117.        
    118.         ConvertWave = RetBuff8
    119.     Else
    120.         ConvertWave = vbEmpty
    121.         Exit Function
    122.     End If
    123. End Function
    Last edited by CVMichael; Mar 19th, 2008 at 09:29 PM.

  8. #8

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    Re: Tutorial under construction

    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:
    1. ' Change volume for 8 Bit Mono sound
    2. Public Sub ChangeVolume8Mono(Buffer8() As Byte, Percent As Single)
    3.     Dim K As Long
    4.     Dim Sample As Long
    5.    
    6.     For K = LBound(Buffer8) To UBound(Buffer8)
    7.         Sample = ((CSng(Buffer8(K)) - 127) * Percent) + 127
    8.        
    9.         If Sample < 0 Then Sample = 0
    10.         If Sample > 255 Then Sample = 255
    11.        
    12.         Buffer8(K) = Sample
    13.     Next K
    14. End Sub
    15.  
    16. ' Change volume for 8 Bit Stereo sound
    17. Public Sub ChangeVolume8Stereo(Buffer8() As Byte, LPercent As Single, RPercent As Single)
    18.     Dim K As Long
    19.     Dim LSample As Long
    20.     Dim RSample As Long
    21.    
    22.     For K = LBound(Buffer8) To UBound(Buffer8) Step 2
    23.         LSample = ((CSng(Buffer8(K)) - 127) * LPercent) + 127
    24.         RSample = ((CSng(Buffer8(K + 1)) - 127) * RPercent) + 127
    25.        
    26.         If LSample < 0 Then LSample = 0
    27.         If LSample > 255 Then LSample = 255
    28.        
    29.         If RSample < 0 Then RSample = 0
    30.         If RSample > 255 Then RSample = 255
    31.        
    32.         Buffer8(K) = LSample
    33.         Buffer8(K + 1) = RSample
    34.     Next K
    35. End 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:
    1. ' Change volume for 16 Bit Mono sound
    2. Public Sub ChangeVolume16Mono(Buffer16() As Integer, Percent As Single)
    3.     Dim K As Long
    4.     Dim Sample As Long
    5.    
    6.     For K = LBound(Buffer16) To UBound(Buffer16)
    7.         Sample = Buffer16(K) * Percent
    8.        
    9.         If Sample < -32768 Then Sample = -32768
    10.         If Sample > 32767 Then Sample = 32767
    11.        
    12.         Buffer16(K) = Sample
    13.     Next K
    14. End Sub
    15.  
    16. ' Change volume for 16 Bit Stereo sound
    17. Public Sub ChangeVolume16Stereo(Buffer16() As Integer, LPercent As Single, RPercent As Single)
    18.     Dim K As Long
    19.     Dim LSample As Long
    20.     Dim RSample As Long
    21.    
    22.     For K = LBound(Buffer16) To UBound(Buffer16) Step 2
    23.         LSample = Buffer16(K) * LPercent
    24.         RSample = Buffer16(K + 1) * RPercent
    25.        
    26.         If LSample < -32768 Then LSample = -32768
    27.         If LSample > 32767 Then LSample = 32767
    28.        
    29.         If RSample < -32768 Then RSample = -32768
    30.         If RSample > 32767 Then RSample = 32767
    31.        
    32.         Buffer16(K) = LSample
    33.         Buffer16(K + 1) = RSample
    34.     Next K
    35. End Sub
    Last edited by CVMichael; Dec 23rd, 2008 at 09:20 AM.

  9. #9

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    Re: Tutorial under construction

    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:
    1. Public Sub SoundStereoToMono16(InStereo() As Integer, OutLeftChannel() As Integer, OutRightChannel() As Integer)
    2.     Dim InPos As Long
    3.     Dim OutPos As Long
    4.    
    5.     ReDim OutLeftChannel((UBound(InStereo) + 1) \ 2 - 1)
    6.     ReDim OutRightChannel(UBound(OutLeftChannel))
    7.    
    8.     For InPos = 0 To UBound(InStereo) Step 2
    9.         OutLeftChannel(OutPos) = InStereo(InPos)
    10.         OutRightChannel(OutPos) = InStereo(InPos + 1)
    11.        
    12.         OutPos = OutPos + 1
    13.     Next InPos
    14. End Sub
    15.  
    16. Public Sub SoundStereoToMono8(InStereo() As Byte, OutLeftChannel() As Byte, OutRightChannel() As Byte)
    17.     Dim InPos As Long
    18.     Dim OutPos As Long
    19.    
    20.     ReDim OutLeftChannel((UBound(InStereo) + 1) \ 2 - 1)
    21.     ReDim OutRightChannel(UBound(OutLeftChannel))
    22.    
    23.     For InPos = 0 To UBound(InStereo) Step 2
    24.         OutLeftChannel(OutPos) = InStereo(InPos)
    25.         OutRightChannel(OutPos) = InStereo(InPos + 1)
    26.        
    27.         OutPos = OutPos + 1
    28.     Next InPos
    29. End Sub

  10. #10

    Thread Starter
    PowerPoster
    Join Date
    Feb 2002
    Location
    Canada, Toronto
    Posts
    5,802

    Re: Tutorial under construction

    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:
    1. Public Function WaveMIX2(Buffer1() As Integer, Buffer2() As Integer, Optional Div As Single = 1) As Integer()
    2.     Dim K As Long, Sample As Single
    3.     Dim Ret() As Integer
    4.    
    5.     ReDim Ret(UBound(Buffer1))
    6.    
    7.     For K = 0 To UBound(Buffer1)
    8.         Sample = (CSng(Buffer1(K)) + Buffer2(K)) * Div
    9.        
    10.         If Sample < -32768 Then Sample = -32768
    11.         If Sample > 32767 Then Sample = 32767
    12.        
    13.         Ret(K) = Sample
    14.     Next K
    15.    
    16.     WaveMIX2 = Ret
    17. End Function
    18.  
    19. Public Function WaveMIX3(Buffer1() As Integer, Buffer2() As Integer, Buffer3() As Integer, Optional Div As Single = 1) As Integer()
    20.     Dim K As Long, Sample As Single
    21.     Dim Ret() As Integer
    22.    
    23.     ReDim Ret(UBound(Buffer1))
    24.    
    25.     For K = 0 To UBound(Buffer1)
    26.         Sample = (CSng(Buffer1(K)) + Buffer2(K) + Buffer3(K)) * Div
    27.        
    28.         If Sample < -32768 Then Sample = -32768
    29.         If Sample > 32767 Then Sample = 32767
    30.        
    31.         Ret(K) = Sample
    32.     Next K
    33.    
    34.     WaveMIX3 = Ret
    35. End Function
    Last edited by CVMichael; Dec 23rd, 2008 at 09:34 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width