DEADSOFTWARE

Sound: OpenAL: Add MIDI support via FluidSynth
authorfgsfds <pvt.fgsfds@gmail.com>
Mon, 2 Sep 2019 16:16:17 +0000 (19:16 +0300)
committerfgsfds <pvt.fgsfds@gmail.com>
Mon, 2 Sep 2019 16:16:17 +0000 (19:16 +0300)
src/engine/e_sound_al.inc
src/engine/e_soundfile_fluid.pas [new file with mode: 0644]
src/game/Doom2DF.lpr
src/lib/fluidsynth/fluidsynth.pas [new file with mode: 0644]
src/lib/modplug/modplug.pas
src/lib/mpg123/mpg123.pas
src/lib/openal/al.pas
src/lib/xmp/xmp.pas

index f6ec993523682ae056ccc574115d1c0ad403cdb8..a5c83646ff18937ecf004e228250272b3af25b05 100644 (file)
@@ -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 (file)
index 0000000..da79055
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ *)
+{$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.
+
index 75118f3f536d3156999a897737f528040f42c97c..9277239bbda6ce8dce35792842adea6f05382be7 100644 (file)
@@ -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 (file)
index 0000000..84b9586
--- /dev/null
@@ -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.
index 7b97bdb5b5a3fb9fb293934c6f60668900290bd9..bd49a1607b7312273566d84098023e586dc6da5c 100644 (file)
@@ -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}
 
 
index dc160b4125ff5d7cd4ffc31ad5ee59de89c5b253..9ad2c91d5908b9cd3293045788ea2539c61e66a8 100644 (file)
@@ -44,7 +44,7 @@ const
   {$MESSAGE ERROR 'MPG123_DYNAMIC not supported'}
 {$IFEND}
 {$ELSE}
-  {$LINKLIB mpg123}
+  {$LINKLIB libmpg123.a}
 {$ENDIF}
 
 type
index 2ca6fc12aac8f3ea24c050ab7a54338d809590a5..264f0e4049d80a9d6fae45114ece52ad7162d927 100644 (file)
@@ -31,7 +31,7 @@ const
 {$ELSEIF DEFINED(Darwin)}
 {$LINKFRAMEWORK OpenAL}
 {$ELSE}
-  {$LINKLIB openal}
+  {$LINKLIB libopenal.a}
 {$ENDIF}
 
 {$include al.inc}
index cd80954cff28152610ec753bfe59d54c4e47d0ee..4b41b901de6c89c1f66a44b1ae90f5907c566e3c 100644 (file)
@@ -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}