News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Programming sound effects

Started by piotrus, March 01, 2007, 03:09:37 PM

Previous topic - Next topic

piotrus

I`d like to write a library which adds an echo effect (and some others) to a .wav file. I`ve read some stuff about .wav file format but still have no idea how to process a sample to get this effect...  :(
Any ideas?

ragdog


Tedd

A quick solution would be to copy the last 'n' milliseconds of the wav, and append them to the end. That should give a basic 'echo' effect.
Once you've got the hang of that you can start to make it more sophiticated, such as adding repeats, fading the volume, overlapping, etc.
As for decoding the wav file, start by working out how to calculate the offset of the sample for a particular time offset - which will require you to extract the useful information from the header, and then use that for the calculation. Once you've done that, find the start and end points to copy for the echo is pretty easy.
(There are a few different wav formats, but just stick with pcm for now.)
No snowflake in an avalanche feels responsible.

gabor

Hi!

Standard wav files have a 44 byte header describing the body part, the samples in this case: the number of channels, the sample rate, the bits per sample (or bytes per sample) and the length of the body part.
So, the first step should be to read this 44 bytes, setup your work buffer according to it. You can either process the whole sample or reading only a portion of it into the buffer.
The delay effect is simple like Tedd posted. The delayer is a module with memory, since it has to store and add earlier sample values. Like having a 1 second delay has to remember the sample value of 1 second earlier. You have to store the unprocessed samples or you can simply read it from the file (having a buffer for it is necessary for real -time sample synth only). The delay given in miliseconds you have to convert into bytes: delay*sampleRate*bytesPerSample/1000 (the division by 1000 is necessary only if the delay is given in miliseconds). Example: for a 44100Hz, 16 bit, stereo sample the position difference between the actual sample and the delay sample at the 1.2 sec delay in bytes is 1.2*44100*4=211680. You can read the file using 2 pointers, the first pointer reads the actual sample, the second that has a delay (211680 bytes in the example) reads the delayed sample. You would amplify this value or it won't sound like an echo. For multiple echo you have to use more file pointers, or in the memory buffer version more pointers on the buffer. Important to use 0 (silence) for the first n samples, where the echo position would point out of the sample (in the example actual position-211680 would be less than 0).
Finally I would note that it can allocate lot of memory though, I would choose the memory buffer version since accessing the disk is slow, and here for every sample the disk has to be accessed numberOfEchos+1 times. On the other hand for a 10 second delay with CD quality sound you have to allocate 10*44100*4=ca. 1.68MB (this is endurable  :bg)

Greets, Gábor

I hope I gave some hints.

piotrus

Quote from: Tedd on March 02, 2007, 12:33:02 PM
A quick solution would be to copy the last 'n' milliseconds of the wav, and append them to the end. That should give a basic 'echo' effect.
Once you've got the hang of that you can start to make it more sophiticated, such as adding repeats, fading the volume, overlapping, etc.


I copied the end samples of the wav, and put them on the end. How can I fade a volume to make an echo sound more realistic?

Tedd

Aah, well done! Now for the fun part :U

Okay, so think about what happens when the volume fades - it becomes quieter!
So, what you'll need to do is reduce the volume level (amplitude) of each sample in the echo part so that's it quieter. Now, the amplitude of each sample is its distance from zero (not just the value itself because they can be negative) so you'll need to reduce that by some amount (let's just say half for now; once you know what to do you can change it.)
Of course, if you just reduce the amplitude of each sample in the echo part by a constant amount, then all you'll get is the sound, and then a quiet repeat -- but this is a first step, get it working before you make it more complex :wink
So then to make it more complex (and hopefully sound better) you'll want to do the actual fade. So the first few samples will be at 99% of their original value, then some 98%, 97, 96, 95, ......, eventually down to silence at zero; make sure you scale the values more accurately though (calculate how fast you want to fade and then apply the gradient to the samples.) Doing this will give you a linear fade, you might also try a logarithmic fade (a curve, instead of a straight line), which might sound more realisitic.

And then, when you put it all together, having multiple repeats (each becomes shorter than the last - echoo..choo..c'oo.o), and the volume fade, you should end up with a fairly nice echo effect :cheekygreen:
No snowflake in an avalanche feels responsible.

piotrus

I divided each sample by 2 and appended them to the end of the file. The "echo buffer" was as long as the whole file, because I experimented with short files.The effect was something like: HELLO TED.. hello ted... :) I changed the "echo buffer" to a constant value instead of the leght of the file. I wanted to get an effect like: "HELLO TED.. ted.. " :) But what I got was: "HELLO TED.. grrhhrrhr" :) The only change in code was a lenght of the buffer and .if which checks if esi>BufferSize. What could be wrong? I checked in debugger, and data in EchoBuffer seem to be OK... :|

mov esi, 5A44   ;the lenght of the echo buffer
     
.while (edi !=00000000h)  ;in edi there`s number of samples (bytes) to proccess

;read sample
   invoke ReadFile,hDebugFile,pBuffer,1, addr SizeReadWrite, NULL      ;pBuffer - pointer to a temporary buffer for a read byte
   mov eax, pBuffer
   mov bl, BYTE PTR [eax]
   xor eax, eax
                xor edx, edx
   mov al, bl
;divide the sample by 2
   div WORD PTR Divider       ;Divider = 2
   mov ecx, pEchoBuffer     
;save the sample in the echo buffer
   mov BYTE PTR [ecx+esi],al
   dec edi   
                inc esi

    .if (esi > EchoBufferSize)
      xor esi, esi
   .endif
.endw
      
mov esi, 5A44h

;append the buffer to the end of the file   
.while (esi != 0)
   invoke WriteFile,hDebugFile, pEchoBuffer, 1, addr SizeReadWrite,NULL
   inc pEchoBuffer
   dec esi   
.endw


Tedd

A few things :bdg

* I hope you're skipping the wav header before doing that :P
* You seem to be treating the samples as bytes - are they really 8-bit?
* You're not sign-extending, and you're using DIV, which means you're messing up the negative samples -- use IDIV, not DIV; and add sign-extension as necessary (if the top bit is 1, then all the bits of the high byte should also be 1 -- see CBW, CDQ, and MOVSX)
* You're copying the echo from the start of the file? (Which would give "HELLO PIO hell-", and not "HELLO PIO-pio")

Play some more :bg Attach full source if you need more help :wink
No snowflake in an avalanche feels responsible.

piotrus

I`ve put movsx and idiv and the effect was still very poor  :'( At least this time I could hear the actual echo :)
Do I really need to care about the sign of the sample? When I analyzed my .wav file in a GoldWave program, it showed that it`s "Wave PCM unsigned 8-bit" ...
What is the diference between signed and unsigned wave files?

Here is my troublemaker:
(When I deleted a line with idiv the effect was OK (but the echo wasn`t quieter :/ ) )

.while (edi != 00000000h)
;read a sample
invoke ReadFile,hDebugFile,pBuffer,1, addr SizeReadWrite, NULL
mov eax, pBuffer

;save the sample in the echo buffer
movsx bx, BYTE PTR [eax]
xor eax, eax
mov ax, bx
xor edx, edx
idiv WORD PTR Divider
mov ecx, pEchoBuffer
mov BYTE PTR [ecx+esi],al

dec edi
inc esi
.if (esi == 2D22h)
mov esi, 0h
.endif

.endw


piotrus

Is it possible that very poor quality of the echo sound is coused by the arithmetic inaccuracy. When I use idiv I divide an integer by integer, and as a result I use only quotient stored in AL??


Tedd

Okay, sorry, the 'standard' is a little messed up..
For wavs with 8-bit samples, they are unsigned -- 0 is the lowest, 255 is the highest, and 128 is 'silent'; whereas wavs with more bits are signed (2's-complement.)

Which means, unfortunately, that simply dividing by 2 (signed or unsigned) isn't quite right - it's worse!
If we scale from -1 (0) to +1 (255)..
Half of +1 (255) is 0.5 (191; but 255/2 = 127!!!) -- very wrong!!
And the problem continues with 'negative' values: half of -1 (0) is -0.5 (64; but 0/2 = 0!!!) :dazzled:

So this all needs special handling - and it's a bit more than a 5 minute patch job ::)



And yes, 8-bit sound is pretty poor, but simply halving the volume shouldn't make it too much worse - the rounding error is only ever going to be less than one, so it shouldn't degrade the quality too much; sampling rate has more of an effect.
No snowflake in an avalanche feels responsible.

sebart7

Lets say sample is 8bit where 127 is silent. You should substract from it 080h, process it (mix with other sample ?, fade ? anything....) and then add 080h back. This way You do work on data where silent is 0 and data range is -127 to +127

Here is an example how You can do this

        mov al,210      ; input sample byte (anything from 0h to 0ffh where 127 is silent)
       
        and eax,0ffh
        sub eax,080h
        xor edx,edx
        mov ebx,2
        div ebx
        add eax,080h
       
        ; here You have in AL Your sampleByte Divided by 2


Tedd

Aaah, nice work! (How did I miss that ::) :lol)
Although it doesn't work exactly with the negative values - it leaves the topmost bit set - but then you store only 'al' so it doesn't matter.

Anyway, that gives you..

.while (edi != 00000000h)
    ;read a sample
    invoke ReadFile,hDebugFile,pBuffer,1, addr SizeReadWrite, NULL
    mov ecx, pBuffer

    ;save the sample in the echo buffer
    xor eax,eax
    mov al,[ecx]        ;get the sample
    sub eax,80h
    xor edx,edx
    idiv Divider        ;(Divider should be a dword)
    add eax,80h

    mov ecx, pEchoBuffer
    mov BYTE PTR [ecx+esi],al

    dec edi
    inc esi
    .if (esi == 2D22h)
        mov esi, 0h
    .endif
.endw


(Not too sure about what some of the code is meant to be doing, but as long as it makes sense with the rest of your program :wink)
No snowflake in an avalanche feels responsible.

piotrus

It works :8) Thanks a lot!  :bg
Now I`m strugling with some improvements. My program adds an echo only at the end of the file. When a sound is short, the effect is quite nice. But with longer sounds I`d like to add an echo to the background of the whole sound. I tried to add echo samples to actual samples. The algorithm looks like that:
1. read a sample
2. add an echo sample
3. save new sample to file
Before changing the sample I dicrease its volume and save it in the echo buffer so I could add it to the other sample.
Is this idea correct?
The only effect I got was some noise in a background, but maybe it`s due to an error in a code.





piotrus

After several hours of debuging I`ve found an error  :bg Program works :bg
Thanks everybody, and special thanks for Ted  :bg
I owe you a beer :)