From 41373861bfc2c613356bcba4f57e854592888ee5 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 1 Sep 2019 03:00:27 +0300 Subject: [PATCH] Sound: OpenAL: Add optional ModPlug loader --- src/engine/e_soundfile_modplug.pas | 191 +++++++++++++++++++++++++++++ src/lib/modplug/modplug.pas | 134 ++++++++++++++++++++ 2 files changed, 325 insertions(+) create mode 100644 src/engine/e_soundfile_modplug.pas create mode 100644 src/lib/modplug/modplug.pas diff --git a/src/engine/e_soundfile_modplug.pas b/src/engine/e_soundfile_modplug.pas new file mode 100644 index 0000000..01fd455 --- /dev/null +++ b/src/engine/e_soundfile_modplug.pas @@ -0,0 +1,191 @@ +(* Copyright (C) Doom 2D: Forever Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *) +{$INCLUDE ../shared/a_modes.inc} +unit e_soundfile_modplug; + +interface + +uses e_soundfile, modplug; + +type + // a module loader that uses libxmp-lite + + TModPlugLoader = class (TSoundLoader) + public + function Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; override; overload; + function Load(FName: string; SStreaming: Boolean): Boolean; override; overload; + function SetPosition(Pos: LongWord): Boolean; override; + function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; override; + function GetAll(var OutPtr: Pointer): LongWord; override; + procedure Free(); override; + private + FFile: PModPlugFile; + end; + + TModPlugLoaderFactory = class (TSoundLoaderFactory) + function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override; + function MatchExtension(FName: string): Boolean; override; + function GetLoader(): TSoundLoader; override; + end; + +implementation + +uses sysutils, utils, e_sound, e_log, classes; + +var + Settings: ModPlug_Settings = ( + mFlags : MODPLUG_ENABLE_OVERSAMPLING or MODPLUG_ENABLE_NOISE_REDUCTION; + mChannels : 2; + mBits : 16; + mFrequency : 44100; + mResamplingMode : MODPLUG_RESAMPLE_LINEAR; + mStereoSeparation : 128; + mMaxMixChannels : 32; + mReverbDepth : 0; + mReverbDelay : 0; + mBassAmount : 0; + mBassRange : 0; + mSurroundDepth : 0; + mSurroundDelay : 0; + mLoopCount : -1; + ); + +(* TModPlugLoaderFactory *) + +function TModPlugLoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean; +var + Mpf: PModPlugFile; +begin + // HACK: there's no "test" function in modplug, so just try to load that shit + Result := False; + + Mpf := ModPlug_Load(Data, Len); + if Mpf = nil then Exit; + ModPlug_Unload(Mpf); + + Result := True; +end; + +function TModPlugLoaderFactory.MatchExtension(FName: string): Boolean; +var + Ext: string; +begin + Ext := GetFilenameExt(FName); + Result := (Ext = '.it') or (Ext = '.xm') or (Ext = '.mod') or (Ext = '.s3m'); +end; + +function TModPlugLoaderFactory.GetLoader(): TSoundLoader; +begin + ModPlug_SetSettings(@Settings); // update settings just in case + Result := TModPlugLoader.Create(); +end; + +(* TModPlugLoader *) + +function TModPlugLoader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; +begin + Result := False; + + FFile := ModPlug_Load(Data, Len); + if FFile = nil then + begin + e_LogWriteln('ModPlug: ERROR: ModPlug_Load failed'); + Exit; + end; + + FFormat.SampleRate := 44100; + FFormat.SampleBits := 16; + FFormat.Channels := 2; + FStreaming := True; // modules are always streaming + + Result := True; +end; + +function TModPlugLoader.Load(FName: string; SStreaming: Boolean): Boolean; +var + S: TStream = nil; + Data: Pointer; + Len: LongInt; +begin + Result := False; + + try + S := openDiskFileRO(FName); + // ayy just read the entire file + Data := GetMem(S.Size); + if Data = nil then + raise Exception.Create('out of memory'); + Len := S.Read(Data^, S.Size); + if Len < 0 then + raise Exception.Create('what the fuck'); + Result := Load(Data, Len, SStreaming) + except + on E: Exception do + e_LogWritefln('ModPlug: ERROR: could not read file `%s`: %s', [FName, E.Message]); + end; + + if Data <> nil then FreeMem(Data); + if S <> nil then S.Free(); +end; + +function TModPlugLoader.SetPosition(Pos: LongWord): Boolean; +begin + Result := False; + if FFile = nil then Exit; + ModPlug_Seek(FFile, Pos); + Result := True; +end; + +function TModPlugLoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord; +var + Cnt: LongInt; +begin + Result := 0; + if FFile = nil then Exit; + + Cnt := ModPlug_Read(FFile, Buf, Len); + if Cnt < 0 then Exit; + + if FLooping and (Cnt < Len) then + begin + // assume it just ended and restart, because modplug only loops if the + // module tells it to + ModPlug_Seek(FFile, 0); + // this used to be Result := Cnt + Read(FFile, Buf + Cnt, Len - Cnt) + // but the difference appears to be negligible + Result := ModPlug_Read(FFile, Buf, Len); + end + else + Result := Len; +end; + +function TModPlugLoader.GetAll(var OutPtr: Pointer): LongWord; +begin + Result := 0; // modules are always streaming, so this don't make sense +end; + +procedure TModPlugLoader.Free(); +begin + if FFile <> nil then + begin + ModPlug_Unload(FFile); + FFile := nil; + end; +end; + +initialization + e_AddSoundLoader(TModPlugLoaderFactory.Create()); +end. diff --git a/src/lib/modplug/modplug.pas b/src/lib/modplug/modplug.pas new file mode 100644 index 0000000..4472895 --- /dev/null +++ b/src/lib/modplug/modplug.pas @@ -0,0 +1,134 @@ +{ + Translation of the libmodplug headers for FreePascal + Copyright (C) 2006 by Ivo Steinmann +} + +(* + * This source code is public domain. + * + * Authors: Kenton Varda (C interface wrapper) + *) + +unit modplug; + +{$MODE OBJFPC} +{$PACKRECORDS C} + +interface + +uses + ctypes; + +{$IFDEF WINDOWS} + {$IFNDEF LIBMODPLUG_PINDOZE_STATIC} + {$DEFINE MP_DYNAMIC} + {$ENDIF} +{$ENDIF} + +{$IFDEF MP_DYNAMIC} +const +{$IF DEFINED(WINDOWS)} + modpluglib = 'libmodplug.dll'; +{$ELSEIF DEFINED(UNIX)} + modpluglib = 'libmodplug.so'; +{$ELSE} + {$MESSAGE ERROR 'MP_DYNAMIC not supported'} +{$IFEND} +{$ELSE} + {$LINKLIB stdc++} // is this necessary? + {$LINKLIB modplug} +{$ENDIF} + + +type + PModPlugFile = Pointer; + ModPlugFile = record + end; + +(* Load a mod file. [data] should point to a block of memory containing the complete + * file, and [size] should be the size of that block. + * Return the loaded mod file on success, or NULL on failure. *) +function ModPlug_Load(data: pointer; size: cint): PModPlugFile; cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF}; + +(* Unload a mod file. *) +procedure ModPlug_Unload(_file: PModPlugFile); cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF}; + +(* Read sample data into the buffer. Returns the number of bytes read. If the end + * of the mod has been reached, zero is returned. *) +function ModPlug_Read(_file: PModPlugFile; buffer: pointer; size: cint): cint; cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF}; + +(* Get the name of the mod. The returned buffer is stored within the ModPlugFile + * structure and will remain valid until you unload the file. *) +function ModPlug_GetName(_file: PModPlugFile): pcchar; cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF}; + +(* Get the length of the mod, in milliseconds. Note that this result is not always + * accurate, especially in the case of mods with loops. *) +function ModPlug_GetLength(_file: PModPlugFile): cint; cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF}; + +(* Seek to a particular position in the song. Note that seeking and MODs don't mix very + * well. Some mods will be missing instruments for a short time after a seek, as ModPlug + * does not scan the sequence backwards to find out which instruments were supposed to be + * playing at that time. (Doing so would be difficult and not very reliable.) Also, + * note that seeking is not very exact in some mods -- especially those for which + * ModPlug_GetLength() does not report the full length. *) +procedure ModPlug_Seek(_file: PModPlugFile; millisecond: cint); cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF}; + + +const +// _ModPlug_Flags + MODPLUG_ENABLE_OVERSAMPLING = 1 shl 0; (* Enable oversampling (highly recommended) *) + MODPLUG_ENABLE_NOISE_REDUCTION = 1 shl 1; (* Enable noise reduction *) + MODPLUG_ENABLE_REVERB = 1 shl 2; (* Enable reverb *) + MODPLUG_ENABLE_MEGABASS = 1 shl 3; (* Enable megabass *) + MODPLUG_ENABLE_SURROUND = 1 shl 4; (* Enable surround sound. *) + +// _ModPlug_ResamplingMode + MODPLUG_RESAMPLE_NEAREST = 0; (* No interpolation (very fast, extremely bad sound quality) *) + MODPLUG_RESAMPLE_LINEAR = 1; (* Linear interpolation (fast, good quality) *) + MODPLUG_RESAMPLE_SPLINE = 2; (* Cubic spline interpolation (high quality) *) + MODPLUG_RESAMPLE_FIR = 3; (* 8-tap fir filter (extremely high quality) *) + +type + PModPlug_Settings = ^ModPlug_Settings; + ModPlug_Settings = record + mFlags : cint; (* One or more of the MODPLUG_ENABLE_* flags above, bitwise-OR'ed *) + mChannels : cint; (* Number of channels - 1 for mono or 2 for stereo *) + mBits : cint; (* Bits per sample - 8, 16, or 32 *) + mFrequency : cint; (* Sampling rate - 11025, 22050, or 44100 *) + mResamplingMode : cint; (* One of MODPLUG_RESAMPLE_*, above *) + mStereoSeparation : cint; (* 1-256 *) + mMaxMixChannels : cint; (* Maximum number of mixing channels, 32-256 *) + mReverbDepth : cint; (* Reverb level 0(quiet)-100(loud) *) + mReverbDelay : cint; (* Reverb delay in ms, usually 40-200ms *) + mBassAmount : cint; (* XBass level 0(quiet)-100(loud) *) + mBassRange : cint; (* XBass cutoff in Hz 10-100 *) + mSurroundDepth : cint; (* Surround level 0(quiet)-100(heavy) *) + mSurroundDelay : cint; (* Surround delay in ms, usually 5-40ms *) + mLoopCount : cint; (* Number of times to loop. Zero prevents looping. -1 loops forever. *) + end; + +(* Get and set the mod decoder settings. All options, except for channels, bits-per-sample, + * sampling rate, and loop count, will take effect immediately. Those options which don't + * take effect immediately will take effect the next time you load a mod. *) +procedure ModPlug_GetSettings(settings: PModPlug_Settings); cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF}; +procedure ModPlug_SetSettings(const settings: PModPlug_Settings); cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF}; + +implementation + +// TODO: why the fuck does this exist here + +(* + +function cppNew(s: cint): pointer; cdecl; public; alias : '_Znaj'; alias : '_Znwj'; +begin + GetMem(Result, s); +end; + +procedure cppDelete(p: pointer); cdecl; public; alias : '_ZdlPv'; alias : '_ZdaPv'; +begin + FreeMem(p); +end; + +*) + +end. -- 2.29.2