📄 unawave.pas
字号:
(*
----------------------------------------------
unaWave.pas
Voice Communicator components version 2.5
PCM wave routines
----------------------------------------------
This source code cannot be used without
proper permission granted to you as a private
person or an entity by the Lake of Soft, Ltd
Visit http://lakeofsoft.com/ for details.
Copyright (c) 2001, 2003 Lake of Soft, Ltd
All rights reserved
----------------------------------------------
created by:
Lake, 25 Mar 2002
modified by:
Lake, Mar-Dec 2002
Lake, Jan-Oct 2003
Lake, Aug 2004
----------------------------------------------
*)
{$I unaDef.inc}
unit
unaWave;
interface
{DP:UNIT
Contains set of routines designed to work with PCM wave data.
}
uses
unaTypes
{$IFDEF __SYSUTILS_H_ }
, Math
{$ENDIF}
;
type
// -- --
punaPCMFormat = ^unaPCMFormat;
unaPCMFormat = packed record
//
pcmSamplesPerSecond: unsigned; // sampling rate
pcmBitsPerSample: unsigned; // bits per sample
pcmNumChannels: unsigned; // number of channels
end;
// -- --
punaWaveFormat = ^unaWaveFormat;
unaWaveFormat = packed record
//
formatTag: unsigned; // format tag id. could be WAVE_FORMAT_PCM or anything else
formatOriginal: unaPCMFormat; // original PCM format
formatUserData: unsigned; // any additional data
end;
// -- --
punaPCMChunk = ^unaPCMChunk;
unaPCMChunk = record
//
chunkFormat: unaPCMFormat; // chunk format
chunkDataLen: unsigned; // length of actual data in chunk
chunkBufSize: unsigned; // size of chunk buffer in bytes
chunkData: pointer; // chunk data
end;
{DP:METHOD
Mixes buf1 and buf2 and stores the result in buf3 (buf3 could be one of buf1 or buf2).
<BR />Returns number of samples mixed.
<BR />bitsN (N = 1, 2 or 3) specifies the number or bits in sample:
<UL>
<LI>8 = 1 byte per sample. Every sample contains PCM value in the following format:</LI>
<TABLE CELLSPACING="0" BORDER="1">
<TR><TD>Value</TD><TD>PCM value</TD></TR>
<TR><TD>0x00</TD><TD>-128</TD></TR>
<TR><TD COLSPAN="2">..</TD></TR>
<TR><TD>0x7F</TD><TD>-1</TD></TR>
<TR><TD>0x80</TD><TD>0</TD></TR>
<TR><TD COLSPAN="2">..</TD></TR>
<TR><TD>0xFF</TD><TD>+127</TD></TR>
</TABLE>
<BR /><BR />
<LI>16 = 2 bytes per sample. Every sample contains PCM value in the following format:</LI>
<TABLE CELLSPACING="0" BORDER="1">
<TR><TD>Value</TD><TD>PCM value</TD></TR>
<TR><TD>0x0000</TD><TD>0</TD></TR>
<TR><TD COLSPAN="2">..</TD></TR>
<TR><TD>0x7FFF</TD><TD>+32767</TD></TR>
<TR><TD>0x8000</TD><TD>-32768</TD></TR>
<TR><TD COLSPAN="2">..</TD></TR>
<TR><TD>0xFFFF</TD><TD>-1</TD></TR>
</TABLE>
<BR /><BR />
<LI>32 = 4 bytes per sample, signed integer. Every sample contains PCM value in the following format:</LI>
<TABLE CELLSPACING="0" BORDER="1">
<TR><TD>Value</TD><TD>PCM value</TD></TR>
<TR><TD>0x00000000</TD><TD>0</TD></TR>
<TR><TD COLSPAN="2">..</TD></TR>
<TR><TD>0x7FFFFFFF</TD><TD>+2147483647</TD></TR>
<TR><TD>0x80000000</TD><TD>-2147483648</TD></TR>
<TR><TD COLSPAN="2">..</TD></TR>
<TR><TD>0xFFFFFFFF</TD><TD>-1</TD></TR>
</TABLE>
<BR /><BR />
</UL>
<BR />Size of buffer #N in bytes = (bitsN * samples * numChannels) shr 3
<BR />Mixing is done by adding (doAdd is true) or subtracting (doAdd is false) samples with clipping.
}
function waveMix(buf1, buf2, buf3: pointer; samples: unsigned; bits1, bits2, bits3: unsigned; doAdd: bool = true; numChannels: unsigned = 1): unsigned; overload;
function waveMix(const chunk1, chunk2, chunk3: unaPCMChunk; doAdd: bool = true): unsigned; overload;
{DP:METHOD
Calculates volume. Calculation is done by adding squares of samples and then dividing result on number of samples and applying square root.
<BR />buf is a pointer to PCM samples
<BR />samples is a number of samples.
<BR />bits should have the value as described in waveMix() routine
<BR />For multi-channel streams specify the channel number you wish to get the power of and total number of channels in the stream.
<BR />Size of buffer can be calculated as (samples * bits * numChannels) shr 3
<BR />Resulting value is from 0 (silence) to 32768 (loudest possible sound).
<BR />Assumption sound has "natural" source is in effect. That means you can get useless results for signals, that are not sound by nature (constant non-zero signal for example).
}
function waveGetVolume(buf: pointer; samples: unsigned; bits: unsigned; numChannels: unsigned = 1; channel: unsigned = 0; deltaMethod: bool = false): unsigned;
{DP:METHOD
Returns Logarithmic volume.
<BR />Input range is from 0 to 32768.
<BR />Result range is from 0 to 300.
}
function waveGetLogVolume(volume: int): unsigned;
{DP:METHOD
Extracts specified channel from PCM chunk.
<BR />dest must be large enough to store required data (one channel).
<BR />channel specifies which channel to extract (0, 1, 2... )
<BR />Returns number of samples stored in dest buffer.
}
function waveExtractChannel(buf, dest: pointer; samples: unsigned; bits: unsigned; numChannels: unsigned = 1; channel: unsigned = 0): unsigned;
{DP:METHOD
Replaces PCM data for specified channel with new one. Old data for this channel will be lost.
<BR />channel specifies which channel to replace (0, 1, 2... )
<BR />Returns number of samples stored in dest buffer.
}
function waveReplaceChannel(buf, source: pointer; samples: unsigned; bits: unsigned; numChannels: unsigned = 1; channel: unsigned = 0): unsigned; assembler;
{DP:METHOD
Reverses PCM samples in wave chunk.
<BR />bits specifies number of bits in sample.
<BR />numChannels specifies number of channels in stream.
<BR />Returns number of samples processed.
}
function waveReverse(buf: pointer; samples: unsigned; bits: unsigned; numChannels: unsigned): unsigned; assembler;
{DP:METHOD
Changes PCM stream characteristics.
<BR />Returns number of bytes produced by the function.
}
function waveResample(bufSrc, bufDst: pointer; samples, numChannelsSrc, numChannelsDst, bitsSrc, bitsDst, rateSrc, rateDst: unsigned): unsigned; overload;
{DP:METHOD
Changes PCM stream characteristics.
<BR />Returns number of bytes produced by the function.
}
function waveResample(const bufSrc: unaPCMChunk; var bufDst: unaPCMChunk): unsigned; overload;
{DP:METHOD
Returns number of full samples which can be stored in given PCM chunk.
}
function waveGetChunkMaxSamplesCount(const chunk: unaPCMChunk): unsigned;
{DP:METHOD
Returns current number of full samples which is stored in given PCM chunk.
}
function waveGetChunkCurSamplesCount(const chunk: unaPCMChunk): unsigned;
{DP:METHOD
Reallocates PCM chunk to hold required number of samples.
<BR />NOTE: chunk.chunkData must be a valid pointer (or nil) before calling this function.
<BR />Returns number of bytes allocated.
}
function waveReallocateChunk(var chunk: unaPCMChunk; numSamples: unsigned = 0): unsigned;
{DP:METHOD
Reads bytes from PCM chunk.
}
function waveReadFromChunk(var chunk: unaPCMChunk; buf: pointer; size: unsigned; bufOffs: unsigned = 0): unsigned;
{DP:METHOD
Writes bytes to PCM chunk.
}
function waveWriteToChunk(var chunk: unaPCMChunk; buf: pointer; size: unsigned; bufOffs: unsigned = 0): unsigned;
{DP:METHOD
Returns next gcd value.
}
function waveFindNextGcd(rate1, rate2: unsigned; startFrom: unsigned): unsigned;
{DP:METHOD
<PRE>
volume:
100 = 10% original volume
500 = 50% original volume
1000 = 100% original volume
2000 = 200% original volume (two times louder)
and so on
</PRE>
NOTE: scale is linear, for better results you need to use log scale instead.
}
function waveModifyVolume(volume: unsigned; buf: pointer; samples: unsigned; bits: unsigned = 1; numChannels: unsigned = 1; channel: int = -1): unsigned; overload;
function waveModifyVolume(volume: unsigned; const chunk: unaPCMChunk; channel: int): unsigned; overload;
implementation
uses
unaUtils;
// -- --
procedure loadSample(); assembler;
{
IN: AL - number of bits in sample (8, 16 or 32)
ESI - memory buffer pointer
OUT: EAX - sample value (always 32 bit signed integer)
AFFECTS:
EAX
ESI
}
asm
cmp al, 16
je @load_A16
cmp al, 8
je @load_A8
cmp al, 4
je @load_A4
//@load_A32:
lodsd // eax = signed integer
jmp @exit
@load_A4:
// not supported yet
jmp @exit
@load_A8:
lodsb
and eax, 0FFh
sub eax, 080h
sal eax, 8 // eax = signed integer
jmp @exit
@load_A16:
lodsw
cwde // eax = signed integer
@exit:
end;
// -- --
procedure saveSample(); assembler;
{
IN: BL - 8, 16 or 32 bits in destination sample
EAX - sample value to store (always 32 bit signed integer)
EDI - memory buffer pointer
OUT: none
AFFECTS:
EAX
EDI
}
asm
push ebx
cmp bl, 8
je @store_8
cmp bl, 16
je @store_16
// store as 32 bit value
//@store_32:
stosd
jmp @exit
// store as 16 bit value
@store_16:
mov ebx, eax
add ebx, 08000h
test ebx, 080000000h
jz @store_16_01
mov eax, 08000h
jmp @store_16_w
@store_16_01:
test eax, 080000000h
jnz @store_16_w
mov ebx, eax
sub ebx, 08000h
test ebx, 080000000h
jnz @store_16_w
mov eax, 07FFFh
@store_16_w:
stosw
jmp @exit
// store as 8 bit value
@store_8:
sar eax, 8
add eax, 080h
test eax, 080000000h
jz @store_8_01
mov eax, 0
jmp @store_8_b
@store_8_01:
cmp eax, 0100h
jb @store_8_b
mov eax, 0FFh
@store_8_b:
stosb
@exit:
pop ebx
end;
// -- --
function waveMix(buf1, buf2, buf3: pointer; samples: unsigned; bits1, bits2, bits3: unsigned; doAdd: bool; numChannels: unsigned): unsigned;
begin
asm
push esi
push edi
push ebx
mov ecx, samples
mov eax, numChannels
imul ecx // EDX:EAX = samples * numChannels
mov ecx, eax
mov result, ecx
// check if we have something to do
cmp ecx, 0
je @exit
cmp buf1, 0
je @exit
cmp buf2, 0
je @exit
cmp buf3, 0
je @exit
mov edi, buf3
mov bl, byte ptr bits3 // dest buffer bits number
mov bh, byte ptr bits1 // src buffer bits number
@loop:
// -- load A operand
mov esi, buf1
mov al, bh
call loadSample
mov buf1, esi
mov edx, eax // store A operand
// -- load B operand
mov esi, buf2
mov al, byte ptr bits2
call loadSample
mov buf2, esi
xchg eax, edx // restore A operand
// -- mix eax and ebx values
cmp doAdd, 0
je @do_mix_sub
@do_mix_add:
add eax, edx
jmp @after_mix
@do_mix_sub:
sub eax, edx
@after_mix:
// BL must be set to bits number
call saveSample // store the result (EAX)
loop @loop
// ------- loop ends here ------
@exit:
pop ebx
pop edi
pop esi
end;
end;
// -- --
function waveMix(const chunk1, chunk2, chunk3: unaPCMChunk; doAdd: bool = true): unsigned;
var
samples: unsigned;
begin
samples := min(waveGetChunkCurSamplesCount(chunk1), waveGetChunkCurSamplesCount(chunk2));
if (0 < samples) then
result := waveMix(chunk1.chunkData, chunk2.chunkData, chunk3.chunkData, samples,
chunk1.chunkFormat.pcmBitsPerSample,
chunk2.chunkFormat.pcmBitsPerSample,
chunk3.chunkFormat.pcmBitsPerSample)
else
result := 0;
end;
// -- --
function waveGetVolume(buf: pointer; samples: unsigned; bits: unsigned; numChannels: unsigned; channel: unsigned; deltaMethod: bool): unsigned;
var
w: int64;
begin
w := 0;
asm
push esi
push edi
push ebx
mov result, 0
mov ecx, samples
cmp ecx, 0
je @exit // exit if there are no samples
mov ebx, numChannels
cmp ebx, 0
je @exit // exit if there are 0 channels
mov eax, channel
cmp eax, ebx
jae @exit // exit if (channel >= numChannels)
// skip required number of channels
mov esi, buf
imul byte ptr bits // AX = channel * bits
cwde // EAX <- AX
shr eax, 3 // convert to bytes
add esi, eax // advance to the beginning of channel
// calculate size of sample (minus one channel)
mov eax, ebx
dec eax // this is required since ESI will be increased by one channel every load
imul byte ptr bits // AX = numChannels * bits
cwde // EAX <- AX
shr eax, 3 // convert to bytes
mov ebx, eax //
xor edx, edx // delta = 0
@loop:
mov al, byte ptr bits
call loadSample // EAX gets 32 bits signed integer
// ESI shifts to next value
cmp deltaMethod, 0
je @doit
// -- calculate sample as delta
mov edi, eax // save current sample
cmp edx, eax
jle @lower // EDX is lower then EAX
sub edx, eax
mov eax, edx
jmp @doit
@lower:
sub eax, edx
@doit:
imul eax // EDX:EAX = EAX * EAX
add dword ptr w, eax
adc dword ptr w[4], edx
add esi, ebx // move pointer to next sample for this channel
mov edx, edi // restore EDX (it must contain prev. sample value for delta method)
loop @loop
// --- loop ends here ---------------
fild w // put w into ST(0)
fidiv dword ptr samples // divide w on N
fsqrt // get square root
fistp w // put ST(0) into w and pop FPU stack
fwait //
mov eax, dword ptr w // assume w is less than 0x100000000
mov result, eax // range is from 0 to 32'768
@exit:
pop ebx
pop edi
pop esi
end;
end;
// -- --
function waveExtractChannel(buf, dest: pointer; samples: unsigned; bits: unsigned; numChannels: unsigned = 1; channel: unsigned = 0): unsigned; assembler;
asm
{
IN: EAX = buf
EDX = dest
ECX = samples
[ebp + $10] = bits
[ebp + $0C] = numChannels
[ebp + $08] = channel
OUT: EAX = num of samples stored
}
push esi
push edi
push ebx
//
push ecx // save number of samples
mov esi, eax // ESI gets source ptr
sub eax, eax // result := 0
cmp ecx, 0
je @exit // exit if there are no samples
mov ebx, numChannels
cmp ebx, 0
je @exit // exit if there are no channels
mov edi, edx // EDI gets dest buf ptr
mov edx, channel
cmp edx, ebx
jae @exit // exit if (channel >= numChannels)
// skip required number of channels
mov bl, byte ptr bits
mov eax, edx // EAX = channel
imul bl // AX = channel * bits
cwde // EAX <- AX
shr eax, 3 // convert to bytes
add esi, eax // advance to the beginning of channel
@cont1:
// calculate size of sample (minus one channel)
mov eax, numChannels
dec eax // this is required since ESI will be increased by one channel every load
imul bl // AX = numChannels * bits
cwde // EAX <- AX
shr eax, 3 // convert to bytes
mov edx, eax // EDX = size of sample in bytes (munis one channel)
@loop:
mov al, bl
call loadSample // EAX gets 32 bits signed integer
// ESI shifts to next value
call saveSample // sample is stored in dest
// EDI shifts to next value
add esi, edx // move pointer to next sample for this channel
loop @loop
// --- loop ends here ---------------
pop eax // EAX = number of samples
push eax
@exit:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -