From 2f55e5929cd544944331b54c0d2b7ae3c3b29b6e Mon Sep 17 00:00:00 2001 From: fgsfds Date: Wed, 11 Mar 2020 04:27:25 +0300 Subject: [PATCH] AL: add GME music loader --- src/engine/e_soundfile_gme.pas | 207 +++++++++++++++++++++++++++++++++ src/game/Doom2DF.lpr | 4 + src/lib/gme/gme.pas | 190 ++++++++++++++++++++++++++++++ 3 files changed, 401 insertions(+) create mode 100644 src/engine/e_soundfile_gme.pas create mode 100644 src/lib/gme/gme.pas diff --git a/src/engine/e_soundfile_gme.pas b/src/engine/e_soundfile_gme.pas new file mode 100644 index 0000000..b3806b9 --- /dev/null +++ b/src/engine/e_soundfile_gme.pas @@ -0,0 +1,207 @@ +(* 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, version 3 of the License ONLY. + * + * 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_gme; + +interface + +uses e_soundfile, GME; + +type + // a module loader that uses libgme (the version from gzdoom) + // TODO: play all tracks in the song file and not just 0 + + TGMELoader = class (TSoundLoader) + public + function Load(Data: Pointer; Len: LongWord; Loop: Boolean): Boolean; override; overload; + function Load(FName: string; Loop: Boolean): Boolean; override; overload; + function Finished(): Boolean; override; + function Restart(): Boolean; override; + function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; override; + procedure Free(); override; + private + FEmu: pgme_music_emu; + FLooping: Boolean; + FTrack: LongInt; + FInfo: pgme_info_t; + + function StartTrack(Track: LongInt): Boolean; + function CalcTrackLength(): LongInt; + end; + + TGMELoaderFactory = class (TSoundLoaderFactory) + public + function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override; + function MatchExtension(FName: string): Boolean; override; + function GetLoader(): TSoundLoader; override; + end; + +implementation + +uses sysutils, utils, math, e_sound, e_log, ctypes; + +(* TGMELoaderFactory *) + +function TGMELoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean; +begin + if (Data = nil) or (Len < 4) then exit(False); + Result := ((gme_identify_header(Data))^ <> #0); +end; + +function TGMELoaderFactory.MatchExtension(FName: string): Boolean; +begin + Result := gme_identify_extension(PChar(FName)) <> nil; +end; + +function TGMELoaderFactory.GetLoader(): TSoundLoader; +begin + Result := TGMELoader.Create(); +end; + +(* TGMELoader *) + +function TGMELoader.StartTrack(Track: LongInt): Boolean; +var + Ret: gme_err_t; +begin + Result := False; + + Ret := gme_track_info(FEmu, @FInfo, Track); + if Ret <> nil then + begin + e_LogWritefln('GME: Error getting info for track %d: %s', [Track, string(Ret)]); + exit; + end; + + FTrack := Track; + + if FLooping then + gme_set_fade(FEmu, -1) + else + gme_set_fade(FEmu, CalcTrackLength()); + + gme_set_autoload_playback_limit(FEmu, 0); + + Ret := gme_start_track(FEmu, Track); + // apparently this can happen + if Ret <> nil then + begin + e_LogWritefln('GME: Could not start track %d: %s', [Track, string(Ret)]); + exit; + end; + + Result := True; +end; + +function TGMELoader.Load(Data: Pointer; Len: LongWord; Loop: Boolean): Boolean; +var + Ret: gme_err_t; +begin + Result := False; + + Ret := gme_open_data(Data, clong(Len), @FEmu, 48000); + if Ret <> nil then + begin + e_LogWritefln('GME: Error loading song from `%p`: %s', [Data, string(Ret)]); + exit; + end; + + FFormat.SampleRate := 48000; + FFormat.SampleBits := 16; + FFormat.Channels := 2; + FStreaming := True; // modules are always streaming + FLooping := Loop; + + Result := StartTrack(0); + if not Result then Free(); +end; + +function TGMELoader.Load(FName: string; Loop: Boolean): Boolean; +var + Ret: gme_err_t; +begin + Result := False; + + Ret := gme_open_file(PChar(FName), @FEmu, 48000); + if Ret <> nil then + begin + e_LogWritefln('GME: Error loading song from `%s`: %s', [FName, string(Ret)]); + exit; + end; + + FFormat.SampleRate := 48000; + FFormat.SampleBits := 16; + FFormat.Channels := 2; + FStreaming := True; // modules are always streaming + FLooping := Loop; + + Result := StartTrack(0); + if not Result then Free(); +end; + +function TGMELoader.CalcTrackLength(): LongInt; +begin + if FInfo = nil then + Result := 150000 + else if FInfo.length > 0 then + Result := FInfo.length + else if FInfo.loop_length > 0 then + Result := FInfo.intro_length + FInfo.loop_length * 2 + else + Result := 150000; +end; + +function TGMELoader.Finished(): Boolean; +begin + if FEmu <> nil then + Result := gme_track_ended(FEmu) <> 0 + else + Result := False; +end; + +function TGMELoader.Restart(): Boolean; +begin + if FEmu = nil then + Result := False + else + Result := StartTrack(FTrack); +end; + +function TGMELoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord; +begin + Result := 0; + + if FEmu = nil then Exit; + + if FLooping and (gme_track_ended(FEmu) <> 0) then + StartTrack(FTrack); + + if gme_play(FEmu, Len div 2, PWord(Buf)) = nil then + Result := Len + else + Result := 0; +end; + +procedure TGMELoader.Free(); +begin + if FInfo <> nil then gme_free_info(FInfo); + if FEmu <> nil then gme_delete(FEmu); + FInfo := nil; + FEmu := nil; +end; + +initialization + e_AddSoundLoader(TGMELoaderFactory.Create()); +end. diff --git a/src/game/Doom2DF.lpr b/src/game/Doom2DF.lpr index 076980a..53d54fc 100644 --- a/src/game/Doom2DF.lpr +++ b/src/game/Doom2DF.lpr @@ -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 index 0000000..e5f5e51 --- /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. -- 2.29.2