what is the simpliest way to play sound from array data in delphi
Asked Answered
M

5

7

Is there any simple function? I am searching something like that

Play(@data, 44000, 100 {time});

Menses answered 11/8, 2010 at 8:47 Comment(0)
B
7

I have worked quite a lot with PCM audio manipulation. I always use this function when playing short sequences of custom waveform audio data:

var
  PlaySoundStopper: PBoolean;
  SoundPlayerActive: boolean = false;

procedure PlaySound(const Sound: TASSound);
var
  hWave: HWAVEOUT;
  hdr: TWaveHdr;
  buf: PAnsiChar;
  fmt: TWaveFormatEx;
  i: Integer;
  n: Integer;
begin

  try

    with fmt do
    begin
      wFormatTag := WAVE_FORMAT_PCM;
      nChannels := length(Sound.Channels);
      nSamplesPerSec := Sound.SampleRate;
      wBitsPerSample := 32;
      nAvgBytesPerSec := nChannels * nSamplesPerSec * wBitsPerSample div 8;
      nBlockAlign := nChannels * wBitsPerSample div 8;
      cbSize := 0;
    end;

    GetMem(buf, fmt.nChannels * length(Sound.Channels[0]) * sizeof(TASWaveformSample));
    if length(Sound.Channels) = 1 then
      CopyMemory(buf, @(Sound.Channels[0, 0]), length(Sound.Channels[0]) * sizeof(TASWaveformSample))
    else
      for i := 0 to high(Sound.Channels[0]) do
        for n := 0 to high(Sound.Channels) do
          CopyMemory(buf + sizeof(TASWaveformSample) * (i * fmt.nChannels + n), @(Sound.Channels[n, i]), sizeof(TASWaveformSample));

    if waveOutOpen(@hWave, WAVE_MAPPER, @fmt, 0, 0, CALLBACK_NULL) <> MMSYSERR_NOERROR then
      raise Exception.Create('SoundPlayerThread.Execute: waveOutOpen failed: ' + SysErrorMessage(GetLastError));

    ZeroMemory(@hdr, sizeof(hdr));
    with hdr do
    begin
      lpData := buf;
      dwBufferLength := fmt.nChannels * length(Sound.Channels[0]) * sizeof(TASWaveformSample);
      dwFlags := 0;
    end;

    try

      SoundPlayerActive := true;

      waveOutPrepareHeader(hWave, @hdr, sizeof(hdr));
      waveOutWrite(hWave, @hdr, sizeof(hdr));
      sleep(500);

      while waveOutUnprepareHeader(hWave, @hdr, sizeof(hdr)) = WAVERR_STILLPLAYING do
        if PlaySoundStopper^ then
        begin
          waveOutPause(hWave);
          waveOutUnprepareHeader(hWave, @hdr, sizeof(hdr));
          break;
        end
        else
          sleep(100);

    finally
      SoundPlayerActive := false;
      waveOutClose(hWave);
      FreeMem(buf);
    end;

  except
    on E: Exception do MessageBox(0, PChar(E.ClassName + ': ' + E.Message), 'Sound Playback Error', MB_ICONERROR);
  end;
end;

where

type
  TASWaveformSample = integer; // signed 32-bit; -2147483648..2147483647
  TASWaveformSamples = packed array of TASWaveformSample; // one channel
  PASSound = ^TASSound;
  TASSound = record
    Channels: packed array of TASWaveformSamples;
    SampleRate: cardinal;
  end;

A perhaps better way, is to use a thread for the playing. Then I do

var
  OwnerForm: HWND; // = 0;
  SndSource: PASSound; // = nil;
  ThreadPlaying: boolean; // = false;

type
  TSoundPlayerThread = class(TThread)
  private
    { Private declarations }
  protected
    procedure Execute; override;
  end;

implemented as

procedure TSoundPlayerThread.Execute;
var
  hWave: HWAVEOUT;
  hdr: TWaveHdr;
  buf: PAnsiChar;
  fmt: TWaveFormatEx;
  i: Integer;
  n: Integer;
begin

  ThreadPlaying := true;
  try

   try

      if not Assigned(SndSource) then
        Exit;

      with fmt do
      begin
        wFormatTag := WAVE_FORMAT_PCM;
        nChannels := length(SndSource^.Channels);
        nSamplesPerSec := SndSource^.SampleRate;
        wBitsPerSample := 32;
        nAvgBytesPerSec := nChannels * nSamplesPerSec * wBitsPerSample div 8;
        nBlockAlign := nChannels * wBitsPerSample div 8;
        cbSize := 0;
      end;

      GetMem(buf, fmt.nChannels * length(SndSource^.Channels[0]) * sizeof(TASWaveformSample));
      if length(SndSource^.Channels) = 1 then
        CopyMemory(buf, @(SndSource^.Channels[0, 0]), length(SndSource^.Channels[0]) * sizeof(TASWaveformSample))
      else
        for i := 0 to high(SndSource^.Channels[0]) do
          for n := 0 to high(SndSource^.Channels) do
            CopyMemory(buf + sizeof(TASWaveformSample) * (i * fmt.nChannels + n), @(SndSource^.Channels[n, i]), sizeof(TASWaveformSample));

      if waveOutOpen(@hWave, WAVE_MAPPER, @fmt, 0, 0, CALLBACK_NULL) <> MMSYSERR_NOERROR then
        raise Exception.Create('SoundPlayerThread.Execute: waveOutOpen failed: ' + SysErrorMessage(GetLastError));

      ZeroMemory(@hdr, sizeof(hdr));
      with hdr do
      begin
        lpData := buf;
        dwBufferLength := fmt.nChannels * length(SndSource^.Channels[0]) * sizeof(TASWaveformSample);
        dwFlags := 0;
      end;

      waveOutPrepareHeader(hWave, @hdr, sizeof(hdr));
      waveOutWrite(hWave, @hdr, sizeof(hdr));
      sleep(500);

      while waveOutUnprepareHeader(hWave, @hdr, sizeof(hdr)) = WAVERR_STILLPLAYING do
      begin
        sleep(100);
        if Terminated then
          waveOutReset(hWave);
      end;

      waveOutClose(hWave);
      FreeMem(buf);

    except
      on E: Exception do MessageBox(0, PChar(E.ClassName + ': ' + E.Message), 'TSoundPlayerThread', MB_ICONERROR);
    end;

  finally
    ThreadPlaying := false;
  end;
end;
Buhrstone answered 11/8, 2010 at 20:51 Comment(1)
i would like to ask you more about PCMs. this is my E-Add [email protected] i need your help.Pol
M
4

Wave Audio Package has TLiveAudioPlayer component. It plays audio from buffer.

Mcculloch answered 11/8, 2010 at 9:25 Comment(2)
Yes it work for me as well. and the good thing of it, no external components like bass.dll lame.exe etc.Pol
I think this answer should've been marked as the best one.Preterit
N
3

The Win32 API PlaySound function can play standard RIFF-encoded audio (such as WAV audio) from a memory block by using its SND_MEMORY flag. Alternatively, if the audio is in the app's resources, you can use the SND_RESOURCE flag instead.

Notus answered 11/8, 2010 at 20:10 Comment(0)
P
2

Microsoft has a Knowledge Base article telling you how you can play sound from memory using MCI. You'll probably need to have the wave file header in your array, or otherwise copy in the right data during the first read, but other than that it should be fairly easy to port over.

Pineal answered 11/8, 2010 at 8:55 Comment(0)
R
0

I couldn't find a complete solution that isn't based on the outdated sndPlaySound, so here are two functions that play ".wav" files from both a TMemoryStream and from a file :

uses mmsystem;

procedure PlaySoundFromFile(FileName : String);
var
  mStream : TMemoryStream;
begin
  mStream := TMemoryStream.Create;
  Try mStream.LoadFromFile(FileName); Except End;
  If mStream.Size > 0 then PlaySoundFromStream(mStream);
  mStream.Free;
end;

procedure PlaySoundFromStream(mStream : TMemoryStream);
begin
  PlaySound(mStream.Memory,0,SND_MEMORY or SND_SYNC);
end;

The sound is played synchronously and from memory, you can find the other PlaySound flags in the link on Remy's answer. If you switch to async playback, make sure to not clear the sound memory before playback ends.

Reubenreuchlin answered 6/4, 2021 at 16:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.