Author Topic: How do I build something that listens only to how loud someone speaks?  (Read 170 times)

El_Diablo

  • Posts: 4
Hi,

I saw some examples, but I can't seem to put my finger on how to listen only via volume.  I'm trying to build a application that listens to the mic and when a certain volume is reached it will press a button to send to a certain application.

This is what I have so far.  All this code will do is shows me 0 first, then 65536 constantly.   How do I fix this code so that I can see the volume of sound from the mic correctly?:

<<Delphi 10.4.1>>

Code: [Select]
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.MPlayer, BASS,
  Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    Timer1: TTimer;
    ComboBox1: TComboBox;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function RecordingCallback(h:HRECORD; b:Pointer; l,u: DWord): boolean; stdcall;
  end;

var
  Form1: TForm1;
  rchan:   HRECORD; // recording channel
  level2: dword;

implementation

{$R *.dfm}

(* This function called while recording audio *)
function TForm1.RecordingCallback(h:HRECORD; b:Pointer; l,u: DWord): boolean; stdcall;
 //var level:dword;
 begin
  level2:=BASS_ChannelGetLevel(h);
  //Memo1.Lines.add('Loword ' + IntToStr(LoWord(level))+' - HiWord '+IntToStr(HiWord(level)));
  Result := True;
 end;

// Start button
procedure TForm1.Button1Click(Sender: TObject);
begin
  BASS_RecordInit(-1);
  Timer1.Enabled := true;
  BASS_RecordStart(44100, 0, 0, @TForm1.RecordingCallback, nil);
end;

// Stop Button
procedure TForm1.Button2Click(Sender: TObject);
begin
  Timer1.Enabled := false;
  BASS_RecordFree;
end;

procedure TForm1.ComboBox1Change(Sender: TObject);
var
i: Integer;
  r: Boolean;
begin
// enable the selected input
    r := True;
    i := 0;
    // first disable all inputs, then...
while r do
    begin
r := BASS_RecordSetInput(i, BASS_INPUT_OFF, -1);
        Inc(i);
end;
    // ...enable the selected.
BASS_RecordSetInput(ComboBox1.ItemIndex, BASS_INPUT_ON, -1);
//UpdateInputInfo; // update info
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  dName: PAnsiChar;
  level: Single;
begin
// check the correct BASS was loaded
if (HIWORD(BASS_GetVersion) <> BASSVERSION) then
begin
MessageBox(0,'An incorrect version of BASS.DLL was loaded', nil,MB_ICONERROR);
Halt;
end;
if (not BASS_RecordInit(-1)) or (not BASS_Init(-1, 44100, 0, Handle, nil)) then
begin
BASS_RecordFree;
BASS_Free();
MessageDlg('Cannot start default recording device!', mtError, [mbOk], 0);
Halt;
end;
i := 0;
dName := BASS_RecordGetInputName(i);
while dName <> nil do
begin
ComboBox1.Items.Add(StrPas(dName));
// is this one currently "on"?
if (BASS_RecordGetInput(i, level) and BASS_INPUT_OFF) = 0 then
        ComboBox1.ItemIndex := i;
Inc(i);
dName := BASS_RecordGetInputName(i);
end;
    ComboBox1Change(Self); // display info
end;


procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Memo1.Lines.add('Loword ' + IntToStr(LoWord(level2))+' - HiWord '+IntToStr(HiWord(level2)));
end;

end.




El_Diablo

  • Posts: 4
It appears you don't need the callback, as it only calls back once.  With a timer I was able to constantly ping the mic for the volume that was being sounded through it.

This "function TForm1.RecordingCallback(h:HRECORD; b:Pointer; l,u: DWord): boolean; stdcall;" is only called once by BASS.  My Timer called it more than once to check for volume of sound coming from mic.

After much trial an error here is my answer to my question:

Code: [Select]
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.MPlayer, System.UITypes, BASS,
  Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    Timer1: TTimer;
    ComboBox1: TComboBox;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function RecordingCallback(h:HRECORD; b:Pointer; l,u: DWord): boolean; stdcall;
  end;

var
  Form1: TForm1;
  rchan:   HRECORD; // recording channel
  level2: dword;
  LoudEnough: boolean = FALSE;
  threshold: DWORD = 1000; // trigger level

implementation

{$R *.dfm}

(* This function called while recording audio *)
function TForm1.RecordingCallback(h:HRECORD; b:Pointer; l,u: DWord): boolean; stdcall;
 //var level:dword;
 begin
  level2:=BASS_ChannelGetLevel(h);
  LoudEnough := (LoWord(level2) >= threshold) or (HiWord(level2) >= threshold);
  //Memo1.Lines.add('Loword ' + IntToStr(LoWord(level))+' - HiWord '+IntToStr(HiWord(level)));
  Result := True;
 end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  {
  if BASS_RecordSetDevice(0) = false then
  begin
    memo1.Lines.Add('BASS_RecordSetDevice ERROR = '+ BASS_ErrorGetCode().ToString);
  end;}

  Timer1.Enabled := true;
  rchan := BASS_RecordStart(44100, 1, 0, @TForm1.RecordingCallback, nil);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Timer1.Enabled := false;
  BASS_RecordFree;
end;

procedure TForm1.ComboBox1Change(Sender: TObject);
var
i: Integer;
  r: Boolean;
begin
// enable the selected input
    r := True;
    i := 0;
    // first disable all inputs, then...
while r do
    begin
r := BASS_RecordSetInput(i, BASS_INPUT_OFF, -1);
        Inc(i);
end;
    // ...enable the selected.
BASS_RecordSetInput(ComboBox1.ItemIndex, BASS_INPUT_ON, -1);
//UpdateInputInfo; // update info
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  dName: PAnsiChar;
  level: Single;
  flags: dWord;
  deviceInfo: BASS_DEVICEINFO;
  info: BASS_INFO;
begin
// check the correct BASS was loaded
if (HIWORD(BASS_GetVersion) <> BASSVERSION) then
begin
MessageBox(0,'An incorrect version of BASS.DLL was loaded', nil,MB_ICONERROR);
Halt;
end;
if (not BASS_RecordInit(-1)) or (not BASS_Init(-1, 44100, 0, Handle, nil)) then
begin
BASS_RecordFree;
BASS_Free();
MessageDlg('Cannot start default recording device!', mtError, [mbOk], 0);
Halt;
end;
i := 0;
// dName := BASS_RecordGetInputName(i);
  //dName := (BASS_RecordGetDeviceInfo(i,deviceInfo));
while (BASS_RecordGetDeviceInfo(i,deviceInfo)) do
begin
    //BASS_GetInfo(info);
ComboBox1.Items.Add(String(deviceInfo.name));
// is this one currently "on"?
    //flags := BASS_RecordGetInput(i, level);
    //if (flags and BASS_INPUT_TYPE_MASK) = BASS_INPUT_TYPE_MIC then
if (BASS_RecordGetInput(i, level) and BASS_INPUT_OFF) = 0 then
        ComboBox1.ItemIndex := i;
Inc(i);
//dName := BASS_RecordGetInputName(i);
end;
    ComboBox1Change(Self); // display info
end;

             //BASS_INPUT_TYPE_MASK
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  level2:=BASS_ChannelGetLevel(rchan);
  LoudEnough := (LoWord(level2) >= threshold) or (HiWord(level2) >= threshold);

  if LoudEnough then Memo1.Lines.add('Push to Talk ON')
    else Memo1.Lines.add('Push to Talk OFF');
  Memo1.Lines.add('Loword ' + LoWord(level2).ToString +' - HiWord '+ HiWord(level2).ToString);
end;

end.

The above works, the old code in the examples for Delphi no longer work!

Ian @ un4seen

  • Administrator
  • Posts: 23311
The Delphi examples are kindly provided by other users but that does mean that they aren't always up to date with the C examples, so the latter should be preferred as a reference if possible.

Regarding your code above, the RECORDPROC (RecordingCallback) signature is incorrect, which might explain why it's only getting called once (it should get called repeatedly). The "user" parameter should be a pointer, so like this:

Code: [Select]
function TForm1.RecordingCallback(h:HRECORD; b:Pointer; l:DWord; u:Pointer): boolean; stdcall;
 //var level:dword;
 begin
  level2:=BASS_ChannelGetLevel(h);
  LoudEnough := (LoWord(level2) >= threshold) or (HiWord(level2) >= threshold);
  //Memo1.Lines.add('Loword ' + IntToStr(LoWord(level))+' - HiWord '+IntToStr(HiWord(level)));
  Result := True;
 end;

But you could just do the level check in the timer instead anyway (remove it from the callback). Depending on the period of your timer, it may also be better to use BASS_ChannelGetLevelEx instead of BASS_ChannelGetLevel because the former has a "length" parameter that tells BASS how much data to look at, so that you don't miss anything between calls.