diff --git a/game/languages/English.ini b/game/languages/English.ini index b10ae0f76..bbf2ffe06 100644 --- a/game/languages/English.ini +++ b/game/languages/English.ini @@ -35,6 +35,7 @@ OPTION_VALUE_FULL_VID_BG=Full (BG & Video) OPTION_VALUE_GAIN_SOFT=Soft OPTION_VALUE_GAIN_MEDIUM=Medium OPTION_VALUE_GAIN_HARD=Hard +OPTION_VALUE_GAIN_REPLAYGAIN=ReplayGain OPTION_VALUE_AUTO=Auto OPTION_VALUE_SEC=Second diff --git a/game/languages/Language.new b/game/languages/Language.new index 446b99e67..5a0414b67 100644 --- a/game/languages/Language.new +++ b/game/languages/Language.new @@ -35,6 +35,7 @@ ;TODO: OPTION_VALUE_GAIN_SOFT=Soft ;TODO: OPTION_VALUE_GAIN_MEDIUM=Medium ;TODO: OPTION_VALUE_GAIN_HARD=Hard +;TODO: OPTION_VALUE_GAIN_REPLAYGAIN=ReplayGain ;TODO: OPTION_VALUE_AUTO=Auto ;TODO: OPTION_VALUE_SEC=Second diff --git a/src/base/UIni.pas b/src/base/UIni.pas index 207c01661..9a20618d2 100644 --- a/src/base/UIni.pas +++ b/src/base/UIni.pas @@ -88,6 +88,7 @@ TInputDeviceConfig = record TVisualizerOption = (voOff, voWhenNoVideo, voWhenNoVideoAndImage, voOn); TBackgroundMusicOption = (bmoOff, bmoOn); TSongMenuMode = ( smRoulette, smChessboard, smCarousel, smSlotMachine, smSlide, smList, smMosaic); + TMusicAutoGainOption = (magOff, {$IFDEF HaveBASS} magSoft, magMedium, magHard, {$ENDIF} magReplayGain); TIni = class private @@ -371,9 +372,7 @@ TIni = class IVoicePassthrough: array[0..1] of UTF8String = ('Off', 'On'); - IMusicAutoGain: array[0..3] of UTF8String = ('Off', 'Soft', 'Medium', 'Hard'); - IMusicAutoGainVals: array[0..3] of integer = (-1, 0, 1, 2); - + IMusicAutoGain: array[0..{$IFDEF HaveBASS}4{$ELSE}1{$ENDIF}] of UTF8String = ('Off', {$IFDEF HaveBASS} 'Soft', 'Medium', 'Hard', {$ENDIF} 'ReplayGain'); const ISyncTo: array[0..2] of UTF8String = ('Music', 'Lyrics', 'Off'); @@ -497,7 +496,7 @@ TIni = class IVoicePassthroughTranslated: array[0..1] of UTF8String = ('Off', 'On'); - IMusicAutoGainTranslated: array[0..3] of UTF8String = ('Off', 'Soft', 'Medium', 'Hard'); + IMusicAutoGainTranslated: array[0..{$IFDEF HaveBASS}4{$ELSE}1{$ENDIF}] of UTF8String = ('Off', {$IFDEF HaveBASS} 'Soft', 'Medium', 'Hard', {$ENDIF} 'ReplayGain'); ISyncToTranslated: array[0..2] of UTF8String = ('Music', 'Lyrics', 'Off'); @@ -684,9 +683,12 @@ procedure TIni.TranslateOptionValues; IVoicePassthroughTranslated[1] := ULanguage.Language.Translate('OPTION_VALUE_ON'); IMusicAutoGainTranslated[0] := ULanguage.Language.Translate('OPTION_VALUE_OFF'); + {$IFDEF HaveBass} IMusicAutoGainTranslated[1] := ULanguage.Language.Translate('OPTION_VALUE_GAIN_SOFT'); IMusicAutoGainTranslated[2] := ULanguage.Language.Translate('OPTION_VALUE_GAIN_MEDIUM'); IMusicAutoGainTranslated[3] := ULanguage.Language.Translate('OPTION_VALUE_GAIN_HARD'); + {$ENDIF} + IMusicAutoGainTranslated[{$IFDEF HaveBass}4{$ELSE}1{$ENDIF}] := ULanguage.Language.Translate('OPTION_VALUE_GAIN_REPLAYGAIN'); ISyncToTranslated[Ord(stMusic)] := ULanguage.Language.Translate('OPTION_VALUE_MUSIC'); ISyncToTranslated[Ord(stLyrics)] := ULanguage.Language.Translate('OPTION_VALUE_LYRICS'); diff --git a/src/base/UMusic.pas b/src/base/UMusic.pas index e68ee3d0b..219554e5a 100644 --- a/src/base/UMusic.pas +++ b/src/base/UMusic.pas @@ -244,7 +244,7 @@ TSoundFX = class end; - TReplayGain = class(TSoundFX) + TAutoGain = class(TSoundFX) end; type @@ -286,6 +286,7 @@ TAudioSourceStream = class(TAudioProcessingStream) function IsError(): boolean; virtual; abstract; public function ReadData(Buffer: PByte; BufferSize: integer): integer; virtual; abstract; + function GetReplayGain(): single; virtual; property EOF: boolean read IsEOF; property Error: boolean read IsError; @@ -309,6 +310,8 @@ TAudioPlaybackStream = class(TAudioProcessingStream) AvgSyncDiff: double; //** average difference between stream and sync clock SyncSource: TSyncSource; SourceStream: TAudioSourceStream; + RG: single; + RGEnabled: boolean; function GetLatency(): double; virtual; abstract; function GetStatus(): TStreamStatus; virtual; abstract; @@ -317,6 +320,8 @@ TAudioPlaybackStream = class(TAudioProcessingStream) function Synchronize(BufferSize: integer; FormatInfo: TAudioFormatInfo): integer; procedure FillBufferWithFrame(Buffer: PByteArray; BufferSize: integer; Frame: PByteArray; FrameSize: integer); public + constructor Create(); + (** * Opens a SourceStream for playback. * Note that the caller (not the TAudioPlaybackStream) is responsible to @@ -325,7 +330,7 @@ TAudioPlaybackStream = class(TAudioProcessingStream) * guarantees to deliver this method's SourceStream parameter to * the OnClose-handler. Freeing SourceStream at OnClose is allowed. *) - function Open(SourceStream: TAudioSourceStream): boolean; virtual; abstract; + function Open(SourceStream: TAudioSourceStream): boolean; virtual; procedure Play(); virtual; abstract; procedure Pause(); virtual; abstract; @@ -342,8 +347,14 @@ TAudioPlaybackStream = class(TAudioProcessingStream) procedure SetSyncSource(SyncSource: TSyncSource); function GetSourceStream(): TAudioSourceStream; + function GetRGAdjustment(): single; + procedure SetRGAdjustment(Adjustment: single); + procedure EnableReplayGain() virtual; + procedure DisableReplayGain() virtual; + property Status: TStreamStatus read GetStatus; property Volume: single read GetVolume write SetVolume; + property RGAdjustment: single read GetRGAdjustment write SetRGAdjustment; end; TAudioDecodeStream = class(TAudioSourceStream) @@ -1075,9 +1086,29 @@ procedure TAudioProcessingStream.PerformOnClose(); end; end; +{ TAudioSourceStream } + +function TAudioSourceStream.GetReplayGain(): single; +begin + Result := 1.0; +end; { TAudioPlaybackStream } +constructor TAudioPlaybackStream.Create(); +begin + RG := 1.0; + RGEnabled := false; + inherited; +end; + +function TAudioPlaybackStream.Open(SourceStream: TAudioSourceStream): boolean; +begin + if (Ini.MusicAutoGain = ord(magReplayGain)) then + RGAdjustment := SourceStream.GetReplayGain(); + Result := true; +end; + function TAudioPlaybackStream.GetSourceStream(): TAudioSourceStream; begin Result := SourceStream; @@ -1209,6 +1240,32 @@ procedure TAudioPlaybackStream.FillBufferWithFrame(Buffer: PByteArray; BufferSiz Move(Frame[0], Buffer[i*FrameSize], FrameSize); end; +procedure TAudioPlaybackStream.SetRGAdjustment(Adjustment: single); +begin + if ((Adjustment > 0.0) AND (Adjustment <= 1.0)) then + begin + RG := Adjustment; + end; +end; + +function TAudioPlaybackStream.GetRGAdjustment(): single; +begin + if (RGEnabled) then + Result := RG + else + Result := 1.0; +end; + +procedure TAudioPlaybackStream.EnableReplayGain(); +begin + RGEnabled := true; +end; + +procedure TAudioPlaybackStream.DisableReplayGain(); +begin + RGEnabled := false; +end; + { TAudioVoiceStream } function TAudioVoiceStream.Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean; diff --git a/src/lib/ffmpeg-5.0/avformat.pas b/src/lib/ffmpeg-5.0/avformat.pas index 5632778a8..bf2ff88a6 100644 --- a/src/lib/ffmpeg-5.0/avformat.pas +++ b/src/lib/ffmpeg-5.0/avformat.pas @@ -80,6 +80,20 @@ TAVFormatContext = record we_do_not_use_packet_size: cuint; we_do_not_use_max_delay: cint; flags: cint; + we_do_not_use_probesize: cint64; + we_do_not_use_max_analyze_duration: cint64; + we_do_not_use_key: pointer; + we_do_not_use_keylen: cint; + we_do_not_use_nb_programs: cuint; + we_do_not_use_programs: pointer; + we_do_not_use_video_codec_id: cenum; + we_do_not_use_audio_codec_id: cenum; + we_do_not_use_subtitle_codec_id: cenum; + we_do_not_use_max_index_size: cuint; + we_do_not_use_max_picture_buffer: cuint; + we_do_not_use_nb_chapters: cuint; + we_do_not_use_chapters: pointer; + metadata: PAVDictionary; do_not_instantiate_this_record: incomplete_record; end; TAVStream = record @@ -93,7 +107,7 @@ TAVStream = record we_do_not_use_disposition: cint; we_do_not_use_discard: cenum; we_do_not_use_sample_aspect_ratio: TAVRational; - we_do_not_use_metadata: PAVDictionary; + metadata: PAVDictionary; we_do_not_use_avg_frame_rate: TAVRational; we_do_not_use_atached_pic: TAVPacket; we_do_not_use_side_data: pointer; diff --git a/src/lib/ffmpeg-5.0/avutil.pas b/src/lib/ffmpeg-5.0/avutil.pas index 3aa8686df..fc37677c6 100644 --- a/src/lib/ffmpeg-5.0/avutil.pas +++ b/src/lib/ffmpeg-5.0/avutil.pas @@ -154,6 +154,11 @@ TAVFrame = record TAVDictionary = record do_not_instantiate_this_record: incomplete_record; end; + PAVDictionaryEntry = ^TAVDictionaryEntry; + TAVDictionaryEntry = record + key: PAnsiChar; + value: PAnsiChar; + end; procedure av_free(ptr: pointer); cdecl; external av__util; procedure av_freep(ptr: pointer); cdecl; external av__util; function av_malloc(size: csize_t): pointer; cdecl; external av__util; @@ -169,6 +174,7 @@ function av_samples_alloc(var audio_data: pcuint8; linesize: pcint; nb_channels: function av_get_packed_sample_fmt(sample_fmt: TAVSampleFormat): TAVSampleFormat; cdecl; external av__util; function av_get_bytes_per_sample(sample_fmt: TAVSampleFormat): cint; cdecl; external av__util; procedure av_log_set_level(level: cint); cdecl; external av__util; +function av_dict_get(m: PAVDictionary; const key: PAnsiChar; prev: PAVDictionaryEntry; flags: cint): PAVDictionaryEntry; cdecl; external av__util; implementation function AVERROR(e: cint): cint; {$IFDEF HasInline}inline;{$ENDIF} begin diff --git a/src/lib/ffmpeg-6.0/avformat.pas b/src/lib/ffmpeg-6.0/avformat.pas index 6fa876015..88fc6997a 100644 --- a/src/lib/ffmpeg-6.0/avformat.pas +++ b/src/lib/ffmpeg-6.0/avformat.pas @@ -75,6 +75,20 @@ TAVFormatContext = record we_do_not_use_packet_size: cuint; we_do_not_use_max_delay: cint; flags: cint; + we_do_not_use_probesize: cint64; + we_do_not_use_max_analyze_duration: cint64; + we_do_not_use_key: pointer; + we_do_not_use_keylen: cint; + we_do_not_use_nb_programs: cuint; + we_do_not_use_programs: pointer; + we_do_not_use_video_codec_id: cenum; + we_do_not_use_audio_codec_id: cenum; + we_do_not_use_subtitle_codec_id: cenum; + we_do_not_use_max_index_size: cuint; + we_do_not_use_max_picture_buffer: cuint; + we_do_not_use_nb_chapters: cuint; + we_do_not_use_chapters: pointer; + metadata: PAVDictionary; do_not_instantiate_this_record: incomplete_record; end; TAVStream = record @@ -90,7 +104,7 @@ TAVStream = record we_do_not_use_disposition: cint; we_do_not_use_discard: cenum; we_do_not_use_sample_aspect_ratio: TAVRational; - we_do_not_use_metadata: PAVDictionary; + metadata: PAVDictionary; we_do_not_use_avg_frame_rate: TAVRational; we_do_not_use_attached_pic: TAVPacket; we_do_not_use_side_data: pointer; diff --git a/src/lib/ffmpeg-6.0/avutil.pas b/src/lib/ffmpeg-6.0/avutil.pas index 082a148d2..57821c5e3 100644 --- a/src/lib/ffmpeg-6.0/avutil.pas +++ b/src/lib/ffmpeg-6.0/avutil.pas @@ -154,6 +154,11 @@ TAVFrame = record TAVDictionary = record do_not_instantiate_this_record: incomplete_record; end; + PAVDictionaryEntry = ^TAVDictionaryEntry; + TAVDictionaryEntry = record + key: PAnsiChar; + value: PAnsiChar; + end; procedure av_free(ptr: pointer); cdecl; external av__util; procedure av_freep(ptr: pointer); cdecl; external av__util; function av_malloc(size: csize_t): pointer; cdecl; external av__util; @@ -169,6 +174,7 @@ function av_samples_alloc(var audio_data: pcuint8; linesize: pcint; nb_channels: function av_get_packed_sample_fmt(sample_fmt: TAVSampleFormat): TAVSampleFormat; cdecl; external av__util; function av_get_bytes_per_sample(sample_fmt: TAVSampleFormat): cint; cdecl; external av__util; procedure av_log_set_level(level: cint); cdecl; external av__util; +function av_dict_get(m: PAVDictionary; const key: PAnsiChar; prev: PAVDictionaryEntry; flags: cint): PAVDictionaryEntry; cdecl; external av__util; implementation function AVERROR(e: cint): cint; {$IFDEF HasInline}inline;{$ENDIF} begin diff --git a/src/lib/ffmpeg-7.0/avformat.pas b/src/lib/ffmpeg-7.0/avformat.pas index 85a82a7e7..04c029726 100644 --- a/src/lib/ffmpeg-7.0/avformat.pas +++ b/src/lib/ffmpeg-7.0/avformat.pas @@ -79,6 +79,17 @@ TAVFormatContext = record we_do_not_use_packet_size: cuint; we_do_not_use_max_delay: cint; flags: cint; + we_do_not_use_probesize: cint64; + we_do_not_use_max_analyze_duration: cint64; + we_do_not_use_key: pointer; + we_do_not_use_keylen: cint; + we_do_not_use_nb_programs: cuint; + we_do_not_use_programs: pointer; + we_do_not_use_video_codec_id: cenum; + we_do_not_use_audio_codec_id: cenum; + we_do_not_use_subtitle_codec_id: cenum; + we_do_not_use_data_codec_id: cenum; + metadata: PAVDictionary; do_not_instantiate_this_record: incomplete_record; end; TAVStream = record @@ -94,7 +105,7 @@ TAVStream = record we_do_not_use_disposition: cint; we_do_not_use_discard: cenum; we_do_not_use_sample_aspect_ratio: TAVRational; - we_do_not_use_metadata: PAVDictionary; + metadata: PAVDictionary; we_do_not_use_avg_frame_rate: TAVRational; we_do_not_use_attached_pic: TAVPacket; we_do_not_use_side_data: pointer; diff --git a/src/lib/ffmpeg-7.0/avutil.pas b/src/lib/ffmpeg-7.0/avutil.pas index 6712ce6f5..6e1ccf136 100644 --- a/src/lib/ffmpeg-7.0/avutil.pas +++ b/src/lib/ffmpeg-7.0/avutil.pas @@ -163,6 +163,11 @@ TAVChannelLayout = record TAVDictionary = record do_not_instantiate_this_record: incomplete_record; end; + PAVDictionaryEntry = ^TAVDictionaryEntry; + TAVDictionaryEntry = record + key: PAnsiChar; + value: PAnsiChar; + end; procedure av_channel_layout_default(ch_layout: PAVChannelLayout; nb_channels: cint); cdecl; external av__util; function av_channel_layout_from_string(channel_layout: PAVChannelLayout; str: PAnsiChar): cint; cdecl; external av__util; procedure av_free(ptr: pointer); cdecl; external av__util; @@ -179,6 +184,7 @@ function av_opt_set_sample_fmt(obj: pointer; name: PAnsiChar; fmt: TAVSampleForm function av_get_packed_sample_fmt(sample_fmt: TAVSampleFormat): TAVSampleFormat; cdecl; external av__util; function av_get_bytes_per_sample(sample_fmt: TAVSampleFormat): cint; cdecl; external av__util; procedure av_log_set_level(level: cint); cdecl; external av__util; +function av_dict_get(m: PAVDictionary; const key: PAnsiChar; prev: PAVDictionaryEntry; flags: cint): PAVDictionaryEntry; cdecl; external av__util; implementation function AVERROR(e: cint): cint; {$IFDEF HasInline}inline;{$ENDIF} begin diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 91fa9f4f1..4099c97df 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -88,6 +88,9 @@ implementation {$DEFINE UseFrameDecoderAPI} {$ENDIF} {$ENDIF} +{$IF LIBAVCODEC_VERSION_MAJOR >= 59} + {$DEFINE HaveMetadata} +{$ENDIF} const MAX_AUDIOQ_SIZE = (5 * 16 * 1024); @@ -188,6 +191,9 @@ TFFmpegDecodeStream = class(TAudioDecodeStream) procedure ResumeDecoderUnlocked(); procedure PauseDecoder(); procedure ResumeDecoder(); + {$IFDEF HaveMetadata} + function GetReplayGain(): single; override; + {$IFEND} public constructor Create(); destructor Destroy(); override; @@ -1088,6 +1094,79 @@ procedure TFFmpegDecodeStream.ResumeDecoder(); SDL_UnlockMutex(fStateLock); end; +{$IFDEF HaveMetadata} +function TFFMpegDecodeStream.GetReplayGain(): single; +var + Metadata: PAVDictionary; + DictEntry: PAVDictionaryEntry; + TagStr: AnsiString; + DecibelVal: Extended; + Tokens: TStringDynArray; + IsOpus: boolean; +begin + Result := 1.0; + + (* FFmpeg stores metadata at the stream level for the Ogg container, and + * at the container level for all other containers *) + if (CompareStr(AnsiString(fFormatCtx^.iformat^.name), 'ogg') = 0) then + begin + if (fAudioStream = nil) then + Exit; + Metadata := fAudioStream^.metadata; + end + else + begin + if (fFormatCtx = nil) then + Exit; + Metadata := fFormatCtx^.metadata; + end; + if (Metadata = nil) then + Exit; + + DictEntry := av_dict_get(Metadata, 'REPLAYGAIN_TRACK_GAIN', nil, 0); + if (DictEntry <> nil) then + begin + TagStr := DictEntry^.value; + Tokens := SplitString(TagStr, 0); + if (System.Length(Tokens) > 0) then + begin + try + DecibelVal := StrToFloat(Tokens[0]); + Result := power(10.0,(DecibelVal / 20.0)); + Log.LogInfo('Parsed ReplayGain tag: ' + floatToStr(DecibelVal) + + ' dB (' + floatToStr(Result) + ')', 'UAudio_FFmpeg'); + except + Log.LogError('Failed to parse ReplayGain tag: ' + TagStr); + end; + end; + end + + (* Opus files support another loudness normalization scheme that is completely + * separate from ReplayGain. Values are stored in the 'R128_TRACK_GAIN' tag, + * encoded as a Q7.8 fixed-point number. To obtain the gain in decibels, + * divide by 256. See RFC 7845 for more information: + * + * https://datatracker.ietf.org/doc/html/rfc7845 + *) + else if (CompareStr(fFilename.GetExtension.ToUTF8, '.opus') = 0) then + begin + DictEntry := av_dict_get(Metadata, 'R128_TRACK_GAIN', nil, 0); + if (DictEntry <> nil) then + begin + TagStr := DictEntry^.value; + try + DecibelVal := StrToFloat(TagStr) / 256.0; + Result := power(10.0,(DecibelVal / 20.0)); + Log.LogInfo('Parsed R128 tag ' + floatToStr(DecibelVal) + + ' dB (' + floatToStr(Result) + ')', 'UAudio_FFmpeg'); + except + Log.LogError('Failed to parse R128 tag: ' + TagStr); + end; + end; + end; +end; +{$IFEND} + procedure TFFmpegDecodeStream.FlushCodecBuffers(); {$IF LIBAVFORMAT_VERSION >= 59000000} var diff --git a/src/media/UAudioPlaybackBase.pas b/src/media/UAudioPlaybackBase.pas index 87f698f85..c10d48f37 100644 --- a/src/media/UAudioPlaybackBase.pas +++ b/src/media/UAudioPlaybackBase.pas @@ -42,7 +42,7 @@ interface SysUtils; type - FReplayGain = class of TReplayGain; + FAutoGain = class of TAutoGain; TAudioPlaybackBase = class(TInterfacedObject, IAudioPlayback) protected @@ -50,7 +50,7 @@ TAudioPlaybackBase = class(TInterfacedObject, IAudioPlayback) OutputDeviceList: TAudioOutputDeviceList; MusicStream: TAudioPlaybackStream; - IReplayGain: FReplayGain; + IAutoGain: FAutoGain; function CreatePlaybackStream(): TAudioPlaybackStream; virtual; abstract; procedure ClearOutputDeviceList(); @@ -157,8 +157,9 @@ function TAudioPlaybackBase.Open(const Filename: IPath): boolean; Result := false; Exit; end; - - if assigned(IReplayGain) and IReplayGain.CanEnable then MusicStream.AddSoundFX(IReplayGain.Create()); + if (Ini.MusicAutoGain = ord(magReplayGain)) then + MusicStream.EnableReplayGain(); + if assigned(IAutoGain) and IAutoGain.CanEnable then MusicStream.AddSoundFX(IAutoGain.Create()); Result := true; end; diff --git a/src/media/UAudioPlayback_Bass.pas b/src/media/UAudioPlayback_Bass.pas index b8453c0af..dffa03621 100644 --- a/src/media/UAudioPlayback_Bass.pas +++ b/src/media/UAudioPlayback_Bass.pas @@ -59,9 +59,11 @@ TBassPlaybackStream = class(TAudioPlaybackStream) Handle: HSTREAM; NeedsRewind: boolean; PausedSeek: boolean; // true if a seek was performed in pause state + fVolume: single; procedure Reset(); function IsEOF(): boolean; + procedure SetVolumeBASS(); protected function GetLatency(): double; override; function GetLoop(): boolean; override; @@ -94,6 +96,8 @@ TBassPlaybackStream = class(TAudioPlaybackStream) function GetAudioFormatInfo(): TAudioFormatInfo; override; function ReadData(Buffer: PByte; BufferSize: integer): integer; + procedure EnableReplayGain() override; + procedure DisableReplayGain() override; property EOF: boolean READ IsEOF; end; @@ -137,7 +141,7 @@ TBassOutputDevice = class(TAudioOutputDevice) BassDeviceID: DWORD; // DeviceID used by BASS end; - TReplayGain_Bass = class(TReplayGain) + TAutoGain_Bass = class(TAutoGain) private procedure Init(); override; public @@ -155,7 +159,7 @@ constructor TAudioPlayback_Bass.Create(); begin inherited; - IReplayGain := TReplayGain_Bass; + IAutoGain := TAutoGain_Bass; end; { TBassPlaybackStream } @@ -250,6 +254,18 @@ function TBassPlaybackStream.ReadData(Buffer: PByte; BufferSize: integer): integ end; end; +procedure TBassPlaybackStream.EnableReplayGain(); +begin + inherited; + SetVolumeBASS(); +end; + +procedure TBassPlaybackStream.DisableReplayGain(); +begin + inherited; + SetVolumeBASS(); +end; + constructor TBassPlaybackStream.Create(); begin inherited; @@ -293,6 +309,7 @@ function TBassPlaybackStream.Open(SourceStream: TAudioSourceStream): boolean; 'TBassPlaybackStream.Open'); Exit; end; + inherited; Result := true; end; @@ -317,6 +334,7 @@ procedure TBassPlaybackStream.Reset(); Close(); NeedsRewind := false; PausedSeek := false; + fVolume := 1.0; end; procedure TBassPlaybackStream.Play(); @@ -356,13 +374,15 @@ procedure TBassPlaybackStream.FadeIn(Time: real; TargetVolume: single); // start stream Play(); // start fade-in: slide from fadeStart- to fadeEnd-volume in FadeInTime - BASS_ChannelSlideAttribute(Handle, BASS_ATTRIB_VOL, TargetVolume, Trunc(Time * 1000)); + BASS_ChannelSlideAttribute(Handle, BASS_ATTRIB_VOL, TargetVolume * RGAdjustment, Trunc(Time * 1000)); + fVolume := TargetVolume; end; procedure TBassPlaybackStream.Fade(Time: real; TargetVolume: single); begin // start fade-in: slide from fadeStart- to fadeEnd-volume in FadeInTime - BASS_ChannelSlideAttribute(Handle, BASS_ATTRIB_VOL, TargetVolume, Trunc(Time * 1000)); + BASS_ChannelSlideAttribute(Handle, BASS_ATTRIB_VOL, TargetVolume * RGAdjustment, Trunc(Time * 1000)); + fVolume := TargetVolume; end; procedure TBassPlaybackStream.Pause(); @@ -394,17 +414,8 @@ function TBassPlaybackStream.GetLatency(): double; end; function TBassPlaybackStream.GetVolume(): single; -var - lVolume: single; begin - if (not BASS_ChannelGetAttribute(Handle, BASS_ATTRIB_VOL, lVolume)) then - begin - Log.LogError('BASS_ChannelGetAttribute: ' + BassCore.ErrorGetString(), - 'TBassPlaybackStream.GetVolume'); - Result := 0; - Exit; - end; - Result := Round(lVolume); + Result := fVolume; end; procedure TBassPlaybackStream.SetVolume(Volume: single); @@ -414,8 +425,13 @@ procedure TBassPlaybackStream.SetVolume(Volume: single); Volume := 0; if Volume > 1.0 then Volume := 1.0; - // set volume - BASS_ChannelSetAttribute(Handle, BASS_ATTRIB_VOL, Volume); + fVolume := Volume; + SetVolumeBASS(); +end; + +procedure TBassPlaybackStream.SetVolumeBASS(); +begin + BASS_ChannelSetAttribute(Handle, BASS_ATTRIB_VOL, fVolume * RGAdjustment); end; function TBassPlaybackStream.GetPosition: real; @@ -787,23 +803,23 @@ function TAudioPlayback_Bass.GetLatency(): double; Result := 0; end; -{ TReplayGain } +{ TAutoGain } -class function TReplayGain_Bass.CanEnable(): boolean; +class function TAutoGain_Bass.CanEnable(): boolean; begin - Result := (Ini.MusicAutoGain > 0); + Result := (TMusicAutoGainOption(Ini.MusicAutoGain) in [magSoft, magMedium, magHard]); end; -procedure TReplayGain_Bass.Init(); +procedure TAutoGain_Bass.Init(); var FxGain: BASS_BFX_DAMP; - I: integer; + I: TMusicAutoGainOption; begin - if Ini.MusicAutoGain < 0 then Exit; - I := IMusicAutoGainVals[Ini.MusicAutoGain]; + if (not CanEnable()) then Exit; + I := TMusicAutoGainOption(Ini.MusicAutoGain); case I of - 0: // soft preset + magSoft: // soft preset begin FxGain.fTarget := 0.92; // target volume level [0<......1] linear FxGain.fQuiet := 0.02; // quiet volume level [0.......1] linear @@ -812,7 +828,7 @@ procedure TReplayGain_Bass.Init(); FxGain.fDelay := 0.5; // delay in seconds before increasing level FxGain.lChannel := BASS_BFX_CHANALL; end; - 1: // medium preset + magMedium: // medium preset begin FxGain.fTarget := 0.94; // target volume level [0<......1] linear FxGain.fQuiet := 0.03; // quiet volume level [0.......1] linear @@ -821,7 +837,7 @@ procedure TReplayGain_Bass.Init(); FxGain.fDelay := 0.35; // delay in seconds before increasing level FxGain.lChannel := BASS_BFX_CHANALL; end; - 2: // hard preset + magHard: // hard preset begin FxGain.fTarget := 0.98; // target volume level [0<......1] linear FxGain.fQuiet := 0.04; // quiet volume level [0.......1] linear @@ -835,17 +851,17 @@ procedure TReplayGain_Bass.Init(); BASS_FXSetParameters(DWORD(EngineData), @FxGain); end; -function TReplayGain_Bass.GetName: String; +function TAutoGain_Bass.GetName: String; begin result := 'BASS_FX Dynamic Amplification'; end; -function TReplayGain_Bass.GetType(): DWORD; +function TAutoGain_Bass.GetType(): DWORD; begin Result := BASS_FX_BFX_DAMP; end; -function TReplayGain_Bass.GetPriority(): LongInt; +function TAutoGain_Bass.GetPriority(): LongInt; begin Result := 3; end; diff --git a/src/media/UAudioPlayback_SoftMixer.pas b/src/media/UAudioPlayback_SoftMixer.pas index c55820824..eb7a4baa7 100644 --- a/src/media/UAudioPlayback_SoftMixer.pas +++ b/src/media/UAudioPlayback_SoftMixer.pas @@ -317,7 +317,7 @@ function TAudioMixerStream.ReadData(Buffer: PByteArray; BufferSize: integer): in begin // mix stream-data with mixer-buffer // Note: use Self.appVolume instead of Self.Volume to prevent recursive locking - Engine.MixBuffers(Buffer, MixerBuffer, Size, AppVolume * Stream.Volume); + Engine.MixBuffers(Buffer, MixerBuffer, Size, AppVolume * Stream.Volume * Stream.RGAdjustment); end; end; @@ -402,6 +402,7 @@ function TGenericPlaybackStream.Open(SourceStream: TAudioSourceStream): boolean; GetMem(SourceBuffer, SourceBufferSize); fVolume := 1.0; + inherited; Result := true; end;