With Direct3D9 I did the type library and module support functions for DirectSound. The archive contains a type library dsvb.tlb and module DS_Functions.bas. In the future, I add a class module to support asynchronous notification until you can use clsTrickWait.cls. The module DS_Functions contains the following functions:
DSCreateSoundBufferFromFile - creates an object with interface IDirectSoundBuffer8 from a file. Supported only WAVE and MP3 files is. MP3 files can contain only the ID3v1 and ID3v2 tags, any other may not be recognized/will not work. Too long (by time) files are not supported. For streaming you need to write streaming decoding based on the function code DSCreateSoundBufferFromMemory;
DSCreateSoundBufferFromMemory - the same, but instead of the file is passed a pointer to the data file in memory and size.
Also in the archive contains an example of a player that implements some methods IDirectSoundBuffer8 interface (volume, pan, frequency, effects). TLB especially did not well tested, so something may not work. If something is not working please write here.
Im testing this code, and it works well, except for this mp3.
its a small mp3, 390 bytes, the mp3 works in media player / directshow, but not with this code.
it gives me error when I try to load it (automation)
all other mp3 I have works well.
anyone have a clue why?
baka, that's because you use effects. Just remove the DSBCAPS_CTRLFX flag from the DSBUFFERDESC structure. If you want to use effects you should make the sound bigger (just test the outSize variable and make it little big bigger).
That isn't applicable to this code because it doesn't use threading so we don't have such situation when a thread can change the memory when we read it. Thus, we can safely use this function.
so, I have done a lot of changes, this to load from an Offset and also to create multiple sounds from 1 source.
I create 3 functions instead of 1, the first one:
Code:
Private Sub DSCreateOpen(ByRef strFileName$)
Dim hFile As Long
Dim hMap As Long
hFile = CreateFile(StrPtr(strFileName), GENERIC_READ, FILE_SHARE_READ, ByVal 0&, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
bufSize = GetFileSize(hFile, ByVal 0)
hMap = CreateFileMapping(hFile, ByVal 0&, PAGE_READONLY, 0, 0, 0): CloseHandle hFile
buflpData = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0): CloseHandle hMap
End Sub
the next function is used to create IDirectSoundBuffer , and can be called multiple times if needed:
Public variables: buflpData, bufSize, buffer(), lpData and bufDesc
Code:
Private Function DSCreateSoundBufferFromMemory(Optional ByVal Offset&, Optional ByVal szData&) As IDirectSoundBuffer
lpData = buflpData + Offset
If szData = 0 Then szData = bufSize
(just the important part, the rest is like the original)
and the last function is to remove the Public buffer and to Unmap
Code:
Private Sub DSCreateClose()
Erase buffer
UnmapViewOfFile lpData
End Sub
everything works, but Im not sure its the best way,
- first the "offset", right now I read the entire file, the Offset will jump ahead to a position in the memory address.
what I want is to read "only" the part of the file that are used, but I dont know how, everything I tried resulted in crash or not working!
- second is the "multiple" creating, is there any other way? or is that the right way to add the same sounds multiple times from 1 source?
(I create 5 hit-sounds so when I hit many monsters theres multiple hit-sounds not just one)
everything works, but Im not sure its the best way,
- first the "offset", right now I read the entire file, the Offset will jump ahead to a position in the memory address.
what I want is to read "only" the part of the file that are used, but I dont know how, everything I tried resulted in crash or not working!
You can map the only the needed part of the file (see dwFileOffsetLow in MapViewOfFile function). Note it should be multiple to memory allocation granularity. The better way is to encapsulate the logic to a class. You could also use no file mapping at all just read the content thru ReadFile. (FileMapping was added in order to make the same logic of opening a file either from a physical file or memory.
second is the "multiple" creating, is there any other way? or is that the right way to add the same sounds multiple times from 1 source?
(I create 5 hit-sounds so when I hit many monsters theres multiple hit-sounds not just one)
ah thanks a lot! I now use _lopen and _llseek and it works well!
here's the code if anyone else want to use it: (its a class, and u need to call Init first)
Code:
Dim ds As DirectSound8
Dim format As MPEGLAYER3WAVEFORMAT
Dim dstFormat As WAVEFORMATEX
Dim acmHdr As ACMSTREAMHEADER
Dim bufDesc As DSBUFFERDESC
Sub init(ByVal hWnd&)
Dim b As curBuffer
Set ds = New DirectSound8
ds.Initialize ByVal 0
ds.SetCooperativeLevel hWnd, DSSCL_NORMAL
b.b(0) = 450377142658.6656@: b.b(1) = 900743977448.248@: b.b(2) = 1351114248211.6672@
b.b(3) = 1801487954948.9248@: b.b(4) = 2702228496423.3344@: b.b(5) = 3602975909897.8496@
b.b(6) = 4503737067267.712@: b.b(7) = 18941235272.0895@: b.b(8) = 4735201446.045@
b.b(9) = 10307921515.2@: b.b(10) = 13743895348.4@: b.b(11) = 3435973838.4@
memcpy Constants.bitrate(0, 1), b.b(0), 96
With format
.wFormatTag = WAVE_FORMAT_MPEGLAYER3
.cbSize = MPEGLAYER3_WFX_EXTRA_BYTES
.wBitsPerSample = 0
.nBlockAlign = 1
.nFramesPerBlock = 1
.nCodecDelay = 0
.fdwFlags = MPEGLAYER3_FLAG_PADDING_OFF
.wID = MPEGLAYER3_ID_MPEG
End With
With dstFormat
.cbSize = Len(dstFormat)
.wBitsPerSample = 16
.wFormatTag = WAVE_FORMAT_PCM
End With
acmHdr.cbStruct = Len(acmHdr)
bufDesc.dwFlags = DSBCAPS_CTRLVOLUME
bufDesc.dwSize = Len(bufDesc)
bufDesc.lpwfxFormat = VarPtr(dstFormat)
End Sub
Private Sub Class_Terminate()
Set ds = Nothing
End Sub
Function DSReadFile(ByRef FileName$, Optional ByVal Offset&, Optional ByVal FileSize&) As IDirectSoundBuffer
Dim hFile As Long
Dim b() As Byte
Dim buffer() As Byte
Dim L As Long
Dim D As Long
Dim ptr As Long
Dim hStream As Long
Dim index As Long
Dim AcmSize As Long
Dim AcmTotal As Long
hFile = lOpen(FileName, 0)
If FileSize = 0 Then FileSize = GetFileSize(hFile, ByVal 0)
If Offset > 0 Then llseek hFile, Offset, FILE_BEGIN
ReDim b(FileSize - 1)
lread hFile, b(0), FileSize
lclose hFile
D = FileSize - 128
If b(D) = &H54 And b(D + 1) = &H41 And b(D + 2) = &H47 Then FileSize = FileSize - 128
If b(0) = &H49 And b(1) = &H44 And b(2) = &H33 Then
If b(5) And &H10 Then FileSize = FileSize - 10
hFile = b(6) * &H200000
hFile = hFile Or (b(7) * &H4000&)
hFile = hFile Or (b(8) * &H80&)
hFile = hFile Or b(9)
hFile = hFile + 10
L = L + hFile
FileSize = FileSize - hFile
Else
D = FileSize - 10
If b(D) = &H33 And b(D + 1) = &H44 And b(D + 2) = &H49 Then
hFile = b(D + 6) * &H200000
hFile = hFile Or (b(D + 7) * &H4000&)
hFile = hFile Or (b(D + 8) * &H80&)
hFile = hFile Or b(D + 9)
FileSize = FileSize - hFile - 20
End If
End If
Do
If b(L) = &HFF And (b(L + 1) And &HE0) = &HE0 Then
D = (b(L + 1) And &H18) \ 8
With format
If D = 3 Then
.nAvgBytesPerSec = Constants.bitrate(0, (b(L + 2) And &HF0) \ &H10)
.nSamplesPerSec = Constants.smprate(0, (b(L + 2) And &HC) \ &H4)
Else
.nAvgBytesPerSec = Constants.bitrate(1, (b(L + 2) And &HF0) \ &H10)
If D = 2 Then .nSamplesPerSec = Constants.smprate(1, (b(L + 2) And &HC) \ &H4) Else .nSamplesPerSec = Constants.smprate(2, (b(L + 2) And &HC) \ &H4)
End If
If D = 3 Then
.nBlockSize = Int(144000 * .nAvgBytesPerSec / .nSamplesPerSec) + CInt((b(L + 2) And &H2) \ 2)
Else
.nBlockSize = Int(72000 * .nAvgBytesPerSec / .nSamplesPerSec) + CInt((b(L + 2) And &H2) \ 2)
End If
.nChannels = -(((b(L + 3) And &HC0) \ 64) <> 3) + 1
.nAvgBytesPerSec = .nAvgBytesPerSec * 128
End With
Exit Do
End If
L = L + 1
FileSize = FileSize - 1
Loop
With dstFormat
.nChannels = format.nChannels
.nSamplesPerSec = format.nSamplesPerSec
.nBlockAlign = (.wBitsPerSample \ 8) * .nChannels
.nAvgBytesPerSec = .nBlockAlign * .nSamplesPerSec
End With
acmStreamOpen hStream, 0, format, dstFormat, ByVal 0&, ByVal 0&, ByVal 0&, 0
Do While FileSize > 0
acmStreamSize hStream, FileSize, AcmSize, ACM_STREAMSIZEF_SOURCE
AcmTotal = AcmTotal + AcmSize
ReDim Preserve buffer(AcmTotal - 1)
With acmHdr
.lppbDst = VarPtr(buffer(index))
.lppbSrc = VarPtr(b(L))
.cbDstLength = AcmSize
.cbSrcLength = FileSize
End With
acmStreamPrepareHeader hStream, acmHdr, 0
acmStreamConvert hStream, acmHdr, ACM_STREAMCONVERTF_BLOCKALIGN
acmStreamUnprepareHeader hStream, acmHdr, 0
FileSize = FileSize - acmHdr.cbSrcLengthUsed
L = L + acmHdr.cbSrcLengthUsed
index = index + acmHdr.cbDstLengthUsed
Loop
acmStreamClose hStream, 0
bufDesc.dwBufferBytes = index
ds.CreateSoundBuffer bufDesc, DSReadFile, ByVal 0&
DSReadFile.Lock 0, 0, ptr, index, 0, 0, DSBLOCK_ENTIREBUFFER
memcpy ByVal ptr, buffer(0), index
DSReadFile.Unlock ptr, index, 0, 0
End Function
ok, another problem, that I can not recreate (since its working for me), but theres one user that can not use it.
the reason is: acmStreamSize
that is not returning the right size, so its impossible to continue.
it could be a "codec" problem, that is missing on the system.
I read in the internet that if that person doesn't have the right codec in the "list" it will not work,
so I wonder, how to check if the person have WAVE_FORMAT_PCM and/or WAVE_FORMAT_MPEGLAYER3
Microsoft IMA ADPCM CODEC
Microsoft CCITT G.711 A-Law and u-Law CODEC
Microsoft GSM 6.10 Audio CODEC
Microsoft ADPCM CODEC
Fraunhofer IIS MPEG Layer-3 Codec (decode only)
Microsoft PCM Converter
so, Microsoft PCM Converter should work with WAVE_FORMAT_PCM
and Fraunhofer IIS MPEG Layer-3 Codec should work with WAVE_FORMAT_MPEGLAYER3
but how do I know that?
Im asking that person to check what codecs he has in his computer.
edit;
that user tried that program and the result is:
Code:
Microsoft IMA ADPCM CODEC
Microsoft CCITT G.711 A-Law and u-Law CODEC
Microsoft GSM 6.10 Audio CODEC
Microsoft ADPCM CODEC
Microsoft PCM Converter
that means Fraunhofer IIS MPEG Layer-3 Codec is missing.
what can be done?
instruction to install the codec?
include the codec with the program?
some other solution?
a solution would be to encode the mp3 to pcm and save them in a file.
this is possible with small mp3, sound-effects.
I have around 50 sound-effects, 161kb in size total, in pcm format I get 2.3MB total, its acceptable size.
but background music would increase size too much, since most of the time mp3>pcm would increase around 15x.
using ADPCM CODEC could also be another way, convert .mp3 to .wav, would increase size 2.5x, still too much.
I tried using ADPCM wav directly into the directsound8, it start playing but not the right speed, I can't change much in dstFormat,
no changes helps, and most of the time it crashes on me. so I suppose only PCM works.
I have a error handler that will prevent a crash if I have the sound device disabled.
but I got report from people using "DSP program" that it will crash the program when starting.
I have:
On Error GoTo noSoundDevice
in the initialization function, this so if something happens it will disable it.
but that doesn't seem to help, it will pass the initialization.
is there anything we can do to prevent a crash for people using DSP?
or somehow a way to detect if a DSP is intalled/active?
Seems the method odl for IDirectSoundBuffer8:GetObjectInPath() is incorrect.
I need it to set the parameters for the effects.
The VB compiler tells me that userdefined types cannot be set ByVal This is due to the two CLSID parameters for this method.
Your ODL:
By the way: If you eventually have some time left it would be great if you'd add the several effect structures (DSFXParamEq, DSFXWavesReverb, ...) to dsvb.tlb.
Seems the method odl for IDirectSoundBuffer8:GetObjectInPath() is incorrect.
I need it to set the parameters for the effects.
The VB compiler tells me that userdefined types cannot be set ByVal This is due to the two CLSID parameters for this method.
Your ODL:
By the way: If you eventually have some time left it would be great if you'd add the several effect structures (DSFXParamEq, DSFXWavesReverb, ...) to dsvb.tlb.
Hope you did not already begin that. I rewrote the odl myself.
I attach the tlb and the odl which I derived from yours but altered several things beneath adding the structures and interfaces for the effects. (E.g. removed any helpstrings).
I tested it and all effects work. At the moment having no time to upload a demo cause I am on vacation for the next three weeks.
So I'll do that later. (I have encapsulated your DS module and some code of the form into just one class. Two buffers can then be played and handled independantly using effects. In addition the two mp3s can be merged, faded together. Let's see...)
Just a short code snippet showing the principle before I leave here ´....
Code:
Sub SetEQ(DSBuf As DSVBLib.IDirectSoundBuffer8)
Dim effdesc As DSVBLib.DSEFFECTDESC
Dim uidEff As DSVBLib.DXGUID
Dim uidAllEff As DSVBLib.DXGUID
Dim IUnkEffEQ As DSVBLib.IDirectSoundFXParamEq
Dim TEffEQ As DSVBLib.DSFXParamEq
Dim ret As Long
effdesc.dwSize = Len(effdesc)
effdesc.dwFlags = DSVBLib.DSFX_LOCSOFTWARE
DSBuf.Stop 'pause the buffer
IIDFromString StrPtr(DSVBLib.IID_All_Objects), uidAllEff
IIDFromString StrPtr(DSVBLib.CLSID_DSFX_STANDARD_PARAMEQ), effdesc.guidDSFXClass
IIDFromString StrPtr(DSVBLib.IID_IDirectSoundFXParamEq), uidEff
'Set effect on buffer:
DSBuf.SetFX 1, effdesc, ret
'Note: more effects could be set at once!
'effdesc then had to be an array of type DSEFFECTDESC
'Get the interface of the DMO effect object:
'(This is a search in the underlying filter graph)
Set IUnkEffEQ = DSBuf.GetObjectInPath(uidAllEff, 0&, uidEff)
'Note: Throws an automation error if something's wrong!
'(Look for the Err.Number in enum DSVBLib.DS_ERR)
If Not IUnkEffEQ Is Nothing Then
'Get the default parameters of the effect:
IUnkEffEQ.GetAllParameters TEffEQ
With TEffEQ
Debug.Print "EQ Params:", .fBandwidth, .fCenter, .fGain
'Change default params to kinda bass woofer
.fCenter = 150 'Hz
.fBandwidth = 8 'semitones
.fGain = 15 'full bandpass amplification; (range -15...15)
End With
'Set the altered params to the effect:
IUnkEffEQ.SetAllParameters TEffEQ
End If
'Play the paused buffer:
'effect change now takes place immediately resuming at last play position
DSBuf.Play 0, 0, 0
Set IUnkEffEQ = Nothing
End Sub
as annonced I attach my DirectSound demo here including the type library I created extending TheTricks one.
I had to split it in two files due to the upload limitations. So please first extract DSoundDemo.zip and then the contents of FurtherMP3s.zip into the subfolder \SampleMP3\ of the demo.
The project uses a single class cDirectSound derived from Anatolys class that doesn't need any other components except the type library. The main intent was to control the effect parameters and to play files in parallel.
Just start the project (ActiveX.exe) in the IDE. Looks like this:
1. is to play all the MP3 files in the sample folder
2. does the same but crossfades the songs
3. You can select an effect to apply to the song and set the available parameters. There is a control form for this to set the values. Here with the I3D-Reverb selected:
(The controls in this form are loaded at runtime according to the file effects.txt which alternatively could also be included in a resource file.)
4. A complete stack of effects can be applied to a playing file. Demo starts without effects and after 6 seconds they come in.
5. Effect parameters can be controlled at runtime. Use the slider to adjust the reverb time while playing the song in loop.
You can click Stop at every time.
mdlTestDS contains some routines that also demonstrate the usage of the class.
Another extention to the class is the implementation of IDirectSoundNotify. Just a demonstration on how to implement and use it. I didn't find many cases where it would be needed as it does not raise real events. Simple timer loops my be sufficient. (Maybe MsgWaitForMultipleObjectsEx should be used instead of WaitForMultipleObjects to generate events?)
I have not tested the whole thing extensively. The usercontrol for the sliders may have some bugs and the sequence of played files in the demo can interfere and end in a mess if you click the play buttons before every song ended. (Two DSound buffers are used in parallel!)
As for me I am using the class now in a DJing application.
how play avi,mp4,from memory address,or MapViewOfFile(hMap,?
See DSCreateSoundBufferFromMemory in the demo class regarding the part commented with '// Expecting MP3
As you can see you will have to write your own code for other formats.
But it will be rather difficult to achieve that when you want to load video formats. Better consider using DirectShow interfaces for such tasks (included in the newer oleexp.tlb)
[FilterGraph -> FileSource + DirectSoundRenderer filters; connect them using the appropriate pins]
Option Explicit
Private Sub Form_Load()
Dim result As Long
result = DirectSoundEnumerate(AddressOf EnumCallback, 0)
If result <> DS_OK Then
MsgBox "Failed to enumerate DirectSound devices", vbCritical
End If
End Sub
Add module and put code.
Code:
Option Explicit
Public Const DS_OK As Long = 0
Public Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Public Declare Function DirectSoundEnumerate Lib "dsound.dll" Alias "DirectSoundEnumerateA" (ByVal lpDSEnumCallback As Long, ByVal lpContext As Long) As Long
Public Declare Function lstrcpy Lib "kernel32" Alias "lstrcpyA" (ByVal lpString1 As String, ByVal lpString2 As Long) As Long
Public Function EnumCallback(ByRef lpGuid As GUID, ByVal lpszDesc As Long, ByVal lpszDrvName As Long, ByVal lpContext As Long) As Long
Dim desc As String
Dim drvName As String
desc = Space$(255)
drvName = Space$(255)
' copy memory to get strings from pointers
Call lstrcpy(desc, lpszDesc)
Call lstrcpy(drvName, lpszDrvName)
' trim the strings to get rid of the trailing null characters
desc = TrimNull(desc)
drvName = TrimNull(drvName)
' output to debug
Debug.Print "Device Description: " & desc
Debug.Print "Driver Name: " & drvName
' return 1 to coutinue enum...
EnumCallback = 1
End Function
Public Function TrimNull(item As String) As String
Dim pos As Integer
pos = InStr(item, Chr$(0))
If pos > 0 Then
TrimNull = Left$(item, pos - 1)
Else
TrimNull = item
End If
End Function