diff --git a/example/Loopback/loopback.lps b/example/Loopback/loopback.lps index bec3982..957187c 100644 --- a/example/Loopback/loopback.lps +++ b/example/Loopback/loopback.lps @@ -21,8 +21,8 @@ - - + + @@ -312,8 +312,8 @@ - - + + @@ -428,123 +428,123 @@ - + - + - + - + - + - + - + - + - - + + - - + + - - + + - - + + - + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - + diff --git a/example/Loopback/unit1.pas b/example/Loopback/unit1.pas index 8741e86..c0714bb 100644 --- a/example/Loopback/unit1.pas +++ b/example/Loopback/unit1.pas @@ -86,15 +86,17 @@ TForm1 = class(TForm) FLoopbackContext: TALSLoopbackContext; // tracks instance FTracks: array[0..2] of TTrack; + FMixingTime: double; + FCanceled: boolean; procedure InitTracks; function GetSampleRate: integer; function GetChannel: TALSLoopbackChannel; function GetSampleType: TALSLoopbackSampleType; - procedure EnableMix(aState: boolean); + procedure EnableMixGUI(aState: boolean); private procedure ProcessLoopbackContextOnProgress(Sender: TALSLoopbackContext; aTimePos: double; const aFrameBuffer: TALSLoopbackFrameBuffer; - var SaveBufferToFile: boolean; var Done: boolean); + var SaveBufferToFile, Cancel: boolean); public end; @@ -126,9 +128,7 @@ procedure TForm1.BitBtn1Click(Sender: TObject); procedure TForm1.BCancelClick(Sender: TObject); begin // User want to cancel the mix - FLoopbackContext.CancelMix; - - BCancel.Tag := 1; + FCanceled := True; end; procedure TForm1.ComboBox2Select(Sender: TObject); @@ -259,7 +259,7 @@ procedure TForm1.BMixToFileClick(Sender: TObject); end; - EnableMix(False); // Avoid any user's interaction. + EnableMixGUI(False); // Avoid any user's interaction. // Customize our context attributes for a loopback context FAttribs.InitDefault; // don't forget this first ! @@ -289,7 +289,7 @@ procedure TForm1.BMixToFileClick(Sender: TObject); outputFilename := ChangeFileExt(Edit1.Text, '.wav'); outputFilename := ConcatPaths([DirectoryEdit1.Text, outputFilename]); - // In this demo, the file output major format is wav file and the bit width is + // In this demo, the file output major format is 'wav' and the bit width is // the same as the loopback context. case ComboBox3.ItemIndex of 0: fileFormat := ALSMakeFileFormat( SF_FORMAT_WAV, SF_FORMAT_PCM_16); @@ -303,30 +303,39 @@ procedure TForm1.BMixToFileClick(Sender: TObject); begin ShowMessage('Can not create output file' + LineEnding + outputFilename); FreeAndNil(FLoopbackContext); - EnableMix(True); + EnableMixGUI(True); Exit; end; - // Define a callback to update our progress bar, vu-meters and controls - // the mixing process. This callback will be fired each time a buffer is + // Define a callback to update our progress bar, vu-meters and controls the + // mixing process. This callback will be fired each time a buffer is // filled with audio FLoopbackContext.OnProgress := @ProcessLoopbackContextOnProgress; - // Start the mixing -> keep the hand until the mixing is done or canceled. - // In the meantime OnProgress callback is fired. - FLoopbackContext.StartMixing; + FCanceled := False; + + // We have to call this method before render audio. + FLoopbackContext.BeginOfMix; + + repeat + // Ask the context to render 10Ms of audio. + FLoopbackContext.Mix(0.010); + until (FMixingTime >= FloatSpinEdit2.Value) or // mixing time reach the end of the interval + FCanceled; // user click cancel button + + // We have to call this method at the end, to finalize the mixing process. + FLoopbackContext.EndOfMix; // Checks error only if the mix was not canceled - if BCancel.Tag = 0 then // if user clicks cancel button, its Tag is sets to 1. + if not FCanceled then begin // Check mixing error if FLoopbackContext.MixingError then Showmessage(FLoopbackContext.MixingStrError) else ShowMessage('Mixdown saved to' + lineending + - outputFilename + lineending + 'SUCCESS'); - end - else BCancel.Tag := 0; + outputFilename + lineending + 'WITH SUCCESS'); + end; // Free loopback context (and loopback device) FreeAndNil(FLoopbackContext); @@ -337,7 +346,7 @@ procedure TForm1.BMixToFileClick(Sender: TObject); ProgressBar1.Position := 0; ProgressBar2.Position := ALS_DECIBEL_MIN_VALUE; ProgressBar3.Position := ALS_DECIBEL_MIN_VALUE; - EnableMix(True); + EnableMixGUI(True); end; procedure TForm1.TrackBar1Change(Sender: TObject); @@ -405,7 +414,7 @@ function TForm1.GetSampleType: TALSLoopbackSampleType; end; end; -procedure TForm1.EnableMix(aState: boolean); +procedure TForm1.EnableMixGUI(aState: boolean); begin Panel1.Enabled := aState; Panel2.Enabled := aState; @@ -426,8 +435,10 @@ procedure TForm1.EnableMix(aState: boolean); // procedure TForm1.ProcessLoopbackContextOnProgress(Sender: TALSLoopbackContext; aTimePos: double; const aFrameBuffer: TALSLoopbackFrameBuffer; - var SaveBufferToFile: boolean; var Done: boolean); + var SaveBufferToFile, Cancel: boolean); begin + FMixingTime := aTimePos; + // update the progress bar according to the current mixing time position ProgressBar1.Position := Round(ProgressBar1.Max*aTimePos/FloatSpinEdit2.Value); @@ -443,8 +454,7 @@ procedure TForm1.ProcessLoopbackContextOnProgress(Sender: TALSLoopbackContext; SaveBufferToFile := (aTimePos >= FloatSpinEdit1.Value) and (aTimePos <= FloatSpinEdit2.Value); - // Stops the mixing when the current mixing time position reach the end time. - Done := aTimePos >= FloatSpinEdit2.Value; + Cancel := FCanceled; end; end. diff --git a/example/console_alplay/console_alplay.lpr b/example/console_alplay/console_alplay.lpr index 031ee64..4a45b1b 100644 --- a/example/console_alplay/console_alplay.lpr +++ b/example/console_alplay/console_alplay.lpr @@ -58,7 +58,7 @@ while OurMusic.State = ALS_PLAYING do begin write(#13+'Press a key to stop - Played '+ - FormatFloat('0.0', OurMusic.GetTimePosition)+'/'+ + FormatFloat('0.0', OurMusic.TimePosition)+'/'+ FormatFloat('0.0', OurMusic.TotalDuration)+' s'); sleep(200); if KeyPressed then OurMusic.Stop; diff --git a/example/console_alplay/console_alplay.lps b/example/console_alplay/console_alplay.lps index e1dd813..025553a 100644 --- a/example/console_alplay/console_alplay.lps +++ b/example/console_alplay/console_alplay.lps @@ -4,13 +4,12 @@ - + - - - + + @@ -67,17 +66,26 @@ - + + + + + + + + + + - + @@ -146,6 +154,14 @@ + + + + + + + + diff --git a/example/console_fileformat/console_fileformat.lps b/example/console_fileformat/console_fileformat.lps index 49764af..fb72ef8 100644 --- a/example/console_fileformat/console_fileformat.lps +++ b/example/console_fileformat/console_fileformat.lps @@ -9,7 +9,7 @@ - + diff --git a/example/console_openal-info/console_openal_info.lps b/example/console_openal-info/console_openal_info.lps index e7a4ae5..3171328 100644 --- a/example/console_openal-info/console_openal_info.lps +++ b/example/console_openal-info/console_openal_info.lps @@ -10,7 +10,7 @@ - + diff --git a/example/effect_parameter/EffectParameter.lpi b/example/effect_parameter/EffectParameter.lpi index 3cc02ee..f0f65a6 100644 --- a/example/effect_parameter/EffectParameter.lpi +++ b/example/effect_parameter/EffectParameter.lpi @@ -102,6 +102,7 @@ + diff --git a/example/effect_parameter/EffectParameter.lps b/example/effect_parameter/EffectParameter.lps index ae2f8d7..36474da 100644 --- a/example/effect_parameter/EffectParameter.lps +++ b/example/effect_parameter/EffectParameter.lps @@ -22,7 +22,7 @@ - + diff --git a/example/effects/effects.lps b/example/effects/effects.lps index 3827bea..20d97a1 100644 --- a/example/effects/effects.lps +++ b/example/effects/effects.lps @@ -21,8 +21,8 @@ - - + + @@ -472,123 +472,123 @@ - + - - + + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + diff --git a/example/effects/unit1.pas b/example/effects/unit1.pas index 4e0092f..2bbebe4 100644 --- a/example/effects/unit1.pas +++ b/example/effects/unit1.pas @@ -492,11 +492,11 @@ procedure TForm1.Timer1Timer(Sender: TObject); else begin Label1.Caption := 'time: ' + - FormatFloat('0.00', FSound.GetTimePosition) + ' / ' + + FormatFloat('0.00', FSound.TimePosition) + ' / ' + FormatFloat('0.00', FSound.TotalDuration) + 's'; ProgressBar1.Max := FSound.Seconds2Byte( FSound.TotalDuration ); // FSound.SampleCount div FSound.ChannelCount; - ProgressBar1.Position := FSound.Seconds2Byte( FSound.GetTimePosition ); + ProgressBar1.Position := FSound.Seconds2Byte( FSound.TimePosition ); case FSound.State of ALS_STOPPED: s := 'STOPPED'; diff --git a/example/multi_context/MultiContext.lps b/example/multi_context/MultiContext.lps index 29eb28a..0044993 100644 --- a/example/multi_context/MultiContext.lps +++ b/example/multi_context/MultiContext.lps @@ -20,9 +20,8 @@ - - - + + @@ -106,9 +105,10 @@ + - - + + @@ -126,123 +126,123 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - - + + - + - - + + - + diff --git a/example/simplerecorder/SimpleRecorder.lps b/example/simplerecorder/SimpleRecorder.lps index 41dc428..f519d96 100644 --- a/example/simplerecorder/SimpleRecorder.lps +++ b/example/simplerecorder/SimpleRecorder.lps @@ -20,7 +20,6 @@ - @@ -33,9 +32,10 @@ + - - + + @@ -239,124 +239,124 @@ - - + + - + - - + + - + - + - + - + - + - + - + - + - + - - + + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + diff --git a/example/simplerecorder/unit2.lfm b/example/simplerecorder/unit2.lfm index 394947b..9a94ff2 100644 --- a/example/simplerecorder/unit2.lfm +++ b/example/simplerecorder/unit2.lfm @@ -11,7 +11,7 @@ object Form2: TForm2 Color = 11590399 Font.Height = 20 Position = poMainFormCenter - LCLVersion = '2.2.3.0' + LCLVersion = '2.2.4.0' object Shape1: TShape Left = 0 Height = 187 diff --git a/example/simplerecorder/unit2.pas b/example/simplerecorder/unit2.pas index ac7b91a..7e70685 100644 --- a/example/simplerecorder/unit2.pas +++ b/example/simplerecorder/unit2.pas @@ -33,7 +33,6 @@ TForm2 = class(TForm) Form2: TForm2; implementation -uses unit1; {$R *.lfm} @@ -46,7 +45,7 @@ procedure TForm2.Timer1Timer(Sender: TObject); // update progress bar position if FMusic.TotalDuration <> 0 then with FMusic do - ProgressBar1.Position := Round( GetTimePosition / TotalDuration * ProgressBar1.Max ); + ProgressBar1.Position := Round( TimePosition / TotalDuration * ProgressBar1.Max ); Timer1.Enabled := True; end; diff --git a/source/alext.inc b/source/alext.inc index 6320a17..d3fbe52 100644 --- a/source/alext.inc +++ b/source/alext.inc @@ -114,11 +114,14 @@ const ALC_CONNECTED=$313; +type + TProc_alcSetThreadContext = function(context: PALCcontext): ALCboolean; cdecl; + TProc_alcGetThreadContext = function(): PALCcontext; cdecl; var //ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context); //ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); - alcSetThreadContext: function(context: PALCcontext): ALCboolean; cdecl; - alcGetThreadContext: function(): PALCcontext; cdecl; + _alcSetThreadContext: TProc_alcSetThreadContext; //function(context: PALCcontext): ALCboolean; cdecl; + _alcGetThreadContext: TProc_alcGetThreadContext; //function(): PALCcontext; cdecl; const AL_SOURCE_DISTANCE_MODEL=$200; @@ -238,10 +241,14 @@ const ALC_6POINT1_SOFT=$1505; ALC_7POINT1_SOFT=$1506; +type + TProc_alcLoopbackOpenDeviceSOFT = function(const deviceName: PALCchar): PALCdevice; cdecl; + TProc_alcIsRenderFormatSupportedSOFT = function(device: PALCdevice; freq: ALCsizei; channels, _type: ALCenum): ALCboolean; cdecl; + TProc_alcRenderSamplesSOFT = procedure(device: PALCdevice; buffer: PALCvoid; samples: ALCsizei); cdecl; var - alcLoopbackOpenDeviceSOFT: function(const deviceName: PALCchar): PALCdevice; cdecl; - alcIsRenderFormatSupportedSOFT: function(device: PALCdevice; freq: ALCsizei; channels, _type: ALCenum): ALCboolean; cdecl; - alcRenderSamplesSOFT: procedure(device: PALCdevice; buffer: PALCvoid; samples: ALCsizei); cdecl; + _alcLoopbackOpenDeviceSOFT: TProc_alcLoopbackOpenDeviceSOFT; + _alcIsRenderFormatSupportedSOFT: TProc_alcIsRenderFormatSupportedSOFT; + _alcRenderSamplesSOFT: TProc_alcRenderSamplesSOFT; const AL_STEREO_ANGLES=$1030; @@ -293,9 +300,12 @@ const //#define AL_SAMPLE_LENGTH_SOFT=$200A; //#define AL_SEC_LENGTH_SOFT=$200B; +type + TProc_alcDevicePauseSOFT = procedure(device: PALCdevice); cdecl; + TProc_alcDeviceResumeSOFT = procedure(device: PALCdevice); cdecl; var - alcDevicePauseSOFT: procedure(device: PALCdevice); cdecl; - alcDeviceResumeSOFT: procedure(device: PALCdevice); cdecl; + _alcDevicePauseSOFT: TProc_alcDevicePauseSOFT; + _alcDeviceResumeSOFT: TProc_alcDeviceResumeSOFT; const @@ -334,9 +344,12 @@ const ALC_HRTF_SPECIFIER_SOFT=$1995; ALC_HRTF_ID_SOFT=$1996; +type + TProc_alcGetStringiSOFT = function(device: PALCdevice; paramName: ALCenum; index: ALCsizei): PALCchar; cdecl; + TProc_alcResetDeviceSOFT = function(device: PALCdevice; const attribs: PALCint): ALCboolean; cdecl; var - alcGetStringiSOFT: function(device: PALCdevice; paramName: ALCenum; index: ALCsizei): PALCchar; cdecl; - alcResetDeviceSOFT: function(device: PALCdevice; const attribs: PALCint): ALCboolean; cdecl; + _alcGetStringiSOFT: TProc_alcGetStringiSOFT; + _alcResetDeviceSOFT: TProc_alcResetDeviceSOFT; const AL_GAIN_LIMIT_SOFT=$200E; @@ -361,6 +374,7 @@ type ALCint64SOFT=_alsoft_int64_t; PALCint64SOFT = ^ALCint64SOFT; ALCuint64SOFT=_alsoft_uint64_t; + TProc_alcGetInteger64vSOFT = procedure(device: PALCdevice; pname: ALCenum; size: ALsizei; values: PALCint64SOFT); cdecl; const ALC_DEVICE_CLOCK_SOFT=$1600; ALC_DEVICE_LATENCY_SOFT=$1601; @@ -369,7 +383,7 @@ const AL_SEC_OFFSET_CLOCK_SOFT=$1203; var - alcGetInteger64vSOFT: procedure(device: PALCdevice; pname: ALCenum; size: ALsizei; values: PALCint64SOFT); cdecl; + _alcGetInteger64vSOFT: TProc_alcGetInteger64vSOFT; const @@ -429,7 +443,7 @@ var type TProc_alcReopenDeviceSOFT = function(device: PALCdevice; const deviceName: PALCchar; const attribs: PALCint): ALCboolean; var - alcReopenDeviceSOFT: TProc_alcReopenDeviceSOFT; + _alcReopenDeviceSOFT: TProc_alcReopenDeviceSOFT; // AL_SOFT_callback_buffer diff --git a/source/als_deviceitem.inc b/source/als_deviceitem.inc new file mode 100644 index 0000000..652c88b --- /dev/null +++ b/source/als_deviceitem.inc @@ -0,0 +1,231 @@ +{$ifdef ALS_INTERFACE} +type + { TALSDeviceItem } + + TALSDeviceItem = object + private + alcGetInteger64vSOFT: TProc_alcGetInteger64vSOFT; + + alcSetThreadContext: TProc_alcSetThreadContext; + alcGetThreadContext: TProc_alcGetThreadContext; + + alcDevicePauseSOFT: TProc_alcDevicePauseSOFT; + alcDeviceResumeSOFT: TProc_alcDeviceResumeSOFT; + + alcGetStringiSOFT: TProc_alcGetStringiSOFT; + alcResetDeviceSOFT: TProc_alcResetDeviceSOFT; + + alcReopenDeviceSOFT: TProc_alcReopenDeviceSOFT; + procedure LoadExtension; + private + alcLoopbackOpenDeviceSOFT: TProc_alcLoopbackOpenDeviceSOFT; + alcIsRenderFormatSupportedSOFT: TProc_alcIsRenderFormatSupportedSOFT; + alcRenderSamplesSOFT: TProc_alcRenderSamplesSOFT; + procedure LoadLoopbackExtension; + procedure FirstInitdefault; + public + Name: string; + Handle: PALCDevice; + FHaveEXT_ALC_EXT_EFX, + FHaveExt_ALC_SOFT_output_mode, + FHaveExt_ALC_SOFT_HRTF, + FHaveExt_ALC_SOFT_loopback, + FHaveExt_ALC_SOFT_output_limiter, + FHaveExt_ALC_EXT_thread_local_context, + FHaveExt_ALC_SOFT_reopen_device, + FHaveExt_ALC_SOFT_device_clock, + FHaveExt_ALC_SOFT_pause_device: boolean; + end; + PALSDeviceItem = ^TALSDeviceItem; + + { TALSPlaybackDeviceItem } + + TALSPlaybackDeviceItem = object(TALSDeviceItem) + public + OpenedCount: integer; + procedure InitDefault; + procedure Open; + procedure Close; + procedure DoCloseDevice; + end; + PALSPlaybackDeviceItem = ^TALSPlaybackDeviceItem; + ArrayOfALSPlaybackDeviceItem = array of TALSPlaybackDeviceItem; + + { TALSLoopbackDeviceItem } + + TALSLoopbackDeviceItem = object(TALSDeviceItem) + OpenedCount: integer; + procedure InitDefault; + procedure Open; + procedure Close; + procedure DoCloseDevice; + end; + PALSLoopbackDeviceItem = ^TALSLoopbackDeviceItem; + +{$endif} + +{$ifdef ALS_IMPLEMENTATION} + +{ TALSLoopbackDeviceItem } + +procedure TALSLoopbackDeviceItem.InitDefault; +begin + FirstInitdefault; + OpenedCount := 0; +end; + +procedure TALSLoopbackDeviceItem.Open; +begin + if not ALSManager.Error then + begin + if Handle = NIL then + begin + LoadLoopbackExtension; + + if alcLoopbackOpenDeviceSOFT <> NIL then begin + if Name = '' then + Handle := alcLoopbackOpenDeviceSOFT(nil) + else + Handle := alcLoopbackOpenDeviceSOFT(PChar(Name)); + end; + + LoadExtension; + end; + + if Handle <> NIL then + inc( OpenedCount ); + end; +end; + +procedure TALSLoopbackDeviceItem.Close; +begin + if not ALSManager.Error then + begin + {$ifndef ALS_ENABLE_CONTEXT_SWITCHING} + _SingleContextIsCurrent := False; + {$endif} + if OpenedCount = 0 then + exit; + dec( OpenedCount ); + if OpenedCount = 0 then + DoCloseDevice; + end; +end; + +procedure TALSLoopbackDeviceItem.DoCloseDevice; +begin + alcCloseDevice(Handle); + Handle := NIL; +end; + +{ TALSPlaybackDeviceItem } + +procedure TALSPlaybackDeviceItem.InitDefault; +begin + FirstInitdefault; + OpenedCount := 0; +end; + +procedure TALSPlaybackDeviceItem.Open; +begin + if not ALSManager.Error then + begin + if Handle = NIL then begin + Handle := alcOpenDevice(PChar(Name)); + LoadExtension; + LoadLoopbackExtension; + end; + inc( OpenedCount ); + end; +end; + +procedure TALSPlaybackDeviceItem.Close; +begin + if not ALSManager.Error then + begin + {$ifndef ALS_ENABLE_CONTEXT_SWITCHING} + _SingleContextIsCurrent := False; + {$endif} + if OpenedCount = 0 then + exit; + dec( OpenedCount ); + if OpenedCount = 0 then + DoCloseDevice; + end; +end; + +procedure TALSPlaybackDeviceItem.DoCloseDevice; +begin + alcCloseDevice( Handle ); + Handle := NIL; +end; + +{ TALSDeviceItem } + +procedure TALSDeviceItem.LoadExtension; +begin + if Handle = NIL then begin + alcSetThreadContext := NIL; + alcGetThreadContext := NIL; + alcDevicePauseSOFT := NIL; + alcDeviceResumeSOFT := NIL; + alcGetStringiSOFT := NIL; + alcResetDeviceSOFT := NIL; + alcGetInteger64vSOFT := NIL; + alcReopenDeviceSOFT := NIL; + end else begin + FHaveEXT_ALC_EXT_EFX := alcIsExtensionPresent(Handle, PChar('ALC_EXT_EFX')); + if FHaveEXT_ALC_EXT_EFX then + FHaveEXT_ALC_EXT_EFX := LoadExt_ALC_EXT_EFX; + + FHaveExt_ALC_SOFT_output_mode := alcIsExtensionPresent(Handle, PChar('ALC_SOFT_output_mode')); + + FHaveExt_ALC_EXT_thread_local_context := LoadExt_ALC_EXT_thread_local_context(Handle); + alcSetThreadContext := openalsoft._alcSetThreadContext; + alcGetThreadContext := openalsoft._alcGetThreadContext; + + LoadLoopbackExtension; + + FHaveExt_ALC_SOFT_output_limiter := alcIsExtensionPresent(Handle, PChar('ALC_SOFT_output_limiter')); + + FHaveExt_ALC_SOFT_pause_device := LoadExt_ALC_SOFT_pause_device(Handle); + alcDevicePauseSOFT := openalsoft._alcDevicePauseSOFT; + alcDeviceResumeSOFT := openalsoft._alcDeviceResumeSOFT; + + FHaveExt_ALC_SOFT_HRTF := LoadExt_ALC_SOFT_HRTF(Handle); + alcGetStringiSOFT := openalsoft._alcGetStringiSOFT; + alcResetDeviceSOFT := openalsoft._alcResetDeviceSOFT; + + FHaveExt_ALC_SOFT_device_clock := LoadExt_ALC_SOFT_device_clock(Handle); + alcGetInteger64vSOFT := openalsoft._alcGetInteger64vSOFT; + + FHaveExt_ALC_SOFT_reopen_device := LoadExt_ALC_SOFT_reopen_device(Handle); + alcReopenDeviceSOFT := openalsoft._alcReopenDeviceSOFT; + end; +end; + +procedure TALSDeviceItem.LoadLoopbackExtension; +begin + FHaveExt_ALC_SOFT_loopback := LoadExt_ALC_SOFT_loopback(NIL); + alcLoopbackOpenDeviceSOFT := openalsoft._alcLoopbackOpenDeviceSOFT; + alcIsRenderFormatSupportedSOFT := openalsoft._alcIsRenderFormatSupportedSOFT; + alcRenderSamplesSOFT := openalsoft._alcRenderSamplesSOFT; +end; + +procedure TALSDeviceItem.FirstInitdefault; +begin + Name := ''; + Handle := NIL; + FHaveEXT_ALC_EXT_EFX := False; + FHaveExt_ALC_SOFT_output_mode := False; + FHaveExt_ALC_SOFT_HRTF := False; + FHaveExt_ALC_SOFT_loopback := False; + FHaveExt_ALC_SOFT_output_limiter := False; + FHaveExt_ALC_EXT_thread_local_context := False; + FHaveExt_ALC_SOFT_reopen_device := False; + FHaveExt_ALC_SOFT_device_clock := False; + FHaveExt_ALC_SOFT_pause_device := False; +end; + +{$endif} + diff --git a/source/als_dsp_utils.pas b/source/als_dsp_utils.pas index d3ad036..a560647 100644 --- a/source/als_dsp_utils.pas +++ b/source/als_dsp_utils.pas @@ -38,8 +38,10 @@ interface ArrayOfSmallint = array of SmallInt; ArrayOfSingle = array of single; - // convert a percent value (range is [0..1]) to decibel - function ALSPercentToDecibel(aValue: single): single; + // use LinearTodB instead + function ALSPercentToDecibel(aValue: single): single; deprecated; + function LinearTodB(aLinearValue: single): single; + function dBToLinear(adBValue: single): single; // convert an array of values in range [0..1] to dB. procedure als_dsp_ValuesToDecibel(p: PSingle; aCount: integer); @@ -69,17 +71,29 @@ interface procedure dsp_Amplify_Smallint(p: PSmallint; aFrameCount: longword; aChannelCount: Smallint; aGain: single); procedure dsp_Amplify_Float(p: PSingle; aFrameCount: longword; aChannelCount: Smallint; aGain: single); + procedure dsp_AmplifySample_Smallint(p: PSmallint; aFrameIndex: longword; aChannelCount: Smallint; aGain: single); inline; + procedure dsp_AmplifySample_Float(p: PSingle; aFrameIndex: longword; aChannelCount: Smallint; aGain: single); inline; implementation uses Math; function ALSPercentToDecibel(aValue: single): single; begin - if aValue > 0.0 then - Result := Max(20*Log10(aValue), ALS_DECIBEL_MIN_VALUE) + Result := LinearTodB(aValue); +end; + +function LinearTodB(aLinearValue: single): single; +begin + if aLinearValue > 0.0 then + Result := Max(20*Log10(aLinearValue), ALS_DECIBEL_MIN_VALUE) else Result := ALS_DECIBEL_MIN_VALUE; end; +function dBToLinear(adBValue: single): single; +begin + Result := Power(10, adBValue*0.05); // *0.05 same than /20 +end; + procedure als_dsp_ValuesToDecibel(p: PSingle; aCount: integer); var i: integer; @@ -119,20 +133,23 @@ function dsp_Mean_Smallint(p: PSmallInt; aFrameCount: longword; aChannelCount: S function dsp_Mean_Float(p: PSingle; aFrameCount: longword; aChannelCount: Smallint): ArrayOfSingle; var - i: longword; + i, fc: longword; begin Result := NIL; SetLength( Result, aChannelCount ); - FillChar( Result, SizeOf(single)*aChannelCount, $00); - while aFrameCount>0 do + FillChar( Result[0], SizeOf(single)*aChannelCount, $00); + + fc := aFrameCount; + while fc>0 do begin for i:=0 to aChannelCount-1 do begin Result[i] := Result[i] + p^; inc( p ); end; - dec( aFrameCount ); + dec( fc ); end; + for i:=0 to aChannelCount-1 do Result[i] := Result[i] / aFrameCount; end; @@ -364,6 +381,30 @@ procedure dsp_Amplify_Float(p: PSingle; aFrameCount: longword; end; end; +procedure dsp_AmplifySample_Smallint(p: PSmallint; aFrameIndex: longword; + aChannelCount: Smallint; aGain: single); +begin + inc(p, aFrameIndex*aChannelCount); + while aChannelCount > 0 do + begin + p^ := Smallint(EnsureRange(Round(p^*aGain), -32768, 32767)); + inc(p); + dec(aChannelCount); + end; +end; + +procedure dsp_AmplifySample_Float(p: PSingle; aFrameIndex: longword; + aChannelCount: Smallint; aGain: single); +begin + inc(p, aFrameIndex*aChannelCount); + while aChannelCount > 0 do + begin + p^ := p^ * aGain; + inc(p); + dec(aChannelCount); + end; +end; + end. diff --git a/source/als_error.inc b/source/als_error.inc index cc0e31f..1e38615 100644 --- a/source/als_error.inc +++ b/source/als_error.inc @@ -126,6 +126,5 @@ begin end; end; - {$endif} diff --git a/source/als_frame_buffers.inc b/source/als_frame_buffers.inc index a715425..9e11243 100644 --- a/source/als_frame_buffers.inc +++ b/source/als_frame_buffers.inc @@ -28,13 +28,14 @@ type function GetChannelsPeak(aIndex: integer): single; function GetChannelsPeakdB(aIndex: integer): single; private - procedure FreeMemory; function GetDataOffset(frameOffset: longword): Pointer; procedure ResetChannelsLevelToZero; public + procedure FreeMemory; procedure ComputeChannelsLevel; + procedure FillWithSilence; procedure Amplify(aGain: single); property OutOfMemory: boolean read FOutOfMemory; @@ -321,6 +322,12 @@ begin FChannelsPeak[i] := FChannelsLevel[i]; end; +procedure TALSFrameBufferBase.FillWithSilence; +begin + if FUseFloat then + FillChar(FData^, FFrameCapacity*FBytePerFrame, $00); +end; + procedure TALSFrameBufferBase.Amplify(aGain: single); begin if aGain = 1.0 then @@ -343,7 +350,7 @@ end; function TALSFrameBufferBase.GetChannelsLeveldB(aIndex: integer): single; begin if (aIndex >= 0) and (aIndex < Length(FChannelsLevel)) then - Result := ALSPercentToDecibel(FChannelsLevel[aIndex]) + Result := LinearTodB(FChannelsLevel[aIndex]) else Result := ALS_DECIBEL_MIN_VALUE; end; @@ -359,7 +366,7 @@ end; function TALSFrameBufferBase.GetChannelsPeakdB(aIndex: integer): single; begin if (aIndex >= 0) and (aIndex < Length(FChannelsPeak)) then - Result := ALSPercentToDecibel(FChannelsPeak[aIndex]) + Result := LinearTodB(FChannelsPeak[aIndex]) else Result := ALS_DECIBEL_MIN_VALUE; end; diff --git a/source/als_velocity_curve.inc b/source/als_velocity_curve.inc index fd44108..2985506 100644 --- a/source/als_velocity_curve.inc +++ b/source/als_velocity_curve.inc @@ -73,7 +73,6 @@ type FValue, FConstPerSecond: single; FState: TParamState; FCurve: TVelocityCurve; - procedure OnElapse(const AElapsedSec: single); virtual; private FOnLockParam: TALSLockParamProc; FOnUnlockParam: TALSUnlockParamProc; @@ -86,6 +85,7 @@ type constructor Create; destructor Destroy; override; + procedure OnElapse(const AElapsedSec: single); virtual; // use velocity curve procedure ChangeTo(aNewValue, aSecond: single; aCurveID: TALSCurveID = ALS_Linear); virtual; // add a constant per second @@ -105,7 +105,6 @@ type FMaxValue: single; FLoop: boolean; procedure ApplyBounds(var AValue: single); - procedure OnElapse(const AElapsedSec: single); override; protected function GetValue: single; override; procedure SetValue(AValue: single); override; @@ -113,6 +112,7 @@ type // if Loop is set to TRUE, value can loop between bounds (usefull for i.e. [0..360] angle) // if it's set to FALSE, value is clamped to FMinValue and FMaxValue. constructor Create(aMin, aMax, aStartValue: single; aLooped: boolean=False); + procedure OnElapse(const AElapsedSec: single); override; end; diff --git a/source/alsound.pas b/source/alsound.pas index 1e8ea11..ab65b23 100644 --- a/source/alsound.pas +++ b/source/alsound.pas @@ -35,7 +35,7 @@ interface als_dsp_utils; const - ALS_VERSION = '1.3.0'; + ALS_VERSION = '2.0.0'; type @@ -305,6 +305,7 @@ TALSEffect = record {$include als_directfilter.inc} {$include als_frame_buffers.inc} {$include als_velocity_curve.inc} + {$include als_deviceitem.inc} type { TALSErrorHandling } @@ -330,8 +331,10 @@ TALSErrorHandling = class end; + TALSNotifyEvent = procedure(Sender: TALSSound) of object; TALSOnCustomDSP = procedure(Sender: TALSSound; - const aBuffer: TALSPlaybackBuffer) of object; + const aBuffer: TALSPlaybackBuffer; + aUserData: Pointer) of object; { TALSSound } @@ -402,7 +405,11 @@ TALSSound = class( TALSErrorHandling ) procedure FreeParameters; private FOnCustomDSP: TALSOnCustomDSP; - procedure SetOnCustomDSP(AValue: TALSOnCustomDSP); + FOnCustomDSPUserData: pointer; + private + FPreviousState: TALSState; + FOnStopped: TALSNotifyEvent; + procedure SetOnStopped(AValue: TALSNotifyEvent); private function GetTimePosition: single; virtual; procedure SetTimePosition(AValue: single); virtual; @@ -477,6 +484,15 @@ TALSSound = class( TALSErrorHandling ) // Sets the position of the sound in the 3D world procedure Position3D( aX, aY, aZ: single ); + public + // Use this method to define a callback to apply your custom DSP effects. + // This callback is called when a buffer is filled with new raw audio data + // and before send it to OpenAL-Soft pipeline. + // Only once for a static sound, when data are loaded or generated in memory. + // Each time a new buffer is read from a streamed sound. + // Your callback must be fast and mustn't update any GUI items. + procedure SetOnCustomDSP(aProc: TALSOnCustomDSP; aUserData: Pointer); + public // The volume of the sound. Range is [0.0 to 8.0] // 0.0=silence 1.0=original volume >1.0=amplification @@ -534,13 +550,7 @@ TALSSound = class( TALSErrorHandling ) // For more info see https://openal-soft.org/openal-extensions/SOFT_source_resampler.txt property ResamplerIndex: integer read GetResamplerIndex write SetResamplerIndex; - // Use this callback to apply your custom DSP effects on the given buffer. - // This callback is called when a buffer is filled with new raw audio data - // and before send it to OpenAL-Soft. - // Only once for a static sound, when data are loaded or generated in memory. - // Each time a new buffer is read from a streamed sound. - // Your callback must be fast and mustn't update any GUI items. - property OnNewBuffer: TALSOnCustomDSP read FOnCustomDSP write SetOnCustomDSP; + property OnStopped: TALSNotifyEvent read FOnStopped write SetOnStopped; // State of the sound. Possible value are ALS_STOPPED, ALS_PLAYING, ALS_PAUSED property State: TALSState read GetState; @@ -565,12 +575,14 @@ TALSSingleStaticBufferSound = class(TALSSound) constructor CreateFromFile(aParent: TALSPlaybackContext; const aFilename: string; aEnableMonitor: boolean; - aOnCustomDSP: TALSOnCustomDSP); + aOnCustomDSP: TALSOnCustomDSP; + aCustomDSPUserData: Pointer); constructor CreateWhiteNoise(aParent: TALSPlaybackContext; aDuration: single; aChannelCount: integer; aEnableMonitor: boolean; - aOnCustomDSP: TALSOnCustomDSP); + aOnCustomDSP: TALSOnCustomDSP; + aCustomDSPUserData: Pointer); destructor Destroy; override; end; @@ -607,11 +619,13 @@ TALSStreamBufferSound = class(TALSSound) constructor CreateFromFile(aParent: TALSPlaybackContext; const aFilename: string; aEnableMonitor: boolean; - aOnCustomDSP: TALSOnCustomDSP); + aOnCustomDSP: TALSOnCustomDSP; + aCustomDSPUserData: Pointer); {constructor CreateFromUrl(aParent: TALSPlaybackContext; const aUrl: string; aEnableMonitor: boolean; - aOnCustomDSP: TALSOnCustomDSP); } + aOnCustomDSP: TALSOnCustomDSP; + aCustomDSPUserData: Pointer); } destructor Destroy; override; end; @@ -760,14 +774,18 @@ TALSPlaybackContext = class(TALSErrorHandling) FCriticalSection: TRTLCriticalSection; FThread: TALSThread; FThreadIsStarted: boolean; + FSoundToProcess: TALSSound; procedure DoUpdate(const aElapsedTime: single); + procedure DoSoundOnStopped; procedure EnterCS; procedure LeaveCS; protected FExecutingConstructor: boolean; private + FHaveExt_ALC_SOFT_HRTF: boolean; FList: TFPList; FPlaylist: TALSPlaylist; + FParentDeviceItem: PALSDeviceItem; FParentDevice: PALCdevice; FContext: PALCcontext; FDistanceModel: TALSDistanceModel; @@ -777,19 +795,14 @@ TALSPlaybackContext = class(TALSErrorHandling) FAuxiliarySendAvailable: ALCInt; FDefaultResamplerIndex: integer; - FHaveEXT_ALC_EXT_EFX, FHaveLowPassFilter, FHaveBandPassFilter, FHaveHighPassFilter, FHaveExt_AL_SOFT_effect_target, FHaveExt_AL_EXT_STEREO_ANGLES, FHaveExt_AL_EXT_BFORMAT, - FHaveExt_ALC_SOFT_HRTF, - FHaveExt_ALC_SOFT_output_mode, FHaveExt_AL_SOFT_deferred_updates, FHaveExt_AL_SOFT_source_resampler, - FHaveExt_ALC_SOFT_loopback, - FHaveExt_ALC_SOFT_output_limiter, FHaveExt_AL_SOFT_source_spatialize, FHaveExt_AL_SOFT_gain_clamp_ex, FHaveExt_AL_EXT_source_distance_model, @@ -800,6 +813,8 @@ TALSPlaybackContext = class(TALSErrorHandling) FInternalSampleType: TALSPlaybackSampleType; + function GetHaveEXT_ALC_EXT_EFX: boolean; + function GetHaveExt_ALC_SOFT_HRTF: boolean; function GetHaveFilter: boolean; function GetHRTFEnabled: boolean; function GetHRTFList: TStringArray; @@ -821,7 +836,7 @@ TALSPlaybackContext = class(TALSErrorHandling) // Don't create playback context directly. // Use ALSManager.CreateDefaultPlaybackContext // or ALSManager.CreatePlaybackContext(...) method for this - constructor Create(aDevice: PALCDevice; const aAttribs: TALSContextAttributes); + constructor Create(aDevice: PALSPlaybackDeviceItem; const aAttribs: TALSContextAttributes); destructor Destroy; override; // Loads a sound file into memory and return its instance. @@ -829,13 +844,15 @@ TALSPlaybackContext = class(TALSErrorHandling) // sound (it take some ram and cpu resources). function AddSound(const aFilename: string; aEnableMonitoring: boolean=False; - aOnCustomDSP: TALSOnCustomDSP=NIL): TALSSound; + aOnCustomDSP: TALSOnCustomDSP=NIL; + aCustomDSPUserData: Pointer=NIL): TALSSound; // Opens the sound file as stream and return its instance. // Set aEnableMonitoring to True if you need the channel's level of the // sound (it take some ram and cpu resources). function AddStream(const aFilename: string; aEnableMonitoring: boolean=False; - aOnCustomDSP: TALSOnCustomDSP=NIL): TALSSound; + aOnCustomDSP: TALSOnCustomDSP=NIL; + aCustomDSPUserData: Pointer=NIL): TALSSound; { TODO : AddWebStream( const aUrl: string ): TOALSound; to play audio from url } @@ -845,7 +862,8 @@ TALSPlaybackContext = class(TALSErrorHandling) function CreateWhiteNoise(aDuration: single; aChannelCount: integer; aEnableMonitoring: boolean=False; - aOnCustomDSP: TALSOnCustomDSP=NIL): TALSSound; + aOnCustomDSP: TALSOnCustomDSP=NIL; + aCustomDSPUserData: Pointer=NIL): TALSSound; // stops the sound and free it procedure Delete(ASound: TALSSound); @@ -907,7 +925,7 @@ TALSPlaybackContext = class(TALSErrorHandling) property ObtainedAuxiliarySendCount: integer read GetObtainedAuxiliarySendCount; property HaveStereoAngle: boolean read FHaveExt_AL_EXT_STEREO_ANGLES; - property HaveEFX: boolean read FHaveEXT_ALC_EXT_EFX; + property HaveEFX: boolean read GetHaveEXT_ALC_EXT_EFX; property HaveFilter: boolean read GetHaveFilter; // The list of available resampler. @@ -917,7 +935,7 @@ TALSPlaybackContext = class(TALSErrorHandling) property HaveExt_AL_SOFT_source_resampler: boolean read FHaveExt_AL_SOFT_source_resampler; // True if the context have the HRTF capability. - property HaveHRTF: boolean read FHaveExt_ALC_SOFT_HRTF; + property HaveHRTF: boolean read GetHaveExt_ALC_SOFT_HRTF; // Gives the list of available HRTF. property HRTFList: TStringArray read GetHRTFList; // Return the success (True) or failure (False) of a HRTF change after a @@ -936,7 +954,7 @@ TALSLoopbackContext = class; aTimePos: double; const aFrameBuffer: TALSLoopbackFrameBuffer; var SaveBufferToFile: boolean; - var Done: boolean) of object; + var Cancel: boolean) of object; { TALSLoopbackContext } @@ -949,10 +967,10 @@ TALSLoopbackContext = class(TALSPlaybackContext) function GetMixingStrError: string; private FOnProgress: TALSProgressEvent; - FTimeSlice: double; + FTimeSlice, + FMixTime: double; FFrameBuffer: TALSLoopbackFrameBuffer; - FIsMixing, - FCancelMixing: boolean; + FIsMixing: boolean; procedure SetTimeSlice(AValue: double); procedure Update(const aElapsedTime: double); procedure InternalCloseDevice; override; @@ -962,10 +980,13 @@ TALSLoopbackContext = class(TALSPlaybackContext) FFile: PSNDFILE; private procedure RenderAudioToBuffer; + procedure SaveBufferToFile; + procedure CloseFile; + procedure DoExceptionNoCallback; public // Don't call directly this constructor. Instead, use method // ALSManager.CreateDefaultLoopbackContext. - constructor Create(aDevice: PALCDevice); + constructor Create(aDevice: PALSLoopbackDeviceItem); // Checks if the specified attributes are supported by a loopback context. function IsAttributesSupported( aSampleRate: integer; @@ -982,20 +1003,23 @@ TALSLoopbackContext = class(TALSPlaybackContext) // ALSManager.ListOfSimpleAudioFileFormat[].Format function PrepareSavingToFile(const aFilename: string; aFileFormat: TALSFileFormat): boolean; - // Start the mixing and keeps the hand until the mixing is done or canceled. - // Callback OnProgress is fired each time the buffer is filled with audio, - // (by default every 10ms) and, for LCL based application, - // Application.ProcessMessage is called regularly. - procedure StartMixing; + // Call this method before start your mixing process, before any calls to + // Mix(...) + procedure BeginOfMix; - // Cancel the current mixing process. If the mix is saved to an output file, - // the file is deleted. - // This method should be used e.g when user click cancel button to - // interrupt a mixing process. - procedure CancelMix; + // Call this method to render audio. "OnProgress" event is triggered every + // time a buffer is filled. + // The buffer length can be adjusted with property "TimeSlice" + // Don't forget to put some sounds in playing state otherwise the result + // will be nothing but silence ! + procedure Mix(aDuration: single); - // This callback is fired after a call to StartMixing, each time the buffer - // is filled with audio. It allows your application to: + // Call this method at the end of your mixing process. It close the output + // file (if any), free buffer memory, etc... + procedure EndOfMix; + + // This callback is triggered each time the buffer is filled with audio. + // It allows your application to: // - control if the current audio buffer content must be saved to the // output file. Usefull to save only a portion of the mix. // - control if the mixing must be stopped. @@ -1129,32 +1153,6 @@ TALSCaptureContext = class(TALSErrorHandling) property StrCaptureError: string read GetStrCaptureError; end; - { TALSPlaybackDeviceItem } - - TALSPlaybackDeviceItem = record - Name: string; - Handle: PALCDevice; - OpenedCount: integer; - procedure InitDefault; - procedure Open; - procedure Close; - procedure DoCloseDevice; - end; - ArrayOfALSPlaybackDeviceItem = array of TALSPlaybackDeviceItem; - - { TALSLoopbackDeviceItem } - - TALSLoopbackDeviceItem = record - Name: string; - Handle: PALCDevice; - OpenedCount: integer; - procedure InitDefault; - procedure Open; - procedure Close; - procedure DoCloseDevice; - end; - - TALSAudioFileSubFormat = record Name: string; Format: longint; //cint; @@ -1246,7 +1244,7 @@ TALSManager = class( TALSErrorHandling ) function CreateDefaultPlaybackContext: TALSPlaybackContext; // Opens the specified playback device and creates a context on it with - // custom context attributes. + // custom attributes. // aNameIndex is an index in the list provided by ListOfPlaybackDeviceName. // You can set aNameIndex to -1 to refer to the default playback device. function CreatePlaybackContext(aNameIndex: integer; @@ -1354,7 +1352,6 @@ procedure UnlockContext; {$endif} end; - {$undef ALS_INTERFACE} {$define ALS_IMPLEMENTATION} {$include als_error.inc} @@ -1363,6 +1360,7 @@ procedure UnlockContext; {$include als_directfilter.inc} {$include als_frame_buffers.inc} {$include als_velocity_curve.inc} +{$include als_deviceitem.inc} {$undef ALS_IMPLEMENTATION} function ALSMakeFileFormat(aFileMajorFormat: TALSFileMajorFormat; @@ -1428,13 +1426,48 @@ procedure TALSLoopbackContext.RenderAudioToBuffer; begin LockContext(FContext); try - alcRenderSamplesSOFT(FParentDevice, FFrameBuffer.Data, ALCsizei(FFrameBuffer.FrameCapacity)); + FParentDeviceItem^.alcRenderSamplesSOFT(FParentDevice, + FFrameBuffer.Data, ALCsizei(FFrameBuffer.FrameCapacity)); FFrameBuffer.FrameCount := FFrameBuffer.FrameCapacity; finally UnlockContext; end; end; +procedure TALSLoopbackContext.SaveBufferToFile; +var written: sf_count_t; +begin + if (FFile <> NIL) then + begin + case FFrameBuffer.SampleType of + ALC_SHORT_SOFT: written := sf_writef_short(FFile, FFrameBuffer.Data, FFrameBuffer.FrameCount); + ALC_INT_SOFT: written := sf_writef_int(FFile, FFrameBuffer.Data, FFrameBuffer.FrameCount); + ALC_FLOAT_SOFT: written := sf_writef_float(FFile, FFrameBuffer.Data, FFrameBuffer.FrameCount); + end; + + // check write file error + if written <> sf_count_t(FFrameBuffer.FrameCount) then + SetMixingError(als_FileWriteErrorWhileMixing); + end; +end; + +procedure TALSLoopbackContext.CloseFile; +begin + if FFile <> NIL then + begin + // close file + sf_write_sync(FFile); + sf_close(FFile); + + FFile := NIL; + end; +end; + +procedure TALSLoopbackContext.DoExceptionNoCallback; +begin + Raise Exception.Create('TALSLoopbackContext.StartMixing - Callback OnProgress must be defined'); +end; + procedure TALSLoopbackContext.SetTimeSlice(AValue: double); begin if FIsMixing then exit; @@ -1454,16 +1487,17 @@ function TALSLoopbackContext.GetMixingStrError: string; Result := ALSound.GetStrError(FLoopbackError); end; -constructor TALSLoopbackContext.Create(aDevice: PALCDevice); +constructor TALSLoopbackContext.Create(aDevice: PALSLoopbackDeviceItem); begin FExecutingConstructor := True; InitializeErrorStatus; ResetMixingError; - FParentDevice := aDevice; + FParentDevice := aDevice^.Handle; + FParentDeviceItem := PALSDeviceItem(aDevice); - if aDevice = NIL then + if aDevice^.Handle = NIL then SetError(als_ALCanNotOpenLoopbackDevice) - else if not alcIsExtensionPresent(aDevice, PChar('ALC_SOFT_loopback')) then + else if not aDevice^.FHaveExt_ALC_SOFT_loopback then SetError(als_ALContextCanNotLoopback); InitCriticalSection( FCriticalSection ); @@ -1481,7 +1515,7 @@ function TALSLoopbackContext.IsAttributesSupported(aSampleRate: integer; Result := False else begin - Result := alcIsRenderFormatSupportedSOFT(FParentDevice, + Result := FParentDeviceItem^.alcIsRenderFormatSupportedSOFT(FParentDevice, ALCsizei(aSampleRate), ALCsizei(Ord(aChannels)), ALCenum(Ord(aSampleType))); alcGetError(FPArentDevice); end; @@ -1523,183 +1557,69 @@ function TALSLoopbackContext.PrepareSavingToFile(const aFilename: string; FFilename := aFilename; // Create output audio file FFileInfo.SampleRate := FSampleRate; - FFileInfo.Format := aFileFormat; + FFileInfo. Format := aFileFormat; FFileInfo.Channels := FFrameBuffer.ChannelCount; - FFile := sf_open(PChar(aFilename), SFM_WRITE, @FFileInfo); -{ if FFile = NIL then - SetMixingError(als_CanNotCreateTargetMixFile); } + FFile := ALSOpenAudioFile(aFilename, SFM_WRITE, @FFileInfo); Result := FFile <> NIL; end; -procedure TALSLoopbackContext.StartMixing; -var - posTime: double; - written: sf_count_t; - done, flagSaveToFile: boolean; +procedure TALSLoopbackContext.BeginOfMix; begin - if Error or - FIsMixing then - exit; - if FOnProgress = NIL then - Raise Exception.Create('TALSLoopbackContext.StartMixing - Callback OnProgress must be defined'); - - ResetMixingError; - - FIsMixing := True; - FCancelMixing := False; - - // reserves buffer memory - FFrameBuffer.FrameCapacity := Round(FSampleRate*FTimeSlice); - - posTime := 0.0; - done := False; - flagSaveToFile := True; - - while not MixingError and - not FCancelMixing and not done do - begin - // Render some audio - RenderAudioToBuffer; - - // Call the callback - FOnProgress(Self, posTime+FTimeSlice, FFrameBuffer, flagSaveToFile, done); - - if (FFile <> NIL) and flagSaveToFile then - begin// save audio to file - case FFrameBuffer.SampleType of - ALC_SHORT_SOFT: written := sf_writef_short(FFile, FFrameBuffer.Data, FFrameBuffer.FrameCount); - ALC_INT_SOFT: written := sf_writef_int(FFile, FFrameBuffer.Data, FFrameBuffer.FrameCount); - ALC_FLOAT_SOFT: written := sf_writef_float(FFile, FFrameBuffer.Data, FFrameBuffer.FrameCount); - end; - - // check write file error - if written <> sf_count_t(FFrameBuffer.FrameCount) then - SetMixingError(als_FileWriteErrorWhileMixing); - end; - - {$ifdef LCL} - Application.ProcessMessages; - {$endif} - - Update(FTimeSlice); - - posTime := posTime + FTimeSlice; - end; - - if FFile <> NIL then - begin - // close file - sf_write_sync(FFile); - sf_close(FFile); - - // operation was canceled ? - if FCancelMixing then - DeleteFile(FFilename); - - FFile := NIL; + DoExceptionNoCallback + else begin + FIsMixing := True; + FMixTime := 0.0; end; - - FFrameBuffer.FreeMemory; - FIsMixing := False; end; -procedure TALSLoopbackContext.CancelMix; +procedure TALSLoopbackContext.Mix(aDuration: single); +var + deltaTime: double; + flagSaveToFile, flagCancel: boolean; begin - FCancelMixing := True; -end; + if Error or MixingError then + exit; -{ TALSLoopbackDeviceItem } + flagSaveToFile := (FFile <> NIL); + flagCancel := False; -procedure TALSLoopbackDeviceItem.InitDefault; -begin - Name := ''; - Handle := nil; - OpenedCount := 0; -end; - -procedure TALSLoopbackDeviceItem.Open; -begin - if not ALSManager.Error then + while (aDuration > 0.0005) and not MixingError and not flagCancel do begin - if Handle = NIL then - begin - if alcIsExtensionPresent(NIL, PChar('ALC_SOFT_loopback')) then - begin - if LoadExt_ALC_SOFT_loopback(NIL) then - if Name = '' then - Handle := alcLoopbackOpenDeviceSOFT(nil) - else - Handle := alcLoopbackOpenDeviceSOFT(PChar(Name)); - end; - end; - inc( OpenedCount ); - end; -end; + // we need to mix slice of time defined by user + deltaTime := Min(aDuration, FTimeSlice); + aDuration := aDuration - deltaTime; -procedure TALSLoopbackDeviceItem.Close; -begin - if not ALSManager.Error then - begin - {$ifndef ALS_ENABLE_CONTEXT_SWITCHING} - _SingleContextIsCurrent := False; - {$endif} - if OpenedCount = 0 then - exit; - dec( OpenedCount ); - if OpenedCount = 0 then - DoCloseDevice; - end; -end; + // reserves buffer memory + if FFrameBuffer.FrameCapacity <> Round(FSampleRate*deltaTime) then + FFrameBuffer.FrameCapacity := Round(FSampleRate*deltaTime); -procedure TALSLoopbackDeviceItem.DoCloseDevice; -begin - alcCloseDevice(Handle); - Handle := NIL; -end; + // Render audio + RenderAudioToBuffer; -{ TALSPlaybackDeviceItem } + // update the context + deltaTime := FFrameBuffer.FrameCount/FSampleRate; + Update(deltaTime); + FMixTime := FMixTime + deltaTime; -procedure TALSPlaybackDeviceItem.InitDefault; -begin - Name := ''; - Handle := nil; - OpenedCount := 0; -end; + // Callback + FOnProgress(Self, FMixTime, FFrameBuffer, flagSaveToFile, flagCancel); -procedure TALSPlaybackDeviceItem.Open; -begin - if not ALSManager.Error then - begin - if Handle = NIL then - Handle := alcOpenDevice(PChar(Name)); - inc( OpenedCount ); + if flagSaveToFile then + SaveBufferToFile; end; end; -procedure TALSPlaybackDeviceItem.Close; +procedure TALSLoopbackContext.EndOfMix; begin - if not ALSManager.Error then - begin - {$ifndef ALS_ENABLE_CONTEXT_SWITCHING} - _SingleContextIsCurrent := False; - {$endif} - if OpenedCount = 0 then - exit; - dec( OpenedCount ); - if OpenedCount = 0 then - DoCloseDevice; + if FIsMixing then begin + CloseFile; + FFrameBuffer.FreeMemory; + FIsMixing := False; end; end; -procedure TALSPlaybackDeviceItem.DoCloseDevice; -begin - alcCloseDevice( Handle ); - Handle := NIL; -end; - - { TALSAudioFileFormat } function TALSAudioFileFormat.SubFormatCount: integer; @@ -1920,7 +1840,7 @@ function TALSCaptureContext.GetCaptureError: boolean; function TALSCaptureContext.GetChannelsLeveldB(Index: integer): single; begin - Result := ALSPercentToDecibel(GetChannelsLevel(Index)); + Result := LinearTodB(GetChannelsLevel(Index)); end; function TALSCaptureContext.GetChannelsPeak(Index: integer): single; @@ -1935,7 +1855,7 @@ function TALSCaptureContext.GetChannelsPeak(Index: integer): single; function TALSCaptureContext.GetChannelsPeakdB(Index: integer): single; begin - Result := ALSPercentToDecibel(GetChannelsPeak(Index)); + Result := LinearTodB(GetChannelsPeak(Index)); end; procedure TALSCaptureContext.SetMonitoringEnabled(AValue: boolean); @@ -2064,7 +1984,7 @@ function TALSCaptureContext.PrepareSavingToFile(const aFileName: string; aFormat tempInfo.SampleRate := FSampleRate; tempInfo.Format := Ord(SF_ENDIAN_CPU) or Ord(SF_FORMAT_AU) or FTempFileSubFormat; tempInfo.Channels := FCapturedFrames.ChannelCount; - FTempFile := sf_open(PChar(FTempFileName), SFM_WRITE, @tempInfo); + FTempFile := ALSOpenAudioFile(FTempFileName, SFM_WRITE, @tempInfo); if FTempFile = nil then FCaptureError := als_CanNotOpenTemporaryCaptureFile; @@ -2166,14 +2086,14 @@ procedure TALSCaptureContext.StopCapture; begin // reopen the temporary '.au' file for reading fileInfo.Format := 0; - FTempFile := sf_open(PChar(FTempFileName), SFM_READ, @fileInfo); + FTempFile := ALSOpenAudioFile(FTempFileName, SFM_READ, @fileInfo); if FTempFile = nil then SetCaptureError(als_CanNotOpenTemporaryCaptureFile); if not CaptureError then begin // open the 'final' file with the requested format - finalFile := sf_open(PChar(FUserFileName), SFM_WRITE, @FUserFileInfo); + finalFile := ALSOpenAudioFile(FUserFileName, SFM_WRITE, @FUserFileInfo); if finalFile = nil then begin SetCaptureError(als_CanNotCreateTargetCaptureFile); @@ -2377,10 +2297,13 @@ procedure TALSManager.RetrievePlaybackDevices; begin FPlaybackDevices := NIL; FDefaultPlaybackDeviceIndex := -1; + if not Error then begin - _defaultDeviceName := openalsoft.GetDefaultDeviceName; A := openalsoft.GetDeviceNames; + if Length(A) = 0 then exit; + + _defaultDeviceName := openalsoft.GetDefaultDeviceName; SetLength(FPlaybackDevices, Length(A)); for i:=0 to High(A) do begin @@ -2659,7 +2582,7 @@ function TALSManager.MixModeIndexToEnum(aIndex: integer): TALSPlaybackContextOut function TALSManager.CreateDefaultLoopbackContext: TALSLoopbackContext; begin FDefaultLoopbackDevice.Open; - Result := TALSLoopbackContext.Create(FDefaultLoopbackDevice.Handle); + Result := TALSLoopbackContext.Create(@FDefaultLoopbackDevice); end; function TALSManager.CreateDefaultPlaybackContext: TALSPlaybackContext; @@ -2676,18 +2599,14 @@ function TALSManager.CreatePlaybackContext(aNameIndex: integer; if aNameIndex = -1 then aNameIndex := FDefaultPlaybackDeviceIndex; - if not Error then - begin - if (aNameIndex < 0) or (aNameIndex > High(ListOfPlaybackDeviceName)) then - Result := TALSPlaybackContext.Create(NIL, aAttribs) - else - begin - FPlaybackDevices[aNameIndex].Open; - Result := TALSPlaybackContext.Create(FPlaybackDevices[aNameIndex].Handle, aAttribs); - end; - end - else - Result := TALSPlaybackContext.Create(NIL, aAttribs); + if Error or + (aNameIndex < 0) or + (aNameIndex >= Length(ListOfPlaybackDeviceName)) then + Result := TALSPlaybackContext.Create(NIL, aAttribs) + else begin + FPlaybackDevices[aNameIndex].Open; + Result := TALSPlaybackContext.Create(@FPlaybackDevices[aNameIndex], aAttribs); + end; end; function TALSManager.CreateDefaultCaptureContext: TALSCaptureContext; @@ -2744,6 +2663,7 @@ procedure TALSContextAttributes.SetLoopbackMode(aSampleRate: integer; SampleRate := ALint(aSampleRate); FLoopbackChannelType := aChannels; FLoopbackSampleType := aSampleType; + ContextUseFloat := aSampleType = ALC_FLOAT_SOFT; end; function TALSContextAttributes.ToArray( aEFXPresent, aOutputModePresent, @@ -2918,7 +2838,7 @@ procedure TALSPlaylist.LoadCurrentMusic; begin FreeCurrentMusic; try - FMusic := TALSStreamBufferSound.CreateFromFile(FParentContext, FList.Strings[FMusicIndex], False, NIL); + FMusic := TALSStreamBufferSound.CreateFromFile(FParentContext, FList.Strings[FMusicIndex], False, NIL, NIL); except FMusic.Free; FMusic := nil; @@ -3439,7 +3359,7 @@ procedure TALSEffect.SetMute(AValue: boolean); not FReady then exit; if FParentContext.Error or - not FParentContext.FHaveEXT_ALC_EXT_EFX then + not FParentContext.FParentDeviceItem^.FHaveEXT_ALC_EXT_EFX then exit; LockContext( FParentContext.FContext ); @@ -3606,7 +3526,7 @@ procedure TALSEffect.SetApplyDistanceAttenuation(AValue: boolean); FApplyDistanceAttenuation:=AValue; if FParentContext.Error or - not FParentContext.FHaveEXT_ALC_EXT_EFX or + not FParentContext.FParentDeviceItem^.FHaveEXT_ALC_EXT_EFX or not Ready then Exit; @@ -3747,6 +3667,7 @@ procedure TALSPlaybackCapturedSound.SetFileName(const aName: string); procedure TALSPlaybackCapturedSound.SetTimePosition(AValue: single); begin // Do nothing here + AValue := AValue; // avoid hint end; constructor TALSPlaybackCapturedSound.CreateFromCapture(aParent: TALSPlaybackContext; @@ -3861,7 +3782,7 @@ procedure TALSStreamBufferSound.PreBuffAudio; break; if FOnCustomDSP <> NIL then - FOnCustomDSP(Self, FBuffers[i]); + FOnCustomDSP(Self, FBuffers[i], FOnCustomDSPUserData); // refill AL buffer with audio alBufferData(FBuffers[i].BufferID, FFormatForAL, FBuffers[i].Data, @@ -3894,63 +3815,71 @@ procedure TALSStreamBufferSound.Update(const aElapsedTime: single); bufid: ALuint; readCount: sf_count_t; bufferIndex: integer; + res: ALenum; begin inherited Update(aElapsedTime); if Error then exit; - // Get the number of processed buffer - alGetSourceiv(FSource, AL_BUFFERS_PROCESSED, @processed); - if processed < 1 then - exit; + EnterCS; + try + // Get the number of processed buffer + alGetSourceiv(FSource, AL_BUFFERS_PROCESSED, @processed); + if processed < 1 then + exit; - FFrameReadAccu := FFrameReadAccu + int64(processed * FBufferFrameCount); + FFrameReadAccu := FFrameReadAccu + int64(processed * FBufferFrameCount); - // Unqueue and fill each processed buffer - while (processed > 0) do - begin - alSourceUnqueueBuffers(FSource, 1, @bufid); - Dec(processed); - - // increment the index of the played buffer. we use this index to retrieve - // the channel's level. - inc(FPlayedBufferIndex); - if FPlayedBufferIndex >= FUsedBuffer then - FPlayedBufferIndex := 0; - - // retrieves the index of the buffer to refill with audio - bufferIndex := 0; - while FBuffers[bufferIndex].BufferID <> bufid do - inc(bufferIndex); - - // Read data from opened file - readCount := FDoReadFromStream(FBuffers[bufferIndex].Data, - FBuffers[bufferIndex].FrameCapacity); - FBuffers[bufferIndex].FrameCount := readCount; - - if readCount > 0 then + // Unqueue and fill each processed buffer + while (processed > 0) do begin - // callback OnNewBuffer - if FOnCustomDSP <> NIL then - FOnCustomDSP(Self, FBuffers[bufferIndex]); - // refill the openAL buffer with... - alBufferData(bufid, ALenum(FFormatForAL), FBuffers[bufferIndex].Data, - ALsizei(readCount * FFrameSize), ALsizei(Fsfinfo.SampleRate)); - // and queue it back on the source - alSourceQueueBuffers(FSource, 1, @bufid); - // Set the opened file read cursor to the beginning if LOOP mode is enabled, - // and the buffer was not completely filled - if FLoop and (readCount < FBuffers[bufferIndex].FrameCapacity) then + alSourceUnqueueBuffers(FSource, 1, @bufid); + Dec(processed); + res := alGetError(); + if res <> AL_NO_ERROR then continue; + + // increment the index of the played buffer. we use this index to retrieve + // the channel's level. + inc(FPlayedBufferIndex); + if FPlayedBufferIndex >= FUsedBuffer then + FPlayedBufferIndex := 0; + + // retrieves the index of the buffer to refill with audio + bufferIndex := 0; + while FBuffers[bufferIndex].BufferID <> bufid do + inc(bufferIndex); + + // Read data from opened file + readCount := FDoReadFromStream(FBuffers[bufferIndex].Data, + FBuffers[bufferIndex].FrameCapacity); + FBuffers[bufferIndex].FrameCount := readCount; + + if readCount > 0 then begin - sf_seek(Fsndfile, 0, SF_SEEK_SET); - FFrameReadAccu := 0; - end; + // callback custom DSP + if FOnCustomDSP <> NIL then + FOnCustomDSP(Self, FBuffers[bufferIndex], FOnCustomDSPUserData); + // refill the openAL buffer with... + alBufferData(bufid, ALenum(FFormatForAL), FBuffers[bufferIndex].Data, + ALsizei(readCount * FFrameSize), ALsizei(Fsfinfo.SampleRate)); + // and queue it back on the source + alSourceQueueBuffers(FSource, 1, @bufid); + // Set the opened file read cursor to the beginning if LOOP mode is enabled, + // and the buffer was not completely filled + if FLoop and (readCount < FBuffers[bufferIndex].FrameCapacity) then + begin + sf_seek(Fsndfile, 0, SF_SEEK_SET); + FFrameReadAccu := 0; + end; - // retrieve the channels level - if FMonitoringEnabled then - FBuffers[bufferIndex].ComputeChannelsLevel; + // retrieve the channels level + if FMonitoringEnabled then + FBuffers[bufferIndex].ComputeChannelsLevel; + end; end; + finally + LeaveCS; end; end; @@ -3969,7 +3898,7 @@ procedure TALSStreamBufferSound.InternalRewind; constructor TALSStreamBufferSound.CreateFromFile(aParent: TALSPlaybackContext; const aFilename: string; aEnableMonitor: boolean; - aOnCustomDSP: TALSOnCustomDSP); + aOnCustomDSP: TALSOnCustomDSP; aCustomDSPUserData: Pointer); var fileopened: boolean; begin @@ -3980,16 +3909,13 @@ constructor TALSStreamBufferSound.CreateFromFile(aParent: TALSPlaybackContext; FFilename := aFilename; FMonitoringEnabled := aEnableMonitor; FOnCustomDSP := aOnCustomDSP; + FOnCustomDSPUserData := aCustomDSPUserData; FDoReadFromStream := @DoReadStreamFromFile; if not Error then begin - {$ifdef windows} - Fsndfile := sf_wchar_open(PWideChar(UnicodeString(aFilename)), SFM_READ, @Fsfinfo); - {$else} - Fsndfile := sf_open(PChar(aFilename), SFM_READ, @Fsfinfo); - {$endif} + Fsndfile := ALSOpenAudioFile(aFilename, SFM_READ, @Fsfinfo); if Fsndfile = nil then SetError(als_FileNotOpened) else @@ -4073,6 +3999,7 @@ procedure TALSStreamBufferSound.SetTimePosition(AValue: single); i: integer; begin if Error then exit; + if TotalDuration = 0 then exit; if (AValue < 0) or (AValue > TotalDuration) then exit; LockContext( FParentContext.FContext ); @@ -4158,7 +4085,8 @@ function TALSSingleStaticBufferSound.GetChannelLevel(index: integer): single; end; constructor TALSSingleStaticBufferSound.CreateFromFile(aParent: TALSPlaybackContext; - const aFilename: string; aEnableMonitor: boolean; aOnCustomDSP: TALSOnCustomDSP); + const aFilename: string; aEnableMonitor: boolean; aOnCustomDSP: TALSOnCustomDSP; + aCustomDSPUserData: Pointer); var sndfile: PSNDFILE; sfinfo: TSF_INFO; @@ -4172,14 +4100,11 @@ constructor TALSSingleStaticBufferSound.CreateFromFile(aParent: TALSPlaybackCont FFilename := aFilename; FMonitoringEnabled := aEnableMonitor; FOnCustomDSP := aOnCustomDSP; + FOnCustomDSPUserData := aCustomDSPUserData; if not Error then begin - {$ifdef windows} - sndfile := sf_wchar_open(PWideChar(UnicodeString(aFilename)), SFM_READ, @sfinfo); - {$else} - sndfile := sf_open(PChar(aFilename), SFM_READ, @sfinfo); - {$endif} + sndfile := ALSOpenAudioFile(aFilename, SFM_READ, @sfinfo); if sndfile = nil then SetError(als_FileNotOpened) else @@ -4218,7 +4143,7 @@ constructor TALSSingleStaticBufferSound.CreateFromFile(aParent: TALSPlaybackCont FByteCount := frameRead*FFrameSize; end; if FOnCustomDSP <> NIL then - FOnCustomDSP(Self, FBuffers[0]); + FOnCustomDSP(Self, FBuffers[0], FOnCustomDSPUserData); end; end; end; @@ -4258,13 +4183,14 @@ constructor TALSSingleStaticBufferSound.CreateFromFile(aParent: TALSPlaybackCont // White noise generation from OpenAL example "altonegen.c" constructor TALSSingleStaticBufferSound.CreateWhiteNoise(aParent: TALSPlaybackContext; aDuration: single; aChannelCount: integer; aEnableMonitor: boolean; - aOnCustomDSP: TALSOnCustomDSP); + aOnCustomDSP: TALSOnCustomDSP; aCustomDSPUserData: Pointer); begin FParentContext := aParent; InitializeErrorStatus; FFilename := ''; FMonitoringEnabled := aEnableMonitor; FOnCustomDSP := aOnCustomDSP; + FOnCustomDSPUserData := aCustomDSPUserData; if not Error then begin @@ -4304,7 +4230,7 @@ constructor TALSSingleStaticBufferSound.CreateWhiteNoise(aParent: TALSPlaybackCo dsp_FillWithWhiteNoise_Smallint(FBuffers[0].Data, FFrameCount, FBuffers[0].ChannelCount); if FOnCustomDSP <> NIL then - FOnCustomDSP(Self, FBuffers[0]); + FOnCustomDSP(Self, FBuffers[0], FOnCustomDSPUserData); alBufferData(FBuffers[0].BufferID, FFormatForAL, FBuffers[0].Data, FByteCount, FSampleRate); CheckALError(als_ALCanNotFillBuffer); @@ -4421,12 +4347,13 @@ destructor TALSPlaybackContext.Destroy; end; function TALSPlaybackContext.AddStream(const aFilename: string; aEnableMonitoring: boolean; - aOnCustomDSP: TALSOnCustomDSP): TALSSound; + aOnCustomDSP: TALSOnCustomDSP; aCustomDSPUserData: Pointer): TALSSound; begin EnterCriticalSection(FCriticalSection); LockContext( FContext ); try - Result := TALSStreamBufferSound.CreateFromFile(Self, aFilename, aEnableMonitoring, aOnCustomDSP); + Result := TALSStreamBufferSound.CreateFromFile(Self, aFilename, + aEnableMonitoring, aOnCustomDSP, aCustomDSPUserData); FList.Add(Result); finally LeaveCriticalSection(FCriticalSection); @@ -4435,13 +4362,14 @@ function TALSPlaybackContext.AddStream(const aFilename: string; aEnableMonitorin end; function TALSPlaybackContext.CreateWhiteNoise(aDuration: single; - aChannelCount: integer; aEnableMonitoring: boolean; aOnCustomDSP: TALSOnCustomDSP): TALSSound; + aChannelCount: integer; aEnableMonitoring: boolean; + aOnCustomDSP: TALSOnCustomDSP; aCustomDSPUserData: Pointer): TALSSound; begin EnterCriticalSection(FCriticalSection); LockContext( FContext ); try Result := TALSSingleStaticBufferSound.CreateWhiteNoise(Self, aDuration, - aChannelCount, aEnableMonitoring, aOnCustomDSP); + aChannelCount, aEnableMonitoring, aOnCustomDSP, aCustomDSPUserData); FList.Add(Result); finally LeaveCriticalSection(FCriticalSection); @@ -4452,7 +4380,6 @@ function TALSPlaybackContext.CreateWhiteNoise(aDuration: single; procedure TALSPlaybackContext.DoUpdate(const aElapsedTime: single); var i: integer; - s: TALSSound; begin EnterCriticalSection(FCriticalSection); FThreadIsStarted:=True; @@ -4475,11 +4402,11 @@ procedure TALSPlaybackContext.DoUpdate(const aElapsedTime: single); // Kill or update sounds for i := FList.Count - 1 downto 0 do begin - s := TALSSound(FList.Items[i]); - if s.FKill then + FSoundToProcess := TALSSound(FList.Items[i]); + if FSoundToProcess.FKill then InternalDeleteSound(i) else - s.Update(aElapsedTime); + FSoundToProcess.Update(aElapsedTime); end; // update playlist (if exists) if FPlaylist <> nil then @@ -4497,6 +4424,12 @@ procedure TALSPlaybackContext.DoUpdate(const aElapsedTime: single); end; end; +procedure TALSPlaybackContext.DoSoundOnStopped; +begin + if FSoundToProcess.FOnStopped <> NIL then + FSoundToProcess.FOnStopped(FSoundToProcess); +end; + procedure TALSPlaybackContext.EnterCS; begin EnterCriticalSection(FCriticalSection); @@ -4542,7 +4475,7 @@ function TALSPlaybackContext.CreateEffect(aEffectType: TALSEffectType; begin Result.InitDefault(Self); - if not ALSManager.Error and FHaveEXT_ALC_EXT_EFX then + if not ALSManager.Error and FParentDeviceItem^.FHaveEXT_ALC_EXT_EFX then begin LockContext( FContext ); try @@ -4611,7 +4544,7 @@ function TALSPlaybackContext.ChangeAttributes(const aAttribs: TALSContextAttribu begin LockContext( FContext ); try - Result := alcResetDeviceSOFT( FParentDevice, @aAttribs ); + Result := FParentDeviceItem^.alcResetDeviceSOFT( FParentDevice, @aAttribs ); finally UnlockContext; end; @@ -4731,10 +4664,20 @@ function TALSPlaybackContext.GetHRTFEnabled: boolean; function TALSPlaybackContext.GetHaveFilter: boolean; begin - Result := FHaveEXT_ALC_EXT_EFX and + Result := FParentDeviceItem^.FHaveEXT_ALC_EXT_EFX and (FHaveLowPassFilter or FHaveBandPassFilter or FHaveHighPassFilter); end; +function TALSPlaybackContext.GetHaveEXT_ALC_EXT_EFX: boolean; +begin + Result := FParentDeviceItem^.FHaveEXT_ALC_EXT_EFX; +end; + +function TALSPlaybackContext.GetHaveExt_ALC_SOFT_HRTF: boolean; +begin + Result := FParentDeviceItem^.FHaveExt_ALC_SOFT_HRTF; +end; + function TALSPlaybackContext.GetHRTFList: TStringArray; var num_hrtf: ALCint; @@ -4750,7 +4693,7 @@ function TALSPlaybackContext.GetHRTFList: TStringArray; alcGetIntegerv(FParentDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, @num_hrtf); SetLength(Result, num_hrtf); for i:=0 to num_hrtf-1 do - Result[i] := alcGetStringiSOFT(FParentDevice, ALC_HRTF_SPECIFIER_SOFT, ALCSizei(i)); + Result[i] := FParentDeviceItem^.alcGetStringiSOFT(FParentDevice, ALC_HRTF_SPECIFIER_SOFT, ALCSizei(i)); end; finally UnlockContext; @@ -4846,31 +4789,31 @@ procedure TALSPlaybackContext.InitializeALContext( // Look for some extensions before the creation of the context because // some attributes use them. - FHaveEXT_ALC_EXT_EFX := alcIsExtensionPresent(FParentDevice, PChar('ALC_EXT_EFX')); +{ FHaveEXT_ALC_EXT_EFX := alcIsExtensionPresent(FParentDevice, PChar('ALC_EXT_EFX')); if FHaveEXT_ALC_EXT_EFX then FHaveEXT_ALC_EXT_EFX := LoadExt_ALC_EXT_EFX; FHaveExt_ALC_SOFT_HRTF := alcIsExtensionPresent(FParentDevice, PChar('ALC_SOFT_HRTF')); if FHaveExt_ALC_SOFT_HRTF then - FHaveExt_ALC_SOFT_HRTF := LoadExt_ALC_SOFT_HRTF(FParentDevice); + FHaveExt_ALC_SOFT_HRTF := LoadExt_ALC_SOFT_HRTF(FParentDevice); } FHaveExt_AL_SOFT_source_resampler := alcIsExtensionPresent(FParentDevice, PChar('AL_SOFT_source_resampler')); if FHaveExt_AL_SOFT_source_resampler then FHaveExt_AL_SOFT_source_resampler := LoadExt_AL_SOFT_source_resampler; - FHaveExt_ALC_SOFT_output_mode := alcIsExtensionPresent(FParentDevice, PChar('ALC_SOFT_output_mode')); +// FHaveExt_ALC_SOFT_output_mode := alcIsExtensionPresent(FParentDevice, PChar('ALC_SOFT_output_mode')); - FHaveExt_ALC_SOFT_loopback := alcIsExtensionPresent(FParentDevice, PChar('ALC_SOFT_loopback')); +// FHaveExt_ALC_SOFT_loopback := alcIsExtensionPresent(FParentDevice, PChar('ALC_SOFT_loopback')); // extension is loaded in TALSLoopbackDeviceItem.Open - FHaveExt_ALC_SOFT_output_limiter := alcIsExtensionPresent(FParentDevice, PChar('ALC_SOFT_output_limiter')); +// FHaveExt_ALC_SOFT_output_limiter := alcIsExtensionPresent(FParentDevice, PChar('ALC_SOFT_output_limiter')); FContext := alcCreateContext(FParentDevice, - @aAttribs.ToArray(FHaveEXT_ALC_EXT_EFX, - FHaveExt_ALC_SOFT_output_mode, - FHaveExt_ALC_SOFT_HRTF, - FHaveExt_ALC_SOFT_loopback, - FHaveExt_ALC_SOFT_output_limiter)[0]); + @aAttribs.ToArray(FParentDeviceItem^.FHaveEXT_ALC_EXT_EFX, + FParentDeviceItem^.FHaveExt_ALC_SOFT_output_mode, + FParentDeviceItem^.FHaveExt_ALC_SOFT_HRTF, + FParentDeviceItem^.FHaveExt_ALC_SOFT_loopback, + FParentDeviceItem^.FHaveExt_ALC_SOFT_output_limiter)[0]); CheckALCError(FParentDevice, als_ALContextNotCreated); if FContext <> nil then @@ -4897,7 +4840,7 @@ procedure TALSPlaybackContext.InitializeALContext( alListenerfv(AL_ORIENTATION, @A[0]); //check for available filter - if FHaveEXT_ALC_EXT_EFX then + if FParentDeviceItem^.FHaveEXT_ALC_EXT_EFX then begin alGenFilters(1, @obj); alGetError(); @@ -4998,14 +4941,16 @@ function TALSPlaybackContext.AddCapturePlayback(aSampleRate: integer; end; end; -constructor TALSPlaybackContext.Create(aDevice: PALCDevice; const aAttribs: TALSContextAttributes); +constructor TALSPlaybackContext.Create(aDevice: PALSPlaybackDeviceItem; + const aAttribs: TALSContextAttributes); begin FExecutingConstructor := True; InitializeErrorStatus; - FParentDevice := aDevice; + FParentDeviceItem := PALSDeviceItem(aDevice); + FParentDevice := aDevice^.Handle; FObtainedSampleRate := aAttribs.SampleRate; - if aDevice = NIL then + if aDevice^.Handle = NIL then SetError(als_ALCanNotOpenPlaybackDevice) else InitializeALContext(aAttribs); @@ -5015,6 +4960,7 @@ constructor TALSPlaybackContext.Create(aDevice: PALCDevice; const aAttribs: TALS InitCriticalSection( FCriticalSection ); FThreadIsStarted:=False; FThread := TALSThread.Create(@DoUpdate, 10, True); + FThread.Priority := tpHighest; // waits for the thread to be started to prevent any problems while not FThreadIsStarted do Sleep(1); @@ -5022,14 +4968,15 @@ constructor TALSPlaybackContext.Create(aDevice: PALCDevice; const aAttribs: TALS end; function TALSPlaybackContext.AddSound(const aFilename: string; aEnableMonitoring: boolean; - aOnCustomDSP: TALSOnCustomDSP): TALSSound; + aOnCustomDSP: TALSOnCustomDSP; aCustomDSPUserData: Pointer): TALSSound; begin LockContext( FContext ); try EnterCriticalSection(FCriticalSection); try - Result := TALSSingleStaticBufferSound.CreateFromFile(Self, aFilename, aEnableMonitoring, aOnCustomDSP); + Result := TALSSingleStaticBufferSound.CreateFromFile(Self, aFilename, + aEnableMonitoring, aOnCustomDSP, aCustomDSPUserData); FList.Add(Result); finally LeaveCriticalSection(FCriticalSection); @@ -5108,7 +5055,7 @@ procedure TALSSound.CreateParameters; Tone.FOnLockParam := @EnterCS; Tone.FOnUnlockParam := @LeaveCS; - if not Error and FParentContext.FHaveEXT_ALC_EXT_EFX then + if not Error and FParentContext.FParentDeviceItem^.FHaveEXT_ALC_EXT_EFX then begin SetLength(FAuxiliarySend, FParentContext.ObtainedAuxiliarySendCount); for i := 0 to High(FAuxiliarySend) do @@ -5135,11 +5082,22 @@ procedure TALSSound.FreeParameters; FAuxiliarySend[i].Disconnect; end; -procedure TALSSound.SetOnCustomDSP(AValue: TALSOnCustomDSP); +procedure TALSSound.SetOnCustomDSP(aProc: TALSOnCustomDSP; aUserData: Pointer); begin EnterCS; try - FOnCustomDSP := AValue; + FOnCustomDSP := aProc; + FOnCustomDSPUserData := aUserData; + finally + LeaveCS; + end; +end; + +procedure TALSSound.SetOnStopped(AValue: TALSNotifyEvent); +begin + EnterCS; + try + FOnStopped := AValue; finally LeaveCS; end; @@ -5536,7 +5494,7 @@ function TALSSound.GetChannelLevel(index: integer): single; function TALSSound.GetChannelLeveldB(index: integer): single; begin - Result := ALSPercentToDecibel( GetChannelLevel(index) ); + Result := LinearTodB( GetChannelLevel(index) ); end; procedure TALSSound.SetMute(AValue: boolean); @@ -5672,7 +5630,10 @@ procedure TALSSound.LeaveCS; procedure TALSSound.Update(const aElapsedTime: single); var v: single; + flagDoOnStopped: boolean; begin + flagDoOnStopped := False; + EnterCriticalSection(FCriticalSection); try v := Volume.Value; @@ -5697,9 +5658,16 @@ procedure TALSSound.Update(const aElapsedTime: single); FKill := FKill or FKillAfterFadeOut; end; + flagDoOnStopped := (State = ALS_STOPPED) and + (FPreviousState <> ALS_STOPPED); + FPreviousState := State; finally LeaveCriticalSection(FCriticalSection);; end; + + if flagDoOnStopped and (FOnStopped <> NIL) then + FParentContext.FThread.Synchronize(FParentContext.FThread, + @FParentContext.DoSoundOnStopped); end; procedure TALSSound.InternalRewind; diff --git a/source/libsndfile.pas b/source/libsndfile.pas index 81ba580..a27b931 100644 --- a/source/libsndfile.pas +++ b/source/libsndfile.pas @@ -906,6 +906,9 @@ TSF_CHUNK_ITERATOR = record function LoadSndFileLibrary( const aFilename: string ): boolean; procedure UnloadSndFileLibrary; +// open an audio file in a cross platform way +function ALSOpenAudioFile(const aFilename: string; aMode: cint; Asfinfo: PSF_INFO): PSNDFILE; + implementation @@ -1010,4 +1013,15 @@ procedure UnloadSndFileLibrary; end; end; +function ALSOpenAudioFile(const aFilename: string; aMode: cint; Asfinfo: PSF_INFO): PSNDFILE; +begin + {$ifdef windows} + Result := sf_wchar_open(PWideChar(UnicodeString(aFilename)), aMode, Asfinfo); + {$else} + Result := sf_open(PChar(aFilename), aMode, Asfinfo); + {$endif} +end; + + + end. diff --git a/source/openalsoft.pas b/source/openalsoft.pas index bc9a15e..24b1374 100644 --- a/source/openalsoft.pas +++ b/source/openalsoft.pas @@ -63,21 +63,23 @@ interface function LoadOpenALCoreLibrary( const aFilename: string ): boolean; procedure UnloadOpenALSoftLibrary; -// load extension -function LoadExt_ALC_EXT_EFX: boolean; // loaded in playback/loopback context +// Device dependant extensions function LoadExt_ALC_EXT_thread_local_context(aDevice: PALCDevice): boolean; +function LoadExt_ALC_SOFT_loopback(aDevice: PALCDevice): boolean; +function LoadExt_ALC_SOFT_pause_device(aDevice: PALCDevice): boolean; +function LoadExt_ALC_SOFT_HRTF(aDevice: PALCDevice): boolean; // loaded in playback/loopback context +function LoadExt_ALC_SOFT_device_clock(aDevice: PALCDevice): boolean; +function LoadExt_ALC_SOFT_reopen_device(aDevice: PALCDevice): boolean; + +// Other extensions +function LoadExt_ALC_EXT_EFX: boolean; // loaded in playback/loopback context function LoadExt_AL_SOFT_buffer_samples: boolean; function LoadExt_AL_SOFT_buffer_sub_data: boolean; function LoadExt_AL_SOFT_callback_buffer: boolean; -function LoadExt_ALC_SOFT_loopback(aDevice: PALCDevice): boolean; function LoadExt_AL_SOFT_source_latency: boolean; function LoadExt_AL_SOFT_deferred_updates: boolean; // loaded in playback/loopback context -function LoadExt_ALC_SOFT_pause_device(aDevice: PALCDevice): boolean; -function LoadExt_ALC_SOFT_HRTF(aDevice: PALCDevice): boolean; // loaded in playback/loopback context function LoadExt_AL_SOFT_source_resampler: boolean; // loaded in playback/loopback context -function LoadExt_ALC_SOFT_device_clock(aDevice: PALCDevice): boolean; function LoadExt_AL_SOFT_events: boolean; -function LoadExt_ALC_SOFT_reopen_device(aDevice: PALCDevice): boolean; // some functions to ease things function GetDeviceNames: TStringArray; @@ -94,18 +96,12 @@ function StringToNullTerminated(const s: string): PChar; var FLoaded_OpenALCore_: boolean=False; FExtensionLoaded_ALC_EXT_EFX: boolean=False; - FExtensionLoaded_ALC_EXT_thread_local_context: boolean=False; FExtensionLoaded_AL_SOFT_buffer_samples: boolean=False; FExtensionLoaded_AL_SOFT_buffer_sub_data: boolean=False; FExtensionLoaded_AL_SOFT_callback_buffer: boolean=False; - FExtensionLoaded_ALC_SOFT_loopback: boolean=False; - FExtensionLoaded_ALC_SOFT_reopen_device: boolean=False; FExtensionLoaded_AL_SOFT_source_latency: boolean=False; FExtensionLoaded_AL_SOFT_deferred_updates: boolean=False; - FExtensionLoaded_ALC_SOFT_pause_device: boolean=False; - FExtensionLoaded_ALC_SOFT_HRTF: boolean=False; FExtensionLoaded_AL_SOFT_source_resampler: boolean=False; - FExtensionLoaded_ALC_SOFT_device_clock: boolean=False; FExtensionLoaded_AL_SOFT_events: boolean=False; implementation @@ -329,17 +325,17 @@ function LoadExt_ALC_EXT_EFX: boolean; function LoadExt_ALC_EXT_thread_local_context(aDevice: PALCDevice): boolean; begin - Result := FExtensionLoaded_ALC_EXT_thread_local_context; - if Result then exit; - if _OpenALLib_Handle <> DynLibs.NilHandle then + Result := alcIsExtensionPresent(NIL, PChar('ALC_EXT_thread_local_context')); + if Result then begin - FExtensionLoaded_ALC_EXT_thread_local_context := True; - Pointer(alcSetThreadContext) := GetALCExtProc(aDevice, 'alcSetThreadContext', FExtensionLoaded_ALC_EXT_thread_local_context); - Pointer(alcGetThreadContext) := GetALCExtProc(aDevice, 'alcGetThreadContext', FExtensionLoaded_ALC_EXT_thread_local_context); - Result := FExtensionLoaded_ALC_EXT_thread_local_context; + Pointer(_alcSetThreadContext) := GetALCExtProc(aDevice, 'alcSetThreadContext', Result); + Pointer(_alcGetThreadContext) := GetALCExtProc(aDevice, 'alcGetThreadContext', Result); end else - Result := False; + begin + _alcSetThreadContext := NIL; + _alcGetThreadContext := NIL; + end; end; function LoadExt_AL_SOFT_buffer_samples: boolean; @@ -393,18 +389,19 @@ function LoadExt_AL_SOFT_callback_buffer: boolean; function LoadExt_ALC_SOFT_loopback(aDevice: PALCDevice): boolean; begin - Result := FExtensionLoaded_ALC_SOFT_loopback; - if Result then exit; - if _OpenALLib_Handle <> DynLibs.NilHandle then + Result := alcIsExtensionPresent(aDevice, PChar('ALC_SOFT_loopback')); + if Result then begin - FExtensionLoaded_ALC_SOFT_loopback := True; - Pointer(alcLoopbackOpenDeviceSOFT) := GetALCExtProc(aDevice, 'alcLoopbackOpenDeviceSOFT', FExtensionLoaded_ALC_SOFT_loopback); - Pointer(alcIsRenderFormatSupportedSOFT) := GetALCExtProc(aDevice, 'alcIsRenderFormatSupportedSOFT', FExtensionLoaded_ALC_SOFT_loopback); - Pointer(alcRenderSamplesSOFT) := GetALCExtProc(aDevice, 'alcRenderSamplesSOFT', FExtensionLoaded_ALC_SOFT_loopback); - Result := FExtensionLoaded_ALC_SOFT_loopback; + Pointer(_alcLoopbackOpenDeviceSOFT) := GetALCExtProc(aDevice, 'alcLoopbackOpenDeviceSOFT', Result); + Pointer(_alcIsRenderFormatSupportedSOFT) := GetALCExtProc(aDevice, 'alcIsRenderFormatSupportedSOFT', Result); + Pointer(_alcRenderSamplesSOFT) := GetALCExtProc(aDevice, 'alcRenderSamplesSOFT', Result); end else - Result := False; + begin + _alcLoopbackOpenDeviceSOFT := NIL; + _alcIsRenderFormatSupportedSOFT := NIL; + _alcRenderSamplesSOFT := NIL; + end; end; function LoadExt_AL_SOFT_source_latency: boolean; @@ -449,32 +446,32 @@ function LoadExt_AL_SOFT_deferred_updates: boolean; function LoadExt_ALC_SOFT_pause_device(aDevice: PALCDevice): boolean; begin - Result := FExtensionLoaded_ALC_SOFT_pause_device; - if Result then exit; - if _OpenALLib_Handle <> DynLibs.NilHandle then + Result := alcIsExtensionPresent(NIL, PChar('ALC_SOFT_pause_device')); + if Result then begin - FExtensionLoaded_ALC_SOFT_pause_device := True; - Pointer(alcDevicePauseSOFT) := GetALCExtProc(aDevice, 'alcDevicePauseSOFT', FExtensionLoaded_ALC_SOFT_pause_device); - Pointer(alcDeviceResumeSOFT) := GetALCExtProc(aDevice, 'alcDeviceResumeSOFT', FExtensionLoaded_ALC_SOFT_pause_device); - Result := FExtensionLoaded_ALC_SOFT_pause_device; + Pointer(_alcDevicePauseSOFT) := GetALCExtProc(aDevice, 'alcDevicePauseSOFT', Result); + Pointer(_alcDeviceResumeSOFT) := GetALCExtProc(aDevice, 'alcDeviceResumeSOFT', Result); end else - Result := False; + begin + _alcDevicePauseSOFT := NIL; + _alcDeviceResumeSOFT := NIL; + end; end; function LoadExt_ALC_SOFT_HRTF(aDevice: PALCDevice): boolean; begin - Result := FExtensionLoaded_ALC_SOFT_HRTF; - if Result then exit; - if _OpenALLib_Handle <> DynLibs.NilHandle then + Result := alcIsExtensionPresent(aDevice, PChar('ALC_SOFT_HRTF')); + if Result then begin - FExtensionLoaded_ALC_SOFT_HRTF := True; - Pointer(alcGetStringiSOFT) := GetALCExtProc(aDevice, 'alcGetStringiSOFT', FExtensionLoaded_ALC_SOFT_HRTF); - Pointer(alcResetDeviceSOFT) := GetALCExtProc(aDevice, 'alcResetDeviceSOFT', FExtensionLoaded_ALC_SOFT_HRTF); - Result := FExtensionLoaded_ALC_SOFT_HRTF; + Pointer(_alcGetStringiSOFT) := GetALCExtProc(aDevice, 'alcGetStringiSOFT', Result); + Pointer(_alcResetDeviceSOFT) := GetALCExtProc(aDevice, 'alcResetDeviceSOFT', Result); end else - Result := False; + begin + _alcGetStringiSOFT := NIL; + _alcResetDeviceSOFT := NIL; + end; end; function LoadExt_AL_SOFT_source_resampler: boolean; @@ -493,16 +490,15 @@ function LoadExt_AL_SOFT_source_resampler: boolean; function LoadExt_ALC_SOFT_device_clock(aDevice: PALCDevice): boolean; begin - Result := FExtensionLoaded_ALC_SOFT_device_clock; - if Result then exit; - if _OpenALLib_Handle <> DynLibs.NilHandle then + Result := alcIsExtensionPresent(NIL, PChar('ALC_SOFT_device_clock')); + if Result then begin - FExtensionLoaded_ALC_SOFT_device_clock := true; - Pointer(alcGetInteger64vSOFT) := GetALCExtProc(aDevice, 'alcGetInteger64vSOFT', FExtensionLoaded_ALC_SOFT_device_clock); - Result := FExtensionLoaded_ALC_SOFT_device_clock; + Pointer(_alcGetInteger64vSOFT) := GetALCExtProc(aDevice, 'alcGetInteger64vSOFT', Result); end else - Result := False; + begin + _alcGetInteger64vSOFT := NIL; + end; end; function LoadExt_AL_SOFT_events: boolean; @@ -524,16 +520,15 @@ function LoadExt_AL_SOFT_events: boolean; function LoadExt_ALC_SOFT_reopen_device(aDevice: PALCDevice): boolean; begin - Result := FExtensionLoaded_ALC_SOFT_reopen_device; - if Result then exit; - if _OpenALLib_Handle <> DynLibs.NilHandle then + Result := alcIsExtensionPresent(NIL, PChar('ALC_SOFT_reopen_device')); + if Result then begin - FExtensionLoaded_ALC_SOFT_reopen_device := True; - Pointer(alcReopenDeviceSOFT) := GetALCExtProc(aDevice, 'alcReopenDeviceSOFT', FExtensionLoaded_ALC_SOFT_reopen_device); - Result := FExtensionLoaded_ALC_SOFT_reopen_device; + Pointer(_alcReopenDeviceSOFT) := GetALCExtProc(aDevice, 'alcReopenDeviceSOFT', Result); end else - Result := False; + begin + _alcReopenDeviceSOFT := NIL; + end; end;