Forum > Audio and Video
Game Music Emulator
Gigatron:
Hi nice people,
Today i would like to share usefull stuff: Game Music Emulator from 'BLARGG C++ library' + todo include crédits
I have no time to play gme_file but this is my first attempt at least write .wav file and play.
Later i will share the next improved version ;
Supported formats by this library :
gme_ay_type,
gme_gbs_type,
gme_gym_type,
gme_hes_type,
gme_kss_type,
gme_nsf_type,
gme_nsfe_type,
gme_sap_type,
gme_spc_type,
gme_vgm_type,
gme_vgz_type;
// test.nsf file included ; nsf = nintendo system
Main unit ;
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit Unit1; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs,gme,mmsystem,windows;const FileName = 'test.nsf'; // opens this file (can be any music type) SampleRate = 44100; // number of samples per second Track = 0; // (0 = first) BufSize = 2048; // multiple of 2 type TWaveWriter = class private FFileStream: TFileStream; FSampleRate: LongWord; FChannels: Word; FBytesWritten: LongWord; public constructor Create(ASampleRate: LongWord; const AFileName: string); destructor Destroy; override; procedure EnableStereo; procedure Write(const Buffer; Count: LongWord); procedure Close; end; type { TForm1 } TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private public MyFile: AnsiString; WavStream : TMemoryStream; end; var Form1: TForm1; WaveWriter: TWaveWriter; Buf: array[0..BufSize-1] of SmallInt; implementation {$R *.lfm}// console mode to doprocedure HandleError(const Str: PAnsiChar);begin if Str <> nil then begin WriteLn('Error: ', Str); ReadLn; Halt(1); end;end; { TWaveWriter } constructor TWaveWriter.Create(ASampleRate: LongWord; const AFileName: string);var Header: array[0..43] of Byte;begin FFileStream := TFileStream.Create(AFileName, fmCreate); FSampleRate := ASampleRate; FChannels := 2; FBytesWritten := 0; // Write WAVE header ZeroMemory(@Header, SizeOf(Header)); //FillChar(Header, SizeOf(Header), 0); Move('RIFF', Header[0], 4); Move('WAVEfmt ', Header[8], 8); PLongWord(@Header[16])^ := 16; PWord(@Header[20])^ := 1; // PCM PWord(@Header[22])^ := FChannels; PLongWord(@Header[24])^ := FSampleRate; PLongWord(@Header[28])^ := FSampleRate * FChannels * 2; PWord(@Header[32])^ := FChannels * 2; PWord(@Header[34])^ := 16; Move('data', Header[36], 4); FFileStream.WriteBuffer(Header, SizeOf(Header));end; destructor TWaveWriter.Destroy;begin Close; FFileStream.Free; inherited;end; procedure TWaveWriter.EnableStereo;begin FChannels := 2;end; procedure TWaveWriter.Write(const Buffer; Count: LongWord);begin FFileStream.WriteBuffer(Buffer, Count * SizeOf(SmallInt)); Inc(FBytesWritten, Count * SizeOf(SmallInt));end; procedure TWaveWriter.Close;var FileSize: LongWord;begin FileSize := FFileStream.Size - 8; FFileStream.Seek(4, soFromBeginning); FFileStream.WriteBuffer(FileSize, 4); FFileStream.Seek(40, soFromBeginning); FFileStream.WriteBuffer(FBytesWritten, 4);end; { TForm1 } procedure TForm1.FormCreate(Sender: TObject);var Emu: PMusic_Emu;begin HandleError(gme_open_file(PAnsiChar(FileName), Emu,SampleRate)); // ShowMessage('Fichier ok'); // Vérifier le démarrage de la piste HandleError(gme_start_track(Emu, Track)); // ShowMessage('Piste ok'); // Ouverture du fichier WAV WaveWriter := TWaveWriter.Create(SampleRate, 'out.wav'); try WaveWriter.EnableStereo; // Enregistrement de 10 secondes while gme_tell(Emu) < 10 * 1000 do begin // Remplir le tampon d'échantillons HandleError(gme_play(Emu, BufSize, @Buf[0])); WaveWriter.Write(Buf, BufSize); end; finally // Nettoyage gme_delete(Emu); WaveWriter.Free; end; // lecture du wave genere MyFile := Application.Location + 'out.wav'; WavStream := TMemoryStream.Create; WavStream.LoadFromFile(MyFile); PlaySound(WavStream.Memory, 0, SND_NODEFAULT or SND_ASYNC or SND_MEMORY); end; end.
Gme Unit :
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit GME; {$mode objfpc}{$H+} interface const GME_VERSION = $000603; // 1 byte major, 1 byte minor, 1 byte patch-level type PMusic_Emu = ^TMusic_Emu; TMusic_Emu = record // to doo end; gme_err_t = PAnsiChar; Pgme_info_t = ^Tgme_info_t; Tgme_info_t = record length: Integer; intro_length: Integer; loop_length: Integer; play_length: Integer; i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15: Integer; system: PAnsiChar; game: PAnsiChar; song: PAnsiChar; author: PAnsiChar; copyright: PAnsiChar; comment: PAnsiChar; dumper: PAnsiChar; s7, s8, s9, s10, s11, s12, s13, s14, s15: PAnsiChar; end; Tgme_equalizer_t = record treble: Double; bass: Double; d2, d3, d4, d5, d6, d7, d8, d9: Double; end; gme_type_t = Pointer; gme_reader_t = function(your_data: Pointer; out_: Pointer; count: Integer): gme_err_t; cdecl; gme_user_cleanup_t = procedure(user_data: Pointer); cdecl; const gme_info_only = -1; var gme_ay_type: gme_type_t; external; gme_gbs_type: gme_type_t; external; gme_gym_type: gme_type_t; external; gme_hes_type: gme_type_t; external; gme_kss_type: gme_type_t; external; gme_nsf_type: gme_type_t; external; gme_nsfe_type: gme_type_t; external; gme_sap_type: gme_type_t; external; gme_spc_type: gme_type_t; external; gme_vgm_type: gme_type_t; external; gme_vgz_type: gme_type_t; external; // gme_wrong_file_type: PAnsiChar; 'gme.dll' name 'gme_'; //function gme_wrong_file_type: PAnsiChar; cdecl; external 'gme.dll' name 'gme_wrong_file_type'; gme_wrong_file_type: PAnsiChar; external 'gme.dll' name 'gme_wrong_file_type'; // Basic operationsfunction gme_open_file(path: PAnsiChar; out out_: PMusic_Emu; sample_rate: Integer): gme_err_t; cdecl; external 'gme.dll' name 'gme_open_file';function gme_track_count(const emu: PMusic_Emu): Integer; cdecl; external 'gme.dll' name 'gme_track_count';function gme_start_track(emu: PMusic_Emu; index: Integer): gme_err_t; cdecl; external 'gme.dll' name 'gme_start_track';function gme_play(emu: PMusic_Emu; count: Integer; out_: PSmallInt): gme_err_t; cdecl; external 'gme.dll' name 'gme_play';procedure gme_delete(emu: PMusic_Emu); cdecl; external 'gme.dll' name 'gme_delete'; // Track position/lengthprocedure gme_set_fade(emu: PMusic_Emu; start_msec: Integer); cdecl; external 'gme.dll' name 'gme_set_fade';procedure gme_set_autoload_playback_limit(emu: PMusic_Emu; do_autoload_limit: Integer); cdecl; external 'gme.dll' name 'gme_set_autoload_playback_limit';function gme_autoload_playback_limit(const emu: PMusic_Emu): Integer; cdecl; external 'gme.dll' name 'gme_autoload_playback_limi';function gme_track_ended(const emu: PMusic_Emu): Integer; cdecl; external 'gme.dll' name 'gme_track_ended';function gme_tell(const emu: PMusic_Emu): Integer; cdecl; external 'gme.dll' name 'gme_tell';function gme_tell_samples(const emu: PMusic_Emu): Integer; cdecl; external 'gme.dll' name 'gme_tell_samples';function gme_seek(emu: PMusic_Emu; msec: Integer): gme_err_t; cdecl; external 'gme.dll' name 'gme_seek';function gme_seek_samples(emu: PMusic_Emu; n: Integer): gme_err_t; cdecl; external 'gme.dll' name 'gme_seek_samples'; // Informationalfunction gme_warning(emu: PMusic_Emu): PAnsiChar; cdecl; external 'gme.dll' name 'gme_warning';function gme_load_m3u(emu: PMusic_Emu; path: PAnsiChar): gme_err_t; cdecl; external 'gme.dll' name 'gme_load_m3u';procedure gme_clear_playlist(emu: PMusic_Emu); cdecl; external 'gme.dll' name 'gme_clear_playlist';function gme_track_info(const emu: PMusic_Emu; out out_: Pgme_info_t; track: Integer): gme_err_t; cdecl; external 'gme.dll' name 'gme_track_info';procedure gme_free_info(info: Pgme_info_t); cdecl; external 'gme.dll' name 'gme_free_info'; // Advanced playbackprocedure gme_set_stereo_depth(emu: PMusic_Emu; depth: Double); cdecl; external 'gme.dll' name 'gme_set_stereo_depth';procedure gme_ignore_silence(emu: PMusic_Emu; ignore: Integer); cdecl; external 'gme.dll' name 'gme_ignore_silence';procedure gme_set_tempo(emu: PMusic_Emu; tempo: Double); cdecl; external 'gme.dll' name 'gme_set_tempo';function gme_voice_count(const emu: PMusic_Emu): Integer; cdecl; external 'gme.dll' name 'gme_voice_count';function gme_voice_name(const emu: PMusic_Emu; i: Integer): PAnsiChar; cdecl; external 'gme.dll' name 'gme_voice_name';procedure gme_mute_voice(emu: PMusic_Emu; index: Integer; mute: Integer); cdecl; external 'gme.dll' name 'gme_mute_voice';procedure gme_mute_voices(emu: PMusic_Emu; muting_mask: Integer); cdecl; external 'gme.dll' name 'gme_mute_voices';procedure gme_equalizer(const emu: PMusic_Emu; out out_: Tgme_equalizer_t); cdecl; external 'gme.dll' name 'gme_equalizer';procedure gme_set_equalizer(emu: PMusic_Emu; const eq: Tgme_equalizer_t); cdecl; external 'gme.dll' name 'gme_set_equalizer';procedure gme_enable_accuracy(emu: PMusic_Emu; enabled: Integer); cdecl; external 'gme.dll' name 'gme_enable_accuracy'; // Game music typesfunction gme_type(const emu: PMusic_Emu): gme_type_t; cdecl; external 'gme.dll' name 'gme_type';function gme_type_list: gme_type_t; cdecl; external 'gme.dll' name 'gme_type_list';function gme_type_system(type_: gme_type_t): PAnsiChar; cdecl; external 'gme.dll' name 'gme_type_system';function gme_type_multitrack(type_: gme_type_t): Integer; cdecl; external 'gme.dll' name 'gme_type_multitrack';function gme_multi_channel(const emu: PMusic_Emu): Integer; cdecl; external 'gme.dll' name 'gme_multi_channel'; // Advanced file loadingfunction gme_open_data(const data: Pointer; size: LongInt; out out_: PMusic_Emu; sample_rate: Integer): gme_err_t; cdecl; external 'gme.dll' name 'gme_open_data';function gme_identify_header(const header: Pointer): PAnsiChar; cdecl; external 'gme.dll' name 'gme_identify_header';function gme_identify_extension(path_or_extension: PAnsiChar): gme_type_t; cdecl; external 'gme.dll' name 'gme_identify_extension';function gme_type_extension(music_type: gme_type_t): PAnsiChar; cdecl; external 'gme.dll' name 'gme_type_extension';function gme_identify_file(path: PAnsiChar; out type_out: gme_type_t): gme_err_t; cdecl; external 'gme.dll' name 'gme_identify_file';function gme_new_emu(type_: gme_type_t; sample_rate: Integer): PMusic_Emu; cdecl; external 'gme.dll' name 'gme_new_emu';function gme_new_emu_multi_channel(type_: gme_type_t; sample_rate: Integer): PMusic_Emu; cdecl; external 'gme.dll' name 'gme_new_emu_multi_channel';function gme_load_file(emu: PMusic_Emu; path: PAnsiChar): gme_err_t; cdecl; external 'gme.dll' name 'gme_load_data';function gme_load_data(emu: PMusic_Emu; const data: Pointer; size: LongInt): gme_err_t; cdecl; external 'gme.dll' name 'gme_load_data';function gme_load_custom(emu: PMusic_Emu; reader: gme_reader_t; file_size: LongInt; your_data: Pointer): gme_err_t; cdecl; external 'gme.dll' name 'gme_load_custom';function gme_load_m3u_data(emu: PMusic_Emu; const data: Pointer; size: LongInt): gme_err_t; cdecl; external 'gme.dll' name 'gme_load_m3u_data'; // User dataprocedure gme_set_user_data(emu: PMusic_Emu; new_user_data: Pointer); cdecl; external 'gme.dll' name 'gme_set_user_data';function gme_user_data(const emu: PMusic_Emu): Pointer; cdecl;external 'gme.dll' name 'gme_user_data';procedure gme_set_user_cleanup(emu: PMusic_Emu; func: gme_user_cleanup_t); cdecl; external 'gme.dll' name 'gme_set_user_cleanup'; implementation end.
Guva:
I see if gme_ay_type is a zx spectrum?
TRon:
@Gigatron:
Thank you for the work. A bit disappointed to see windows (and static headers) only though :P
fwiw the whole idea behind things like libao, portaudio and friends is to play audio in a platform agnostic manner (and yes that does require to install a library on windows (or place the dll next to your executable).
if you can decode a specific buffersize (as per your example) then it should be possible to play the decoded (wav) buffer live. In the case gme or the used sound-output solution does not buffer the decoding then you can either do it manually or use something like a thread/timer.
I am still working on the portaudio stuff so I'll have a better look at these gme headers later.
Fred vS:
--- Quote from: TRon on September 17, 2024, 04:07:28 pm ---I am still working on the portaudio stuff...
--- End quote ---
Excellent (and best imho) choice!
There is a fpc portaudio header with dynamic loading here.
TRon:
--- Quote from: Fred vS on September 17, 2024, 05:46:30 pm ---Excellent (and best imho) choice!
--- End quote ---
I personally dislike portaudio (too much issues in the past).
I find libao much easier to use but that is personal preference.
--- Quote ---There is a fpc portaudio header with dynamic loading here.
--- End quote ---
Yes, thank you for that fred vS.
Unfortunately there are some issues with those headers (conversion errors, you could compare when I cleaned up and posted mine) and I personally like consistency e.g. types prefixed with T and P (as i did for the other headers).
Navigation
[0] Message Index
[#] Next page