Here's a "mini tutorial" on sound memblocks I wrote for another thread a while back. Among other things, it shows how to determine the duration of a wave file:
``````````````````````````
` Sound memblock example `
` Written by Rudolpho, `
` June 21:st 2009 `
````` `````
` (It's my cat's eight `
` birthday!) `
``````````````````````````
#constant true 1
#constant false 0
` Enter the absolute filename of a wave file here
#constant WAV "C:\audio.wav"
type WAVEFORMATEX
format as word ` Even though represented as dwords in DBPro
channels as word ` memblocks, in actual wave files, these are
samplesPerSec as dword ` words (no, not this one ;))
avgBytesPerSec as dword
blockAlign as word
bitsPerSample as word
sbSize as dword
dataOffset as dword
samples as dword
endtype
` We will store our wave data's header here
global header as WAVEFORMATEX
` The memblock and sound number used to hold the wave data
global mem as dword : mem = 99
global snd as dword : snd = 99
` Load the specified wave file and convert it to a memblock
load sound WAV, snd, false, false, true
make memblock from sound mem, snd
` Look up and display the header data
header.format = memblock dword(mem, 0)
header.channels = memblock dword(mem, 4)
header.samplesPerSec = memblock dword(mem, 8)
header.avgBytesPerSec = memblock dword(mem, 12)
header.blockAlign = memblock dword(mem, 16)
header.bitsPerSample = memblock dword(mem, 20)
header.sbSize = memblock dword(mem, 24) ` Optional data may follow the wave
` header, in which case this value
` will tell us how long that data is
` (in bytes).
header.dataOffset = 28 + header.sbSize ` This is not actually part of the
` wave header data, but just
` precalculated to hold the actual
` position where the audio data begins.
` Another precalculated value for future use; the total number of samples in the wave data.
header.samples = (get memblock size(mem) - (28 + header.sbSize)) / ((header.bitsPerSample / 8) * header.channels)
` Tell the user the data
print getFileName(WAV) ` The name of the source file
print "----------------"
supported = false
select header.format
case 1
print " Standard PCM"
supported = true
` This is the absolutely most usual format I believe and most files I've seen
` is in this format. I'm not sure about the other's though, so let's just tell the
` user that there isn't anything we can do if one of the those should appear.
` As you would guess, this list isn't nearly complete either.
endcase
case 2
printc " Advanced PCM"
endcase
case 0x20
printc " YAMAHA Advanced PCM"
endcase
case 0x30
printc " Dolby AC2"
endcase
case 0x200
printc " CREATIVE Advanced PCM"
endcase
case 0xffff
printc " Wave format extensible"
endcase
case default
printc "Unknown format"
endcase
endselect
if not supported
print " (unsupported)"
endif
select header.channels
case 1
print " 1 channel (mono)"
endcase
case 2
print " 2 channels (stereo)"
endcase
case default
print " " + str$(header.channels) + " channels"
endcase
endselect
` Note: this is usually not repressented as caring for multiple samples on separate channels,
` so let us do so neither.
print " Samples per second: " + str$(header.samplesPerSec / header.channels)
print " Average bytes per second: " + str$(header.avgBytesPerSec)
` For all supported files here, blockAlign will equal the size of each sample block in bytes.
` It may differ in other formats, however.
` (In other words, if supported, blockAlign = (channels * bitsPerSample / 8)
if supported
print " Sample block size: " + str$(header.blockAlign) + " bytes"
else
print " Block align: " + str$(header.blockAlign) + " bytes"
endif
printc " Resolution: "
select header.bitsPerSample
case 8
print "8 bits/sample"
endcase
case 16
print "16 bits/sample"
endcase
case default
print str$(header.bitsPerSample) + " bit float data (unsupported)"
supported = false
` Certain high-resolution wave files may use floating point values instead of integers.
` While it is probably possible to just use DBPro's float data type for 32-bit floats,
` it is not neccessarily true that they use the same scheme, I suppose, and therefore
` we will not bother with those formats here.
` I have never seen any 32-bit audio file either and I cannot be bothered with 24-bit
` floats ;)
endcase
endselect
printc " Contains additional data: "
if header.sbSize > 0
print "yes (" + str$(header.sbSize) + " bytes extra data)"
else
print "no"
endif
` Now for something that actually requires us to do some calculations based on the header
` data instead of just displaying it straight away; determine the length of the wave data.
print " Duration: " + str$(getWaveDuration(), 3) + " seconds"
getWaveformImage(99, 320, 200, 0, rgb(0, 150, 0))
wait key
end
function getWaveDuration()
` Since, unfortunately, it is not possible to pass UDT variables to functions, we will
` use the set values of mem and the global header here. This is of course not very
` portable, and one should actually pass the memblock ID to this function and then use
` memblock dword(memID, xx) to get it's header information, but I thought this would be
` easier to read and understand as an example.
` So, we have already precalculated the number of samples in the wave data (see line 60).
` The remaining math is as simple as this:
local duration as double float
duration = header.samples / (header.samplesPerSec * 1.0) ` => float division
endfunction duration
` Draws the waveform to the specified image
function getWaveformImage(img as dword, width as dword, height as dword, bgCol as dword, col as dword)
` Again, for simplicity, we use the global variables, hence making this rather inportable.
` For speed purposes, the image is created as a memblock.
` Seeing as this is a sound example, I won't go into detail on the image memblock
` format (there are plenty of tutorials around on this though).
local samplesPerPixel as dword
local sampleHeight as dword
local im as dword
local sample as dword
local pixel, lastPixel as double integer
im = findFreeMemblock()
make memblock im, 12 + (width * height * 4)
write memblock dword im, 0, width
write memblock dword im, 4, height
write memblock dword im, 8, 32
` It is obviously a lot faster to use black as the background colour
if bgCol <> 0
for x = 1 to width
for y = 1 to height
write memblock dword im, 12 + (4 * w * h), bgCol
next y
next x
endif
` Of course, we will have a lot less pixels than samples, so we will have to use average
` values for the visual representation of the audio data
samplesPerPixel = int((header.samples / (width * 1.0)) + 0.5) ` => float division
sampleHeight = (height / header.channels) - ((header.channels - 1) * 10)
channelJump = header.channels * (header.bitsPerSample / 8)
dataChunk = samplesPerPixel * (header.bitsPerSample / 8)
bytesPerSample = (header.bitsPerSample / 8)
peakValue = 256 ^ bytesPerSample
dataStep = dataChunk * SamplesPerPixel
pixel = 127 ` Default starting value
for c = 1 to header.channels
for p = 1 to width
lastPixel = pixel
pixel = 0
for d = 1 to dataStep step bytesPerSample
pixel = pixel + readMemblockBytes(mem, header.dataOffset + (((p - 1) * samplesPerPixel) + (c * channelJump * d)) * bytesPerSample, bytesPerSample)
next d
pixel = int(((((pixel * 1.0) / samplesPerPixel) / (peakValue * 1.0)) * sampleHeight) + 0.5) ` Average value over the given time
`write memblock dword im, 12 + ((((sampleHeight - pixel - 1) * width) + p) * 4), col
` Draw it as connected lines instead of separate pixels
for y = lastPixel to pixel
drawMemblockPixel(im, p, (sampleHeight * c) - ((c - 1) * 10) - y, col)
next y
next p
next c
make image from memblock img, im
paste image img, 320, 0
delete memblock im
endfunction
` Reads the specified number of bytes from a memblock and returns the result as a dword
` Note: only works with 1, 2, 3 or 4 bytes.
function readMemblockBytes(m as dword, pos as dword, bytes as byte)
local result as dword
if bytes > 4 or bytes = 0 or (pos + bytes) > get memblock size(m)
exitfunction 0
endif
select bytes
case 1
result = memblock byte(m, pos)
endcase
case 2
result = memblock word(m, pos)
endcase
case 3
result = (memblock word(m, pos) * 256) + memblock byte(m, pos + 2)
endcase
case 4
result = memblock dword(m, pos)
endcase
endselect
endfunction result
` Helper function; gets the file name separated from the path from an absolute file name
function getFileName(file as string)
l = len(file)
for p = l to 1 step -1
if mid$(file, p) = "\"
exitfunction right$(file, l - p)
endif
next p
` If we get here, no backslash was found; return the whole input string
endfunction file
` Helper function; finds a free memblock
function findFreeMemblock()
for m = 1 to 255
if not memblock exist(m)
exitfunction m
endif
next m
endfunction 0
` Helper function; draws a pixel to an memblock image
` Note: only works with 32-bit images
function drawMemblockPixel(m as dword, x as dword, y as dword, col as dword)
if x < 0 or x >= memblock dword(m, 0) or y < 0 or y >= memblock dword(m, 4)
` Invalid position; do nothing.
exitfunction
endif
write memblock dword m, 12 + (((y * memblock dword(m, 0)) + x) * 4), col
endfunction
And here is a pre-written set of functions to just get the duration of a loaded sound using a temporary memblock:
function getWaveDuration(sound as integer)
local mem as integer
local size as dword
local sps as dword
local c as dword
local d as float
mem = freeMemblock()
make memblock from sound mem, sound
size = get memblock size(mem) - 28
sps = memblock dword(mem, 8)
c = memblock dword(mem, 4)
d = (size * 1.0) / (sps * c * 2.0) `Each sample is repressented by a word (2 bytes)
delete memblock mem
endfunction d
function freeMemblock()
for i = 1 to 255
if not memblock exist(i) then exitfunction i
next i
endfunction 0
(If you have it, it is probably favourable to use IanM's
find free memblock function instead of the one in there).
Hope that helps
Rudolpho
"Why do programmers get Halloween and Christmas mixed up?" Because Oct(31) = Dec(25)