Real time microphone in Delphi? - delphi

Is there a real-time microphone Delphi component?
I'm looking for one that provides the results without a buffer.

You'll want an audio card that has ASIO drivers, such as the higher end Sound Blaster cards. ASIO has very low latency, as it provides a minimal layer between the software and hardware. Then you can use this:
Delphi ASIO & VST Project
Other alternatives include using the FFMPEG Delphi port and, of course, Direct X from Jedi.

There's AudioLab from Mitov Software, which claims to do things you describe. I haven't tried it.

Real time get microphone data in one array :
unit WaveSound;
interface
uses
SysUtils, MMSystem;
const
NUMSAMPLES = 1024; // Number of Samples
type
TIndata = array[0 .. NUMSAMPLES - 1] of Integer;
PIndata = ^TIndata;
TFrec= record
Fx, dx :Integer;
end;
function SNDInitWaveIn: Cardinal;
procedure SNDProcWaveIn(var Indata : TIndata);
procedure SNDStopWave;
implementation
var
DevHandle : Integer;
WAVEFORMAT1 : TWAVEFORMATEX;
Wave : WAVEHDR;
function SNDInitWaveIn: Cardinal;
begin
with WAVEFORMAT1 do begin
wFormatTag := WAVE_FORMAT_PCM;
nChannels := 1;
nSamplesPerSec := 44100;// 11025; //11khz
wBitsPerSample := 16;
nBlockAlign := (nChannels * wBitsPerSample) div 8;
nAvgBytesPerSec := nBlockAlign * nSamplesPerSec;
cbSize := 0;
end;
Result:= waveInOpen(#DevHandle, cardinal(-1),#WAVEFORMAT1, cardinal(0), cardinal(0), cardinal(0));
If not(DevHandle = 0) Then waveInStart(DevHandle);
end;
procedure SNDProcWaveIn(var Indata : TIndata);
begin
//lpdata requires the address of an array to fill up data with
Wave.lpData := #Indata;
//the buffer length
Wave.dwBufferLength := NUMSAMPLES;
Wave.dwFlags := 0;
//prepare device for input
waveInPrepareHeader(DevHandle, #Wave, sizeof(Wave));
waveInAddBuffer(DevHandle, #Wave, sizeof(Wave));
// if the following statement is removed, the vis. will be a lot faster (avs style)
// but uses up 100% of cpu!
// this is why i hate avs
Sleep(10); // give device a breather
// the following loop is quite useless, but anyway...
repeat
//Just wait for the blocks to be done or the device to close
until (((Wave.dwFlags and WHDR_DONE)= WHDR_DONE) or (DevHandle = 0));
If (DevHandle = 0) Then Exit; //Cut out if the device is closed
waveInUnprepareHeader(DevHandle, #Wave, sizeof(Wave));
end;
procedure SNDStopWave;
begin
waveInReset(DevHandle);
waveInClose(DevHandle);
DevHandle := 0;
end;
/////////// [sample of use : ] /////////////////
var
ind :TIndata;
procedure TForm1.FormCreate(Sender: TObject);
begin
SNDInitWaveIn;
Timer1.Enabled:= true;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
SNDProcWaveIn(ind);
//code for proc data in "ind" buffer
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
SNDStopWave;
end;

Related

Using delphi to capture mic audio in LINEAR16

From SO I get the next unit code to capture Mic Audio.
I´d like to use the audio captured to send to google speech recognize api.
The google speech api accepts only some encoded formats.
I need the audio in LINEAR16 Uncompressed 16-bit signed little-endian samples.
How could I modify this code to do it?
Sample of use:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
SNDProcWaveIn(ind);
//code for proc data in "ind" buffer
end;
unit WaveSound;
interface
uses
SysUtils, MMSystem;
const
NUMSAMPLES = 1024; // Number of Samples
type
TIndata = array[0 .. NUMSAMPLES - 1] of Integer;
PIndata = ^TIndata;
TFrec= record
Fx, dx :Integer;
end;
function SNDInitWaveIn: Cardinal;
procedure SNDProcWaveIn(var Indata : TIndata);
procedure SNDStopWave;
implementation
var
DevHandle : Integer;
WAVEFORMAT1 : TWAVEFORMATEX;
Wave : WAVEHDR;
function SNDInitWaveIn: Cardinal;
begin
with WAVEFORMAT1 do begin
wFormatTag := WAVE_FORMAT_PCM;
nChannels := 1;
nSamplesPerSec := 44100;// 11025; //11khz
wBitsPerSample := 16;
nBlockAlign := (nChannels * wBitsPerSample) div 8;
nAvgBytesPerSec := nBlockAlign * nSamplesPerSec;
cbSize := 0;
end;
Result:= waveInOpen(#DevHandle, cardinal(-1),#WAVEFORMAT1, cardinal(0), cardinal(0), cardinal(0));
If not(DevHandle = 0) Then waveInStart(DevHandle);
end;
procedure SNDProcWaveIn(var Indata : TIndata);
begin
//lpdata requires the address of an array to fill up data with
Wave.lpData := #Indata;
//the buffer length
Wave.dwBufferLength := NUMSAMPLES;
Wave.dwFlags := 0;
//prepare device for input
waveInPrepareHeader(DevHandle, #Wave, sizeof(Wave));
waveInAddBuffer(DevHandle, #Wave, sizeof(Wave));
// if the following statement is removed, the vis. will be a lot faster (avs style)
// but uses up 100% of cpu!
// this is why i hate avs
Sleep(10); // give device a breather
// the following loop is quite useless, but anyway...
repeat
//Just wait for the blocks to be done or the device to close
until (((Wave.dwFlags and WHDR_DONE)= WHDR_DONE) or (DevHandle = 0));
If (DevHandle = 0) Then Exit; //Cut out if the device is closed
waveInUnprepareHeader(DevHandle, #Wave, sizeof(Wave));
end;
procedure SNDStopWave;
begin
waveInReset(DevHandle);
waveInClose(DevHandle);
DevHandle := 0;
end;
end.

How lock CTRL+ALT+DEL using SetWindowHookEx api?

Good afternoon,
I need lock CTRL+ALT+DEL combination using SetWindowsHookEx and today i have done a code and don't is working until now.
This code is executing in a dll ( this dll is my software ) that is injected in other process.
So, how i can adapt this code below to work?
const
WH_KEYBOARD_LL = 13;
LLKHF_ALTDOWN = $20;
type
KBDLLHOOKSTRUCT = record
vkCode: DWORD;
scanCode: DWORD;
flags: DWORD;
time: DWORD;
dwExtraInfo: Longint ;
end;
var
hhkLowLevelKybd : HHOOK;
FoldProc : LongInt;
hSASWnd : HWND;
hThread : Cardinal;
{$R *.dfm}
Function LowLevelKeyboardProc(nCode : Integer; wParam : Longint; var LParam: KBDLLHOOKSTRUCT) : Longint; stdcall;
var
fEatKeystroke : Boolean;
dwThreadId : Cardinal;
begin
If (nCode = HC_ACTION) Then
begin
If (wParam = WM_KEYDOWN) Or
(wParam = WM_SYSKEYDOWN) Or
(wParam = WM_KEYUP) Or
(wParam = WM_SYSKEYUP) Then
begin
fEatKeystroke :=
(((GetKeyState(VK_CONTROL) And $8000) <> 0) And
((LParam.flags And LLKHF_ALTDOWN ) <> 0) And
(LParam.vkCode = VK_DELETE));
End;
If fEatKeystroke Then
Result := -1
Else
Result := CallNextHookEx(0, nCode, wParam, LongInt(#LParam));
End;
end;
////////// FormCreate event here ///////////
hhkLowLevelKybd := 0;
hhkLowLevelKybd := SetWindowsHookEx(WH_KEYBOARD_LL, #LowLevelKeyboardProc,
HInstance, 0);
end.
Windows does not allow you to intercept Ctrl+Alt+Del for security reasons. Earlier versions (pre-Vista?) used to allow it by replacing the GINA DLL, but it's not been allowed for years.
That key combination is known as a secure attention sequence which is guaranteed to be trustworthy as part of the login process.
If your goal is to only allow your application to be run, you can configure it to act in kiosk mode if you're running a suitable version of Windows, as shown in Set up a device for anyone to use (kiosk mode) at TechNet which #LURD kindly provided.
By design it's impossible to trap or block Ctrl+Alt+Del (The Secure Attention Sequence). There is however a commercial library available (disclaimer: I am the author), SasLibEx.
SasLibEx: a library that can simulate or block the Secure Attention
Sequence (Ctrl+Alt+Del) but it can even unlock a
workstation or session without entering or needing the user’s
credentials (and many more things)
See this screencast for a demo.
Impossible. The Ctl-Alt-Del gets trapped in the Kernel and never makes it to the user mode space where your app is running.
I have had to do this on kiosks systems (using Win XP and Vista) and I did it with a keyboard filter driver (which runs in the kernel) that swaps out the scan codes when the key are pressed.
Not is impossible, see the following code:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils,
Windows,
Registry,
vcl.Dialogs;
procedure DisableCtrAltDel(boolState: Boolean);
var
SystemReg: TRegistry;
Data: Array [1 .. 48] of Byte;
i: Byte;
begin
try
for i := 1 to 48 do
Data[i] := $00;
Data[9] := $09;
Data[15] := $5B;
Data[16] := $E0;
Data[19] := $5C;
Data[20] := $E0;
Data[23] := $5D;
Data[24] := $E0;
Data[27] := $44;
Data[31] := $1D;
Data[35] := $38;
Data[39] := $1D;
Data[40] := $E0;
Data[43] := $38;
Data[44] := $E0;
try
SystemReg := TRegistry.Create;
with SystemReg do
begin
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('\System\CurrentControlSet\Control\Keyboard Layout', True);
if boolState then
WriteBinaryData('Scancode Map', Data, SizeOf(Data))
else
DeleteValue('Scancode Map');
MessageDlg('Restart Windows in order the changes to take effect!',
mtInformation, [mbOK], 0);
CloseKey;
end;
finally
SystemReg.Free;
end;
except
MessageDlg
('Error occurred while trying to disable ctrl+alt+del and Task Manager',
mtWarning, [mbOK], 0);
end;
end;
begin
try
DisableCtrAltDel(True);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Reference

Playing PCM Wav File in Delphi

I have written a simple code that reads the header of a Wav File and then starts playing it. this is my code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Generics.collections,
Vcl.ExtCtrls, MMSystem;
type
TForm1 = class(TForm)
Button1: TButton;
OpenDialog1: TOpenDialog;
Label1: TLabel;
Label2: TLabel;
Shape1: TShape;
Image1: TImage;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TWaveformSample = integer; // signed 32-bit; -2147483648..2147483647
TWaveformSamples = packed array of TWaveformSample; // one channel
var
Form1: TForm1;
myWavFile: file;
DataBlock: array[0..3] of byte;
Count: integer;
NumOfChannels: integer;
SampleRate: integer;
BytesPerSecond: integer;
ByesPerSample: integer;
BitsPerSample: integer;
CompressionCode: integer;
CompressionDesc: string;
BlockAlign: integer;
ExtraFormatBytes: integer;
CompressionCodes: TDictionary<integer, string>;
BytesRead: integer;
Samples: TWaveformSamples;
fmt: TWaveFormatEx;
PacketIsPlaying: Boolean;
implementation
{$R *.dfm}
procedure InitAudioSys;
begin
with fmt do
begin
wFormatTag := WAVE_FORMAT_PCM;
nChannels := NumOfChannels;
nSamplesPerSec := SampleRate;
wBitsPerSample := BitsPerSample;
nAvgBytesPerSec := nChannels * nSamplesPerSec * wBitsPerSample div 8;
nBlockAlign := nChannels * wBitsPerSample div 8;
cbSize := 0;
end;
end;
procedure PlaySound;
var
wo: integer;
hdr: TWaveHdr;
begin
if Length(samples) = 0 then
begin
Writeln('Error: No audio has been created yet.');
Exit;
end;
if waveOutOpen(#wo, WAVE_MAPPER, #fmt, 0, 0, CALLBACK_NULL) = MMSYSERR_NOERROR then
try
PacketIsPlaying := True;
ZeroMemory(#hdr, sizeof(hdr));
with hdr do
begin
lpData := #samples[0];
dwBufferLength := fmt.nChannels * Length(Samples) * sizeof(TWaveformSample);
dwFlags := 0;
end;
waveOutPrepareHeader(wo, #hdr, sizeof(hdr));
waveOutWrite(wo, #hdr, sizeof(hdr));
//sleep(450);
//while waveOutUnprepareHeader(wo, #hdr, sizeof(hdr)) = WAVERR_STILLPLAYING do
//sleep(100);
finally
waveOutClose(wo);
PacketIsPlaying := False;
end;
end;
function ReadDataBlock(Size: integer): Boolean;
begin
try
BlockRead(myWavFile, DataBlock, Size, Count);
INC(BytesRead, Size);
Result := True;
except
Result := False;
end;
end;
function OpenWav(FileName: string): Boolean;
begin
try
Assignfile(myWavFile, filename);
Reset(myWavFile, 1);
Result := True;
except
Result := False;
end;
end;
function CloseWav: Boolean;
begin
try
CloseFile(myWavFile);
Result := True;
except
Result := False;
end;
end;
function ValidateWav: Boolean;
const
RIFF: array[0..3] of byte = (82, 73, 70, 70);
WAVE: array[0..3] of byte = (87, 65, 86, 69);
_FMT: array[0..3] of byte = (102, 109, 116, 32);
FACT: array[0..3] of byte = (102, 97, 99, 116);
DATA: array[0..3] of byte = (100, 97, 116, 97);
_DATA: array[0..3] of byte = (64, 61, 74, 61);
var
RiffChunkSize, FmtChunkSize, FactChunkSize, DataChunkSize, i, j, tmp, Freq: integer;
omega,
dt, t: double;
vol: double;
begin
BytesRead := 0;
//Check "RIFF"
ReadDataBlock(4);
if not CompareMem(#DataBlock, #RIFF, SizeOf(DataBlock)) then
begin
Result := False;
Exit;
end;
//Get "RIFF" Chunk Data Size
ReadDataBlock(4);
Move(DataBlock, RiffChunkSize, 4);
//Check "WAVE"
ReadDataBlock(4);
if not CompareMem(#DataBlock, #WAVE, SizeOf(DataBlock)) then
begin
Result := False;
Exit;
end;
{FMT ---------------------------------------------------------------------}
//Check "FMT"
ReadDataBlock(4);
if not CompareMem(#DataBlock, #_FMT, SizeOf(DataBlock)) then
begin
Result := False;
Exit;
end;
//Get "FMT" Chunk Data Size
ReadDataBlock(4);
Move(DataBlock, FmtChunkSize, 4);
BytesRead := 0;
//Get Wav Compression Code
ReadDataBlock(2);
Move(DataBlock, CompressionCode, 2);
if not CompressionCodes.TryGetValue(CompressionCode, CompressionDesc) then
CompressionDesc := 'File Error!';
//Get Number of Channels
ReadDataBlock(2);
Move(DataBlock, NumOfChannels, 2);
//Get Sample Rate
ReadDataBlock(4);
Move(DataBlock, SampleRate, 4);
//Get Average Bytes Per Second
ReadDataBlock(4);
Move(DataBlock, BytesPerSecond, 4);
//Get Block Align
ReadDataBlock(2);
Move(DataBlock, BlockAlign, 2);
//Get Bits Per Sample
ReadDataBlock(2);
Move(DataBlock, BitsPerSample, 2);
//Extra Format Bytes
if BytesRead <= FmtChunkSize - 2 then
begin
ReadDataBlock(2);
Move(DataBlock, ExtraFormatBytes, 2);
end;
//If it's not Uncompressed/PCM File, then we have Extra Format Bytes
if CompressionCode <> 1 then
begin
//Skip Compression Data
for i := 0 to FmtChunkSize - BytesRead - 1 do
ReadDataBlock(1);
Result := False;
Exit;
end;
{FACT --------------------------------------------------------------------}
{FactChunkSize := 0;
//Check "FACT"
ReadDataBlock(4);
if CompareMem(#DataBlock, #FACT, SizeOf(DataBlock)) then
begin
//Get "FMT" Chunk Data Size
ReadDataBlock(4);
Move(DataBlock, FactChunkSize, 4);
BytesRead := 0;
for i := 0 to FactChunkSize - BytesRead - 1 do
ReadDataBlock(1);
end; }
{DATA ------------------------------------------------------------------}
while BytesRead < FmtChunkSize do
ReadDataBlock(1);
BytesRead := 0;
//Skip bytes until "data" shows up
while (not CompareMem(#DataBlock, #DATA, SizeOf(DataBlock))) and (not CompareMem(#DataBlock, #_DATA, SizeOf(DataBlock))) do
begin
ReadDataBlock(4);
end;
ReadDataBlock(4);
Move(DataBlock, DataChunkSize, 4);
Form1.Label1.Caption := 'Compression Code: ' + IntToStr(CompressionCode) + #10#13 +
'Compression Description: ' + CompressionDesc + #10#13 +
'Number of Channels: ' + IntToStr(NumOfChannels) + #10#13 +
'Sample Rate: ' + IntToStr(SampleRate) + #10#13 +
'Byes per Sample: ' + IntToStr(ByesPerSample) + #10#13 +
'Byes per Second: ' + IntToStr(BytesPerSecond) + #10#13 +
'Bits per Second: ' + IntToStr(BitsPerSample);
tmp := FileSize(myWavFile) - DataChunkSize;
{ j := 0;
Form1.Image1.Canvas.Rectangle(0, 0, Form1.Image1.Width, Form1.Image1.Height);
for i := 0 to (DataChunkSize div 20) do
begin
//BlockRead(myWavFile, DataBlock, 76, Count);
tmp := tmp + 76;
Seek(myWavFile, tmp);
ReadDataBlock(4);
Move(DataBlock, Freq, 4);
if i mod ((DataChunkSize div 80) div Form1.Image1.Width) = 0 then
begin
INC(J);
Form1.Image1.Canvas.MoveTo(j, 121 div 2);
Form1.Image1.Canvas.LineTo(j, (121 div 2) - Trunc((Freq / High(Integer)) * (121 div 2)));
end;
Application.ProcessMessages;
end;
Seek(myWavFile, FileSize(myWavFile) - DataChunkSize); }
InitAudioSys;
PacketIsPlaying := False;
SetLength(Samples, fmt.nSamplesPerSec);
while PacketIsPlaying = false do
begin
for i := 0 to fmt.nSamplesPerSec do
begin
ReadDataBlock(4);
Move(DataBlock, Freq, 4);
Samples[i] := Freq;
end;
PlaySound;
Sleep(2000);
Application.ProcessMessages;
end;
Result := True;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
f: file;
b: array[0..3] of byte;
count: integer;
begin
with opendialog1 do
if execute then
begin
Form1.Image1.Canvas.Rectangle(0, 0, Form1.Image1.Width, Form1.Image1.Height);
Label1.Font.Color := clBlack;
OpenWav(FileName);
if ValidateWav = False then
begin
Label1.Caption := 'Invalid File Data!';
Label1.Font.Color := clRed;
Exit;
end;
CloseWav;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CompressionCodes.Destroy;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height);
CompressionCodes := TDictionary<integer, string>.Create;
CompressionCodes.Add(0, 'Unknown');
CompressionCodes.Add(1, 'PCM/Uncompressed');
CompressionCodes.Add(2, 'Microsoft ADPCM');
CompressionCodes.Add(6, 'ITU G.711 a-law');
CompressionCodes.Add(7, 'ITU G.711 µ-law');
CompressionCodes.Add(17, 'IMA ADPCM');
CompressionCodes.Add(20, 'ITU G.723 ADPCM (Yamaha)');
CompressionCodes.Add(49, 'GSM 6.10');
CompressionCodes.Add(64, 'ITU G.721 ADPCM');
CompressionCodes.Add(80, 'MPEG');
CompressionCodes.Add(85, 'ISO/MPEG');
CompressionCodes.Add(65536, 'Experimental');
end;
end.
The Code needs a TLabel, a Tbutton and an OpenFileDialog on the form.
I have problem with the File Playback. currently I create arrays of samples with the length of SamplesPerSecond and play them one after another with the delay of 2000 (delays less than 2000ms will raise error).
What I want now is how can I Read samples and play them one after another smoothly and without delay. and Also I want to be able to visualize every few samples on a graph as the file is being played.
Funny you post this when you did, because I just yesterday wrote a working WAV player using Microsoft's waveOut... API.
You are not reading through the RIFF chunks effectively/correctly. I strongly suggest you use Microsoft's Multimedia functions (mmioOpen(), mmioDescend(), mmioAscend() and mmioRead()) instead of using AssignFile() and BlockRead(). WAV files are more complicated than you think, the code you have shown is not flexible enough to handle everything it may encounter. For instance, FMT is not always the first chunk in a WAV file, and there may be other chunks present before the DATA chunk, which you are not skipping.
When using waveOutOpen(), you should pass the original WAVEFORMATEX as read from the file, rather than creating a new WAVEFORMATEX that you populate with interpreted values. Using MMIO functions, you can declare a WAVEFORMATEX variable, mmioDescend() into the FMT chunk, mmioRead() the entire chunk directly into the variable, and then pass the variable as-is to waveOutOpen().
When using waveOutWrite(), you should use multiple audio buffers that you loop through (you can pre-prepare them with waveOutPrepareHeader() before you start reading the audio sample data, so you are only preparing them once). If you supply the wave device with only one buffer at a time, you are likely to get choppy audio playback (which it sounds like you are). It is best to use at least 3 buffers (my player uses 20, but I may knock that back later):
Fill 2 buffers with sample data and pass them to waveOutWrite() right away, and fill the 3rd buffer while they are playing.
When your waveOutOpen() callback says the 1st buffer is done playing, pass the 3rd buffer to waveOutWrite() and fill the 1st buffer with new data.
When the callback says the 2nd buffer is done playing, pass the 1st buffer to waveOutWrite() and fill the 2nd buffer with new data.
When the callback says the 3rd buffer is done playing, pass the 2nd buffer to waveOutWrite() and fill the 3rd buffer with new data.
And so on, continuing this round-robin logic until the end of the DATA chunk is reached.
The wave device should always have at least 2 active audio buffers playing at any given time to avoid gaps in the playback. Let the callback tell you when each buffer is done so you can provide the next buffer.
I based my player code on David Overton's tutorial, which has a LOT of information, and code examples:
Playing Audio in Windows using waveOut Interface
http://www.et.hs-wismar.de/~litschke/TMS/Audioprogrammierung.pdf
http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=4422&lngWId=3
The only tweaks I made to the tutorial's code was to:
using MMIO functions for file I/O.
using the RTL's memory management functions instead of OS memory functions.
changed the size of the audio buffers. David uses 8KB buffers, which I found caused garbage playback after a few seconds as the wave device was not being fed audio samples fast enough for my WAV files (which are GSM encoded, not PCM, so they have smaller sample sizes). I changed the buffer size to the nAvgBytesPerSec value reported by the FMT chunk, and then the audio played cleanly all the way through.
error handling.
Try this (translated to Delphi from my real code written in C++):
{
The following is based on code written by David Overton:
Playing Audio in Windows using waveOut Interface
http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=4422&lngWId=3
https://www.et.hs-wismar.de/~litschke/TMS/Audioprogrammierung.pdf
But with some custom tweaks.
}
uses
..., Winapi.Windows, Winapi.MMSystem;
const
BLOCK_COUNT = 20;
procedure waveOutProc(hWaveOut: HWAVEOUT; uMsg: UINT; dwInstance, dwParam1, dwParam2: DWORD_PTR): stdcall; forward;
function writeAudio(hWaveOut: HWAVEOUT; data: PByte; size: Integer): Boolean; forward;
var
waveCriticalSection: CRITICAL_SECTION;
waveBlocks: PWaveHdr;
waveFreeBlockCount: Integer;
waveCurrentBlock: Integer;
buffer: array[0..1023] of Byte;
mmckinfoParent: MMCKINFO;
mmckinfoSubchunk: MMCKINFO;
dwFmtSize: DWORD;
dwDataSize: DWORD;
dwSizeToRead: DWORD;
hmmio: HMMIO;
wfxBuffer: array of Byte;
wfx: PWaveFormatEx;
hWaveOut: HWAVEOUT;
blockBuffer: array of Byte;
pBlockData: PByte;
i: Integer;
readBytes: LONG;
begin
...
hmmio := mmioOpen(PChar(FileName), nil, MMIO_READ or MMIO_DENYWRITE);
if hmmio = 0 then
raise Exception.Create('Unable to open WAV file');
try
mmckinfoParent.fccType := mmioStringToFOURCC('WAVE', 0);
if mmioDescend(hmmio, #mmckinfoParent, nil, MMIO_FINDRIFF) <> MMSYSERR_NOERROR then
raise Exception.CreateFmt('%s is not a WAVE file', [FileName]);
mmckinfoSubchunk.ckid := mmioStringToFOURCC('fmt', 0);
if mmioDescend(hmmio, #mmckinfoSubchunk, #mmckinfoParent, MMIO_FINDCHUNK) <> MMSYSERR_NOERROR then
raise Exception.Create('File has no FMT chunk');
dwFmtSize := mmckinfoSubchunk.cksize;
if dwFmtSize = 0 then
raise Exception.Create('File FMT chunk is empty');
SetLength(wfxBuffer, dwFmtSize);
wfx := PWaveFormatEx(Pointer(wfxBuffer));
if mmioRead(hmmio, PAnsiChar(wfx), dwFmtSize) <> dwFmtSize then
raise Exception.Create('Failed to read FMT chunk');
if mmioAscend(hmmio, #mmckinfoSubchunk, 0) <> MMSYSERR_NOERROR then
raise Exception.Create('Failed to ascend into RIFF chunk');
mmckinfoSubchunk.ckid := mmioStringToFOURCC('data', 0);
if mmioDescend(hmmio, #mmckinfoSubchunk, #mmckinfoParent, MMIO_FINDCHUNK) <> MMSYSERR_NOERROR then
raise Exception.Create('File has no DATA chunk');
dwDataSize := mmckinfoSubchunk.cksize;
if dwDataSize <> 0 then
begin
hWaveOut := 0;
if waveOutOpen(#hWaveOut, WAVE_MAPPER, wfx, DWORD_PTR(#waveOutProc), 0, CALLBACK_FUNCTION) <> MMSYSERR_NOERROR then
raise Exception.Create('Unable to open wave mapper device');
try
SetLength(blockBuffer, (sizeof(WAVEHDR) + wfx.nAvgBytesPerSec) * BLOCK_COUNT);
pBlockData := PByte(blockBuffer);
waveBlocks := PWaveHdr(pBlockData);
Inc(pBlockData, sizeof(WAVEHDR) * BLOCK_COUNT);
for i := 0 to BLOCK_COUNT-1 do
begin
ZeroMemory(#waveBlocks[i], sizeof(WAVEHDR));
waveBlocks[i].dwBufferLength := wfx.nAvgBytesPerSec;
waveBlocks[i].lpData := pBlockData;
if waveOutPrepareHeader(hWaveOut, #waveBlocks[i], sizeof(WAVEHDR)) <> MMSYSERR_NOERROR then
raise Exception.Create('Failed to prepare a WAV audio header');
Inc(pBlockData, wfx.nAvgBytesPerSec);
end;
waveFreeBlockCount := BLOCK_COUNT;
waveCurrentBlock := 0;
InitializeCriticalSection(#waveCriticalSection);
try
repeat
dwSizeToRead := Min(dwDataSize, sizeof(buffer));
readBytes := mmioRead(hmmio, PAnsiChar(buffer), dwSizeToRead);
if readBytes <= 0 then Break;
if readBytes < sizeof(buffer) then
ZeroMemory(#buffer[readBytes], sizeof(buffer) - readBytes);
writeAudio(hWaveOut, buffer, sizeof(buffer));
Dec(dwDataSize, readBytes);
until dwDataSize = 0;
writeAudio(hWaveOut, nil, 0);
while waveFreeBlockCount < BLOCK_COUNT do
Sleep(10);
for i := 0 to BLOCK_COUNT-1 do
begin
if (waveBlocks[i].dwFlags and WHDR_PREPARED) <> 0 then
waveOutUnprepareHeader(hWaveOut, #waveBlocks[i], sizeof(WAVEHDR));
end;
finally
DeleteCriticalSection(#waveCriticalSection);
end;
finally
waveOutClose(hWaveOut);
end;
end;
finally
mmioClose(hmmio, 0);
end;
end;
procedure waveOutProc(hWaveOut: HWAVEOUT; uMsg: UINT; dwInstance, dwParam1, dwParam2: DWORD_PTR); stdcall;
begin
if uMsg = WOM_DONE then
begin
EnterCriticalSection(&waveCriticalSection);
Inc(waveFreeBlockCount);
LeaveCriticalSection(&waveCriticalSection);
end;
end;
procedure writeAudio(hWaveOut: HWAVEOUT; data: PByte; size: Integer);
var
current: PWaveHdr;
remaining: Integer;
begin
current := #waveBlocks[waveCurrentBlock];
if data = nil then
begin
if current.dwUser <> 0 then
begin
if current.dwUser < current.dwBufferLength then
begin
remaining := Integer(current.dwBufferLength - current.dwUser);
ZeroMemory(current.lpData + current.dwUser, remaining);
Inc(current.dwUser, remainint);
end;
EnterCriticalSection(&waveCriticalSection);
Dec(waveFreeBlockCount);
LeaveCriticalSection(&waveCriticalSection);
if waveOutWrite(hWaveOut, current, sizeof(WAVEHDR)) <> MMSYSERR_NOERROR then
raise Exception.Create('Failed to write a WAV audio header');
end;
end else
begin
while size > 0 do
begin
remaining := Integer(current.dwBufferLength - current.dwUser);
if size < remaining then
begin
Move(data^, (current.lpData + current.dwUser)^, size);
Inc(current.dwUser, size);
Break;
end;
Move(data^, (current.lpData + current.dwUser)^, remaining);
Inc(current.dwUser, remaining);
Inc(data, remaining);
Dec(size, remaining);
EnterCriticalSection(&waveCriticalSection);
Dec(waveFreeBlockCount);
LeaveCriticalSection(&waveCriticalSection);
if waveOutWrite(hWaveOut, current, sizeof(WAVEHDR)) <> MMSYSERR_NOERROR then
raise Exception.Create('Failed to write a WAV audio header');
while waveFreeBlockCount = 0 do
Sleep(10);
Inc(waveCurrentBlock);
waveCurrentBlock := waveCurrentBlock mod BLOCK_COUNT;
current := #waveBlocks[waveCurrentBlock];
current.dwUser := 0;
end;
end;
end;
Regarding visualization of the samples, you are best off using a 3rd party component for that (and you probably should be using a 3rd party WAV player anyway, instead of writing API code manually), such as Mitov Software's AudioLab components.
UINT is an unsigned 32 bit integer which is not used in Delphi. Change UINT to "cardinal", which is Delphi's 32 bit unsigned integer.
Ian

Using the TEdit context menu for TRichEdit

Is there a simple/clever way to load the standard Windows TEdit menu into this TRichEdit?
I know that I could create a simple menu to simulate the TEdit menu for the simple operations like copy/paste etc. (Example), however I would also like to keep the more advanced menu options such as the unicode options, reading order, and to utilize the same localization strings.
Edit: I have found a possible lead (trying to figure it out as I'm not an MFC expert)...
Based on the "possible lead" and a bit of MSDN, I came up with a possible solution.
I'm still unable to resolve the reading order issue (and the unicode options). It seems that it works differently for RichEdit than for Edit, and simply setting or getting the WS_EX_RTLREADING flag does not work as excpected. Anyways, here is the code:
procedure RichEditPopupMenu(re: TRichEdit);
const
IDM_UNDO = WM_UNDO;
IDM_CUT = WM_CUT;
IDM_COPY = WM_COPY;
IDM_PASTE = WM_PASTE;
IDM_DELETE = WM_CLEAR;
IDM_SELALL = EM_SETSEL;
IDM_RTL = $8000; // WM_APP ?
Enables: array[Boolean] of DWORD = (MF_DISABLED or MF_GRAYED, MF_ENABLED);
Checks: array[Boolean] of DWORD = (MF_UNCHECKED, MF_CHECKED);
var
hUser32: HMODULE;
hmnu, hmenuTrackPopup: HMENU;
Cmd: DWORD;
Flags: Cardinal;
HasSelText: Boolean;
FormHandle: HWND;
// IsRTL: Boolean;
begin
hUser32 := LoadLibraryEx(user32, 0, LOAD_LIBRARY_AS_DATAFILE);
if (hUser32 <> 0) then
try
hmnu := LoadMenu(hUser32, MAKEINTRESOURCE(1));
if (hmnu <> 0) then
try
hmenuTrackPopup := GetSubMenu(hmnu, 0);
HasSelText := Length(re.SelText) <> 0;
EnableMenuItem(hmnu, IDM_UNDO, Enables[re.CanUndo]);
EnableMenuItem(hmnu, IDM_CUT, Enables[HasSelText]);
EnableMenuItem(hmnu, IDM_COPY, Enables[HasSelText]);
EnableMenuItem(hmnu, IDM_PASTE, Enables[Clipboard.HasFormat(CF_TEXT)]);
EnableMenuItem(hmnu, IDM_DELETE, Enables[HasSelText]);
EnableMenuItem(hmnu, IDM_SELALL, Enables[Length(re.Text) <> 0]);
// IsRTL := GetWindowLong(re.Handle, GWL_EXSTYLE) and WS_EX_RTLREADING <> 0;
// EnableMenuItem(hmnu, IDM_RTL, Enables[True]);
// CheckMenuItem(hmnu, IDM_RTL, Checks[IsRTL]);
FormHandle := GetParentForm(re).Handle;
Flags := TPM_LEFTALIGN or TPM_RIGHTBUTTON or TPM_NONOTIFY or TPM_RETURNCMD;
Cmd := DWORD(TrackPopupMenu(hmenuTrackPopup, Flags,
Mouse.CursorPos.X, Mouse.CursorPos.Y, 0, FormHandle, nil));
if Cmd <> 0 then
begin
case Cmd of
IDM_UNDO: re.Undo;
IDM_CUT: re.CutToClipboard;
IDM_COPY: re.CopyToClipboard;
IDM_PASTE: re.PasteFromClipboard;
IDM_DELETE: re.ClearSelection;
IDM_SELALL: re.SelectAll;
IDM_RTL:; // ?
end;
end;
finally
DestroyMenu(hmnu);
end;
finally
FreeLibrary(hUser32);
end;
end;
procedure TForm1.RichEditEx1ContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean);
begin
RichEditPopupMenu(TRichEdit(Sender));
Handled := True;
end;
Any feedback would be nice :)

How can I generate continuous tones of varying frequencies?

I want to generate and play a continuous sound with specific frequencies and amplitudes that change over time. I don't want to have a delay between sounds. How can I do this with Delphi or C++ Builder?
This very simple example should get you started.
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows, MMSystem;
type
TWaveformSample = integer; // signed 32-bit; -2147483648..2147483647
TWaveformSamples = packed array of TWaveformSample; // one channel
var
Samples: TWaveformSamples;
fmt: TWaveFormatEx;
procedure InitAudioSys;
begin
with fmt do
begin
wFormatTag := WAVE_FORMAT_PCM;
nChannels := 1;
nSamplesPerSec := 44100;
wBitsPerSample := 32;
nAvgBytesPerSec := nChannels * nSamplesPerSec * wBitsPerSample div 8;
nBlockAlign := nChannels * wBitsPerSample div 8;
cbSize := 0;
end;
end;
// Hz // msec
procedure CreatePureSineTone(const AFreq: integer; const ADuration: integer;
const AVolume: double { in [0, 1] });
var
i: Integer;
omega,
dt, t: double;
vol: double;
begin
omega := 2*Pi*AFreq;
dt := 1/fmt.nSamplesPerSec;
t := 0;
vol := MaxInt * AVolume;
SetLength(Samples, Round((ADuration / 1000) * fmt.nSamplesPerSec));
for i := 0 to high(Samples) do
begin
Samples[i] := round(vol*sin(omega*t));
t := t + dt;
end;
end;
procedure PlaySound;
var
wo: integer;
hdr: TWaveHdr;
begin
if Length(samples) = 0 then
begin
Writeln('Error: No audio has been created yet.');
Exit;
end;
if waveOutOpen(#wo, WAVE_MAPPER, #fmt, 0, 0, CALLBACK_NULL) = MMSYSERR_NOERROR then
try
ZeroMemory(#hdr, sizeof(hdr));
with hdr do
begin
lpData := #samples[0];
dwBufferLength := fmt.nChannels * Length(Samples) * sizeof(TWaveformSample);
dwFlags := 0;
end;
waveOutPrepareHeader(wo, #hdr, sizeof(hdr));
waveOutWrite(wo, #hdr, sizeof(hdr));
sleep(500);
while waveOutUnprepareHeader(wo, #hdr, sizeof(hdr)) = WAVERR_STILLPLAYING do
sleep(100);
finally
waveOutClose(wo);
end;
end;
begin
try
InitAudioSys;
CreatePureSineTone(400, 1000, 0.7);
PlaySound;
except
on E: Exception do
begin
Writeln(E.Classname, ': ', E.Message);
Readln;
end;
end;
end.
Notice in particular the neat interface you get:
InitAudioSys;
CreatePureSineTone(400, 1000, 0.7);
PlaySound;
By using WaveAudio library it's possible to generate a continous cosinus wave.
I was gonna post some code but I can't figure out how to do it properly so I won't.
But all you need to do is use TLiveAudioPlayer and then override the OnData event.
And also set Async to true if there is no message pump.
Update in dec 2021, I just came across my answer by chance... so I would like to update it, I used this ASIO library in 2009 I think and later, great library below:*
I would recommend ASIO library for Delphi !
https://sourceforge.net/projects/delphiasiovst/
Using this is super easy, not all files have to be included, start with the main one and add the rest from there, also see the examples.
Ultimately it's as easy as OnSomeEvent/OnSomeBuffer
and then simply filling an array with floating point values.
Don't remember the exact name of the OnEvent but you'll find it easily in the examples.
Another thing to do is set some component to active/true and voila.
The nice thing about ASIO is very low latency, it's even possible to get it down to 50 microseconds or even lower.
It does require an ASIO driver for your sound chip.
ASIO = audio stream input output
API designed by audio engineers !
It probably doesn't get any better than this ! ;)

Resources