DEADSOFTWARE

AL: add GME music loader
authorfgsfds <pvt.fgsfds@gmail.com>
Wed, 11 Mar 2020 01:27:25 +0000 (04:27 +0300)
committerfgsfds <pvt.fgsfds@gmail.com>
Wed, 11 Mar 2020 01:27:25 +0000 (04:27 +0300)
src/engine/e_soundfile_gme.pas [new file with mode: 0644]
src/game/Doom2DF.lpr
src/lib/gme/gme.pas [new file with mode: 0644]

diff --git a/src/engine/e_soundfile_gme.pas b/src/engine/e_soundfile_gme.pas
new file mode 100644 (file)
index 0000000..b3806b9
--- /dev/null
@@ -0,0 +1,207 @@
+(* Copyright (C)  Doom 2D: Forever Developers\r
+ *\r
+ * This program is free software: you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation, version 3 of the License ONLY.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License\r
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
+ *)\r
+{$INCLUDE ../shared/a_modes.inc}\r
+unit e_soundfile_gme;\r
+\r
+interface\r
+\r
+uses e_soundfile, GME;\r
+\r
+type\r
+  // a module loader that uses libgme (the version from gzdoom)\r
+  // TODO: play all tracks in the song file and not just 0\r
+\r
+  TGMELoader = class (TSoundLoader)\r
+  public\r
+    function Load(Data: Pointer; Len: LongWord; Loop: Boolean): Boolean; override; overload;\r
+    function Load(FName: string; Loop: Boolean): Boolean; override; overload;\r
+    function Finished(): Boolean; override;\r
+    function Restart(): Boolean; override;\r
+    function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; override;\r
+    procedure Free(); override;\r
+  private\r
+    FEmu: pgme_music_emu;\r
+    FLooping: Boolean;\r
+    FTrack: LongInt;\r
+    FInfo: pgme_info_t;\r
+\r
+    function StartTrack(Track: LongInt): Boolean;\r
+    function CalcTrackLength(): LongInt;\r
+  end;\r
+\r
+  TGMELoaderFactory = class (TSoundLoaderFactory)\r
+  public\r
+    function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override;\r
+    function MatchExtension(FName: string): Boolean; override;\r
+    function GetLoader(): TSoundLoader; override;\r
+  end;\r
+\r
+implementation\r
+\r
+uses sysutils, utils, math, e_sound, e_log, ctypes;\r
+\r
+(* TGMELoaderFactory *)\r
+\r
+function TGMELoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean;\r
+begin\r
+  if (Data = nil) or (Len < 4) then exit(False);\r
+  Result := ((gme_identify_header(Data))^ <> #0);\r
+end;\r
+\r
+function TGMELoaderFactory.MatchExtension(FName: string): Boolean;\r
+begin\r
+  Result := gme_identify_extension(PChar(FName)) <> nil;\r
+end;\r
+\r
+function TGMELoaderFactory.GetLoader(): TSoundLoader;\r
+begin\r
+  Result := TGMELoader.Create();\r
+end;\r
+\r
+(* TGMELoader *)\r
+\r
+function TGMELoader.StartTrack(Track: LongInt): Boolean;\r
+var\r
+  Ret: gme_err_t;\r
+begin\r
+  Result := False;\r
+\r
+  Ret := gme_track_info(FEmu, @FInfo, Track);\r
+  if Ret <> nil then\r
+  begin\r
+    e_LogWritefln('GME: Error getting info for track %d: %s', [Track, string(Ret)]);\r
+    exit;\r
+  end;\r
+\r
+  FTrack := Track;\r
+\r
+  if FLooping then\r
+    gme_set_fade(FEmu, -1)\r
+  else\r
+    gme_set_fade(FEmu, CalcTrackLength());\r
+\r
+  gme_set_autoload_playback_limit(FEmu, 0);\r
+\r
+  Ret := gme_start_track(FEmu, Track);\r
+  // apparently this can happen\r
+  if Ret <> nil then\r
+  begin\r
+    e_LogWritefln('GME: Could not start track %d: %s', [Track, string(Ret)]);\r
+    exit;\r
+  end;\r
+\r
+  Result := True;\r
+end;\r
+\r
+function TGMELoader.Load(Data: Pointer; Len: LongWord; Loop: Boolean): Boolean;\r
+var\r
+  Ret: gme_err_t;\r
+begin\r
+  Result := False;\r
+\r
+  Ret := gme_open_data(Data, clong(Len), @FEmu, 48000);\r
+  if Ret <> nil then\r
+  begin\r
+    e_LogWritefln('GME: Error loading song from `%p`: %s', [Data, string(Ret)]);\r
+    exit;\r
+  end;\r
+\r
+  FFormat.SampleRate := 48000;\r
+  FFormat.SampleBits := 16;\r
+  FFormat.Channels := 2;\r
+  FStreaming := True; // modules are always streaming\r
+  FLooping := Loop;\r
+\r
+  Result := StartTrack(0);\r
+  if not Result then Free();\r
+end;\r
+\r
+function TGMELoader.Load(FName: string; Loop: Boolean): Boolean;\r
+var\r
+  Ret: gme_err_t;\r
+begin\r
+  Result := False;\r
+\r
+  Ret := gme_open_file(PChar(FName), @FEmu, 48000);\r
+  if Ret <> nil then\r
+  begin\r
+    e_LogWritefln('GME: Error loading song from `%s`: %s', [FName, string(Ret)]);\r
+    exit;\r
+  end;\r
+\r
+  FFormat.SampleRate := 48000;\r
+  FFormat.SampleBits := 16;\r
+  FFormat.Channels := 2;\r
+  FStreaming := True; // modules are always streaming\r
+  FLooping := Loop;\r
+\r
+  Result := StartTrack(0);\r
+  if not Result then Free();\r
+end;\r
+\r
+function TGMELoader.CalcTrackLength(): LongInt;\r
+begin\r
+  if FInfo = nil then\r
+    Result := 150000\r
+  else if FInfo.length > 0 then\r
+    Result := FInfo.length\r
+  else if FInfo.loop_length > 0 then\r
+    Result := FInfo.intro_length + FInfo.loop_length * 2\r
+  else\r
+    Result := 150000;\r
+end;\r
+\r
+function TGMELoader.Finished(): Boolean;\r
+begin\r
+  if FEmu <> nil then\r
+    Result := gme_track_ended(FEmu) <> 0\r
+  else\r
+    Result := False;\r
+end;\r
+\r
+function TGMELoader.Restart(): Boolean;\r
+begin\r
+  if FEmu = nil then\r
+    Result := False\r
+  else\r
+    Result := StartTrack(FTrack);\r
+end;\r
+\r
+function TGMELoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord;\r
+begin\r
+  Result := 0;\r
+\r
+  if FEmu = nil then Exit;\r
+\r
+  if FLooping and (gme_track_ended(FEmu) <> 0) then\r
+    StartTrack(FTrack);\r
+\r
+  if gme_play(FEmu, Len div 2, PWord(Buf)) = nil then\r
+    Result := Len\r
+  else\r
+    Result := 0;\r
+end;\r
+\r
+procedure TGMELoader.Free();\r
+begin\r
+  if FInfo <> nil then gme_free_info(FInfo);\r
+  if FEmu <> nil then gme_delete(FEmu);\r
+  FInfo := nil;\r
+  FEmu := nil;\r
+end;\r
+\r
+initialization\r
+  e_AddSoundLoader(TGMELoaderFactory.Create());\r
+end.\r
index 076980afd984b2fdb3001407bea70026cac7caaf..53d54fc0e4d7c0d44b635c216e71c333eb779e45 100644 (file)
@@ -81,6 +81,10 @@ uses
     xmp in '../lib/xmp/xmp.pas',
     e_soundfile_xmp in '../engine/e_soundfile_xmp.pas',
   {$ENDIF}
+  {$IFDEF USE_GME}
+    gme in '../lib/gme/gme.pas',
+    e_soundfile_gme in '../engine/e_soundfile_gme.pas',
+  {$ENDIF}
   {$IFDEF USE_MPG123}
     mpg123 in '../lib/mpg123/mpg123.pas',
     e_soundfile_mp3 in '../engine/e_soundfile_mp3.pas',
diff --git a/src/lib/gme/gme.pas b/src/lib/gme/gme.pas
new file mode 100644 (file)
index 0000000..e5f5e51
--- /dev/null
@@ -0,0 +1,190 @@
+unit GME;
+
+{$IFDEF FPC}
+{$PACKRECORDS C}
+{$MODE OBJFPC}
+{$ENDIF}
+
+interface
+
+uses ctypes;
+
+{$IF DEFINED(WINDOWS)}
+  {$IFDEF LIBGME_WINDOZE_STATIC}
+    {$ERROR libgme won't static-link on Windows until we switch to FPC 3.2.0}
+    // {$LINKLIB libgme.a}
+  {$ELSE}
+    {$DEFINE GME_DYNAMIC}
+    const gmelib = 'libgme.dll';
+  {$ENDIF}
+{$ELSEIF DEFINED(UNIX)}
+  {$DEFINE GME_DYNAMIC}
+  const gmelib = 'libgme.so';
+{$ELSE}
+  {$ERROR libgme not supported on this platform. Fix it!}
+{$ENDIF}
+
+type
+  // first parameter of most gme_ functions is a pointer to the Music_Emu
+  pgme_music_emu = pointer;
+  ppgme_music_emu = ^pgme_music_emu;
+
+  // track information
+  gme_info_t = record
+    // times in milliseconds, -1 if unknown
+    length:       longint; // total length, if file specifies it
+    intro_length: longint; // length of song up to looping section
+    loop_length:  longint; // length of looping section
+    play_length:  longint; // length if available, otherwise intro_length+loop_length*2 if available,
+                           // otherwise a default of 150000 (2.5 minutes).
+    i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14,i15: longint; // reserved (jesus christ)
+    // various metadata (empty string if not available)
+    system:    pchar;
+    game:      pchar;
+    song:      pchar;
+    author:    pchar;
+    copyright: pchar;
+    comment:   pchar;
+    dump:      pchar;
+    // reserved (holy fuck)
+    s7,s8,s9,s10,s11,s12,s13,s14,s15: pchar; 
+  end;
+  pgme_info_t = ^gme_info_t;
+  ppgme_info_t = ^pgme_info_t;
+
+  // frequency equalizer parameters
+  gme_equalizer_t = record
+    treble: double;
+    bass: double;
+    d2,d3,d4,d5,d6,d7,d8,d9: double; // reserved (please stop)
+  end;
+  pgme_equalizer_t = ^gme_equalizer_t;
+
+  // music file type identifier; can also hold NULL
+  gme_type_t = pointer;
+  pgme_type_t = ^gme_type_t;
+
+  // all errors are just const char* msg, NULL === success
+  gme_err_t = pchar;
+
+const
+  gme_info_only = -1;
+
+var
+  // emulator type constants for each supported file type
+  gme_ay_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_gbs_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_gym_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_hes_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_kss_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_nsf_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_nsfe_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_sap_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_spc_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_vgm_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+  gme_vgz_type: gme_type_t; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+  // error returned if GME encounters an invalid file type
+  gme_wrong_file_type: pchar; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+{ basic API }
+
+// create emulator and load game music file/data into it. Sets *out to new emulator.
+function gme_open_file(const path: pchar; eout: ppgme_music_emu; sample_rate: longint): gme_err_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// same as gme_open_file(), but uses file data already in memory; makes copy of data;
+// the resulting Music_Emu object will be set to single channel mode
+function gme_open_data(const data: pointer; size: clong; eout: ppgme_music_emu; sample_rate: longint): gme_err_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// number of tracks available
+function gme_track_count(const emu: pgme_music_emu): longint; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// start a track, where 0 is the first track
+function gme_start_track(emu: pgme_music_emu; track: longint): gme_err_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// generate 'count' 16-bit signed samples into 'out'; output is in stereo
+function gme_play(emu: pgme_music_emu; count: longint; buf: pword): gme_err_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// finish using emulator and free memory
+procedure gme_delete(emu: pgme_music_emu); cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+{ track positioning }
+
+// Set time to start fading track out. Once fade ends track_ended() returns true.
+// Fade time can be changed while track is playing.
+procedure gme_set_fade(emu: pgme_music_emu; start_msec: longint); cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// true if a track has reached its end
+function gme_track_ended(const emu: pgme_music_emu): longint; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// number of milliseconds (1000 = one second) played since beginning of track 
+function gme_tell(const emu: pgme_music_emu): longint; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// number of samples generated since beginning of track
+function gme_tell_samples(const emu: pgme_music_emu): longint; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// seek to new time in track; seeking backwards or far forward can take a while
+function gme_seek(emu: pgme_music_emu; msec: longint): gme_err_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// equivalent to restarting track then skipping n samples
+function gme_seek_samples(emu: pgme_music_emu; n: longint): gme_err_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// If do_autoload_limit is nonzero, then automatically load track length
+// metadata (if present) and terminate playback once the track length has been
+// reached. Otherwise playback will continue for an arbitrary period of time
+// until a prolonged period of silence is detected.
+// By default, playback limits are loaded and applied.
+procedure gme_set_autoload_playback_limit(emu: pgme_music_emu; do_autoload_limit: longint); cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+{ informational }
+
+// most recent warning string, or NULL if none; clears current warning after returning
+function gme_warning(emu: pgme_music_emu): pchar; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// gets information for a particular track (length, name, author, etc.); must be freed after use
+function gme_track_info(const emu: pgme_music_emu; iout: ppgme_info_t; track: longint): gme_err_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// frees track information
+procedure gme_free_info(info: pgme_info_t); cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+{ advanced playback }
+
+// Adjust stereo echo depth, where 0.0 = off and 1.0 = maximum.
+// Has no effect for GYM, SPC, and Sega Genesis VGM music
+procedure gme_set_stereo_depth(emu: pgme_music_emu; depth: double); cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// enables/disables most accurate sound emulation options
+procedure gme_enable_accuracy(emu: pgme_music_emu; enable: longint); cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+{ music type ident }
+
+// Type of this emulator
+function gme_type(const emu: pgme_music_emu): gme_type_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// Pointer to array of all music types, with NULL entry at end. Allows a player linked
+// to this library to support new music types without having to be updated.
+function gme_type_list(): pgme_type_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// Determine likely game music type based on first four bytes of file. Returns
+// string containing proper file suffix (i.e. "NSF", "SPC", etc.) or "" if
+// file header is not recognized.
+function gme_identify_header(const header: pointer): pchar; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// Get corresponding music type for file path or extension passed in.
+function gme_identify_extension(const path_or_extension: pchar): gme_type_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// Create new emulator and set sample rate. Returns NULL if out of memory.
+// If you only need track information, pass gme_info_only for sample_rate.
+function gme_new_emu(stype: gme_type_t; sample_rate: longint): pgme_music_emu; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// Load music file into emulator
+function gme_load_file(emu: pgme_music_emu; const path: pchar): gme_err_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+// Load music file from memory into emulator. Makes a copy of data passed.
+function gme_load_data(emu: pgme_music_emu; const data: pointer; len: clong): gme_err_t; cdecl; external {$IFDEF GME_DYNAMIC}gmelib{$ENDIF};
+
+
+implementation
+
+
+end.