From 01f7c9f6fb35b2b193cb967c2d881ab9fc5b6bcc Mon Sep 17 00:00:00 2001 From: fgsfds Date: Mon, 2 Sep 2019 19:16:17 +0300 Subject: [PATCH] Sound: OpenAL: Add MIDI support via FluidSynth --- src/engine/e_sound_al.inc | 6 +- src/engine/e_soundfile_fluid.pas | 248 ++++++++++++++++++++++++++++++ src/game/Doom2DF.lpr | 4 + src/lib/fluidsynth/fluidsynth.pas | 75 +++++++++ src/lib/modplug/modplug.pas | 4 +- src/lib/mpg123/mpg123.pas | 2 +- src/lib/openal/al.pas | 2 +- src/lib/xmp/xmp.pas | 4 +- 8 files changed, 337 insertions(+), 8 deletions(-) create mode 100644 src/engine/e_soundfile_fluid.pas create mode 100644 src/lib/fluidsynth/fluidsynth.pas diff --git a/src/engine/e_sound_al.inc b/src/engine/e_sound_al.inc index f6ec993..a5c8364 100644 --- a/src/engine/e_sound_al.inc +++ b/src/engine/e_sound_al.inc @@ -288,6 +288,8 @@ begin exit; end; + Loader.Looping := e_SoundsArray[find_id].Loops; + if not Loader.Load(FileName, e_SoundsArray[find_id].isMusic) then begin e_LogWritefln('Could not load sound `%s`', [FileName]); @@ -329,7 +331,6 @@ begin end else begin - Loader.Looping := e_SoundsArray[find_id].Loops; e_SoundsArray[find_id].alBuffer := 0; e_SoundsArray[find_id].Loader := Loader; end; @@ -362,6 +363,8 @@ begin exit; end; + Loader.Looping := e_SoundsArray[find_id].Loops; + if not Loader.Load(pData, LongWord(Length), e_SoundsArray[find_id].isMusic) then begin e_LogWritefln('Could not load sound `%p`', [pData]); @@ -403,7 +406,6 @@ begin end else begin - Loader.Looping := e_SoundsArray[find_id].Loops; e_SoundsArray[find_id].alBuffer := 0; e_SoundsArray[find_id].Loader := Loader; end; diff --git a/src/engine/e_soundfile_fluid.pas b/src/engine/e_soundfile_fluid.pas new file mode 100644 index 0000000..da79055 --- /dev/null +++ b/src/engine/e_soundfile_fluid.pas @@ -0,0 +1,248 @@ +(* 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_fluid; + +interface + +uses e_soundfile, fluidsynth; + +type + // a midi loader that uses fluidsynth + + TFluidLoader = 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 + FSynth: pfluid_synth_t; + FPlayer: pfluid_player_t; + end; + + TFluidLoaderFactory = class (TSoundLoaderFactory) + public + function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override; + function MatchExtension(FName: string): Boolean; override; + function GetLoader(): TSoundLoader; override; + end; + +const + DEFAULT_SOUNDFONT = 'data/soundfont.sf2'; + +var + e_Soundfont: string = ''; + +implementation + +uses sysutils, utils, e_sound, e_log, ctypes{$IFDEF WINDOWS}, windirs{$ENDIF}; + +var + FluidSettings: pfluid_settings_t = nil; + +function FindDefaultSoundfont(): string; +{$IFDEF WINDOWS} +var + SfNames: array [0..1] of string = ( + // creative soundfonts + 'ct4mgm.sf2', + 'ct2mgm.sf2' + // gm.dls unsupported + ); + I: Integer; + SysDir, S: string; +begin + SysDir := GetWindowsSpecialDir(CSIDL_SYSTEM, False); + for I := Low(SfNames) to High(SfNames) do + begin + S := SysDir + SfNames[I]; + if FileExists(S) then + begin + e_LogWriteln('FluidSynth: Found system soundfont ' + S); + Result := S; + exit; + end; + end; + Result := DEFAULT_SOUNDFONT; +end; +{$ELSE} +begin + Result := DEFAULT_SOUNDFONT; +end; +{$ENDIF} + +(* TFluidLoaderFactory *) + +function TFluidLoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean; +var + P: PLongWord; +const + MIDIHDR = $6468544D; // 'MThd' +begin + Result := False; + if Len < 14 then Exit; // the header is at least 4+4+6 bytes + P := Data; + Result := ((P+0)^ = MIDIHDR) and ((P+1)^ <> 0); // header length is not 0 +end; + +function TFluidLoaderFactory.MatchExtension(FName: string): Boolean; +var + Ext: string; +begin + Ext := GetFilenameExt(FName); + Result := (Ext = '.mid') or (Ext = '.midi'); +end; + +function TFluidLoaderFactory.GetLoader(): TSoundLoader; +begin + if e_Soundfont = '' then e_Soundfont := FindDefaultSoundfont(); + Result := TFluidLoader.Create(); +end; + +(* TFluidLoader *) + +function TFluidLoader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; +var + Ret: cint; +begin + Result := False; + + try + FSynth := new_fluid_synth(FluidSettings); + if FSynth = nil then + raise Exception.Create('new_fluid_synth failed'); + Ret := fluid_synth_sfload(FSynth, PChar(e_Soundfont), 1); + if Ret = FLUID_FAILED then + raise Exception.Create('fluid_synth_sfload failed'); + FPlayer := new_fluid_player(FSynth); + if FPlayer = nil then + raise Exception.Create('new_fluid_player failed'); + Ret := fluid_player_add_mem(FPlayer, Data, Len); + if Ret = FLUID_FAILED then + raise Exception.Create('fluid_player_add failed'); + fluid_player_play(FPlayer); + except + on E: Exception do + begin + e_LogWriteln('FluidSynth: Load(Data) failed: ' + E.Message); + if FPlayer <> nil then delete_fluid_player(FPlayer); + if FSynth <> nil then delete_fluid_synth(FSynth); + FPlayer := nil; + FSynth := nil; + Exit; + end; + end; + + if FLooping then + fluid_player_set_loop(FPlayer, -1); + FFormat.SampleRate := 44100; + FFormat.SampleBits := 16; + FFormat.Channels := 2; + FStreaming := True; + + Result := True; +end; + +function TFluidLoader.Load(FName: string; SStreaming: Boolean): Boolean; +var + Ret: cint; +begin + Result := False; + + try + FSynth := new_fluid_synth(FluidSettings); + if FSynth = nil then + raise Exception.Create('new_fluid_synth failed'); + Ret := fluid_synth_sfload(FSynth, PChar(e_Soundfont), 1); + if Ret = FLUID_FAILED then + raise Exception.Create('fluid_synth_sfload failed'); + FPlayer := new_fluid_player(FSynth); + if FPlayer = nil then + raise Exception.Create('new_fluid_player failed'); + Ret := fluid_player_add(FPlayer, PChar(FName)); + if Ret = FLUID_FAILED then + raise Exception.Create('fluid_player_add failed'); + fluid_player_play(FPlayer); + except + on E: Exception do + begin + e_LogWriteln('FluidSynth: Load(Data) failed: ' + E.Message); + if FPlayer <> nil then delete_fluid_player(FPlayer); + if FSynth <> nil then delete_fluid_synth(FSynth); + FPlayer := nil; + FSynth := nil; + Exit; + end; + end; + + if FLooping then + fluid_player_set_loop(FPlayer, -1); + FFormat.SampleRate := 44100; + FFormat.SampleBits := 16; + FFormat.Channels := 2; + FStreaming := True; + + Result := True; +end; + +function TFluidLoader.SetPosition(Pos: LongWord): Boolean; +begin + Result := False; // unsupported? +end; + +function TFluidLoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord; +var + Ret: cint; +begin + Result := 0; + if (FSynth = nil) or (FPlayer = nil) then Exit; + Ret := fluid_synth_write_s16(FSynth, Len div 4, Buf, 0, 2, Buf, 1, 2); + if Ret = FLUID_OK then Result := Len; +end; + +function TFluidLoader.GetAll(var OutPtr: Pointer): LongWord; +begin + Result := 0; // midis are always streaming, so this don't make sense +end; + +procedure TFluidLoader.Free(); +begin + if FPlayer <> nil then delete_fluid_player(FPlayer); + if FSynth <> nil then delete_fluid_synth(FSynth); + FPlayer := nil; + FSynth := nil; +end; + +initialization + FluidSettings := new_fluid_settings(); + if FluidSettings <> nil then + begin + fluid_settings_setint(FluidSettings, PChar('synth.midi-channels'), 16); + fluid_settings_setint(FluidSettings, PChar('synth.cpu-cores'), 0); + fluid_settings_setnum(FluidSettings, PChar('synth.sample-rate'), 44100); + fluid_settings_setnum(FluidSettings, PChar('synth.gain'), 1); + fluid_settings_setstr(FluidSettings, PChar('player.timing-source'), PChar('sample')); + e_AddSoundLoader(TFluidLoaderFactory.Create()); + end; +finalization + if FluidSettings <> nil then + delete_fluid_settings(FluidSettings); +end. + diff --git a/src/game/Doom2DF.lpr b/src/game/Doom2DF.lpr index 75118f3..9277239 100644 --- a/src/game/Doom2DF.lpr +++ b/src/game/Doom2DF.lpr @@ -63,6 +63,10 @@ uses AL in '../lib/openal/al.pas', e_soundfile in '../engine/e_soundfile.pas', e_soundfile_wav in '../engine/e_soundfile_wav.pas', + {$IFDEF USE_FLUIDSYNTH} + fluidsynth in '../lib/fluidsynth/fluidsynth.pas', + e_soundfile_fluid in '../engine/e_soundfile_fluid.pas', + {$ENDIF} {$IFDEF USE_MODPLUG} modplug in '../lib/modplug/modplug.pas', e_soundfile_modplug in '../engine/e_soundfile_modplug.pas', diff --git a/src/lib/fluidsynth/fluidsynth.pas b/src/lib/fluidsynth/fluidsynth.pas new file mode 100644 index 0000000..84b9586 --- /dev/null +++ b/src/lib/fluidsynth/fluidsynth.pas @@ -0,0 +1,75 @@ +unit fluidsynth; + +{$MODE OBJFPC}{$H+} + +interface + +uses + ctypes; + +{$IFDEF FPC} +{$PACKRECORDS C} +{$ENDIF} + +{$IF 0} + {$IFNDEF LIBFLUIDSYNTH_WINDOZE_STATIC} + {$DEFINE FS_DYNAMIC} + {$ENDIF} +{$ENDIF} + +{$IF DEFINED(FS_DYNAMIC)} +const +{$IF DEFINED(WINDOWS)} + fluidlib = 'libfluidsynth.dll'; +{$ELSEIF DEFINED(UNIX)} + fluidlib = 'libfluidsynth.so'; +{$ELSE} + {$MESSAGE ERROR 'FLUIDSYNTH_DYNAMIC not supported'} +{$IFEND} +{$ELSE} + {$LINKLIB libfluidsynth.a} +{$ENDIF} + +const + FLUID_OK = 0; + FLUID_FAILED = -1; + +type + pfluid_settings_t = pointer; + pfluid_synth_t = pointer; + pfluid_player_t = pointer; + pfluid_sfont_t = pointer; + pfluid_sfloader_t = pointer; + +function fluid_version_str(): pchar; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +procedure fluid_version(major, minor, patch: pcint); cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; + +function new_fluid_settings(): pfluid_settings_t; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +procedure delete_fluid_settings(s: pfluid_settings_t); cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; + +function fluid_settings_setstr(s: pfluid_settings_t; key, val: pchar): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_settings_getstr(s: pfluid_settings_t; key: pchar; var val: pchar): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_settings_setnum(s: pfluid_settings_t; key: pchar; val: cdouble): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_settings_getnum(s: pfluid_settings_t; key: pchar; var val: cdouble): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_settings_setint(s: pfluid_settings_t; key: pchar; val: cint): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_settings_getint(s: pfluid_settings_t; key: pchar; var val: cint): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; + +function new_fluid_synth(settings: pfluid_settings_t): pfluid_synth_t; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function delete_fluid_synth(synth: pfluid_synth_t): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_synth_get_settings(synth: pfluid_synth_t): pfluid_settings_t; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_synth_sfload(synth: pfluid_synth_t; fname: pchar; reset: cint): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; + +function fluid_synth_write_s16(synth: pfluid_synth_t; len: cint; lout: pointer; loff, linc: cint; rout: pointer; roff, rinc: cint): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; + +function new_fluid_player(synth: pfluid_synth_t): pfluid_player_t; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function delete_fluid_player(player: pfluid_player_t): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_player_add(player: pfluid_player_t; fname: pchar): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_player_add_mem(player: pfluid_player_t; buf: pointer; len: csize_t): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_player_play(player: pfluid_player_t): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_player_stop(player: pfluid_player_t): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_player_set_loop(player: pfluid_player_t; loop: cint): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; +function fluid_player_get_status(player: pfluid_player_t): cint; cdecl; external {$IFDEF FS_DYNAMIC}fluidlib{$ENDIF}; + +implementation + +end. diff --git a/src/lib/modplug/modplug.pas b/src/lib/modplug/modplug.pas index 7b97bdb..bd49a16 100644 --- a/src/lib/modplug/modplug.pas +++ b/src/lib/modplug/modplug.pas @@ -35,8 +35,8 @@ const {$MESSAGE ERROR 'MP_DYNAMIC not supported'} {$IFEND} {$ELSE} - {$LINKLIB stdc++} // is this necessary? - {$LINKLIB modplug} + {$LINKLIB libstdc++.a} // is this necessary? + {$LINKLIB libmodplug.a} {$ENDIF} diff --git a/src/lib/mpg123/mpg123.pas b/src/lib/mpg123/mpg123.pas index dc160b4..9ad2c91 100644 --- a/src/lib/mpg123/mpg123.pas +++ b/src/lib/mpg123/mpg123.pas @@ -44,7 +44,7 @@ const {$MESSAGE ERROR 'MPG123_DYNAMIC not supported'} {$IFEND} {$ELSE} - {$LINKLIB mpg123} + {$LINKLIB libmpg123.a} {$ENDIF} type diff --git a/src/lib/openal/al.pas b/src/lib/openal/al.pas index 2ca6fc1..264f0e4 100644 --- a/src/lib/openal/al.pas +++ b/src/lib/openal/al.pas @@ -31,7 +31,7 @@ const {$ELSEIF DEFINED(Darwin)} {$LINKFRAMEWORK OpenAL} {$ELSE} - {$LINKLIB openal} + {$LINKLIB libopenal.a} {$ENDIF} {$include al.inc} diff --git a/src/lib/xmp/xmp.pas b/src/lib/xmp/xmp.pas index cd80954..4b41b90 100644 --- a/src/lib/xmp/xmp.pas +++ b/src/lib/xmp/xmp.pas @@ -32,9 +32,9 @@ const {$IFEND} {$ELSE} {$IF DEFINED(USE_XMP_FULL)} - {$LINKLIB xmp} + {$LINKLIB libxmp.a} {$ELSE} - {$LINKLIB xmp-lite} + {$LINKLIB libxmp-lite.a} {$ENDIF} {$ENDIF} -- 2.29.2