summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 9193a04)
raw | patch | inline | side by side (parent: 9193a04)
author | fgsfds <pvt.fgsfds@gmail.com> | |
Wed, 11 Mar 2020 01:27:25 +0000 (04:27 +0300) | ||
committer | fgsfds <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] | patch | blob |
src/game/Doom2DF.lpr | patch | blob | history | |
src/lib/gme/gme.pas | [new file with mode: 0644] | patch | blob |
diff --git a/src/engine/e_soundfile_gme.pas b/src/engine/e_soundfile_gme.pas
--- /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
diff --git a/src/game/Doom2DF.lpr b/src/game/Doom2DF.lpr
index 076980afd984b2fdb3001407bea70026cac7caaf..53d54fc0e4d7c0d44b635c216e71c333eb779e45 100644 (file)
--- a/src/game/Doom2DF.lpr
+++ b/src/game/Doom2DF.lpr
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
--- /dev/null
+++ b/src/lib/gme/gme.pas
@@ -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.