|
-
Jan 12th, 2018, 04:48 AM
#6
Re: How to get a pointer to an element in a byte array?
 Originally Posted by dreammanor
The function that I want to realize now is:
How to use mmioRead to read a piece of wave data into a byte-array.
I guess it should be done with array-pointer.
Edit:
I think of another way, that is, write the file-header to buffer1, then write the wave data to buffer2, and then merge buffer1 and buffer2 into buffer3 (or attach buffer2 to buffer1). But in this case, the speed will be much slower.
A few years ago, I wrote a little WavInfo-Class, which works (RC5-) Stream-Based -
and it parses out all the Info from a *.wav-file without using the mmio-API.
Beside the usual Wav-Format-Fields, it also gives reliable info about the Offset to the real PCM-Data
(which does not necessarily need to start at offset 44).
Here it is (I named it cWavInfo):
Code:
Option Explicit 'RC5-cStream-based Wav-info-reader (Author Olaf Schmidt, 2012)
'precalculated FourCCs
Private Const RIFF As Long = &H46464952, Wave As Long = &H45564157
Private Const data As Long = &H61746164, FMT_ As Long = &H20746D66
Private Type tRiffChunk
ID As Long
Size As Long
End Type
Private Type tWaveHeader
RiffChunk As tRiffChunk
RiffChunkFormat As Long
RiffSubChunk As tRiffChunk
WaveAudioFormat As Integer
WaveNumChannels As Integer
WaveSampleRate As Long
WaveByteRate As Long
WaveBlockAlign As Integer
WaveBitsPerSample As Integer
cbSize As Integer
wValidBitsPerSample As Integer
dwChannelMask As Long
SubFormat(0 To 7) As Integer
End Type
Private Header As tWaveHeader
Private mWaveMSec As Long, mWaveSamples As Long, mWaveDataOffs As Long, mWaveDataLen As Long
Public Sub ReadWaveInfoFromStream(Strm As cStream)
Dim Pos As Long, RiffChunk As tRiffChunk, EmptyHeader As tWaveHeader
Header = EmptyHeader
mWaveMSec = 0: mWaveSamples = 0
mWaveDataOffs = 0: mWaveDataLen = 0
Strm.SetPosition 0
Strm.ReadToPtr VarPtr(Header), Len(Header)
If Header.RiffChunk.ID <> RIFF Then Err.Raise vbObjectError, , "The file is not in RIFF format."
If Header.RiffChunkFormat <> Wave Then Err.Raise vbObjectError, , "The file is in RIFF format but is not a WAVE file."
Do Until Header.RiffSubChunk.ID = FMT_
Pos = Pos + Len(RiffChunk) + Header.RiffSubChunk.Size
If Pos <= 0 Or Pos >= Strm.GetSize Then Err.Raise vbObjectError, , "The file is a WAVE file, but the header is corrupt."
Strm.SetPosition Pos
Strm.ReadToPtr VarPtr(Header), Len(Header)
Loop
Strm.SetPosition Pos + 20 + Header.RiffSubChunk.Size
Strm.ReadToPtr VarPtr(RiffChunk), Len(RiffChunk)
Do Until RiffChunk.ID = data
Strm.SetPosition RiffChunk.Size, STRM_SeekFromCurPos
If Strm.GetPosition = Strm.GetSize Then Err.Raise vbObjectError, , "The file is in RIFF format but is not a WAVE file."
Strm.ReadToPtr VarPtr(RiffChunk), Len(RiffChunk)
Loop
If Header.RiffSubChunk.Size = 40 Then
Header.WaveAudioFormat = Header.SubFormat(0)
Else
Header.wValidBitsPerSample = Header.WaveBitsPerSample
End If
mWaveDataLen = RiffChunk.Size
mWaveDataOffs = Strm.GetPosition
mWaveSamples = mWaveDataLen / Header.WaveNumChannels / (Header.WaveBitsPerSample / 8)
mWaveMSec = (mWaveSamples / Header.WaveSampleRate) * 1000
End Sub
Public Property Get AudioFormat() As Long
AudioFormat = Header.WaveAudioFormat
End Property
Public Property Get AudioFormatString() As String
AudioFormatString = Choose(AudioFormat + 1, "Unknown", "PCM", "ADPCM", "IEEE_FLOAT")
End Property
Public Property Get Channels() As Integer
Channels = Header.WaveNumChannels
End Property
Public Property Get SampleRate() As Long
SampleRate = Header.WaveSampleRate
End Property
Public Property Get ByteRate() As Long
ByteRate = Header.WaveByteRate
End Property
Public Property Get BitsPerSample() As Integer
BitsPerSample = Header.WaveBitsPerSample
End Property
Public Property Get BlockAlign() As Integer
BlockAlign = Header.WaveBlockAlign
End Property
Public Property Get ValidBitsPerSample() As Integer
ValidBitsPerSample = Header.wValidBitsPerSample
End Property
Public Property Get Samples() As Long
Samples = mWaveSamples
End Property
Public Property Get DataOffs() As Long
DataOffs = mWaveDataOffs
End Property
Public Property Get DataLen() As Long
DataLen = mWaveDataLen
End Property
Public Property Get DataLenSec() As Double
DataLenSec = mWaveMSec / 1000
End Property
Public Property Get DataLen_msec() As Long
DataLen_msec = mWaveMSec
End Property
And if your intent is, to describe (and later play) shorter "Selection-Snippets" from a larger Wav-File,
you could use a cSelection-Class like the following (which has 4 Read/Write-Props and 2 "calculated ones"):
Code:
Option Explicit
Public StreamTotalLengthSec As Double, StartPerc As Double, EndPerc As Double, Text As String
Public Property Get StreamOffsetSeconds() As Double
StreamOffsetSeconds = StartPerc * StreamTotalLengthSec
End Property
Public Property Get LengthSeconds() As Double
LengthSeconds = EndPerc * StreamTotalLengthSec - StreamOffsetSeconds
End Property
Well, once you're "armed" with the two little Classes above, the remaining Form-Demo-Code becomes as simple as that:
(define a Wav-File of your choice in Form_Load, and then click the Form).
Code:
Option Explicit
Private Declare Function PlaySound& Lib "winmm" (data As Any, Optional ByVal hMod&, Optional ByVal Flags& = 5)
Private Stream As cStream, WavInfo As New cWavInfo
Private Sub Form_Load()
Set Stream = New_c.FSO.OpenFileStream("c:\temp\SampleLoop.wav")
WavInfo.ReadWaveInfoFromStream Stream
End Sub
Private Sub Form_Click()
'init a Selection-Object with the Duration from cWavInfo and a Percent-Range
Dim Sel As New cSelection
Sel.StreamTotalLengthSec = WavInfo.DataLenSec
Sel.StartPerc = 0.15
Sel.EndPerc = 0.35
'now play the selection-snippet
PlaySelection Sel, WavInfo
End Sub
Public Sub PlaySelection(Sel As cSelection, WavInfo As cWavInfo)
If Sel Is Nothing Or Stream Is Nothing Then Exit Sub
With WavInfo
Dim B() As Byte: B = "" 'init B to an empty Array
Stream.SetPosition .DataOffs + Int(Sel.StreamOffsetSeconds * .SampleRate) * .BlockAlign
Stream.ReadToByteArr B, Int(Sel.LengthSeconds * .SampleRate) * .BlockAlign
PlayWavBuf .SampleRate, .Channels, .BitsPerSample, B
End With
End Sub
Public Sub PlayWavBuf(ByVal Freq As Long, ByVal Channels As Long, ByVal Bits As Long, WavBuf() As Byte)
PlaySound ByVal 0& 'cancel any (potentially) still asynchronously playing sounds
Static B() As Byte
Dim WavBufLen As Long, Strm As cStream
WavBufLen = UBound(WavBuf) + 1
Set Strm = New_c.Stream 'let's create an InMemory-Stream
Strm.WriteFromByteArr StrConv("RIFF WAVEfmt data ", vbFromUnicode)
Strm.WriteFromByteArr WavBuf
Strm.SetPosition 4: Strm.WriteFromPtr VarPtr(WavBufLen + 36), 4 'FileSize (without the 8 Riff-Hdr-Bytes)
Strm.SetPosition 16: Strm.WriteFromPtr VarPtr(16&), 4 'Format-Data-Length
Strm.WriteFromPtr VarPtr(1 + 65536 * Channels), 4 'PCM=1 + Channels
Strm.WriteFromPtr VarPtr(Freq), 4 'Samples per second
Strm.WriteFromPtr VarPtr(Freq * Channels * Bits \ 8), 4 'Bytes per second
Strm.WriteFromPtr VarPtr(Channels * Bits \ 8 + 65536 * Bits), 4 'Bytes per Sample + Bits
Strm.SetPosition 40: Strm.WriteFromPtr VarPtr(WavBufLen), 4 'Wav-Data-length
Strm.SetPosition 0 'prepare for a re-read of the whole stream (now including the header)
If Strm.ReadToByteArr(B) Then PlaySound B(0) 'read and finally play the (now wav-hdr-prefixed) buffer
End Sub
Private Sub Form_Terminate()
PlaySound ByVal 0& 'cancel any (potentially) still asynchronously playing sounds
New_c.CleanupRichClientDll
End Sub
The above PlaySelection-Function (which works in conjunction with the WavInfo-Class and the RC5-cStream-Class),
answers a lot of your recent questions I assume.
HTH
Olaf
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|