Hello, I’ve been searching the forums for something that will create a waveform of an mp3. By this, I mean something that will return the volume of the file at a specific time/sample. I know there is a VU meter, but that only returns the volume in realtime (while the file is playing). I want to be able to store the volume values into an array and then I’ll be able to convert that array into a waveform.
- Yonkey asked 16 years ago
Okay, got another update. My first mistake was testing for TRUE from sample_lock rather than 1. Here’s the code that actually returns values:
Dim lSample As Long
Dim lStream As Long
Dim sFile As String Dim holdIndex As Integer Dim holdData() As Byte Dim holdLength As Long Dim holdOffset As Long Dim ptr1 As Long, ptr2 As Long, len1 As Long, len2 As Long Call FSOUND_Init(44100, 32, 0) sFile = returnFileName("wav", False, holdIndex, "wav") If sFile <> "" Then lStream = FSOUND_Stream_OpenFile(sFile, FSOUND_MPEGACCURATE, 0) lSample = FSOUND_Stream_GetSample(lStream) ' holdLength = FSOUND_Stream_GetLength(lStream) ' holdOffset = 0 ' ReDim holdData(0 To holdLength - 1) ' If FSOUND_Sample_Lock(lSample, holdOffset, holdLength, ptr1, ptr2, len1, len2) = 1 Then ' CopyMemory holdData(0), ptr1, len1 ' Call FSOUND_Sample_Unlock(lSample, ptr1, ptr2, len1, len2) ' End If ' Call FSOUND_Stream_Close(lStream) Call FSOUND_Sample_Free(lSample) FSOUND_Close
And this will actually return values. The clip I tested is an 8bit sample and I verified with a simple binary read that the number of bytes being returned from Stream_GetLength is correct. However, the data being returned isn’t correct (also verified from binary reading the clip). len1 is returning a value that I can’t seem to make sense of.
Okay, scratch that. I checked the clip with FSOUND_Sample_GetLength(lsample) and it’s giving me 4410 which is also what len1 is telling me. So… errmmm… what do I do with that? Before finding FMOD, I was trying to binary read files (works with uncompressed waves just fine) and I used this clip as a test. However, when I was grabbing the information, I was used to getting an array of values back – 1031616 of them. Since it’s 8 bit, I was getting 0 to 255 as the values. Which checked out.
Not being very good at audio related stuff, I’m at a loss as to what to do with sample_lock’s return values. They don’t match up with what the binary read returns and there isn’t as many of them.
Any more help on the subject?
I don’t think you need to get the pointer of the byte array.
Just pass the lock function the first number in the array :
If FSOUND_Sample_Lock(lSample, holdOffset, holdLength, holdData(0), ptr2, len1, len2) = True Then
You don’t have to get the pointer because I think the declaration of Sample_Lock already mentions ByRef, so vb gets the pointer for you.
Now I’m not 100% sure because I haven’t used sample_lock yet and I haven’t really checked all the details, but I think you might try this.
- Adion answered 15 years ago
I was going to post a question about this until I searched and found this one so I’ll bump it.
The docs for FSOUND_Sample_Lock are fairly hard for me to swallow at one go and I’m getting a False return when I run the function. Since I’m not exactly sure what values it’s looking for, I had to try some different guesses and I’m sure they’re completely wrong. Any guru want to take a look at nudge me in the right direction? I really need this part to work so I’ll work hard to get it going right.
Dim lSample As Long
Dim lStream As Long
Dim sFile As String Dim holdIndex As Integer Dim holdData() As Byte Dim holdLength As Long Dim holdOffset As Long Dim ptr1 As Long, ptr2 As Long, len1 As Long, len2 As Long Call FSOUND_Init(44100, 32, 0) sFile = returnFileName("wav", False, holdIndex, "wav") lSample = FSOUND_Sample_Load(FSOUND_FREE, sFile, FSOUND_MPEGACCURATE, 0) ' holdLength = FSOUND_Sample_GetLength(lSample) len1 = holdLength ' ReDim holdData(0 To holdLength - 1) ' ptr1 = faststring.VB6.VarPtrByteArray(holdData) ' from the faststring typelib ' holdOffset = 0 ' If FSOUND_Sample_Lock(lSample, holdOffset, holdLength, ptr1, ptr2, len1, len2) = True Then ' Good ' ' Code to handle data goes here ' Call FSOUND_Sample_Unlock(lSample, ptr1, ptr2, len1, len2) ' Else ' Bad End If ' Call FSOUND_Sample_Free(lSample) FSOUND_Close
Don’t laugh at the bad code >_< So… any pointers (no pun intended) ? I figure I’ll use CopyMemory once I get a good return value and copy ptr1 to HoldData but please correct me if I’m wrong. I’ll also have to look into handling 8 bit, 16 bit, 24 bit, etc… and using the correct variables.
I have to take a closer look at this problem, but let me see if I can’t give you some help right now.
As a rule, VB and pointers do not mix. Granted, there are methods that give us VB programmers limited access to the pointers behind the scenes. It is best to leave pointers alone. If you have to work with them, be a good object-oriented programmer and securely encapsulate them, so the rest of your program is left untainted from pointer work.
But more on your specific application… If I hear you correctly, what you basically want is access to the individual samples from a waveform. To get access, you need to first lock the memory where the waveform is located. The reason you can’t just ask for a pointer is that a pointer might not exist. Sounds can be anywhere in system memory or in memory on the sound card itself. The data may not even be in a contigious block. So a conventional pointer may not be available. Locking tells the API to get the specified section of the waveform in a contigious block in system memory. Unlocking then tells the API that the data in that block of memory is ready to be moved back to wherever it needs to go.
VB does not directly support pointers, so all you get is a Long containing the 32-bit address. It has been awhile, but I believe CopyArray (or is it CopyMemory) lets you fill a VB-style array with content from a C-style array. Check the VB documentation for how to use the method correctly.
What I advise you to do is create a VB class module that encapsulates a waveform and provides methods for editing ala Sound Forge. These methods and [i:104256el]only[/i:104256el] these methods need utilize the FMOD methods directly. This allows all of the nasty pointer work to be hidden behind the public methods of the class module.
[b:104256el][i:104256el]Clever Code Alert[/i:104256el][/b:104256el]: There is a really cool thing you can do to improve performance. This VB class module would naturally provide methods to access the individual samples. But depending on how you intend to work with the data, you may not need to load the entire file in the beginning. As the application asks for individual samples or groups of samples, you can query FMOD for the data at that time. You also cleverly cache all of the samples, so requests in the future may be honored faster. There is more code required to manage deferred loading, but it can make your application perf better. (And applications like Sound Forge, Cool Edit, et al. can be very processor-intensive.)
If you need any more help than this, let me know so I can re-install Visual Studio 6.0. (I have moved on to .NET; but since VB.NET is very different from the 6.0 version, I would need the old tools.)
Good luck coding…
I had another idea. What if you split the song into n pieces (say n = number of seconds in the song), play all of them on n different channels simultaneously, then use the GetVolume on each channel to get the volume. The only thing is I dont know if GetVolume returns the volume of the mp3, or the volume of the mixer.
So GetCurrentVU is based on the actual volume output? Is there a way to mute the whole mixer and still have GetCurrentVU to get the volume of mp3s?
Hey, you can make it play faster by using setfrequency, but when you mute it, the VU meter will always return 0.
The only way to do this is using samples and locking/unlocking them, or creating a dsp that can get the data while playing.
Creating a dsp is almost impossible with VB only (you will need a combination C/VB)
Locking and unlocking of a sample is possible the way I described, but isn’t really easy.
I don’t have much time for the moment, but if I have some time I might add a function similar to the GetSpectrum function so you can pass an array and a sample to it and it retreives the sample data.
Well, I wanted something similar to the FSOUND_GetCurrentVU but to get the volume WITHOUT playing the file. For an example of what I mean, open an mp3 in SoundForge or Cool Edit. It will load the waveform of the mp3 (it takes about 20 seconds on my computer and longer on slower computers) without playing it.
So I figured the only way to get the volume of an mp3 at a specific time is to get the volume of an mp3 at a specific sample. The samples would get loaded into an array but it seems like it’s impossible to get any volume data from a sample. GetVolume will return the volume of a channel (which stays constant), GetCurrentVU returns the volume only when the song is playing. That is why I proposed a GetSampleVolume.
I just had an idea! Is there a way to play an mp3 at a faster rate? Maybe I could mute the sound, play the mp3 as fast as possible and use GetCurrentVU to create the array of values.
<font size=-1>[ This Message was edited by: Yonkey on 2002-04-21 08:13 ]</font>
I think I’m going to give up on this. It’s too complex for a newbie like me and I don’t completely understand what I’m doing by locking. Everytime I try to code something you guys tell me to, VB just gives me different error messages. I have no idea what locking samples does (well, I know it returns a pointer to the beginning of a sample but I have no idea why I need to do that). The only time I’m getting actual data into my array is with the Load function. Still, I have no idea what to do even if i did have an array of memory addresses OR samples.
It would be nice if there were a function like:
Double FSOUND_GetSampleVolume(FSOUND_SAMPLE *sptr). Where all you have to do is send it a sample from the song and it could return the volume as a double from 0 to 1. Maybe this can be added to future versions of Fmod, because having to use pointers, byvals, locking, unlocking, etc. is way too confusing for a beginner.
I have checked out what should be done, and here is how you could do it :
First, make sure the declaration in fmod.bas for sample_lock says ‘…ByRef ptr1 As Long, ByRef ptr2 As Long, ByRef len1 As Long, ByRef len2 As Long…’
You pass this function some variables declared as :
Dim ptr1 as long, ptr2 as long, len1 as long, len2 as long
When passed, ptr1 and ptr2 are the memory adress to the buffer and len1 and len2 are the actual lengths.
Now you can make a function similar to the GetSpectrum function found in fmod.bas
It takes the two pointers and the two lengths as BYVAL parameters, so you can use these values for a copymemory : something like :
CopyMemory buf1(0), ByVal ptr1, len1
This will copy the data found by ptr1 to the array buf1.
The type of this array depends on your sample input (use integer for 16bit)
I’m not sure what len1 and len2 are (samples or bytes)
If they are in bytes you can leave it like this, else you have to multiply it by 2 or 4 depending on the sample type.
Thanks for that bit of help but I still am not getting anywhere. I don’t think I’m doing this right because I’m just getting pointer addresses for the SampleData(0) only and the rest are all saying 0. Here’s what I have in the function:
Dim LengthofData As Long
LengthofData = FSOUND_Stream_GetLength(stream1)
Dim SampleData() As Long
Dim i As Long
Dim Chunk As Byte
Dim ptrStart, ptrFin As Long
ptrStart = VarPtr(SampleData(0))
ptrFin = VarPtr(SampleData(1))
Dim Start, Fin As Long
Start = VarPtr(Start)
Fin = VarPtr(Fin)
Chunk = 100
SampleData(0) = FSOUND_Sample_Load(0, filename, FSOUND_LOADRAW, 0)
For i = 0 To LengthofData
ptrStart = VarPtr(SampleData(i))
Start = VarPtr(Start)
Fin = VarPtr(Fin)
If i < LengthofData Then ptrFin = VarPtr(SampleData(i + 1))
Call FSOUND_Sample_Lock(SampleData(i), Chunk * i, Chunk, ptrStart, ptrFin, Start, Fin)
‘Call FSOUND_Sample_Unlock(SampleData(i), ptrStart, ptrFin, Start, Fin)
I had to comment out the unlock code because it says VB doesn’t support that kind of automation. But even if this thing worked, I still don’t see how you can get the volume of the sample wrt time, all I’m getting right now are the addresses to samples wrt bytes.
Instead of allocating the samplePointer as
Dim SamplePointer as Long
You should declare it as follows :
Dim SampleData() as Integer
LengthOfData is the length of the uncompressed data you want in bytes. Check out the fmod documentation on how to get the length of a sample.
Then you have to pass SampleData(0) instead of SampleData to the function.
Now you can just get the volume at any time.
Hope this gets you started…
Yeah, I was looking at those 3 functions and tried doing the load first. This is what I have so far:
Dim samplePtr As Long
samplePtr = FSOUND_Sample_Load(0, filename, FSOUND_LOADRAW, 0)
Call FSOUND_Sample_Lock(samplePtr, spot, 10, lock1, lock2, len1, len2)
Call FSOUND_Sample_Unlock(samplePtr, lock1, lock2, len1, len2)
I just found out how to do pointers in VB using the CopyMemory and VarPtr. But I still don’t know how to get the volume of that loaded/locked sample.
<font size=-1>[ This Message was edited by: Yonkey on 2002-04-03 08:16 ]</font>
Please login first to submit.