Added GME for game music emulation.

This commit is contained in:
vspader 2007-10-11 23:11:58 +00:00
parent ad844b1df3
commit 2b0eaf3369
118 changed files with 30978 additions and 0 deletions

View file

@ -86,6 +86,7 @@
17C809990C3BD231005707C4 /* Flac.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 17C808820C3BD173005707C4 /* Flac.bundle */; };
17C8099A0C3BD233005707C4 /* FileSource.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 17C808790C3BD167005707C4 /* FileSource.bundle */; };
17C809E60C3BD487005707C4 /* CoreAudio.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 17C809E30C3BD46D005707C4 /* CoreAudio.bundle */; };
17C8F3CF0CBED66C008D969D /* GME.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 17C8F3CD0CBED663008D969D /* GME.bundle */; };
17E41E070C130DFF00AC744D /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = 17E41E060C130DFF00AC744D /* Credits.html */; };
17E41E230C130EE200AC744D /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 17E41E220C130EE200AC744D /* Help */; };
17F3BB890CBC565900864489 /* CueSheet.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 17F3BB880CBC565100864489 /* CueSheet.bundle */; };
@ -280,6 +281,20 @@
remoteGlobalIDString = 8D5B49AC048680CD000E48DA;
remoteInfo = CoreAudio;
};
17C8F3CC0CBED663008D969D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 17C8F3C80CBED663008D969D /* GME.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 8D5B49B6048680CD000E48DA /* GME.bundle */;
remoteInfo = "GME Plugin";
};
17C8F44B0CBEDD37008D969D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 17C8F3C80CBED663008D969D /* GME.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 8D5B49AC048680CD000E48DA /* GME Plugin */;
remoteInfo = "GME Plugin";
};
17F3BB870CBC565100864489 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 17F3BB830CBC565100864489 /* CueSheet.xcodeproj */;
@ -359,6 +374,7 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
17C8F3CF0CBED66C008D969D /* GME.bundle in CopyFiles */,
17F3BB890CBC565900864489 /* CueSheet.bundle in CopyFiles */,
8E8D41C80CBB0DA900135C1B /* Pls.bundle in CopyFiles */,
8E8D40880CBB038E00135C1B /* M3u.bundle in CopyFiles */,
@ -520,6 +536,7 @@
17C808B00C3BD1C5005707C4 /* TagLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = TagLib.xcodeproj; path = Plugins/TagLib/TagLib.xcodeproj; sourceTree = "<group>"; };
17C808B70C3BD1D2005707C4 /* Vorbis.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Vorbis.xcodeproj; path = Plugins/Vorbis/Vorbis.xcodeproj; sourceTree = "<group>"; };
17C808C00C3BD1DD005707C4 /* WavPack.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = WavPack.xcodeproj; path = Plugins/WavPack/WavPack.xcodeproj; sourceTree = "<group>"; };
17C8F3C80CBED663008D969D /* GME.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GME.xcodeproj; path = Plugins/GME/GME.xcodeproj; sourceTree = "<group>"; };
17E41C470C1304BB00AC744D /* Swedish */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = Swedish; path = Swedish.lproj/MainMenu.nib; sourceTree = "<group>"; };
17E41C480C1304C700AC744D /* Swedish */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = Swedish; path = Swedish.lproj/OpenURLPanel.nib; sourceTree = "<group>"; };
17E41C490C1304D200AC744D /* Swedish */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Swedish; path = Swedish.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -808,6 +825,7 @@
17B619FF0B909ED400BC003F /* PlugIns */ = {
isa = PBXGroup;
children = (
17C8F3C80CBED663008D969D /* GME.xcodeproj */,
17F3BB830CBC565100864489 /* CueSheet.xcodeproj */,
8E8D41C20CBB0DA000135C1B /* Pls.xcodeproj */,
8E8D40820CBB036600135C1B /* M3u.xcodeproj */,
@ -914,6 +932,14 @@
name = Products;
sourceTree = "<group>";
};
17C8F3C90CBED663008D969D /* Products */ = {
isa = PBXGroup;
children = (
17C8F3CD0CBED663008D969D /* GME.bundle */,
);
name = Products;
sourceTree = "<group>";
};
17F3BB840CBC565100864489 /* Products */ = {
isa = PBXGroup;
children = (
@ -1144,6 +1170,7 @@
8E8D40930CBB03AF00135C1B /* PBXTargetDependency */,
8E8D41CC0CBB0DD200135C1B /* PBXTargetDependency */,
17F3BB8B0CBC566200864489 /* PBXTargetDependency */,
17C8F44C0CBEDD37008D969D /* PBXTargetDependency */,
);
name = Cog;
productInstallPath = "$(HOME)/Applications";
@ -1194,6 +1221,10 @@
ProductGroup = 17F562270C3BD8FB0019975C /* Products */;
ProjectRef = 17F562260C3BD8FB0019975C /* General.xcodeproj */;
},
{
ProductGroup = 17C8F3C90CBED663008D969D /* Products */;
ProjectRef = 17C8F3C80CBED663008D969D /* GME.xcodeproj */;
},
{
ProductGroup = 17C808840C3BD181005707C4 /* Products */;
ProjectRef = 17C808830C3BD181005707C4 /* HTTPSource.xcodeproj */;
@ -1319,6 +1350,13 @@
remoteRef = 17C809E20C3BD46D005707C4 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
17C8F3CD0CBED663008D969D /* GME.bundle */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = GME.bundle;
remoteRef = 17C8F3CC0CBED663008D969D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
17F3BB880CBC565100864489 /* CueSheet.bundle */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
@ -1521,6 +1559,11 @@
name = CoreAudio;
targetProxy = 17C809E40C3BD47C005707C4 /* PBXContainerItemProxy */;
};
17C8F44C0CBEDD37008D969D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = "GME Plugin";
targetProxy = 17C8F44B0CBEDD37008D969D /* PBXContainerItemProxy */;
};
17F3BB8B0CBC566200864489 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = CueSheet;

Binary file not shown.

View file

@ -0,0 +1,675 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 42;
objects = {
/* Begin PBXBuildFile section */
17C8F1F40CBED286008D969D /* Ay_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F18B0CBED286008D969D /* Ay_Apu.cpp */; };
17C8F1F50CBED286008D969D /* Ay_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F18C0CBED286008D969D /* Ay_Apu.h */; };
17C8F1F60CBED286008D969D /* Ay_Cpu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F18D0CBED286008D969D /* Ay_Cpu.cpp */; };
17C8F1F70CBED286008D969D /* Ay_Cpu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F18E0CBED286008D969D /* Ay_Cpu.h */; };
17C8F1F80CBED286008D969D /* Ay_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F18F0CBED286008D969D /* Ay_Emu.cpp */; };
17C8F1F90CBED286008D969D /* Ay_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1900CBED286008D969D /* Ay_Emu.h */; };
17C8F1FA0CBED286008D969D /* blargg_common.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1910CBED286008D969D /* blargg_common.h */; };
17C8F1FB0CBED286008D969D /* blargg_config.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1920CBED286008D969D /* blargg_config.h */; };
17C8F1FC0CBED286008D969D /* blargg_endian.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1930CBED286008D969D /* blargg_endian.h */; };
17C8F1FD0CBED286008D969D /* blargg_source.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1940CBED286008D969D /* blargg_source.h */; };
17C8F1FE0CBED286008D969D /* Blip_Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1950CBED286008D969D /* Blip_Buffer.cpp */; };
17C8F1FF0CBED286008D969D /* Blip_Buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1960CBED286008D969D /* Blip_Buffer.h */; };
17C8F2000CBED286008D969D /* Classic_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1970CBED286008D969D /* Classic_Emu.cpp */; };
17C8F2010CBED286008D969D /* Classic_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1980CBED286008D969D /* Classic_Emu.h */; };
17C8F2020CBED286008D969D /* Data_Reader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1990CBED286008D969D /* Data_Reader.cpp */; };
17C8F2030CBED286008D969D /* Data_Reader.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F19A0CBED286008D969D /* Data_Reader.h */; };
17C8F2040CBED286008D969D /* Dual_Resampler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F19B0CBED286008D969D /* Dual_Resampler.cpp */; };
17C8F2050CBED286008D969D /* Dual_Resampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F19C0CBED286008D969D /* Dual_Resampler.h */; };
17C8F2060CBED286008D969D /* Effects_Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F19D0CBED286008D969D /* Effects_Buffer.cpp */; };
17C8F2070CBED286008D969D /* Effects_Buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F19E0CBED286008D969D /* Effects_Buffer.h */; };
17C8F2080CBED286008D969D /* Fir_Resampler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F19F0CBED286008D969D /* Fir_Resampler.cpp */; };
17C8F2090CBED286008D969D /* Fir_Resampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1A00CBED286008D969D /* Fir_Resampler.h */; };
17C8F20A0CBED286008D969D /* Gb_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1A10CBED286008D969D /* Gb_Apu.cpp */; };
17C8F20B0CBED286008D969D /* Gb_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1A20CBED286008D969D /* Gb_Apu.h */; };
17C8F20C0CBED286008D969D /* gb_cpu_io.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1A30CBED286008D969D /* gb_cpu_io.h */; };
17C8F20D0CBED286008D969D /* Gb_Cpu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1A40CBED286008D969D /* Gb_Cpu.cpp */; };
17C8F20E0CBED286008D969D /* Gb_Cpu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1A50CBED286008D969D /* Gb_Cpu.h */; };
17C8F20F0CBED286008D969D /* Gb_Oscs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1A60CBED286008D969D /* Gb_Oscs.cpp */; };
17C8F2100CBED286008D969D /* Gb_Oscs.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1A70CBED286008D969D /* Gb_Oscs.h */; };
17C8F2110CBED286008D969D /* Gbs_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1A80CBED286008D969D /* Gbs_Emu.cpp */; };
17C8F2120CBED286008D969D /* Gbs_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1A90CBED286008D969D /* Gbs_Emu.h */; };
17C8F2130CBED286008D969D /* Gme_File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1AA0CBED286008D969D /* Gme_File.cpp */; };
17C8F2140CBED286008D969D /* Gme_File.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1AB0CBED286008D969D /* Gme_File.h */; };
17C8F2150CBED286008D969D /* gme.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1AC0CBED286008D969D /* gme.cpp */; };
17C8F2160CBED286008D969D /* gme.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1AD0CBED286008D969D /* gme.h */; settings = {ATTRIBUTES = (Public, ); }; };
17C8F2180CBED286008D969D /* Gym_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1AF0CBED286008D969D /* Gym_Emu.cpp */; };
17C8F2190CBED286008D969D /* Gym_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1B00CBED286008D969D /* Gym_Emu.h */; };
17C8F21A0CBED286008D969D /* Hes_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1B10CBED286008D969D /* Hes_Apu.cpp */; };
17C8F21B0CBED286008D969D /* Hes_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1B20CBED286008D969D /* Hes_Apu.h */; };
17C8F21C0CBED286008D969D /* hes_cpu_io.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1B30CBED286008D969D /* hes_cpu_io.h */; };
17C8F21D0CBED286008D969D /* Hes_Cpu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1B40CBED286008D969D /* Hes_Cpu.cpp */; };
17C8F21E0CBED286008D969D /* Hes_Cpu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1B50CBED286008D969D /* Hes_Cpu.h */; };
17C8F21F0CBED286008D969D /* Hes_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1B60CBED286008D969D /* Hes_Emu.cpp */; };
17C8F2200CBED286008D969D /* Hes_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1B70CBED286008D969D /* Hes_Emu.h */; };
17C8F2210CBED286008D969D /* Kss_Cpu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1B80CBED286008D969D /* Kss_Cpu.cpp */; };
17C8F2220CBED286008D969D /* Kss_Cpu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1B90CBED286008D969D /* Kss_Cpu.h */; };
17C8F2230CBED286008D969D /* Kss_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1BA0CBED286008D969D /* Kss_Emu.cpp */; };
17C8F2240CBED286008D969D /* Kss_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1BB0CBED286008D969D /* Kss_Emu.h */; };
17C8F2250CBED286008D969D /* Kss_Scc_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1BC0CBED286008D969D /* Kss_Scc_Apu.cpp */; };
17C8F2260CBED286008D969D /* Kss_Scc_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1BD0CBED286008D969D /* Kss_Scc_Apu.h */; };
17C8F2280CBED286008D969D /* M3u_Playlist.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1BF0CBED286008D969D /* M3u_Playlist.cpp */; };
17C8F2290CBED286008D969D /* M3u_Playlist.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1C00CBED286008D969D /* M3u_Playlist.h */; };
17C8F22A0CBED286008D969D /* Multi_Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1C10CBED286008D969D /* Multi_Buffer.cpp */; };
17C8F22B0CBED286008D969D /* Multi_Buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1C20CBED286008D969D /* Multi_Buffer.h */; };
17C8F22C0CBED286008D969D /* Music_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1C30CBED286008D969D /* Music_Emu.cpp */; };
17C8F22D0CBED286008D969D /* Music_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1C40CBED286008D969D /* Music_Emu.h */; };
17C8F22E0CBED286008D969D /* Nes_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1C50CBED286008D969D /* Nes_Apu.cpp */; };
17C8F22F0CBED286008D969D /* Nes_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1C60CBED286008D969D /* Nes_Apu.h */; };
17C8F2300CBED286008D969D /* nes_cpu_io.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1C70CBED286008D969D /* nes_cpu_io.h */; };
17C8F2310CBED286008D969D /* Nes_Cpu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1C80CBED286008D969D /* Nes_Cpu.cpp */; };
17C8F2320CBED286008D969D /* Nes_Cpu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1C90CBED286008D969D /* Nes_Cpu.h */; };
17C8F2330CBED286008D969D /* Nes_Fme7_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1CA0CBED286008D969D /* Nes_Fme7_Apu.cpp */; };
17C8F2340CBED286008D969D /* Nes_Fme7_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1CB0CBED286008D969D /* Nes_Fme7_Apu.h */; };
17C8F2350CBED286008D969D /* Nes_Namco_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1CC0CBED286008D969D /* Nes_Namco_Apu.cpp */; };
17C8F2360CBED286008D969D /* Nes_Namco_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1CD0CBED286008D969D /* Nes_Namco_Apu.h */; };
17C8F2370CBED286008D969D /* Nes_Oscs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1CE0CBED286008D969D /* Nes_Oscs.cpp */; };
17C8F2380CBED286008D969D /* Nes_Oscs.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1CF0CBED286008D969D /* Nes_Oscs.h */; };
17C8F2390CBED286008D969D /* Nes_Vrc6_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1D00CBED286008D969D /* Nes_Vrc6_Apu.cpp */; };
17C8F23A0CBED286008D969D /* Nes_Vrc6_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1D10CBED286008D969D /* Nes_Vrc6_Apu.h */; };
17C8F23B0CBED286008D969D /* Nsf_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1D20CBED286008D969D /* Nsf_Emu.cpp */; };
17C8F23C0CBED286008D969D /* Nsf_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1D30CBED286008D969D /* Nsf_Emu.h */; };
17C8F23D0CBED286008D969D /* Nsfe_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1D40CBED286008D969D /* Nsfe_Emu.cpp */; };
17C8F23E0CBED286008D969D /* Nsfe_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1D50CBED286008D969D /* Nsfe_Emu.h */; };
17C8F23F0CBED286008D969D /* Sap_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1D60CBED286008D969D /* Sap_Apu.cpp */; };
17C8F2400CBED286008D969D /* Sap_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1D70CBED286008D969D /* Sap_Apu.h */; };
17C8F2410CBED286008D969D /* sap_cpu_io.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1D80CBED286008D969D /* sap_cpu_io.h */; };
17C8F2420CBED286008D969D /* Sap_Cpu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1D90CBED286008D969D /* Sap_Cpu.cpp */; };
17C8F2430CBED286008D969D /* Sap_Cpu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1DA0CBED286008D969D /* Sap_Cpu.h */; };
17C8F2440CBED286008D969D /* Sap_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1DB0CBED286008D969D /* Sap_Emu.cpp */; };
17C8F2450CBED286008D969D /* Sap_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1DC0CBED286008D969D /* Sap_Emu.h */; };
17C8F2460CBED286008D969D /* Sms_Apu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1DD0CBED286008D969D /* Sms_Apu.cpp */; };
17C8F2470CBED286008D969D /* Sms_Apu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1DE0CBED286008D969D /* Sms_Apu.h */; };
17C8F2480CBED286008D969D /* Sms_Oscs.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1DF0CBED286008D969D /* Sms_Oscs.h */; };
17C8F2490CBED286008D969D /* Snes_Spc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1E00CBED286008D969D /* Snes_Spc.cpp */; };
17C8F24A0CBED286008D969D /* Snes_Spc.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1E10CBED286008D969D /* Snes_Spc.h */; };
17C8F24B0CBED286008D969D /* Spc_Cpu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1E20CBED286008D969D /* Spc_Cpu.cpp */; };
17C8F24C0CBED286008D969D /* Spc_Cpu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1E30CBED286008D969D /* Spc_Cpu.h */; };
17C8F24D0CBED286008D969D /* Spc_Dsp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1E40CBED286008D969D /* Spc_Dsp.cpp */; };
17C8F24E0CBED286008D969D /* Spc_Dsp.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1E50CBED286008D969D /* Spc_Dsp.h */; };
17C8F24F0CBED286008D969D /* Spc_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1E60CBED286008D969D /* Spc_Emu.cpp */; };
17C8F2500CBED286008D969D /* Spc_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1E70CBED286008D969D /* Spc_Emu.h */; };
17C8F2510CBED286008D969D /* Vgm_Emu_Impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1E80CBED286008D969D /* Vgm_Emu_Impl.cpp */; };
17C8F2520CBED286008D969D /* Vgm_Emu_Impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1E90CBED286008D969D /* Vgm_Emu_Impl.h */; };
17C8F2530CBED286008D969D /* Vgm_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1EA0CBED286008D969D /* Vgm_Emu.cpp */; };
17C8F2540CBED286008D969D /* Vgm_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1EB0CBED286008D969D /* Vgm_Emu.h */; };
17C8F2550CBED286008D969D /* Ym2413_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1EC0CBED286008D969D /* Ym2413_Emu.cpp */; };
17C8F2560CBED286008D969D /* Ym2413_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1ED0CBED286008D969D /* Ym2413_Emu.h */; };
17C8F2570CBED286008D969D /* Ym2612_Emu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 17C8F1EE0CBED286008D969D /* Ym2612_Emu.cpp */; };
17C8F2580CBED286008D969D /* Ym2612_Emu.h in Headers */ = {isa = PBXBuildFile; fileRef = 17C8F1EF0CBED286008D969D /* Ym2612_Emu.h */; };
8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; };
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
089C1667FE841158C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
17C8F18B0CBED286008D969D /* Ay_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Ay_Apu.cpp; path = gme/Ay_Apu.cpp; sourceTree = "<group>"; };
17C8F18C0CBED286008D969D /* Ay_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Ay_Apu.h; path = gme/Ay_Apu.h; sourceTree = "<group>"; };
17C8F18D0CBED286008D969D /* Ay_Cpu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Ay_Cpu.cpp; path = gme/Ay_Cpu.cpp; sourceTree = "<group>"; };
17C8F18E0CBED286008D969D /* Ay_Cpu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Ay_Cpu.h; path = gme/Ay_Cpu.h; sourceTree = "<group>"; };
17C8F18F0CBED286008D969D /* Ay_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Ay_Emu.cpp; path = gme/Ay_Emu.cpp; sourceTree = "<group>"; };
17C8F1900CBED286008D969D /* Ay_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Ay_Emu.h; path = gme/Ay_Emu.h; sourceTree = "<group>"; };
17C8F1910CBED286008D969D /* blargg_common.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = blargg_common.h; path = gme/blargg_common.h; sourceTree = "<group>"; };
17C8F1920CBED286008D969D /* blargg_config.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = blargg_config.h; path = gme/blargg_config.h; sourceTree = "<group>"; };
17C8F1930CBED286008D969D /* blargg_endian.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = blargg_endian.h; path = gme/blargg_endian.h; sourceTree = "<group>"; };
17C8F1940CBED286008D969D /* blargg_source.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = blargg_source.h; path = gme/blargg_source.h; sourceTree = "<group>"; };
17C8F1950CBED286008D969D /* Blip_Buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Blip_Buffer.cpp; path = gme/Blip_Buffer.cpp; sourceTree = "<group>"; };
17C8F1960CBED286008D969D /* Blip_Buffer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Blip_Buffer.h; path = gme/Blip_Buffer.h; sourceTree = "<group>"; };
17C8F1970CBED286008D969D /* Classic_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Classic_Emu.cpp; path = gme/Classic_Emu.cpp; sourceTree = "<group>"; };
17C8F1980CBED286008D969D /* Classic_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Classic_Emu.h; path = gme/Classic_Emu.h; sourceTree = "<group>"; };
17C8F1990CBED286008D969D /* Data_Reader.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Data_Reader.cpp; path = gme/Data_Reader.cpp; sourceTree = "<group>"; };
17C8F19A0CBED286008D969D /* Data_Reader.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Data_Reader.h; path = gme/Data_Reader.h; sourceTree = "<group>"; };
17C8F19B0CBED286008D969D /* Dual_Resampler.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Dual_Resampler.cpp; path = gme/Dual_Resampler.cpp; sourceTree = "<group>"; };
17C8F19C0CBED286008D969D /* Dual_Resampler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Dual_Resampler.h; path = gme/Dual_Resampler.h; sourceTree = "<group>"; };
17C8F19D0CBED286008D969D /* Effects_Buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Effects_Buffer.cpp; path = gme/Effects_Buffer.cpp; sourceTree = "<group>"; };
17C8F19E0CBED286008D969D /* Effects_Buffer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Effects_Buffer.h; path = gme/Effects_Buffer.h; sourceTree = "<group>"; };
17C8F19F0CBED286008D969D /* Fir_Resampler.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Fir_Resampler.cpp; path = gme/Fir_Resampler.cpp; sourceTree = "<group>"; };
17C8F1A00CBED286008D969D /* Fir_Resampler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Fir_Resampler.h; path = gme/Fir_Resampler.h; sourceTree = "<group>"; };
17C8F1A10CBED286008D969D /* Gb_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Gb_Apu.cpp; path = gme/Gb_Apu.cpp; sourceTree = "<group>"; };
17C8F1A20CBED286008D969D /* Gb_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Gb_Apu.h; path = gme/Gb_Apu.h; sourceTree = "<group>"; };
17C8F1A30CBED286008D969D /* gb_cpu_io.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = gb_cpu_io.h; path = gme/gb_cpu_io.h; sourceTree = "<group>"; };
17C8F1A40CBED286008D969D /* Gb_Cpu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Gb_Cpu.cpp; path = gme/Gb_Cpu.cpp; sourceTree = "<group>"; };
17C8F1A50CBED286008D969D /* Gb_Cpu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Gb_Cpu.h; path = gme/Gb_Cpu.h; sourceTree = "<group>"; };
17C8F1A60CBED286008D969D /* Gb_Oscs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Gb_Oscs.cpp; path = gme/Gb_Oscs.cpp; sourceTree = "<group>"; };
17C8F1A70CBED286008D969D /* Gb_Oscs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Gb_Oscs.h; path = gme/Gb_Oscs.h; sourceTree = "<group>"; };
17C8F1A80CBED286008D969D /* Gbs_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Gbs_Emu.cpp; path = gme/Gbs_Emu.cpp; sourceTree = "<group>"; };
17C8F1A90CBED286008D969D /* Gbs_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Gbs_Emu.h; path = gme/Gbs_Emu.h; sourceTree = "<group>"; };
17C8F1AA0CBED286008D969D /* Gme_File.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Gme_File.cpp; path = gme/Gme_File.cpp; sourceTree = "<group>"; };
17C8F1AB0CBED286008D969D /* Gme_File.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Gme_File.h; path = gme/Gme_File.h; sourceTree = "<group>"; };
17C8F1AC0CBED286008D969D /* gme.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = gme.cpp; path = gme/gme.cpp; sourceTree = "<group>"; };
17C8F1AD0CBED286008D969D /* gme.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = gme.h; path = gme/gme.h; sourceTree = "<group>"; };
17C8F1AF0CBED286008D969D /* Gym_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Gym_Emu.cpp; path = gme/Gym_Emu.cpp; sourceTree = "<group>"; };
17C8F1B00CBED286008D969D /* Gym_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Gym_Emu.h; path = gme/Gym_Emu.h; sourceTree = "<group>"; };
17C8F1B10CBED286008D969D /* Hes_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Hes_Apu.cpp; path = gme/Hes_Apu.cpp; sourceTree = "<group>"; };
17C8F1B20CBED286008D969D /* Hes_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Hes_Apu.h; path = gme/Hes_Apu.h; sourceTree = "<group>"; };
17C8F1B30CBED286008D969D /* hes_cpu_io.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = hes_cpu_io.h; path = gme/hes_cpu_io.h; sourceTree = "<group>"; };
17C8F1B40CBED286008D969D /* Hes_Cpu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Hes_Cpu.cpp; path = gme/Hes_Cpu.cpp; sourceTree = "<group>"; };
17C8F1B50CBED286008D969D /* Hes_Cpu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Hes_Cpu.h; path = gme/Hes_Cpu.h; sourceTree = "<group>"; };
17C8F1B60CBED286008D969D /* Hes_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Hes_Emu.cpp; path = gme/Hes_Emu.cpp; sourceTree = "<group>"; };
17C8F1B70CBED286008D969D /* Hes_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Hes_Emu.h; path = gme/Hes_Emu.h; sourceTree = "<group>"; };
17C8F1B80CBED286008D969D /* Kss_Cpu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Kss_Cpu.cpp; path = gme/Kss_Cpu.cpp; sourceTree = "<group>"; };
17C8F1B90CBED286008D969D /* Kss_Cpu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Kss_Cpu.h; path = gme/Kss_Cpu.h; sourceTree = "<group>"; };
17C8F1BA0CBED286008D969D /* Kss_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Kss_Emu.cpp; path = gme/Kss_Emu.cpp; sourceTree = "<group>"; };
17C8F1BB0CBED286008D969D /* Kss_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Kss_Emu.h; path = gme/Kss_Emu.h; sourceTree = "<group>"; };
17C8F1BC0CBED286008D969D /* Kss_Scc_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Kss_Scc_Apu.cpp; path = gme/Kss_Scc_Apu.cpp; sourceTree = "<group>"; };
17C8F1BD0CBED286008D969D /* Kss_Scc_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Kss_Scc_Apu.h; path = gme/Kss_Scc_Apu.h; sourceTree = "<group>"; };
17C8F1BF0CBED286008D969D /* M3u_Playlist.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = M3u_Playlist.cpp; path = gme/M3u_Playlist.cpp; sourceTree = "<group>"; };
17C8F1C00CBED286008D969D /* M3u_Playlist.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = M3u_Playlist.h; path = gme/M3u_Playlist.h; sourceTree = "<group>"; };
17C8F1C10CBED286008D969D /* Multi_Buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Multi_Buffer.cpp; path = gme/Multi_Buffer.cpp; sourceTree = "<group>"; };
17C8F1C20CBED286008D969D /* Multi_Buffer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Multi_Buffer.h; path = gme/Multi_Buffer.h; sourceTree = "<group>"; };
17C8F1C30CBED286008D969D /* Music_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Music_Emu.cpp; path = gme/Music_Emu.cpp; sourceTree = "<group>"; };
17C8F1C40CBED286008D969D /* Music_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Music_Emu.h; path = gme/Music_Emu.h; sourceTree = "<group>"; };
17C8F1C50CBED286008D969D /* Nes_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Nes_Apu.cpp; path = gme/Nes_Apu.cpp; sourceTree = "<group>"; };
17C8F1C60CBED286008D969D /* Nes_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Nes_Apu.h; path = gme/Nes_Apu.h; sourceTree = "<group>"; };
17C8F1C70CBED286008D969D /* nes_cpu_io.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = nes_cpu_io.h; path = gme/nes_cpu_io.h; sourceTree = "<group>"; };
17C8F1C80CBED286008D969D /* Nes_Cpu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Nes_Cpu.cpp; path = gme/Nes_Cpu.cpp; sourceTree = "<group>"; };
17C8F1C90CBED286008D969D /* Nes_Cpu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Nes_Cpu.h; path = gme/Nes_Cpu.h; sourceTree = "<group>"; };
17C8F1CA0CBED286008D969D /* Nes_Fme7_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Nes_Fme7_Apu.cpp; path = gme/Nes_Fme7_Apu.cpp; sourceTree = "<group>"; };
17C8F1CB0CBED286008D969D /* Nes_Fme7_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Nes_Fme7_Apu.h; path = gme/Nes_Fme7_Apu.h; sourceTree = "<group>"; };
17C8F1CC0CBED286008D969D /* Nes_Namco_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Nes_Namco_Apu.cpp; path = gme/Nes_Namco_Apu.cpp; sourceTree = "<group>"; };
17C8F1CD0CBED286008D969D /* Nes_Namco_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Nes_Namco_Apu.h; path = gme/Nes_Namco_Apu.h; sourceTree = "<group>"; };
17C8F1CE0CBED286008D969D /* Nes_Oscs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Nes_Oscs.cpp; path = gme/Nes_Oscs.cpp; sourceTree = "<group>"; };
17C8F1CF0CBED286008D969D /* Nes_Oscs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Nes_Oscs.h; path = gme/Nes_Oscs.h; sourceTree = "<group>"; };
17C8F1D00CBED286008D969D /* Nes_Vrc6_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Nes_Vrc6_Apu.cpp; path = gme/Nes_Vrc6_Apu.cpp; sourceTree = "<group>"; };
17C8F1D10CBED286008D969D /* Nes_Vrc6_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Nes_Vrc6_Apu.h; path = gme/Nes_Vrc6_Apu.h; sourceTree = "<group>"; };
17C8F1D20CBED286008D969D /* Nsf_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Nsf_Emu.cpp; path = gme/Nsf_Emu.cpp; sourceTree = "<group>"; };
17C8F1D30CBED286008D969D /* Nsf_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Nsf_Emu.h; path = gme/Nsf_Emu.h; sourceTree = "<group>"; };
17C8F1D40CBED286008D969D /* Nsfe_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Nsfe_Emu.cpp; path = gme/Nsfe_Emu.cpp; sourceTree = "<group>"; };
17C8F1D50CBED286008D969D /* Nsfe_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Nsfe_Emu.h; path = gme/Nsfe_Emu.h; sourceTree = "<group>"; };
17C8F1D60CBED286008D969D /* Sap_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Sap_Apu.cpp; path = gme/Sap_Apu.cpp; sourceTree = "<group>"; };
17C8F1D70CBED286008D969D /* Sap_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Sap_Apu.h; path = gme/Sap_Apu.h; sourceTree = "<group>"; };
17C8F1D80CBED286008D969D /* sap_cpu_io.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = sap_cpu_io.h; path = gme/sap_cpu_io.h; sourceTree = "<group>"; };
17C8F1D90CBED286008D969D /* Sap_Cpu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Sap_Cpu.cpp; path = gme/Sap_Cpu.cpp; sourceTree = "<group>"; };
17C8F1DA0CBED286008D969D /* Sap_Cpu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Sap_Cpu.h; path = gme/Sap_Cpu.h; sourceTree = "<group>"; };
17C8F1DB0CBED286008D969D /* Sap_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Sap_Emu.cpp; path = gme/Sap_Emu.cpp; sourceTree = "<group>"; };
17C8F1DC0CBED286008D969D /* Sap_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Sap_Emu.h; path = gme/Sap_Emu.h; sourceTree = "<group>"; };
17C8F1DD0CBED286008D969D /* Sms_Apu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Sms_Apu.cpp; path = gme/Sms_Apu.cpp; sourceTree = "<group>"; };
17C8F1DE0CBED286008D969D /* Sms_Apu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Sms_Apu.h; path = gme/Sms_Apu.h; sourceTree = "<group>"; };
17C8F1DF0CBED286008D969D /* Sms_Oscs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Sms_Oscs.h; path = gme/Sms_Oscs.h; sourceTree = "<group>"; };
17C8F1E00CBED286008D969D /* Snes_Spc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Snes_Spc.cpp; path = gme/Snes_Spc.cpp; sourceTree = "<group>"; };
17C8F1E10CBED286008D969D /* Snes_Spc.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Snes_Spc.h; path = gme/Snes_Spc.h; sourceTree = "<group>"; };
17C8F1E20CBED286008D969D /* Spc_Cpu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Spc_Cpu.cpp; path = gme/Spc_Cpu.cpp; sourceTree = "<group>"; };
17C8F1E30CBED286008D969D /* Spc_Cpu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Spc_Cpu.h; path = gme/Spc_Cpu.h; sourceTree = "<group>"; };
17C8F1E40CBED286008D969D /* Spc_Dsp.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Spc_Dsp.cpp; path = gme/Spc_Dsp.cpp; sourceTree = "<group>"; };
17C8F1E50CBED286008D969D /* Spc_Dsp.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Spc_Dsp.h; path = gme/Spc_Dsp.h; sourceTree = "<group>"; };
17C8F1E60CBED286008D969D /* Spc_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Spc_Emu.cpp; path = gme/Spc_Emu.cpp; sourceTree = "<group>"; };
17C8F1E70CBED286008D969D /* Spc_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Spc_Emu.h; path = gme/Spc_Emu.h; sourceTree = "<group>"; };
17C8F1E80CBED286008D969D /* Vgm_Emu_Impl.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Vgm_Emu_Impl.cpp; path = gme/Vgm_Emu_Impl.cpp; sourceTree = "<group>"; };
17C8F1E90CBED286008D969D /* Vgm_Emu_Impl.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Vgm_Emu_Impl.h; path = gme/Vgm_Emu_Impl.h; sourceTree = "<group>"; };
17C8F1EA0CBED286008D969D /* Vgm_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Vgm_Emu.cpp; path = gme/Vgm_Emu.cpp; sourceTree = "<group>"; };
17C8F1EB0CBED286008D969D /* Vgm_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Vgm_Emu.h; path = gme/Vgm_Emu.h; sourceTree = "<group>"; };
17C8F1EC0CBED286008D969D /* Ym2413_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Ym2413_Emu.cpp; path = gme/Ym2413_Emu.cpp; sourceTree = "<group>"; };
17C8F1ED0CBED286008D969D /* Ym2413_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Ym2413_Emu.h; path = gme/Ym2413_Emu.h; sourceTree = "<group>"; };
17C8F1EE0CBED286008D969D /* Ym2612_Emu.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Ym2612_Emu.cpp; path = gme/Ym2612_Emu.cpp; sourceTree = "<group>"; };
17C8F1EF0CBED286008D969D /* Ym2612_Emu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Ym2612_Emu.h; path = gme/Ym2612_Emu.h; sourceTree = "<group>"; };
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8DC2EF5B0486A6940098B216 /* GME.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GME.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D2F7E79907B2D74100F64583 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8DC2EF560486A6940098B216 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
034768DFFF38A50411DB9C8B /* Products */ = {
isa = PBXGroup;
children = (
8DC2EF5B0486A6940098B216 /* GME.framework */,
);
name = Products;
sourceTree = "<group>";
};
0867D691FE84028FC02AAC07 /* GME */ = {
isa = PBXGroup;
children = (
17C8F1850CBED267008D969D /* Headers */,
17C8F1860CBED26C008D969D /* Source */,
089C1665FE841158C02AAC07 /* Resources */,
0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */,
034768DFFF38A50411DB9C8B /* Products */,
);
name = GME;
sourceTree = "<group>";
};
0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */ = {
isa = PBXGroup;
children = (
1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */,
1058C7B2FEA5585E11CA2CBB /* Other Frameworks */,
);
name = "External Frameworks and Libraries";
sourceTree = "<group>";
};
089C1665FE841158C02AAC07 /* Resources */ = {
isa = PBXGroup;
children = (
8DC2EF5A0486A6940098B216 /* Info.plist */,
089C1666FE841158C02AAC07 /* InfoPlist.strings */,
);
name = Resources;
sourceTree = "<group>";
};
1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */ = {
isa = PBXGroup;
children = (
1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */,
);
name = "Linked Frameworks";
sourceTree = "<group>";
};
1058C7B2FEA5585E11CA2CBB /* Other Frameworks */ = {
isa = PBXGroup;
children = (
0867D6A5FE840307C02AAC07 /* AppKit.framework */,
D2F7E79907B2D74100F64583 /* CoreData.framework */,
0867D69BFE84028FC02AAC07 /* Foundation.framework */,
);
name = "Other Frameworks";
sourceTree = "<group>";
};
17C8F1850CBED267008D969D /* Headers */ = {
isa = PBXGroup;
children = (
17C8F1AD0CBED286008D969D /* gme.h */,
);
name = Headers;
sourceTree = "<group>";
};
17C8F1860CBED26C008D969D /* Source */ = {
isa = PBXGroup;
children = (
17C8F18B0CBED286008D969D /* Ay_Apu.cpp */,
17C8F18C0CBED286008D969D /* Ay_Apu.h */,
17C8F18D0CBED286008D969D /* Ay_Cpu.cpp */,
17C8F18E0CBED286008D969D /* Ay_Cpu.h */,
17C8F18F0CBED286008D969D /* Ay_Emu.cpp */,
17C8F1900CBED286008D969D /* Ay_Emu.h */,
17C8F1910CBED286008D969D /* blargg_common.h */,
17C8F1920CBED286008D969D /* blargg_config.h */,
17C8F1930CBED286008D969D /* blargg_endian.h */,
17C8F1940CBED286008D969D /* blargg_source.h */,
17C8F1950CBED286008D969D /* Blip_Buffer.cpp */,
17C8F1960CBED286008D969D /* Blip_Buffer.h */,
17C8F1970CBED286008D969D /* Classic_Emu.cpp */,
17C8F1980CBED286008D969D /* Classic_Emu.h */,
17C8F1990CBED286008D969D /* Data_Reader.cpp */,
17C8F19A0CBED286008D969D /* Data_Reader.h */,
17C8F19B0CBED286008D969D /* Dual_Resampler.cpp */,
17C8F19C0CBED286008D969D /* Dual_Resampler.h */,
17C8F19D0CBED286008D969D /* Effects_Buffer.cpp */,
17C8F19E0CBED286008D969D /* Effects_Buffer.h */,
17C8F19F0CBED286008D969D /* Fir_Resampler.cpp */,
17C8F1A00CBED286008D969D /* Fir_Resampler.h */,
17C8F1A10CBED286008D969D /* Gb_Apu.cpp */,
17C8F1A20CBED286008D969D /* Gb_Apu.h */,
17C8F1A30CBED286008D969D /* gb_cpu_io.h */,
17C8F1A40CBED286008D969D /* Gb_Cpu.cpp */,
17C8F1A50CBED286008D969D /* Gb_Cpu.h */,
17C8F1A60CBED286008D969D /* Gb_Oscs.cpp */,
17C8F1A70CBED286008D969D /* Gb_Oscs.h */,
17C8F1A80CBED286008D969D /* Gbs_Emu.cpp */,
17C8F1A90CBED286008D969D /* Gbs_Emu.h */,
17C8F1AA0CBED286008D969D /* Gme_File.cpp */,
17C8F1AB0CBED286008D969D /* Gme_File.h */,
17C8F1AC0CBED286008D969D /* gme.cpp */,
17C8F1AF0CBED286008D969D /* Gym_Emu.cpp */,
17C8F1B00CBED286008D969D /* Gym_Emu.h */,
17C8F1B10CBED286008D969D /* Hes_Apu.cpp */,
17C8F1B20CBED286008D969D /* Hes_Apu.h */,
17C8F1B30CBED286008D969D /* hes_cpu_io.h */,
17C8F1B40CBED286008D969D /* Hes_Cpu.cpp */,
17C8F1B50CBED286008D969D /* Hes_Cpu.h */,
17C8F1B60CBED286008D969D /* Hes_Emu.cpp */,
17C8F1B70CBED286008D969D /* Hes_Emu.h */,
17C8F1B80CBED286008D969D /* Kss_Cpu.cpp */,
17C8F1B90CBED286008D969D /* Kss_Cpu.h */,
17C8F1BA0CBED286008D969D /* Kss_Emu.cpp */,
17C8F1BB0CBED286008D969D /* Kss_Emu.h */,
17C8F1BC0CBED286008D969D /* Kss_Scc_Apu.cpp */,
17C8F1BD0CBED286008D969D /* Kss_Scc_Apu.h */,
17C8F1BF0CBED286008D969D /* M3u_Playlist.cpp */,
17C8F1C00CBED286008D969D /* M3u_Playlist.h */,
17C8F1C10CBED286008D969D /* Multi_Buffer.cpp */,
17C8F1C20CBED286008D969D /* Multi_Buffer.h */,
17C8F1C30CBED286008D969D /* Music_Emu.cpp */,
17C8F1C40CBED286008D969D /* Music_Emu.h */,
17C8F1C50CBED286008D969D /* Nes_Apu.cpp */,
17C8F1C60CBED286008D969D /* Nes_Apu.h */,
17C8F1C70CBED286008D969D /* nes_cpu_io.h */,
17C8F1C80CBED286008D969D /* Nes_Cpu.cpp */,
17C8F1C90CBED286008D969D /* Nes_Cpu.h */,
17C8F1CA0CBED286008D969D /* Nes_Fme7_Apu.cpp */,
17C8F1CB0CBED286008D969D /* Nes_Fme7_Apu.h */,
17C8F1CC0CBED286008D969D /* Nes_Namco_Apu.cpp */,
17C8F1CD0CBED286008D969D /* Nes_Namco_Apu.h */,
17C8F1CE0CBED286008D969D /* Nes_Oscs.cpp */,
17C8F1CF0CBED286008D969D /* Nes_Oscs.h */,
17C8F1D00CBED286008D969D /* Nes_Vrc6_Apu.cpp */,
17C8F1D10CBED286008D969D /* Nes_Vrc6_Apu.h */,
17C8F1D20CBED286008D969D /* Nsf_Emu.cpp */,
17C8F1D30CBED286008D969D /* Nsf_Emu.h */,
17C8F1D40CBED286008D969D /* Nsfe_Emu.cpp */,
17C8F1D50CBED286008D969D /* Nsfe_Emu.h */,
17C8F1D60CBED286008D969D /* Sap_Apu.cpp */,
17C8F1D70CBED286008D969D /* Sap_Apu.h */,
17C8F1D80CBED286008D969D /* sap_cpu_io.h */,
17C8F1D90CBED286008D969D /* Sap_Cpu.cpp */,
17C8F1DA0CBED286008D969D /* Sap_Cpu.h */,
17C8F1DB0CBED286008D969D /* Sap_Emu.cpp */,
17C8F1DC0CBED286008D969D /* Sap_Emu.h */,
17C8F1DD0CBED286008D969D /* Sms_Apu.cpp */,
17C8F1DE0CBED286008D969D /* Sms_Apu.h */,
17C8F1DF0CBED286008D969D /* Sms_Oscs.h */,
17C8F1E00CBED286008D969D /* Snes_Spc.cpp */,
17C8F1E10CBED286008D969D /* Snes_Spc.h */,
17C8F1E20CBED286008D969D /* Spc_Cpu.cpp */,
17C8F1E30CBED286008D969D /* Spc_Cpu.h */,
17C8F1E40CBED286008D969D /* Spc_Dsp.cpp */,
17C8F1E50CBED286008D969D /* Spc_Dsp.h */,
17C8F1E60CBED286008D969D /* Spc_Emu.cpp */,
17C8F1E70CBED286008D969D /* Spc_Emu.h */,
17C8F1E80CBED286008D969D /* Vgm_Emu_Impl.cpp */,
17C8F1E90CBED286008D969D /* Vgm_Emu_Impl.h */,
17C8F1EA0CBED286008D969D /* Vgm_Emu.cpp */,
17C8F1EB0CBED286008D969D /* Vgm_Emu.h */,
17C8F1EC0CBED286008D969D /* Ym2413_Emu.cpp */,
17C8F1ED0CBED286008D969D /* Ym2413_Emu.h */,
17C8F1EE0CBED286008D969D /* Ym2612_Emu.cpp */,
17C8F1EF0CBED286008D969D /* Ym2612_Emu.h */,
);
name = Source;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8DC2EF500486A6940098B216 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
17C8F1F50CBED286008D969D /* Ay_Apu.h in Headers */,
17C8F1F70CBED286008D969D /* Ay_Cpu.h in Headers */,
17C8F1F90CBED286008D969D /* Ay_Emu.h in Headers */,
17C8F1FA0CBED286008D969D /* blargg_common.h in Headers */,
17C8F1FB0CBED286008D969D /* blargg_config.h in Headers */,
17C8F1FC0CBED286008D969D /* blargg_endian.h in Headers */,
17C8F1FD0CBED286008D969D /* blargg_source.h in Headers */,
17C8F1FF0CBED286008D969D /* Blip_Buffer.h in Headers */,
17C8F2010CBED286008D969D /* Classic_Emu.h in Headers */,
17C8F2030CBED286008D969D /* Data_Reader.h in Headers */,
17C8F2050CBED286008D969D /* Dual_Resampler.h in Headers */,
17C8F2070CBED286008D969D /* Effects_Buffer.h in Headers */,
17C8F2090CBED286008D969D /* Fir_Resampler.h in Headers */,
17C8F20B0CBED286008D969D /* Gb_Apu.h in Headers */,
17C8F20C0CBED286008D969D /* gb_cpu_io.h in Headers */,
17C8F20E0CBED286008D969D /* Gb_Cpu.h in Headers */,
17C8F2100CBED286008D969D /* Gb_Oscs.h in Headers */,
17C8F2120CBED286008D969D /* Gbs_Emu.h in Headers */,
17C8F2140CBED286008D969D /* Gme_File.h in Headers */,
17C8F2160CBED286008D969D /* gme.h in Headers */,
17C8F2190CBED286008D969D /* Gym_Emu.h in Headers */,
17C8F21B0CBED286008D969D /* Hes_Apu.h in Headers */,
17C8F21C0CBED286008D969D /* hes_cpu_io.h in Headers */,
17C8F21E0CBED286008D969D /* Hes_Cpu.h in Headers */,
17C8F2200CBED286008D969D /* Hes_Emu.h in Headers */,
17C8F2220CBED286008D969D /* Kss_Cpu.h in Headers */,
17C8F2240CBED286008D969D /* Kss_Emu.h in Headers */,
17C8F2260CBED286008D969D /* Kss_Scc_Apu.h in Headers */,
17C8F2290CBED286008D969D /* M3u_Playlist.h in Headers */,
17C8F22B0CBED286008D969D /* Multi_Buffer.h in Headers */,
17C8F22D0CBED286008D969D /* Music_Emu.h in Headers */,
17C8F22F0CBED286008D969D /* Nes_Apu.h in Headers */,
17C8F2300CBED286008D969D /* nes_cpu_io.h in Headers */,
17C8F2320CBED286008D969D /* Nes_Cpu.h in Headers */,
17C8F2340CBED286008D969D /* Nes_Fme7_Apu.h in Headers */,
17C8F2360CBED286008D969D /* Nes_Namco_Apu.h in Headers */,
17C8F2380CBED286008D969D /* Nes_Oscs.h in Headers */,
17C8F23A0CBED286008D969D /* Nes_Vrc6_Apu.h in Headers */,
17C8F23C0CBED286008D969D /* Nsf_Emu.h in Headers */,
17C8F23E0CBED286008D969D /* Nsfe_Emu.h in Headers */,
17C8F2400CBED286008D969D /* Sap_Apu.h in Headers */,
17C8F2410CBED286008D969D /* sap_cpu_io.h in Headers */,
17C8F2430CBED286008D969D /* Sap_Cpu.h in Headers */,
17C8F2450CBED286008D969D /* Sap_Emu.h in Headers */,
17C8F2470CBED286008D969D /* Sms_Apu.h in Headers */,
17C8F2480CBED286008D969D /* Sms_Oscs.h in Headers */,
17C8F24A0CBED286008D969D /* Snes_Spc.h in Headers */,
17C8F24C0CBED286008D969D /* Spc_Cpu.h in Headers */,
17C8F24E0CBED286008D969D /* Spc_Dsp.h in Headers */,
17C8F2500CBED286008D969D /* Spc_Emu.h in Headers */,
17C8F2520CBED286008D969D /* Vgm_Emu_Impl.h in Headers */,
17C8F2540CBED286008D969D /* Vgm_Emu.h in Headers */,
17C8F2560CBED286008D969D /* Ym2413_Emu.h in Headers */,
17C8F2580CBED286008D969D /* Ym2612_Emu.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8DC2EF4F0486A6940098B216 /* GME Framework */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "GME Framework" */;
buildPhases = (
8DC2EF500486A6940098B216 /* Headers */,
8DC2EF520486A6940098B216 /* Resources */,
8DC2EF540486A6940098B216 /* Sources */,
8DC2EF560486A6940098B216 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "GME Framework";
productInstallPath = "$(HOME)/Library/Frameworks";
productName = GME;
productReference = 8DC2EF5B0486A6940098B216 /* GME.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
0867D690FE84028FC02AAC07 /* Project object */ = {
isa = PBXProject;
buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "GME" */;
hasScannedForEncodings = 1;
mainGroup = 0867D691FE84028FC02AAC07 /* GME */;
productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
projectDirPath = "";
targets = (
8DC2EF4F0486A6940098B216 /* GME Framework */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8DC2EF520486A6940098B216 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8DC2EF540486A6940098B216 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
17C8F1F40CBED286008D969D /* Ay_Apu.cpp in Sources */,
17C8F1F60CBED286008D969D /* Ay_Cpu.cpp in Sources */,
17C8F1F80CBED286008D969D /* Ay_Emu.cpp in Sources */,
17C8F1FE0CBED286008D969D /* Blip_Buffer.cpp in Sources */,
17C8F2000CBED286008D969D /* Classic_Emu.cpp in Sources */,
17C8F2020CBED286008D969D /* Data_Reader.cpp in Sources */,
17C8F2040CBED286008D969D /* Dual_Resampler.cpp in Sources */,
17C8F2060CBED286008D969D /* Effects_Buffer.cpp in Sources */,
17C8F2080CBED286008D969D /* Fir_Resampler.cpp in Sources */,
17C8F20A0CBED286008D969D /* Gb_Apu.cpp in Sources */,
17C8F20D0CBED286008D969D /* Gb_Cpu.cpp in Sources */,
17C8F20F0CBED286008D969D /* Gb_Oscs.cpp in Sources */,
17C8F2110CBED286008D969D /* Gbs_Emu.cpp in Sources */,
17C8F2130CBED286008D969D /* Gme_File.cpp in Sources */,
17C8F2150CBED286008D969D /* gme.cpp in Sources */,
17C8F2180CBED286008D969D /* Gym_Emu.cpp in Sources */,
17C8F21A0CBED286008D969D /* Hes_Apu.cpp in Sources */,
17C8F21D0CBED286008D969D /* Hes_Cpu.cpp in Sources */,
17C8F21F0CBED286008D969D /* Hes_Emu.cpp in Sources */,
17C8F2210CBED286008D969D /* Kss_Cpu.cpp in Sources */,
17C8F2230CBED286008D969D /* Kss_Emu.cpp in Sources */,
17C8F2250CBED286008D969D /* Kss_Scc_Apu.cpp in Sources */,
17C8F2280CBED286008D969D /* M3u_Playlist.cpp in Sources */,
17C8F22A0CBED286008D969D /* Multi_Buffer.cpp in Sources */,
17C8F22C0CBED286008D969D /* Music_Emu.cpp in Sources */,
17C8F22E0CBED286008D969D /* Nes_Apu.cpp in Sources */,
17C8F2310CBED286008D969D /* Nes_Cpu.cpp in Sources */,
17C8F2330CBED286008D969D /* Nes_Fme7_Apu.cpp in Sources */,
17C8F2350CBED286008D969D /* Nes_Namco_Apu.cpp in Sources */,
17C8F2370CBED286008D969D /* Nes_Oscs.cpp in Sources */,
17C8F2390CBED286008D969D /* Nes_Vrc6_Apu.cpp in Sources */,
17C8F23B0CBED286008D969D /* Nsf_Emu.cpp in Sources */,
17C8F23D0CBED286008D969D /* Nsfe_Emu.cpp in Sources */,
17C8F23F0CBED286008D969D /* Sap_Apu.cpp in Sources */,
17C8F2420CBED286008D969D /* Sap_Cpu.cpp in Sources */,
17C8F2440CBED286008D969D /* Sap_Emu.cpp in Sources */,
17C8F2460CBED286008D969D /* Sms_Apu.cpp in Sources */,
17C8F2490CBED286008D969D /* Snes_Spc.cpp in Sources */,
17C8F24B0CBED286008D969D /* Spc_Cpu.cpp in Sources */,
17C8F24D0CBED286008D969D /* Spc_Dsp.cpp in Sources */,
17C8F24F0CBED286008D969D /* Spc_Emu.cpp in Sources */,
17C8F2510CBED286008D969D /* Vgm_Emu_Impl.cpp in Sources */,
17C8F2530CBED286008D969D /* Vgm_Emu.cpp in Sources */,
17C8F2550CBED286008D969D /* Ym2413_Emu.cpp in Sources */,
17C8F2570CBED286008D969D /* Ym2612_Emu.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
089C1666FE841158C02AAC07 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
089C1667FE841158C02AAC07 /* English */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
1DEB91AE08733DA50010E9CD /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
COPY_PHASE_STRIP = NO;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
FRAMEWORK_VERSION = A;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_FIX_AND_CONTINUE = YES;
GCC_MODEL_TUNING = G5;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "@loader_path/../Frameworks";
PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO;
PRODUCT_NAME = GME;
SHARED_PRECOMPS_DIR = "";
SYMROOT = ../../build;
WRAPPER_EXTENSION = framework;
ZERO_LINK = YES;
};
name = Debug;
};
1DEB91AF08733DA50010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
ppc,
i386,
);
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
FRAMEWORK_VERSION = A;
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
GCC_MODEL_TUNING = G5;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "@loader_path/../Frameworks";
PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO;
PRODUCT_NAME = GME;
SHARED_PRECOMPS_DIR = "";
SYMROOT = ../../build;
WRAPPER_EXTENSION = framework;
};
name = Release;
};
1DEB91B208733DA50010E9CD /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
PREBINDING = NO;
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
};
name = Debug;
};
1DEB91B308733DA50010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
PREBINDING = NO;
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "GME Framework" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1DEB91AE08733DA50010E9CD /* Debug */,
1DEB91AF08733DA50010E9CD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "GME" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1DEB91B208733DA50010E9CD /* Debug */,
1DEB91B308733DA50010E9CD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 0867D690FE84028FC02AAC07 /* Project object */;
}

26
Frameworks/GME/Info.plist Normal file
View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.yourcocoaframework</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

395
Frameworks/GME/gme/Ay_Apu.cpp Executable file
View file

@ -0,0 +1,395 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Ay_Apu.h"
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// Emulation inaccuracies:
// * Noise isn't run when not in use
// * Changes to envelope and noise periods are delayed until next reload
// * Super-sonic tone should attenuate output to about 60%, not 50%
// Tones above this frequency are treated as disabled tone at half volume.
// Power of two is more efficient (avoids division).
unsigned const inaudible_freq = 16384;
int const period_factor = 16;
static byte const amp_table [16] =
{
#define ENTRY( n ) byte (n * Ay_Apu::amp_range + 0.5)
// With channels tied together and 1K resistor to ground (as datasheet recommends),
// output nearly matches logarithmic curve as claimed. Approx. 1.5 dB per step.
ENTRY(0.000000),ENTRY(0.007813),ENTRY(0.011049),ENTRY(0.015625),
ENTRY(0.022097),ENTRY(0.031250),ENTRY(0.044194),ENTRY(0.062500),
ENTRY(0.088388),ENTRY(0.125000),ENTRY(0.176777),ENTRY(0.250000),
ENTRY(0.353553),ENTRY(0.500000),ENTRY(0.707107),ENTRY(1.000000),
/*
// Measured from an AY-3-8910A chip with date code 8611.
// Direct voltages without any load (very linear)
ENTRY(0.000000),ENTRY(0.046237),ENTRY(0.064516),ENTRY(0.089785),
ENTRY(0.124731),ENTRY(0.173118),ENTRY(0.225806),ENTRY(0.329032),
ENTRY(0.360215),ENTRY(0.494624),ENTRY(0.594624),ENTRY(0.672043),
ENTRY(0.766129),ENTRY(0.841935),ENTRY(0.926882),ENTRY(1.000000),
// With only some load
ENTRY(0.000000),ENTRY(0.011940),ENTRY(0.017413),ENTRY(0.024876),
ENTRY(0.036318),ENTRY(0.054229),ENTRY(0.072637),ENTRY(0.122388),
ENTRY(0.174129),ENTRY(0.239303),ENTRY(0.323881),ENTRY(0.410945),
ENTRY(0.527363),ENTRY(0.651741),ENTRY(0.832338),ENTRY(1.000000),
*/
#undef ENTRY
};
static byte const modes [8] =
{
#define MODE( a0,a1, b0,b1, c0,c1 ) \
(a0 | a1<<1 | b0<<2 | b1<<3 | c0<<4 | c1<<5)
MODE( 1,0, 1,0, 1,0 ),
MODE( 1,0, 0,0, 0,0 ),
MODE( 1,0, 0,1, 1,0 ),
MODE( 1,0, 1,1, 1,1 ),
MODE( 0,1, 0,1, 0,1 ),
MODE( 0,1, 1,1, 1,1 ),
MODE( 0,1, 1,0, 0,1 ),
MODE( 0,1, 0,0, 0,0 ),
};
Ay_Apu::Ay_Apu()
{
// build full table of the upper 8 envelope waveforms
for ( int m = 8; m--; )
{
byte* out = env.modes [m];
int flags = modes [m];
for ( int x = 3; --x >= 0; )
{
int amp = flags & 1;
int end = flags >> 1 & 1;
int step = end - amp;
amp *= 15;
for ( int y = 16; --y >= 0; )
{
*out++ = amp_table [amp];
amp += step;
}
flags >>= 2;
}
}
output( 0 );
volume( 1.0 );
reset();
}
void Ay_Apu::reset()
{
last_time = 0;
noise.delay = 0;
noise.lfsr = 1;
osc_t* osc = &oscs [osc_count];
do
{
osc--;
osc->period = period_factor;
osc->delay = 0;
osc->last_amp = 0;
osc->phase = 0;
}
while ( osc != oscs );
for ( int i = sizeof regs; --i >= 0; )
regs [i] = 0;
regs [7] = 0xFF;
write_data_( 13, 0 );
}
void Ay_Apu::write_data_( int addr, int data )
{
assert( (unsigned) addr < reg_count );
if ( (unsigned) addr >= 14 )
{
#ifdef dprintf
dprintf( "Wrote to I/O port %02X\n", (int) addr );
#endif
}
// envelope mode
if ( addr == 13 )
{
if ( !(data & 8) ) // convert modes 0-7 to proper equivalents
data = (data & 4) ? 15 : 9;
env.wave = env.modes [data - 7];
env.pos = -48;
env.delay = 0; // will get set to envelope period in run_until()
}
regs [addr] = data;
// handle period changes accurately
int i = addr >> 1;
if ( i < osc_count )
{
blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100L * period_factor) +
regs [i * 2] * period_factor;
if ( !period )
period = period_factor;
// adjust time of next timer expiration based on change in period
osc_t& osc = oscs [i];
if ( (osc.delay += period - osc.period) < 0 )
osc.delay = 0;
osc.period = period;
}
// TODO: same as above for envelope timer, and it also has a divide by two after it
}
int const noise_off = 0x08;
int const tone_off = 0x01;
void Ay_Apu::run_until( blip_time_t final_end_time )
{
require( final_end_time >= last_time );
// noise period and initial values
blip_time_t const noise_period_factor = period_factor * 2; // verified
blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor;
if ( !noise_period )
noise_period = noise_period_factor;
blip_time_t const old_noise_delay = noise.delay;
blargg_ulong const old_noise_lfsr = noise.lfsr;
// envelope period
blip_time_t const env_period_factor = period_factor * 2; // verified
blip_time_t env_period = (regs [12] * 0x100L + regs [11]) * env_period_factor;
if ( !env_period )
env_period = env_period_factor; // same as period 1 on my AY chip
if ( !env.delay )
env.delay = env_period;
// run each osc separately
for ( int index = 0; index < osc_count; index++ )
{
osc_t* const osc = &oscs [index];
int osc_mode = regs [7] >> index;
// output
Blip_Buffer* const osc_output = osc->output;
if ( !osc_output )
continue;
osc_output->set_modified();
// period
int half_vol = 0;
blip_time_t inaudible_period = (blargg_ulong) (osc_output->clock_rate() +
inaudible_freq) / (inaudible_freq * 2);
if ( osc->period <= inaudible_period && !(osc_mode & tone_off) )
{
half_vol = 1; // Actually around 60%, but 50% is close enough
osc_mode |= tone_off;
}
// envelope
blip_time_t start_time = last_time;
blip_time_t end_time = final_end_time;
int const vol_mode = regs [0x08 + index];
int volume = amp_table [vol_mode & 0x0F] >> half_vol;
int osc_env_pos = env.pos;
if ( vol_mode & 0x10 )
{
volume = env.wave [osc_env_pos] >> half_vol;
// use envelope only if it's a repeating wave or a ramp that hasn't finished
if ( !(regs [13] & 1) || osc_env_pos < -32 )
{
end_time = start_time + env.delay;
if ( end_time >= final_end_time )
end_time = final_end_time;
//if ( !(regs [12] | regs [11]) )
// dprintf( "Used envelope period 0\n" );
}
else if ( !volume )
{
osc_mode = noise_off | tone_off;
}
}
else if ( !volume )
{
osc_mode = noise_off | tone_off;
}
// tone time
blip_time_t const period = osc->period;
blip_time_t time = start_time + osc->delay;
if ( osc_mode & tone_off ) // maintain tone's phase when off
{
blargg_long count = (final_end_time - time + period - 1) / period;
time += count * period;
osc->phase ^= count & 1;
}
// noise time
blip_time_t ntime = final_end_time;
blargg_ulong noise_lfsr = 1;
if ( !(osc_mode & noise_off) )
{
ntime = start_time + old_noise_delay;
noise_lfsr = old_noise_lfsr;
//if ( (regs [6] & 0x1F) == 0 )
// dprintf( "Used noise period 0\n" );
}
// The following efficiently handles several cases (least demanding first):
// * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC
// * Just tone or just noise, envelope disabled
// * Envelope controlling tone and/or noise
// * Tone and noise disabled, envelope enabled with high frequency
// * Tone and noise together
// * Tone and noise together with envelope
// This loop only runs one iteration if envelope is disabled. If envelope
// is being used as a waveform (tone and noise disabled), this loop will
// still be reasonably efficient since the bulk of it will be skipped.
while ( 1 )
{
// current amplitude
int amp = 0;
if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) )
amp = volume;
{
int delta = amp - osc->last_amp;
if ( delta )
{
osc->last_amp = amp;
synth_.offset( start_time, delta, osc_output );
}
}
// Run wave and noise interleved with each catching up to the other.
// If one or both are disabled, their "current time" will be past end time,
// so there will be no significant performance hit.
if ( ntime < end_time || time < end_time )
{
// Since amplitude was updated above, delta will always be +/- volume,
// so we can avoid using last_amp every time to calculate the delta.
int delta = amp * 2 - volume;
int delta_non_zero = delta != 0;
int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 );
do
{
// run noise
blip_time_t end = end_time;
if ( end_time > time ) end = time;
if ( phase & delta_non_zero )
{
while ( ntime <= end ) // must advance *past* time to avoid hang
{
int changed = noise_lfsr + 1;
noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1);
if ( changed & 2 )
{
delta = -delta;
synth_.offset( ntime, delta, osc_output );
}
ntime += noise_period;
}
}
else
{
// 20 or more noise periods on average for some music
blargg_long remain = end - ntime;
blargg_long count = remain / noise_period;
if ( remain >= 0 )
ntime += noise_period + count * noise_period;
}
// run tone
end = end_time;
if ( end_time > ntime ) end = ntime;
if ( noise_lfsr & delta_non_zero )
{
while ( time < end )
{
delta = -delta;
synth_.offset( time, delta, osc_output );
time += period;
//phase ^= 1;
}
//assert( phase == (delta > 0) );
phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1);
// (delta > 0)
}
else
{
// loop usually runs less than once
//SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period );
while ( time < end )
{
time += period;
phase ^= 1;
}
}
}
while ( time < end_time || ntime < end_time );
osc->last_amp = (delta + volume) >> 1;
if ( !(osc_mode & tone_off) )
osc->phase = phase;
}
if ( end_time >= final_end_time )
break; // breaks first time when envelope is disabled
// next envelope step
if ( ++osc_env_pos >= 0 )
osc_env_pos -= 32;
volume = env.wave [osc_env_pos] >> half_vol;
start_time = end_time;
end_time += env_period;
if ( end_time > final_end_time )
end_time = final_end_time;
}
osc->delay = time - final_end_time;
if ( !(osc_mode & noise_off) )
{
noise.delay = ntime - final_end_time;
noise.lfsr = noise_lfsr;
}
}
// TODO: optimized saw wave envelope?
// maintain envelope phase
blip_time_t remain = final_end_time - last_time - env.delay;
if ( remain >= 0 )
{
blargg_long count = (remain + env_period) / env_period;
env.pos += count;
if ( env.pos >= 0 )
env.pos = (env.pos & 31) - 32;
remain -= count * env_period;
assert( -remain <= env_period );
}
env.delay = -remain;
assert( env.delay > 0 );
assert( env.pos < 0 );
last_time = final_end_time;
}

107
Frameworks/GME/gme/Ay_Apu.h Executable file
View file

@ -0,0 +1,107 @@
// AY-3-8910 sound chip emulator
// Game_Music_Emu 0.5.2
#ifndef AY_APU_H
#define AY_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
class Ay_Apu {
public:
// Set buffer to generate all sound into, or disable sound if NULL
void output( Blip_Buffer* );
// Reset sound chip
void reset();
// Write to register at specified time
enum { reg_count = 16 };
void write( blip_time_t time, int addr, int data );
// Run sound to specified time, end current time frame, then start a new
// time frame at time 0. Time frames have no effect on emulation and each
// can be whatever length is convenient.
void end_frame( blip_time_t length );
// Additional features
// Set sound output of specific oscillator to buffer, where index is
// 0, 1, or 2. If buffer is NULL, the specified oscillator is muted.
enum { osc_count = 3 };
void osc_output( int index, Blip_Buffer* );
// Set overall volume (default is 1.0)
void volume( double );
// Set treble equalization (see documentation)
void treble_eq( blip_eq_t const& );
public:
Ay_Apu();
typedef unsigned char byte;
private:
struct osc_t
{
blip_time_t period;
blip_time_t delay;
short last_amp;
short phase;
Blip_Buffer* output;
} oscs [osc_count];
blip_time_t last_time;
byte latch;
byte regs [reg_count];
struct {
blip_time_t delay;
blargg_ulong lfsr;
} noise;
struct {
blip_time_t delay;
byte const* wave;
int pos;
byte modes [8] [48]; // values already passed through volume table
} env;
void run_until( blip_time_t );
void write_data_( int addr, int data );
public:
enum { amp_range = 255 };
Blip_Synth<blip_good_quality,1> synth_;
};
inline void Ay_Apu::volume( double v ) { synth_.volume( 0.7 / osc_count / amp_range * v ); }
inline void Ay_Apu::treble_eq( blip_eq_t const& eq ) { synth_.treble_eq( eq ); }
inline void Ay_Apu::write( blip_time_t time, int addr, int data )
{
run_until( time );
write_data_( addr, data );
}
inline void Ay_Apu::osc_output( int i, Blip_Buffer* buf )
{
assert( (unsigned) i < osc_count );
oscs [i].output = buf;
}
inline void Ay_Apu::output( Blip_Buffer* buf )
{
osc_output( 0, buf );
osc_output( 1, buf );
osc_output( 2, buf );
}
inline void Ay_Apu::end_frame( blip_time_t time )
{
if ( time > last_time )
run_until( time );
assert( last_time >= time );
last_time -= time;
}
#endif

1665
Frameworks/GME/gme/Ay_Cpu.cpp Executable file

File diff suppressed because it is too large Load diff

92
Frameworks/GME/gme/Ay_Cpu.h Executable file
View file

@ -0,0 +1,92 @@
// Z80 CPU emulator
// Game_Music_Emu 0.5.2
#ifndef AY_CPU_H
#define AY_CPU_H
#include "blargg_endian.h"
typedef blargg_long cpu_time_t;
// must be defined by caller
void ay_cpu_out( class Ay_Cpu*, cpu_time_t, unsigned addr, int data );
int ay_cpu_in( class Ay_Cpu*, unsigned addr );
class Ay_Cpu {
public:
// Clear all registers and keep pointer to 64K memory passed in
void reset( void* mem_64k );
// Run until specified time is reached. Returns true if suspicious/unsupported
// instruction was encountered at any point during run.
bool run( cpu_time_t end_time );
// Time of beginning of next instruction
cpu_time_t time() const { return state->time + state->base; }
// Alter current time. Not supported during run() call.
void set_time( cpu_time_t t ) { state->time = t - state->base; }
void adjust_time( int delta ) { state->time += delta; }
typedef BOOST::uint8_t uint8_t;
typedef BOOST::uint16_t uint16_t;
#if BLARGG_BIG_ENDIAN
struct regs_t { uint8_t b, c, d, e, h, l, flags, a; };
#else
struct regs_t { uint8_t c, b, e, d, l, h, a, flags; };
#endif
BOOST_STATIC_ASSERT( sizeof (regs_t) == 8 );
struct pairs_t { uint16_t bc, de, hl, fa; };
// Registers are not updated until run() returns
struct registers_t {
uint16_t pc;
uint16_t sp;
uint16_t ix;
uint16_t iy;
union {
regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a
pairs_t w; // w.bc, w.de, w.hl. w.fa
};
union {
regs_t b;
pairs_t w;
} alt;
uint8_t iff1;
uint8_t iff2;
uint8_t r;
uint8_t i;
uint8_t im;
};
//registers_t r; (below for efficiency)
// can read this far past end of memory
enum { cpu_padding = 0x100 };
public:
Ay_Cpu();
private:
uint8_t szpc [0x200];
uint8_t* mem;
cpu_time_t end_time_;
struct state_t {
cpu_time_t base;
cpu_time_t time;
};
state_t* state; // points to state_ or a local copy within run()
state_t state_;
void set_end_time( cpu_time_t t );
public:
registers_t r;
};
inline void Ay_Cpu::set_end_time( cpu_time_t t )
{
cpu_time_t delta = state->base - t;
state->base = t;
state->time += delta;
}
#endif

404
Frameworks/GME/gme/Ay_Emu.cpp Executable file
View file

@ -0,0 +1,404 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Ay_Emu.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
long const spectrum_clock = 3546900;
long const cpc_clock = 2000000;
unsigned const ram_start = 0x4000;
int const osc_count = Ay_Apu::osc_count + 1;
Ay_Emu::Ay_Emu()
{
beeper_output = 0;
set_type( gme_ay_type );
static const char* const names [osc_count] = {
"Wave 1", "Wave 2", "Wave 3", "Beeper"
};
set_voice_names( names );
static int const types [osc_count] = {
wave_type | 0, wave_type | 1, wave_type | 2, mixed_type | 0
};
set_voice_types( types );
set_silence_lookahead( 6 );
}
Ay_Emu::~Ay_Emu() { }
// Track info
static byte const* get_data( Ay_Emu::file_t const& file, byte const* ptr, int min_size )
{
long pos = ptr - (byte const*) file.header;
long file_size = file.end - (byte const*) file.header;
assert( (unsigned long) pos <= (unsigned long) file_size - 2 );
int offset = (BOOST::int16_t) get_be16( ptr );
if ( !offset || blargg_ulong (pos + offset) > blargg_ulong (file_size - min_size) )
return 0;
return ptr + offset;
}
static blargg_err_t parse_header( byte const* in, long size, Ay_Emu::file_t* out )
{
typedef Ay_Emu::header_t header_t;
out->header = (header_t const*) in;
out->end = in + size;
if ( size < Ay_Emu::header_size )
return gme_wrong_file_type;
header_t const& h = *(header_t const*) in;
if ( memcmp( h.tag, "ZXAYEMUL", 8 ) )
return gme_wrong_file_type;
out->tracks = get_data( *out, h.track_info, (h.max_track + 1) * 4 );
if ( !out->tracks )
return "Missing track data";
return 0;
}
static void copy_ay_fields( Ay_Emu::file_t const& file, track_info_t* out, int track )
{
Gme_File::copy_field_( out->song, (char const*) get_data( file, file.tracks + track * 4, 1 ) );
byte const* track_info = get_data( file, file.tracks + track * 4 + 2, 6 );
if ( track_info )
out->length = get_be16( track_info + 4 ) * (1000L / 50); // frames to msec
Gme_File::copy_field_( out->author, (char const*) get_data( file, file.header->author, 1 ) );
Gme_File::copy_field_( out->comment, (char const*) get_data( file, file.header->comment, 1 ) );
}
blargg_err_t Ay_Emu::track_info_( track_info_t* out, int track ) const
{
copy_ay_fields( file, out, track );
return 0;
}
struct Ay_File : Gme_Info_
{
Ay_Emu::file_t file;
Ay_File() { set_type( gme_ay_type ); }
blargg_err_t load_mem_( byte const* begin, long size )
{
RETURN_ERR( parse_header( begin, size, &file ) );
set_track_count( file.header->max_track + 1 );
return 0;
}
blargg_err_t track_info_( track_info_t* out, int track ) const
{
copy_ay_fields( file, out, track );
return 0;
}
};
static Music_Emu* new_ay_emu () { return BLARGG_NEW Ay_Emu ; }
static Music_Emu* new_ay_file() { return BLARGG_NEW Ay_File; }
gme_type_t_ const gme_ay_type [1] = { "ZX Spectrum", 0, &new_ay_emu, &new_ay_file, "AY", 1 };
// Setup
blargg_err_t Ay_Emu::load_mem_( byte const* in, long size )
{
assert( offsetof (header_t,track_info [2]) == header_size );
RETURN_ERR( parse_header( in, size, &file ) );
set_track_count( file.header->max_track + 1 );
if ( file.header->vers > 2 )
set_warning( "Unknown file version" );
set_voice_count( osc_count );
apu.volume( gain() );
return setup_buffer( spectrum_clock );
}
void Ay_Emu::update_eq( blip_eq_t const& eq )
{
apu.treble_eq( eq );
}
void Ay_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer*, Blip_Buffer* )
{
if ( i >= Ay_Apu::osc_count )
beeper_output = center;
else
apu.osc_output( i, center );
}
// Emulation
void Ay_Emu::set_tempo_( double t )
{
play_period = blip_time_t (clock_rate() / 50 / t);
}
blargg_err_t Ay_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
memset( mem.ram + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET
memset( mem.ram + 0x0100, 0xFF, 0x4000 - 0x100 );
memset( mem.ram + ram_start, 0x00, sizeof mem.ram - ram_start );
memset( mem.padding1, 0xFF, sizeof mem.padding1 );
memset( mem.ram + 0x10000, 0xFF, sizeof mem.ram - 0x10000 );
// locate data blocks
byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 );
if ( !data ) return "File data missing";
byte const* const more_data = get_data( file, data + 10, 6 );
if ( !more_data ) return "File data missing";
byte const* blocks = get_data( file, data + 12, 8 );
if ( !blocks ) return "File data missing";
// initial addresses
cpu::reset( mem.ram );
r.sp = get_be16( more_data );
r.b.a = r.b.b = r.b.d = r.b.h = data [8];
r.b.flags = r.b.c = r.b.e = r.b.l = data [9];
r.alt.w = r.w;
r.ix = r.iy = r.w.hl;
unsigned addr = get_be16( blocks );
if ( !addr ) return "File data missing";
unsigned init = get_be16( more_data + 2 );
if ( !init )
init = addr;
// copy blocks into memory
do
{
blocks += 2;
unsigned len = get_be16( blocks ); blocks += 2;
if ( addr + len > 0x10000 )
{
set_warning( "Bad data block size" );
len = 0x10000 - addr;
}
check( len );
byte const* in = get_data( file, blocks, 0 ); blocks += 2;
if ( len > blargg_ulong (file.end - in) )
{
set_warning( "Missing file data" );
len = file.end - in;
}
//dprintf( "addr: $%04X, len: $%04X\n", addr, len );
if ( addr < ram_start && addr >= 0x400 ) // several tracks use low data
dprintf( "Block addr in ROM\n" );
memcpy( mem.ram + addr, in, len );
if ( file.end - blocks < 8 )
{
set_warning( "Missing file data" );
break;
}
}
while ( (addr = get_be16( blocks )) != 0 );
// copy and configure driver
static byte const passive [] = {
0xF3, // DI
0xCD, 0, 0, // CALL init
0xED, 0x5E, // LOOP: IM 2
0xFB, // EI
0x76, // HALT
0x18, 0xFA // JR LOOP
};
static byte const active [] = {
0xF3, // DI
0xCD, 0, 0, // CALL init
0xED, 0x56, // LOOP: IM 1
0xFB, // EI
0x76, // HALT
0xCD, 0, 0, // CALL play
0x18, 0xF7 // JR LOOP
};
memcpy( mem.ram, passive, sizeof passive );
unsigned play_addr = get_be16( more_data + 4 );
//dprintf( "Play: $%04X\n", play_addr );
if ( play_addr )
{
memcpy( mem.ram, active, sizeof active );
mem.ram [ 9] = play_addr;
mem.ram [10] = play_addr >> 8;
}
mem.ram [2] = init;
mem.ram [3] = init >> 8;
mem.ram [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET)
memcpy( mem.ram + 0x10000, mem.ram, 0x80 ); // some code wraps around (ugh)
beeper_delta = int (apu.amp_range * 0.65);
last_beeper = 0;
apu.reset();
next_play = play_period;
// start at spectrum speed
change_clock_rate( spectrum_clock );
set_tempo( tempo() );
spectrum_mode = false;
cpc_mode = false;
cpc_latch = 0;
return 0;
}
// Emulation
void Ay_Emu::cpu_out_misc( cpu_time_t time, unsigned addr, int data )
{
if ( !cpc_mode )
{
switch ( addr & 0xFEFF )
{
case 0xFEFD:
spectrum_mode = true;
apu_addr = data & 0x0F;
return;
case 0xBEFD:
spectrum_mode = true;
apu.write( time, apu_addr, data );
return;
}
}
if ( !spectrum_mode )
{
switch ( addr >> 8 )
{
case 0xF6:
switch ( data & 0xC0 )
{
case 0xC0:
apu_addr = cpc_latch & 0x0F;
goto enable_cpc;
case 0x80:
apu.write( time, apu_addr, cpc_latch );
goto enable_cpc;
}
break;
case 0xF4:
cpc_latch = data;
goto enable_cpc;
}
}
dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
return;
enable_cpc:
if ( !cpc_mode )
{
cpc_mode = true;
change_clock_rate( cpc_clock );
set_tempo( tempo() );
}
}
void ay_cpu_out( Ay_Cpu* cpu, cpu_time_t time, unsigned addr, int data )
{
Ay_Emu& emu = STATIC_CAST(Ay_Emu&,*cpu);
if ( (addr & 0xFF) == 0xFE && !emu.cpc_mode )
{
int delta = emu.beeper_delta;
data &= 0x10;
if ( emu.last_beeper != data )
{
emu.last_beeper = data;
emu.beeper_delta = -delta;
emu.spectrum_mode = true;
if ( emu.beeper_output )
emu.apu.synth_.offset( time, delta, emu.beeper_output );
}
}
else
{
emu.cpu_out_misc( time, addr, data );
}
}
int ay_cpu_in( Ay_Cpu*, unsigned addr )
{
// keyboard read and other things
if ( (addr & 0xFF) == 0xFE )
return 0xFF; // other values break some beeper tunes
dprintf( "Unmapped IN : $%04X\n", addr );
return 0xFF;
}
blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int )
{
set_time( 0 );
if ( !(spectrum_mode | cpc_mode) )
duration /= 2; // until mode is set, leave room for halved clock rate
while ( time() < duration )
{
cpu::run( min( duration, next_play ) );
if ( time() >= next_play )
{
next_play += play_period;
if ( r.iff1 )
{
if ( mem.ram [r.pc] == 0x76 )
r.pc++;
r.iff1 = r.iff2 = 0;
mem.ram [--r.sp] = uint8_t (r.pc >> 8);
mem.ram [--r.sp] = uint8_t (r.pc);
r.pc = 0x38;
cpu::adjust_time( 12 );
if ( r.im == 2 )
{
cpu::adjust_time( 6 );
unsigned addr = r.i * 0x100u + 0xFF;
r.pc = mem.ram [(addr + 1) & 0xFFFF] * 0x100u + mem.ram [addr];
}
}
}
}
duration = time();
next_play -= duration;
check( next_play >= 0 );
adjust_time( -duration );
apu.end_frame( duration );
return 0;
}

70
Frameworks/GME/gme/Ay_Emu.h Executable file
View file

@ -0,0 +1,70 @@
// Sinclair Spectrum AY music file emulator
// Game_Music_Emu 0.5.2
#ifndef AY_EMU_H
#define AY_EMU_H
#include "Classic_Emu.h"
#include "Ay_Apu.h"
#include "Ay_Cpu.h"
class Ay_Emu : private Ay_Cpu, public Classic_Emu {
typedef Ay_Cpu cpu;
public:
// AY file header
enum { header_size = 0x14 };
struct header_t
{
byte tag [8];
byte vers;
byte player;
byte unused [2];
byte author [2];
byte comment [2];
byte max_track;
byte first_track;
byte track_info [2];
};
static gme_type_t static_type() { return gme_ay_type; }
public:
Ay_Emu();
~Ay_Emu();
struct file_t {
header_t const* header;
byte const* end;
byte const* tracks;
};
protected:
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t load_mem_( byte const*, long );
blargg_err_t start_track_( int );
blargg_err_t run_clocks( blip_time_t&, int );
void set_tempo_( double );
void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
void update_eq( blip_eq_t const& );
private:
file_t file;
unsigned play_addr;
cpu_time_t play_period;
cpu_time_t next_play;
Blip_Buffer* beeper_output;
int beeper_delta;
int last_beeper;
int apu_addr;
int cpc_latch;
bool spectrum_mode;
bool cpc_mode;
// large items
struct {
byte padding1 [0x100];
byte ram [0x10000 + 0x100];
} mem;
Ay_Apu apu;
friend void ay_cpu_out( Ay_Cpu*, cpu_time_t, unsigned addr, int data );
void cpu_out_misc( cpu_time_t, unsigned addr, int data );
};
#endif

View file

@ -0,0 +1,446 @@
// Blip_Buffer 0.4.1. http://www.slack.net/~ant/
#include "Blip_Buffer.h"
#include <assert.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
int const silent_buf_size = 1; // size used for Silent_Blip_Buffer
Blip_Buffer::Blip_Buffer()
{
factor_ = LONG_MAX;
offset_ = 0;
buffer_ = 0;
buffer_size_ = 0;
sample_rate_ = 0;
reader_accum_ = 0;
bass_shift_ = 0;
clock_rate_ = 0;
bass_freq_ = 16;
length_ = 0;
// assumptions code makes about implementation-defined features
#ifndef NDEBUG
// right shift of negative value preserves sign
buf_t_ i = -0x7FFFFFFE;
assert( (i >> 1) == -0x3FFFFFFF );
// casting to short truncates to 16 bits and sign-extends
i = 0x18000;
assert( (short) i == -0x8000 );
#endif
}
Blip_Buffer::~Blip_Buffer()
{
if ( buffer_size_ != silent_buf_size )
free( buffer_ );
}
Silent_Blip_Buffer::Silent_Blip_Buffer()
{
factor_ = 0;
buffer_ = buf;
buffer_size_ = silent_buf_size;
memset( buf, 0, sizeof buf ); // in case machine takes exception for signed overflow
}
void Blip_Buffer::clear( int entire_buffer )
{
offset_ = 0;
reader_accum_ = 0;
modified_ = 0;
if ( buffer_ )
{
long count = (entire_buffer ? buffer_size_ : samples_avail());
memset( buffer_, 0, (count + blip_buffer_extra_) * sizeof (buf_t_) );
}
}
Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec )
{
if ( buffer_size_ == silent_buf_size )
{
assert( 0 );
return "Internal (tried to resize Silent_Blip_Buffer)";
}
// start with maximum length that resampled time can represent
long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - blip_buffer_extra_ - 64;
if ( msec != blip_max_length )
{
long s = (new_rate * (msec + 1) + 999) / 1000;
if ( s < new_size )
new_size = s;
else
assert( 0 ); // fails if requested buffer length exceeds limit
}
if ( buffer_size_ != new_size )
{
void* p = realloc( buffer_, (new_size + blip_buffer_extra_) * sizeof *buffer_ );
if ( !p )
return "Out of memory";
buffer_ = (buf_t_*) p;
}
buffer_size_ = new_size;
assert( buffer_size_ != silent_buf_size );
// update things based on the sample rate
sample_rate_ = new_rate;
length_ = new_size * 1000 / new_rate - 1;
if ( msec )
assert( length_ == msec ); // ensure length is same as that passed in
if ( clock_rate_ )
clock_rate( clock_rate_ );
bass_freq( bass_freq_ );
clear();
return 0; // success
}
blip_resampled_time_t Blip_Buffer::clock_rate_factor( long rate ) const
{
double ratio = (double) sample_rate_ / rate;
blip_long factor = (blip_long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 );
assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large
return (blip_resampled_time_t) factor;
}
void Blip_Buffer::bass_freq( int freq )
{
bass_freq_ = freq;
int shift = 31;
if ( freq > 0 )
{
shift = 13;
long f = (freq << 16) / sample_rate_;
while ( (f >>= 1) && --shift ) { }
}
bass_shift_ = shift;
}
void Blip_Buffer::end_frame( blip_time_t t )
{
offset_ += t * factor_;
assert( samples_avail() <= (long) buffer_size_ ); // time outside buffer length
}
void Blip_Buffer::remove_silence( long count )
{
assert( count <= samples_avail() ); // tried to remove more samples than available
offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
}
long Blip_Buffer::count_samples( blip_time_t t ) const
{
unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY;
unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY;
return (long) (last_sample - first_sample);
}
blip_time_t Blip_Buffer::count_clocks( long count ) const
{
if ( !factor_ )
{
assert( 0 ); // sample rate and clock rates must be set first
return 0;
}
if ( count > buffer_size_ )
count = buffer_size_;
blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_);
}
void Blip_Buffer::remove_samples( long count )
{
if ( count )
{
remove_silence( count );
// copy remaining samples to beginning and clear old samples
long remain = samples_avail() + blip_buffer_extra_;
memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ );
memset( buffer_ + remain, 0, count * sizeof *buffer_ );
}
}
// Blip_Synth_
Blip_Synth_Fast_::Blip_Synth_Fast_()
{
buf = 0;
last_amp = 0;
delta_factor = 0;
}
void Blip_Synth_Fast_::volume_unit( double new_unit )
{
delta_factor = int (new_unit * (1L << blip_sample_bits) + 0.5);
}
#if !BLIP_BUFFER_FAST
Blip_Synth_::Blip_Synth_( short* p, int w ) :
impulses( p ),
width( w )
{
volume_unit_ = 0.0;
kernel_unit = 0;
buf = 0;
last_amp = 0;
delta_factor = 0;
}
#undef PI
#define PI 3.1415926535897932384626433832795029
static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff )
{
if ( cutoff >= 0.999 )
cutoff = 0.999;
if ( treble < -300.0 )
treble = -300.0;
if ( treble > 5.0 )
treble = 5.0;
double const maxh = 4096.0;
double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) );
double const pow_a_n = pow( rolloff, maxh - maxh * cutoff );
double const to_angle = PI / 2 / maxh / oversample;
for ( int i = 0; i < count; i++ )
{
double angle = ((i - count) * 2 + 1) * to_angle;
double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle );
double cos_nc_angle = cos( maxh * cutoff * angle );
double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle );
double cos_angle = cos( angle );
c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle;
double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle);
double b = 2.0 - cos_angle - cos_angle;
double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle;
out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d
}
}
void blip_eq_t::generate( float* out, int count ) const
{
// lower cutoff freq for narrow kernels with their wider transition band
// (8 points->1.49, 16 points->1.15)
double oversample = blip_res * 2.25 / count + 0.85;
double half_rate = sample_rate * 0.5;
if ( cutoff_freq )
oversample = half_rate / cutoff_freq;
double cutoff = rolloff_freq * oversample / half_rate;
gen_sinc( out, count, blip_res * oversample, treble, cutoff );
// apply (half of) hamming window
double to_fraction = PI / (count - 1);
for ( int i = count; i--; )
out [i] *= 0.54f - 0.46f * (float) cos( i * to_fraction );
}
void Blip_Synth_::adjust_impulse()
{
// sum pairs for each phase and add error correction to end of first half
int const size = impulses_size();
for ( int p = blip_res; p-- >= blip_res / 2; )
{
int p2 = blip_res - 2 - p;
long error = kernel_unit;
for ( int i = 1; i < size; i += blip_res )
{
error -= impulses [i + p ];
error -= impulses [i + p2];
}
if ( p == p2 )
error /= 2; // phase = 0.5 impulse uses same half for both sides
impulses [size - blip_res + p] += (short) error;
//printf( "error: %ld\n", error );
}
//for ( int i = blip_res; i--; printf( "\n" ) )
// for ( int j = 0; j < width / 2; j++ )
// printf( "%5ld,", impulses [j * blip_res + i + 1] );
}
void Blip_Synth_::treble_eq( blip_eq_t const& eq )
{
float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2];
int const half_size = blip_res / 2 * (width - 1);
eq.generate( &fimpulse [blip_res], half_size );
int i;
// need mirror slightly past center for calculation
for ( i = blip_res; i--; )
fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i];
// starts at 0
for ( i = 0; i < blip_res; i++ )
fimpulse [i] = 0.0f;
// find rescale factor
double total = 0.0;
for ( i = 0; i < half_size; i++ )
total += fimpulse [blip_res + i];
//double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB
//double const base_unit = 37888.0; // allows treble to +5 dB
double const base_unit = 32768.0; // necessary for blip_unscaled to work
double rescale = base_unit / 2 / total;
kernel_unit = (long) base_unit;
// integrate, first difference, rescale, convert to int
double sum = 0.0;
double next = 0.0;
int const impulses_size = this->impulses_size();
for ( i = 0; i < impulses_size; i++ )
{
impulses [i] = (short) floor( (next - sum) * rescale + 0.5 );
sum += fimpulse [i];
next += fimpulse [i + blip_res];
}
adjust_impulse();
// volume might require rescaling
double vol = volume_unit_;
if ( vol )
{
volume_unit_ = 0.0;
volume_unit( vol );
}
}
void Blip_Synth_::volume_unit( double new_unit )
{
if ( new_unit != volume_unit_ )
{
// use default eq if it hasn't been set yet
if ( !kernel_unit )
treble_eq( -8.0 );
volume_unit_ = new_unit;
double factor = new_unit * (1L << blip_sample_bits) / kernel_unit;
if ( factor > 0.0 )
{
int shift = 0;
// if unit is really small, might need to attenuate kernel
while ( factor < 2.0 )
{
shift++;
factor *= 2.0;
}
if ( shift )
{
kernel_unit >>= shift;
assert( kernel_unit > 0 ); // fails if volume unit is too low
// keep values positive to avoid round-towards-zero of sign-preserving
// right shift for negative values
long offset = 0x8000 + (1 << (shift - 1));
long offset2 = 0x8000 >> shift;
for ( int i = impulses_size(); i--; )
impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2);
adjust_impulse();
}
}
delta_factor = (int) floor( factor + 0.5 );
//printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit );
}
}
#endif
long Blip_Buffer::read_samples( blip_sample_t* BLIP_RESTRICT out, long max_samples, int stereo )
{
long count = samples_avail();
if ( count > max_samples )
count = max_samples;
if ( count )
{
int const bass = BLIP_READER_BASS( *this );
BLIP_READER_BEGIN( reader, *this );
if ( !stereo )
{
for ( blip_long n = count; n; --n )
{
blip_long s = BLIP_READER_READ( reader );
if ( (blip_sample_t) s != s )
s = 0x7FFF - (s >> 24);
*out++ = (blip_sample_t) s;
BLIP_READER_NEXT( reader, bass );
}
}
else
{
for ( blip_long n = count; n; --n )
{
blip_long s = BLIP_READER_READ( reader );
if ( (blip_sample_t) s != s )
s = 0x7FFF - (s >> 24);
*out = (blip_sample_t) s;
out += 2;
BLIP_READER_NEXT( reader, bass );
}
}
BLIP_READER_END( reader, *this );
remove_samples( count );
}
return count;
}
void Blip_Buffer::mix_samples( blip_sample_t const* in, long count )
{
if ( buffer_size_ == silent_buf_size )
{
assert( 0 );
return;
}
buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2;
int const sample_shift = blip_sample_bits - 16;
int prev = 0;
while ( count-- )
{
blip_long s = (blip_long) *in++ << sample_shift;
*out += s - prev;
prev = s;
++out;
}
*out -= prev;
}

489
Frameworks/GME/gme/Blip_Buffer.h Executable file
View file

@ -0,0 +1,489 @@
// Band-limited sound synthesis buffer
// Blip_Buffer 0.4.1
#ifndef BLIP_BUFFER_H
#define BLIP_BUFFER_H
// internal
#include <limits.h>
#if INT_MAX >= 0x7FFFFFFF
typedef int blip_long;
typedef unsigned blip_ulong;
#else
typedef long blip_long;
typedef unsigned long blip_ulong;
#endif
// Time unit at source clock rate
typedef blip_long blip_time_t;
// Output samples are 16-bit signed, with a range of -32768 to 32767
typedef short blip_sample_t;
enum { blip_sample_max = 32767 };
class Blip_Buffer {
public:
typedef const char* blargg_err_t;
// Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults
// to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there
// isn't enough memory, returns error without affecting current buffer setup.
blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 );
// Set number of source time units per second
void clock_rate( long );
// End current time frame of specified duration and make its samples available
// (along with any still-unread samples) for reading with read_samples(). Begins
// a new time frame at the end of the current frame.
void end_frame( blip_time_t time );
// Read at most 'max_samples' out of buffer into 'dest', removing them from from
// the buffer. Returns number of samples actually read and removed. If stereo is
// true, increments 'dest' one extra time after writing each sample, to allow
// easy interleving of two channels into a stereo output buffer.
long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 );
// Additional optional features
// Current output sample rate
long sample_rate() const;
// Length of buffer, in milliseconds
int length() const;
// Number of source time units per second
long clock_rate() const;
// Set frequency high-pass filter frequency, where higher values reduce bass more
void bass_freq( int frequency );
// Number of samples delay from synthesis to samples read out
int output_latency() const;
// Remove all available samples and clear buffer to silence. If 'entire_buffer' is
// false, just clears out any samples waiting rather than the entire buffer.
void clear( int entire_buffer = 1 );
// Number of samples available for reading with read_samples()
long samples_avail() const;
// Remove 'count' samples from those waiting to be read
void remove_samples( long count );
// Experimental features
// Count number of clocks needed until 'count' samples will be available.
// If buffer can't even hold 'count' samples, returns number of clocks until
// buffer becomes full.
blip_time_t count_clocks( long count ) const;
// Number of raw samples that can be mixed within frame of specified duration.
long count_samples( blip_time_t duration ) const;
// Mix 'count' samples from 'buf' into buffer.
void mix_samples( blip_sample_t const* buf, long count );
// not documented yet
void set_modified() { modified_ = 1; }
int clear_modified() { int b = modified_; modified_ = 0; return b; }
typedef blip_ulong blip_resampled_time_t;
void remove_silence( long count );
blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; }
blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; }
blip_resampled_time_t clock_rate_factor( long clock_rate ) const;
public:
Blip_Buffer();
~Blip_Buffer();
// Deprecated
typedef blip_resampled_time_t resampled_time_t;
blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); }
blargg_err_t sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); }
private:
// noncopyable
Blip_Buffer( const Blip_Buffer& );
Blip_Buffer& operator = ( const Blip_Buffer& );
public:
typedef blip_time_t buf_t_;
blip_ulong factor_;
blip_resampled_time_t offset_;
buf_t_* buffer_;
blip_long buffer_size_;
blip_long reader_accum_;
int bass_shift_;
private:
long sample_rate_;
long clock_rate_;
int bass_freq_;
int length_;
int modified_;
friend class Blip_Reader;
};
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
// Number of bits in resample ratio fraction. Higher values give a more accurate ratio
// but reduce maximum buffer size.
#ifndef BLIP_BUFFER_ACCURACY
#define BLIP_BUFFER_ACCURACY 16
#endif
// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in
// noticeable broadband noise when synthesizing high frequency square waves.
// Affects size of Blip_Synth objects since they store the waveform directly.
#ifndef BLIP_PHASE_BITS
#if BLIP_BUFFER_FAST
#define BLIP_PHASE_BITS 8
#else
#define BLIP_PHASE_BITS 6
#endif
#endif
// Internal
typedef blip_ulong blip_resampled_time_t;
int const blip_widest_impulse_ = 16;
int const blip_buffer_extra_ = blip_widest_impulse_ + 2;
int const blip_res = 1 << BLIP_PHASE_BITS;
class blip_eq_t;
class Blip_Synth_Fast_ {
public:
Blip_Buffer* buf;
int last_amp;
int delta_factor;
void volume_unit( double );
Blip_Synth_Fast_();
void treble_eq( blip_eq_t const& ) { }
};
class Blip_Synth_ {
public:
Blip_Buffer* buf;
int last_amp;
int delta_factor;
void volume_unit( double );
Blip_Synth_( short* impulses, int width );
void treble_eq( blip_eq_t const& );
private:
double volume_unit_;
short* const impulses;
int const width;
blip_long kernel_unit;
int impulses_size() const { return blip_res / 2 * width + 1; }
void adjust_impulse();
};
// Quality level. Start with blip_good_quality.
const int blip_med_quality = 8;
const int blip_good_quality = 12;
const int blip_high_quality = 16;
// Range specifies the greatest expected change in amplitude. Calculate it
// by finding the difference between the maximum and minimum expected
// amplitudes (max - min).
template<int quality,int range>
class Blip_Synth {
public:
// Set overall volume of waveform
void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); }
// Configure low-pass filter (see blip_buffer.txt)
void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); }
// Get/set Blip_Buffer used for output
Blip_Buffer* output() const { return impl.buf; }
void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; }
// Update amplitude of waveform at given time. Using this requires a separate
// Blip_Synth for each waveform.
void update( blip_time_t time, int amplitude );
// Low-level interface
// Add an amplitude transition of specified delta, optionally into specified buffer
// rather than the one set with output(). Delta can be positive or negative.
// The actual change in amplitude is delta * (volume / range)
void offset( blip_time_t, int delta, Blip_Buffer* ) const;
void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); }
// Works directly in terms of fractional output samples. Contact author for more info.
void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const;
// Same as offset(), except code is inlined for higher performance
void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const {
offset_resampled( t * buf->factor_ + buf->offset_, delta, buf );
}
void offset_inline( blip_time_t t, int delta ) const {
offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf );
}
private:
#if BLIP_BUFFER_FAST
Blip_Synth_Fast_ impl;
#else
Blip_Synth_ impl;
typedef short imp_t;
imp_t impulses [blip_res * (quality / 2) + 1];
public:
Blip_Synth() : impl( impulses, quality ) { }
#endif
};
// Low-pass equalization parameters
class blip_eq_t {
public:
// Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce
// treble, small positive values (0 to 5.0) increase treble.
blip_eq_t( double treble_db = 0 );
// See blip_buffer.txt
blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 );
private:
double treble;
long rolloff_freq;
long sample_rate;
long cutoff_freq;
void generate( float* out, int count ) const;
friend class Blip_Synth_;
};
int const blip_sample_bits = 30;
// Dummy Blip_Buffer to direct sound output to, for easy muting without
// having to stop sound code.
class Silent_Blip_Buffer : public Blip_Buffer {
buf_t_ buf [blip_buffer_extra_ + 1];
public:
// The following cannot be used (an assertion will fail if attempted):
blargg_err_t set_sample_rate( long samples_per_sec, int msec_length );
blip_time_t count_clocks( long count ) const;
void mix_samples( blip_sample_t const* buf, long count );
Silent_Blip_Buffer();
};
#if defined (__GNUC__) || _MSC_VER >= 1100
#define BLIP_RESTRICT __restrict
#else
#define BLIP_RESTRICT
#endif
// Optimized reading from Blip_Buffer, for use in custom sample output
// Begin reading from buffer. Name should be unique to the current block.
#define BLIP_READER_BEGIN( name, blip_buffer ) \
const Blip_Buffer::buf_t_* BLIP_RESTRICT name##_reader_buf = (blip_buffer).buffer_;\
blip_long name##_reader_accum = (blip_buffer).reader_accum_
// Get value to pass to BLIP_READER_NEXT()
#define BLIP_READER_BASS( blip_buffer ) ((blip_buffer).bass_shift_)
// Constant value to use instead of BLIP_READER_BASS(), for slightly more optimal
// code at the cost of having no bass control
int const blip_reader_default_bass = 9;
// Current sample
#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16))
// Current raw sample in full internal resolution
#define BLIP_READER_READ_RAW( name ) (name##_reader_accum)
// Advance to next sample
#define BLIP_READER_NEXT( name, bass ) \
(void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass)))
// End reading samples from buffer. The number of samples read must now be removed
// using Blip_Buffer::remove_samples().
#define BLIP_READER_END( name, blip_buffer ) \
(void) ((blip_buffer).reader_accum_ = name##_reader_accum)
// Compatibility with older version
const long blip_unscaled = 65535;
const int blip_low_quality = blip_med_quality;
const int blip_best_quality = blip_high_quality;
// Deprecated; use BLIP_READER macros as follows:
// Blip_Reader r; r.begin( buf ); -> BLIP_READER_BEGIN( r, buf );
// int bass = r.begin( buf ) -> BLIP_READER_BEGIN( r, buf ); int bass = BLIP_READER_BASS( buf );
// r.read() -> BLIP_READER_READ( r )
// r.read_raw() -> BLIP_READER_READ_RAW( r )
// r.next( bass ) -> BLIP_READER_NEXT( r, bass )
// r.next() -> BLIP_READER_NEXT( r, blip_reader_default_bass )
// r.end( buf ) -> BLIP_READER_END( r, buf )
class Blip_Reader {
public:
int begin( Blip_Buffer& );
blip_long read() const { return accum >> (blip_sample_bits - 16); }
blip_long read_raw() const { return accum; }
void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); }
void end( Blip_Buffer& b ) { b.reader_accum_ = accum; }
private:
const Blip_Buffer::buf_t_* buf;
blip_long accum;
};
// End of public interface
#include <assert.h>
template<int quality,int range>
inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time,
int delta, Blip_Buffer* blip_buf ) const
{
// Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the
// need for a longer buffer as set by set_sample_rate().
assert( (blip_long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ );
delta *= impl.delta_factor;
blip_long* BLIP_RESTRICT buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY);
int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1));
#if BLIP_BUFFER_FAST
blip_long left = buf [0] + delta;
// Kind of crappy, but doing shift after multiply results in overflow.
// Alternate way of delaying multiply by delta_factor results in worse
// sub-sample resolution.
blip_long right = (delta >> BLIP_PHASE_BITS) * phase;
left -= right;
right += buf [1];
buf [0] = left;
buf [1] = right;
#else
int const fwd = (blip_widest_impulse_ - quality) / 2;
int const rev = fwd + quality - 2;
int const mid = quality / 2 - 1;
imp_t const* BLIP_RESTRICT imp = impulses + blip_res - phase;
#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \
defined (__x86_64__) || defined (__ia64__) || defined (__i386__)
// straight forward implementation resulted in better code on GCC for x86
#define ADD_IMP( out, in ) \
buf [out] += (blip_long) imp [blip_res * (in)] * delta
#define BLIP_FWD( i ) {\
ADD_IMP( fwd + i, i );\
ADD_IMP( fwd + 1 + i, i + 1 );\
}
#define BLIP_REV( r ) {\
ADD_IMP( rev - r, r + 1 );\
ADD_IMP( rev + 1 - r, r );\
}
BLIP_FWD( 0 )
if ( quality > 8 ) BLIP_FWD( 2 )
if ( quality > 12 ) BLIP_FWD( 4 )
{
ADD_IMP( fwd + mid - 1, mid - 1 );
ADD_IMP( fwd + mid , mid );
imp = impulses + phase;
}
if ( quality > 12 ) BLIP_REV( 6 )
if ( quality > 8 ) BLIP_REV( 4 )
BLIP_REV( 2 )
ADD_IMP( rev , 1 );
ADD_IMP( rev + 1, 0 );
#else
// for RISC processors, help compiler by reading ahead of writes
#define BLIP_FWD( i ) {\
blip_long t0 = i0 * delta + buf [fwd + i];\
blip_long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i];\
i0 = imp [blip_res * (i + 2)];\
buf [fwd + i] = t0;\
buf [fwd + 1 + i] = t1;\
}
#define BLIP_REV( r ) {\
blip_long t0 = i0 * delta + buf [rev - r];\
blip_long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r];\
i0 = imp [blip_res * (r - 1)];\
buf [rev - r] = t0;\
buf [rev + 1 - r] = t1;\
}
blip_long i0 = *imp;
BLIP_FWD( 0 )
if ( quality > 8 ) BLIP_FWD( 2 )
if ( quality > 12 ) BLIP_FWD( 4 )
{
blip_long t0 = i0 * delta + buf [fwd + mid - 1];
blip_long t1 = imp [blip_res * mid] * delta + buf [fwd + mid ];
imp = impulses + phase;
i0 = imp [blip_res * mid];
buf [fwd + mid - 1] = t0;
buf [fwd + mid ] = t1;
}
if ( quality > 12 ) BLIP_REV( 6 )
if ( quality > 8 ) BLIP_REV( 4 )
BLIP_REV( 2 )
blip_long t0 = i0 * delta + buf [rev ];
blip_long t1 = *imp * delta + buf [rev + 1];
buf [rev ] = t0;
buf [rev + 1] = t1;
#endif
#endif
}
#undef BLIP_FWD
#undef BLIP_REV
template<int quality,int range>
#if BLIP_BUFFER_FAST
inline
#endif
void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const
{
offset_resampled( t * buf->factor_ + buf->offset_, delta, buf );
}
template<int quality,int range>
#if BLIP_BUFFER_FAST
inline
#endif
void Blip_Synth<quality,range>::update( blip_time_t t, int amp )
{
int delta = amp - impl.last_amp;
impl.last_amp = amp;
offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf );
}
inline blip_eq_t::blip_eq_t( double t ) :
treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { }
inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) :
treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { }
inline int Blip_Buffer::length() const { return length_; }
inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); }
inline long Blip_Buffer::sample_rate() const { return sample_rate_; }
inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; }
inline long Blip_Buffer::clock_rate() const { return clock_rate_; }
inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); }
inline int Blip_Reader::begin( Blip_Buffer& blip_buf )
{
buf = blip_buf.buffer_;
accum = blip_buf.reader_accum_;
return blip_buf.bass_shift_;
}
int const blip_max_length = 0;
int const blip_default_length = 250;
#endif

View file

@ -0,0 +1,184 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Classic_Emu.h"
#include "Multi_Buffer.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Classic_Emu::Classic_Emu()
{
buf = 0;
stereo_buffer = 0;
voice_types = 0;
// avoid inconsistency in our duplicated constants
assert( (int) wave_type == (int) Multi_Buffer::wave_type );
assert( (int) noise_type == (int) Multi_Buffer::noise_type );
assert( (int) mixed_type == (int) Multi_Buffer::mixed_type );
}
Classic_Emu::~Classic_Emu()
{
delete stereo_buffer;
}
void Classic_Emu::set_equalizer_( equalizer_t const& eq )
{
Music_Emu::set_equalizer_( eq );
update_eq( eq.treble );
if ( buf )
buf->bass_freq( equalizer().bass );
}
blargg_err_t Classic_Emu::set_sample_rate_( long rate )
{
if ( !buf )
{
if ( !stereo_buffer )
CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer );
buf = stereo_buffer;
}
return buf->set_sample_rate( rate, 1000 / 20 );
}
void Classic_Emu::mute_voices_( int mask )
{
Music_Emu::mute_voices_( mask );
for ( int i = voice_count(); i--; )
{
if ( mask & (1 << i) )
{
set_voice( i, 0, 0, 0 );
}
else
{
Multi_Buffer::channel_t ch = buf->channel( i, (voice_types ? voice_types [i] : 0) );
assert( (ch.center && ch.left && ch.right) ||
(!ch.center && !ch.left && !ch.right) ); // all or nothing
set_voice( i, ch.center, ch.left, ch.right );
}
}
}
void Classic_Emu::change_clock_rate( long rate )
{
clock_rate_ = rate;
buf->clock_rate( rate );
}
blargg_err_t Classic_Emu::setup_buffer( long rate )
{
change_clock_rate( rate );
RETURN_ERR( buf->set_channel_count( voice_count() ) );
set_equalizer( equalizer() );
buf_changed_count = buf->channels_changed_count();
return 0;
}
blargg_err_t Classic_Emu::start_track_( int track )
{
RETURN_ERR( Music_Emu::start_track_( track ) );
buf->clear();
return 0;
}
blargg_err_t Classic_Emu::play_( long count, sample_t* out )
{
long remain = count;
while ( remain )
{
remain -= buf->read_samples( &out [count - remain], remain );
if ( remain )
{
if ( buf_changed_count != buf->channels_changed_count() )
{
buf_changed_count = buf->channels_changed_count();
remute_voices();
}
int msec = buf->length();
blip_time_t clocks_emulated = (blargg_long) msec * clock_rate_ / 1000;
RETURN_ERR( run_clocks( clocks_emulated, msec ) );
assert( clocks_emulated );
buf->end_frame( clocks_emulated );
}
}
return 0;
}
// Rom_Data
blargg_err_t Rom_Data_::load_rom_data_( Data_Reader& in,
int header_size, void* header_out, int fill, long pad_size )
{
long file_offset = pad_size - header_size;
rom_addr = 0;
mask = 0;
size_ = 0;
rom.clear();
file_size_ = in.remain();
if ( file_size_ <= header_size ) // <= because there must be data after header
return gme_wrong_file_type;
blargg_err_t err = rom.resize( file_offset + file_size_ + pad_size );
if ( !err )
err = in.read( rom.begin() + file_offset, file_size_ );
if ( err )
{
rom.clear();
return err;
}
file_size_ -= header_size;
memcpy( header_out, &rom [file_offset], header_size );
memset( rom.begin() , fill, pad_size );
memset( rom.end() - pad_size, fill, pad_size );
return 0;
}
void Rom_Data_::set_addr_( long addr, int unit )
{
rom_addr = addr - unit - pad_extra;
long rounded = (addr + file_size_ + unit - 1) / unit * unit;
if ( rounded <= 0 )
{
rounded = 0;
}
else
{
int shift = 0;
unsigned long max_addr = (unsigned long) (rounded - 1);
while ( max_addr >> shift )
shift++;
mask = (1L << shift) - 1;
}
if ( addr < 0 )
addr = 0;
size_ = rounded;
if ( rom.resize( rounded - rom_addr + pad_extra ) ) { } // OK if shrink fails
if ( 0 )
{
dprintf( "addr: %X\n", addr );
dprintf( "file_size: %d\n", file_size_ );
dprintf( "rounded: %d\n", rounded );
dprintf( "mask: $%X\n", mask );
}
}

127
Frameworks/GME/gme/Classic_Emu.h Executable file
View file

@ -0,0 +1,127 @@
// Common aspects of emulators which use Blip_Buffer for sound output
// Game_Music_Emu 0.5.2
#ifndef CLASSIC_EMU_H
#define CLASSIC_EMU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
#include "Music_Emu.h"
class Classic_Emu : public Music_Emu {
public:
Classic_Emu();
~Classic_Emu();
void set_buffer( Multi_Buffer* );
protected:
// Services
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
void set_voice_types( int const* t ) { voice_types = t; }
blargg_err_t setup_buffer( long clock_rate );
long clock_rate() const { return clock_rate_; }
void change_clock_rate( long ); // experimental
// Overridable
virtual void set_voice( int index, Blip_Buffer* center,
Blip_Buffer* left, Blip_Buffer* right ) = 0;
virtual void update_eq( blip_eq_t const& ) = 0;
virtual blargg_err_t start_track_( int track ) = 0;
virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) = 0;
protected:
blargg_err_t set_sample_rate_( long sample_rate );
void mute_voices_( int );
void set_equalizer_( equalizer_t const& );
blargg_err_t play_( long, sample_t* );
private:
Multi_Buffer* buf;
Multi_Buffer* stereo_buffer; // NULL if using custom buffer
long clock_rate_;
unsigned buf_changed_count;
int const* voice_types;
};
inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf )
{
assert( !buf && new_buf );
buf = new_buf;
}
// ROM data handler, used by several Classic_Emu derivitives. Loads file data
// with padding on both sides, allowing direct use in bank mapping. The main purpose
// is to allow all file data to be loaded with only one read() call (for efficiency).
class Rom_Data_ {
public:
typedef unsigned char byte;
protected:
enum { pad_extra = 8 };
blargg_vector<byte> rom;
long file_size_;
blargg_long rom_addr;
blargg_long mask;
blargg_long size_; // TODO: eliminate
blargg_err_t load_rom_data_( Data_Reader& in, int header_size, void* header_out,
int fill, long pad_size );
void set_addr_( long addr, int unit );
};
template<int unit>
class Rom_Data : public Rom_Data_ {
enum { pad_size = unit + pad_extra };
public:
// Load file data, using already-loaded header 'h' if not NULL. Copy header
// from loaded file data into *out and fill unmapped bytes with 'fill'.
blargg_err_t load( Data_Reader& in, int header_size, void* header_out, int fill )
{
return load_rom_data_( in, header_size, header_out, fill, pad_size );
}
// Size of file data read in (excluding header)
long file_size() const { return file_size_; }
// Pointer to beginning of file data
byte* begin() const { return rom.begin() + pad_size; }
// Set address that file data should start at
void set_addr( long addr ) { set_addr_( addr, unit ); }
// Free data
void clear() { rom.clear(); }
// Size of data + start addr, rounded to a multiple of unit
long size() const { return size_; }
// Pointer to unmapped page filled with same value
byte* unmapped() { return rom.begin(); }
// Mask address to nearest power of two greater than size()
blargg_long mask_addr( blargg_long addr ) const
{
#ifdef check
check( addr <= mask );
#endif
return addr & mask;
}
// Pointer to page starting at addr. Returns unmapped() if outside data.
byte* at_addr( blargg_long addr )
{
blargg_ulong offset = mask_addr( addr ) - rom_addr;
if ( offset > blargg_ulong (rom.size() - pad_size) )
offset = 0; // unmapped
return &rom [offset];
}
};
#ifndef GME_APU_HOOK
#define GME_APU_HOOK( emu, addr, data ) ((void) 0)
#endif
#ifndef GME_FRAME_HOOK
#define GME_FRAME_HOOK( emu ) ((void) 0)
#else
#define GME_FRAME_HOOK_DEFINED 1
#endif
#endif

View file

@ -0,0 +1,315 @@
// File_Extractor 0.4.0. http://www.slack.net/~ant/
#include "Data_Reader.h"
#include "blargg_endian.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
/* Copyright (C) 2005-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
const char Data_Reader::eof_error [] = "Unexpected end of file";
blargg_err_t Data_Reader::read( void* p, long s )
{
long result = read_avail( p, s );
if ( result != s )
{
if ( result >= 0 && result < s )
return eof_error;
return "Read error";
}
return 0;
}
blargg_err_t Data_Reader::skip( long count )
{
char buf [512];
while ( count )
{
long n = sizeof buf;
if ( n > count )
n = count;
count -= n;
RETURN_ERR( read( buf, n ) );
}
return 0;
}
long File_Reader::remain() const { return size() - tell(); }
blargg_err_t File_Reader::skip( long n )
{
assert( n >= 0 );
if ( !n )
return 0;
return seek( tell() + n );
}
// Subset_Reader
Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
{
in = dr;
remain_ = dr->remain();
if ( remain_ > size )
remain_ = size;
}
long Subset_Reader::remain() const { return remain_; }
long Subset_Reader::read_avail( void* p, long s )
{
if ( s > remain_ )
s = remain_;
remain_ -= s;
return in->read_avail( p, s );
}
// Remaining_Reader
Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
{
header = (char const*) h;
header_end = header + size;
in = r;
}
long Remaining_Reader::remain() const { return header_end - header + in->remain(); }
long Remaining_Reader::read_first( void* out, long count )
{
long first = header_end - header;
if ( first )
{
if ( first > count )
first = count;
void const* old = header;
header += first;
memcpy( out, old, first );
}
return first;
}
long Remaining_Reader::read_avail( void* out, long count )
{
long first = read_first( out, count );
long second = count - first;
if ( second )
{
second = in->read_avail( (char*) out + first, second );
if ( second <= 0 )
return second;
}
return first + second;
}
blargg_err_t Remaining_Reader::read( void* out, long count )
{
long first = read_first( out, count );
long second = count - first;
if ( !second )
return 0;
return in->read( (char*) out + first, second );
}
// Mem_File_Reader
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
begin( (const char*) p ),
size_( s )
{
pos = 0;
}
long Mem_File_Reader::size() const { return size_; }
long Mem_File_Reader::read_avail( void* p, long s )
{
long r = remain();
if ( s > r )
s = r;
memcpy( p, begin + pos, s );
pos += s;
return s;
}
long Mem_File_Reader::tell() const { return pos; }
blargg_err_t Mem_File_Reader::seek( long n )
{
if ( n > size_ )
return eof_error;
pos = n;
return 0;
}
// Callback_Reader
Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
callback( c ),
data( d )
{
remain_ = size;
}
long Callback_Reader::remain() const { return remain_; }
long Callback_Reader::read_avail( void* out, long count )
{
if ( count > remain_ )
count = remain_;
if ( Callback_Reader::read( out, count ) )
count = -1;
return count;
}
blargg_err_t Callback_Reader::read( void* out, long count )
{
if ( count > remain_ )
return eof_error;
return callback( data, out, count );
}
// Std_File_Reader
Std_File_Reader::Std_File_Reader() : file_( 0 ) { }
Std_File_Reader::~Std_File_Reader() { close(); }
blargg_err_t Std_File_Reader::open( const char* path )
{
file_ = fopen( path, "rb" );
if ( !file_ )
return "Couldn't open file";
return 0;
}
long Std_File_Reader::size() const
{
long pos = tell();
fseek( (FILE*) file_, 0, SEEK_END );
long result = tell();
fseek( (FILE*) file_, pos, SEEK_SET );
return result;
}
long Std_File_Reader::read_avail( void* p, long s )
{
return fread( p, 1, s, (FILE*) file_ );
}
blargg_err_t Std_File_Reader::read( void* p, long s )
{
if ( s == (long) fread( p, 1, s, (FILE*) file_ ) )
return 0;
if ( feof( (FILE*) file_ ) )
return eof_error;
return "Couldn't read from file";
}
long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); }
blargg_err_t Std_File_Reader::seek( long n )
{
if ( !fseek( (FILE*) file_, n, SEEK_SET ) )
return 0;
if ( n > size() )
return eof_error;
return "Error seeking in file";
}
void Std_File_Reader::close()
{
if ( file_ )
{
fclose( (FILE*) file_ );
file_ = 0;
}
}
// Gzip_File_Reader
#ifdef HAVE_ZLIB_H
#include "zlib.h"
static const char* get_gzip_eof( const char* path, long* eof )
{
FILE* file = fopen( path, "rb" );
if ( !file )
return "Couldn't open file";
unsigned char buf [4];
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B )
{
fseek( file, -4, SEEK_END );
fread( buf, 4, 1, file );
*eof = get_le32( buf );
}
else
{
fseek( file, 0, SEEK_END );
*eof = ftell( file );
}
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0;
fclose( file );
return err;
}
Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { }
Gzip_File_Reader::~Gzip_File_Reader() { close(); }
blargg_err_t Gzip_File_Reader::open( const char* path )
{
close();
RETURN_ERR( get_gzip_eof( path, &size_ ) );
file_ = gzopen( path, "rb" );
if ( !file_ )
return "Couldn't open file";
return 0;
}
long Gzip_File_Reader::size() const { return size_; }
long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); }
long Gzip_File_Reader::tell() const { return gztell( file_ ); }
blargg_err_t Gzip_File_Reader::seek( long n )
{
if ( gzseek( file_, n, SEEK_SET ) >= 0 )
return 0;
if ( n > size_ )
return eof_error;
return "Error seeking in file";
}
void Gzip_File_Reader::close()
{
if ( file_ )
{
gzclose( file_ );
file_ = 0;
}
}
#endif

151
Frameworks/GME/gme/Data_Reader.h Executable file
View file

@ -0,0 +1,151 @@
// Data reader interface for uniform access
// File_Extractor 0.4.0
#ifndef DATA_READER_H
#define DATA_READER_H
#include "blargg_common.h"
// Supports reading and finding out how many bytes are remaining
class Data_Reader {
public:
virtual ~Data_Reader() { }
static const char eof_error []; // returned by read() when request goes beyond end
// Read at most count bytes and return number actually read, or <= 0 if error
virtual long read_avail( void*, long n ) = 0;
// Read exactly count bytes and return error if they couldn't be read
virtual blargg_err_t read( void*, long count );
// Number of bytes remaining until end of file
virtual long remain() const = 0;
// Read and discard count bytes
virtual blargg_err_t skip( long count );
public:
Data_Reader() { }
typedef blargg_err_t error_t; // deprecated
private:
// noncopyable
Data_Reader( const Data_Reader& );
Data_Reader& operator = ( const Data_Reader& );
};
// Supports seeking in addition to Data_Reader operations
class File_Reader : public Data_Reader {
public:
// Size of file
virtual long size() const = 0;
// Current position in file
virtual long tell() const = 0;
// Go to new position
virtual blargg_err_t seek( long ) = 0;
long remain() const;
blargg_err_t skip( long n );
};
// Disk file reader
class Std_File_Reader : public File_Reader {
public:
blargg_err_t open( const char* path );
void close();
public:
Std_File_Reader();
~Std_File_Reader();
long size() const;
blargg_err_t read( void*, long );
long read_avail( void*, long );
long tell() const;
blargg_err_t seek( long );
private:
void* file_;
};
// Treats range of memory as a file
class Mem_File_Reader : public File_Reader {
public:
Mem_File_Reader( const void*, long size );
public:
long size() const;
long read_avail( void*, long );
long tell() const;
blargg_err_t seek( long );
private:
const char* const begin;
const long size_;
long pos;
};
// Makes it look like there are only count bytes remaining
class Subset_Reader : public Data_Reader {
public:
Subset_Reader( Data_Reader*, long count );
public:
long remain() const;
long read_avail( void*, long );
private:
Data_Reader* in;
long remain_;
};
// Joins already-read header and remaining data into original file (to avoid seeking)
class Remaining_Reader : public Data_Reader {
public:
Remaining_Reader( void const* header, long size, Data_Reader* );
public:
long remain() const;
long read_avail( void*, long );
blargg_err_t read( void*, long );
private:
char const* header;
char const* header_end;
Data_Reader* in;
long read_first( void* out, long count );
};
// Invokes callback function to read data. Size of data must be specified in advance.
class Callback_Reader : public Data_Reader {
public:
typedef const char* (*callback_t)( void* data, void* out, long count );
Callback_Reader( callback_t, long size, void* data = 0 );
public:
long read_avail( void*, long );
blargg_err_t read( void*, long );
long remain() const;
private:
callback_t const callback;
void* const data;
long remain_;
};
#ifdef HAVE_ZLIB_H
// Gzip compressed file reader
class Gzip_File_Reader : public File_Reader {
public:
blargg_err_t open( const char* path );
void close();
public:
Gzip_File_Reader();
~Gzip_File_Reader();
long size() const;
long read_avail( void*, long );
long tell() const;
blargg_err_t seek( long );
private:
void* file_;
long size_;
};
#endif
#endif

View file

@ -0,0 +1,131 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Dual_Resampler.h"
#include <stdlib.h>
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
unsigned const resampler_extra = 256;
Dual_Resampler::Dual_Resampler() { }
Dual_Resampler::~Dual_Resampler() { }
blargg_err_t Dual_Resampler::reset( int pairs )
{
// expand allocations a bit
RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) );
resize( pairs );
resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2);
return resampler.buffer_size( resampler_size );
}
void Dual_Resampler::resize( int pairs )
{
int new_sample_buf_size = pairs * 2;
if ( sample_buf_size != new_sample_buf_size )
{
if ( (unsigned) new_sample_buf_size > sample_buf.size() )
{
check( false );
return;
}
sample_buf_size = new_sample_buf_size;
oversamples_per_frame = int (pairs * resampler.ratio()) * 2 + 2;
clear();
}
}
void Dual_Resampler::play_frame_( Blip_Buffer& blip_buf, dsample_t* out )
{
long pair_count = sample_buf_size >> 1;
blip_time_t blip_time = blip_buf.count_clocks( pair_count );
int sample_count = oversamples_per_frame - resampler.written();
int new_count = play_frame( blip_time, sample_count, resampler.buffer() );
assert( new_count < resampler_size );
blip_buf.end_frame( blip_time );
assert( blip_buf.samples_avail() == pair_count );
resampler.write( new_count );
long count = resampler.read( sample_buf.begin(), sample_buf_size );
assert( count == (long) sample_buf_size );
mix_samples( blip_buf, out );
blip_buf.remove_samples( pair_count );
}
void Dual_Resampler::dual_play( long count, dsample_t* out, Blip_Buffer& blip_buf )
{
// empty extra buffer
long remain = sample_buf_size - buf_pos;
if ( remain )
{
if ( remain > count )
remain = count;
count -= remain;
memcpy( out, &sample_buf [buf_pos], remain * sizeof *out );
out += remain;
buf_pos += remain;
}
// entire frames
while ( count >= (long) sample_buf_size )
{
play_frame_( blip_buf, out );
out += sample_buf_size;
count -= sample_buf_size;
}
// extra
if ( count )
{
play_frame_( blip_buf, sample_buf.begin() );
buf_pos = count;
memcpy( out, sample_buf.begin(), count * sizeof *out );
out += count;
}
}
void Dual_Resampler::mix_samples( Blip_Buffer& blip_buf, dsample_t* out )
{
Blip_Reader sn;
int bass = sn.begin( blip_buf );
const dsample_t* in = sample_buf.begin();
for ( int n = sample_buf_size >> 1; n--; )
{
int s = sn.read();
blargg_long l = (blargg_long) in [0] * 2 + s;
if ( (BOOST::int16_t) l != l )
l = 0x7FFF - (l >> 24);
sn.next( bass );
blargg_long r = (blargg_long) in [1] * 2 + s;
if ( (BOOST::int16_t) r != r )
r = 0x7FFF - (r >> 24);
in += 2;
out [0] = l;
out [1] = r;
out += 2;
}
sn.end( blip_buf );
}

View file

@ -0,0 +1,50 @@
// Combination of Fir_Resampler and Blip_Buffer mixing. Used by Sega FM emulators.
// Game_Music_Emu 0.5.2
#ifndef DUAL_RESAMPLER_H
#define DUAL_RESAMPLER_H
#include "Fir_Resampler.h"
#include "Blip_Buffer.h"
class Dual_Resampler {
public:
Dual_Resampler();
virtual ~Dual_Resampler();
typedef short dsample_t;
double setup( double oversample, double rolloff, double gain );
blargg_err_t reset( int max_pairs );
void resize( int pairs_per_frame );
void clear();
void dual_play( long count, dsample_t* out, Blip_Buffer& );
protected:
virtual int play_frame( blip_time_t, int pcm_count, dsample_t* pcm_out ) = 0;
private:
blargg_vector<dsample_t> sample_buf;
int sample_buf_size;
int oversamples_per_frame;
int buf_pos;
int resampler_size;
Fir_Resampler<12> resampler;
void mix_samples( Blip_Buffer&, dsample_t* );
void play_frame_( Blip_Buffer&, dsample_t* );
};
inline double Dual_Resampler::setup( double oversample, double rolloff, double gain )
{
return resampler.time_ratio( oversample, rolloff, gain * 0.5 );
}
inline void Dual_Resampler::clear()
{
buf_pos = sample_buf_size;
resampler.clear();
}
#endif

View file

@ -0,0 +1,529 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Effects_Buffer.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
typedef blargg_long fixed_t;
#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5)
#define FMUL( x, y ) (((x) * (y)) >> 15)
const unsigned echo_size = 4096;
const unsigned echo_mask = echo_size - 1;
BOOST_STATIC_ASSERT( (echo_size & echo_mask) == 0 ); // must be power of 2
const unsigned reverb_size = 8192 * 2;
const unsigned reverb_mask = reverb_size - 1;
BOOST_STATIC_ASSERT( (reverb_size & reverb_mask) == 0 ); // must be power of 2
Effects_Buffer::config_t::config_t()
{
pan_1 = -0.15f;
pan_2 = 0.15f;
reverb_delay = 88.0f;
reverb_level = 0.12f;
echo_delay = 61.0f;
echo_level = 0.10f;
delay_variance = 18.0f;
effects_enabled = false;
}
void Effects_Buffer::set_depth( double d )
{
float f = (float) d;
config_t c;
c.pan_1 = -0.6f * f;
c.pan_2 = 0.6f * f;
c.reverb_delay = 880 * 0.1f;
c.echo_delay = 610 * 0.1f;
if ( f > 0.5 )
f = 0.5; // TODO: more linear reduction of extreme reverb/echo
c.reverb_level = 0.5f * f;
c.echo_level = 0.30f * f;
c.delay_variance = 180 * 0.1f;
c.effects_enabled = (d > 0.0f);
config( c );
}
Effects_Buffer::Effects_Buffer( bool center_only ) : Multi_Buffer( 2 )
{
buf_count = center_only ? max_buf_count - 4 : max_buf_count;
echo_pos = 0;
reverb_pos = 0;
stereo_remain = 0;
effect_remain = 0;
effects_enabled = false;
set_depth( 0 );
}
Effects_Buffer::~Effects_Buffer() { }
blargg_err_t Effects_Buffer::set_sample_rate( long rate, int msec )
{
if ( !echo_buf.size() )
RETURN_ERR( echo_buf.resize( echo_size ) );
if ( !reverb_buf.size() )
RETURN_ERR( reverb_buf.resize( reverb_size ) );
for ( int i = 0; i < buf_count; i++ )
RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) );
config( config_ );
clear();
return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() );
}
void Effects_Buffer::clock_rate( long rate )
{
for ( int i = 0; i < buf_count; i++ )
bufs [i].clock_rate( rate );
}
void Effects_Buffer::bass_freq( int freq )
{
for ( int i = 0; i < buf_count; i++ )
bufs [i].bass_freq( freq );
}
void Effects_Buffer::clear()
{
stereo_remain = 0;
effect_remain = 0;
if ( echo_buf.size() )
memset( &echo_buf [0], 0, echo_size * sizeof echo_buf [0] );
if ( reverb_buf.size() )
memset( &reverb_buf [0], 0, reverb_size * sizeof reverb_buf [0] );
for ( int i = 0; i < buf_count; i++ )
bufs [i].clear();
}
inline int pin_range( int n, int max, int min = 0 )
{
if ( n < min )
return min;
if ( n > max )
return max;
return n;
}
void Effects_Buffer::config( const config_t& cfg )
{
channels_changed();
// clear echo and reverb buffers
if ( !config_.effects_enabled && cfg.effects_enabled && echo_buf.size() )
{
memset( &echo_buf [0], 0, echo_size * sizeof echo_buf [0] );
memset( &reverb_buf [0], 0, reverb_size * sizeof reverb_buf [0] );
}
config_ = cfg;
if ( config_.effects_enabled )
{
// convert to internal format
chans.pan_1_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_1 );
chans.pan_1_levels [1] = TO_FIXED( 2 ) - chans.pan_1_levels [0];
chans.pan_2_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_2 );
chans.pan_2_levels [1] = TO_FIXED( 2 ) - chans.pan_2_levels [0];
chans.reverb_level = TO_FIXED( config_.reverb_level );
chans.echo_level = TO_FIXED( config_.echo_level );
int delay_offset = int (1.0 / 2000 * config_.delay_variance * sample_rate());
int reverb_sample_delay = int (1.0 / 1000 * config_.reverb_delay * sample_rate());
chans.reverb_delay_l = pin_range( reverb_size -
(reverb_sample_delay - delay_offset) * 2, reverb_size - 2, 0 );
chans.reverb_delay_r = pin_range( reverb_size + 1 -
(reverb_sample_delay + delay_offset) * 2, reverb_size - 1, 1 );
int echo_sample_delay = int (1.0 / 1000 * config_.echo_delay * sample_rate());
chans.echo_delay_l = pin_range( echo_size - 1 - (echo_sample_delay - delay_offset),
echo_size - 1 );
chans.echo_delay_r = pin_range( echo_size - 1 - (echo_sample_delay + delay_offset),
echo_size - 1 );
chan_types [0].center = &bufs [0];
chan_types [0].left = &bufs [3];
chan_types [0].right = &bufs [4];
chan_types [1].center = &bufs [1];
chan_types [1].left = &bufs [3];
chan_types [1].right = &bufs [4];
chan_types [2].center = &bufs [2];
chan_types [2].left = &bufs [5];
chan_types [2].right = &bufs [6];
assert( 2 < chan_types_count );
}
else
{
// set up outputs
for ( unsigned i = 0; i < chan_types_count; i++ )
{
channel_t& c = chan_types [i];
c.center = &bufs [0];
c.left = &bufs [1];
c.right = &bufs [2];
}
}
if ( buf_count < max_buf_count )
{
for ( int i = 0; i < chan_types_count; i++ )
{
channel_t& c = chan_types [i];
c.left = c.center;
c.right = c.center;
}
}
}
Effects_Buffer::channel_t Effects_Buffer::channel( int i, int type )
{
int out = 2;
if ( !type )
{
out = i % 5;
if ( out > 2 )
out = 2;
}
else if ( !(type & noise_type) && (type & type_index_mask) % 3 != 0 )
{
out = type & 1;
}
return chan_types [out];
}
void Effects_Buffer::end_frame( blip_time_t clock_count )
{
int bufs_used = 0;
for ( int i = 0; i < buf_count; i++ )
{
bufs_used |= bufs [i].clear_modified() << i;
bufs [i].end_frame( clock_count );
}
int stereo_mask = (config_.effects_enabled ? 0x78 : 0x06);
if ( (bufs_used & stereo_mask) && buf_count == max_buf_count )
stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency();
if ( effects_enabled || config_.effects_enabled )
effect_remain = bufs [0].samples_avail() + bufs [0].output_latency();
effects_enabled = config_.effects_enabled;
}
long Effects_Buffer::samples_avail() const
{
return bufs [0].samples_avail() * 2;
}
long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples )
{
require( total_samples % 2 == 0 ); // count must be even
long remain = bufs [0].samples_avail();
if ( remain > (total_samples >> 1) )
remain = (total_samples >> 1);
total_samples = remain;
while ( remain )
{
int active_bufs = buf_count;
long count = remain;
// optimizing mixing to skip any channels which had nothing added
if ( effect_remain )
{
if ( count > effect_remain )
count = effect_remain;
if ( stereo_remain )
{
mix_enhanced( out, count );
}
else
{
mix_mono_enhanced( out, count );
active_bufs = 3;
}
}
else if ( stereo_remain )
{
mix_stereo( out, count );
active_bufs = 3;
}
else
{
mix_mono( out, count );
active_bufs = 1;
}
out += count * 2;
remain -= count;
stereo_remain -= count;
if ( stereo_remain < 0 )
stereo_remain = 0;
effect_remain -= count;
if ( effect_remain < 0 )
effect_remain = 0;
for ( int i = 0; i < buf_count; i++ )
{
if ( i < active_bufs )
bufs [i].remove_samples( count );
else
bufs [i].remove_silence( count ); // keep time synchronized
}
}
return total_samples * 2;
}
void Effects_Buffer::mix_mono( blip_sample_t* out_, blargg_long count )
{
blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [0] );
BLIP_READER_BEGIN( c, bufs [0] );
// unrolled loop
for ( blargg_long n = count >> 1; n; --n )
{
blargg_long cs0 = BLIP_READER_READ( c );
BLIP_READER_NEXT( c, bass );
blargg_long cs1 = BLIP_READER_READ( c );
BLIP_READER_NEXT( c, bass );
if ( (BOOST::int16_t) cs0 != cs0 )
cs0 = 0x7FFF - (cs0 >> 24);
((BOOST::uint32_t*) out) [0] = ((BOOST::uint16_t) cs0) | (cs0 << 16);
if ( (BOOST::int16_t) cs1 != cs1 )
cs1 = 0x7FFF - (cs1 >> 24);
((BOOST::uint32_t*) out) [1] = ((BOOST::uint16_t) cs1) | (cs1 << 16);
out += 4;
}
if ( count & 1 )
{
int s = BLIP_READER_READ( c );
BLIP_READER_NEXT( c, bass );
out [0] = s;
out [1] = s;
if ( (BOOST::int16_t) s != s )
{
s = 0x7FFF - (s >> 24);
out [0] = s;
out [1] = s;
}
}
BLIP_READER_END( c, bufs [0] );
}
void Effects_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count )
{
blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [0] );
BLIP_READER_BEGIN( c, bufs [0] );
BLIP_READER_BEGIN( l, bufs [1] );
BLIP_READER_BEGIN( r, bufs [2] );
while ( count-- )
{
int cs = BLIP_READER_READ( c );
BLIP_READER_NEXT( c, bass );
int left = cs + BLIP_READER_READ( l );
int right = cs + BLIP_READER_READ( r );
BLIP_READER_NEXT( l, bass );
BLIP_READER_NEXT( r, bass );
if ( (BOOST::int16_t) left != left )
left = 0x7FFF - (left >> 24);
out [0] = left;
out [1] = right;
out += 2;
if ( (BOOST::int16_t) right != right )
out [-1] = 0x7FFF - (right >> 24);
}
BLIP_READER_END( r, bufs [2] );
BLIP_READER_END( l, bufs [1] );
BLIP_READER_END( c, bufs [0] );
}
void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out_, blargg_long count )
{
blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [2] );
BLIP_READER_BEGIN( center, bufs [2] );
BLIP_READER_BEGIN( sq1, bufs [0] );
BLIP_READER_BEGIN( sq2, bufs [1] );
blip_sample_t* const reverb_buf = this->reverb_buf.begin();
blip_sample_t* const echo_buf = this->echo_buf.begin();
int echo_pos = this->echo_pos;
int reverb_pos = this->reverb_pos;
while ( count-- )
{
int sum1_s = BLIP_READER_READ( sq1 );
int sum2_s = BLIP_READER_READ( sq2 );
BLIP_READER_NEXT( sq1, bass );
BLIP_READER_NEXT( sq2, bass );
int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) +
FMUL( sum2_s, chans.pan_2_levels [0] ) +
reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask];
int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) +
FMUL( sum2_s, chans.pan_2_levels [1] ) +
reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask];
fixed_t reverb_level = chans.reverb_level;
reverb_buf [reverb_pos] = (blip_sample_t) FMUL( new_reverb_l, reverb_level );
reverb_buf [reverb_pos + 1] = (blip_sample_t) FMUL( new_reverb_r, reverb_level );
reverb_pos = (reverb_pos + 2) & reverb_mask;
int sum3_s = BLIP_READER_READ( center );
BLIP_READER_NEXT( center, bass );
int left = new_reverb_l + sum3_s + FMUL( chans.echo_level,
echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] );
int right = new_reverb_r + sum3_s + FMUL( chans.echo_level,
echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] );
echo_buf [echo_pos] = sum3_s;
echo_pos = (echo_pos + 1) & echo_mask;
if ( (BOOST::int16_t) left != left )
left = 0x7FFF - (left >> 24);
out [0] = left;
out [1] = right;
out += 2;
if ( (BOOST::int16_t) right != right )
out [-1] = 0x7FFF - (right >> 24);
}
this->reverb_pos = reverb_pos;
this->echo_pos = echo_pos;
BLIP_READER_END( sq1, bufs [0] );
BLIP_READER_END( sq2, bufs [1] );
BLIP_READER_END( center, bufs [2] );
}
void Effects_Buffer::mix_enhanced( blip_sample_t* out_, blargg_long count )
{
blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [2] );
BLIP_READER_BEGIN( center, bufs [2] );
BLIP_READER_BEGIN( l1, bufs [3] );
BLIP_READER_BEGIN( r1, bufs [4] );
BLIP_READER_BEGIN( l2, bufs [5] );
BLIP_READER_BEGIN( r2, bufs [6] );
BLIP_READER_BEGIN( sq1, bufs [0] );
BLIP_READER_BEGIN( sq2, bufs [1] );
blip_sample_t* const reverb_buf = this->reverb_buf.begin();
blip_sample_t* const echo_buf = this->echo_buf.begin();
int echo_pos = this->echo_pos;
int reverb_pos = this->reverb_pos;
while ( count-- )
{
int sum1_s = BLIP_READER_READ( sq1 );
int sum2_s = BLIP_READER_READ( sq2 );
BLIP_READER_NEXT( sq1, bass );
BLIP_READER_NEXT( sq2, bass );
int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) +
FMUL( sum2_s, chans.pan_2_levels [0] ) + BLIP_READER_READ( l1 ) +
reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask];
int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) +
FMUL( sum2_s, chans.pan_2_levels [1] ) + BLIP_READER_READ( r1 ) +
reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask];
BLIP_READER_NEXT( l1, bass );
BLIP_READER_NEXT( r1, bass );
fixed_t reverb_level = chans.reverb_level;
reverb_buf [reverb_pos] = (blip_sample_t) FMUL( new_reverb_l, reverb_level );
reverb_buf [reverb_pos + 1] = (blip_sample_t) FMUL( new_reverb_r, reverb_level );
reverb_pos = (reverb_pos + 2) & reverb_mask;
int sum3_s = BLIP_READER_READ( center );
BLIP_READER_NEXT( center, bass );
int left = new_reverb_l + sum3_s + BLIP_READER_READ( l2 ) + FMUL( chans.echo_level,
echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] );
int right = new_reverb_r + sum3_s + BLIP_READER_READ( r2 ) + FMUL( chans.echo_level,
echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] );
BLIP_READER_NEXT( l2, bass );
BLIP_READER_NEXT( r2, bass );
echo_buf [echo_pos] = sum3_s;
echo_pos = (echo_pos + 1) & echo_mask;
if ( (BOOST::int16_t) left != left )
left = 0x7FFF - (left >> 24);
out [0] = left;
out [1] = right;
out += 2;
if ( (BOOST::int16_t) right != right )
out [-1] = 0x7FFF - (right >> 24);
}
this->reverb_pos = reverb_pos;
this->echo_pos = echo_pos;
BLIP_READER_END( l1, bufs [3] );
BLIP_READER_END( r1, bufs [4] );
BLIP_READER_END( l2, bufs [5] );
BLIP_READER_END( r2, bufs [6] );
BLIP_READER_END( sq1, bufs [0] );
BLIP_READER_END( sq2, bufs [1] );
BLIP_READER_END( center, bufs [2] );
}

View file

@ -0,0 +1,86 @@
// Multi-channel effects buffer with panning, echo and reverb
// Game_Music_Emu 0.5.2
#ifndef EFFECTS_BUFFER_H
#define EFFECTS_BUFFER_H
#include "Multi_Buffer.h"
// Effects_Buffer uses several buffers and outputs stereo sample pairs.
class Effects_Buffer : public Multi_Buffer {
public:
// If center_only is true, only center buffers are created and
// less memory is used.
Effects_Buffer( bool center_only = false );
// Channel Effect Center Pan
// ---------------------------------
// 0,5 reverb pan_1
// 1,6 reverb pan_2
// 2,7 echo -
// 3 echo -
// 4 echo -
// Channel configuration
struct config_t {
double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right
double pan_2;
double echo_delay; // msec
double echo_level; // 0.0 to 1.0
double reverb_delay; // msec
double delay_variance; // difference between left/right delays (msec)
double reverb_level; // 0.0 to 1.0
bool effects_enabled; // if false, use optimized simple mixer
config_t();
};
// Set configuration of buffer
virtual void config( const config_t& );
void set_depth( double );
public:
~Effects_Buffer();
blargg_err_t set_sample_rate( long samples_per_sec, int msec = blip_default_length );
void clock_rate( long );
void bass_freq( int );
void clear();
channel_t channel( int, int );
void end_frame( blip_time_t );
long read_samples( blip_sample_t*, long );
long samples_avail() const;
private:
typedef long fixed_t;
enum { max_buf_count = 7 };
Blip_Buffer bufs [max_buf_count];
enum { chan_types_count = 3 };
channel_t chan_types [3];
config_t config_;
long stereo_remain;
long effect_remain;
int buf_count;
bool effects_enabled;
blargg_vector<blip_sample_t> reverb_buf;
blargg_vector<blip_sample_t> echo_buf;
int reverb_pos;
int echo_pos;
struct {
fixed_t pan_1_levels [2];
fixed_t pan_2_levels [2];
int echo_delay_l;
int echo_delay_r;
fixed_t echo_level;
int reverb_delay_l;
int reverb_delay_r;
fixed_t reverb_level;
} chans;
void mix_mono( blip_sample_t*, blargg_long );
void mix_stereo( blip_sample_t*, blargg_long );
void mix_enhanced( blip_sample_t*, blargg_long );
void mix_mono_enhanced( blip_sample_t*, blargg_long );
};
#endif

View file

@ -0,0 +1,199 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Fir_Resampler.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#undef PI
#define PI 3.1415926535897932384626433832795029
static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale,
int count, short* out )
{
double const maxh = 256;
double const step = PI / maxh * spacing;
double const to_w = maxh * 2 / width;
double const pow_a_n = pow( rolloff, maxh );
scale /= maxh * 2;
double angle = (count / 2 - 1 + offset) * -step;
while ( count-- )
{
*out++ = 0;
double w = angle * to_w;
if ( fabs( w ) < PI )
{
double rolloff_cos_a = rolloff * cos( angle );
double num = 1 - rolloff_cos_a -
pow_a_n * cos( maxh * angle ) +
pow_a_n * rolloff * cos( (maxh - 1) * angle );
double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
double sinc = scale * num / den - scale;
out [-1] = (short) (cos( w ) * sinc + sinc);
}
angle += step;
}
}
Fir_Resampler_::Fir_Resampler_( int width, sample_t* impulses_ ) :
width_( width ),
write_offset( width * stereo - stereo ),
impulses( impulses_ )
{
write_pos = 0;
res = 1;
imp_phase = 0;
skip_bits = 0;
step = stereo;
ratio_ = 1.0;
}
Fir_Resampler_::~Fir_Resampler_() { }
void Fir_Resampler_::clear()
{
imp_phase = 0;
if ( buf.size() )
{
write_pos = &buf [write_offset];
memset( buf.begin(), 0, write_offset * sizeof buf [0] );
}
}
blargg_err_t Fir_Resampler_::buffer_size( int new_size )
{
RETURN_ERR( buf.resize( new_size + write_offset ) );
clear();
return 0;
}
double Fir_Resampler_::time_ratio( double new_factor, double rolloff, double gain )
{
ratio_ = new_factor;
double fstep = 0.0;
{
double least_error = 2;
double pos = 0;
res = -1;
for ( int r = 1; r <= max_res; r++ )
{
pos += ratio_;
double nearest = floor( pos + 0.5 );
double error = fabs( pos - nearest );
if ( error < least_error )
{
res = r;
fstep = nearest / res;
least_error = error;
}
}
}
skip_bits = 0;
step = stereo * (int) floor( fstep );
ratio_ = fstep;
fstep = fmod( fstep, 1.0 );
double filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_;
double pos = 0.0;
input_per_cycle = 0;
for ( int i = 0; i < res; i++ )
{
gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter,
double (0x7FFF * gain * filter),
(int) width_, impulses + i * width_ );
pos += fstep;
input_per_cycle += step;
if ( pos >= 0.9999999 )
{
pos -= 1.0;
skip_bits |= 1 << i;
input_per_cycle++;
}
}
clear();
return ratio_;
}
int Fir_Resampler_::input_needed( blargg_long output_count ) const
{
blargg_long input_count = 0;
unsigned long skip = skip_bits >> imp_phase;
int remain = res - imp_phase;
while ( (output_count -= 2) > 0 )
{
input_count += step + (skip & 1) * stereo;
skip >>= 1;
if ( !--remain )
{
skip = skip_bits;
remain = res;
}
output_count -= 2;
}
long input_extra = input_count - (write_pos - &buf [(width_ - 1) * stereo]);
if ( input_extra < 0 )
input_extra = 0;
return input_extra;
}
int Fir_Resampler_::avail_( blargg_long input_count ) const
{
int cycle_count = input_count / input_per_cycle;
int output_count = cycle_count * res * stereo;
input_count -= cycle_count * input_per_cycle;
blargg_ulong skip = skip_bits >> imp_phase;
int remain = res - imp_phase;
while ( input_count >= 0 )
{
input_count -= step + (skip & 1) * stereo;
skip >>= 1;
if ( !--remain )
{
skip = skip_bits;
remain = res;
}
output_count += 2;
}
return output_count;
}
int Fir_Resampler_::skip_input( long count )
{
int remain = write_pos - buf.begin();
int max_count = remain - width_ * stereo;
if ( count > max_count )
count = max_count;
remain -= count;
write_pos = &buf [remain];
memmove( buf.begin(), &buf [count], remain * sizeof buf [0] );
return count;
}

View file

@ -0,0 +1,171 @@
// Finite impulse response (FIR) resampler with adjustable FIR size
// Game_Music_Emu 0.5.2
#ifndef FIR_RESAMPLER_H
#define FIR_RESAMPLER_H
#include "blargg_common.h"
#include <string.h>
class Fir_Resampler_ {
public:
// Use Fir_Resampler<width> (below)
// Set input/output resampling ratio and optionally low-pass rolloff and gain.
// Returns actual ratio used (rounded to internal precision).
double time_ratio( double factor, double rolloff = 0.999, double gain = 1.0 );
// Current input/output ratio
double ratio() const { return ratio_; }
// Input
typedef short sample_t;
// Resize and clear input buffer
blargg_err_t buffer_size( int );
// Clear input buffer. At least two output samples will be available after
// two input samples are written.
void clear();
// Number of input samples that can be written
int max_write() const { return buf.end() - write_pos; }
// Pointer to place to write input samples
sample_t* buffer() { return write_pos; }
// Notify resampler that 'count' input samples have been written
void write( long count );
// Number of input samples in buffer
int written() const { return write_pos - &buf [write_offset]; }
// Skip 'count' input samples. Returns number of samples actually skipped.
int skip_input( long count );
// Output
// Number of extra input samples needed until 'count' output samples are available
int input_needed( blargg_long count ) const;
// Number of output samples available
int avail() const { return avail_( write_pos - &buf [width_ * stereo] ); }
public:
~Fir_Resampler_();
protected:
enum { stereo = 2 };
enum { max_res = 32 };
blargg_vector<sample_t> buf;
sample_t* write_pos;
int res;
int imp_phase;
int const width_;
int const write_offset;
blargg_ulong skip_bits;
int step;
int input_per_cycle;
double ratio_;
sample_t* impulses;
Fir_Resampler_( int width, sample_t* );
int avail_( blargg_long input_count ) const;
};
// Width is number of points in FIR. Must be even and 4 or more. More points give
// better quality and rolloff effectiveness, and take longer to calculate.
template<int width>
class Fir_Resampler : public Fir_Resampler_ {
BOOST_STATIC_ASSERT( width >= 4 && width % 2 == 0 );
short impulses [max_res] [width];
public:
Fir_Resampler() : Fir_Resampler_( width, impulses [0] ) { }
// Read at most 'count' samples. Returns number of samples actually read.
typedef short sample_t;
int read( sample_t* out, blargg_long count );
};
// End of public interface
inline void Fir_Resampler_::write( long count )
{
write_pos += count;
assert( write_pos <= buf.end() );
}
template<int width>
int Fir_Resampler<width>::read( sample_t* out_begin, blargg_long count )
{
sample_t* out = out_begin;
const sample_t* in = buf.begin();
sample_t* end_pos = write_pos;
blargg_ulong skip = skip_bits >> imp_phase;
sample_t const* imp = impulses [imp_phase];
int remain = res - imp_phase;
int const step = this->step;
count >>= 1;
if ( end_pos - in >= width * stereo )
{
end_pos -= width * stereo;
do
{
count--;
// accumulate in extended precision
blargg_long l = 0;
blargg_long r = 0;
const sample_t* i = in;
if ( count < 0 )
break;
for ( int n = width / 2; n; --n )
{
int pt0 = imp [0];
l += pt0 * i [0];
r += pt0 * i [1];
int pt1 = imp [1];
imp += 2;
l += pt1 * i [2];
r += pt1 * i [3];
i += 4;
}
remain--;
l >>= 15;
r >>= 15;
in += (skip * stereo) & stereo;
skip >>= 1;
in += step;
if ( !remain )
{
imp = impulses [0];
skip = skip_bits;
remain = res;
}
out [0] = (sample_t) l;
out [1] = (sample_t) r;
out += 2;
}
while ( in <= end_pos );
}
imp_phase = res - remain;
int left = write_pos - in;
write_pos = &buf [left];
memmove( buf.begin(), in, left * sizeof *in );
return out - out_begin;
}
#endif

306
Frameworks/GME/gme/Gb_Apu.cpp Executable file
View file

@ -0,0 +1,306 @@
// Gb_Snd_Emu 0.1.5. http://www.slack.net/~ant/
#include "Gb_Apu.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
unsigned const vol_reg = 0xFF24;
unsigned const status_reg = 0xFF26;
Gb_Apu::Gb_Apu()
{
square1.synth = &square_synth;
square2.synth = &square_synth;
wave.synth = &other_synth;
noise.synth = &other_synth;
oscs [0] = &square1;
oscs [1] = &square2;
oscs [2] = &wave;
oscs [3] = &noise;
for ( int i = 0; i < osc_count; i++ )
{
Gb_Osc& osc = *oscs [i];
osc.regs = &regs [i * 5];
osc.output = 0;
osc.outputs [0] = 0;
osc.outputs [1] = 0;
osc.outputs [2] = 0;
osc.outputs [3] = 0;
}
set_tempo( 1.0 );
volume( 1.0 );
reset();
}
void Gb_Apu::treble_eq( const blip_eq_t& eq )
{
square_synth.treble_eq( eq );
other_synth.treble_eq( eq );
}
void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
require( (unsigned) index < osc_count );
require( (center && left && right) || (!center && !left && !right) );
Gb_Osc& osc = *oscs [index];
osc.outputs [1] = right;
osc.outputs [2] = left;
osc.outputs [3] = center;
osc.output = osc.outputs [osc.output_select];
}
void Gb_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, center, left, right );
}
void Gb_Apu::update_volume()
{
// TODO: doesn't handle differing left/right global volume (support would
// require modification to all oscillator code)
int data = regs [vol_reg - start_addr];
double vol = (max( data & 7, data >> 4 & 7 ) + 1) * volume_unit;
square_synth.volume( vol );
other_synth.volume( vol );
}
static unsigned char const powerup_regs [0x20] = {
0x80,0x3F,0x00,0xFF,0xBF, // square 1
0xFF,0x3F,0x00,0xFF,0xBF, // square 2
0x7F,0xFF,0x9F,0xFF,0xBF, // wave
0xFF,0xFF,0x00,0x00,0xBF, // noise
0x00, // left/right enables
0x77, // master volume
0x80, // power
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
void Gb_Apu::set_tempo( double t )
{
frame_period = 4194304 / 256; // 256 Hz
if ( t != 1.0 )
frame_period = blip_time_t (frame_period / t);
}
void Gb_Apu::reset()
{
next_frame_time = 0;
last_time = 0;
frame_count = 0;
square1.reset();
square2.reset();
wave.reset();
noise.reset();
noise.bits = 1;
wave.wave_pos = 0;
// avoid click at beginning
regs [vol_reg - start_addr] = 0x77;
update_volume();
regs [status_reg - start_addr] = 0x01; // force power
write_register( 0, status_reg, 0x00 );
static unsigned char const initial_wave [] = {
0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C, // wave table
0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA
};
memcpy( wave.wave, initial_wave, sizeof wave.wave );
}
void Gb_Apu::run_until( blip_time_t end_time )
{
require( end_time >= last_time ); // end_time must not be before previous time
if ( end_time == last_time )
return;
while ( true )
{
blip_time_t time = next_frame_time;
if ( time > end_time )
time = end_time;
// run oscillators
for ( int i = 0; i < osc_count; ++i )
{
Gb_Osc& osc = *oscs [i];
if ( osc.output )
{
osc.output->set_modified(); // TODO: misses optimization opportunities?
int playing = false;
if ( osc.enabled && osc.volume &&
(!(osc.regs [4] & osc.len_enabled_mask) || osc.length) )
playing = -1;
switch ( i )
{
case 0: square1.run( last_time, time, playing ); break;
case 1: square2.run( last_time, time, playing ); break;
case 2: wave .run( last_time, time, playing ); break;
case 3: noise .run( last_time, time, playing ); break;
}
}
}
last_time = time;
if ( time == end_time )
break;
next_frame_time += frame_period;
// 256 Hz actions
square1.clock_length();
square2.clock_length();
wave.clock_length();
noise.clock_length();
frame_count = (frame_count + 1) & 3;
if ( frame_count == 0 )
{
// 64 Hz actions
square1.clock_envelope();
square2.clock_envelope();
noise.clock_envelope();
}
if ( frame_count & 1 )
square1.clock_sweep(); // 128 Hz action
}
}
void Gb_Apu::end_frame( blip_time_t end_time )
{
if ( end_time > last_time )
run_until( end_time );
assert( next_frame_time >= end_time );
next_frame_time -= end_time;
assert( last_time >= end_time );
last_time -= end_time;
}
void Gb_Apu::write_register( blip_time_t time, unsigned addr, int data )
{
require( (unsigned) data < 0x100 );
int reg = addr - start_addr;
if ( (unsigned) reg >= register_count )
return;
run_until( time );
int old_reg = regs [reg];
regs [reg] = data;
if ( addr < vol_reg )
{
write_osc( reg / 5, reg, data );
}
else if ( addr == vol_reg && data != old_reg ) // global volume
{
// return all oscs to 0
for ( int i = 0; i < osc_count; i++ )
{
Gb_Osc& osc = *oscs [i];
int amp = osc.last_amp;
osc.last_amp = 0;
if ( amp && osc.enabled && osc.output )
other_synth.offset( time, -amp, osc.output );
}
if ( wave.outputs [3] )
other_synth.offset( time, 30, wave.outputs [3] );
update_volume();
if ( wave.outputs [3] )
other_synth.offset( time, -30, wave.outputs [3] );
// oscs will update with new amplitude when next run
}
else if ( addr == 0xFF25 || addr == status_reg )
{
int mask = (regs [status_reg - start_addr] & 0x80) ? ~0 : 0;
int flags = regs [0xFF25 - start_addr] & mask;
// left/right assignments
for ( int i = 0; i < osc_count; i++ )
{
Gb_Osc& osc = *oscs [i];
osc.enabled &= mask;
int bits = flags >> i;
Blip_Buffer* old_output = osc.output;
osc.output_select = (bits >> 3 & 2) | (bits & 1);
osc.output = osc.outputs [osc.output_select];
if ( osc.output != old_output )
{
int amp = osc.last_amp;
osc.last_amp = 0;
if ( amp && old_output )
other_synth.offset( time, -amp, old_output );
}
}
if ( addr == status_reg && data != old_reg )
{
if ( !(data & 0x80) )
{
for ( unsigned i = 0; i < sizeof powerup_regs; i++ )
{
if ( i != status_reg - start_addr )
write_register( time, i + start_addr, powerup_regs [i] );
}
}
else
{
//dprintf( "APU powered on\n" );
}
}
}
else if ( addr >= 0xFF30 )
{
int index = (addr & 0x0F) * 2;
wave.wave [index] = data >> 4;
wave.wave [index + 1] = data & 0x0F;
}
}
int Gb_Apu::read_register( blip_time_t time, unsigned addr )
{
run_until( time );
int index = addr - start_addr;
require( (unsigned) index < register_count );
int data = regs [index];
if ( addr == status_reg )
{
data = (data & 0x80) | 0x70;
for ( int i = 0; i < osc_count; i++ )
{
const Gb_Osc& osc = *oscs [i];
if ( osc.enabled && (osc.length || !(osc.regs [4] & osc.len_enabled_mask)) )
data |= 1 << i;
}
}
return data;
}

90
Frameworks/GME/gme/Gb_Apu.h Executable file
View file

@ -0,0 +1,90 @@
// Nintendo Game Boy PAPU sound chip emulator
// Gb_Snd_Emu 0.1.5
#ifndef GB_APU_H
#define GB_APU_H
#include "Gb_Oscs.h"
class Gb_Apu {
public:
// Set overall volume of all oscillators, where 1.0 is full volume
void volume( double );
// Set treble equalization
void treble_eq( const blip_eq_t& );
// Outputs can be assigned to a single buffer for mono output, or to three
// buffers for stereo output (using Stereo_Buffer to do the mixing).
// Assign all oscillator outputs to specified buffer(s). If buffer
// is NULL, silences all oscillators.
void output( Blip_Buffer* mono );
void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
// Assign single oscillator output to buffer(s). Valid indicies are 0 to 3,
// which refer to Square 1, Square 2, Wave, and Noise. If buffer is NULL,
// silences oscillator.
enum { osc_count = 4 };
void osc_output( int index, Blip_Buffer* mono );
void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
// Reset oscillators and internal state
void reset();
// Reads and writes at addr must satisfy start_addr <= addr <= end_addr
enum { start_addr = 0xFF10 };
enum { end_addr = 0xFF3F };
enum { register_count = end_addr - start_addr + 1 };
// Write 'data' to address at specified time
void write_register( blip_time_t, unsigned addr, int data );
// Read from address at specified time
int read_register( blip_time_t, unsigned addr );
// Run all oscillators up to specified time, end current time frame, then
// start a new frame at time 0.
void end_frame( blip_time_t );
void set_tempo( double );
public:
Gb_Apu();
private:
// noncopyable
Gb_Apu( const Gb_Apu& );
Gb_Apu& operator = ( const Gb_Apu& );
Gb_Osc* oscs [osc_count];
blip_time_t next_frame_time;
blip_time_t last_time;
blip_time_t frame_period;
double volume_unit;
int frame_count;
Gb_Square square1;
Gb_Square square2;
Gb_Wave wave;
Gb_Noise noise;
BOOST::uint8_t regs [register_count];
Gb_Square::Synth square_synth; // used by squares
Gb_Wave::Synth other_synth; // used by wave and noise
void update_volume();
void run_until( blip_time_t );
void write_osc( int index, int reg, int data );
};
inline void Gb_Apu::output( Blip_Buffer* b ) { output( b, b, b ); }
inline void Gb_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); }
inline void Gb_Apu::volume( double vol )
{
volume_unit = 0.60 / osc_count / 15 /*steps*/ / 2 /*?*/ / 8 /*master vol range*/ * vol;
update_volume();
}
#endif

1056
Frameworks/GME/gme/Gb_Cpu.cpp Executable file

File diff suppressed because it is too large Load diff

93
Frameworks/GME/gme/Gb_Cpu.h Executable file
View file

@ -0,0 +1,93 @@
// Nintendo Game Boy CPU emulator
// Treats every instruction as taking 4 cycles
// Game_Music_Emu 0.5.2
#ifndef GB_CPU_H
#define GB_CPU_H
#include "blargg_common.h"
#include "blargg_endian.h"
typedef unsigned gb_addr_t; // 16-bit CPU address
class Gb_Cpu {
enum { clocks_per_instr = 4 };
public:
typedef BOOST::uint8_t uint8_t;
// Clear registers and map all pages to unmapped
void reset( void* unmapped = 0 );
// Map code memory (memory accessed via the program counter). Start and size
// must be multiple of page_size.
enum { page_size = 0x2000 };
void map_code( gb_addr_t start, unsigned size, void* code );
uint8_t* get_code( gb_addr_t );
// Push a byte on the stack
void push_byte( int );
// Game Boy Z80 registers. *Not* kept updated during a call to run().
struct core_regs_t {
#if BLARGG_BIG_ENDIAN
uint8_t b, c, d, e, h, l, flags, a;
#else
uint8_t c, b, e, d, l, h, a, flags;
#endif
};
struct registers_t : core_regs_t {
long pc; // more than 16 bits to allow overflow detection
BOOST::uint16_t sp;
};
registers_t r;
// Interrupt enable flag set by EI and cleared by DI
//bool interrupts_enabled; // unused
// Base address for RST vectors (normally 0)
gb_addr_t rst_base;
// If CPU executes opcode 0xFF at this address, it treats as illegal instruction
enum { idle_addr = 0xF00D };
// Run CPU for at least 'count' cycles and return false, or return true if
// illegal instruction is encountered.
bool run( blargg_long count );
// Number of clock cycles remaining for most recent run() call
blargg_long remain() const { return state->remain * clocks_per_instr; }
// Can read this many bytes past end of a page
enum { cpu_padding = 8 };
public:
Gb_Cpu() : rst_base( 0 ) { state = &state_; }
enum { page_shift = 13 };
enum { page_count = 0x10000 >> page_shift };
private:
// noncopyable
Gb_Cpu( const Gb_Cpu& );
Gb_Cpu& operator = ( const Gb_Cpu& );
struct state_t {
uint8_t* code_map [page_count + 1];
blargg_long remain;
};
state_t* state; // points to state_ or a local copy within run()
state_t state_;
void set_code_page( int, uint8_t* );
};
inline BOOST::uint8_t* Gb_Cpu::get_code( gb_addr_t addr )
{
return state->code_map [addr >> page_shift] + addr
#if !BLARGG_NONPORTABLE
% (unsigned) page_size
#endif
;
}
#endif

336
Frameworks/GME/gme/Gb_Oscs.cpp Executable file
View file

@ -0,0 +1,336 @@
// Gb_Snd_Emu 0.1.5. http://www.slack.net/~ant/
#include "Gb_Apu.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// Gb_Osc
void Gb_Osc::reset()
{
delay = 0;
last_amp = 0;
length = 0;
output_select = 3;
output = outputs [output_select];
}
void Gb_Osc::clock_length()
{
if ( (regs [4] & len_enabled_mask) && length )
length--;
}
// Gb_Env
void Gb_Env::clock_envelope()
{
if ( env_delay && !--env_delay )
{
env_delay = regs [2] & 7;
int v = volume - 1 + (regs [2] >> 2 & 2);
if ( (unsigned) v < 15 )
volume = v;
}
}
bool Gb_Env::write_register( int reg, int data )
{
switch ( reg )
{
case 1:
length = 64 - (regs [1] & 0x3F);
break;
case 2:
if ( !(data >> 4) )
enabled = false;
break;
case 4:
if ( data & trigger )
{
env_delay = regs [2] & 7;
volume = regs [2] >> 4;
enabled = true;
if ( length == 0 )
length = 64;
return true;
}
}
return false;
}
// Gb_Square
void Gb_Square::reset()
{
phase = 0;
sweep_freq = 0;
sweep_delay = 0;
Gb_Env::reset();
}
void Gb_Square::clock_sweep()
{
int sweep_period = (regs [0] & period_mask) >> 4;
if ( sweep_period && sweep_delay && !--sweep_delay )
{
sweep_delay = sweep_period;
regs [3] = sweep_freq & 0xFF;
regs [4] = (regs [4] & ~0x07) | (sweep_freq >> 8 & 0x07);
int offset = sweep_freq >> (regs [0] & shift_mask);
if ( regs [0] & 0x08 )
offset = -offset;
sweep_freq += offset;
if ( sweep_freq < 0 )
{
sweep_freq = 0;
}
else if ( sweep_freq >= 2048 )
{
sweep_delay = 0; // don't modify channel frequency any further
sweep_freq = 2048; // silence sound immediately
}
}
}
void Gb_Square::run( blip_time_t time, blip_time_t end_time, int playing )
{
if ( sweep_freq == 2048 )
playing = false;
static unsigned char const table [4] = { 1, 2, 4, 6 };
int const duty = table [regs [1] >> 6];
int amp = volume & playing;
if ( phase >= duty )
amp = -amp;
int frequency = this->frequency();
if ( unsigned (frequency - 1) > 2040 ) // frequency < 1 || frequency > 2041
{
// really high frequency results in DC at half volume
amp = volume >> 1;
playing = false;
}
{
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth->offset( time, delta, output );
}
}
time += delay;
if ( !playing )
time = end_time;
if ( time < end_time )
{
int const period = (2048 - frequency) * 4;
Blip_Buffer* const output = this->output;
int phase = this->phase;
int delta = amp * 2;
do
{
phase = (phase + 1) & 7;
if ( phase == 0 || phase == duty )
{
delta = -delta;
synth->offset_inline( time, delta, output );
}
time += period;
}
while ( time < end_time );
this->phase = phase;
last_amp = delta >> 1;
}
delay = time - end_time;
}
// Gb_Noise
void Gb_Noise::run( blip_time_t time, blip_time_t end_time, int playing )
{
int amp = volume & playing;
int tap = 13 - (regs [3] & 8);
if ( bits >> tap & 2 )
amp = -amp;
{
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth->offset( time, delta, output );
}
}
time += delay;
if ( !playing )
time = end_time;
if ( time < end_time )
{
static unsigned char const table [8] = { 8, 16, 32, 48, 64, 80, 96, 112 };
int period = table [regs [3] & 7] << (regs [3] >> 4);
// keep parallel resampled time to eliminate time conversion in the loop
Blip_Buffer* const output = this->output;
const blip_resampled_time_t resampled_period =
output->resampled_duration( period );
blip_resampled_time_t resampled_time = output->resampled_time( time );
unsigned bits = this->bits;
int delta = amp * 2;
do
{
unsigned changed = (bits >> tap) + 1;
time += period;
bits <<= 1;
if ( changed & 2 )
{
delta = -delta;
bits |= 1;
synth->offset_resampled( resampled_time, delta, output );
}
resampled_time += resampled_period;
}
while ( time < end_time );
this->bits = bits;
last_amp = delta >> 1;
}
delay = time - end_time;
}
// Gb_Wave
inline void Gb_Wave::write_register( int reg, int data )
{
switch ( reg )
{
case 0:
if ( !(data & 0x80) )
enabled = false;
break;
case 1:
length = 256 - regs [1];
break;
case 2:
volume = data >> 5 & 3;
break;
case 4:
if ( data & trigger & regs [0] )
{
wave_pos = 0;
enabled = true;
if ( length == 0 )
length = 256;
}
}
}
void Gb_Wave::run( blip_time_t time, blip_time_t end_time, int playing )
{
int volume_shift = (volume - 1) & 7; // volume = 0 causes shift = 7
int frequency;
{
int amp = (wave [wave_pos] >> volume_shift & playing) * 2;
frequency = this->frequency();
if ( unsigned (frequency - 1) > 2044 ) // frequency < 1 || frequency > 2045
{
amp = 30 >> volume_shift & playing;
playing = false;
}
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth->offset( time, delta, output );
}
}
time += delay;
if ( !playing )
time = end_time;
if ( time < end_time )
{
Blip_Buffer* const output = this->output;
int const period = (2048 - frequency) * 2;
int wave_pos = (this->wave_pos + 1) & (wave_size - 1);
do
{
int amp = (wave [wave_pos] >> volume_shift) * 2;
wave_pos = (wave_pos + 1) & (wave_size - 1);
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth->offset_inline( time, delta, output );
}
time += period;
}
while ( time < end_time );
this->wave_pos = (wave_pos - 1) & (wave_size - 1);
}
delay = time - end_time;
}
// Gb_Apu::write_osc
void Gb_Apu::write_osc( int index, int reg, int data )
{
reg -= index * 5;
Gb_Square* sq = &square2;
switch ( index )
{
case 0:
sq = &square1;
case 1:
if ( sq->write_register( reg, data ) && index == 0 )
{
square1.sweep_freq = square1.frequency();
if ( (regs [0] & sq->period_mask) && (regs [0] & sq->shift_mask) )
{
square1.sweep_delay = 1; // cause sweep to recalculate now
square1.clock_sweep();
}
}
break;
case 2:
wave.write_register( reg, data );
break;
case 3:
if ( noise.write_register( reg, data ) )
noise.bits = 0x7FFF;
}
}

83
Frameworks/GME/gme/Gb_Oscs.h Executable file
View file

@ -0,0 +1,83 @@
// Private oscillators used by Gb_Apu
// Gb_Snd_Emu 0.1.5
#ifndef GB_OSCS_H
#define GB_OSCS_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
struct Gb_Osc
{
enum { trigger = 0x80 };
enum { len_enabled_mask = 0x40 };
Blip_Buffer* outputs [4]; // NULL, right, left, center
Blip_Buffer* output;
int output_select;
BOOST::uint8_t* regs; // osc's 5 registers
int delay;
int last_amp;
int volume;
int length;
int enabled;
void reset();
void clock_length();
int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; }
};
struct Gb_Env : Gb_Osc
{
int env_delay;
void reset();
void clock_envelope();
bool write_register( int, int );
};
struct Gb_Square : Gb_Env
{
enum { period_mask = 0x70 };
enum { shift_mask = 0x07 };
typedef Blip_Synth<blip_good_quality,1> Synth;
Synth const* synth;
int sweep_delay;
int sweep_freq;
int phase;
void reset();
void clock_sweep();
void run( blip_time_t, blip_time_t, int playing );
};
struct Gb_Noise : Gb_Env
{
typedef Blip_Synth<blip_med_quality,1> Synth;
Synth const* synth;
unsigned bits;
void run( blip_time_t, blip_time_t, int playing );
};
struct Gb_Wave : Gb_Osc
{
typedef Blip_Synth<blip_med_quality,1> Synth;
Synth const* synth;
int wave_pos;
enum { wave_size = 32 };
BOOST::uint8_t wave [wave_size];
void write_register( int, int );
void run( blip_time_t, blip_time_t, int playing );
};
inline void Gb_Env::reset()
{
env_delay = 0;
Gb_Osc::reset();
}
#endif

288
Frameworks/GME/gme/Gbs_Emu.cpp Executable file
View file

@ -0,0 +1,288 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Gbs_Emu.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq = { -47.0, 2000 };
Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 300 };
Gbs_Emu::Gbs_Emu()
{
set_type( gme_gbs_type );
static const char* const names [Gb_Apu::osc_count] = {
"Square 1", "Square 2", "Wave", "Noise"
};
set_voice_names( names );
static int const types [Gb_Apu::osc_count] = {
wave_type | 1, wave_type | 2, wave_type | 0, mixed_type | 0
};
set_voice_types( types );
set_silence_lookahead( 6 );
set_max_initial_silence( 21 );
set_gain( 1.2 );
static equalizer_t const eq = { -1.0, 120 };
set_equalizer( eq );
}
Gbs_Emu::~Gbs_Emu() { }
void Gbs_Emu::unload()
{
rom.clear();
Music_Emu::unload();
}
// Track info
static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out )
{
GME_COPY_FIELD( h, out, game );
GME_COPY_FIELD( h, out, author );
GME_COPY_FIELD( h, out, copyright );
}
blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const
{
copy_gbs_fields( header_, out );
return 0;
}
static blargg_err_t check_gbs_header( void const* header )
{
if ( memcmp( header, "GBS", 3 ) )
return gme_wrong_file_type;
return 0;
}
struct Gbs_File : Gme_Info_
{
Gbs_Emu::header_t h;
Gbs_File() { set_type( gme_gbs_type ); }
blargg_err_t load_( Data_Reader& in )
{
blargg_err_t err = in.read( &h, Gbs_Emu::header_size );
if ( err )
return (err == in.eof_error ? gme_wrong_file_type : err);
set_track_count( h.track_count );
return check_gbs_header( &h );
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_gbs_fields( h, out );
return 0;
}
};
static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; }
static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; }
gme_type_t_ const gme_gbs_type [1] = { "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 };
// Setup
blargg_err_t Gbs_Emu::load_( Data_Reader& in )
{
assert( offsetof (header_t,copyright [32]) == header_size );
RETURN_ERR( rom.load( in, header_size, &header_, 0 ) );
set_track_count( header_.track_count );
RETURN_ERR( check_gbs_header( &header_ ) );
if ( header_.vers != 1 )
set_warning( "Unknown file version" );
if ( header_.timer_mode & 0x78 )
set_warning( "Invalid timer mode" );
unsigned load_addr = get_le16( header_.load_addr );
if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F ||
load_addr < 0x400 )
set_warning( "Invalid load/init/play address" );
set_voice_count( Gb_Apu::osc_count );
apu.volume( gain() );
return setup_buffer( 4194304 );
}
void Gbs_Emu::update_eq( blip_eq_t const& eq )
{
apu.treble_eq( eq );
}
void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{
apu.osc_output( i, c, l, r );
}
// Emulation
// see gb_cpu_io.h for read/write functions
void Gbs_Emu::set_bank( int n )
{
blargg_long addr = rom.mask_addr( n * (blargg_long) bank_size );
if ( addr == 0 && rom.size() > bank_size )
{
// TODO: what is the correct behavior? Current Game & Watch Gallery
// rip requires that this have no effect or set to bank 1.
//dprintf( "Selected ROM bank 0\n" );
return;
//n = 1;
}
cpu::map_code( bank_size, bank_size, rom.at_addr( addr ) );
}
void Gbs_Emu::update_timer()
{
if ( header_.timer_mode & 0x04 )
{
static byte const rates [4] = { 10, 4, 6, 8 };
int shift = rates [ram [hi_page + 7] & 3] - (header_.timer_mode >> 7);
play_period = (256L - ram [hi_page + 6]) << shift;
}
else
{
play_period = 70224; // 59.73 Hz
}
if ( tempo() != 1.0 )
play_period = blip_time_t (play_period / tempo());
}
static BOOST::uint8_t const sound_data [Gb_Apu::register_count] = {
0x80, 0xBF, 0x00, 0x00, 0xBF, // square 1
0x00, 0x3F, 0x00, 0x00, 0xBF, // square 2
0x7F, 0xFF, 0x9F, 0x00, 0xBF, // wave
0x00, 0xFF, 0x00, 0x00, 0xBF, // noise
0x77, 0xF3, 0xF1, // vin/volume, status, power mode
0, 0, 0, 0, 0, 0, 0, 0, 0, // unused
0xAC, 0xDD, 0xDA, 0x48, 0x36, 0x02, 0xCF, 0x16, // waveform data
0x2C, 0x04, 0xE5, 0x2C, 0xAC, 0xDD, 0xDA, 0x48
};
void Gbs_Emu::cpu_jsr( gb_addr_t addr )
{
check( cpu::r.sp == get_le16( header_.stack_ptr ) );
cpu::r.pc = addr;
cpu_write( --cpu::r.sp, idle_addr >> 8 );
cpu_write( --cpu::r.sp, idle_addr&0xFF );
}
void Gbs_Emu::set_tempo_( double t )
{
apu.set_tempo( t );
update_timer();
}
blargg_err_t Gbs_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
memset( ram, 0, 0x4000 );
memset( ram + 0x4000, 0xFF, 0x1F80 );
memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 );
ram [hi_page] = 0; // joypad reads back as 0
apu.reset();
for ( int i = 0; i < (int) sizeof sound_data; i++ )
apu.write_register( 0, i + apu.start_addr, sound_data [i] );
cpu::reset( rom.unmapped() );
unsigned load_addr = get_le16( header_.load_addr );
cpu::rst_base = load_addr;
rom.set_addr( load_addr );
cpu::map_code( ram_addr, 0x10000 - ram_addr, ram );
cpu::map_code( 0, bank_size, rom.at_addr( 0 ) );
set_bank( rom.size() > bank_size );
ram [hi_page + 6] = header_.timer_modulo;
ram [hi_page + 7] = header_.timer_mode;
update_timer();
next_play = play_period;
cpu::r.a = track;
cpu::r.pc = idle_addr;
cpu::r.sp = get_le16( header_.stack_ptr );
cpu_time = 0;
cpu_jsr( get_le16( header_.init_addr ) );
return 0;
}
blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int )
{
cpu_time = 0;
while ( cpu_time < duration )
{
long count = duration - cpu_time;
cpu_time = duration;
bool result = cpu::run( count );
cpu_time -= cpu::remain();
if ( result )
{
if ( cpu::r.pc == idle_addr )
{
if ( next_play > duration )
{
cpu_time = duration;
break;
}
if ( cpu_time < next_play )
cpu_time = next_play;
next_play += play_period;
cpu_jsr( get_le16( header_.play_addr ) );
GME_FRAME_HOOK( this );
// TODO: handle timer rates different than 60 Hz
}
else if ( cpu::r.pc > 0xFFFF )
{
dprintf( "PC wrapped around\n" );
cpu::r.pc &= 0xFFFF;
}
else
{
set_warning( "Emulation error (illegal/unsupported instruction)" );
dprintf( "Bad opcode $%.2x at $%.4x\n",
(int) *cpu::get_code( cpu::r.pc ), (int) cpu::r.pc );
cpu::r.pc = (cpu::r.pc + 1) & 0xFFFF;
cpu_time += 6;
}
}
}
duration = cpu_time;
next_play -= cpu_time;
if ( next_play < 0 ) // could go negative if routine is taking too long to return
next_play = 0;
apu.end_frame( cpu_time );
return 0;
}

88
Frameworks/GME/gme/Gbs_Emu.h Executable file
View file

@ -0,0 +1,88 @@
// Nintendo Game Boy GBS music file emulator
// Game_Music_Emu 0.5.2
#ifndef GBS_EMU_H
#define GBS_EMU_H
#include "Classic_Emu.h"
#include "Gb_Apu.h"
#include "Gb_Cpu.h"
class Gbs_Emu : private Gb_Cpu, public Classic_Emu {
typedef Gb_Cpu cpu;
public:
// Equalizer profiles for Game Boy Color speaker and headphones
static equalizer_t const handheld_eq;
static equalizer_t const headphones_eq;
// GBS file header
enum { header_size = 112 };
struct header_t
{
char tag [3];
byte vers;
byte track_count;
byte first_track;
byte load_addr [2];
byte init_addr [2];
byte play_addr [2];
byte stack_ptr [2];
byte timer_modulo;
byte timer_mode;
char game [32];
char author [32];
char copyright [32];
};
// Header for currently loaded file
header_t const& header() const { return header_; }
static gme_type_t static_type() { return gme_gbs_type; }
public:
// deprecated
Music_Emu::load;
blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
{ return load_remaining_( &h, sizeof h, in ); }
public:
Gbs_Emu();
~Gbs_Emu();
protected:
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t load_( Data_Reader& );
blargg_err_t start_track_( int );
blargg_err_t run_clocks( blip_time_t&, int );
void set_tempo_( double );
void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
void update_eq( blip_eq_t const& );
void unload();
private:
// rom
enum { bank_size = 0x4000 };
Rom_Data<bank_size> rom;
void set_bank( int );
// timer
blip_time_t cpu_time;
blip_time_t play_period;
blip_time_t next_play;
void update_timer();
header_t header_;
void cpu_jsr( gb_addr_t );
public: private: friend class Gb_Cpu;
blip_time_t clock() const { return cpu_time - cpu::remain(); }
enum { joypad_addr = 0xFF00 };
enum { ram_addr = 0xA000 };
enum { hi_page = 0xFF00 - ram_addr };
byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding];
Gb_Apu apu;
int cpu_read( gb_addr_t );
void cpu_write( gb_addr_t, int );
};
#endif

216
Frameworks/GME/gme/Gme_File.cpp Executable file
View file

@ -0,0 +1,216 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Gme_File.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
const char gme_wrong_file_type [] = "Wrong file type for this emulator";
void Gme_File::clear_playlist()
{
playlist.clear();
clear_playlist_();
track_count_ = raw_track_count_;
}
void Gme_File::unload()
{
clear_playlist(); // *before* clearing track count
track_count_ = 0;
raw_track_count_ = 0;
file_data.clear();
}
Gme_File::Gme_File()
{
type_ = 0;
user_data_ = 0;
user_cleanup_ = 0;
unload(); // clears fields
blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
}
Gme_File::~Gme_File()
{
if ( user_cleanup_ )
user_cleanup_( user_data_ );
}
blargg_err_t Gme_File::load_mem_( byte const* data, long size )
{
require( data != file_data.begin() ); // load_mem_() or load_() must be overridden
Mem_File_Reader in( data, size );
return load_( in );
}
blargg_err_t Gme_File::load_( Data_Reader& in )
{
RETURN_ERR( file_data.resize( in.remain() ) );
RETURN_ERR( in.read( file_data.begin(), file_data.size() ) );
return load_mem_( file_data.begin(), file_data.size() );
}
// public load functions call this at beginning
void Gme_File::pre_load() { unload(); }
void Gme_File::post_load_() { }
// public load functions call this at end
blargg_err_t Gme_File::post_load( blargg_err_t err )
{
if ( !track_count() )
set_track_count( type()->track_count );
if ( !err )
post_load_();
else
unload();
return err;
}
// Public load functions
blargg_err_t Gme_File::load_mem( void const* in, long size )
{
pre_load();
return post_load( load_mem_( (byte const*) in, size ) );
}
blargg_err_t Gme_File::load( Data_Reader& in )
{
pre_load();
return post_load( load_( in ) );
}
blargg_err_t Gme_File::load_file( const char* path )
{
pre_load();
GME_FILE_READER in;
RETURN_ERR( in.open( path ) );
return post_load( load_( in ) );
}
blargg_err_t Gme_File::load_remaining_( void const* h, long s, Data_Reader& in )
{
Remaining_Reader rem( h, s, &in );
return load( rem );
}
// Track info
void Gme_File::copy_field_( char* out, const char* in, int in_size )
{
if ( !in || !*in )
return;
// remove spaces/junk from beginning
while ( in_size && unsigned (*in - 1) <= ' ' - 1 )
{
in++;
in_size--;
}
// truncate
if ( in_size > max_field_ )
in_size = max_field_;
// find terminator
int len = 0;
while ( len < in_size && in [len] )
len++;
// remove spaces/junk from end
while ( len && unsigned (in [len - 1]) <= ' ' )
len--;
// copy
out [len] = 0;
memcpy( out, in, len );
// strip out stupid fields that should have been left blank
if ( !strcmp( out, "?" ) || !strcmp( out, "<?>" ) || !strcmp( out, "< ? >" ) )
out [0] = 0;
}
void Gme_File::copy_field_( char* out, const char* in )
{
copy_field_( out, in, max_field_ );
}
blargg_err_t Gme_File::remap_track_( int* track_io ) const
{
if ( (unsigned) *track_io >= (unsigned) track_count() )
return "Invalid track";
if ( (unsigned) *track_io < (unsigned) playlist.size() )
{
M3u_Playlist::entry_t const& e = playlist [*track_io];
*track_io = 0;
if ( e.track >= 0 )
{
*track_io = e.track;
if ( !(type_->flags_ & 0x02) )
*track_io -= e.decimal_track;
}
if ( *track_io >= raw_track_count_ )
return "Invalid track in m3u playlist";
}
else
{
check( !playlist.size() );
}
return 0;
}
blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
{
out->track_count = track_count();
out->length = -1;
out->loop_length = -1;
out->intro_length = -1;
out->song [0] = 0;
out->game [0] = 0;
out->author [0] = 0;
out->copyright [0] = 0;
out->comment [0] = 0;
out->dumper [0] = 0;
out->system [0] = 0;
copy_field_( out->system, type()->system );
int remapped = track;
RETURN_ERR( remap_track_( &remapped ) );
RETURN_ERR( track_info_( out, remapped ) );
// override with m3u info
if ( playlist.size() )
{
M3u_Playlist::info_t const& i = playlist.info();
copy_field_( out->game , i.title );
copy_field_( out->author, i.engineer );
copy_field_( out->author, i.composer );
copy_field_( out->dumper, i.ripping );
M3u_Playlist::entry_t const& e = playlist [track];
copy_field_( out->song, e.name );
if ( e.length >= 0 ) out->length = e.length * 1000L;
if ( e.intro >= 0 ) out->intro_length = e.intro * 1000L;
if ( e.loop >= 0 ) out->loop_length = e.loop * 1000L;
}
return 0;
}

145
Frameworks/GME/gme/Gme_File.h Executable file
View file

@ -0,0 +1,145 @@
// Common interface to game music file loading and information
// Game_Music_Emu 0.5.2
#ifndef GME_FILE_H
#define GME_FILE_H
#include "gme.h"
#include "blargg_common.h"
#include "Data_Reader.h"
#include "M3u_Playlist.h"
// Error returned if file is wrong type
//extern const char gme_wrong_file_type []; // declared in gme.h
struct Gme_File {
public:
// File loading
// Each loads game music data from a file and returns an error if
// file is wrong type or is seriously corrupt. They also set warning
// string for minor problems.
// Load from file on disk
blargg_err_t load_file( const char* path );
// Load from custom data source (see Data_Reader.h)
blargg_err_t load( Data_Reader& );
// Load from file already read into memory. Keeps pointer to data, so you
// must not free it until you're done with the file.
blargg_err_t load_mem( void const* data, long size );
// Load an m3u playlist. Must be done after loading main music file.
blargg_err_t load_m3u( const char* path );
blargg_err_t load_m3u( Data_Reader& in );
// Clears any loaded m3u playlist and any internal playlist that the music
// format supports (NSFE for example).
void clear_playlist();
// Informational
// Type of emulator. For example if this returns gme_nsfe_type, this object
// is an NSFE emulator, and you can cast to an Nsfe_Emu* if necessary.
gme_type_t type() const;
// Most recent warning string, or NULL if none. Clears current warning after
// returning.
const char* warning();
// Number of tracks or 0 if no file has been loaded
int track_count() const;
// Get information for a track (length, name, author, etc.)
// See gme.h for definition of struct track_info_t.
blargg_err_t track_info( track_info_t* out, int track ) const;
// User data/cleanup
// Set/get pointer to data you want to associate with this emulator.
// You can use this for whatever you want.
void set_user_data( void* p ) { user_data_ = p; }
void* user_data() const { return user_data_; }
// Register cleanup function to be called when deleting emulator, or NULL to
// clear it. Passes user_data to cleanup function.
void set_user_cleanup( gme_user_cleanup_t func ) { user_cleanup_ = func; }
public:
// deprecated
int error_count() const; // use warning()
public:
Gme_File();
virtual ~Gme_File();
BLARGG_DISABLE_NOTHROW
typedef BOOST::uint8_t byte;
protected:
// Services
void set_track_count( int n ) { track_count_ = raw_track_count_ = n; }
void set_warning( const char* s ) { warning_ = s; }
void set_type( gme_type_t t ) { type_ = t; }
blargg_err_t load_remaining_( void const* header, long header_size, Data_Reader& remaining );
// Overridable
virtual void unload(); // called before loading file and if loading fails
virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_()
virtual blargg_err_t load_mem_( byte const* data, long size ); // use data in memory
virtual blargg_err_t track_info_( track_info_t* out, int track ) const = 0;
virtual void pre_load();
virtual void post_load_();
virtual void clear_playlist_() { }
public:
blargg_err_t remap_track_( int* track_io ) const; // need by Music_Emu
private:
// noncopyable
Gme_File( const Gme_File& );
Gme_File& operator = ( const Gme_File& );
gme_type_t type_;
int track_count_;
int raw_track_count_;
const char* warning_;
void* user_data_;
gme_user_cleanup_t user_cleanup_;
M3u_Playlist playlist;
char playlist_warning [64];
blargg_vector<byte> file_data; // only if loaded into memory using default load
blargg_err_t load_m3u_( blargg_err_t );
blargg_err_t post_load( blargg_err_t err );
public:
// track_info field copying
enum { max_field_ = 255 };
static void copy_field_( char* out, const char* in );
static void copy_field_( char* out, const char* in, int len );
};
Music_Emu* gme_new_( Music_Emu*, long sample_rate );
#define GME_COPY_FIELD( in, out, name ) \
{ Gme_File::copy_field_( out->name, in.name, sizeof in.name ); }
#ifndef GME_FILE_READER
#ifdef HAVE_ZLIB_H
#define GME_FILE_READER Gzip_File_Reader
#else
#define GME_FILE_READER Std_File_Reader
#endif
#elif defined (GME_FILE_READER_INCLUDE)
#include GME_FILE_READER_INCLUDE
#endif
inline gme_type_t Gme_File::type() const { return type_; }
inline int Gme_File::error_count() const { return warning_ != 0; }
inline int Gme_File::track_count() const { return track_count_; }
inline const char* Gme_File::warning()
{
const char* s = warning_;
warning_ = 0;
return s;
}
#endif

379
Frameworks/GME/gme/Gym_Emu.cpp Executable file
View file

@ -0,0 +1,379 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Gym_Emu.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
double const min_tempo = 0.25;
double const oversample_factor = 5 / 3.0;
double const fm_gain = 3.0;
const long base_clock = 53700300;
const long clock_rate = base_clock / 15;
Gym_Emu::Gym_Emu()
{
data = 0;
pos = 0;
set_type( gme_gym_type );
static const char* const names [] = {
"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG"
};
set_voice_names( names );
set_silence_lookahead( 1 ); // tracks should already be trimmed
}
Gym_Emu::~Gym_Emu() { }
// Track info
static void get_gym_info( Gym_Emu::header_t const& h, long length, track_info_t* out )
{
if ( !memcmp( h.tag, "GYMX", 4 ) )
{
length = length * 50 / 3; // 1000 / 60
long loop = get_le32( h.loop_start );
if ( loop )
{
out->intro_length = loop * 50 / 3;
out->loop_length = length - out->intro_length;
}
else
{
out->length = length;
out->intro_length = length; // make it clear that track is no longer than length
out->loop_length = 0;
}
// more stupidity where the field should have been left
if ( strcmp( h.song, "Unknown Song" ) )
GME_COPY_FIELD( h, out, song );
if ( strcmp( h.game, "Unknown Game" ) )
GME_COPY_FIELD( h, out, game );
if ( strcmp( h.copyright, "Unknown Publisher" ) )
GME_COPY_FIELD( h, out, copyright );
if ( strcmp( h.dumper, "Unknown Person" ) )
GME_COPY_FIELD( h, out, dumper );
if ( strcmp( h.comment, "Header added by YMAMP" ) )
GME_COPY_FIELD( h, out, comment );
}
}
blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const
{
get_gym_info( header_, track_length(), out );
return 0;
}
static long gym_track_length( byte const* p, byte const* end )
{
long time = 0;
while ( p < end )
{
switch ( *p++ )
{
case 0:
time++;
break;
case 1:
case 2:
p += 2;
break;
case 3:
p += 1;
break;
}
}
return time;
}
long Gym_Emu::track_length() const { return gym_track_length( data, data_end ); }
static blargg_err_t check_header( byte const* in, long size, int* data_offset = 0 )
{
if ( size < 4 )
return gme_wrong_file_type;
if ( memcmp( in, "GYMX", 4 ) == 0 )
{
if ( size < Gym_Emu::header_size + 1 )
return gme_wrong_file_type;
if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 )
return "Packed GYM file not supported";
if ( data_offset )
*data_offset = Gym_Emu::header_size;
}
else if ( *in > 3 )
{
return gme_wrong_file_type;
}
return 0;
}
struct Gym_File : Gme_Info_
{
byte const* file_begin;
byte const* file_end;
int data_offset;
Gym_File() { set_type( gme_gym_type ); }
blargg_err_t load_mem_( byte const* in, long size )
{
file_begin = in;
file_end = in + size;
data_offset = 0;
return check_header( in, size, &data_offset );
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
long length = gym_track_length( &file_begin [data_offset], file_end );
get_gym_info( *(Gym_Emu::header_t const*) file_begin, length, out );
return 0;
}
};
static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; }
static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; }
gme_type_t_ const gme_gym_type [1] = { "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 };
// Setup
blargg_err_t Gym_Emu::set_sample_rate_( long sample_rate )
{
blip_eq_t eq( -32, 8000, sample_rate );
apu.treble_eq( eq );
dac_synth.treble_eq( eq );
apu.volume( 0.135 * fm_gain * gain() );
dac_synth.volume( 0.125 / 256 * fm_gain * gain() );
double factor = Dual_Resampler::setup( oversample_factor, 0.990, fm_gain * gain() );
fm_sample_rate = sample_rate * factor;
RETURN_ERR( blip_buf.set_sample_rate( sample_rate, int (1000 / 60.0 / min_tempo) ) );
blip_buf.clock_rate( clock_rate );
RETURN_ERR( fm.set_rate( fm_sample_rate, base_clock / 7.0 ) );
RETURN_ERR( Dual_Resampler::reset( long (1.0 / 60 / min_tempo * sample_rate) ) );
return 0;
}
void Gym_Emu::set_tempo_( double t )
{
if ( t < min_tempo )
{
set_tempo( min_tempo );
return;
}
if ( blip_buf.sample_rate() )
{
clocks_per_frame = long (clock_rate / 60 / tempo());
Dual_Resampler::resize( long (sample_rate() / (60.0 * tempo())) );
}
}
void Gym_Emu::mute_voices_( int mask )
{
Music_Emu::mute_voices_( mask );
fm.mute_voices( mask );
dac_muted = (mask & 0x40) != 0;
apu.output( (mask & 0x80) ? 0 : &blip_buf );
}
blargg_err_t Gym_Emu::load_mem_( byte const* in, long size )
{
assert( offsetof (header_t,packed [4]) == header_size );
int offset = 0;
RETURN_ERR( check_header( in, size, &offset ) );
set_voice_count( 8 );
data = in + offset;
data_end = in + size;
loop_begin = 0;
if ( offset )
header_ = *(header_t const*) in;
else
memset( &header_, 0, sizeof header_ );
return 0;
}
// Emulation
blargg_err_t Gym_Emu::start_track_( int track )
{
RETURN_ERR( Music_Emu::start_track_( track ) );
pos = data;
loop_remain = get_le32( header_.loop_start );
prev_dac_count = 0;
dac_enabled = false;
dac_amp = -1;
fm.reset();
apu.reset();
blip_buf.clear();
Dual_Resampler::clear();
return 0;
}
void Gym_Emu::run_dac( int dac_count )
{
// Guess beginning and end of sample and adjust rate and buffer position accordingly.
// count dac samples in next frame
int next_dac_count = 0;
const byte* p = this->pos;
int cmd;
while ( (cmd = *p++) != 0 )
{
int data = *p++;
if ( cmd <= 2 )
++p;
if ( cmd == 1 && data == 0x2A )
next_dac_count++;
}
// detect beginning and end of sample
int rate_count = dac_count;
int start = 0;
if ( !prev_dac_count && next_dac_count && dac_count < next_dac_count )
{
rate_count = next_dac_count;
start = next_dac_count - dac_count;
}
else if ( prev_dac_count && !next_dac_count && dac_count < prev_dac_count )
{
rate_count = prev_dac_count;
}
// Evenly space samples within buffer section being used
blip_resampled_time_t period = blip_buf.resampled_duration( clocks_per_frame ) / rate_count;
blip_resampled_time_t time = blip_buf.resampled_time( 0 ) +
period * start + (period >> 1);
int dac_amp = this->dac_amp;
if ( dac_amp < 0 )
dac_amp = dac_buf [0];
for ( int i = 0; i < dac_count; i++ )
{
int delta = dac_buf [i] - dac_amp;
dac_amp += delta;
dac_synth.offset_resampled( time, delta, &blip_buf );
time += period;
}
this->dac_amp = dac_amp;
}
void Gym_Emu::parse_frame()
{
int dac_count = 0;
const byte* pos = this->pos;
if ( loop_remain && !--loop_remain )
loop_begin = pos; // find loop on first time through sequence
int cmd;
while ( (cmd = *pos++) != 0 )
{
int data = *pos++;
if ( cmd == 1 )
{
int data2 = *pos++;
if ( data != 0x2A )
{
if ( data == 0x2B )
dac_enabled = (data2 & 0x80) != 0;
fm.write0( data, data2 );
}
else if ( dac_count < (int) sizeof dac_buf )
{
dac_buf [dac_count] = data2;
dac_count += dac_enabled;
}
}
else if ( cmd == 2 )
{
fm.write1( data, *pos++ );
}
else if ( cmd == 3 )
{
apu.write_data( 0, data );
}
else
{
// to do: many GYM streams are full of errors, and error count should
// reflect cases where music is really having problems
//log_error();
--pos; // put data back
}
}
// loop
if ( pos >= data_end )
{
check( pos == data_end );
if ( loop_begin )
pos = loop_begin;
else
set_track_ended();
}
this->pos = pos;
// dac
if ( dac_count && !dac_muted )
run_dac( dac_count );
prev_dac_count = dac_count;
}
int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf )
{
if ( !track_ended() )
parse_frame();
apu.end_frame( blip_time );
memset( buf, 0, sample_count * sizeof *buf );
fm.run( sample_count >> 1, buf );
return sample_count;
}
blargg_err_t Gym_Emu::play_( long count, sample_t* out )
{
Dual_Resampler::dual_play( count, out, blip_buf );
return 0;
}

82
Frameworks/GME/gme/Gym_Emu.h Executable file
View file

@ -0,0 +1,82 @@
// Sega Genesis/Mega Drive GYM music file emulator
// Includes with PCM timing recovery to improve sample quality.
// Game_Music_Emu 0.5.2
#ifndef GYM_EMU_H
#define GYM_EMU_H
#include "Dual_Resampler.h"
#include "Ym2612_Emu.h"
#include "Music_Emu.h"
#include "Sms_Apu.h"
class Gym_Emu : public Music_Emu, private Dual_Resampler {
public:
// GYM file header
enum { header_size = 428 };
struct header_t
{
char tag [4];
char song [32];
char game [32];
char copyright [32];
char emulator [32];
char dumper [32];
char comment [256];
byte loop_start [4]; // in 1/60 seconds, 0 if not looped
byte packed [4];
};
// Header for currently loaded file
header_t const& header() const { return header_; }
static gme_type_t static_type() { return gme_gym_type; }
public:
// deprecated
Music_Emu::load;
blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
{ return load_remaining_( &h, sizeof h, in ); }
enum { gym_rate = 60 };
long track_length() const; // use track_info()
public:
Gym_Emu();
~Gym_Emu();
protected:
blargg_err_t load_mem_( byte const*, long );
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t set_sample_rate_( long sample_rate );
blargg_err_t start_track_( int );
blargg_err_t play_( long count, sample_t* );
void mute_voices_( int );
void set_tempo_( double );
int play_frame( blip_time_t blip_time, int sample_count, sample_t* buf );
private:
// sequence data begin, loop begin, current position, end
const byte* data;
const byte* loop_begin;
const byte* pos;
const byte* data_end;
blargg_long loop_remain; // frames remaining until loop beginning has been located
header_t header_;
double fm_sample_rate;
blargg_long clocks_per_frame;
void parse_frame();
// dac (pcm)
int dac_amp;
int prev_dac_count;
bool dac_enabled;
bool dac_muted;
void run_dac( int );
// sound
Blip_Buffer blip_buf;
Ym2612_Emu fm;
Blip_Synth<blip_med_quality,1> dac_synth;
Sms_Apu apu;
byte dac_buf [1024];
};
#endif

315
Frameworks/GME/gme/Hes_Apu.cpp Executable file
View file

@ -0,0 +1,315 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Hes_Apu.h"
#include <string.h>
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
bool const center_waves = true; // reduces asymmetry and clamping when starting notes
Hes_Apu::Hes_Apu()
{
Hes_Osc* osc = &oscs [osc_count];
do
{
osc--;
osc->outputs [0] = 0;
osc->outputs [1] = 0;
osc->chans [0] = 0;
osc->chans [1] = 0;
osc->chans [2] = 0;
}
while ( osc != oscs );
reset();
}
void Hes_Apu::reset()
{
latch = 0;
balance = 0xFF;
Hes_Osc* osc = &oscs [osc_count];
do
{
osc--;
memset( osc, 0, offsetof (Hes_Osc,outputs) );
osc->noise_lfsr = 1;
osc->control = 0x40;
osc->balance = 0xFF;
}
while ( osc != oscs );
}
void Hes_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
require( (unsigned) index < osc_count );
oscs [index].chans [0] = center;
oscs [index].chans [1] = left;
oscs [index].chans [2] = right;
Hes_Osc* osc = &oscs [osc_count];
do
{
osc--;
balance_changed( *osc );
}
while ( osc != oscs );
}
void Hes_Osc::run_until( synth_t& synth_, blip_time_t end_time )
{
Blip_Buffer* const osc_outputs_0 = outputs [0]; // cache often-used values
if ( osc_outputs_0 && control & 0x80 )
{
int dac = this->dac;
int const volume_0 = volume [0];
{
int delta = dac * volume_0 - last_amp [0];
if ( delta )
synth_.offset( last_time, delta, osc_outputs_0 );
osc_outputs_0->set_modified();
}
Blip_Buffer* const osc_outputs_1 = outputs [1];
int const volume_1 = volume [1];
if ( osc_outputs_1 )
{
int delta = dac * volume_1 - last_amp [1];
if ( delta )
synth_.offset( last_time, delta, osc_outputs_1 );
osc_outputs_1->set_modified();
}
blip_time_t time = last_time + delay;
if ( time < end_time )
{
if ( noise & 0x80 )
{
if ( volume_0 | volume_1 )
{
// noise
int const period = (32 - (noise & 0x1F)) * 64; // TODO: correct?
unsigned noise_lfsr = this->noise_lfsr;
do
{
int new_dac = 0x1F & -(noise_lfsr >> 1 & 1);
// Implemented using "Galios configuration"
// TODO: find correct LFSR algorithm
noise_lfsr = (noise_lfsr >> 1) ^ (0xE008 & -(noise_lfsr & 1));
//noise_lfsr = (noise_lfsr >> 1) ^ (0x6000 & -(noise_lfsr & 1));
int delta = new_dac - dac;
if ( delta )
{
dac = new_dac;
synth_.offset( time, delta * volume_0, osc_outputs_0 );
if ( osc_outputs_1 )
synth_.offset( time, delta * volume_1, osc_outputs_1 );
}
time += period;
}
while ( time < end_time );
this->noise_lfsr = noise_lfsr;
assert( noise_lfsr );
}
}
else if ( !(control & 0x40) )
{
// wave
int phase = (this->phase + 1) & 0x1F; // pre-advance for optimal inner loop
int period = this->period * 2;
if ( period >= 14 && (volume_0 | volume_1) )
{
do
{
int new_dac = wave [phase];
phase = (phase + 1) & 0x1F;
int delta = new_dac - dac;
if ( delta )
{
dac = new_dac;
synth_.offset( time, delta * volume_0, osc_outputs_0 );
if ( osc_outputs_1 )
synth_.offset( time, delta * volume_1, osc_outputs_1 );
}
time += period;
}
while ( time < end_time );
}
else
{
if ( !period )
{
// TODO: Gekisha Boy assumes that period = 0 silences wave
//period = 0x1000 * 2;
period = 1;
//if ( !(volume_0 | volume_1) )
// dprintf( "Used period 0\n" );
}
// maintain phase when silent
blargg_long count = (end_time - time + period - 1) / period;
phase += count; // phase will be masked below
time += count * period;
}
this->phase = (phase - 1) & 0x1F; // undo pre-advance
}
}
time -= end_time;
if ( time < 0 )
time = 0;
delay = time;
this->dac = dac;
last_amp [0] = dac * volume_0;
last_amp [1] = dac * volume_1;
}
last_time = end_time;
}
void Hes_Apu::balance_changed( Hes_Osc& osc )
{
static short const log_table [32] = { // ~1.5 db per step
#define ENTRY( factor ) short (factor * Hes_Osc::amp_range / 31.0 + 0.5)
ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ),
ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ),
ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ),
ENTRY( 0.037163 ),ENTRY( 0.044194 ),ENTRY( 0.052556 ),ENTRY( 0.062500 ),
ENTRY( 0.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ),
ENTRY( 0.148651 ),ENTRY( 0.176777 ),ENTRY( 0.210224 ),ENTRY( 0.250000 ),
ENTRY( 0.297302 ),ENTRY( 0.353553 ),ENTRY( 0.420448 ),ENTRY( 0.500000 ),
ENTRY( 0.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ),
#undef ENTRY
};
int vol = (osc.control & 0x1F) - 0x1E * 2;
int left = vol + (osc.balance >> 3 & 0x1E) + (balance >> 3 & 0x1E);
if ( left < 0 ) left = 0;
int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E);
if ( right < 0 ) right = 0;
left = log_table [left ];
right = log_table [right];
// optimizing for the common case of being centered also allows easy
// panning using Effects_Buffer
osc.outputs [0] = osc.chans [0]; // center
osc.outputs [1] = 0;
if ( left != right )
{
osc.outputs [0] = osc.chans [1]; // left
osc.outputs [1] = osc.chans [2]; // right
}
if ( center_waves )
{
osc.last_amp [0] += (left - osc.volume [0]) * 16;
osc.last_amp [1] += (right - osc.volume [1]) * 16;
}
osc.volume [0] = left;
osc.volume [1] = right;
}
void Hes_Apu::write_data( blip_time_t time, int addr, int data )
{
if ( addr == 0x800 )
{
latch = data & 7;
}
else if ( addr == 0x801 )
{
if ( balance != data )
{
balance = data;
Hes_Osc* osc = &oscs [osc_count];
do
{
osc--;
osc->run_until( synth, time );
balance_changed( *oscs );
}
while ( osc != oscs );
}
}
else if ( latch < osc_count )
{
Hes_Osc& osc = oscs [latch];
osc.run_until( synth, time );
switch ( addr )
{
case 0x802:
osc.period = (osc.period & 0xF00) | data;
break;
case 0x803:
osc.period = (osc.period & 0x0FF) | ((data & 0x0F) << 8);
break;
case 0x804:
if ( osc.control & 0x40 & ~data )
osc.phase = 0;
osc.control = data;
balance_changed( osc );
break;
case 0x805:
osc.balance = data;
balance_changed( osc );
break;
case 0x806:
data &= 0x1F;
if ( !(osc.control & 0x40) )
{
osc.wave [osc.phase] = data;
osc.phase = (osc.phase + 1) & 0x1F;
}
else if ( osc.control & 0x80 )
{
osc.dac = data;
}
break;
case 0x807:
if ( &osc >= &oscs [4] )
osc.noise = data;
break;
case 0x809:
if ( !(data & 0x80) && (data & 0x03) != 0 )
dprintf( "HES LFO not supported\n" );
}
}
}
void Hes_Apu::end_frame( blip_time_t end_time )
{
Hes_Osc* osc = &oscs [osc_count];
do
{
osc--;
if ( end_time > osc->last_time )
osc->run_until( synth, end_time );
assert( osc->last_time >= end_time );
osc->last_time -= end_time;
}
while ( osc != oscs );
}

66
Frameworks/GME/gme/Hes_Apu.h Executable file
View file

@ -0,0 +1,66 @@
// Turbo Grafx 16 (PC Engine) PSG sound chip emulator
// Game_Music_Emu 0.5.2
#ifndef HES_APU_H
#define HES_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
struct Hes_Osc
{
unsigned char wave [32];
short volume [2];
int last_amp [2];
int delay;
int period;
unsigned char noise;
unsigned char phase;
unsigned char balance;
unsigned char dac;
blip_time_t last_time;
Blip_Buffer* outputs [2];
Blip_Buffer* chans [3];
unsigned noise_lfsr;
unsigned char control;
enum { amp_range = 0x8000 };
typedef Blip_Synth<blip_med_quality,1> synth_t;
void run_until( synth_t& synth, blip_time_t );
};
class Hes_Apu {
public:
void treble_eq( blip_eq_t const& );
void volume( double );
enum { osc_count = 6 };
void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
void reset();
enum { start_addr = 0x0800 };
enum { end_addr = 0x0809 };
void write_data( blip_time_t, int addr, int data );
void end_frame( blip_time_t );
public:
Hes_Apu();
private:
Hes_Osc oscs [osc_count];
int latch;
int balance;
Hes_Osc::synth_t synth;
void balance_changed( Hes_Osc& );
void recalc_chans();
};
inline void Hes_Apu::volume( double v ) { synth.volume( 1.8 / osc_count / Hes_Osc::amp_range * v ); }
inline void Hes_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
#endif

1303
Frameworks/GME/gme/Hes_Cpu.cpp Executable file

File diff suppressed because it is too large Load diff

124
Frameworks/GME/gme/Hes_Cpu.h Executable file
View file

@ -0,0 +1,124 @@
// PC Engine CPU emulator for use with HES music files
// Game_Music_Emu 0.5.2
#ifndef HES_CPU_H
#define HES_CPU_H
#include "blargg_common.h"
typedef blargg_long hes_time_t; // clock cycle count
typedef unsigned hes_addr_t; // 16-bit address
enum { future_hes_time = LONG_MAX / 2 + 1 };
class Hes_Cpu {
public:
typedef BOOST::uint8_t uint8_t;
void reset();
enum { page_size = 0x2000 };
enum { page_shift = 13 };
enum { page_count = 8 };
void set_mmr( int reg, int bank );
uint8_t const* get_code( hes_addr_t );
uint8_t ram [page_size];
// not kept updated during a call to run()
struct registers_t {
BOOST::uint16_t pc;
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t status;
uint8_t sp;
};
registers_t r;
// page mapping registers
uint8_t mmr [page_count + 1];
// Set end_time and run CPU from current time. Returns true if any illegal
// instructions were encountered.
bool run( hes_time_t end_time );
// Time of beginning of next instruction to be executed
hes_time_t time() const { return state->time + state->base; }
void set_time( hes_time_t t ) { state->time = t - state->base; }
void adjust_time( int delta ) { state->time += delta; }
hes_time_t irq_time() const { return irq_time_; }
void set_irq_time( hes_time_t );
hes_time_t end_time() const { return end_time_; }
void set_end_time( hes_time_t );
void end_frame( hes_time_t );
// Attempt to execute instruction here results in CPU advancing time to
// lesser of irq_time() and end_time() (or end_time() if IRQs are
// disabled)
enum { idle_addr = 0x1FFF };
// Can read this many bytes past end of a page
enum { cpu_padding = 8 };
public:
Hes_Cpu() { state = &state_; }
enum { irq_inhibit = 0x04 };
private:
// noncopyable
Hes_Cpu( const Hes_Cpu& );
Hes_Cpu& operator = ( const Hes_Cpu& );
struct state_t {
uint8_t const* code_map [page_count + 1];
hes_time_t base;
blargg_long time;
};
state_t* state; // points to state_ or a local copy within run()
state_t state_;
hes_time_t irq_time_;
hes_time_t end_time_;
void set_code_page( int, void const* );
inline int update_end_time( hes_time_t end, hes_time_t irq );
};
inline BOOST::uint8_t const* Hes_Cpu::get_code( hes_addr_t addr )
{
return state->code_map [addr >> page_shift] + addr
#if !BLARGG_NONPORTABLE
% (unsigned) page_size
#endif
;
}
inline int Hes_Cpu::update_end_time( hes_time_t t, hes_time_t irq )
{
if ( irq < t && !(r.status & irq_inhibit) ) t = irq;
int delta = state->base - t;
state->base = t;
return delta;
}
inline void Hes_Cpu::set_irq_time( hes_time_t t )
{
state->time += update_end_time( end_time_, (irq_time_ = t) );
}
inline void Hes_Cpu::set_end_time( hes_time_t t )
{
state->time += update_end_time( (end_time_ = t), irq_time_ );
}
inline void Hes_Cpu::end_frame( hes_time_t t )
{
assert( state == &state_ );
state_.base -= t;
if ( irq_time_ < future_hes_time ) irq_time_ -= t;
if ( end_time_ < future_hes_time ) end_time_ -= t;
}
#endif

529
Frameworks/GME/gme/Hes_Emu.cpp Executable file
View file

@ -0,0 +1,529 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Hes_Emu.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const timer_mask = 0x04;
int const vdp_mask = 0x02;
int const i_flag_mask = 0x04;
int const unmapped = 0xFF;
long const period_60hz = 262 * 455L; // scanlines * clocks per scanline
Hes_Emu::Hes_Emu()
{
timer.raw_load = 0;
set_type( gme_hes_type );
static const char* const names [Hes_Apu::osc_count] = {
"Wave 1", "Wave 2", "Wave 3", "Wave 4", "Multi 1", "Multi 2"
};
set_voice_names( names );
static int const types [Hes_Apu::osc_count] = {
wave_type | 0, wave_type | 1, wave_type | 2, wave_type | 3,
mixed_type | 0, mixed_type | 1
};
set_voice_types( types );
set_silence_lookahead( 6 );
set_gain( 1.11 );
}
Hes_Emu::~Hes_Emu() { }
void Hes_Emu::unload()
{
rom.clear();
Music_Emu::unload();
}
// Track info
static byte const* copy_field( byte const* in, char* out )
{
if ( in )
{
int len = 0x20;
if ( in [0x1F] && !in [0x2F] )
len = 0x30; // fields are sometimes 16 bytes longer (ugh)
// since text fields are where any data could be, detect non-text
// and fields with data after zero byte terminator
int i = 0;
for ( i = 0; i < len && in [i]; i++ )
if ( ((in [i] + 1) & 0xFF) < ' ' + 1 ) // also treat 0xFF as non-text
return 0; // non-ASCII found
for ( ; i < len; i++ )
if ( in [i] )
return 0; // data after terminator
Gme_File::copy_field_( out, (char const*) in, len );
in += len;
}
return in;
}
static void copy_hes_fields( byte const* in, track_info_t* out )
{
if ( *in >= ' ' )
{
in = copy_field( in, out->game );
in = copy_field( in, out->author );
in = copy_field( in, out->copyright );
}
}
blargg_err_t Hes_Emu::track_info_( track_info_t* out, int ) const
{
copy_hes_fields( rom.begin() + 0x20, out );
return 0;
}
static blargg_err_t check_hes_header( void const* header )
{
if ( memcmp( header, "HESM", 4 ) )
return gme_wrong_file_type;
return 0;
}
struct Hes_File : Gme_Info_
{
struct header_t {
char header [Hes_Emu::header_size];
char unused [0x20];
byte fields [0x30 * 3];
} h;
Hes_File() { set_type( gme_hes_type ); }
blargg_err_t load_( Data_Reader& in )
{
assert( offsetof (header_t,fields) == Hes_Emu::header_size + 0x20 );
blargg_err_t err = in.read( &h, sizeof h );
if ( err )
return (err == in.eof_error ? gme_wrong_file_type : err);
return check_hes_header( &h );
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_hes_fields( h.fields, out );
return 0;
}
};
static Music_Emu* new_hes_emu () { return BLARGG_NEW Hes_Emu ; }
static Music_Emu* new_hes_file() { return BLARGG_NEW Hes_File; }
gme_type_t_ const gme_hes_type [1] = { "PC Engine", 256, &new_hes_emu, &new_hes_file, "HES", 1 };
// Setup
blargg_err_t Hes_Emu::load_( Data_Reader& in )
{
assert( offsetof (header_t,unused [4]) == header_size );
RETURN_ERR( rom.load( in, header_size, &header_, unmapped ) );
RETURN_ERR( check_hes_header( header_.tag ) );
if ( header_.vers != 0 )
set_warning( "Unknown file version" );
if ( memcmp( header_.data_tag, "DATA", 4 ) )
set_warning( "Data header missing" );
if ( memcmp( header_.unused, "\0\0\0\0", 4 ) )
set_warning( "Unknown header data" );
// File spec supports multiple blocks, but I haven't found any, and
// many files have bad sizes in the only block, so it's simpler to
// just try to load the damn data as best as possible.
long addr = get_le32( header_.addr );
long size = get_le32( header_.size );
long const rom_max = 0x100000;
if ( addr & ~(rom_max - 1) )
{
set_warning( "Invalid address" );
addr &= rom_max - 1;
}
if ( (unsigned long) (addr + size) > (unsigned long) rom_max )
set_warning( "Invalid size" );
if ( size != rom.file_size() )
{
if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) )
set_warning( "Multiple DATA not supported" );
else if ( size < rom.file_size() )
set_warning( "Extra file data" );
else
set_warning( "Missing file data" );
}
rom.set_addr( addr );
set_voice_count( apu.osc_count );
apu.volume( gain() );
return setup_buffer( 7159091 );
}
void Hes_Emu::update_eq( blip_eq_t const& eq )
{
apu.treble_eq( eq );
}
void Hes_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
apu.osc_output( i, center, left, right );
}
// Emulation
void Hes_Emu::recalc_timer_load()
{
timer.load = timer.raw_load * timer_base + 1;
}
void Hes_Emu::set_tempo_( double t )
{
play_period = hes_time_t (period_60hz / t);
timer_base = int (1024 / t);
recalc_timer_load();
}
blargg_err_t Hes_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
memset( ram, 0, sizeof ram ); // some HES music relies on zero fill
memset( sgx, 0, sizeof sgx );
apu.reset();
cpu::reset();
for ( unsigned i = 0; i < sizeof header_.banks; i++ )
set_mmr( i, header_.banks [i] );
set_mmr( page_count, 0xFF ); // unmapped beyond end of address space
irq.disables = timer_mask | vdp_mask;
irq.timer = future_hes_time;
irq.vdp = future_hes_time;
timer.enabled = false;
timer.raw_load= 0x80;
timer.count = timer.load;
timer.fired = false;
timer.last_time = 0;
vdp.latch = 0;
vdp.control = 0;
vdp.next_vbl = 0;
ram [0x1FF] = (idle_addr - 1) >> 8;
ram [0x1FE] = (idle_addr - 1) & 0xFF;
r.sp = 0xFD;
r.pc = get_le16( header_.init_addr );
r.a = track;
recalc_timer_load();
last_frame_hook = 0;
return 0;
}
// Hardware
void Hes_Emu::cpu_write_vdp( int addr, int data )
{
switch ( addr )
{
case 0:
vdp.latch = data & 0x1F;
break;
case 2:
if ( vdp.latch == 5 )
{
if ( data & 0x04 )
set_warning( "Scanline interrupt unsupported" );
run_until( time() );
vdp.control = data;
irq_changed();
}
else
{
dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data );
}
break;
case 3:
dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data );
break;
}
}
void Hes_Emu::cpu_write_( hes_addr_t addr, int data )
{
if ( unsigned (addr - apu.start_addr) <= apu.end_addr - apu.start_addr )
{
GME_APU_HOOK( this, addr - apu.start_addr, data );
// avoid going way past end when a long block xfer is writing to I/O space
hes_time_t t = min( time(), end_time() + 8 );
apu.write_data( t, addr, data );
return;
}
hes_time_t time = this->time();
switch ( addr )
{
case 0x0000:
case 0x0002:
case 0x0003:
cpu_write_vdp( addr, data );
return;
case 0x0C00: {
run_until( time );
timer.raw_load = (data & 0x7F) + 1;
recalc_timer_load();
timer.count = timer.load;
break;
}
case 0x0C01:
data &= 1;
if ( timer.enabled == data )
return;
run_until( time );
timer.enabled = data;
if ( data )
timer.count = timer.load;
break;
case 0x1402:
run_until( time );
irq.disables = data;
if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values
dprintf( "Int mask: $%02X\n", data );
break;
case 0x1403:
run_until( time );
if ( timer.enabled )
timer.count = timer.load;
timer.fired = false;
break;
#ifndef NDEBUG
case 0x1000: // I/O port
case 0x0402: // palette
case 0x0403:
case 0x0404:
case 0x0405:
return;
default:
dprintf( "unmapped write $%04X <- $%02X\n", addr, data );
return;
#endif
}
irq_changed();
}
int Hes_Emu::cpu_read_( hes_addr_t addr )
{
hes_time_t time = this->time();
addr &= page_size - 1;
switch ( addr )
{
case 0x0000:
if ( irq.vdp > time )
return 0;
irq.vdp = future_hes_time;
run_until( time );
irq_changed();
return 0x20;
case 0x0002:
case 0x0003:
dprintf( "VDP read not supported: %d\n", addr );
return 0;
case 0x0C01:
//return timer.enabled; // TODO: remove?
case 0x0C00:
run_until( time );
dprintf( "Timer count read\n" );
return (unsigned) (timer.count - 1) / timer_base;
case 0x1402:
return irq.disables;
case 0x1403:
{
int status = 0;
if ( irq.timer <= time ) status |= timer_mask;
if ( irq.vdp <= time ) status |= vdp_mask;
return status;
}
#ifndef NDEBUG
case 0x1000: // I/O port
case 0x180C: // CD-ROM
case 0x180D:
break;
default:
dprintf( "unmapped read $%04X\n", addr );
#endif
}
return unmapped;
}
// see hes_cpu_io.h for core read/write functions
// Emulation
void Hes_Emu::run_until( hes_time_t present )
{
while ( vdp.next_vbl < present )
vdp.next_vbl += play_period;
hes_time_t elapsed = present - timer.last_time;
if ( elapsed > 0 )
{
if ( timer.enabled )
{
timer.count -= elapsed;
if ( timer.count <= 0 )
timer.count += timer.load;
}
timer.last_time = present;
}
}
void Hes_Emu::irq_changed()
{
hes_time_t present = time();
if ( irq.timer > present )
{
irq.timer = future_hes_time;
if ( timer.enabled && !timer.fired )
irq.timer = present + timer.count;
}
if ( irq.vdp > present )
{
irq.vdp = future_hes_time;
if ( vdp.control & 0x08 )
irq.vdp = vdp.next_vbl;
}
hes_time_t time = future_hes_time;
if ( !(irq.disables & timer_mask) ) time = irq.timer;
if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp );
set_irq_time( time );
}
int Hes_Emu::cpu_done()
{
check( time() >= end_time() ||
(!(r.status & i_flag_mask) && time() >= irq_time()) );
if ( !(r.status & i_flag_mask) )
{
hes_time_t present = time();
if ( irq.timer <= present && !(irq.disables & timer_mask) )
{
timer.fired = true;
irq.timer = future_hes_time;
irq_changed(); // overkill, but not worth writing custom code
#if GME_FRAME_HOOK_DEFINED
{
unsigned const threshold = period_60hz / 30;
unsigned long elapsed = present - last_frame_hook;
if ( elapsed - period_60hz + threshold / 2 < threshold )
{
last_frame_hook = present;
GME_FRAME_HOOK( this );
}
}
#endif
return 0x0A;
}
if ( irq.vdp <= present && !(irq.disables & vdp_mask) )
{
// work around for bugs with music not acknowledging VDP
//run_until( present );
//irq.vdp = future_hes_time;
//irq_changed();
#if GME_FRAME_HOOK_DEFINED
last_frame_hook = present;
GME_FRAME_HOOK( this );
#endif
return 0x08;
}
}
return 0;
}
static void adjust_time( blargg_long& time, hes_time_t delta )
{
if ( time < future_hes_time )
{
time -= delta;
if ( time < 0 )
time = 0;
}
}
blargg_err_t Hes_Emu::run_clocks( blip_time_t& duration_, int )
{
blip_time_t const duration = duration_; // cache
if ( cpu::run( duration ) )
set_warning( "Emulation error (illegal instruction)" );
check( time() >= duration );
//check( time() - duration < 20 ); // Txx instruction could cause going way over
run_until( duration );
// end time frame
timer.last_time -= duration;
vdp.next_vbl -= duration;
#if GME_FRAME_HOOK_DEFINED
last_frame_hook -= duration;
#endif
cpu::end_frame( duration );
::adjust_time( irq.timer, duration );
::adjust_time( irq.vdp, duration );
apu.end_frame( duration );
return 0;
}

94
Frameworks/GME/gme/Hes_Emu.h Executable file
View file

@ -0,0 +1,94 @@
// TurboGrafx-16/PC Engine HES music file emulator
// Game_Music_Emu 0.5.2
#ifndef HES_EMU_H
#define HES_EMU_H
#include "Classic_Emu.h"
#include "Hes_Apu.h"
#include "Hes_Cpu.h"
class Hes_Emu : private Hes_Cpu, public Classic_Emu {
typedef Hes_Cpu cpu;
public:
// HES file header
enum { header_size = 0x20 };
struct header_t
{
byte tag [4];
byte vers;
byte first_track;
byte init_addr [2];
byte banks [8];
byte data_tag [4];
byte size [4];
byte addr [4];
byte unused [4];
};
// Header for currently loaded file
header_t const& header() const { return header_; }
static gme_type_t static_type() { return gme_hes_type; }
public:
Hes_Emu();
~Hes_Emu();
protected:
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t load_( Data_Reader& );
blargg_err_t start_track_( int );
blargg_err_t run_clocks( blip_time_t&, int );
void set_tempo_( double );
void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
void update_eq( blip_eq_t const& );
void unload();
public: private: friend class Hes_Cpu;
byte* write_pages [page_count + 1]; // 0 if unmapped or I/O space
int cpu_read_( hes_addr_t );
int cpu_read( hes_addr_t );
void cpu_write_( hes_addr_t, int data );
void cpu_write( hes_addr_t, int );
void cpu_write_vdp( int addr, int data );
byte const* cpu_set_mmr( int page, int bank );
int cpu_done();
private:
Rom_Data<page_size> rom;
header_t header_;
hes_time_t play_period;
hes_time_t last_frame_hook;
int timer_base;
struct {
hes_time_t last_time;
blargg_long count;
blargg_long load;
int raw_load;
byte enabled;
byte fired;
} timer;
struct {
hes_time_t next_vbl;
byte latch;
byte control;
} vdp;
struct {
hes_time_t timer;
hes_time_t vdp;
byte disables;
} irq;
void recalc_timer_load();
// large items
Hes_Apu apu;
byte sgx [3 * page_size + cpu_padding];
void irq_changed();
void run_until( hes_time_t );
};
#endif

1706
Frameworks/GME/gme/Kss_Cpu.cpp Executable file

File diff suppressed because it is too large Load diff

124
Frameworks/GME/gme/Kss_Cpu.h Executable file
View file

@ -0,0 +1,124 @@
// Z80 CPU emulator
// Game_Music_Emu 0.5.2
#ifndef KSS_CPU_H
#define KSS_CPU_H
#include "blargg_endian.h"
typedef blargg_long cpu_time_t;
// must be defined by caller
void kss_cpu_out( class Kss_Cpu*, cpu_time_t, unsigned addr, int data );
int kss_cpu_in( class Kss_Cpu*, cpu_time_t, unsigned addr );
void kss_cpu_write( class Kss_Cpu*, unsigned addr, int data );
class Kss_Cpu {
public:
typedef BOOST::uint8_t uint8_t;
// Clear registers and map all pages to unmapped
void reset( void* unmapped_write, void const* unmapped_read );
// Map memory. Start and size must be multiple of page_size.
enum { page_size = 0x2000 };
void map_mem( unsigned addr, blargg_ulong size, void* write, void const* read );
// Map address to page
uint8_t* write( unsigned addr );
uint8_t const* read( unsigned addr );
// Run until specified time is reached. Returns true if suspicious/unsupported
// instruction was encountered at any point during run.
bool run( cpu_time_t end_time );
// Time of beginning of next instruction
cpu_time_t time() const { return state->time + state->base; }
// Alter current time. Not supported during run() call.
void set_time( cpu_time_t t ) { state->time = t - state->base; }
void adjust_time( int delta ) { state->time += delta; }
typedef BOOST::uint16_t uint16_t;
#if BLARGG_BIG_ENDIAN
struct regs_t { uint8_t b, c, d, e, h, l, flags, a; };
#else
struct regs_t { uint8_t c, b, e, d, l, h, a, flags; };
#endif
BOOST_STATIC_ASSERT( sizeof (regs_t) == 8 );
struct pairs_t { uint16_t bc, de, hl, fa; };
// Registers are not updated until run() returns
struct registers_t {
uint16_t pc;
uint16_t sp;
uint16_t ix;
uint16_t iy;
union {
regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a
pairs_t w; // w.bc, w.de, w.hl. w.fa
};
union {
regs_t b;
pairs_t w;
} alt;
uint8_t iff1;
uint8_t iff2;
uint8_t r;
uint8_t i;
uint8_t im;
};
//registers_t r; (below for efficiency)
enum { idle_addr = 0xFFFF };
// can read this far past end of a page
enum { cpu_padding = 0x100 };
public:
Kss_Cpu();
enum { page_shift = 13 };
enum { page_count = 0x10000 >> page_shift };
private:
uint8_t szpc [0x200];
cpu_time_t end_time_;
struct state_t {
uint8_t const* read [page_count + 1];
uint8_t * write [page_count + 1];
cpu_time_t base;
cpu_time_t time;
};
state_t* state; // points to state_ or a local copy within run()
state_t state_;
void set_end_time( cpu_time_t t );
void set_page( int i, void* write, void const* read );
public:
registers_t r;
};
#if BLARGG_NONPORTABLE
#define KSS_CPU_PAGE_OFFSET( addr ) (addr)
#else
#define KSS_CPU_PAGE_OFFSET( addr ) ((addr) & (page_size - 1))
#endif
inline BOOST::uint8_t* Kss_Cpu::write( unsigned addr )
{
return state->write [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr );
}
inline BOOST::uint8_t const* Kss_Cpu::read( unsigned addr )
{
return state->read [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr );
}
inline void Kss_Cpu::set_end_time( cpu_time_t t )
{
cpu_time_t delta = state->base - t;
state->base = t;
state->time += delta;
}
#endif

414
Frameworks/GME/gme/Kss_Emu.cpp Executable file
View file

@ -0,0 +1,414 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Kss_Emu.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
long const clock_rate = 3579545;
int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count;
Kss_Emu::Kss_Emu()
{
sn = 0;
set_type( gme_kss_type );
set_silence_lookahead( 6 );
static const char* const names [osc_count] = {
"Square 1", "Square 2", "Square 3",
"Wave 1", "Wave 2", "Wave 3", "Wave 4", "Wave 5"
};
set_voice_names( names );
static int const types [osc_count] = {
wave_type | 0, wave_type | 1, wave_type | 2,
wave_type | 3, wave_type | 4, wave_type | 5, wave_type | 6, wave_type | 7
};
set_voice_types( types );
memset( unmapped_read, 0xFF, sizeof unmapped_read );
}
Kss_Emu::~Kss_Emu() { unload(); }
void Kss_Emu::unload()
{
delete sn;
sn = 0;
Classic_Emu::unload();
}
// Track info
static void copy_kss_fields( Kss_Emu::header_t const& h, track_info_t* out )
{
const char* system = "MSX";
if ( h.device_flags & 0x02 )
{
system = "Sega Master System";
if ( h.device_flags & 0x04 )
system = "Game Gear";
}
Gme_File::copy_field_( out->system, system );
}
blargg_err_t Kss_Emu::track_info_( track_info_t* out, int ) const
{
copy_kss_fields( header_, out );
return 0;
}
static blargg_err_t check_kss_header( void const* header )
{
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
return gme_wrong_file_type;
return 0;
}
struct Kss_File : Gme_Info_
{
Kss_Emu::header_t header_;
Kss_File() { set_type( gme_kss_type ); }
blargg_err_t load_( Data_Reader& in )
{
blargg_err_t err = in.read( &header_, Kss_Emu::header_size );
if ( err )
return (err == in.eof_error ? gme_wrong_file_type : err);
return check_kss_header( &header_ );
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_kss_fields( header_, out );
return 0;
}
};
static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; }
static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; }
gme_type_t_ const gme_kss_type [1] = { "MSX", 256, &new_kss_emu, &new_kss_file, "KSS", 0x03 };
// Setup
void Kss_Emu::update_gain()
{
double g = gain() * 1.4;
if ( scc_accessed )
g *= 1.5;
ay.volume( g );
scc.volume( g );
if ( sn )
sn->volume( g );
}
blargg_err_t Kss_Emu::load_( Data_Reader& in )
{
memset( &header_, 0, sizeof header_ );
assert( offsetof (header_t,device_flags) == header_size - 1 );
assert( offsetof (ext_header_t,msx_audio_vol) == ext_header_size - 1 );
RETURN_ERR( rom.load( in, header_size, STATIC_CAST(header_t*,&header_), 0 ) );
RETURN_ERR( check_kss_header( header_.tag ) );
if ( header_.tag [3] == 'C' )
{
if ( header_.extra_header )
{
header_.extra_header = 0;
set_warning( "Unknown data in header" );
}
if ( header_.device_flags & ~0x0F )
{
header_.device_flags &= 0x0F;
set_warning( "Unknown data in header" );
}
}
else
{
ext_header_t& ext = header_;
memcpy( &ext, rom.begin(), min( (int) ext_header_size, (int) header_.extra_header ) );
if ( header_.extra_header > 0x10 )
set_warning( "Unknown data in header" );
}
if ( header_.device_flags & 0x09 )
set_warning( "FM sound not supported" );
scc_enabled = 0xC000;
if ( header_.device_flags & 0x04 )
scc_enabled = 0;
if ( header_.device_flags & 0x02 && !sn )
CHECK_ALLOC( sn = BLARGG_NEW( Sms_Apu ) );
set_voice_count( osc_count );
return setup_buffer( ::clock_rate );
}
void Kss_Emu::update_eq( blip_eq_t const& eq )
{
ay.treble_eq( eq );
scc.treble_eq( eq );
if ( sn )
sn->treble_eq( eq );
}
void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
int i2 = i - ay.osc_count;
if ( i2 >= 0 )
scc.osc_output( i2, center );
else
ay.osc_output( i, center );
if ( sn && i < sn->osc_count )
sn->osc_output( i, center, left, right );
}
// Emulation
void Kss_Emu::set_tempo_( double t )
{
blip_time_t period =
(header_.device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60);
play_period = blip_time_t (period / t);
}
blargg_err_t Kss_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
memset( ram, 0xC9, 0x4000 );
memset( ram + 0x4000, 0, sizeof ram - 0x4000 );
// copy driver code to lo RAM
static byte const bios [] = {
0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG
0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG
};
static byte const vectors [] = {
0xC3, 0x01, 0x00, // $0093: WRTPSG vector
0xC3, 0x09, 0x00, // $0096: RDPSG vector
};
memcpy( ram + 0x01, bios, sizeof bios );
memcpy( ram + 0x93, vectors, sizeof vectors );
// copy non-banked data into RAM
unsigned load_addr = get_le16( header_.load_addr );
long orig_load_size = get_le16( header_.load_size );
long load_size = min( orig_load_size, rom.file_size() );
load_size = min( load_size, long (mem_size - load_addr) );
if ( load_size != orig_load_size )
set_warning( "Excessive data size" );
memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size );
rom.set_addr( -load_size - header_.extra_header );
// check available bank data
blargg_long const bank_size = this->bank_size();
int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size;
bank_count = header_.bank_mode & 0x7F;
if ( bank_count > max_banks )
{
bank_count = max_banks;
set_warning( "Bank data missing" );
}
//dprintf( "load_size : $%X\n", load_size );
//dprintf( "bank_size : $%X\n", bank_size );
//dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F );
ram [idle_addr] = 0xFF;
cpu::reset( unmapped_write, unmapped_read );
cpu::map_mem( 0, mem_size, ram, ram );
ay.reset();
scc.reset();
if ( sn )
sn->reset();
r.sp = 0xF380;
ram [--r.sp] = idle_addr >> 8;
ram [--r.sp] = idle_addr & 0xFF;
r.b.a = track;
r.pc = get_le16( header_.init_addr );
next_play = play_period;
scc_accessed = false;
gain_updated = false;
update_gain();
ay_latch = 0;
return 0;
}
void Kss_Emu::set_bank( int logical, int physical )
{
unsigned const bank_size = this->bank_size();
unsigned addr = 0x8000;
if ( logical && bank_size == 8 * 1024 )
addr = 0xA000;
physical -= header_.first_bank;
if ( (unsigned) physical >= (unsigned) bank_count )
{
byte* data = ram + addr;
cpu::map_mem( addr, bank_size, data, data );
}
else
{
long phys = physical * (blargg_long) bank_size;
for ( unsigned offset = 0; offset < bank_size; offset += page_size )
cpu::map_mem( addr + offset, page_size,
unmapped_write, rom.at_addr( phys + offset ) );
}
}
void Kss_Emu::cpu_write( unsigned addr, int data )
{
data &= 0xFF;
switch ( addr )
{
case 0x9000:
set_bank( 0, data );
return;
case 0xB000:
set_bank( 1, data );
return;
}
int scc_addr = (addr & 0xDFFF) ^ 0x9800;
if ( scc_addr < scc.reg_count )
{
scc_accessed = true;
scc.write( time(), scc_addr, data );
return;
}
dprintf( "LD ($%04X),$%02X\n", addr, data );
}
void kss_cpu_write( Kss_Cpu* cpu, unsigned addr, int data )
{
*cpu->write( addr ) = data;
if ( (addr & STATIC_CAST(Kss_Emu&,*cpu).scc_enabled) == 0x8000 )
STATIC_CAST(Kss_Emu&,*cpu).cpu_write( addr, data );
}
void kss_cpu_out( Kss_Cpu* cpu, cpu_time_t time, unsigned addr, int data )
{
data &= 0xFF;
Kss_Emu& emu = STATIC_CAST(Kss_Emu&,*cpu);
switch ( addr & 0xFF )
{
case 0xA0:
emu.ay_latch = data & 0x0F;
return;
case 0xA1:
GME_APU_HOOK( &emu, emu.ay_latch, data );
emu.ay.write( time, emu.ay_latch, data );
return;
case 0x06:
if ( emu.sn && (emu.header_.device_flags & 0x04) )
{
emu.sn->write_ggstereo( time, data );
return;
}
break;
case 0x7E:
case 0x7F:
if ( emu.sn )
{
GME_APU_HOOK( &emu, 16, data );
emu.sn->write_data( time, data );
return;
}
break;
case 0xFE:
emu.set_bank( 0, data );
return;
#ifndef NDEBUG
case 0xF1: // FM data
if ( data )
break; // trap non-zero data
case 0xF0: // FM addr
case 0xA8: // PPI
return;
#endif
}
dprintf( "OUT $%04X,$%02X\n", addr, data );
}
int kss_cpu_in( Kss_Cpu*, cpu_time_t, unsigned addr )
{
//Kss_Emu& emu = STATIC_CAST(Kss_Emu&,*cpu);
//switch ( addr & 0xFF )
//{
//}
dprintf( "IN $%04X\n", addr );
return 0;
}
// Emulation
blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int )
{
while ( time() < duration )
{
blip_time_t end = min( duration, next_play );
cpu::run( min( duration, next_play ) );
if ( r.pc == idle_addr )
set_time( end );
if ( time() >= next_play )
{
next_play += play_period;
if ( r.pc == idle_addr )
{
if ( !gain_updated )
{
gain_updated = true;
if ( scc_accessed )
update_gain();
}
ram [--r.sp] = idle_addr >> 8;
ram [--r.sp] = idle_addr & 0xFF;
r.pc = get_le16( header_.play_addr );
GME_FRAME_HOOK( this );
}
}
}
duration = time();
next_play -= duration;
check( next_play >= 0 );
adjust_time( -duration );
ay.end_frame( duration );
scc.end_frame( duration );
if ( sn )
sn->end_frame( duration );
return 0;
}

96
Frameworks/GME/gme/Kss_Emu.h Executable file
View file

@ -0,0 +1,96 @@
// MSX computer KSS music file emulator
// Game_Music_Emu 0.5.2
#ifndef KSS_EMU_H
#define KSS_EMU_H
#include "Classic_Emu.h"
#include "Kss_Scc_Apu.h"
#include "Kss_Cpu.h"
#include "Sms_Apu.h"
#include "Ay_Apu.h"
class Kss_Emu : private Kss_Cpu, public Classic_Emu {
typedef Kss_Cpu cpu;
public:
// KSS file header
enum { header_size = 0x10 };
struct header_t
{
byte tag [4];
byte load_addr [2];
byte load_size [2];
byte init_addr [2];
byte play_addr [2];
byte first_bank;
byte bank_mode;
byte extra_header;
byte device_flags;
};
enum { ext_header_size = 0x10 };
struct ext_header_t
{
byte data_size [4];
byte unused [4];
byte first_track [2];
byte last_tack [2];
byte psg_vol;
byte scc_vol;
byte msx_music_vol;
byte msx_audio_vol;
};
struct composite_header_t : header_t, ext_header_t { };
// Header for currently loaded file
composite_header_t const& header() const { return header_; }
static gme_type_t static_type() { return gme_kss_type; }
public:
Kss_Emu();
~Kss_Emu();
protected:
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t load_( Data_Reader& );
blargg_err_t start_track_( int );
blargg_err_t run_clocks( blip_time_t&, int );
void set_tempo_( double );
void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
void update_eq( blip_eq_t const& );
void unload();
private:
Rom_Data<page_size> rom;
composite_header_t header_;
bool scc_accessed;
bool gain_updated;
void update_gain();
unsigned scc_enabled; // 0 or 0xC000
byte const* bank_data;
int bank_count;
void set_bank( int logical, int physical );
blargg_long bank_size() const { return (16 * 1024L) >> (header_.bank_mode >> 7 & 1); }
blip_time_t play_period;
blip_time_t next_play;
int ay_latch;
friend void kss_cpu_out( class Kss_Cpu*, cpu_time_t, unsigned addr, int data );
friend int kss_cpu_in( class Kss_Cpu*, cpu_time_t, unsigned addr );
void cpu_write( unsigned addr, int data );
friend void kss_cpu_write( class Kss_Cpu*, unsigned addr, int data );
// large items
enum { mem_size = 0x10000 };
byte ram [mem_size + cpu_padding];
Ay_Apu ay;
Scc_Apu scc;
Sms_Apu* sn;
byte unmapped_read [0x100];
byte unmapped_write [page_size];
};
#endif

View file

@ -0,0 +1,97 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Kss_Scc_Apu.h"
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// Tones above this frequency are treated as disabled tone at half volume.
// Power of two is more efficient (avoids division).
unsigned const inaudible_freq = 16384;
int const wave_size = 0x20;
void Scc_Apu::run_until( blip_time_t end_time )
{
for ( int index = 0; index < osc_count; index++ )
{
osc_t& osc = oscs [index];
Blip_Buffer* const output = osc.output;
if ( !output )
continue;
output->set_modified();
blip_time_t period = (regs [0x80 + index * 2 + 1] & 0x0F) * 0x100 +
regs [0x80 + index * 2] + 1;
int volume = 0;
if ( regs [0x8F] & (1 << index) )
{
blip_time_t inaudible_period = (blargg_ulong) (output->clock_rate() +
inaudible_freq * 32) / (inaudible_freq * 16);
if ( period > inaudible_period )
volume = (regs [0x8A + index] & 0x0F) * (amp_range / 256 / 15);
}
BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size;
if ( index == osc_count - 1 )
wave -= wave_size; // last two oscs share wave
{
int amp = wave [osc.phase] * volume;
int delta = amp - osc.last_amp;
if ( delta )
{
osc.last_amp = amp;
synth.offset( last_time, delta, output );
}
}
blip_time_t time = last_time + osc.delay;
if ( time < end_time )
{
if ( !volume )
{
// maintain phase
blargg_long count = (end_time - time + period - 1) / period;
osc.phase = (osc.phase + count) & (wave_size - 1);
time += count * period;
}
else
{
int phase = osc.phase;
int last_wave = wave [phase];
phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop
do
{
int amp = wave [phase];
phase = (phase + 1) & (wave_size - 1);
int delta = amp - last_wave;
if ( delta )
{
last_wave = amp;
synth.offset( time, delta * volume, output );
}
time += period;
}
while ( time < end_time );
osc.phase = phase = (phase - 1) & (wave_size - 1); // undo pre-advance
osc.last_amp = wave [phase] * volume;
}
}
osc.delay = time - end_time;
}
last_time = end_time;
}

106
Frameworks/GME/gme/Kss_Scc_Apu.h Executable file
View file

@ -0,0 +1,106 @@
// Konami SCC sound chip emulator
// Game_Music_Emu 0.5.2
#ifndef KSS_SCC_APU_H
#define KSS_SCC_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
#include <string.h>
class Scc_Apu {
public:
// Set buffer to generate all sound into, or disable sound if NULL
void output( Blip_Buffer* );
// Reset sound chip
void reset();
// Write to register at specified time
enum { reg_count = 0x90 };
void write( blip_time_t time, int reg, int data );
// Run sound to specified time, end current time frame, then start a new
// time frame at time 0. Time frames have no effect on emulation and each
// can be whatever length is convenient.
void end_frame( blip_time_t length );
// Additional features
// Set sound output of specific oscillator to buffer, where index is
// 0 to 4. If buffer is NULL, the specified oscillator is muted.
enum { osc_count = 5 };
void osc_output( int index, Blip_Buffer* );
// Set overall volume (default is 1.0)
void volume( double );
// Set treble equalization (see documentation)
void treble_eq( blip_eq_t const& );
public:
Scc_Apu();
private:
enum { amp_range = 0x8000 };
struct osc_t
{
int delay;
int phase;
int last_amp;
Blip_Buffer* output;
};
osc_t oscs [osc_count];
blip_time_t last_time;
unsigned char regs [reg_count];
Blip_Synth<blip_med_quality,1> synth;
void run_until( blip_time_t );
};
inline void Scc_Apu::volume( double v ) { synth.volume( 0.43 / osc_count / amp_range * v ); }
inline void Scc_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
inline void Scc_Apu::osc_output( int index, Blip_Buffer* b )
{
assert( (unsigned) index < osc_count );
oscs [index].output = b;
}
inline void Scc_Apu::write( blip_time_t time, int addr, int data )
{
assert( (unsigned) addr < reg_count );
run_until( time );
regs [addr] = data;
}
inline void Scc_Apu::end_frame( blip_time_t end_time )
{
if ( end_time > last_time )
run_until( end_time );
last_time -= end_time;
assert( last_time >= 0 );
}
inline void Scc_Apu::output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; i++ )
oscs [i].output = buf;
}
inline Scc_Apu::Scc_Apu()
{
output( 0 );
}
inline void Scc_Apu::reset()
{
last_time = 0;
for ( int i = 0; i < osc_count; i++ )
memset( &oscs [i], 0, offsetof (osc_t,output) );
memset( regs, 0, sizeof regs );
}
#endif

View file

@ -0,0 +1,426 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "M3u_Playlist.h"
#include "Music_Emu.h"
#include <string.h>
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// gme functions defined here to avoid linking in m3u code unless it's used
blargg_err_t Gme_File::load_m3u_( blargg_err_t err )
{
require( raw_track_count_ ); // file must be loaded first
if ( !err )
{
if ( playlist.size() )
track_count_ = playlist.size();
int line = playlist.first_error();
if ( line )
{
// avoid using bloated printf()
char* out = &playlist_warning [sizeof playlist_warning];
*--out = 0;
do {
*--out = line % 10 + '0';
} while ( (line /= 10) > 0 );
static const char str [] = "Problem in m3u at line ";
out -= sizeof str - 1;
memcpy( out, str, sizeof str - 1 );
set_warning( out );
}
}
return err;
}
blargg_err_t Gme_File::load_m3u( const char* path ) { return load_m3u_( playlist.load( path ) ); }
blargg_err_t Gme_File::load_m3u( Data_Reader& in ) { return load_m3u_( playlist.load( in ) ); }
gme_err_t gme_load_m3u( Music_Emu* me, const char* path ) { return me->load_m3u( path ); }
gme_err_t gme_load_m3u_data( Music_Emu* me, const void* data, long size )
{
Mem_File_Reader in( data, size );
return me->load_m3u( in );
}
static char* skip_white( char* in )
{
while ( *in == ' ' )
in++;
return in;
}
inline unsigned from_dec( unsigned n ) { return n - '0'; }
static char* parse_filename( char* in, M3u_Playlist::entry_t& entry )
{
entry.file = in;
entry.type = "";
char* out = in;
while ( 1 )
{
int c = *in;
if ( !c ) break;
in++;
if ( c == ',' ) // commas in filename
{
char* p = skip_white( in );
if ( *p == '$' || from_dec( *p ) <= 9 )
{
in = p;
break;
}
}
if ( c == ':' && in [0] == ':' && in [1] && in [2] != ',' ) // ::type suffix
{
entry.type = ++in;
while ( (c = *in) != 0 && c != ',' )
in++;
if ( c == ',' )
{
*in++ = 0; // terminate type
in = skip_white( in );
}
break;
}
if ( c == '\\' ) // \ prefix for special characters
{
c = *in;
if ( !c ) break;
in++;
}
*out++ = (char) c;
}
*out = 0; // terminate string
return in;
}
static char* next_field( char* in, int* result )
{
while ( 1 )
{
in = skip_white( in );
if ( !*in )
break;
if ( *in == ',' )
{
in++;
break;
}
*result = 1;
in++;
}
return skip_white( in );
}
static char* parse_int_( char* in, int* out )
{
int n = 0;
while ( 1 )
{
unsigned d = from_dec( *in );
if ( d > 9 )
break;
in++;
n = n * 10 + d;
*out = n;
}
return in;
}
static char* parse_int( char* in, int* out, int* result )
{
return next_field( parse_int_( in, out ), result );
}
// Returns 16 or greater if not hex
inline int from_hex_char( int h )
{
h -= 0x30;
if ( (unsigned) h > 9 )
h = ((h - 0x11) & 0xDF) + 10;
return h;
}
static char* parse_track( char* in, M3u_Playlist::entry_t& entry, int* result )
{
if ( *in == '$' )
{
in++;
int n = 0;
while ( 1 )
{
int h = from_hex_char( *in );
if ( h > 15 )
break;
in++;
n = n * 16 + h;
entry.track = n;
}
}
else
{
in = parse_int_( in, &entry.track );
if ( entry.track >= 0 )
entry.decimal_track = 1;
}
return next_field( in, result );
}
static char* parse_time_( char* in, int* out )
{
*out = -1;
int n = -1;
in = parse_int_( in, &n );
if ( n >= 0 )
{
*out = n;
if ( *in == ':' )
{
n = -1;
in = parse_int_( in + 1, &n );
if ( n >= 0 )
*out = *out * 60 + n;
}
}
return in;
}
static char* parse_time( char* in, int* out, int* result )
{
return next_field( parse_time_( in, out ), result );
}
static char* parse_name( char* in )
{
char* out = in;
while ( 1 )
{
int c = *in;
if ( !c ) break;
in++;
if ( c == ',' ) // commas in string
{
char* p = skip_white( in );
if ( *p == ',' || *p == '-' || from_dec( *p ) <= 9 )
{
in = p;
break;
}
}
if ( c == '\\' ) // \ prefix for special characters
{
c = *in;
if ( !c ) break;
in++;
}
*out++ = (char) c;
}
*out = 0; // terminate string
return in;
}
static int parse_line( char* in, M3u_Playlist::entry_t& entry )
{
int result = 0;
// file
entry.file = in;
entry.type = "";
in = parse_filename( in, entry );
// track
entry.track = -1;
entry.decimal_track = 0;
in = parse_track( in, entry, &result );
// name
entry.name = in;
in = parse_name( in );
// time
entry.length = -1;
in = parse_time( in, &entry.length, &result );
// loop
entry.intro = -1;
entry.loop = -1;
if ( *in == '-' )
{
entry.loop = entry.length;
in++;
}
else
{
in = parse_time_( in, &entry.loop );
if ( entry.loop >= 0 )
{
entry.intro = 0;
if ( *in == '-' ) // trailing '-' means that intro length was specified
{
in++;
entry.intro = entry.loop;
entry.loop = entry.length - entry.intro;
}
}
}
in = next_field( in, &result );
// fade
entry.fade = -1;
in = parse_time( in, &entry.fade, &result );
// repeat
entry.repeat = -1;
in = parse_int( in, &entry.repeat, &result );
return result;
}
static void parse_comment( char* in, M3u_Playlist::info_t& info, bool first )
{
in = skip_white( in + 1 );
const char* field = in;
while ( *in && *in != ':' )
in++;
if ( *in == ':' )
{
const char* text = skip_white( in + 1 );
if ( *text )
{
*in = 0;
if ( !strcmp( "Composer", field ) ) info.composer = text;
else if ( !strcmp( "Engineer", field ) ) info.engineer = text;
else if ( !strcmp( "Ripping" , field ) ) info.ripping = text;
else if ( !strcmp( "Tagging" , field ) ) info.tagging = text;
else
text = 0;
if ( text )
return;
*in = ':';
}
}
if ( first )
info.title = field;
}
blargg_err_t M3u_Playlist::parse_()
{
info_.title = "";
info_.composer = "";
info_.engineer = "";
info_.ripping = "";
info_.tagging = "";
int const CR = 13;
int const LF = 10;
data.end() [-1] = LF; // terminate input
first_error_ = 0;
bool first_comment = true;
int line = 0;
int count = 0;
char* in = data.begin();
while ( in < data.end() )
{
// find end of line and terminate it
line++;
char* begin = in;
while ( *in != CR && *in != LF )
{
if ( !*in )
return "Not an m3u playlist";
in++;
}
if ( in [0] == CR && in [1] == LF ) // treat CR,LF as a single line
*in++ = 0;
*in++ = 0;
// parse line
if ( *begin == '#' )
{
parse_comment( begin, info_, first_comment );
first_comment = false;
}
else if ( *begin )
{
if ( (int) entries.size() <= count )
RETURN_ERR( entries.resize( count * 2 + 64 ) );
if ( !parse_line( begin, entries [count] ) )
count++;
else if ( !first_error_ )
first_error_ = line;
first_comment = false;
}
}
if ( count <= 0 )
return "Not an m3u playlist";
if ( !(info_.composer [0] | info_.engineer [0] | info_.ripping [0] | info_.tagging [0]) )
info_.title = "";
return entries.resize( count );
}
blargg_err_t M3u_Playlist::parse()
{
blargg_err_t err = parse_();
if ( err )
{
entries.clear();
data.clear();
}
return err;
}
blargg_err_t M3u_Playlist::load( Data_Reader& in )
{
RETURN_ERR( data.resize( in.remain() + 1 ) );
RETURN_ERR( in.read( data.begin(), data.size() - 1 ) );
return parse();
}
blargg_err_t M3u_Playlist::load( const char* path )
{
GME_FILE_READER in;
RETURN_ERR( in.open( path ) );
return load( in );
}
blargg_err_t M3u_Playlist::load( void const* in, long size )
{
RETURN_ERR( data.resize( size + 1 ) );
memcpy( data.begin(), in, size );
return parse();
}

View file

@ -0,0 +1,67 @@
// M3U playlist file parser, with support for subtrack information
// Game_Music_Emu 0.5.2
#ifndef M3U_PLAYLIST_H
#define M3U_PLAYLIST_H
#include "blargg_common.h"
#include "Data_Reader.h"
class M3u_Playlist {
public:
// Load playlist data
blargg_err_t load( const char* path );
blargg_err_t load( Data_Reader& in );
blargg_err_t load( void const* data, long size );
// Line number of first parse error, 0 if no error. Any lines with parse
// errors are ignored.
int first_error() const { return first_error_; }
struct info_t
{
const char* title;
const char* composer;
const char* engineer;
const char* ripping;
const char* tagging;
};
info_t const& info() const { return info_; }
struct entry_t
{
const char* file; // filename without stupid ::TYPE suffix
const char* type; // if filename has ::TYPE suffix, this will be "TYPE". "" if none.
const char* name;
bool decimal_track; // true if track was specified in hex
// integers are -1 if not present
int track; // 1-based
int length; // seconds
int intro;
int loop;
int fade;
int repeat; // count
};
entry_t const& operator [] ( int i ) const { return entries [i]; }
int size() const { return entries.size(); }
void clear();
private:
blargg_vector<entry_t> entries;
blargg_vector<char> data;
int first_error_;
info_t info_;
blargg_err_t parse();
blargg_err_t parse_();
};
inline void M3u_Playlist::clear()
{
first_error_ = 0;
entries.clear();
data.clear();
}
#endif

View file

@ -0,0 +1,232 @@
// Blip_Buffer 0.4.1. http://www.slack.net/~ant/
#include "Multi_Buffer.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf )
{
length_ = 0;
sample_rate_ = 0;
channels_changed_count_ = 1;
}
blargg_err_t Multi_Buffer::set_channel_count( int ) { return 0; }
// Silent_Buffer
Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse
{
// TODO: better to use empty Blip_Buffer so caller never has to check for NULL?
chan.left = 0;
chan.center = 0;
chan.right = 0;
}
// Mono_Buffer
Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 )
{
chan.center = &buf;
chan.left = &buf;
chan.right = &buf;
}
Mono_Buffer::~Mono_Buffer() { }
blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec )
{
RETURN_ERR( buf.set_sample_rate( rate, msec ) );
return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() );
}
// Stereo_Buffer
Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 )
{
chan.center = &bufs [0];
chan.left = &bufs [1];
chan.right = &bufs [2];
}
Stereo_Buffer::~Stereo_Buffer() { }
blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec )
{
for ( int i = 0; i < buf_count; i++ )
RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) );
return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() );
}
void Stereo_Buffer::clock_rate( long rate )
{
for ( int i = 0; i < buf_count; i++ )
bufs [i].clock_rate( rate );
}
void Stereo_Buffer::bass_freq( int bass )
{
for ( unsigned i = 0; i < buf_count; i++ )
bufs [i].bass_freq( bass );
}
void Stereo_Buffer::clear()
{
stereo_added = 0;
was_stereo = false;
for ( int i = 0; i < buf_count; i++ )
bufs [i].clear();
}
void Stereo_Buffer::end_frame( blip_time_t clock_count )
{
stereo_added = 0;
for ( unsigned i = 0; i < buf_count; i++ )
{
stereo_added |= bufs [i].clear_modified() << i;
bufs [i].end_frame( clock_count );
}
}
long Stereo_Buffer::read_samples( blip_sample_t* out, long count )
{
require( !(count & 1) ); // count must be even
count = (unsigned) count / 2;
long avail = bufs [0].samples_avail();
if ( count > avail )
count = avail;
if ( count )
{
int bufs_used = stereo_added | was_stereo;
//dprintf( "%X\n", bufs_used );
if ( bufs_used <= 1 )
{
mix_mono( out, count );
bufs [0].remove_samples( count );
bufs [1].remove_silence( count );
bufs [2].remove_silence( count );
}
else if ( bufs_used & 1 )
{
mix_stereo( out, count );
bufs [0].remove_samples( count );
bufs [1].remove_samples( count );
bufs [2].remove_samples( count );
}
else
{
mix_stereo_no_center( out, count );
bufs [0].remove_silence( count );
bufs [1].remove_samples( count );
bufs [2].remove_samples( count );
}
// to do: this might miss opportunities for optimization
if ( !bufs [0].samples_avail() )
{
was_stereo = stereo_added;
stereo_added = 0;
}
}
return count * 2;
}
void Stereo_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count )
{
blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [1] );
BLIP_READER_BEGIN( left, bufs [1] );
BLIP_READER_BEGIN( right, bufs [2] );
BLIP_READER_BEGIN( center, bufs [0] );
for ( ; count; --count )
{
int c = BLIP_READER_READ( center );
blargg_long l = c + BLIP_READER_READ( left );
blargg_long r = c + BLIP_READER_READ( right );
if ( (BOOST::int16_t) l != l )
l = 0x7FFF - (l >> 24);
BLIP_READER_NEXT( center, bass );
if ( (BOOST::int16_t) r != r )
r = 0x7FFF - (r >> 24);
BLIP_READER_NEXT( left, bass );
BLIP_READER_NEXT( right, bass );
out [0] = l;
out [1] = r;
out += 2;
}
BLIP_READER_END( center, bufs [0] );
BLIP_READER_END( right, bufs [2] );
BLIP_READER_END( left, bufs [1] );
}
void Stereo_Buffer::mix_stereo_no_center( blip_sample_t* out_, blargg_long count )
{
blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [1] );
BLIP_READER_BEGIN( left, bufs [1] );
BLIP_READER_BEGIN( right, bufs [2] );
for ( ; count; --count )
{
blargg_long l = BLIP_READER_READ( left );
if ( (BOOST::int16_t) l != l )
l = 0x7FFF - (l >> 24);
blargg_long r = BLIP_READER_READ( right );
if ( (BOOST::int16_t) r != r )
r = 0x7FFF - (r >> 24);
BLIP_READER_NEXT( left, bass );
BLIP_READER_NEXT( right, bass );
out [0] = l;
out [1] = r;
out += 2;
}
BLIP_READER_END( right, bufs [2] );
BLIP_READER_END( left, bufs [1] );
}
void Stereo_Buffer::mix_mono( blip_sample_t* out_, blargg_long count )
{
blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [0] );
BLIP_READER_BEGIN( center, bufs [0] );
for ( ; count; --count )
{
blargg_long s = BLIP_READER_READ( center );
if ( (BOOST::int16_t) s != s )
s = 0x7FFF - (s >> 24);
BLIP_READER_NEXT( center, bass );
out [0] = s;
out [1] = s;
out += 2;
}
BLIP_READER_END( center, bufs [0] );
}

156
Frameworks/GME/gme/Multi_Buffer.h Executable file
View file

@ -0,0 +1,156 @@
// Multi-channel sound buffer interface, and basic mono and stereo buffers
// Blip_Buffer 0.4.1
#ifndef MULTI_BUFFER_H
#define MULTI_BUFFER_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
// Interface to one or more Blip_Buffers mapped to one or more channels
// consisting of left, center, and right buffers.
class Multi_Buffer {
public:
Multi_Buffer( int samples_per_frame );
virtual ~Multi_Buffer() { }
// Set the number of channels available
virtual blargg_err_t set_channel_count( int );
// Get indexed channel, from 0 to channel count - 1
struct channel_t {
Blip_Buffer* center;
Blip_Buffer* left;
Blip_Buffer* right;
};
enum { type_index_mask = 0xFF };
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
virtual channel_t channel( int index, int type ) = 0;
// See Blip_Buffer.h
virtual blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) = 0;
virtual void clock_rate( long ) = 0;
virtual void bass_freq( int ) = 0;
virtual void clear() = 0;
long sample_rate() const;
// Length of buffer, in milliseconds
int length() const;
// See Blip_Buffer.h
virtual void end_frame( blip_time_t ) = 0;
// Number of samples per output frame (1 = mono, 2 = stereo)
int samples_per_frame() const;
// Count of changes to channel configuration. Incremented whenever
// a change is made to any of the Blip_Buffers for any channel.
unsigned channels_changed_count() { return channels_changed_count_; }
// See Blip_Buffer.h
virtual long read_samples( blip_sample_t*, long ) = 0;
virtual long samples_avail() const = 0;
public:
BLARGG_DISABLE_NOTHROW
protected:
void channels_changed() { channels_changed_count_++; }
private:
// noncopyable
Multi_Buffer( const Multi_Buffer& );
Multi_Buffer& operator = ( const Multi_Buffer& );
unsigned channels_changed_count_;
long sample_rate_;
int length_;
int const samples_per_frame_;
};
// Uses a single buffer and outputs mono samples.
class Mono_Buffer : public Multi_Buffer {
Blip_Buffer buf;
channel_t chan;
public:
// Buffer used for all channels
Blip_Buffer* center() { return &buf; }
public:
Mono_Buffer();
~Mono_Buffer();
blargg_err_t set_sample_rate( long rate, int msec = blip_default_length );
void clock_rate( long rate ) { buf.clock_rate( rate ); }
void bass_freq( int freq ) { buf.bass_freq( freq ); }
void clear() { buf.clear(); }
long samples_avail() const { return buf.samples_avail(); }
long read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); }
channel_t channel( int, int ) { return chan; }
void end_frame( blip_time_t t ) { buf.end_frame( t ); }
};
// Uses three buffers (one for center) and outputs stereo sample pairs.
class Stereo_Buffer : public Multi_Buffer {
public:
// Buffers used for all channels
Blip_Buffer* center() { return &bufs [0]; }
Blip_Buffer* left() { return &bufs [1]; }
Blip_Buffer* right() { return &bufs [2]; }
public:
Stereo_Buffer();
~Stereo_Buffer();
blargg_err_t set_sample_rate( long, int msec = blip_default_length );
void clock_rate( long );
void bass_freq( int );
void clear();
channel_t channel( int, int ) { return chan; }
void end_frame( blip_time_t );
long samples_avail() const { return bufs [0].samples_avail() * 2; }
long read_samples( blip_sample_t*, long );
private:
enum { buf_count = 3 };
Blip_Buffer bufs [buf_count];
channel_t chan;
int stereo_added;
int was_stereo;
void mix_stereo_no_center( blip_sample_t*, blargg_long );
void mix_stereo( blip_sample_t*, blargg_long );
void mix_mono( blip_sample_t*, blargg_long );
};
// Silent_Buffer generates no samples, useful where no sound is wanted
class Silent_Buffer : public Multi_Buffer {
channel_t chan;
public:
Silent_Buffer();
blargg_err_t set_sample_rate( long rate, int msec = blip_default_length )
{
return Multi_Buffer::set_sample_rate( rate, msec );
}
void clock_rate( long ) { }
void bass_freq( int ) { }
void clear() { }
channel_t channel( int, int ) { return chan; }
void end_frame( blip_time_t ) { }
long samples_avail() const { return 0; }
long read_samples( blip_sample_t*, long ) { return 0; }
};
inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec )
{
sample_rate_ = rate;
length_ = msec;
return 0;
}
inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; }
inline long Multi_Buffer::sample_rate() const { return sample_rate_; }
inline int Multi_Buffer::length() const { return length_; }
#endif

410
Frameworks/GME/gme/Music_Emu.cpp Executable file
View file

@ -0,0 +1,410 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Music_Emu.h"
#include "Multi_Buffer.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const stereo = 2; // number of channels for stereo
int const silence_max = 6; // seconds
int const silence_threshold = 0x10;
long const fade_block_size = 512;
int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift)
Music_Emu::equalizer_t const Music_Emu::tv_eq = { -8.0, 180 };
void Music_Emu::clear_track_vars()
{
current_track_ = -1;
out_time = 0;
emu_time = 0;
emu_track_ended_ = true;
track_ended_ = true;
fade_start = LONG_MAX / 2 + 1;
fade_step = 1;
silence_time = 0;
silence_count = 0;
buf_remain = 0;
warning(); // clear warning
}
void Music_Emu::unload()
{
voice_count_ = 0;
clear_track_vars();
Gme_File::unload();
}
Music_Emu::Music_Emu()
{
effects_buffer = 0;
sample_rate_ = 0;
mute_mask_ = 0;
tempo_ = 1.0;
gain_ = 1.0;
// defaults
max_initial_silence = 2;
silence_lookahead = 3;
ignore_silence_ = false;
equalizer_.treble = -1.0;
equalizer_.bass = 60;
static const char* const names [] = {
"Voice 1", "Voice 2", "Voice 3", "Voice 4",
"Voice 5", "Voice 6", "Voice 7", "Voice 8"
};
set_voice_names( names );
Music_Emu::unload(); // non-virtual
}
Music_Emu::~Music_Emu() { delete effects_buffer; }
blargg_err_t Music_Emu::set_sample_rate( long rate )
{
require( !sample_rate() ); // sample rate can't be changed once set
RETURN_ERR( set_sample_rate_( rate ) );
RETURN_ERR( buf.resize( buf_size ) );
sample_rate_ = rate;
return 0;
}
void Music_Emu::pre_load()
{
require( sample_rate() ); // set_sample_rate() must be called before loading a file
Gme_File::pre_load();
}
void Music_Emu::set_equalizer( equalizer_t const& eq )
{
equalizer_ = eq;
set_equalizer_( eq );
}
void Music_Emu::mute_voice( int index, bool mute )
{
require( (unsigned) index < (unsigned) voice_count() );
int bit = 1 << index;
int mask = mute_mask_ | bit;
if ( !mute )
mask ^= bit;
mute_voices( mask );
}
void Music_Emu::mute_voices( int mask )
{
require( sample_rate() ); // sample rate must be set first
mute_mask_ = mask;
mute_voices_( mask );
}
void Music_Emu::set_tempo( double t )
{
require( sample_rate() ); // sample rate must be set first
double const min = 0.02;
double const max = 4.00;
if ( t < min ) t = min;
if ( t > max ) t = max;
tempo_ = t;
set_tempo_( t );
}
void Music_Emu::post_load_()
{
set_tempo( tempo_ );
remute_voices();
}
blargg_err_t Music_Emu::start_track( int track )
{
clear_track_vars();
int remapped = track;
RETURN_ERR( remap_track_( &remapped ) );
current_track_ = track;
RETURN_ERR( start_track_( remapped ) );
emu_track_ended_ = false;
track_ended_ = false;
if ( !ignore_silence_ )
{
// play until non-silence or end of track
for ( long end = max_initial_silence * stereo * sample_rate(); emu_time < end; )
{
fill_buf();
if ( buf_remain | (int) emu_track_ended_ )
break;
}
emu_time = buf_remain;
out_time = 0;
silence_time = 0;
silence_count = 0;
}
return track_ended() ? warning() : 0;
}
void Music_Emu::end_track_if_error( blargg_err_t err )
{
if ( err )
{
emu_track_ended_ = true;
set_warning( err );
}
}
// Tell/Seek
blargg_long Music_Emu::msec_to_samples( blargg_long msec ) const
{
blargg_long sec = msec / 1000;
msec -= sec * 1000;
return (sec * sample_rate() + msec * sample_rate() / 1000) * stereo;
}
long Music_Emu::tell() const
{
blargg_long rate = sample_rate() * stereo;
blargg_long sec = out_time / rate;
return sec * 1000 + (out_time - sec * rate) * 1000 / rate;
}
blargg_err_t Music_Emu::seek( long msec )
{
blargg_long time = msec_to_samples( msec );
if ( time < out_time )
RETURN_ERR( start_track( current_track_ ) );
return skip( time - out_time );
}
blargg_err_t Music_Emu::skip( long count )
{
require( current_track() >= 0 ); // start_track() must have been called already
out_time += count;
// remove from silence and buf first
{
long n = min( count, silence_count );
silence_count -= n;
count -= n;
n = min( count, buf_remain );
buf_remain -= n;
count -= n;
}
if ( count && !emu_track_ended_ )
{
emu_time += count;
end_track_if_error( skip_( count ) );
}
if ( !(silence_count | buf_remain) ) // caught up to emulator, so update track ended
track_ended_ |= emu_track_ended_;
return 0;
}
blargg_err_t Music_Emu::skip_( long count )
{
// for long skip, mute sound
const long threshold = 30000;
if ( count > threshold )
{
int saved_mute = mute_mask_;
mute_voices( ~0 );
while ( count > threshold / 2 && !emu_track_ended_ )
{
RETURN_ERR( play_( buf_size, buf.begin() ) );
count -= buf_size;
}
mute_voices( saved_mute );
}
while ( count && !emu_track_ended_ )
{
long n = buf_size;
if ( n > count )
n = count;
count -= n;
RETURN_ERR( play_( n, buf.begin() ) );
}
return 0;
}
// Fading
void Music_Emu::set_fade( long start_msec, long length_msec )
{
fade_step = sample_rate() * length_msec / (fade_block_size * fade_shift * 1000 / stereo);
fade_start = msec_to_samples( start_msec );
}
// unit / pow( 2.0, (double) x / step )
static int int_log( blargg_long x, int step, int unit )
{
int shift = x / step;
int fraction = (x - shift * step) * unit / step;
return ((unit - fraction) + (fraction >> 1)) >> shift;
}
void Music_Emu::handle_fade( long out_count, sample_t* out )
{
for ( int i = 0; i < out_count; i += fade_block_size )
{
int const shift = 14;
int const unit = 1 << shift;
int gain = int_log( (out_time + i - fade_start) / fade_block_size,
fade_step, unit );
if ( gain < (unit >> fade_shift) )
track_ended_ = emu_track_ended_ = true;
sample_t* io = &out [i];
for ( int count = min( fade_block_size, out_count - i ); count; --count )
{
*io = sample_t ((*io * gain) >> shift);
++io;
}
}
}
// Silence detection
void Music_Emu::emu_play( long count, sample_t* out )
{
check( current_track_ >= 0 );
emu_time += count;
if ( current_track_ >= 0 && !emu_track_ended_ )
end_track_if_error( play_( count, out ) );
else
memset( out, 0, count * sizeof *out );
}
// number of consecutive silent samples at end
static long count_silence( Music_Emu::sample_t* begin, long size )
{
Music_Emu::sample_t first = *begin;
*begin = silence_threshold; // sentinel
Music_Emu::sample_t* p = begin + size;
while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { }
*begin = first;
return size - (p - begin);
}
// fill internal buffer and check it for silence
void Music_Emu::fill_buf()
{
assert( !buf_remain );
if ( !emu_track_ended_ )
{
emu_play( buf_size, buf.begin() );
long silence = count_silence( buf.begin(), buf_size );
if ( silence < buf_size )
{
silence_time = emu_time - silence;
buf_remain = buf_size;
return;
}
}
silence_count += buf_size;
}
blargg_err_t Music_Emu::play( long out_count, sample_t* out )
{
if ( track_ended_ )
{
memset( out, 0, out_count * sizeof *out );
}
else
{
require( current_track() >= 0 );
require( out_count % stereo == 0 );
assert( emu_time >= out_time );
// prints nifty graph of how far ahead we are when searching for silence
//dprintf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" );
long pos = 0;
if ( silence_count )
{
// during a run of silence, run emulator at >=2x speed so it gets ahead
long ahead_time = silence_lookahead * (out_time + out_count - silence_time) + silence_time;
while ( emu_time < ahead_time && !(buf_remain | emu_track_ended_) )
fill_buf();
// fill with silence
pos = min( silence_count, out_count );
memset( out, 0, pos * sizeof *out );
silence_count -= pos;
if ( emu_time - silence_time > silence_max * stereo * sample_rate() )
{
track_ended_ = emu_track_ended_ = true;
silence_count = 0;
buf_remain = 0;
}
}
if ( buf_remain )
{
// empty silence buf
long n = min( buf_remain, out_count - pos );
memcpy( &out [pos], buf.begin() + (buf_size - buf_remain), n * sizeof *out );
buf_remain -= n;
pos += n;
}
// generate remaining samples normally
long remain = out_count - pos;
if ( remain )
{
emu_play( remain, out + pos );
track_ended_ |= emu_track_ended_;
if ( !ignore_silence_ || out_time > fade_start )
{
// check end for a new run of silence
long silence = count_silence( out + pos, remain );
if ( silence < remain )
silence_time = emu_time - silence;
if ( emu_time - silence_time >= buf_size )
fill_buf(); // cause silence detection on next play()
}
}
if ( out_time > fade_start )
handle_fade( out_count, out );
}
out_time += out_count;
return 0;
}
// Gme_Info_
blargg_err_t Gme_Info_::set_sample_rate_( long ) { return 0; }
void Gme_Info_::pre_load() { Gme_File::pre_load(); } // skip Music_Emu
void Gme_Info_::post_load_() { Gme_File::post_load_(); } // skip Music_Emu
void Gme_Info_::set_equalizer_( equalizer_t const& ){ check( false ); }
void Gme_Info_::mute_voices_( int ) { check( false ); }
void Gme_Info_::set_tempo_( double ) { }
blargg_err_t Gme_Info_::start_track_( int ) { return "Use full emulator for playback"; }
blargg_err_t Gme_Info_::play_( long, sample_t* ) { return "Use full emulator for playback"; }

211
Frameworks/GME/gme/Music_Emu.h Executable file
View file

@ -0,0 +1,211 @@
// Common interface to game music file emulators
// Game_Music_Emu 0.5.2
#ifndef MUSIC_EMU_H
#define MUSIC_EMU_H
#include "Gme_File.h"
class Multi_Buffer;
struct Music_Emu : public Gme_File {
public:
// Basic functionality (see Gme_File.h for file loading/track info functions)
// Set output sample rate. Must be called only once before loading file.
blargg_err_t set_sample_rate( long sample_rate );
// Start a track, where 0 is the first track. Also clears warning string.
blargg_err_t start_track( int );
// Generate 'count' samples info 'buf'. Output is in stereo. Any emulation
// errors set warning string, and major errors also end track.
typedef short sample_t;
blargg_err_t play( long count, sample_t* buf );
// Informational
// Sample rate sound is generated at
long sample_rate() const;
// Index of current track or -1 if one hasn't been started
int current_track() const;
// Number of voices used by currently loaded file
int voice_count() const;
// Names of voices
const char** voice_names() const;
// Track status/control
// Number of milliseconds (1000 msec = 1 second) played since beginning of track
long tell() const;
// Seek to new time in track. Seeking backwards or far forward can take a while.
blargg_err_t seek( long msec );
// Skip n samples
blargg_err_t skip( long n );
// True if a track has reached its end
bool track_ended() const;
// Set start time and length of track fade out. Once fade ends track_ended() returns
// true. Fade time can be changed while track is playing.
void set_fade( long start_msec, long length_msec = 8000 );
// Disable automatic end-of-track detection and skipping of silence at beginning
void ignore_silence( bool disable = true );
// Info for current track
Gme_File::track_info;
blargg_err_t track_info( track_info_t* out ) const;
// Sound customization
// Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed.
// Track length as returned by track_info() assumes a tempo of 1.0.
void set_tempo( double );
// Mute/unmute voice i, where voice 0 is first voice
void mute_voice( int index, bool mute = true );
// Set muting state of all voices at once using a bit mask, where -1 mutes them all,
// 0 unmutes them all, 0x01 mutes just the first voice, etc.
void mute_voices( int mask );
// Change overall output amplitude, where 1.0 results in minimal clamping.
// Must be called before set_sample_rate().
void set_gain( double );
// Request use of custom multichannel buffer. Only supported by "classic" emulators;
// on others this has no effect. Should be called only once *before* set_sample_rate().
virtual void set_buffer( Multi_Buffer* ) { }
// Sound equalization (treble/bass)
// Frequency equalizer parameters (see gme.txt)
// See gme.h for definition of struct gme_equalizer_t.
typedef gme_equalizer_t equalizer_t;
// Current frequency equalizater parameters
equalizer_t const& equalizer() const;
// Set frequency equalizer parameters
void set_equalizer( equalizer_t const& );
// Equalizer settings for TV speaker
static equalizer_t const tv_eq;
public:
Music_Emu();
~Music_Emu();
protected:
void set_max_initial_silence( int n ) { max_initial_silence = n; }
void set_silence_lookahead( int n ) { silence_lookahead = n; }
void set_voice_count( int n ) { voice_count_ = n; }
void set_voice_names( const char* const* names );
void set_track_ended() { emu_track_ended_ = true; }
double gain() const { return gain_; }
double tempo() const { return tempo_; }
void remute_voices();
virtual blargg_err_t set_sample_rate_( long sample_rate ) = 0;
virtual void set_equalizer_( equalizer_t const& ) { };
virtual void mute_voices_( int mask ) = 0;
virtual void set_tempo_( double ) = 0;
virtual blargg_err_t start_track_( int ) = 0; // tempo is set before this
virtual blargg_err_t play_( long count, sample_t* out ) = 0;
virtual blargg_err_t skip_( long count );
protected:
virtual void unload();
virtual void pre_load();
virtual void post_load_();
private:
// general
equalizer_t equalizer_;
int max_initial_silence;
const char** voice_names_;
int voice_count_;
int mute_mask_;
double tempo_;
double gain_;
long sample_rate_;
blargg_long msec_to_samples( blargg_long msec ) const;
// track-specific
int current_track_;
blargg_long out_time; // number of samples played since start of track
blargg_long emu_time; // number of samples emulator has generated since start of track
bool emu_track_ended_; // emulator has reached end of track
volatile bool track_ended_;
void clear_track_vars();
void end_track_if_error( blargg_err_t );
// fading
blargg_long fade_start;
int fade_step;
void handle_fade( long count, sample_t* out );
// silence detection
int silence_lookahead; // speed to run emulator when looking ahead for silence
bool ignore_silence_;
long silence_time; // number of samples where most recent silence began
long silence_count; // number of samples of silence to play before using buf
long buf_remain; // number of samples left in silence buffer
enum { buf_size = 2048 };
blargg_vector<sample_t> buf;
void fill_buf();
void emu_play( long count, sample_t* out );
Multi_Buffer* effects_buffer;
friend Music_Emu* gme_new_emu( gme_type_t, long );
friend void gme_set_stereo_depth( Music_Emu*, double );
};
// base class for info-only derivations
struct Gme_Info_ : Music_Emu
{
virtual blargg_err_t set_sample_rate_( long sample_rate );
virtual void set_equalizer_( equalizer_t const& );
virtual void mute_voices_( int mask );
virtual void set_tempo_( double );
virtual blargg_err_t start_track_( int );
virtual blargg_err_t play_( long count, sample_t* out );
virtual void pre_load();
virtual void post_load_();
};
inline blargg_err_t Music_Emu::track_info( track_info_t* out ) const
{
return track_info( out, current_track_ );
}
inline long Music_Emu::sample_rate() const { return sample_rate_; }
inline const char** Music_Emu::voice_names() const { return voice_names_; }
inline int Music_Emu::voice_count() const { return voice_count_; }
inline int Music_Emu::current_track() const { return current_track_; }
inline bool Music_Emu::track_ended() const { return track_ended_; }
inline const Music_Emu::equalizer_t& Music_Emu::equalizer() const { return equalizer_; }
inline void Music_Emu::set_tempo_( double t ) { tempo_ = t; }
inline void Music_Emu::remute_voices() { mute_voices( mute_mask_ ); }
inline void Music_Emu::ignore_silence( bool b ) { ignore_silence_ = b; }
inline blargg_err_t Music_Emu::start_track_( int ) { return 0; }
inline void Music_Emu::set_voice_names( const char* const* names )
{
// Intentional removal of const, so users don't have to remember obscure const in middle
voice_names_ = (const char**) names;
}
inline void Music_Emu::mute_voices_( int ) { }
inline void Music_Emu::set_gain( double g )
{
assert( !sample_rate() ); // you must set gain before setting sample rate
gain_ = g;
}
#endif

391
Frameworks/GME/gme/Nes_Apu.cpp Executable file
View file

@ -0,0 +1,391 @@
// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
#include "Nes_Apu.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const amp_range = 15;
Nes_Apu::Nes_Apu() :
square1( &square_synth ),
square2( &square_synth )
{
tempo_ = 1.0;
dmc.apu = this;
dmc.prg_reader = NULL;
irq_notifier_ = NULL;
oscs [0] = &square1;
oscs [1] = &square2;
oscs [2] = &triangle;
oscs [3] = &noise;
oscs [4] = &dmc;
output( NULL );
volume( 1.0 );
reset( false );
}
void Nes_Apu::treble_eq( const blip_eq_t& eq )
{
square_synth.treble_eq( eq );
triangle.synth.treble_eq( eq );
noise.synth.treble_eq( eq );
dmc.synth.treble_eq( eq );
}
void Nes_Apu::enable_nonlinear( double v )
{
dmc.nonlinear = true;
square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v );
const double tnd = 0.48 / 202 * nonlinear_tnd_gain();
triangle.synth.volume( 3.0 * tnd );
noise.synth.volume( 2.0 * tnd );
dmc.synth.volume( tnd );
square1 .last_amp = 0;
square2 .last_amp = 0;
triangle.last_amp = 0;
noise .last_amp = 0;
dmc .last_amp = 0;
}
void Nes_Apu::volume( double v )
{
dmc.nonlinear = false;
square_synth.volume( 0.1128 / amp_range * v );
triangle.synth.volume( 0.12765 / amp_range * v );
noise.synth.volume( 0.0741 / amp_range * v );
dmc.synth.volume( 0.42545 / 127 * v );
}
void Nes_Apu::output( Blip_Buffer* buffer )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, buffer );
}
void Nes_Apu::set_tempo( double t )
{
tempo_ = t;
frame_period = (dmc.pal_mode ? 8314 : 7458);
if ( t != 1.0 )
frame_period = (int) (frame_period / t) & ~1; // must be even
}
void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac )
{
dmc.pal_mode = pal_mode;
set_tempo( tempo_ );
square1.reset();
square2.reset();
triangle.reset();
noise.reset();
dmc.reset();
last_time = 0;
last_dmc_time = 0;
osc_enables = 0;
irq_flag = false;
earliest_irq_ = no_irq;
frame_delay = 1;
write_register( 0, 0x4017, 0x00 );
write_register( 0, 0x4015, 0x00 );
for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ )
write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 );
dmc.dac = initial_dmc_dac;
if ( !dmc.nonlinear )
triangle.last_amp = 15;
if ( !dmc.nonlinear ) // TODO: remove?
dmc.last_amp = initial_dmc_dac; // prevent output transition
}
void Nes_Apu::irq_changed()
{
nes_time_t new_irq = dmc.next_irq;
if ( dmc.irq_flag | irq_flag ) {
new_irq = 0;
}
else if ( new_irq > next_irq ) {
new_irq = next_irq;
}
if ( new_irq != earliest_irq_ ) {
earliest_irq_ = new_irq;
if ( irq_notifier_ )
irq_notifier_( irq_data );
}
}
// frames
void Nes_Apu::run_until( nes_time_t end_time )
{
require( end_time >= last_dmc_time );
if ( end_time > next_dmc_read_time() )
{
nes_time_t start = last_dmc_time;
last_dmc_time = end_time;
dmc.run( start, end_time );
}
}
void Nes_Apu::run_until_( nes_time_t end_time )
{
require( end_time >= last_time );
if ( end_time == last_time )
return;
if ( last_dmc_time < end_time )
{
nes_time_t start = last_dmc_time;
last_dmc_time = end_time;
dmc.run( start, end_time );
}
while ( true )
{
// earlier of next frame time or end time
nes_time_t time = last_time + frame_delay;
if ( time > end_time )
time = end_time;
frame_delay -= time - last_time;
// run oscs to present
square1.run( last_time, time );
square2.run( last_time, time );
triangle.run( last_time, time );
noise.run( last_time, time );
last_time = time;
if ( time == end_time )
break; // no more frames to run
// take frame-specific actions
frame_delay = frame_period;
switch ( frame++ )
{
case 0:
if ( !(frame_mode & 0xC0) ) {
next_irq = time + frame_period * 4 + 2;
irq_flag = true;
}
// fall through
case 2:
// clock length and sweep on frames 0 and 2
square1.clock_length( 0x20 );
square2.clock_length( 0x20 );
noise.clock_length( 0x20 );
triangle.clock_length( 0x80 ); // different bit for halt flag on triangle
square1.clock_sweep( -1 );
square2.clock_sweep( 0 );
// frame 2 is slightly shorter in mode 1
if ( dmc.pal_mode && frame == 3 )
frame_delay -= 2;
break;
case 1:
// frame 1 is slightly shorter in mode 0
if ( !dmc.pal_mode )
frame_delay -= 2;
break;
case 3:
frame = 0;
// frame 3 is almost twice as long in mode 1
if ( frame_mode & 0x80 )
frame_delay += frame_period - (dmc.pal_mode ? 2 : 6);
break;
}
// clock envelopes and linear counter every frame
triangle.clock_linear_counter();
square1.clock_envelope();
square2.clock_envelope();
noise.clock_envelope();
}
}
template<class T>
inline void zero_apu_osc( T* osc, nes_time_t time )
{
Blip_Buffer* output = osc->output;
int last_amp = osc->last_amp;
osc->last_amp = 0;
if ( output && last_amp )
osc->synth.offset( time, -last_amp, output );
}
void Nes_Apu::end_frame( nes_time_t end_time )
{
if ( end_time > last_time )
run_until_( end_time );
if ( dmc.nonlinear )
{
zero_apu_osc( &square1, last_time );
zero_apu_osc( &square2, last_time );
zero_apu_osc( &triangle, last_time );
zero_apu_osc( &noise, last_time );
zero_apu_osc( &dmc, last_time );
}
// make times relative to new frame
last_time -= end_time;
require( last_time >= 0 );
last_dmc_time -= end_time;
require( last_dmc_time >= 0 );
if ( next_irq != no_irq ) {
next_irq -= end_time;
check( next_irq >= 0 );
}
if ( dmc.next_irq != no_irq ) {
dmc.next_irq -= end_time;
check( dmc.next_irq >= 0 );
}
if ( earliest_irq_ != no_irq ) {
earliest_irq_ -= end_time;
if ( earliest_irq_ < 0 )
earliest_irq_ = 0;
}
}
// registers
static const unsigned char length_table [0x20] = {
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
};
void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data )
{
require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx)
require( (unsigned) data <= 0xFF );
// Ignore addresses outside range
if ( unsigned (addr - start_addr) > end_addr - start_addr )
return;
run_until_( time );
if ( addr < 0x4014 )
{
// Write to channel
int osc_index = (addr - start_addr) >> 2;
Nes_Osc* osc = oscs [osc_index];
int reg = addr & 3;
osc->regs [reg] = data;
osc->reg_written [reg] = true;
if ( osc_index == 4 )
{
// handle DMC specially
dmc.write_register( reg, data );
}
else if ( reg == 3 )
{
// load length counter
if ( (osc_enables >> osc_index) & 1 )
osc->length_counter = length_table [(data >> 3) & 0x1F];
// reset square phase
if ( osc_index < 2 )
((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1;
}
}
else if ( addr == 0x4015 )
{
// Channel enables
for ( int i = osc_count; i--; )
if ( !((data >> i) & 1) )
oscs [i]->length_counter = 0;
bool recalc_irq = dmc.irq_flag;
dmc.irq_flag = false;
int old_enables = osc_enables;
osc_enables = data;
if ( !(data & 0x10) ) {
dmc.next_irq = no_irq;
recalc_irq = true;
}
else if ( !(old_enables & 0x10) ) {
dmc.start(); // dmc just enabled
}
if ( recalc_irq )
irq_changed();
}
else if ( addr == 0x4017 )
{
// Frame mode
frame_mode = data;
bool irq_enabled = !(data & 0x40);
irq_flag &= irq_enabled;
next_irq = no_irq;
// mode 1
frame_delay = (frame_delay & 1);
frame = 0;
if ( !(data & 0x80) )
{
// mode 0
frame = 1;
frame_delay += frame_period;
if ( irq_enabled )
next_irq = time + frame_delay + frame_period * 3 + 1;
}
irq_changed();
}
}
int Nes_Apu::read_status( nes_time_t time )
{
run_until_( time - 1 );
int result = (dmc.irq_flag << 7) | (irq_flag << 6);
for ( int i = 0; i < osc_count; i++ )
if ( oscs [i]->length_counter )
result |= 1 << i;
run_until_( time );
if ( irq_flag )
{
result |= 0x40;
irq_flag = false;
irq_changed();
}
//dprintf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result );
return result;
}

179
Frameworks/GME/gme/Nes_Apu.h Executable file
View file

@ -0,0 +1,179 @@
// NES 2A03 APU sound chip emulator
// Nes_Snd_Emu 0.1.8
#ifndef NES_APU_H
#define NES_APU_H
#include "blargg_common.h"
typedef blargg_long nes_time_t; // CPU clock cycle count
typedef unsigned nes_addr_t; // 16-bit memory address
#include "Nes_Oscs.h"
struct apu_state_t;
class Nes_Buffer;
class Nes_Apu {
public:
// Set buffer to generate all sound into, or disable sound if NULL
void output( Blip_Buffer* );
// Set memory reader callback used by DMC oscillator to fetch samples.
// When callback is invoked, 'user_data' is passed unchanged as the
// first parameter.
void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), void* user_data = NULL );
// All time values are the number of CPU clock cycles relative to the
// beginning of the current time frame. Before resetting the CPU clock
// count, call end_frame( last_cpu_time ).
// Write to register (0x4000-0x4017, except 0x4014 and 0x4016)
enum { start_addr = 0x4000 };
enum { end_addr = 0x4017 };
void write_register( nes_time_t, nes_addr_t, int data );
// Read from status register at 0x4015
enum { status_addr = 0x4015 };
int read_status( nes_time_t );
// Run all oscillators up to specified time, end current time frame, then
// start a new time frame at time 0. Time frames have no effect on emulation
// and each can be whatever length is convenient.
void end_frame( nes_time_t );
// Additional optional features (can be ignored without any problem)
// Reset internal frame counter, registers, and all oscillators.
// Use PAL timing if pal_timing is true, otherwise use NTSC timing.
// Set the DMC oscillator's initial DAC value to initial_dmc_dac without
// any audible click.
void reset( bool pal_mode = false, int initial_dmc_dac = 0 );
// Adjust frame period
void set_tempo( double );
// Save/load exact emulation state
void save_state( apu_state_t* out ) const;
void load_state( apu_state_t const& );
// Set overall volume (default is 1.0)
void volume( double );
// Set treble equalization (see notes.txt)
void treble_eq( const blip_eq_t& );
// Set sound output of specific oscillator to buffer. If buffer is NULL,
// the specified oscillator is muted and emulation accuracy is reduced.
// The oscillators are indexed as follows: 0) Square 1, 1) Square 2,
// 2) Triangle, 3) Noise, 4) DMC.
enum { osc_count = 5 };
void osc_output( int index, Blip_Buffer* buffer );
// Set IRQ time callback that is invoked when the time of earliest IRQ
// may have changed, or NULL to disable. When callback is invoked,
// 'user_data' is passed unchanged as the first parameter.
void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL );
// Get time that APU-generated IRQ will occur if no further register reads
// or writes occur. If IRQ is already pending, returns irq_waiting. If no
// IRQ will occur, returns no_irq.
enum { no_irq = LONG_MAX / 2 + 1 };
enum { irq_waiting = 0 };
nes_time_t earliest_irq( nes_time_t ) const;
// Count number of DMC reads that would occur if 'run_until( t )' were executed.
// If last_read is not NULL, set *last_read to the earliest time that
// 'count_dmc_reads( time )' would result in the same result.
int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const;
// Time when next DMC memory read will occur
nes_time_t next_dmc_read_time() const;
// Run DMC until specified time, so that any DMC memory reads can be
// accounted for (i.e. inserting CPU wait states).
void run_until( nes_time_t );
public:
Nes_Apu();
BLARGG_DISABLE_NOTHROW
private:
friend class Nes_Nonlinearizer;
void enable_nonlinear( double volume );
static double nonlinear_tnd_gain() { return 0.75; }
private:
friend struct Nes_Dmc;
// noncopyable
Nes_Apu( const Nes_Apu& );
Nes_Apu& operator = ( const Nes_Apu& );
Nes_Osc* oscs [osc_count];
Nes_Square square1;
Nes_Square square2;
Nes_Noise noise;
Nes_Triangle triangle;
Nes_Dmc dmc;
double tempo_;
nes_time_t last_time; // has been run until this time in current frame
nes_time_t last_dmc_time;
nes_time_t earliest_irq_;
nes_time_t next_irq;
int frame_period;
int frame_delay; // cycles until frame counter runs next
int frame; // current frame (0-3)
int osc_enables;
int frame_mode;
bool irq_flag;
void (*irq_notifier_)( void* user_data );
void* irq_data;
Nes_Square::Synth square_synth; // shared by squares
void irq_changed();
void state_restored();
void run_until_( nes_time_t );
// TODO: remove
friend class Nes_Core;
};
inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf )
{
assert( (unsigned) osc < osc_count );
oscs [osc]->output = buf;
}
inline nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const
{
return earliest_irq_;
}
inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data )
{
dmc.prg_reader_data = user_data;
dmc.prg_reader = func;
}
inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data )
{
irq_notifier_ = func;
irq_data = user_data;
}
inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const
{
return dmc.count_reads( time, last_read );
}
inline nes_time_t Nes_Dmc::next_read_time() const
{
if ( length_counter == 0 )
return Nes_Apu::no_irq; // not reading
return apu->last_dmc_time + delay + long (bits_remain - 1) * period;
}
inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); }
#endif

1084
Frameworks/GME/gme/Nes_Cpu.cpp Executable file

File diff suppressed because it is too large Load diff

114
Frameworks/GME/gme/Nes_Cpu.h Executable file
View file

@ -0,0 +1,114 @@
// NES 6502 CPU emulator
// Game_Music_Emu 0.5.2
#ifndef NES_CPU_H
#define NES_CPU_H
#include "blargg_common.h"
typedef blargg_long nes_time_t; // clock cycle count
typedef unsigned nes_addr_t; // 16-bit address
enum { future_nes_time = LONG_MAX / 2 + 1 };
class Nes_Cpu {
public:
typedef BOOST::uint8_t uint8_t;
// Clear registers, map low memory and its three mirrors to address 0,
// and mirror unmapped_page in remaining memory
void reset( void const* unmapped_page = 0 );
// Map code memory (memory accessed via the program counter). Start and size
// must be multiple of page_size. If mirror is true, repeats code page
// throughout address range.
enum { page_size = 0x800 };
void map_code( nes_addr_t start, unsigned size, void const* code, bool mirror = false );
// Access emulated memory as CPU does
uint8_t const* get_code( nes_addr_t );
// 2KB of RAM at address 0
uint8_t low_mem [0x800];
// NES 6502 registers. Not kept updated during a call to run().
struct registers_t {
BOOST::uint16_t pc;
BOOST::uint8_t a;
BOOST::uint8_t x;
BOOST::uint8_t y;
BOOST::uint8_t status;
BOOST::uint8_t sp;
};
registers_t r;
// Set end_time and run CPU from current time. Returns true if execution
// stopped due to encountering bad_opcode.
bool run( nes_time_t end_time );
// Time of beginning of next instruction to be executed
nes_time_t time() const { return state->time + state->base; }
void set_time( nes_time_t t ) { state->time = t - state->base; }
void adjust_time( int delta ) { state->time += delta; }
nes_time_t irq_time() const { return irq_time_; }
void set_irq_time( nes_time_t );
nes_time_t end_time() const { return end_time_; }
void set_end_time( nes_time_t );
// Number of undefined instructions encountered and skipped
void clear_error_count() { error_count_ = 0; }
unsigned long error_count() const { return error_count_; }
// CPU invokes bad opcode handler if it encounters this
enum { bad_opcode = 0xF2 };
public:
Nes_Cpu() { state = &state_; }
enum { page_bits = 11 };
enum { page_count = 0x10000 >> page_bits };
enum { irq_inhibit = 0x04 };
private:
struct state_t {
uint8_t const* code_map [page_count + 1];
nes_time_t base;
int time;
};
state_t* state; // points to state_ or a local copy within run()
state_t state_;
nes_time_t irq_time_;
nes_time_t end_time_;
unsigned long error_count_;
void set_code_page( int, void const* );
inline int update_end_time( nes_time_t end, nes_time_t irq );
};
inline BOOST::uint8_t const* Nes_Cpu::get_code( nes_addr_t addr )
{
return state->code_map [addr >> page_bits] + addr
#if !BLARGG_NONPORTABLE
% (unsigned) page_size
#endif
;
}
inline int Nes_Cpu::update_end_time( nes_time_t t, nes_time_t irq )
{
if ( irq < t && !(r.status & irq_inhibit) ) t = irq;
int delta = state->base - t;
state->base = t;
return delta;
}
inline void Nes_Cpu::set_irq_time( nes_time_t t )
{
state->time += update_end_time( end_time_, (irq_time_ = t) );
}
inline void Nes_Cpu::set_end_time( nes_time_t t )
{
state->time += update_end_time( (end_time_ = t), irq_time_ );
}
#endif

View file

@ -0,0 +1,121 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Nes_Fme7_Apu.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
void Nes_Fme7_Apu::reset()
{
last_time = 0;
for ( int i = 0; i < osc_count; i++ )
oscs [i].last_amp = 0;
fme7_apu_state_t* state = this;
memset( state, 0, sizeof *state );
}
unsigned char const Nes_Fme7_Apu::amp_table [16] =
{
#define ENTRY( n ) (unsigned char) (n * amp_range + 0.5)
ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156),
ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624),
ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498),
ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000)
#undef ENTRY
};
void Nes_Fme7_Apu::run_until( blip_time_t end_time )
{
require( end_time >= last_time );
for ( int index = 0; index < osc_count; index++ )
{
int mode = regs [7] >> index;
int vol_mode = regs [010 + index];
int volume = amp_table [vol_mode & 0x0F];
Blip_Buffer* const osc_output = oscs [index].output;
if ( !osc_output )
continue;
osc_output->set_modified();
// check for unsupported mode
#ifndef NDEBUG
if ( (mode & 011) <= 001 && vol_mode & 0x1F )
dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n",
mode, vol_mode & 0x1F );
#endif
if ( (mode & 001) | (vol_mode & 0x10) )
volume = 0; // noise and envelope aren't supported
// period
int const period_factor = 16;
unsigned period = (regs [index * 2 + 1] & 0x0F) * 0x100 * period_factor +
regs [index * 2] * period_factor;
if ( period < 50 ) // around 22 kHz
{
volume = 0;
if ( !period ) // on my AY-3-8910A, period doesn't have extra one added
period = period_factor;
}
// current amplitude
int amp = volume;
if ( !phases [index] )
amp = 0;
{
int delta = amp - oscs [index].last_amp;
if ( delta )
{
oscs [index].last_amp = amp;
synth.offset( last_time, delta, osc_output );
}
}
blip_time_t time = last_time + delays [index];
if ( time < end_time )
{
int delta = amp * 2 - volume;
if ( volume )
{
do
{
delta = -delta;
synth.offset_inline( time, delta, osc_output );
time += period;
}
while ( time < end_time );
oscs [index].last_amp = (delta + volume) >> 1;
phases [index] = (delta > 0);
}
else
{
// maintain phase when silent
int count = (end_time - time + period - 1) / period;
phases [index] ^= count & 1;
time += (blargg_long) count * period;
}
}
delays [index] = time - end_time;
}
last_time = end_time;
}

131
Frameworks/GME/gme/Nes_Fme7_Apu.h Executable file
View file

@ -0,0 +1,131 @@
// Sunsoft FME-7 sound emulator
// Game_Music_Emu 0.5.2
#ifndef NES_FME7_APU_H
#define NES_FME7_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
struct fme7_apu_state_t
{
enum { reg_count = 14 };
BOOST::uint8_t regs [reg_count];
BOOST::uint8_t phases [3]; // 0 or 1
BOOST::uint8_t latch;
BOOST::uint16_t delays [3]; // a, b, c
};
class Nes_Fme7_Apu : private fme7_apu_state_t {
public:
// See Nes_Apu.h for reference
void reset();
void volume( double );
void treble_eq( blip_eq_t const& );
void output( Blip_Buffer* );
enum { osc_count = 3 };
void osc_output( int index, Blip_Buffer* );
void end_frame( blip_time_t );
void save_state( fme7_apu_state_t* ) const;
void load_state( fme7_apu_state_t const& );
// Mask and addresses of registers
enum { addr_mask = 0xE000 };
enum { data_addr = 0xE000 };
enum { latch_addr = 0xC000 };
// (addr & addr_mask) == latch_addr
void write_latch( int );
// (addr & addr_mask) == data_addr
void write_data( blip_time_t, int data );
public:
Nes_Fme7_Apu();
BLARGG_DISABLE_NOTHROW
private:
// noncopyable
Nes_Fme7_Apu( const Nes_Fme7_Apu& );
Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& );
static unsigned char const amp_table [16];
struct {
Blip_Buffer* output;
int last_amp;
} oscs [osc_count];
blip_time_t last_time;
enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff
Blip_Synth<blip_good_quality,1> synth;
void run_until( blip_time_t );
};
inline void Nes_Fme7_Apu::volume( double v )
{
synth.volume( 0.38 / amp_range * v ); // to do: fine-tune
}
inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq )
{
synth.treble_eq( eq );
}
inline void Nes_Fme7_Apu::osc_output( int i, Blip_Buffer* buf )
{
assert( (unsigned) i < osc_count );
oscs [i].output = buf;
}
inline void Nes_Fme7_Apu::output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, buf );
}
inline Nes_Fme7_Apu::Nes_Fme7_Apu()
{
output( NULL );
volume( 1.0 );
reset();
}
inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; }
inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data )
{
if ( (unsigned) latch >= reg_count )
{
#ifdef dprintf
dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch );
#endif
return;
}
run_until( time );
regs [latch] = data;
}
inline void Nes_Fme7_Apu::end_frame( blip_time_t time )
{
if ( time > last_time )
run_until( time );
assert( last_time >= time );
last_time -= time;
}
inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const
{
*out = *this;
}
inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in )
{
reset();
fme7_apu_state_t* state = this;
*state = in;
}
#endif

View file

@ -0,0 +1,145 @@
// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
#include "Nes_Namco_Apu.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Nes_Namco_Apu::Nes_Namco_Apu()
{
output( NULL );
volume( 1.0 );
reset();
}
void Nes_Namco_Apu::reset()
{
last_time = 0;
addr_reg = 0;
int i;
for ( i = 0; i < reg_count; i++ )
reg [i] = 0;
for ( i = 0; i < osc_count; i++ )
{
Namco_Osc& osc = oscs [i];
osc.delay = 0;
osc.last_amp = 0;
osc.wave_pos = 0;
}
}
void Nes_Namco_Apu::output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, buf );
}
/*
void Nes_Namco_Apu::reflect_state( Tagged_Data& data )
{
reflect_int16( data, BLARGG_4CHAR('A','D','D','R'), &addr_reg );
static const char hex [17] = "0123456789ABCDEF";
int i;
for ( i = 0; i < reg_count; i++ )
reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], &reg [i] );
for ( i = 0; i < osc_count; i++ )
{
reflect_int32( data, BLARGG_4CHAR('D','L','Y','0') + i, &oscs [i].delay );
reflect_int16( data, BLARGG_4CHAR('P','O','S','0') + i, &oscs [i].wave_pos );
}
}
*/
void Nes_Namco_Apu::end_frame( blip_time_t time )
{
if ( time > last_time )
run_until( time );
assert( last_time >= time );
last_time -= time;
}
void Nes_Namco_Apu::run_until( blip_time_t nes_end_time )
{
int active_oscs = (reg [0x7F] >> 4 & 7) + 1;
for ( int i = osc_count - active_oscs; i < osc_count; i++ )
{
Namco_Osc& osc = oscs [i];
Blip_Buffer* output = osc.output;
if ( !output )
continue;
output->set_modified();
blip_resampled_time_t time =
output->resampled_time( last_time ) + osc.delay;
blip_resampled_time_t end_time = output->resampled_time( nes_end_time );
osc.delay = 0;
if ( time < end_time )
{
const BOOST::uint8_t* osc_reg = &reg [i * 8 + 0x40];
if ( !(osc_reg [4] & 0xE0) )
continue;
int volume = osc_reg [7] & 15;
if ( !volume )
continue;
blargg_long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0];
if ( freq < 64 * active_oscs )
continue; // prevent low frequencies from excessively delaying freq changes
blip_resampled_time_t period =
output->resampled_duration( 983040 ) / freq * active_oscs;
int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4;
if ( !wave_size )
continue;
int last_amp = osc.last_amp;
int wave_pos = osc.wave_pos;
do
{
// read wave sample
int addr = wave_pos + osc_reg [6];
int sample = reg [addr >> 1] >> (addr << 2 & 4);
wave_pos++;
sample = (sample & 15) * volume;
// output impulse if amplitude changed
int delta = sample - last_amp;
if ( delta )
{
last_amp = sample;
synth.offset_resampled( time, delta, output );
}
// next sample
time += period;
if ( wave_pos >= wave_size )
wave_pos = 0;
}
while ( time < end_time );
osc.wave_pos = wave_pos;
osc.last_amp = last_amp;
}
osc.delay = time - end_time;
}
last_time = nes_end_time;
}

View file

@ -0,0 +1,102 @@
// Namco 106 sound chip emulator
// Nes_Snd_Emu 0.1.8
#ifndef NES_NAMCO_APU_H
#define NES_NAMCO_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
struct namco_state_t;
class Nes_Namco_Apu {
public:
// See Nes_Apu.h for reference.
void volume( double );
void treble_eq( const blip_eq_t& );
void output( Blip_Buffer* );
enum { osc_count = 8 };
void osc_output( int index, Blip_Buffer* );
void reset();
void end_frame( blip_time_t );
// Read/write data register is at 0x4800
enum { data_reg_addr = 0x4800 };
void write_data( blip_time_t, int );
int read_data();
// Write-only address register is at 0xF800
enum { addr_reg_addr = 0xF800 };
void write_addr( int );
// to do: implement save/restore
void save_state( namco_state_t* out ) const;
void load_state( namco_state_t const& );
public:
Nes_Namco_Apu();
BLARGG_DISABLE_NOTHROW
private:
// noncopyable
Nes_Namco_Apu( const Nes_Namco_Apu& );
Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& );
struct Namco_Osc {
blargg_long delay;
Blip_Buffer* output;
short last_amp;
short wave_pos;
};
Namco_Osc oscs [osc_count];
blip_time_t last_time;
int addr_reg;
enum { reg_count = 0x80 };
BOOST::uint8_t reg [reg_count];
Blip_Synth<blip_good_quality,15> synth;
BOOST::uint8_t& access();
void run_until( blip_time_t );
};
/*
struct namco_state_t
{
BOOST::uint8_t regs [0x80];
BOOST::uint8_t addr;
BOOST::uint8_t unused;
BOOST::uint8_t positions [8];
BOOST::uint32_t delays [8];
};
*/
inline BOOST::uint8_t& Nes_Namco_Apu::access()
{
int addr = addr_reg & 0x7F;
if ( addr_reg & 0x80 )
addr_reg = (addr + 1) | 0x80;
return reg [addr];
}
inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count * v ); }
inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); }
inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; }
inline int Nes_Namco_Apu::read_data() { return access(); }
inline void Nes_Namco_Apu::osc_output( int i, Blip_Buffer* buf )
{
assert( (unsigned) i < osc_count );
oscs [i].output = buf;
}
inline void Nes_Namco_Apu::write_data( blip_time_t time, int data )
{
run_until( time );
access() = data;
}
#endif

551
Frameworks/GME/gme/Nes_Oscs.cpp Executable file
View file

@ -0,0 +1,551 @@
// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
#include "Nes_Apu.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// Nes_Osc
void Nes_Osc::clock_length( int halt_mask )
{
if ( length_counter && !(regs [0] & halt_mask) )
length_counter--;
}
void Nes_Envelope::clock_envelope()
{
int period = regs [0] & 15;
if ( reg_written [3] ) {
reg_written [3] = false;
env_delay = period;
envelope = 15;
}
else if ( --env_delay < 0 ) {
env_delay = period;
if ( envelope | (regs [0] & 0x20) )
envelope = (envelope - 1) & 15;
}
}
int Nes_Envelope::volume() const
{
return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope;
}
// Nes_Square
void Nes_Square::clock_sweep( int negative_adjust )
{
int sweep = regs [1];
if ( --sweep_delay < 0 )
{
reg_written [1] = true;
int period = this->period();
int shift = sweep & shift_mask;
if ( shift && (sweep & 0x80) && period >= 8 )
{
int offset = period >> shift;
if ( sweep & negate_flag )
offset = negative_adjust - offset;
if ( period + offset < 0x800 )
{
period += offset;
// rewrite period
regs [2] = period & 0xFF;
regs [3] = (regs [3] & ~7) | ((period >> 8) & 7);
}
}
}
if ( reg_written [1] ) {
reg_written [1] = false;
sweep_delay = (sweep >> 4) & 7;
}
}
// TODO: clean up
inline nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period )
{
nes_time_t remain = end_time - time;
if ( remain > 0 )
{
int count = (remain + timer_period - 1) / timer_period;
phase = (phase + count) & (phase_range - 1);
time += (blargg_long) count * timer_period;
}
return time;
}
void Nes_Square::run( nes_time_t time, nes_time_t end_time )
{
const int period = this->period();
const int timer_period = (period + 1) * 2;
if ( !output )
{
delay = maintain_phase( time + delay, end_time, timer_period ) - end_time;
return;
}
output->set_modified();
int offset = period >> (regs [1] & shift_mask);
if ( regs [1] & negate_flag )
offset = 0;
const int volume = this->volume();
if ( volume == 0 || period < 8 || (period + offset) >= 0x800 )
{
if ( last_amp ) {
synth.offset( time, -last_amp, output );
last_amp = 0;
}
time += delay;
time = maintain_phase( time, end_time, timer_period );
}
else
{
// handle duty select
int duty_select = (regs [0] >> 6) & 3;
int duty = 1 << duty_select; // 1, 2, 4, 2
int amp = 0;
if ( duty_select == 3 ) {
duty = 2; // negated 25%
amp = volume;
}
if ( phase < duty )
amp ^= volume;
{
int delta = update_amp( amp );
if ( delta )
synth.offset( time, delta, output );
}
time += delay;
if ( time < end_time )
{
Blip_Buffer* const output = this->output;
const Synth& synth = this->synth;
int delta = amp * 2 - volume;
int phase = this->phase;
do {
phase = (phase + 1) & (phase_range - 1);
if ( phase == 0 || phase == duty ) {
delta = -delta;
synth.offset_inline( time, delta, output );
}
time += timer_period;
}
while ( time < end_time );
last_amp = (delta + volume) >> 1;
this->phase = phase;
}
}
delay = time - end_time;
}
// Nes_Triangle
void Nes_Triangle::clock_linear_counter()
{
if ( reg_written [3] )
linear_counter = regs [0] & 0x7F;
else if ( linear_counter )
linear_counter--;
if ( !(regs [0] & 0x80) )
reg_written [3] = false;
}
inline int Nes_Triangle::calc_amp() const
{
int amp = phase_range - phase;
if ( amp < 0 )
amp = phase - (phase_range + 1);
return amp;
}
// TODO: clean up
inline nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period )
{
nes_time_t remain = end_time - time;
if ( remain > 0 )
{
int count = (remain + timer_period - 1) / timer_period;
phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1);
phase++;
time += (blargg_long) count * timer_period;
}
return time;
}
void Nes_Triangle::run( nes_time_t time, nes_time_t end_time )
{
const int timer_period = period() + 1;
if ( !output )
{
time += delay;
delay = 0;
if ( length_counter && linear_counter && timer_period >= 3 )
delay = maintain_phase( time, end_time, timer_period ) - end_time;
return;
}
output->set_modified();
// to do: track phase when period < 3
// to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks.
int delta = update_amp( calc_amp() );
if ( delta )
synth.offset( time, delta, output );
time += delay;
if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 )
{
time = end_time;
}
else if ( time < end_time )
{
Blip_Buffer* const output = this->output;
int phase = this->phase;
int volume = 1;
if ( phase > phase_range ) {
phase -= phase_range;
volume = -volume;
}
do {
if ( --phase == 0 ) {
phase = phase_range;
volume = -volume;
}
else {
synth.offset_inline( time, volume, output );
}
time += timer_period;
}
while ( time < end_time );
if ( volume < 0 )
phase += phase_range;
this->phase = phase;
last_amp = calc_amp();
}
delay = time - end_time;
}
// Nes_Dmc
void Nes_Dmc::reset()
{
address = 0;
dac = 0;
buf = 0;
bits_remain = 1;
bits = 0;
buf_full = false;
silence = true;
next_irq = Nes_Apu::no_irq;
irq_flag = false;
irq_enabled = false;
Nes_Osc::reset();
period = 0x1AC;
}
void Nes_Dmc::recalc_irq()
{
nes_time_t irq = Nes_Apu::no_irq;
if ( irq_enabled && length_counter )
irq = apu->last_dmc_time + delay +
((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1;
if ( irq != next_irq ) {
next_irq = irq;
apu->irq_changed();
}
}
int Nes_Dmc::count_reads( nes_time_t time, nes_time_t* last_read ) const
{
if ( last_read )
*last_read = time;
if ( length_counter == 0 )
return 0; // not reading
nes_time_t first_read = next_read_time();
nes_time_t avail = time - first_read;
if ( avail <= 0 )
return 0;
int count = (avail - 1) / (period * 8) + 1;
if ( !(regs [0] & loop_flag) && count > length_counter )
count = length_counter;
if ( last_read )
{
*last_read = first_read + (count - 1) * (period * 8) + 1;
check( *last_read <= time );
check( count == count_reads( *last_read, NULL ) );
check( count - 1 == count_reads( *last_read - 1, NULL ) );
}
return count;
}
static short const dmc_period_table [2] [16] = {
{428, 380, 340, 320, 286, 254, 226, 214, // NTSC
190, 160, 142, 128, 106, 84, 72, 54},
{398, 354, 316, 298, 276, 236, 210, 198, // PAL
176, 148, 132, 118, 98, 78, 66, 50}
};
inline void Nes_Dmc::reload_sample()
{
address = 0x4000 + regs [2] * 0x40;
length_counter = regs [3] * 0x10 + 1;
}
static byte const dac_table [128] =
{
0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14,
15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27,
27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38,
39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49,
50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59,
59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67,
68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75,
76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83,
};
void Nes_Dmc::write_register( int addr, int data )
{
if ( addr == 0 )
{
period = dmc_period_table [pal_mode] [data & 15];
irq_enabled = (data & 0xC0) == 0x80; // enabled only if loop disabled
irq_flag &= irq_enabled;
recalc_irq();
}
else if ( addr == 1 )
{
int old_dac = dac;
dac = data & 0x7F;
// adjust last_amp so that "pop" amplitude will be properly non-linear
// with respect to change in dac
int faked_nonlinear = dac - (dac_table [dac] - dac_table [old_dac]);
if ( !nonlinear )
last_amp = faked_nonlinear;
}
}
void Nes_Dmc::start()
{
reload_sample();
fill_buffer();
recalc_irq();
}
void Nes_Dmc::fill_buffer()
{
if ( !buf_full && length_counter )
{
require( prg_reader ); // prg_reader must be set
buf = prg_reader( prg_reader_data, 0x8000u + address );
address = (address + 1) & 0x7FFF;
buf_full = true;
if ( --length_counter == 0 )
{
if ( regs [0] & loop_flag ) {
reload_sample();
}
else {
apu->osc_enables &= ~0x10;
irq_flag = irq_enabled;
next_irq = Nes_Apu::no_irq;
apu->irq_changed();
}
}
}
}
void Nes_Dmc::run( nes_time_t time, nes_time_t end_time )
{
int delta = update_amp( dac );
if ( !output )
{
silence = true;
}
else
{
output->set_modified();
if ( delta )
synth.offset( time, delta, output );
}
time += delay;
if ( time < end_time )
{
int bits_remain = this->bits_remain;
if ( silence && !buf_full )
{
int count = (end_time - time + period - 1) / period;
bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1;
time += count * period;
}
else
{
Blip_Buffer* const output = this->output;
const int period = this->period;
int bits = this->bits;
int dac = this->dac;
do
{
if ( !silence )
{
int step = (bits & 1) * 4 - 2;
bits >>= 1;
if ( unsigned (dac + step) <= 0x7F ) {
dac += step;
synth.offset_inline( time, step, output );
}
}
time += period;
if ( --bits_remain == 0 )
{
bits_remain = 8;
if ( !buf_full ) {
silence = true;
}
else {
silence = false;
bits = buf;
buf_full = false;
if ( !output )
silence = true;
fill_buffer();
}
}
}
while ( time < end_time );
this->dac = dac;
this->last_amp = dac;
this->bits = bits;
}
this->bits_remain = bits_remain;
}
delay = time - end_time;
}
// Nes_Noise
static short const noise_period_table [16] = {
0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0,
0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4
};
void Nes_Noise::run( nes_time_t time, nes_time_t end_time )
{
int period = noise_period_table [regs [2] & 15];
if ( !output )
{
// TODO: clean up
time += delay;
delay = time + (end_time - time + period - 1) / period * period - end_time;
return;
}
output->set_modified();
const int volume = this->volume();
int amp = (noise & 1) ? volume : 0;
{
int delta = update_amp( amp );
if ( delta )
synth.offset( time, delta, output );
}
time += delay;
if ( time < end_time )
{
const int mode_flag = 0x80;
if ( !volume )
{
// round to next multiple of period
time += (end_time - time + period - 1) / period * period;
// approximate noise cycling while muted, by shuffling up noise register
// to do: precise muted noise cycling?
if ( !(regs [2] & mode_flag) ) {
int feedback = (noise << 13) ^ (noise << 14);
noise = (feedback & 0x4000) | (noise >> 1);
}
}
else
{
Blip_Buffer* const output = this->output;
// using resampled time avoids conversion in synth.offset()
blip_resampled_time_t rperiod = output->resampled_duration( period );
blip_resampled_time_t rtime = output->resampled_time( time );
int noise = this->noise;
int delta = amp * 2 - volume;
const int tap = (regs [2] & mode_flag ? 8 : 13);
do {
int feedback = (noise << tap) ^ (noise << 14);
time += period;
if ( (noise + 1) & 2 ) {
// bits 0 and 1 of noise differ
delta = -delta;
synth.offset_resampled( rtime, delta, output );
}
rtime += rperiod;
noise = (feedback & 0x4000) | (noise >> 1);
}
while ( time < end_time );
last_amp = (delta + volume) >> 1;
this->noise = noise;
}
}
delay = time - end_time;
}

147
Frameworks/GME/gme/Nes_Oscs.h Executable file
View file

@ -0,0 +1,147 @@
// Private oscillators used by Nes_Apu
// Nes_Snd_Emu 0.1.8
#ifndef NES_OSCS_H
#define NES_OSCS_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
class Nes_Apu;
struct Nes_Osc
{
unsigned char regs [4];
bool reg_written [4];
Blip_Buffer* output;
int length_counter;// length counter (0 if unused by oscillator)
int delay; // delay until next (potential) transition
int last_amp; // last amplitude oscillator was outputting
void clock_length( int halt_mask );
int period() const {
return (regs [3] & 7) * 0x100 + (regs [2] & 0xFF);
}
void reset() {
delay = 0;
last_amp = 0;
}
int update_amp( int amp ) {
int delta = amp - last_amp;
last_amp = amp;
return delta;
}
};
struct Nes_Envelope : Nes_Osc
{
int envelope;
int env_delay;
void clock_envelope();
int volume() const;
void reset() {
envelope = 0;
env_delay = 0;
Nes_Osc::reset();
}
};
// Nes_Square
struct Nes_Square : Nes_Envelope
{
enum { negate_flag = 0x08 };
enum { shift_mask = 0x07 };
enum { phase_range = 8 };
int phase;
int sweep_delay;
typedef Blip_Synth<blip_good_quality,1> Synth;
Synth const& synth; // shared between squares
Nes_Square( Synth const* s ) : synth( *s ) { }
void clock_sweep( int adjust );
void run( nes_time_t, nes_time_t );
void reset() {
sweep_delay = 0;
Nes_Envelope::reset();
}
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period );
};
// Nes_Triangle
struct Nes_Triangle : Nes_Osc
{
enum { phase_range = 16 };
int phase;
int linear_counter;
Blip_Synth<blip_med_quality,1> synth;
int calc_amp() const;
void run( nes_time_t, nes_time_t );
void clock_linear_counter();
void reset() {
linear_counter = 0;
phase = 1;
Nes_Osc::reset();
}
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period );
};
// Nes_Noise
struct Nes_Noise : Nes_Envelope
{
int noise;
Blip_Synth<blip_med_quality,1> synth;
void run( nes_time_t, nes_time_t );
void reset() {
noise = 1 << 14;
Nes_Envelope::reset();
}
};
// Nes_Dmc
struct Nes_Dmc : Nes_Osc
{
int address; // address of next byte to read
int period;
//int length_counter; // bytes remaining to play (already defined in Nes_Osc)
int buf;
int bits_remain;
int bits;
bool buf_full;
bool silence;
enum { loop_flag = 0x40 };
int dac;
nes_time_t next_irq;
bool irq_enabled;
bool irq_flag;
bool pal_mode;
bool nonlinear;
int (*prg_reader)( void*, nes_addr_t ); // needs to be initialized to prg read function
void* prg_reader_data;
Nes_Apu* apu;
Blip_Synth<blip_med_quality,1> synth;
void start();
void write_register( int, int );
void run( nes_time_t, nes_time_t );
void recalc_irq();
void fill_buffer();
void reload_sample();
void reset();
int count_reads( nes_time_t, nes_time_t* ) const;
nes_time_t next_read_time() const;
};
#endif

View file

@ -0,0 +1,215 @@
// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
#include "Nes_Vrc6_Apu.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Nes_Vrc6_Apu::Nes_Vrc6_Apu()
{
output( NULL );
volume( 1.0 );
reset();
}
void Nes_Vrc6_Apu::reset()
{
last_time = 0;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc& osc = oscs [i];
for ( int j = 0; j < reg_count; j++ )
osc.regs [j] = 0;
osc.delay = 0;
osc.last_amp = 0;
osc.phase = 1;
osc.amp = 0;
}
}
void Nes_Vrc6_Apu::output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, buf );
}
void Nes_Vrc6_Apu::run_until( blip_time_t time )
{
require( time >= last_time );
run_square( oscs [0], time );
run_square( oscs [1], time );
run_saw( time );
last_time = time;
}
void Nes_Vrc6_Apu::write_osc( blip_time_t time, int osc_index, int reg, int data )
{
require( (unsigned) osc_index < osc_count );
require( (unsigned) reg < reg_count );
run_until( time );
oscs [osc_index].regs [reg] = data;
}
void Nes_Vrc6_Apu::end_frame( blip_time_t time )
{
if ( time > last_time )
run_until( time );
assert( last_time >= time );
last_time -= time;
}
void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const
{
assert( sizeof (vrc6_apu_state_t) == 20 );
out->saw_amp = oscs [2].amp;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc const& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ )
out->regs [i] [r] = osc.regs [r];
out->delays [i] = osc.delay;
out->phases [i] = osc.phase;
}
}
void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in )
{
reset();
oscs [2].amp = in.saw_amp;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ )
osc.regs [r] = in.regs [i] [r];
osc.delay = in.delays [i];
osc.phase = in.phases [i];
}
if ( !oscs [2].phase )
oscs [2].phase = 1;
}
void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time )
{
Blip_Buffer* output = osc.output;
if ( !output )
return;
output->set_modified();
int volume = osc.regs [0] & 15;
if ( !(osc.regs [2] & 0x80) )
volume = 0;
int gate = osc.regs [0] & 0x80;
int duty = ((osc.regs [0] >> 4) & 7) + 1;
int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp;
blip_time_t time = last_time;
if ( delta )
{
osc.last_amp += delta;
square_synth.offset( time, delta, output );
}
time += osc.delay;
osc.delay = 0;
int period = osc.period();
if ( volume && !gate && period > 4 )
{
if ( time < end_time )
{
int phase = osc.phase;
do
{
phase++;
if ( phase == 16 )
{
phase = 0;
osc.last_amp = volume;
square_synth.offset( time, volume, output );
}
if ( phase == duty )
{
osc.last_amp = 0;
square_synth.offset( time, -volume, output );
}
time += period;
}
while ( time < end_time );
osc.phase = phase;
}
osc.delay = time - end_time;
}
}
void Nes_Vrc6_Apu::run_saw( blip_time_t end_time )
{
Vrc6_Osc& osc = oscs [2];
Blip_Buffer* output = osc.output;
if ( !output )
return;
output->set_modified();
int amp = osc.amp;
int amp_step = osc.regs [0] & 0x3F;
blip_time_t time = last_time;
int last_amp = osc.last_amp;
if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) )
{
osc.delay = 0;
int delta = (amp >> 3) - last_amp;
last_amp = amp >> 3;
saw_synth.offset( time, delta, output );
}
else
{
time += osc.delay;
if ( time < end_time )
{
int period = osc.period() * 2;
int phase = osc.phase;
do
{
if ( --phase == 0 )
{
phase = 7;
amp = 0;
}
int delta = (amp >> 3) - last_amp;
if ( delta )
{
last_amp = amp >> 3;
saw_synth.offset( time, delta, output );
}
time += period;
amp = (amp + amp_step) & 0xFF;
}
while ( time < end_time );
osc.phase = phase;
osc.amp = amp;
}
osc.delay = time - end_time;
}
osc.last_amp = last_amp;
}

View file

@ -0,0 +1,95 @@
// Konami VRC6 sound chip emulator
// Nes_Snd_Emu 0.1.8
#ifndef NES_VRC6_APU_H
#define NES_VRC6_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
struct vrc6_apu_state_t;
class Nes_Vrc6_Apu {
public:
// See Nes_Apu.h for reference
void reset();
void volume( double );
void treble_eq( blip_eq_t const& );
void output( Blip_Buffer* );
enum { osc_count = 3 };
void osc_output( int index, Blip_Buffer* );
void end_frame( blip_time_t );
void save_state( vrc6_apu_state_t* ) const;
void load_state( vrc6_apu_state_t const& );
// Oscillator 0 write-only registers are at $9000-$9002
// Oscillator 1 write-only registers are at $A000-$A002
// Oscillator 2 write-only registers are at $B000-$B002
enum { reg_count = 3 };
enum { base_addr = 0x9000 };
enum { addr_step = 0x1000 };
void write_osc( blip_time_t, int osc, int reg, int data );
public:
Nes_Vrc6_Apu();
BLARGG_DISABLE_NOTHROW
private:
// noncopyable
Nes_Vrc6_Apu( const Nes_Vrc6_Apu& );
Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& );
struct Vrc6_Osc
{
BOOST::uint8_t regs [3];
Blip_Buffer* output;
int delay;
int last_amp;
int phase;
int amp; // only used by saw
int period() const
{
return (regs [2] & 0x0F) * 0x100L + regs [1] + 1;
}
};
Vrc6_Osc oscs [osc_count];
blip_time_t last_time;
Blip_Synth<blip_med_quality,1> saw_synth;
Blip_Synth<blip_good_quality,1> square_synth;
void run_until( blip_time_t );
void run_square( Vrc6_Osc& osc, blip_time_t );
void run_saw( blip_time_t );
};
struct vrc6_apu_state_t
{
BOOST::uint8_t regs [3] [3];
BOOST::uint8_t saw_amp;
BOOST::uint16_t delays [3];
BOOST::uint8_t phases [3];
BOOST::uint8_t unused;
};
inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf )
{
assert( (unsigned) i < osc_count );
oscs [i].output = buf;
}
inline void Nes_Vrc6_Apu::volume( double v )
{
double const factor = 0.0967 * 2;
saw_synth.volume( factor / 31 * v );
square_synth.volume( factor * 0.5 / 15 * v );
}
inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq )
{
saw_synth.treble_eq( eq );
square_synth.treble_eq( eq );
}
#endif

557
Frameworks/GME/gme/Nsf_Emu.cpp Executable file
View file

@ -0,0 +1,557 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Nsf_Emu.h"
#include "blargg_endian.h"
#include <string.h>
#include <stdio.h>
#if !NSF_EMU_APU_ONLY
#include "Nes_Namco_Apu.h"
#include "Nes_Vrc6_Apu.h"
#include "Nes_Fme7_Apu.h"
#endif
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const vrc6_flag = 0x01;
int const namco_flag = 0x10;
int const fme7_flag = 0x20;
long const clock_divisor = 12;
Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq = { -1.0, 80 };
Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80 };
int Nsf_Emu::pcm_read( void* emu, nes_addr_t addr )
{
return *((Nsf_Emu*) emu)->cpu::get_code( addr );
}
Nsf_Emu::Nsf_Emu()
{
vrc6 = 0;
namco = 0;
fme7 = 0;
set_type( gme_nsf_type );
set_silence_lookahead( 6 );
apu.dmc_reader( pcm_read, this );
Music_Emu::set_equalizer( nes_eq );
set_gain( 1.4 );
memset( unmapped_code, Nes_Cpu::bad_opcode, sizeof unmapped_code );
}
Nsf_Emu::~Nsf_Emu() { unload(); }
void Nsf_Emu::unload()
{
#if !NSF_EMU_APU_ONLY
{
delete vrc6;
vrc6 = 0;
delete namco;
namco = 0;
delete fme7;
fme7 = 0;
}
#endif
rom.clear();
Music_Emu::unload();
}
// Track info
static void copy_nsf_fields( Nsf_Emu::header_t const& h, track_info_t* out )
{
GME_COPY_FIELD( h, out, game );
GME_COPY_FIELD( h, out, author );
GME_COPY_FIELD( h, out, copyright );
if ( h.chip_flags )
Gme_File::copy_field_( out->system, "Famicom" );
}
blargg_err_t Nsf_Emu::track_info_( track_info_t* out, int ) const
{
copy_nsf_fields( header_, out );
return 0;
}
static blargg_err_t check_nsf_header( void const* header )
{
if ( memcmp( header, "NESM\x1A", 5 ) )
return gme_wrong_file_type;
return 0;
}
struct Nsf_File : Gme_Info_
{
Nsf_Emu::header_t h;
Nsf_File() { set_type( gme_nsf_type ); }
blargg_err_t load_( Data_Reader& in )
{
blargg_err_t err = in.read( &h, Nsf_Emu::header_size );
if ( err )
return (err == in.eof_error ? gme_wrong_file_type : err);
if ( h.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag) )
set_warning( "Uses unsupported audio expansion hardware" );
set_track_count( h.track_count );
return check_nsf_header( &h );
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_nsf_fields( h, out );
return 0;
}
};
static Music_Emu* new_nsf_emu () { return BLARGG_NEW Nsf_Emu ; }
static Music_Emu* new_nsf_file() { return BLARGG_NEW Nsf_File; }
gme_type_t_ const gme_nsf_type [1] = { "Nintendo NES", 0, &new_nsf_emu, &new_nsf_file, "NSF", 1 };
// Setup
void Nsf_Emu::set_tempo_( double t )
{
unsigned playback_rate = get_le16( header_.ntsc_speed );
unsigned standard_rate = 0x411A;
clock_rate_ = 1789772.72727;
play_period = 262 * 341L * 4 - 2; // two fewer PPU clocks every four frames
if ( pal_only )
{
play_period = 33247 * clock_divisor;
clock_rate_ = 1662607.125;
standard_rate = 0x4E20;
playback_rate = get_le16( header_.pal_speed );
}
if ( !playback_rate )
playback_rate = standard_rate;
if ( playback_rate != standard_rate || t != 1.0 )
play_period = long (playback_rate * clock_rate_ / (1000000.0 / clock_divisor * t));
apu.set_tempo( t );
}
blargg_err_t Nsf_Emu::init_sound()
{
if ( header_.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag) )
set_warning( "Uses unsupported audio expansion hardware" );
{
#define APU_NAMES "Square 1", "Square 2", "Triangle", "Noise", "DMC"
int const count = Nes_Apu::osc_count;
static const char* const apu_names [count] = { APU_NAMES };
set_voice_count( count );
set_voice_names( apu_names );
}
static int const types [] = {
wave_type | 1, wave_type | 2, wave_type | 0,
noise_type | 0, mixed_type | 1,
wave_type | 3, wave_type | 4, wave_type | 5,
wave_type | 6, wave_type | 7, wave_type | 8, wave_type | 9,
wave_type |10, wave_type |11, wave_type |12, wave_type |13
};
set_voice_types( types ); // common to all sound chip configurations
double adjusted_gain = gain();
#if NSF_EMU_APU_ONLY
{
if ( header_.chip_flags )
set_warning( "Uses unsupported audio expansion hardware" );
}
#else
{
if ( header_.chip_flags & (namco_flag | vrc6_flag | fme7_flag) )
set_voice_count( Nes_Apu::osc_count + 3 );
if ( header_.chip_flags & namco_flag )
{
namco = BLARGG_NEW Nes_Namco_Apu;
CHECK_ALLOC( namco );
adjusted_gain *= 0.75;
int const count = Nes_Apu::osc_count + Nes_Namco_Apu::osc_count;
static const char* const names [count] = {
APU_NAMES,
"Wave 1", "Wave 2", "Wave 3", "Wave 4",
"Wave 5", "Wave 6", "Wave 7", "Wave 8"
};
set_voice_count( count );
set_voice_names( names );
}
if ( header_.chip_flags & vrc6_flag )
{
vrc6 = BLARGG_NEW Nes_Vrc6_Apu;
CHECK_ALLOC( vrc6 );
adjusted_gain *= 0.75;
{
int const count = Nes_Apu::osc_count + Nes_Vrc6_Apu::osc_count;
static const char* const names [count] = {
APU_NAMES,
"Saw Wave", "Square 3", "Square 4"
};
set_voice_count( count );
set_voice_names( names );
}
if ( header_.chip_flags & namco_flag )
{
int const count = Nes_Apu::osc_count + Nes_Vrc6_Apu::osc_count +
Nes_Namco_Apu::osc_count;
static const char* const names [count] = {
APU_NAMES,
"Saw Wave", "Square 3", "Square 4",
"Wave 1", "Wave 2", "Wave 3", "Wave 4",
"Wave 5", "Wave 6", "Wave 7", "Wave 8"
};
set_voice_count( count );
set_voice_names( names );
}
}
if ( header_.chip_flags & fme7_flag )
{
fme7 = BLARGG_NEW Nes_Fme7_Apu;
CHECK_ALLOC( fme7 );
adjusted_gain *= 0.75;
int const count = Nes_Apu::osc_count + Nes_Fme7_Apu::osc_count;
static const char* const names [count] = {
APU_NAMES,
"Square 3", "Square 4", "Square 5"
};
set_voice_count( count );
set_voice_names( names );
}
if ( namco ) namco->volume( adjusted_gain );
if ( vrc6 ) vrc6 ->volume( adjusted_gain );
if ( fme7 ) fme7 ->volume( adjusted_gain );
}
#endif
apu.volume( adjusted_gain );
return 0;
}
blargg_err_t Nsf_Emu::load_( Data_Reader& in )
{
assert( offsetof (header_t,unused [4]) == header_size );
RETURN_ERR( rom.load( in, header_size, &header_, 0 ) );
set_track_count( header_.track_count );
RETURN_ERR( check_nsf_header( &header_ ) );
if ( header_.vers != 1 )
set_warning( "Unknown file version" );
// sound and memory
blargg_err_t err = init_sound();
if ( err )
return err;
// set up data
nes_addr_t load_addr = get_le16( header_.load_addr );
init_addr = get_le16( header_.init_addr );
play_addr = get_le16( header_.play_addr );
if ( !load_addr ) load_addr = rom_begin;
if ( !init_addr ) init_addr = rom_begin;
if ( !play_addr ) play_addr = rom_begin;
if ( load_addr < rom_begin || init_addr < rom_begin )
{
const char* w = warning();
if ( !w )
w = "Corrupt file (invalid load/init/play address)";
return w;
}
rom.set_addr( load_addr % bank_size );
int total_banks = rom.size() / bank_size;
// bank switching
int first_bank = (load_addr - rom_begin) / bank_size;
for ( int i = 0; i < bank_count; i++ )
{
unsigned bank = i - first_bank;
if ( bank >= (unsigned) total_banks )
bank = 0;
initial_banks [i] = bank;
if ( header_.banks [i] )
{
// bank-switched
memcpy( initial_banks, header_.banks, sizeof initial_banks );
break;
}
}
pal_only = (header_.speed_flags & 3) == 1;
#if !NSF_EMU_EXTRA_FLAGS
header_.speed_flags = 0;
#endif
set_tempo( tempo() );
return setup_buffer( (long) (clock_rate_ + 0.5) );
}
void Nsf_Emu::update_eq( blip_eq_t const& eq )
{
apu.treble_eq( eq );
#if !NSF_EMU_APU_ONLY
{
if ( namco ) namco->treble_eq( eq );
if ( vrc6 ) vrc6 ->treble_eq( eq );
if ( fme7 ) fme7 ->treble_eq( eq );
}
#endif
}
void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
{
if ( i < Nes_Apu::osc_count )
{
apu.osc_output( i, buf );
return;
}
i -= Nes_Apu::osc_count;
#if !NSF_EMU_APU_ONLY
{
if ( fme7 && i < Nes_Fme7_Apu::osc_count )
{
fme7->osc_output( i, buf );
return;
}
if ( vrc6 )
{
if ( i < Nes_Vrc6_Apu::osc_count )
{
// put saw first
if ( --i < 0 )
i = 2;
vrc6->osc_output( i, buf );
return;
}
i -= Nes_Vrc6_Apu::osc_count;
}
if ( namco && i < Nes_Namco_Apu::osc_count )
{
namco->osc_output( i, buf );
return;
}
}
#endif
}
// Emulation
// see nes_cpu_io.h for read/write functions
void Nsf_Emu::cpu_write_misc( nes_addr_t addr, int data )
{
#if !NSF_EMU_APU_ONLY
{
if ( namco )
{
switch ( addr )
{
case Nes_Namco_Apu::data_reg_addr:
namco->write_data( time(), data );
return;
case Nes_Namco_Apu::addr_reg_addr:
namco->write_addr( data );
return;
}
}
if ( addr >= Nes_Fme7_Apu::latch_addr && fme7 )
{
switch ( addr & Nes_Fme7_Apu::addr_mask )
{
case Nes_Fme7_Apu::latch_addr:
fme7->write_latch( data );
return;
case Nes_Fme7_Apu::data_addr:
fme7->write_data( time(), data );
return;
}
}
if ( vrc6 )
{
unsigned reg = addr & (Nes_Vrc6_Apu::addr_step - 1);
unsigned osc = unsigned (addr - Nes_Vrc6_Apu::base_addr) / Nes_Vrc6_Apu::addr_step;
if ( osc < Nes_Vrc6_Apu::osc_count && reg < Nes_Vrc6_Apu::reg_count )
{
vrc6->write_osc( time(), osc, reg, data );
return;
}
}
}
#endif
// unmapped write
#ifndef NDEBUG
{
// some games write to $8000 and $8001 repeatedly
if ( addr == 0x8000 || addr == 0x8001 ) return;
// probably namco sound mistakenly turned on in mck
if ( addr == 0x4800 || addr == 0xF800 ) return;
// memory mapper?
if ( addr == 0xFFF8 ) return;
dprintf( "write_unmapped( 0x%04X, 0x%02X )\n", (unsigned) addr, (unsigned) data );
}
#endif
}
blargg_err_t Nsf_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
memset( low_mem, 0, sizeof low_mem );
memset( sram, 0, sizeof sram );
cpu::reset( unmapped_code ); // also maps low_mem
cpu::map_code( sram_addr, sizeof sram, sram );
for ( int i = 0; i < bank_count; ++i )
cpu_write( bank_select_addr + i, initial_banks [i] );
apu.reset( pal_only, (header_.speed_flags & 0x20) ? 0x3F : 0 );
apu.write_register( 0, 0x4015, 0x0F );
apu.write_register( 0, 0x4017, (header_.speed_flags & 0x10) ? 0x80 : 0 );
#if !NSF_EMU_APU_ONLY
{
if ( namco ) namco->reset();
if ( vrc6 ) vrc6 ->reset();
if ( fme7 ) fme7 ->reset();
}
#endif
play_ready = 4;
play_extra = 0;
next_play = play_period / clock_divisor;
saved_state.pc = badop_addr;
low_mem [0x1FF] = (badop_addr - 1) >> 8;
low_mem [0x1FE] = (badop_addr - 1) & 0xFF;
r.sp = 0xFD;
r.pc = init_addr;
r.a = track;
r.x = pal_only;
return 0;
}
blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int )
{
set_time( 0 );
while ( time() < duration )
{
nes_time_t end = min( next_play, duration );
end = min( end, time() + 32767 ); // allows CPU to use 16-bit time delta
if ( cpu::run( end ) )
{
if ( r.pc != badop_addr )
{
set_warning( "Emulation error (illegal instruction)" );
r.pc++;
}
else
{
play_ready = 1;
if ( saved_state.pc != badop_addr )
{
cpu::r = saved_state;
saved_state.pc = badop_addr;
}
else
{
set_time( end );
}
}
}
if ( time() >= next_play )
{
nes_time_t period = (play_period + play_extra) / clock_divisor;
play_extra = play_period - period * clock_divisor;
next_play += period;
if ( play_ready && !--play_ready )
{
check( saved_state.pc == badop_addr );
if ( r.pc != badop_addr )
saved_state = cpu::r;
r.pc = play_addr;
low_mem [0x100 + r.sp--] = (badop_addr - 1) >> 8;
low_mem [0x100 + r.sp--] = (badop_addr - 1) & 0xFF;
GME_FRAME_HOOK( this );
}
}
}
if ( cpu::error_count() )
{
cpu::clear_error_count();
set_warning( "Emulation error (illegal instruction)" );
}
duration = time();
next_play -= duration;
check( next_play >= 0 );
if ( next_play < 0 )
next_play = 0;
apu.end_frame( duration );
#if !NSF_EMU_APU_ONLY
{
if ( namco ) namco->end_frame( duration );
if ( vrc6 ) vrc6 ->end_frame( duration );
if ( fme7 ) fme7 ->end_frame( duration );
}
#endif
return 0;
}

106
Frameworks/GME/gme/Nsf_Emu.h Executable file
View file

@ -0,0 +1,106 @@
// Nintendo NES/Famicom NSF music file emulator
// Game_Music_Emu 0.5.2
#ifndef NSF_EMU_H
#define NSF_EMU_H
#include "Classic_Emu.h"
#include "Nes_Apu.h"
#include "Nes_Cpu.h"
class Nsf_Emu : private Nes_Cpu, public Classic_Emu {
typedef Nes_Cpu cpu;
public:
// Equalizer profiles for US NES and Japanese Famicom
static equalizer_t const nes_eq;
static equalizer_t const famicom_eq;
// NSF file header
enum { header_size = 0x80 };
struct header_t
{
char tag [5];
byte vers;
byte track_count;
byte first_track;
byte load_addr [2];
byte init_addr [2];
byte play_addr [2];
char game [32];
char author [32];
char copyright [32];
byte ntsc_speed [2];
byte banks [8];
byte pal_speed [2];
byte speed_flags;
byte chip_flags;
byte unused [4];
};
// Header for currently loaded file
header_t const& header() const { return header_; }
static gme_type_t static_type() { return gme_nsf_type; }
public:
// deprecated
Music_Emu::load;
blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
{ return load_remaining_( &h, sizeof h, in ); }
public:
Nsf_Emu();
~Nsf_Emu();
Nes_Apu* apu_() { return &apu; }
protected:
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t load_( Data_Reader& );
blargg_err_t start_track_( int );
blargg_err_t run_clocks( blip_time_t&, int );
void set_tempo_( double );
void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
void update_eq( blip_eq_t const& );
void unload();
protected:
enum { bank_count = 8 };
byte initial_banks [bank_count];
nes_addr_t init_addr;
nes_addr_t play_addr;
double clock_rate_;
bool pal_only;
// timing
Nes_Cpu::registers_t saved_state;
nes_time_t next_play;
nes_time_t play_period;
int play_extra;
int play_ready;
enum { rom_begin = 0x8000 };
enum { bank_select_addr = 0x5FF8 };
enum { bank_size = 0x1000 };
Rom_Data<bank_size> rom;
public: private: friend class Nes_Cpu;
void cpu_jsr( nes_addr_t );
int cpu_read( nes_addr_t );
void cpu_write( nes_addr_t, int );
void cpu_write_misc( nes_addr_t, int );
enum { badop_addr = bank_select_addr };
private:
class Nes_Namco_Apu* namco;
class Nes_Vrc6_Apu* vrc6;
class Nes_Fme7_Apu* fme7;
Nes_Apu apu;
static int pcm_read( void*, nes_addr_t );
blargg_err_t init_sound();
header_t header_;
enum { sram_addr = 0x6000 };
byte sram [0x2000];
byte unmapped_code [Nes_Cpu::page_size + 8];
};
#endif

330
Frameworks/GME/gme/Nsfe_Emu.cpp Executable file
View file

@ -0,0 +1,330 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Nsfe_Emu.h"
#include "blargg_endian.h"
#include <string.h>
#include <ctype.h>
/* Copyright (C) 2005-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Nsfe_Info::Nsfe_Info() { playlist_disabled = false; }
Nsfe_Info::~Nsfe_Info() { }
inline void Nsfe_Info::unload()
{
track_name_data.clear();
track_names.clear();
playlist.clear();
track_times.clear();
}
// TODO: if no playlist, treat as if there is a playlist that is just 1,2,3,4,5... ?
void Nsfe_Info::disable_playlist( bool b )
{
playlist_disabled = b;
info.track_count = playlist.size();
if ( !info.track_count || playlist_disabled )
info.track_count = actual_track_count_;
}
int Nsfe_Info::remap_track( int track ) const
{
if ( !playlist_disabled && (unsigned) track < playlist.size() )
track = playlist [track];
return track;
}
// Read multiple strings and separate into individual strings
static blargg_err_t read_strs( Data_Reader& in, long size, blargg_vector<char>& chars,
blargg_vector<const char*>& strs )
{
RETURN_ERR( chars.resize( size + 1 ) );
chars [size] = 0; // in case last string doesn't have terminator
RETURN_ERR( in.read( &chars [0], size ) );
RETURN_ERR( strs.resize( 128 ) );
int count = 0;
for ( int i = 0; i < size; i++ )
{
if ( (int) strs.size() <= count )
RETURN_ERR( strs.resize( count * 2 ) );
strs [count++] = &chars [i];
while ( i < size && chars [i] )
i++;
}
return strs.resize( count );
}
// Copy in to out, where out has out_max characters allocated. Truncate to
// out_max - 1 characters.
static void copy_str( const char* in, char* out, int out_max )
{
out [out_max - 1] = 0;
strncpy( out, in, out_max - 1 );
}
struct nsfe_info_t
{
byte load_addr [2];
byte init_addr [2];
byte play_addr [2];
byte speed_flags;
byte chip_flags;
byte track_count;
byte first_track;
byte unused [6];
};
blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsf_Emu* nsf_emu )
{
int const nsfe_info_size = 16;
assert( offsetof (nsfe_info_t,unused [6]) == nsfe_info_size );
// check header
byte signature [4];
blargg_err_t err = in.read( signature, sizeof signature );
if ( err )
return (err == in.eof_error ? gme_wrong_file_type : err);
if ( memcmp( signature, "NSFE", 4 ) )
return gme_wrong_file_type;
// free previous info
track_name_data.clear();
track_names.clear();
playlist.clear();
track_times.clear();
// default nsf header
static const Nsf_Emu::header_t base_header =
{
{'N','E','S','M','\x1A'},// tag
1, // version
1, 1, // track count, first track
{0,0},{0,0},{0,0}, // addresses
"","","", // strings
{0x1A, 0x41}, // NTSC rate
{0,0,0,0,0,0,0,0}, // banks
{0x20, 0x4E}, // PAL rate
0, 0, // flags
{0,0,0,0} // unused
};
Nsf_Emu::header_t& header = info;
header = base_header;
// parse tags
int phase = 0;
while ( phase != 3 )
{
// read size and tag
byte block_header [2] [4];
RETURN_ERR( in.read( block_header, sizeof block_header ) );
blargg_long size = get_le32( block_header [0] );
blargg_long tag = get_le32( block_header [1] );
//dprintf( "tag: %c%c%c%c\n", char(tag), char(tag>>8), char(tag>>16), char(tag>>24) );
switch ( tag )
{
case BLARGG_4CHAR('O','F','N','I'): {
check( phase == 0 );
if ( size < 8 )
return "Corrupt file";
nsfe_info_t finfo;
finfo.track_count = 1;
finfo.first_track = 0;
RETURN_ERR( in.read( &finfo, min( size, (blargg_long) nsfe_info_size ) ) );
if ( size > nsfe_info_size )
RETURN_ERR( in.skip( size - nsfe_info_size ) );
phase = 1;
info.speed_flags = finfo.speed_flags;
info.chip_flags = finfo.chip_flags;
info.track_count = finfo.track_count;
this->actual_track_count_ = finfo.track_count;
info.first_track = finfo.first_track;
memcpy( info.load_addr, finfo.load_addr, 2 * 3 );
break;
}
case BLARGG_4CHAR('K','N','A','B'):
if ( size > (int) sizeof info.banks )
return "Corrupt file";
RETURN_ERR( in.read( info.banks, size ) );
break;
case BLARGG_4CHAR('h','t','u','a'): {
blargg_vector<char> chars;
blargg_vector<const char*> strs;
RETURN_ERR( read_strs( in, size, chars, strs ) );
int n = strs.size();
if ( n > 3 )
copy_str( strs [3], info.dumper, sizeof info.dumper );
if ( n > 2 )
copy_str( strs [2], info.copyright, sizeof info.copyright );
if ( n > 1 )
copy_str( strs [1], info.author, sizeof info.author );
if ( n > 0 )
copy_str( strs [0], info.game, sizeof info.game );
break;
}
case BLARGG_4CHAR('e','m','i','t'):
RETURN_ERR( track_times.resize( size / 4 ) );
RETURN_ERR( in.read( track_times.begin(), track_times.size() * 4 ) );
break;
case BLARGG_4CHAR('l','b','l','t'):
RETURN_ERR( read_strs( in, size, track_name_data, track_names ) );
break;
case BLARGG_4CHAR('t','s','l','p'):
RETURN_ERR( playlist.resize( size ) );
RETURN_ERR( in.read( &playlist [0], size ) );
break;
case BLARGG_4CHAR('A','T','A','D'): {
check( phase == 1 );
phase = 2;
if ( !nsf_emu )
{
RETURN_ERR( in.skip( size ) );
}
else
{
Subset_Reader sub( &in, size ); // limit emu to nsf data
Remaining_Reader rem( &header, Nsf_Emu::header_size, &sub );
RETURN_ERR( nsf_emu->load( rem ) );
check( rem.remain() == 0 );
}
break;
}
case BLARGG_4CHAR('D','N','E','N'):
check( phase == 2 );
phase = 3;
break;
default:
// tags that can be skipped start with a lowercase character
check( islower( (tag >> 24) & 0xFF ) );
RETURN_ERR( in.skip( size ) );
break;
}
}
return 0;
}
blargg_err_t Nsfe_Info::track_info_( track_info_t* out, int track ) const
{
int remapped = remap_track( track );
if ( (unsigned) remapped < track_times.size() )
{
long length = (BOOST::int32_t) get_le32( track_times [remapped] );
if ( length > 0 )
out->length = length;
}
if ( (unsigned) remapped < track_names.size() )
Gme_File::copy_field_( out->song, track_names [remapped] );
GME_COPY_FIELD( info, out, game );
GME_COPY_FIELD( info, out, author );
GME_COPY_FIELD( info, out, copyright );
GME_COPY_FIELD( info, out, dumper );
return 0;
}
Nsfe_Emu::Nsfe_Emu()
{
loading = false;
set_type( gme_nsfe_type );
}
Nsfe_Emu::~Nsfe_Emu() { }
void Nsfe_Emu::unload()
{
if ( !loading )
info.unload(); // TODO: extremely hacky!
Nsf_Emu::unload();
}
blargg_err_t Nsfe_Emu::track_info_( track_info_t* out, int track ) const
{
return info.track_info_( out, track );
}
struct Nsfe_File : Gme_Info_
{
Nsfe_Info info;
Nsfe_File() { set_type( gme_nsfe_type ); }
blargg_err_t load_( Data_Reader& in )
{
RETURN_ERR( info.load( in, 0 ) );
info.disable_playlist( false );
set_track_count( info.info.track_count );
return 0;
}
blargg_err_t track_info_( track_info_t* out, int track ) const
{
return info.track_info_( out, track );
}
};
static Music_Emu* new_nsfe_emu () { return BLARGG_NEW Nsfe_Emu ; }
static Music_Emu* new_nsfe_file() { return BLARGG_NEW Nsfe_File; }
gme_type_t_ const gme_nsfe_type [1] = { "Nintendo NES", 0, &new_nsfe_emu, &new_nsfe_file, "NSFE", 1 };
blargg_err_t Nsfe_Emu::load_( Data_Reader& in )
{
if ( loading )
return Nsf_Emu::load_( in );
// TODO: this hacky recursion-avoidance could have subtle problems
loading = true;
blargg_err_t err = info.load( in, this );
loading = false;
disable_playlist( false );
return err;
}
void Nsfe_Emu::disable_playlist( bool b )
{
info.disable_playlist( b );
set_track_count( info.info.track_count );
}
void Nsfe_Emu::clear_playlist_()
{
disable_playlist();
Nsf_Emu::clear_playlist_();
}
blargg_err_t Nsfe_Emu::start_track_( int track )
{
return Nsf_Emu::start_track_( info.remap_track( track ) );
}

68
Frameworks/GME/gme/Nsfe_Emu.h Executable file
View file

@ -0,0 +1,68 @@
// Nintendo NES/Famicom NSFE music file emulator
// Game_Music_Emu 0.5.2
#ifndef NSFE_EMU_H
#define NSFE_EMU_H
#include "blargg_common.h"
#include "Nsf_Emu.h"
// Allows reading info from NSFE file without creating emulator
class Nsfe_Info {
public:
blargg_err_t load( Data_Reader&, Nsf_Emu* );
struct info_t : Nsf_Emu::header_t
{
char game [256];
char author [256];
char copyright [256];
char dumper [256];
} info;
void disable_playlist( bool = true );
blargg_err_t track_info_( track_info_t* out, int track ) const;
int remap_track( int i ) const;
void unload();
Nsfe_Info();
~Nsfe_Info();
private:
blargg_vector<char> track_name_data;
blargg_vector<const char*> track_names;
blargg_vector<unsigned char> playlist;
blargg_vector<char [4]> track_times;
int actual_track_count_;
bool playlist_disabled;
};
class Nsfe_Emu : public Nsf_Emu {
public:
static gme_type_t static_type() { return gme_nsfe_type; }
public:
// deprecated
struct header_t { char tag [4]; };
Music_Emu::load;
blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
{ return load_remaining_( &h, sizeof h, in ); }
void disable_playlist( bool = true ); // use clear_playlist()
public:
Nsfe_Emu();
~Nsfe_Emu();
protected:
blargg_err_t load_( Data_Reader& );
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t start_track_( int );
void unload();
void clear_playlist_();
private:
Nsfe_Info info;
bool loading;
};
#endif

334
Frameworks/GME/gme/Sap_Apu.cpp Executable file
View file

@ -0,0 +1,334 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Sap_Apu.h"
#include <string.h>
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const max_frequency = 12000; // pure waves above this frequency are silenced
static void gen_poly( blargg_ulong mask, int count, byte* out )
{
blargg_ulong n = 1;
do
{
int bits = 0;
int b = 0;
do
{
// implemented using "Galios configuration"
bits |= (n & 1) << b;
n = (n >> 1) ^ (mask & -(n & 1));
}
while ( b++ < 7 );
*out++ = bits;
}
while ( --count );
}
// poly5
int const poly5_len = (1 << 5) - 1;
blargg_ulong const poly5_mask = (1UL << poly5_len) - 1;
blargg_ulong const poly5 = 0x167C6EA1;
inline blargg_ulong run_poly5( blargg_ulong in, int shift )
{
return (in << shift & poly5_mask) | (in >> (poly5_len - shift));
}
#define POLY_MASK( width, tap1, tap2 ) \
((1UL << (width - 1 - tap1)) | (1UL << (width - 1 - tap2)))
Sap_Apu_Impl::Sap_Apu_Impl()
{
gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 );
gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 );
gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 );
if ( 0 ) // comment out to recauculate poly5 constant
{
byte poly5 [4];
gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 );
blargg_ulong n = poly5 [3] * 0x1000000L + poly5 [2] * 0x10000L +
poly5 [1] * 0x100L + poly5 [0];
blargg_ulong rev = n & 1;
for ( int i = 1; i < poly5_len; i++ )
rev |= (n >> i & 1) << (poly5_len - i);
dprintf( "poly5: 0x%08lX\n", rev );
}
}
Sap_Apu::Sap_Apu()
{
impl = 0;
for ( int i = 0; i < osc_count; i++ )
osc_output( i, 0 );
}
void Sap_Apu::reset( Sap_Apu_Impl* new_impl )
{
impl = new_impl;
last_time = 0;
poly5_pos = 0;
poly4_pos = 0;
polym_pos = 0;
control = 0;
for ( int i = 0; i < osc_count; i++ )
memset( &oscs [i], 0, offsetof (osc_t,output) );
}
inline void Sap_Apu::calc_periods()
{
// 15/64 kHz clock
int divider = 28;
if ( this->control & 1 )
divider = 114;
for ( int i = 0; i < osc_count; i++ )
{
osc_t* const osc = &oscs [i];
int const osc_reload = osc->regs [0]; // cache
blargg_long period = (osc_reload + 1) * divider;
static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 };
if ( this->control & fast_bits [i] )
{
period = osc_reload + 4;
if ( i & 1 )
{
period = osc_reload * 0x100L + osc [-1].regs [0] + 7;
if ( !(this->control & fast_bits [i - 1]) )
period = (period - 6) * divider;
if ( (osc [-1].regs [1] & 0x1F) > 0x10 )
dprintf( "Use of slave channel in 16-bit mode not supported\n" );
}
}
osc->period = period;
}
}
void Sap_Apu::run_until( blip_time_t end_time )
{
calc_periods();
Sap_Apu_Impl* const impl = this->impl; // cache
// 17/9-bit poly selection
byte const* polym = impl->poly17;
int polym_len = poly17_len;
if ( this->control & 0x80 )
{
polym_len = poly9_len;
polym = impl->poly9;
}
polym_pos %= polym_len;
for ( int i = 0; i < osc_count; i++ )
{
osc_t* const osc = &oscs [i];
blip_time_t time = last_time + osc->delay;
blip_time_t const period = osc->period;
// output
Blip_Buffer* output = osc->output;
if ( output )
{
output->set_modified();
int const osc_control = osc->regs [1]; // cache
int volume = (osc_control & 0x0F) * 2;
if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency
((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) )
{
if ( !(osc_control & 0x10) )
volume >>= 1; // inaudible frequency = half volume
int delta = volume - osc->last_amp;
if ( delta )
{
osc->last_amp = volume;
impl->synth.offset( last_time, delta, output );
}
// TODO: doesn't maintain high pass flip-flop (very minor issue)
}
else
{
// high pass
static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 };
blip_time_t period2 = 0; // unused if no high pass
blip_time_t time2 = end_time;
if ( this->control & hipass_bits [i] )
{
period2 = osc [2].period;
time2 = last_time + osc [2].delay;
if ( osc->invert )
{
// trick inner wave loop into inverting output
osc->last_amp -= volume;
volume = -volume;
}
}
if ( time < end_time || time2 < end_time )
{
// poly source
static byte const poly1 [] = { 0x55, 0x55 }; // square wave
byte const* poly = poly1;
int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster
int poly_pos = osc->phase & 1;
int poly_inc = 1;
if ( !(osc_control & 0x20) )
{
poly = polym;
poly_len = polym_len;
poly_pos = polym_pos;
if ( osc_control & 0x40 )
{
poly = impl->poly4;
poly_len = poly4_len;
poly_pos = poly4_pos;
}
poly_inc = period % poly_len;
poly_pos = (poly_pos + osc->delay) % poly_len;
}
poly_inc -= poly_len; // allows more optimized inner loop below
// square/poly5 wave
blargg_ulong wave = poly5;
check( poly5 & 1 ); // low bit is set for pure wave
int poly5_inc = 0;
if ( !(osc_control & 0x80) )
{
wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len );
poly5_inc = period % poly5_len;
}
// Run wave and high pass interleved with each catching up to the other.
// Disabled high pass has no performance effect since inner wave loop
// makes no compromise for high pass, and only runs once in that case.
int osc_last_amp = osc->last_amp;
do
{
// run high pass
if ( time2 < time )
{
int delta = -osc_last_amp;
if ( volume < 0 )
delta += volume;
if ( delta )
{
osc_last_amp += delta - volume;
volume = -volume;
impl->synth.offset( time2, delta, output );
}
}
while ( time2 <= time ) // must advance *past* time to avoid hang
time2 += period2;
// run wave
blip_time_t end = end_time;
if ( end > time2 )
end = time2;
while ( time < end )
{
if ( wave & 1 )
{
int amp = volume & -(poly [poly_pos >> 3] >> (poly_pos & 7) & 1);
if ( (poly_pos += poly_inc) < 0 )
poly_pos += poly_len;
int delta = amp - osc_last_amp;
if ( delta )
{
osc_last_amp = amp;
impl->synth.offset( time, delta, output );
}
}
wave = run_poly5( wave, poly5_inc );
time += period;
}
}
while ( time < end_time || time2 < end_time );
osc->phase = poly_pos;
osc->last_amp = osc_last_amp;
}
osc->invert = 0;
if ( volume < 0 )
{
// undo inversion trickery
osc->last_amp -= volume;
osc->invert = 1;
}
}
}
// maintain divider
blip_time_t remain = end_time - time;
if ( remain > 0 )
{
blargg_long count = (remain + period - 1) / period;
osc->phase ^= count;
time += count * period;
}
osc->delay = time - end_time;
}
// advance polies
blip_time_t duration = end_time - last_time;
last_time = end_time;
poly4_pos = (poly4_pos + duration) % poly4_len;
poly5_pos = (poly5_pos + duration) % poly5_len;
polym_pos += duration; // will get %'d on next call
}
void Sap_Apu::write_data( blip_time_t time, unsigned addr, int data )
{
run_until( time );
int i = (addr ^ 0xD200) >> 1;
if ( i < osc_count )
{
oscs [i].regs [addr & 1] = data;
}
else if ( addr == 0xD208 )
{
control = data;
}
else if ( addr == 0xD209 )
{
oscs [0].delay = 0;
oscs [1].delay = 0;
oscs [2].delay = 0;
oscs [3].delay = 0;
}
/*
// TODO: are polynomials reset in this case?
else if ( addr == 0xD20F )
{
if ( (data & 3) == 0 )
polym_pos = 0;
}
*/
}
void Sap_Apu::end_frame( blip_time_t end_time )
{
if ( end_time > last_time )
run_until( end_time );
last_time -= end_time;
}

77
Frameworks/GME/gme/Sap_Apu.h Executable file
View file

@ -0,0 +1,77 @@
// Atari POKEY sound chip emulator
// Game_Music_Emu 0.5.2
#ifndef SAP_APU_H
#define SAP_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
class Sap_Apu_Impl;
class Sap_Apu {
public:
enum { osc_count = 4 };
void osc_output( int index, Blip_Buffer* );
void reset( Sap_Apu_Impl* );
enum { start_addr = 0xD200 };
enum { end_addr = 0xD209 };
void write_data( blip_time_t, unsigned addr, int data );
void end_frame( blip_time_t );
public:
Sap_Apu();
private:
struct osc_t
{
unsigned char regs [2];
unsigned char phase;
unsigned char invert;
int last_amp;
blip_time_t delay;
blip_time_t period; // always recalculated before use; here for convenience
Blip_Buffer* output;
};
osc_t oscs [osc_count];
Sap_Apu_Impl* impl;
blip_time_t last_time;
int poly5_pos;
int poly4_pos;
int polym_pos;
int control;
void calc_periods();
void run_until( blip_time_t );
enum { poly4_len = (1L << 4) - 1 };
enum { poly9_len = (1L << 9) - 1 };
enum { poly17_len = (1L << 17) - 1 };
friend class Sap_Apu_Impl;
};
// Common tables and Blip_Synth that can be shared among multiple Sap_Apu objects
class Sap_Apu_Impl {
public:
Blip_Synth<blip_good_quality,1> synth;
Sap_Apu_Impl();
void volume( double d ) { synth.volume( 1.0 / Sap_Apu::osc_count / 30 * d ); }
private:
typedef unsigned char byte;
byte poly4 [Sap_Apu::poly4_len / 8 + 1];
byte poly9 [Sap_Apu::poly9_len / 8 + 1];
byte poly17 [Sap_Apu::poly17_len / 8 + 1];
friend class Sap_Apu;
};
inline void Sap_Apu::osc_output( int i, Blip_Buffer* b )
{
assert( (unsigned) i < osc_count );
oscs [i].output = b;
}
#endif

1011
Frameworks/GME/gme/Sap_Cpu.cpp Executable file

File diff suppressed because it is too large Load diff

83
Frameworks/GME/gme/Sap_Cpu.h Executable file
View file

@ -0,0 +1,83 @@
// Atari 6502 CPU emulator
// Game_Music_Emu 0.5.2
#ifndef SAP_CPU_H
#define SAP_CPU_H
#include "blargg_common.h"
typedef blargg_long sap_time_t; // clock cycle count
typedef unsigned sap_addr_t; // 16-bit address
enum { future_sap_time = LONG_MAX / 2 + 1 };
class Sap_Cpu {
public:
typedef BOOST::uint8_t uint8_t;
// Clear all registers and keep pointer to 64K memory passed in
void reset( void* mem_64k );
// Run until specified time is reached. Returns true if suspicious/unsupported
// instruction was encountered at any point during run.
bool run( sap_time_t end_time );
// Registers are not updated until run() returns (except I flag in status)
struct registers_t {
BOOST::uint16_t pc;
BOOST::uint8_t a;
BOOST::uint8_t x;
BOOST::uint8_t y;
BOOST::uint8_t status;
BOOST::uint8_t sp;
};
registers_t r;
enum { idle_addr = 0xFEFF };
// Time of beginning of next instruction to be executed
sap_time_t time() const { return state->time + state->base; }
void set_time( sap_time_t t ) { state->time = t - state->base; }
void adjust_time( int delta ) { state->time += delta; }
sap_time_t irq_time() const { return irq_time_; }
void set_irq_time( sap_time_t );
sap_time_t end_time() const { return end_time_; }
void set_end_time( sap_time_t );
public:
Sap_Cpu() { state = &state_; }
enum { irq_inhibit = 0x04 };
private:
struct state_t {
sap_time_t base;
sap_time_t time;
};
state_t* state; // points to state_ or a local copy within run()
state_t state_;
sap_time_t irq_time_;
sap_time_t end_time_;
uint8_t* mem;
inline sap_time_t update_end_time( sap_time_t end, sap_time_t irq );
};
inline sap_time_t Sap_Cpu::update_end_time( sap_time_t t, sap_time_t irq )
{
if ( irq < t && !(r.status & irq_inhibit) ) t = irq;
sap_time_t delta = state->base - t;
state->base = t;
return delta;
}
inline void Sap_Cpu::set_irq_time( sap_time_t t )
{
state->time += update_end_time( end_time_, (irq_time_ = t) );
}
inline void Sap_Cpu::set_end_time( sap_time_t t )
{
state->time += update_end_time( (end_time_ = t), irq_time_ );
}
#endif

442
Frameworks/GME/gme/Sap_Emu.cpp Executable file
View file

@ -0,0 +1,442 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Sap_Emu.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
long const base_scanline_period = 114;
Sap_Emu::Sap_Emu()
{
set_type( gme_sap_type );
static const char* const names [Sap_Apu::osc_count * 2] = {
"Wave 1", "Wave 2", "Wave 3", "Wave 4",
"Wave 5", "Wave 6", "Wave 7", "Wave 8",
};
set_voice_names( names );
static int const types [Sap_Apu::osc_count * 2] = {
wave_type | 1, wave_type | 2, wave_type | 3, wave_type | 0,
wave_type | 5, wave_type | 6, wave_type | 7, wave_type | 4,
};
set_voice_types( types );
set_silence_lookahead( 6 );
}
Sap_Emu::~Sap_Emu() { }
// Track info
// Returns 16 or greater if not hex
inline int from_hex_char( int h )
{
h -= 0x30;
if ( (unsigned) h > 9 )
h = ((h - 0x11) & 0xDF) + 10;
return h;
}
static long from_hex( byte const* in )
{
unsigned result = 0;
for ( int n = 4; n--; )
{
int h = from_hex_char( *in++ );
if ( h > 15 )
return -1;
result = result * 0x10 + h;
}
return result;
}
static int from_dec( byte const* in, byte const* end )
{
if ( in >= end )
return -1;
int n = 0;
while ( in < end )
{
int dig = *in++ - '0';
if ( (unsigned) dig > 9 )
return -1;
n = n * 10 + dig;
}
return n;
}
static void parse_string( byte const* in, byte const* end, int len, char* out )
{
byte const* start = in;
if ( *in++ == '\"' )
{
start++;
while ( in < end && *in != '\"' )
in++;
}
else
{
in = end;
}
len = min( len - 1, int (in - start) );
out [len] = 0;
memcpy( out, start, len );
}
static blargg_err_t parse_info( byte const* in, long size, Sap_Emu::info_t* out )
{
out->track_count = 1;
out->author [0] = 0;
out->name [0] = 0;
out->copyright [0] = 0;
if ( size < 16 || memcmp( in, "SAP\x0D\x0A", 5 ) )
return gme_wrong_file_type;
byte const* file_end = in + size - 5;
in += 5;
while ( in < file_end && (in [0] != 0xFF || in [1] != 0xFF) )
{
byte const* line_end = in;
while ( line_end < file_end && *line_end != 0x0D )
line_end++;
char const* tag = (char const*) in;
while ( in < line_end && *in > ' ' )
in++;
int tag_len = (char const*) in - tag;
while ( in < line_end && *in <= ' ' ) in++;
if ( tag_len <= 0 )
{
// skip line
}
else if ( !strncmp( "INIT", tag, tag_len ) )
{
out->init_addr = from_hex( in );
if ( (unsigned long) out->init_addr > 0xFFFF )
return "Invalid init address";
}
else if ( !strncmp( "PLAYER", tag, tag_len ) )
{
out->play_addr = from_hex( in );
if ( (unsigned long) out->play_addr > 0xFFFF )
return "Invalid play address";
}
else if ( !strncmp( "MUSIC", tag, tag_len ) )
{
out->music_addr = from_hex( in );
if ( (unsigned long) out->music_addr > 0xFFFF )
return "Invalid music address";
}
else if ( !strncmp( "SONGS", tag, tag_len ) )
{
out->track_count = from_dec( in, line_end );
if ( out->track_count <= 0 )
return "Invalid track count";
}
else if ( !strncmp( "TYPE", tag, tag_len ) )
{
switch ( out->type = *in )
{
case 'C':
case 'B':
break;
case 'D':
return "Digimusic not supported";
default:
return "Unsupported player type";
}
}
else if ( !strncmp( "STEREO", tag, tag_len ) )
{
out->stereo = true;
}
else if ( !strncmp( "FASTPLAY", tag, tag_len ) )
{
out->fastplay = from_dec( in, line_end );
if ( out->fastplay <= 0 )
return "Invalid fastplay value";
}
else if ( !strncmp( "AUTHOR", tag, tag_len ) )
{
parse_string( in, line_end, sizeof out->author, out->author );
}
else if ( !strncmp( "NAME", tag, tag_len ) )
{
parse_string( in, line_end, sizeof out->name, out->name );
}
else if ( !strncmp( "DATE", tag, tag_len ) )
{
parse_string( in, line_end, sizeof out->copyright, out->copyright );
}
in = line_end + 2;
}
if ( in [0] != 0xFF || in [1] != 0xFF )
return "ROM data missing";
out->rom_data = in + 2;
return 0;
}
static void copy_sap_fields( Sap_Emu::info_t const& in, track_info_t* out )
{
Gme_File::copy_field_( out->game, in.name );
Gme_File::copy_field_( out->author, in.author );
Gme_File::copy_field_( out->copyright, in.copyright );
}
blargg_err_t Sap_Emu::track_info_( track_info_t* out, int ) const
{
copy_sap_fields( info, out );
return 0;
}
struct Sap_File : Gme_Info_
{
Sap_Emu::info_t info;
Sap_File() { set_type( gme_sap_type ); }
blargg_err_t load_mem_( byte const* begin, long size )
{
RETURN_ERR( parse_info( begin, size, &info ) );
set_track_count( info.track_count );
return 0;
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_sap_fields( info, out );
return 0;
}
};
static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; }
static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; }
gme_type_t_ const gme_sap_type [1] = { "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 };
// Setup
blargg_err_t Sap_Emu::load_mem_( byte const* in, long size )
{
file_end = in + size;
info.warning = 0;
info.type = 'B';
info.stereo = false;
info.init_addr = -1;
info.play_addr = -1;
info.music_addr = -1;
info.fastplay = 312;
RETURN_ERR( parse_info( in, size, &info ) );
set_warning( info.warning );
set_track_count( info.track_count );
set_voice_count( Sap_Apu::osc_count << info.stereo );
apu_impl.volume( gain() );
return setup_buffer( 1773447 );
}
void Sap_Emu::update_eq( blip_eq_t const& eq )
{
apu_impl.synth.treble_eq( eq );
}
void Sap_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
int i2 = i - Sap_Apu::osc_count;
if ( i2 >= 0 )
apu2.osc_output( i2, right );
else
apu.osc_output( i, (info.stereo ? left : center) );
}
// Emulation
void Sap_Emu::set_tempo_( double t )
{
scanline_period = sap_time_t (base_scanline_period / t);
}
inline sap_time_t Sap_Emu::play_period() const { return info.fastplay * scanline_period; }
void Sap_Emu::cpu_jsr( sap_addr_t addr )
{
check( r.sp >= 0xFE ); // catch anything trying to leave data on stack
r.pc = addr;
int high_byte = (idle_addr - 1) >> 8;
if ( r.sp == 0xFE && mem.ram [0x1FF] == high_byte )
r.sp = 0xFF; // pop extra byte off
mem.ram [0x100 + r.sp--] = high_byte; // some routines use RTI to return
mem.ram [0x100 + r.sp--] = high_byte;
mem.ram [0x100 + r.sp--] = (idle_addr - 1) & 0xFF;
}
void Sap_Emu::run_routine( sap_addr_t addr )
{
cpu_jsr( addr );
cpu::run( 312 * base_scanline_period * 60 );
check( r.pc == idle_addr );
}
inline void Sap_Emu::call_init( int track )
{
switch ( info.type )
{
case 'B':
r.a = track;
run_routine( info.init_addr );
break;
case 'C':
r.a = 0x70;
r.x = info.music_addr&0xFF;
r.y = info.music_addr >> 8;
run_routine( info.play_addr + 3 );
r.a = 0;
r.x = track;
run_routine( info.play_addr + 3 );
break;
}
}
blargg_err_t Sap_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
memset( &mem, 0, sizeof mem );
byte const* in = info.rom_data;
while ( file_end - in >= 5 )
{
unsigned start = get_le16( in );
unsigned end = get_le16( in + 2 );
//dprintf( "Block $%04X-$%04X\n", start, end );
in += 4;
if ( end < start )
{
set_warning( "Invalid file data block" );
break;
}
long len = end - start + 1;
if ( len > file_end - in )
{
set_warning( "Invalid file data block" );
break;
}
memcpy( mem.ram + start, in, len );
in += len;
if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF )
in += 2;
}
apu.reset( &apu_impl );
apu2.reset( &apu_impl );
cpu::reset( mem.ram );
time_mask = 0; // disables sound during init
call_init( track );
time_mask = -1;
next_play = play_period();
return 0;
}
// Emulation
// see sap_cpu_io.h for read/write functions
void Sap_Emu::cpu_write_( sap_addr_t addr, int data )
{
if ( (addr ^ Sap_Apu::start_addr) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) )
{
GME_APU_HOOK( this, addr - Sap_Apu::start_addr, data );
apu.write_data( time() & time_mask, addr, data );
return;
}
if ( (addr ^ (Sap_Apu::start_addr + 0x10)) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) &&
info.stereo )
{
GME_APU_HOOK( this, addr - 0x10 - Sap_Apu::start_addr + 10, data );
apu2.write_data( time() & time_mask, addr ^ 0x10, data );
return;
}
if ( (addr & ~0x0010) != 0xD20F || data != 0x03 )
dprintf( "Unmapped write $%04X <- $%02X\n", addr, data );
}
inline void Sap_Emu::call_play()
{
switch ( info.type )
{
case 'B':
cpu_jsr( info.play_addr );
break;
case 'C':
cpu_jsr( info.play_addr + 6 );
break;
}
}
blargg_err_t Sap_Emu::run_clocks( blip_time_t& duration, int )
{
set_time( 0 );
while ( time() < duration )
{
if ( cpu::run( duration ) || r.pc > idle_addr )
return "Emulation error (illegal instruction)";
if ( r.pc == idle_addr )
{
if ( next_play <= duration )
{
set_time( next_play );
next_play += play_period();
call_play();
GME_FRAME_HOOK( this );
}
else
{
set_time( duration );
}
}
}
duration = time();
next_play -= duration;
check( next_play >= 0 );
if ( next_play < 0 )
next_play = 0;
apu.end_frame( duration );
if ( info.stereo )
apu2.end_frame( duration );
return 0;
}

69
Frameworks/GME/gme/Sap_Emu.h Executable file
View file

@ -0,0 +1,69 @@
// Atari XL/XE SAP music file emulator
// Game_Music_Emu 0.5.2
#ifndef SAP_EMU_H
#define SAP_EMU_H
#include "Classic_Emu.h"
#include "Sap_Apu.h"
#include "Sap_Cpu.h"
class Sap_Emu : private Sap_Cpu, public Classic_Emu {
typedef Sap_Cpu cpu;
public:
static gme_type_t static_type() { return gme_sap_type; }
public:
Sap_Emu();
~Sap_Emu();
struct info_t {
byte const* rom_data;
const char* warning;
long init_addr;
long play_addr;
long music_addr;
int type;
int track_count;
int fastplay;
bool stereo;
char author [256];
char name [256];
char copyright [ 32];
};
protected:
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t load_mem_( byte const*, long );
blargg_err_t start_track_( int );
blargg_err_t run_clocks( blip_time_t&, int );
void set_tempo_( double );
void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
void update_eq( blip_eq_t const& );
public: private: friend class Sap_Cpu;
int cpu_read( sap_addr_t );
void cpu_write( sap_addr_t, int );
void cpu_write_( sap_addr_t, int );
private:
info_t info;
byte const* file_end;
sap_time_t scanline_period;
sap_time_t next_play;
sap_time_t time_mask;
Sap_Apu apu;
Sap_Apu apu2;
// large items
struct {
byte padding1 [0x100];
byte ram [0x10000];
byte padding2 [0x100];
} mem;
Sap_Apu_Impl apu_impl;
sap_time_t play_period() const;
void call_play();
void cpu_jsr( sap_addr_t );
void call_init( int track );
void run_routine( sap_addr_t );
};
#endif

330
Frameworks/GME/gme/Sms_Apu.cpp Executable file
View file

@ -0,0 +1,330 @@
// Sms_Snd_Emu 0.1.4. http://www.slack.net/~ant/
#include "Sms_Apu.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// Sms_Osc
Sms_Osc::Sms_Osc()
{
output = 0;
outputs [0] = 0; // always stays NULL
outputs [1] = 0;
outputs [2] = 0;
outputs [3] = 0;
}
void Sms_Osc::reset()
{
delay = 0;
last_amp = 0;
volume = 0;
output_select = 3;
output = outputs [3];
}
// Sms_Square
inline void Sms_Square::reset()
{
period = 0;
phase = 0;
Sms_Osc::reset();
}
void Sms_Square::run( blip_time_t time, blip_time_t end_time )
{
if ( !volume || period <= 128 )
{
// ignore 16kHz and higher
if ( last_amp )
{
synth->offset( time, -last_amp, output );
last_amp = 0;
}
time += delay;
if ( !period )
{
time = end_time;
}
else if ( time < end_time )
{
// keep calculating phase
int count = (end_time - time + period - 1) / period;
phase = (phase + count) & 1;
time += count * period;
}
}
else
{
int amp = phase ? volume : -volume;
{
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth->offset( time, delta, output );
}
}
time += delay;
if ( time < end_time )
{
Blip_Buffer* const output = this->output;
int delta = amp * 2;
do
{
delta = -delta;
synth->offset_inline( time, delta, output );
time += period;
phase ^= 1;
}
while ( time < end_time );
this->last_amp = phase ? volume : -volume;
}
}
delay = time - end_time;
}
// Sms_Noise
static int const noise_periods [3] = { 0x100, 0x200, 0x400 };
inline void Sms_Noise::reset()
{
period = &noise_periods [0];
shifter = 0x8000;
feedback = 0x9000;
Sms_Osc::reset();
}
void Sms_Noise::run( blip_time_t time, blip_time_t end_time )
{
int amp = volume;
if ( shifter & 1 )
amp = -amp;
{
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth.offset( time, delta, output );
}
}
time += delay;
if ( !volume )
time = end_time;
if ( time < end_time )
{
Blip_Buffer* const output = this->output;
unsigned shifter = this->shifter;
int delta = amp * 2;
int period = *this->period * 2;
if ( !period )
period = 16;
do
{
int changed = shifter + 1;
shifter = (feedback & -(shifter & 1)) ^ (shifter >> 1);
if ( changed & 2 ) // true if bits 0 and 1 differ
{
delta = -delta;
synth.offset_inline( time, delta, output );
}
time += period;
}
while ( time < end_time );
this->shifter = shifter;
this->last_amp = delta >> 1;
}
delay = time - end_time;
}
// Sms_Apu
Sms_Apu::Sms_Apu()
{
for ( int i = 0; i < 3; i++ )
{
squares [i].synth = &square_synth;
oscs [i] = &squares [i];
}
oscs [3] = &noise;
volume( 1.0 );
reset();
}
Sms_Apu::~Sms_Apu()
{
}
void Sms_Apu::volume( double vol )
{
vol *= 0.85 / (osc_count * 64 * 2);
square_synth.volume( vol );
noise.synth.volume( vol );
}
void Sms_Apu::treble_eq( const blip_eq_t& eq )
{
square_synth.treble_eq( eq );
noise.synth.treble_eq( eq );
}
void Sms_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
require( (unsigned) index < osc_count );
require( (center && left && right) || (!center && !left && !right) );
Sms_Osc& osc = *oscs [index];
osc.outputs [1] = right;
osc.outputs [2] = left;
osc.outputs [3] = center;
osc.output = osc.outputs [osc.output_select];
}
void Sms_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, center, left, right );
}
void Sms_Apu::reset( unsigned feedback, int noise_width )
{
last_time = 0;
latch = 0;
if ( !feedback || !noise_width )
{
feedback = 0x0009;
noise_width = 16;
}
// convert to "Galios configuration"
looped_feedback = 1 << (noise_width - 1);
noise_feedback = 0;
while ( noise_width-- )
{
noise_feedback = (noise_feedback << 1) | (feedback & 1);
feedback >>= 1;
}
squares [0].reset();
squares [1].reset();
squares [2].reset();
noise.reset();
}
void Sms_Apu::run_until( blip_time_t end_time )
{
require( end_time >= last_time ); // end_time must not be before previous time
if ( end_time > last_time )
{
// run oscillators
for ( int i = 0; i < osc_count; ++i )
{
Sms_Osc& osc = *oscs [i];
if ( osc.output )
{
osc.output->set_modified();
if ( i < 3 )
squares [i].run( last_time, end_time );
else
noise.run( last_time, end_time );
}
}
last_time = end_time;
}
}
void Sms_Apu::end_frame( blip_time_t end_time )
{
if ( end_time > last_time )
run_until( end_time );
assert( last_time >= end_time );
last_time -= end_time;
}
void Sms_Apu::write_ggstereo( blip_time_t time, int data )
{
require( (unsigned) data <= 0xFF );
run_until( time );
for ( int i = 0; i < osc_count; i++ )
{
Sms_Osc& osc = *oscs [i];
int flags = data >> i;
Blip_Buffer* old_output = osc.output;
osc.output_select = (flags >> 3 & 2) | (flags & 1);
osc.output = osc.outputs [osc.output_select];
if ( osc.output != old_output && osc.last_amp )
{
if ( old_output )
{
old_output->set_modified();
square_synth.offset( time, -osc.last_amp, old_output );
}
osc.last_amp = 0;
}
}
}
// volumes [i] = 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 )
static unsigned char const volumes [16] = {
64, 50, 39, 31, 24, 19, 15, 12, 9, 7, 5, 4, 3, 2, 1, 0
};
void Sms_Apu::write_data( blip_time_t time, int data )
{
require( (unsigned) data <= 0xFF );
run_until( time );
if ( data & 0x80 )
latch = data;
int index = (latch >> 5) & 3;
if ( latch & 0x10 )
{
oscs [index]->volume = volumes [data & 15];
}
else if ( index < 3 )
{
Sms_Square& sq = squares [index];
if ( data & 0x80 )
sq.period = (sq.period & 0xFF00) | (data << 4 & 0x00FF);
else
sq.period = (sq.period & 0x00FF) | (data << 8 & 0x3F00);
}
else
{
int select = data & 3;
if ( select < 3 )
noise.period = &noise_periods [select];
else
noise.period = &squares [2].period;
noise.feedback = (data & 0x04) ? noise_feedback : looped_feedback;
noise.shifter = 0x8000;
}
}

75
Frameworks/GME/gme/Sms_Apu.h Executable file
View file

@ -0,0 +1,75 @@
// Sega Master System SN76489 PSG sound chip emulator
// Sms_Snd_Emu 0.1.4
#ifndef SMS_APU_H
#define SMS_APU_H
#include "Sms_Oscs.h"
class Sms_Apu {
public:
// Set overall volume of all oscillators, where 1.0 is full volume
void volume( double );
// Set treble equalization
void treble_eq( const blip_eq_t& );
// Outputs can be assigned to a single buffer for mono output, or to three
// buffers for stereo output (using Stereo_Buffer to do the mixing).
// Assign all oscillator outputs to specified buffer(s). If buffer
// is NULL, silences all oscillators.
void output( Blip_Buffer* mono );
void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
// Assign single oscillator output to buffer(s). Valid indicies are 0 to 3,
// which refer to Square 1, Square 2, Square 3, and Noise. If buffer is NULL,
// silences oscillator.
enum { osc_count = 4 };
void osc_output( int index, Blip_Buffer* mono );
void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
// Reset oscillators and internal state
void reset( unsigned noise_feedback = 0, int noise_width = 0 );
// Write GameGear left/right assignment byte
void write_ggstereo( blip_time_t, int );
// Write to data port
void write_data( blip_time_t, int );
// Run all oscillators up to specified time, end current frame, then
// start a new frame at time 0.
void end_frame( blip_time_t );
public:
Sms_Apu();
~Sms_Apu();
private:
// noncopyable
Sms_Apu( const Sms_Apu& );
Sms_Apu& operator = ( const Sms_Apu& );
Sms_Osc* oscs [osc_count];
Sms_Square squares [3];
Sms_Square::Synth square_synth; // used by squares
blip_time_t last_time;
int latch;
Sms_Noise noise;
unsigned noise_feedback;
unsigned looped_feedback;
void run_until( blip_time_t );
};
struct sms_apu_state_t
{
unsigned char regs [8] [2];
unsigned char latch;
};
inline void Sms_Apu::output( Blip_Buffer* b ) { output( b, b, b ); }
inline void Sms_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); }
#endif

48
Frameworks/GME/gme/Sms_Oscs.h Executable file
View file

@ -0,0 +1,48 @@
// Private oscillators used by Sms_Apu
// Sms_Snd_Emu 0.1.4
#ifndef SMS_OSCS_H
#define SMS_OSCS_H
#include "Blip_Buffer.h"
struct Sms_Osc
{
Blip_Buffer* outputs [4]; // NULL, right, left, center
Blip_Buffer* output;
int output_select;
int delay;
int last_amp;
int volume;
Sms_Osc();
void reset();
};
struct Sms_Square : Sms_Osc
{
int period;
int phase;
typedef Blip_Synth<blip_good_quality,1> Synth;
const Synth* synth;
void reset();
void run( blip_time_t, blip_time_t );
};
struct Sms_Noise : Sms_Osc
{
const int* period;
unsigned shifter;
unsigned feedback;
typedef Blip_Synth<blip_med_quality,1> Synth;
Synth synth;
void reset();
void run( blip_time_t, blip_time_t );
};
#endif

489
Frameworks/GME/gme/Snes_Spc.cpp Executable file
View file

@ -0,0 +1,489 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Snes_Spc.h"
#include <string.h>
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// always in the future (CPU time can go over 0, but not by this much)
int const timer_disabled_time = 127;
Snes_Spc::Snes_Spc() : dsp( mem.ram ), cpu( this, mem.ram )
{
set_tempo( 1.0 );
// Put STOP instruction around memory to catch PC underflow/overflow.
memset( mem.padding1, 0xFF, sizeof mem.padding1 );
memset( mem.padding2, 0xFF, sizeof mem.padding2 );
// A few tracks read from the last four bytes of IPL ROM
boot_rom [sizeof boot_rom - 2] = 0xC0;
boot_rom [sizeof boot_rom - 1] = 0xFF;
memset( boot_rom, 0, sizeof boot_rom - 2 );
}
void Snes_Spc::set_tempo( double t )
{
int unit = (int) (16.0 / t + 0.5);
timer [0].divisor = unit * 8; // 8 kHz
timer [1].divisor = unit * 8; // 8 kHz
timer [2].divisor = unit; // 64 kHz
}
// Load
void Snes_Spc::set_ipl_rom( void const* in )
{
memcpy( boot_rom, in, sizeof boot_rom );
}
blargg_err_t Snes_Spc::load_spc( const void* data, long size )
{
struct spc_file_t {
char signature [27];
char unused [10];
uint8_t pc [2];
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t status;
uint8_t sp;
char unused2 [212];
uint8_t ram [0x10000];
uint8_t dsp [128];
uint8_t ipl_rom [128];
};
assert( offsetof (spc_file_t,ipl_rom) == spc_file_size );
const spc_file_t* spc = (spc_file_t const*) data;
if ( size < spc_file_size )
return "Not an SPC file";
if ( strncmp( spc->signature, "SNES-SPC700 Sound File Data", 27 ) != 0 )
return "Not an SPC file";
registers_t regs;
regs.pc = spc->pc [1] * 0x100 + spc->pc [0];
regs.a = spc->a;
regs.x = spc->x;
regs.y = spc->y;
regs.status = spc->status;
regs.sp = spc->sp;
if ( (unsigned long) size >= sizeof *spc )
set_ipl_rom( spc->ipl_rom );
const char* error = load_state( regs, spc->ram, spc->dsp );
echo_accessed = false;
return error;
}
void Snes_Spc::clear_echo()
{
if ( !(dsp.read( 0x6C ) & 0x20) )
{
unsigned addr = 0x100 * dsp.read( 0x6D );
size_t size = 0x800 * dsp.read( 0x7D );
memset( mem.ram + addr, 0xFF, min( size, sizeof mem.ram - addr ) );
}
}
// Handle other file formats (emulator save states) in user code, not here.
blargg_err_t Snes_Spc::load_state( const registers_t& cpu_state, const void* new_ram,
const void* dsp_state )
{
// cpu
cpu.r = cpu_state;
// Allow DSP to generate one sample before code starts
// (Tengai Makyo Zero, Tenjin's Table Toss first notes are lost since it
// clears KON 31 cycles from starting execution. It works on the SNES
// since the SPC player adds a few extra cycles delay after restoring
// KON from the DSP registers at the end of an SPC file).
extra_cycles = 32;
// ram
memcpy( mem.ram, new_ram, sizeof mem.ram );
memcpy( extra_ram, mem.ram + rom_addr, sizeof extra_ram );
// boot rom (have to force enable_rom() to update it)
rom_enabled = !(mem.ram [0xF1] & 0x80);
enable_rom( !rom_enabled );
// dsp
dsp.reset();
int i;
for ( i = 0; i < Spc_Dsp::register_count; i++ )
dsp.write( i, ((uint8_t const*) dsp_state) [i] );
// timers
for ( i = 0; i < timer_count; i++ )
{
Timer& t = timer [i];
t.next_tick = 0;
t.enabled = (mem.ram [0xF1] >> i) & 1;
if ( !t.enabled )
t.next_tick = timer_disabled_time;
t.count = 0;
t.counter = mem.ram [0xFD + i] & 15;
int p = mem.ram [0xFA + i];
t.period = p ? p : 0x100;
}
// Handle registers which already give 0 when read by setting RAM and not changing it.
// Put STOP instruction in registers which can be read, to catch attempted CPU execution.
mem.ram [0xF0] = 0;
mem.ram [0xF1] = 0;
mem.ram [0xF3] = 0xFF;
mem.ram [0xFA] = 0;
mem.ram [0xFB] = 0;
mem.ram [0xFC] = 0;
mem.ram [0xFD] = 0xFF;
mem.ram [0xFE] = 0xFF;
mem.ram [0xFF] = 0xFF;
return 0; // success
}
// Hardware
// Current time starts negative and ends at 0
inline spc_time_t Snes_Spc::time() const
{
return -cpu.remain();
}
// Keep track of next time to run and avoid a function call if it hasn't been reached.
// Timers
void Snes_Spc::Timer::run_until_( spc_time_t time )
{
if ( !enabled )
dprintf( "next_tick: %ld, time: %ld", (long) next_tick, (long) time );
assert( enabled ); // when disabled, next_tick should always be in the future
int elapsed = ((time - next_tick) / divisor) + 1;
next_tick += elapsed * divisor;
elapsed += count;
if ( elapsed >= period ) // avoid unnecessary division
{
int n = elapsed / period;
elapsed -= n * period;
counter = (counter + n) & 15;
}
count = elapsed;
}
// DSP
const int clocks_per_sample = 32; // 1.024 MHz CPU clock / 32000 samples per second
void Snes_Spc::run_dsp_( spc_time_t time )
{
int count = ((time - next_dsp) >> 5) + 1; // divide by clocks_per_sample
sample_t* buf = sample_buf;
if ( buf ) {
sample_buf = buf + count * 2; // stereo
assert( sample_buf <= buf_end );
}
next_dsp += count * clocks_per_sample;
dsp.run( count, buf );
}
inline void Snes_Spc::run_dsp( spc_time_t time )
{
if ( time >= next_dsp )
run_dsp_( time );
}
// Debug-only check for read/write within echo buffer, since this might result in
// inaccurate emulation due to the DSP not being caught up to the present.
inline void Snes_Spc::check_for_echo_access( spc_addr_t addr )
{
if ( !echo_accessed && !(dsp.read( 0x6C ) & 0x20) )
{
// ** If echo accesses are found that require running the DSP, cache
// the start and end address on DSP writes to speed up checking.
unsigned start = 0x100 * dsp.read( 0x6D );
unsigned end = start + 0x800 * dsp.read( 0x7D );
if ( start <= addr && addr < end ) {
echo_accessed = true;
dprintf( "Read/write at $%04X within echo buffer\n", (unsigned) addr );
}
}
}
// Read
int Snes_Spc::read( spc_addr_t addr )
{
int result = mem.ram [addr];
if ( (rom_addr <= addr && addr < 0xFFFC || addr >= 0xFFFE) && rom_enabled )
dprintf( "Read from ROM: %04X -> %02X\n", addr, result );
if ( unsigned (addr - 0xF0) < 0x10 )
{
assert( 0xF0 <= addr && addr <= 0xFF );
// counters
int i = addr - 0xFD;
if ( i >= 0 )
{
Timer& t = timer [i];
t.run_until( time() );
int old = t.counter;
t.counter = 0;
return old;
}
// dsp
if ( addr == 0xF3 )
{
run_dsp( time() );
if ( mem.ram [0xF2] >= Spc_Dsp::register_count )
dprintf( "DSP read from $%02X\n", (int) mem.ram [0xF2] );
return dsp.read( mem.ram [0xF2] & 0x7F );
}
if ( addr == 0xF0 || addr == 0xF1 || addr == 0xF8 ||
addr == 0xF9 || addr == 0xFA )
dprintf( "Read from register $%02X\n", (int) addr );
// Registers which always read as 0 are handled by setting mem.ram [reg] to 0
// at startup then never changing that value.
check(( check_for_echo_access( addr ), true ));
}
return result;
}
// Write
void Snes_Spc::enable_rom( bool enable )
{
if ( rom_enabled != enable )
{
rom_enabled = enable;
memcpy( mem.ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size );
// TODO: ROM can still get overwritten when DSP writes to echo buffer
}
}
void Snes_Spc::write( spc_addr_t addr, int data )
{
// first page is very common
if ( addr < 0xF0 ) {
mem.ram [addr] = (uint8_t) data;
}
else switch ( addr )
{
// RAM
default:
check(( check_for_echo_access( addr ), true ));
if ( addr < rom_addr ) {
mem.ram [addr] = (uint8_t) data;
}
else {
extra_ram [addr - rom_addr] = (uint8_t) data;
if ( !rom_enabled )
mem.ram [addr] = (uint8_t) data;
}
break;
// DSP
//case 0xF2: // mapped to RAM
case 0xF3: {
run_dsp( time() );
int reg = mem.ram [0xF2];
if ( next_dsp > 0 ) {
// skip mode
// key press
if ( reg == 0x4C )
keys_pressed |= data & ~dsp.read( 0x5C );
// key release
if ( reg == 0x5C ) {
keys_released |= data;
keys_pressed &= ~data;
}
}
if ( reg < Spc_Dsp::register_count ) {
dsp.write( reg, data );
}
else {
dprintf( "DSP write to $%02X\n", (int) reg );
}
break;
}
case 0xF0: // Test register
dprintf( "Wrote $%02X to $F0\n", (int) data );
break;
// Config
case 0xF1:
{
// timers
for ( int i = 0; i < timer_count; i++ )
{
Timer& t = timer [i];
if ( !(data & (1 << i)) ) {
t.enabled = 0;
t.next_tick = timer_disabled_time;
}
else if ( !t.enabled ) {
// just enabled
t.enabled = 1;
t.counter = 0;
t.count = 0;
t.next_tick = time();
}
}
// port clears
if ( data & 0x10 ) {
mem.ram [0xF4] = 0;
mem.ram [0xF5] = 0;
}
if ( data & 0x20 ) {
mem.ram [0xF6] = 0;
mem.ram [0xF7] = 0;
}
enable_rom( (data & 0x80) != 0 );
break;
}
// Ports
case 0xF4:
case 0xF5:
case 0xF6:
case 0xF7:
// to do: handle output ports
break;
//case 0xF8: // verified on SNES that these are read/write (RAM)
//case 0xF9:
// Timers
case 0xFA:
case 0xFB:
case 0xFC: {
Timer& t = timer [addr - 0xFA];
if ( (t.period & 0xFF) != data ) {
t.run_until( time() );
t.period = data ? data : 0x100;
}
break;
}
// Counters (cleared on write)
case 0xFD:
case 0xFE:
case 0xFF:
dprintf( "Wrote to counter $%02X\n", (int) addr );
timer [addr - 0xFD].counter = 0;
break;
}
}
// Play
blargg_err_t Snes_Spc::skip( long count )
{
if ( count > 4 * 32000L )
{
// don't run DSP for long durations (2-3 times faster)
const long sync_count = 32000L * 2;
// keep track of any keys pressed/released (and not subsequently released)
keys_pressed = 0;
keys_released = 0;
// sentinel tells play to ignore DSP
RETURN_ERR( play( count - sync_count, skip_sentinel ) );
// press/release keys now
dsp.write( 0x5C, keys_released & ~keys_pressed );
dsp.write( 0x4C, keys_pressed );
clear_echo();
// play the last few seconds normally to help synchronize DSP
count = sync_count;
}
return play( count );
}
blargg_err_t Snes_Spc::play( long count, sample_t* out )
{
require( count % 2 == 0 ); // output is always in pairs of samples
// CPU time() runs from -duration to 0
spc_time_t duration = (count / 2) * clocks_per_sample;
// DSP output is made on-the-fly when the CPU reads/writes DSP registers
sample_buf = out;
buf_end = out + (out && out != skip_sentinel ? count : 0);
next_dsp = (out == skip_sentinel) ? clocks_per_sample : -duration + clocks_per_sample;
// Localize timer next_tick times and run them to the present to prevent a running
// but ignored timer's next_tick from getting too far behind and overflowing.
for ( int i = 0; i < timer_count; i++ )
{
Timer& t = timer [i];
if ( t.enabled )
{
t.next_tick -= duration;
t.run_until( -duration );
}
}
// Run CPU for duration, reduced by any extra cycles from previous run
int elapsed = cpu.run( duration - extra_cycles );
if ( elapsed > 0 )
{
dprintf( "Unhandled instruction $%02X, pc = $%04X\n",
(int) cpu.read( cpu.r.pc ), (unsigned) cpu.r.pc );
return "Emulation error (illegal/unsupported instruction)";
}
extra_cycles = -elapsed;
// Catch DSP up to present.
run_dsp( 0 );
if ( out ) {
assert( next_dsp == clocks_per_sample );
assert( out == skip_sentinel || sample_buf - out == count );
}
buf_end = 0;
return 0;
}

121
Frameworks/GME/gme/Snes_Spc.h Executable file
View file

@ -0,0 +1,121 @@
// Super Nintendo (SNES) SPC-700 APU Emulator
// Game_Music_Emu 0.5.2
#ifndef SNES_SPC_H
#define SNES_SPC_H
#include "blargg_common.h"
#include "Spc_Cpu.h"
#include "Spc_Dsp.h"
class Snes_Spc {
public:
// Load copy of SPC data into emulator. Clear echo buffer if 'clear_echo' is true.
enum { spc_file_size = 0x10180 };
blargg_err_t load_spc( const void* spc, long spc_size );
// Generate 'count' samples and optionally write to 'buf'. Count must be even.
// Sample output is 16-bit 32kHz, signed stereo pairs with the left channel first.
typedef short sample_t;
blargg_err_t play( long count, sample_t* buf = NULL );
// Optional functionality
// Load copy of state into emulator.
typedef Spc_Cpu::registers_t registers_t;
blargg_err_t load_state( const registers_t& cpu_state, const void* ram_64k,
const void* dsp_regs_128 );
// Clear echo buffer, useful because many tracks have junk in the buffer.
void clear_echo();
// Mute voice n if bit n (1 << n) of mask is set
enum { voice_count = Spc_Dsp::voice_count };
void mute_voices( int mask );
// Skip forward by the specified number of samples (64000 samples = 1 second)
blargg_err_t skip( long count );
// Set gain, where 1.0 is normal. When greater than 1.0, output is clamped the
// 16-bit sample range.
void set_gain( double );
// If true, prevent channels and global volumes from being phase-negated
void disable_surround( bool disable = true );
// Set 128 bytes to use for IPL boot ROM. Makes copy. Default is zero filled,
// to avoid including copyrighted code from the SPC-700.
void set_ipl_rom( const void* );
void set_tempo( double );
public:
Snes_Spc();
typedef BOOST::uint8_t uint8_t;
private:
// timers
struct Timer
{
spc_time_t next_tick;
int period;
int count;
int divisor;
int enabled;
int counter;
void run_until_( spc_time_t );
void run_until( spc_time_t time )
{
if ( time >= next_tick )
run_until_( time );
}
};
enum { timer_count = 3 };
Timer timer [timer_count];
// hardware
int extra_cycles;
spc_time_t time() const;
int read( spc_addr_t );
void write( spc_addr_t, int );
friend class Spc_Cpu;
// dsp
sample_t* sample_buf;
sample_t* buf_end; // to do: remove this once possible bug resolved
spc_time_t next_dsp;
Spc_Dsp dsp;
int keys_pressed;
int keys_released;
sample_t skip_sentinel [1]; // special value for play() passed by skip()
void run_dsp( spc_time_t );
void run_dsp_( spc_time_t );
bool echo_accessed;
void check_for_echo_access( spc_addr_t );
// boot rom
enum { rom_size = 64 };
enum { rom_addr = 0xFFC0 };
bool rom_enabled;
void enable_rom( bool );
// CPU and RAM (at end because it's large)
Spc_Cpu cpu;
uint8_t extra_ram [rom_size];
struct {
// padding to catch jumps before beginning or past end
uint8_t padding1 [0x100];
uint8_t ram [0x10000];
uint8_t padding2 [0x100];
} mem;
uint8_t boot_rom [rom_size];
};
inline void Snes_Spc::disable_surround( bool disable ) { dsp.disable_surround( disable ); }
inline void Snes_Spc::mute_voices( int mask ) { dsp.mute_voices( mask ); }
inline void Snes_Spc::set_gain( double v ) { dsp.set_gain( v ); }
#endif

1062
Frameworks/GME/gme/Spc_Cpu.cpp Executable file

File diff suppressed because it is too large Load diff

57
Frameworks/GME/gme/Spc_Cpu.h Executable file
View file

@ -0,0 +1,57 @@
// Super Nintendo (SNES) SPC-700 CPU emulator
// Game_Music_Emu 0.5.2
#ifndef SPC_CPU_H
#define SPC_CPU_H
#include "blargg_common.h"
typedef unsigned spc_addr_t;
typedef blargg_long spc_time_t;
class Snes_Spc;
class Spc_Cpu {
typedef BOOST::uint8_t uint8_t;
uint8_t* const ram;
public:
// Keeps pointer to 64K RAM
Spc_Cpu( Snes_Spc* spc, uint8_t* ram );
// SPC-700 registers. *Not* kept updated during a call to run().
struct registers_t {
long pc; // more than 16 bits to allow overflow detection
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t status;
uint8_t sp;
} r;
// Run CPU for at least 'count' cycles. Return the number of cycles remaining
// when emulation stopped (negative if extra cycles were emulated). Emulation
// stops when there are no more remaining cycles or an unhandled instruction
// is encountered (STOP, SLEEP, and any others not yet implemented). In the
// latter case, the return value is greater than zero.
spc_time_t run( spc_time_t count );
// Number of clock cycles remaining for current run() call
spc_time_t remain() const;
// Access memory as the emulated CPU does
int read ( spc_addr_t );
void write( spc_addr_t, int );
private:
// noncopyable
Spc_Cpu( const Spc_Cpu& );
Spc_Cpu& operator = ( const Spc_Cpu& );
unsigned mem_bit( spc_addr_t );
spc_time_t remain_;
Snes_Spc& emu;
};
inline spc_time_t Spc_Cpu::remain() const { return remain_; }
#endif

666
Frameworks/GME/gme/Spc_Dsp.cpp Executable file
View file

@ -0,0 +1,666 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
// Based on Brad Martin's OpenSPC DSP emulator
#include "Spc_Dsp.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2002 Brad Martin */
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
Spc_Dsp::Spc_Dsp( uint8_t* ram_ ) : ram( ram_ )
{
set_gain( 1.0 );
mute_voices( 0 );
disable_surround( false );
assert( offsetof (globals_t,unused9 [2]) == register_count );
assert( sizeof (voice) == register_count );
blargg_verify_byte_order();
}
void Spc_Dsp::mute_voices( int mask )
{
for ( int i = 0; i < voice_count; i++ )
voice_state [i].enabled = (mask >> i & 1) ? 31 : 7;
}
void Spc_Dsp::reset()
{
keys = 0;
echo_ptr = 0;
noise_count = 0;
noise = 1;
fir_offset = 0;
g.flags = 0xE0; // reset, mute, echo off
g.key_ons = 0;
for ( int i = 0; i < voice_count; i++ )
{
voice_t& v = voice_state [i];
v.on_cnt = 0;
v.volume [0] = 0;
v.volume [1] = 0;
v.envstate = state_release;
}
memset( fir_buf, 0, sizeof fir_buf );
}
void Spc_Dsp::write( int i, int data )
{
require( (unsigned) i < register_count );
reg [i] = data;
int high = i >> 4;
switch ( i & 0x0F )
{
// voice volume
case 0:
case 1: {
short* volume = voice_state [high].volume;
int left = (int8_t) reg [i & ~1];
int right = (int8_t) reg [i | 1];
volume [0] = left;
volume [1] = right;
// kill surround only if enabled and signs of volumes differ
if ( left * right < surround_threshold )
{
if ( left < 0 )
volume [0] = -left;
else
volume [1] = -right;
}
break;
}
// fir coefficients
case 0x0F:
fir_coeff [high] = (int8_t) data; // sign-extend
break;
}
}
// This table is for envelope timing. It represents the number of counts
// that should be subtracted from the counter each sample period (32kHz).
// The counter starts at 30720 (0x7800). Each count divides exactly into
// 0x7800 without remainder.
const int env_rate_init = 0x7800;
static short const env_rates [0x20] =
{
0x0000, 0x000F, 0x0014, 0x0018, 0x001E, 0x0028, 0x0030, 0x003C,
0x0050, 0x0060, 0x0078, 0x00A0, 0x00C0, 0x00F0, 0x0140, 0x0180,
0x01E0, 0x0280, 0x0300, 0x03C0, 0x0500, 0x0600, 0x0780, 0x0A00,
0x0C00, 0x0F00, 0x1400, 0x1800, 0x1E00, 0x2800, 0x3C00, 0x7800
};
const int env_range = 0x800;
inline int Spc_Dsp::clock_envelope( int v )
{ /* Return value is current
* ENVX */
raw_voice_t& raw_voice = this->voice [v];
voice_t& voice = voice_state [v];
int envx = voice.envx;
if ( voice.envstate == state_release )
{
/*
* Docs: "When in the state of "key off". the "click" sound is
* prevented by the addition of the fixed value 1/256" WTF???
* Alright, I'm going to choose to interpret that this way:
* When a note is keyed off, start the RELEASE state, which
* subtracts 1/256th each sample period (32kHz). Note there's
* no need for a count because it always happens every update.
*/
envx -= env_range / 256;
if ( envx <= 0 )
{
envx = 0;
keys &= ~(1 << v);
return -1;
}
voice.envx = envx;
raw_voice.envx = envx >> 8;
return envx;
}
int cnt = voice.envcnt;
int adsr1 = raw_voice.adsr [0];
if ( adsr1 & 0x80 )
{
switch ( voice.envstate )
{
case state_attack: {
// increase envelope by 1/64 each step
int t = adsr1 & 15;
if ( t == 15 )
{
envx += env_range / 2;
}
else
{
cnt -= env_rates [t * 2 + 1];
if ( cnt > 0 )
break;
envx += env_range / 64;
cnt = env_rate_init;
}
if ( envx >= env_range )
{
envx = env_range - 1;
voice.envstate = state_decay;
}
voice.envx = envx;
break;
}
case state_decay: {
// Docs: "DR... [is multiplied] by the fixed value
// 1-1/256." Well, at least that makes some sense.
// Multiplying ENVX by 255/256 every time DECAY is
// updated.
cnt -= env_rates [((adsr1 >> 3) & 0xE) + 0x10];
if ( cnt <= 0 )
{
cnt = env_rate_init;
envx -= ((envx - 1) >> 8) + 1;
voice.envx = envx;
}
int sustain_level = raw_voice.adsr [1] >> 5;
if ( envx <= (sustain_level + 1) * 0x100 )
voice.envstate = state_sustain;
break;
}
case state_sustain:
// Docs: "SR [is multiplied] by the fixed value 1-1/256."
// Multiplying ENVX by 255/256 every time SUSTAIN is
// updated.
cnt -= env_rates [raw_voice.adsr [1] & 0x1F];
if ( cnt <= 0 )
{
cnt = env_rate_init;
envx -= ((envx - 1) >> 8) + 1;
voice.envx = envx;
}
break;
case state_release:
// handled above
break;
}
}
else
{ /* GAIN mode is set */
/*
* Note: if the game switches between ADSR and GAIN modes
* partway through, should the count be reset, or should it
* continue from where it was? Does the DSP actually watch for
* that bit to change, or does it just go along with whatever
* it sees when it performs the update? I'm going to assume
* the latter and not update the count, unless I see a game
* that obviously wants the other behavior. The effect would
* be pretty subtle, in any case.
*/
int t = raw_voice.gain;
if (t < 0x80)
{
envx = voice.envx = t << 4;
}
else switch (t >> 5)
{
case 4: /* Docs: "Decrease (linear): Subtraction
* of the fixed value 1/64." */
cnt -= env_rates [t & 0x1F];
if (cnt > 0)
break;
cnt = env_rate_init;
envx -= env_range / 64;
if ( envx < 0 )
{
envx = 0;
if ( voice.envstate == state_attack )
voice.envstate = state_decay;
}
voice.envx = envx;
break;
case 5: /* Docs: "Drecrease <sic> (exponential):
* Multiplication by the fixed value
* 1-1/256." */
cnt -= env_rates [t & 0x1F];
if (cnt > 0)
break;
cnt = env_rate_init;
envx -= ((envx - 1) >> 8) + 1;
if ( envx < 0 )
{
envx = 0;
if ( voice.envstate == state_attack )
voice.envstate = state_decay;
}
voice.envx = envx;
break;
case 6: /* Docs: "Increase (linear): Addition of
* the fixed value 1/64." */
cnt -= env_rates [t & 0x1F];
if (cnt > 0)
break;
cnt = env_rate_init;
envx += env_range / 64;
if ( envx >= env_range )
envx = env_range - 1;
voice.envx = envx;
break;
case 7: /* Docs: "Increase (bent line): Addition
* of the constant 1/64 up to .75 of the
* constaint <sic> 1/256 from .75 to 1." */
cnt -= env_rates [t & 0x1F];
if (cnt > 0)
break;
cnt = env_rate_init;
if ( envx < env_range * 3 / 4 )
envx += env_range / 64;
else
envx += env_range / 256;
if ( envx >= env_range )
envx = env_range - 1;
voice.envx = envx;
break;
}
}
voice.envcnt = cnt;
raw_voice.envx = envx >> 4;
return envx;
}
// Clamp n into range -32768 <= n <= 32767
inline int clamp_16( int n )
{
if ( (BOOST::int16_t) n != n )
n = BOOST::int16_t (0x7FFF - (n >> 31));
return n;
}
void Spc_Dsp::run( long count, short* out_buf )
{
// to do: make clock_envelope() inline so that this becomes a leaf function?
// Should we just fill the buffer with silence? Flags won't be cleared
// during this run so it seems it should keep resetting every sample.
if ( g.flags & 0x80 )
reset();
struct src_dir {
char start [2];
char loop [2];
};
const src_dir* const sd = (src_dir*) &ram [g.wave_page * 0x100];
int left_volume = g.left_volume;
int right_volume = g.right_volume;
if ( left_volume * right_volume < surround_threshold )
right_volume = -right_volume; // kill global surround
left_volume *= emu_gain;
right_volume *= emu_gain;
while ( --count >= 0 )
{
// Here we check for keys on/off. Docs say that successive writes
// to KON/KOF must be separated by at least 2 Ts periods or risk
// being neglected. Therefore DSP only looks at these during an
// update, and not at the time of the write. Only need to do this
// once however, since the regs haven't changed over the whole
// period we need to catch up with.
g.wave_ended &= ~g.key_ons; // Keying on a voice resets that bit in ENDX.
if ( g.noise_enables )
{
noise_count -= env_rates [g.flags & 0x1F];
if ( noise_count <= 0 )
{
noise_count = env_rate_init;
noise_amp = BOOST::int16_t (noise * 2);
// TODO: switch to Galios style
int feedback = (noise << 13) ^ (noise << 14);
noise = (feedback & 0x4000) | (noise >> 1);
}
}
// What is the expected behavior when pitch modulation is enabled on
// voice 0? Jurassic Park 2 does this. Assume 0 for now.
blargg_long prev_outx = 0;
int echol = 0;
int echor = 0;
int left = 0;
int right = 0;
for ( int vidx = 0; vidx < voice_count; vidx++ )
{
const int vbit = 1 << vidx;
raw_voice_t& raw_voice = voice [vidx];
voice_t& voice = voice_state [vidx];
if ( voice.on_cnt && !--voice.on_cnt )
{
// key on
keys |= vbit;
voice.addr = GET_LE16( sd [raw_voice.waveform].start );
voice.block_remain = 1;
voice.envx = 0;
voice.block_header = 0;
voice.fraction = 0x3FFF; // decode three samples immediately
voice.interp0 = 0; // BRR decoder filter uses previous two samples
voice.interp1 = 0;
// NOTE: Real SNES does *not* appear to initialize the
// envelope counter to anything in particular. The first
// cycle always seems to come at a random time sooner than
// expected; as yet, I have been unable to find any
// pattern. I doubt it will matter though, so we'll go
// ahead and do the full time for now.
voice.envcnt = env_rate_init;
voice.envstate = state_attack;
}
if ( g.key_ons & vbit & ~g.key_offs )
{
// voice doesn't come on if key off is set
g.key_ons &= ~vbit;
voice.on_cnt = 8;
}
if ( keys & g.key_offs & vbit )
{
// key off
voice.envstate = state_release;
voice.on_cnt = 0;
}
int envx;
if ( !(keys & vbit) || (envx = clock_envelope( vidx )) < 0 )
{
raw_voice.envx = 0;
raw_voice.outx = 0;
prev_outx = 0;
continue;
}
// Decode samples when fraction >= 1.0 (0x1000)
for ( int n = voice.fraction >> 12; --n >= 0; )
{
if ( !--voice.block_remain )
{
if ( voice.block_header & 1 )
{
g.wave_ended |= vbit;
if ( voice.block_header & 2 )
{
// verified (played endless looping sample and ENDX was set)
voice.addr = GET_LE16( sd [raw_voice.waveform].loop );
}
else
{
// first block was end block; don't play anything (verified)
goto sample_ended; // to do: find alternative to goto
}
}
voice.block_header = ram [voice.addr++];
voice.block_remain = 16; // nybbles
}
// if next block has end flag set, *this* block ends *early* (verified)
if ( voice.block_remain == 9 && (ram [voice.addr + 5] & 3) == 1 &&
(voice.block_header & 3) != 3 )
{
sample_ended:
g.wave_ended |= vbit;
keys &= ~vbit;
raw_voice.envx = 0;
voice.envx = 0;
// add silence samples to interpolation buffer
do
{
voice.interp3 = voice.interp2;
voice.interp2 = voice.interp1;
voice.interp1 = voice.interp0;
voice.interp0 = 0;
}
while ( --n >= 0 );
break;
}
int delta = ram [voice.addr];
if ( voice.block_remain & 1 )
{
delta <<= 4; // use lower nybble
voice.addr++;
}
// Use sign-extended upper nybble
delta = int8_t (delta) >> 4;
// For invalid ranges (D,E,F): if the nybble is negative,
// the result is F000. If positive, 0000. Nothing else
// like previous range, etc seems to have any effect. If
// range is valid, do the shift normally. Note these are
// both shifted right once to do the filters properly, but
// the output will be shifted back again at the end.
int shift = voice.block_header >> 4;
delta = (delta << shift) >> 1;
if ( shift > 0x0C )
delta = (delta >> 14) & ~0x7FF;
// One, two and three point IIR filters
int smp1 = voice.interp0;
int smp2 = voice.interp1;
if ( voice.block_header & 8 )
{
delta += smp1;
delta -= smp2 >> 1;
if ( !(voice.block_header & 4) )
{
delta += (-smp1 - (smp1 >> 1)) >> 5;
delta += smp2 >> 5;
}
else
{
delta += (-smp1 * 13) >> 7;
delta += (smp2 + (smp2 >> 1)) >> 4;
}
}
else if ( voice.block_header & 4 )
{
delta += smp1 >> 1;
delta += (-smp1) >> 5;
}
voice.interp3 = voice.interp2;
voice.interp2 = smp2;
voice.interp1 = smp1;
voice.interp0 = BOOST::int16_t (clamp_16( delta ) * 2); // sign-extend
}
// rate (with possible modulation)
int rate = GET_LE16( raw_voice.rate ) & 0x3FFF;
if ( g.pitch_mods & vbit )
rate = (rate * (prev_outx + 32768)) >> 15;
// Gaussian interpolation using most recent 4 samples
int index = voice.fraction >> 2 & 0x3FC;
voice.fraction = (voice.fraction & 0x0FFF) + rate;
const BOOST::int16_t* table = (BOOST::int16_t const*) ((char const*) gauss + index);
const BOOST::int16_t* table2 = (BOOST::int16_t const*) ((char const*) gauss + (255*4 - index));
int s = ((table [0] * voice.interp3) >> 12) +
((table [1] * voice.interp2) >> 12) +
((table2 [1] * voice.interp1) >> 12);
s = (BOOST::int16_t) (s * 2);
s += (table2 [0] * voice.interp0) >> 11 & ~1;
int output = clamp_16( s );
if ( g.noise_enables & vbit )
output = noise_amp;
// scale output and set outx values
output = (output * envx) >> 11 & ~1;
// output and apply muting (by setting voice.enabled to 31)
// if voice is externally disabled (not a SNES feature)
int l = (voice.volume [0] * output) >> voice.enabled;
int r = (voice.volume [1] * output) >> voice.enabled;
prev_outx = output;
raw_voice.outx = int8_t (output >> 8);
if ( g.echo_ons & vbit )
{
echol += l;
echor += r;
}
left += l;
right += r;
}
// end of channel loop
// main volume control
left = (left * left_volume ) >> (7 + emu_gain_bits);
right = (right * right_volume) >> (7 + emu_gain_bits);
// Echo FIR filter
// read feedback from echo buffer
int echo_ptr = this->echo_ptr;
uint8_t* echo_buf = &ram [(g.echo_page * 0x100 + echo_ptr) & 0xFFFF];
echo_ptr += 4;
if ( echo_ptr >= (g.echo_delay & 15) * 0x800 )
echo_ptr = 0;
int fb_left = (BOOST::int16_t) GET_LE16( echo_buf ); // sign-extend
int fb_right = (BOOST::int16_t) GET_LE16( echo_buf + 2 ); // sign-extend
this->echo_ptr = echo_ptr;
// put samples in history ring buffer
const int fir_offset = this->fir_offset;
short (*fir_pos) [2] = &fir_buf [fir_offset];
this->fir_offset = (fir_offset + 7) & 7; // move backwards one step
fir_pos [0] [0] = (short) fb_left;
fir_pos [0] [1] = (short) fb_right;
fir_pos [8] [0] = (short) fb_left; // duplicate at +8 eliminates wrap checking below
fir_pos [8] [1] = (short) fb_right;
// FIR
fb_left = fb_left * fir_coeff [7] +
fir_pos [1] [0] * fir_coeff [6] +
fir_pos [2] [0] * fir_coeff [5] +
fir_pos [3] [0] * fir_coeff [4] +
fir_pos [4] [0] * fir_coeff [3] +
fir_pos [5] [0] * fir_coeff [2] +
fir_pos [6] [0] * fir_coeff [1] +
fir_pos [7] [0] * fir_coeff [0];
fb_right = fb_right * fir_coeff [7] +
fir_pos [1] [1] * fir_coeff [6] +
fir_pos [2] [1] * fir_coeff [5] +
fir_pos [3] [1] * fir_coeff [4] +
fir_pos [4] [1] * fir_coeff [3] +
fir_pos [5] [1] * fir_coeff [2] +
fir_pos [6] [1] * fir_coeff [1] +
fir_pos [7] [1] * fir_coeff [0];
left += (fb_left * g.left_echo_volume ) >> 14;
right += (fb_right * g.right_echo_volume) >> 14;
// echo buffer feedback
if ( !(g.flags & 0x20) )
{
echol += (fb_left * g.echo_feedback) >> 14;
echor += (fb_right * g.echo_feedback) >> 14;
SET_LE16( echo_buf , clamp_16( echol ) );
SET_LE16( echo_buf + 2, clamp_16( echor ) );
}
if ( out_buf )
{
// write final samples
left = clamp_16( left );
right = clamp_16( right );
int mute = g.flags & 0x40;
out_buf [0] = (short) left;
out_buf [1] = (short) right;
out_buf += 2;
// muting
if ( mute )
{
out_buf [-2] = 0;
out_buf [-1] = 0;
}
}
}
}
// Base normal_gauss table is almost exactly (with an error of 0 or -1 for each entry):
// int normal_gauss [512];
// normal_gauss [i] = exp((i-511)*(i-511)*-9.975e-6)*pow(sin(0.00307096*i),1.7358)*1304.45
// Interleved gauss table (to improve cache coherency).
// gauss [i * 2 + j] = normal_gauss [(1 - j) * 256 + i]
const BOOST::int16_t Spc_Dsp::gauss [512] =
{
370,1305, 366,1305, 362,1304, 358,1304, 354,1304, 351,1304, 347,1304, 343,1303,
339,1303, 336,1303, 332,1302, 328,1302, 325,1301, 321,1300, 318,1300, 314,1299,
311,1298, 307,1297, 304,1297, 300,1296, 297,1295, 293,1294, 290,1293, 286,1292,
283,1291, 280,1290, 276,1288, 273,1287, 270,1286, 267,1284, 263,1283, 260,1282,
257,1280, 254,1279, 251,1277, 248,1275, 245,1274, 242,1272, 239,1270, 236,1269,
233,1267, 230,1265, 227,1263, 224,1261, 221,1259, 218,1257, 215,1255, 212,1253,
210,1251, 207,1248, 204,1246, 201,1244, 199,1241, 196,1239, 193,1237, 191,1234,
188,1232, 186,1229, 183,1227, 180,1224, 178,1221, 175,1219, 173,1216, 171,1213,
168,1210, 166,1207, 163,1205, 161,1202, 159,1199, 156,1196, 154,1193, 152,1190,
150,1186, 147,1183, 145,1180, 143,1177, 141,1174, 139,1170, 137,1167, 134,1164,
132,1160, 130,1157, 128,1153, 126,1150, 124,1146, 122,1143, 120,1139, 118,1136,
117,1132, 115,1128, 113,1125, 111,1121, 109,1117, 107,1113, 106,1109, 104,1106,
102,1102, 100,1098, 99,1094, 97,1090, 95,1086, 94,1082, 92,1078, 90,1074,
89,1070, 87,1066, 86,1061, 84,1057, 83,1053, 81,1049, 80,1045, 78,1040,
77,1036, 76,1032, 74,1027, 73,1023, 71,1019, 70,1014, 69,1010, 67,1005,
66,1001, 65, 997, 64, 992, 62, 988, 61, 983, 60, 978, 59, 974, 58, 969,
56, 965, 55, 960, 54, 955, 53, 951, 52, 946, 51, 941, 50, 937, 49, 932,
48, 927, 47, 923, 46, 918, 45, 913, 44, 908, 43, 904, 42, 899, 41, 894,
40, 889, 39, 884, 38, 880, 37, 875, 36, 870, 36, 865, 35, 860, 34, 855,
33, 851, 32, 846, 32, 841, 31, 836, 30, 831, 29, 826, 29, 821, 28, 816,
27, 811, 27, 806, 26, 802, 25, 797, 24, 792, 24, 787, 23, 782, 23, 777,
22, 772, 21, 767, 21, 762, 20, 757, 20, 752, 19, 747, 19, 742, 18, 737,
17, 732, 17, 728, 16, 723, 16, 718, 15, 713, 15, 708, 15, 703, 14, 698,
14, 693, 13, 688, 13, 683, 12, 678, 12, 674, 11, 669, 11, 664, 11, 659,
10, 654, 10, 649, 10, 644, 9, 640, 9, 635, 9, 630, 8, 625, 8, 620,
8, 615, 7, 611, 7, 606, 7, 601, 6, 596, 6, 592, 6, 587, 6, 582,
5, 577, 5, 573, 5, 568, 5, 563, 4, 559, 4, 554, 4, 550, 4, 545,
4, 540, 3, 536, 3, 531, 3, 527, 3, 522, 3, 517, 2, 513, 2, 508,
2, 504, 2, 499, 2, 495, 2, 491, 2, 486, 1, 482, 1, 477, 1, 473,
1, 469, 1, 464, 1, 460, 1, 456, 1, 451, 1, 447, 1, 443, 1, 439,
0, 434, 0, 430, 0, 426, 0, 422, 0, 418, 0, 414, 0, 410, 0, 405,
0, 401, 0, 397, 0, 393, 0, 389, 0, 385, 0, 381, 0, 378, 0, 374,
};

152
Frameworks/GME/gme/Spc_Dsp.h Executable file
View file

@ -0,0 +1,152 @@
// Super Nintendo (SNES) SPC DSP emulator
// Game_Music_Emu 0.5.2
#ifndef SPC_DSP_H
#define SPC_DSP_H
#include "blargg_common.h"
class Spc_Dsp {
typedef BOOST::int8_t int8_t;
typedef BOOST::uint8_t uint8_t;
public:
// Keeps pointer to 64K ram
Spc_Dsp( uint8_t* ram );
// Mute voice n if bit n (1 << n) of mask is clear.
enum { voice_count = 8 };
void mute_voices( int mask );
// Clear state and silence everything.
void reset();
// Set gain, where 1.0 is normal. When greater than 1.0, output is clamped to
// the 16-bit sample range.
void set_gain( double );
// If true, prevent channels and global volumes from being phase-negated
void disable_surround( bool disable );
// Read/write register 'n', where n ranges from 0 to register_count - 1.
enum { register_count = 128 };
int read ( int n );
void write( int n, int );
// Run DSP for 'count' samples. Write resulting samples to 'buf' if not NULL.
void run( long count, short* buf = NULL );
// End of public interface
private:
struct raw_voice_t {
int8_t left_vol;
int8_t right_vol;
uint8_t rate [2];
uint8_t waveform;
uint8_t adsr [2]; // envelope rates for attack, decay, and sustain
uint8_t gain; // envelope gain (if not using ADSR)
int8_t envx; // current envelope level
int8_t outx; // current sample
int8_t unused [6];
};
struct globals_t {
int8_t unused1 [12];
int8_t left_volume; // 0C Main Volume Left (-.7)
int8_t echo_feedback; // 0D Echo Feedback (-.7)
int8_t unused2 [14];
int8_t right_volume; // 1C Main Volume Right (-.7)
int8_t unused3 [15];
int8_t left_echo_volume; // 2C Echo Volume Left (-.7)
uint8_t pitch_mods; // 2D Pitch Modulation on/off for each voice
int8_t unused4 [14];
int8_t right_echo_volume; // 3C Echo Volume Right (-.7)
uint8_t noise_enables; // 3D Noise output on/off for each voice
int8_t unused5 [14];
uint8_t key_ons; // 4C Key On for each voice
uint8_t echo_ons; // 4D Echo on/off for each voice
int8_t unused6 [14];
uint8_t key_offs; // 5C key off for each voice (instantiates release mode)
uint8_t wave_page; // 5D source directory (wave table offsets)
int8_t unused7 [14];
uint8_t flags; // 6C flags and noise freq
uint8_t echo_page; // 6D
int8_t unused8 [14];
uint8_t wave_ended; // 7C
uint8_t echo_delay; // 7D ms >> 4
char unused9 [2];
};
union {
raw_voice_t voice [voice_count];
uint8_t reg [register_count];
globals_t g;
};
uint8_t* const ram;
// Cache of echo FIR values for faster access
short fir_coeff [voice_count];
// fir_buf [i + 8] == fir_buf [i], to avoid wrap checking in FIR code
short fir_buf [16] [2];
int fir_offset; // (0 to 7)
enum { emu_gain_bits = 8 };
int emu_gain;
int keyed_on; // 8-bits for 8 voices
int keys;
int echo_ptr;
int noise_amp;
int noise;
int noise_count;
int surround_threshold;
static BOOST::int16_t const gauss [];
enum state_t {
state_attack,
state_decay,
state_sustain,
state_release
};
struct voice_t {
short volume [2];
short fraction;// 12-bit fractional position
short interp3; // most recent four decoded samples
short interp2;
short interp1;
short interp0;
short block_remain; // number of nybbles remaining in current block
unsigned short addr;
short block_header; // header byte from current block
short envcnt;
short envx;
short on_cnt;
short enabled; // 7 if enabled, 31 if disabled
short envstate;
short unused; // pad to power of 2
};
voice_t voice_state [voice_count];
int clock_envelope( int );
};
inline void Spc_Dsp::disable_surround( bool disable ) { surround_threshold = disable ? 0 : -0x7FFF; }
inline void Spc_Dsp::set_gain( double v ) { emu_gain = (int) (v * (1 << emu_gain_bits)); }
inline int Spc_Dsp::read( int i )
{
assert( (unsigned) i < register_count );
return reg [i];
}
#endif

326
Frameworks/GME/gme/Spc_Emu.cpp Executable file
View file

@ -0,0 +1,326 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Spc_Emu.h"
#include "blargg_endian.h"
#include <stdlib.h>
#include <string.h>
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Spc_Emu::Spc_Emu()
{
set_type( gme_spc_type );
static const char* const names [Snes_Spc::voice_count] = {
"DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8"
};
set_voice_names( names );
set_gain( 1.4 );
}
Spc_Emu::~Spc_Emu() { }
// Track info
long const trailer_offset = 0x10200;
byte const* Spc_Emu::trailer() const { return &file_data [min( file_size, trailer_offset )]; }
long Spc_Emu::trailer_size() const { return max( 0L, file_size - trailer_offset ); }
static void get_spc_xid6( byte const* begin, long size, track_info_t* out )
{
// header
byte const* end = begin + size;
if ( size < 8 || memcmp( begin, "xid6", 4 ) )
{
check( false );
return;
}
long info_size = get_le32( begin + 4 );
byte const* in = begin + 8;
if ( end - in > info_size )
{
dprintf( "Extra data after SPC xid6 info\n" );
end = in + info_size;
}
int year = 0;
char copyright [256 + 5];
int copyright_len = 0;
int const year_len = 5;
while ( end - in >= 4 )
{
// header
int id = in [0];
int data = in [3] * 0x100 + in [2];
int type = in [1];
int len = type ? data : 0;
in += 4;
if ( len > end - in )
{
check( false );
break; // block goes past end of data
}
// handle specific block types
char* field = 0;
switch ( id )
{
case 0x01: field = out->song; break;
case 0x02: field = out->game; break;
case 0x03: field = out->author; break;
case 0x04: field = out->dumper; break;
case 0x07: field = out->comment; break;
case 0x14: year = data; break;
//case 0x30: // intro length
// Many SPCs have intro length set wrong for looped tracks, making it useless
/*
case 0x30:
check( len == 4 );
if ( len >= 4 )
{
out->intro_length = get_le32( in ) / 64;
if ( out->length > 0 )
{
long loop = out->length - out->intro_length;
if ( loop >= 2000 )
out->loop_length = loop;
}
}
break;
*/
case 0x13:
copyright_len = min( len, (int) sizeof copyright - year_len );
memcpy( &copyright [year_len], in, copyright_len );
break;
default:
if ( id < 0x01 || (id > 0x07 && id < 0x10) ||
(id > 0x14 && id < 0x30) || id > 0x36 )
dprintf( "Unknown SPC xid6 block: %X\n", (int) id );
break;
}
if ( field )
{
check( type == 1 );
Gme_File::copy_field_( field, (char const*) in, len );
}
// skip to next block
in += len;
// blocks are supposed to be 4-byte aligned with zero-padding...
byte const* unaligned = in;
while ( (in - begin) & 3 && in < end )
{
if ( *in++ != 0 )
{
// ...but some files have no padding
in = unaligned;
dprintf( "SPC info tag wasn't properly padded to align\n" );
break;
}
}
}
char* p = &copyright [year_len];
if ( year )
{
*--p = ' ';
for ( int n = 4; n--; )
{
*--p = char (year % 10 + '0');
year /= 10;
}
copyright_len += year_len;
}
if ( copyright_len )
Gme_File::copy_field_( out->copyright, p, copyright_len );
check( in == end );
}
static void get_spc_info( Spc_Emu::header_t const& h, byte const* xid6, long xid6_size,
track_info_t* out )
{
// decode length (can be in text or binary format, sometimes ambiguous ugh)
long len_secs = 0;
for ( int i = 0; i < 3; i++ )
{
unsigned n = h.len_secs [i] - '0';
if ( n > 9 )
{
// ignore single-digit text lengths
// (except if author field is present and begins at offset 1, ugh)
if ( i == 1 && (h.author [0] || !h.author [1]) )
len_secs = 0;
break;
}
len_secs *= 10;
len_secs += n;
}
if ( !len_secs || len_secs > 0x1FFF )
len_secs = get_le16( h.len_secs );
if ( len_secs < 0x1FFF )
out->length = len_secs * 1000;
int offset = (h.author [0] < ' ' || unsigned (h.author [0] - '0') <= 9);
Gme_File::copy_field_( out->author, &h.author [offset], sizeof h.author - offset );
GME_COPY_FIELD( h, out, song );
GME_COPY_FIELD( h, out, game );
GME_COPY_FIELD( h, out, dumper );
GME_COPY_FIELD( h, out, comment );
if ( xid6_size )
get_spc_xid6( xid6, xid6_size, out );
}
blargg_err_t Spc_Emu::track_info_( track_info_t* out, int ) const
{
get_spc_info( header(), trailer(), trailer_size(), out );
return 0;
}
static blargg_err_t check_spc_header( void const* header )
{
if ( memcmp( header, "SNES-SPC700 Sound File Data", 27 ) )
return gme_wrong_file_type;
return 0;
}
struct Spc_File : Gme_Info_
{
Spc_Emu::header_t header;
blargg_vector<byte> xid6;
Spc_File() { set_type( gme_spc_type ); }
blargg_err_t load_( Data_Reader& in )
{
long file_size = in.remain();
if ( file_size < Snes_Spc::spc_file_size )
return gme_wrong_file_type;
RETURN_ERR( in.read( &header, Spc_Emu::header_size ) );
RETURN_ERR( check_spc_header( header.tag ) );
long const xid6_offset = 0x10200;
long xid6_size = file_size - xid6_offset;
if ( xid6_size > 0 )
{
RETURN_ERR( xid6.resize( xid6_size ) );
RETURN_ERR( in.skip( xid6_offset - Spc_Emu::header_size ) );
RETURN_ERR( in.read( xid6.begin(), xid6.size() ) );
}
return 0;
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
get_spc_info( header, xid6.begin(), xid6.size(), out );
return 0;
}
};
static Music_Emu* new_spc_emu () { return BLARGG_NEW Spc_Emu ; }
static Music_Emu* new_spc_file() { return BLARGG_NEW Spc_File; }
gme_type_t_ const gme_spc_type [1] = { "Super Nintendo", 1, &new_spc_emu, &new_spc_file, "SPC", 0 };
// Setup
blargg_err_t Spc_Emu::set_sample_rate_( long sample_rate )
{
apu.set_gain( gain() );
if ( sample_rate != native_sample_rate )
{
RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) );
resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 );
}
return 0;
}
void Spc_Emu::mute_voices_( int m )
{
Music_Emu::mute_voices_( m );
apu.mute_voices( m );
}
blargg_err_t Spc_Emu::load_mem_( byte const* in, long size )
{
assert( offsetof (header_t,unused2 [46]) == header_size );
file_data = in;
file_size = size;
set_voice_count( Snes_Spc::voice_count );
if ( size < Snes_Spc::spc_file_size )
return gme_wrong_file_type;
return check_spc_header( in );
}
// Emulation
void Spc_Emu::set_tempo_( double t ) { apu.set_tempo( t ); }
blargg_err_t Spc_Emu::start_track_( int track )
{
RETURN_ERR( Music_Emu::start_track_( track ) );
resampler.clear();
RETURN_ERR( apu.load_spc( file_data, file_size ) );
apu.clear_echo();
return 0;
}
blargg_err_t Spc_Emu::skip_( long count )
{
if ( sample_rate() != native_sample_rate )
{
count = long (count * resampler.ratio()) & ~1;
count -= resampler.skip_input( count );
}
// TODO: shouldn't skip be adjusted for the 64 samples read afterwards?
if ( count > 0 )
RETURN_ERR( apu.skip( count ) );
// eliminate pop due to resampler
const int resampler_latency = 64;
sample_t buf [resampler_latency];
return play_( resampler_latency, buf );
}
blargg_err_t Spc_Emu::play_( long count, sample_t* out )
{
if ( sample_rate() == native_sample_rate )
return apu.play( count, out );
long remain = count;
while ( remain > 0 )
{
remain -= resampler.read( &out [count - remain], remain );
if ( remain > 0 )
{
long n = resampler.max_write();
RETURN_ERR( apu.play( n, resampler.buffer() ) );
resampler.write( n );
}
}
check( remain == 0 );
return 0;
}

77
Frameworks/GME/gme/Spc_Emu.h Executable file
View file

@ -0,0 +1,77 @@
// Super Nintendo SPC music file emulator
// Game_Music_Emu 0.5.2
#ifndef SPC_EMU_H
#define SPC_EMU_H
#include "Fir_Resampler.h"
#include "Music_Emu.h"
#include "Snes_Spc.h"
class Spc_Emu : public Music_Emu {
public:
// The Super Nintendo hardware samples at 32kHz. Other sample rates are
// handled by resampling the 32kHz output; emulation accuracy is not affected.
enum { native_sample_rate = 32000 };
// SPC file header
enum { header_size = 0x100 };
struct header_t
{
char tag [35];
byte format;
byte version;
byte pc [2];
byte a, x, y, psw, sp;
byte unused [2];
char song [32];
char game [32];
char dumper [16];
char comment [32];
byte date [11];
byte len_secs [3];
byte fade_msec [4];
char author [32]; // sometimes first char should be skipped (see official SPC spec)
byte mute_mask;
byte emulator;
byte unused2 [46];
};
// Header for currently loaded file
header_t const& header() const { return *(header_t const*) file_data; }
// Prevents channels and global volumes from being phase-negated
void disable_surround( bool disable = true );
static gme_type_t static_type() { return gme_spc_type; }
public:
// deprecated
Music_Emu::load;
blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
{ return load_remaining_( &h, sizeof h, in ); }
byte const* trailer() const; // use track_info()
long trailer_size() const;
public:
Spc_Emu();
~Spc_Emu();
protected:
blargg_err_t load_mem_( byte const*, long );
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t set_sample_rate_( long );
blargg_err_t start_track_( int );
blargg_err_t play_( long, sample_t* );
blargg_err_t skip_( long );
void mute_voices_( int );
void set_tempo_( double );
private:
byte const* file_data;
long file_size;
Fir_Resampler<24> resampler;
Snes_Spc apu;
};
inline void Spc_Emu::disable_surround( bool b ) { apu.disable_surround( b ); }
#endif

412
Frameworks/GME/gme/Vgm_Emu.cpp Executable file
View file

@ -0,0 +1,412 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Vgm_Emu.h"
#include "blargg_endian.h"
#include <string.h>
#include <math.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
double const fm_gain = 3.0; // FM emulators are internally quieter to avoid 16-bit overflow
double const rolloff = 0.990;
double const oversample_factor = 1.5;
Vgm_Emu::Vgm_Emu()
{
disable_oversampling_ = false;
psg_rate = 0;
set_type( gme_vgm_type );
static int const types [8] = {
wave_type | 1, wave_type | 0, wave_type | 2, noise_type | 0
};
set_voice_types( types );
set_silence_lookahead( 1 ); // tracks should already be trimmed
static equalizer_t const eq = { -14.0, 80 };
set_equalizer( eq );
}
Vgm_Emu::~Vgm_Emu() { }
// Track info
static byte const* skip_gd3_str( byte const* in, byte const* end )
{
while ( end - in >= 2 )
{
in += 2;
if ( !(in [-2] | in [-1]) )
break;
}
return in;
}
static byte const* get_gd3_str( byte const* in, byte const* end, char* field )
{
byte const* mid = skip_gd3_str( in, end );
int len = (mid - in) / 2 - 1;
if ( len > 0 )
{
len = min( len, (int) Gme_File::max_field_ );
field [len] = 0;
for ( int i = 0; i < len; i++ )
field [i] = (in [i * 2 + 1] ? '?' : in [i * 2]); // TODO: convert to utf-8
}
return mid;
}
static byte const* get_gd3_pair( byte const* in, byte const* end, char* field )
{
return skip_gd3_str( get_gd3_str( in, end, field ), end );
}
static void parse_gd3( byte const* in, byte const* end, track_info_t* out )
{
in = get_gd3_pair( in, end, out->song );
in = get_gd3_pair( in, end, out->game );
in = get_gd3_pair( in, end, out->system );
in = get_gd3_pair( in, end, out->author );
in = get_gd3_str ( in, end, out->copyright );
in = get_gd3_pair( in, end, out->dumper );
in = get_gd3_str ( in, end, out->comment );
}
int const gd3_header_size = 12;
static long check_gd3_header( byte const* h, long remain )
{
if ( remain < gd3_header_size ) return 0;
if ( memcmp( h, "Gd3 ", 4 ) ) return 0;
if ( get_le32( h + 4 ) >= 0x200 ) return 0;
long gd3_size = get_le32( h + 8 );
if ( gd3_size > remain - gd3_header_size ) return 0;
return gd3_size;
}
byte const* Vgm_Emu::gd3_data( int* size ) const
{
if ( size )
*size = 0;
long gd3_offset = get_le32( header().gd3_offset ) - 0x2C;
if ( gd3_offset < 0 )
return 0;
byte const* gd3 = data + header_size + gd3_offset;
long gd3_size = check_gd3_header( gd3, data_end - gd3 );
if ( !gd3_size )
return 0;
if ( size )
*size = gd3_size + gd3_header_size;
return gd3;
}
static void get_vgm_length( Vgm_Emu::header_t const& h, track_info_t* out )
{
long length = get_le32( h.track_duration ) * 10 / 441;
if ( length > 0 )
{
long loop = get_le32( h.loop_duration );
if ( loop > 0 && get_le32( h.loop_offset ) )
{
out->loop_length = loop * 10 / 441;
out->intro_length = length - out->loop_length;
}
else
{
out->length = length; // 1000 / 44100 (VGM files used 44100 as timebase)
out->intro_length = length; // make it clear that track is no longer than length
out->loop_length = 0;
}
}
}
blargg_err_t Vgm_Emu::track_info_( track_info_t* out, int ) const
{
get_vgm_length( header(), out );
int size;
byte const* gd3 = gd3_data( &size );
if ( gd3 )
parse_gd3( gd3 + gd3_header_size, gd3 + size, out );
return 0;
}
static blargg_err_t check_vgm_header( Vgm_Emu::header_t const& h )
{
if ( memcmp( h.tag, "Vgm ", 4 ) )
return gme_wrong_file_type;
return 0;
}
struct Vgm_File : Gme_Info_
{
Vgm_Emu::header_t h;
blargg_vector<byte> gd3;
Vgm_File() { set_type( gme_vgm_type ); }
blargg_err_t load_( Data_Reader& in )
{
long file_size = in.remain();
if ( file_size <= Vgm_Emu::header_size )
return gme_wrong_file_type;
RETURN_ERR( in.read( &h, Vgm_Emu::header_size ) );
RETURN_ERR( check_vgm_header( h ) );
long gd3_offset = get_le32( h.gd3_offset ) - 0x2C;
long remain = file_size - Vgm_Emu::header_size - gd3_offset;
byte gd3_h [gd3_header_size];
if ( gd3_offset > 0 || remain >= gd3_header_size )
{
RETURN_ERR( in.skip( gd3_offset ) );
RETURN_ERR( in.read( gd3_h, sizeof gd3_h ) );
long gd3_size = check_gd3_header( gd3_h, remain );
if ( gd3_size )
{
RETURN_ERR( gd3.resize( gd3_size ) );
RETURN_ERR( in.read( gd3.begin(), gd3.size() ) );
}
}
return 0;
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
get_vgm_length( h, out );
if ( gd3.size() )
parse_gd3( gd3.begin(), gd3.end(), out );
return 0;
}
};
static Music_Emu* new_vgm_emu () { return BLARGG_NEW Vgm_Emu ; }
static Music_Emu* new_vgm_file() { return BLARGG_NEW Vgm_File; }
gme_type_t_ const gme_vgm_type [1] = { "Sega SMS/Genesis", 1, &new_vgm_emu, &new_vgm_file, "VGM", 1 };
gme_type_t_ const gme_vgz_type [1] = { "Sega SMS/Genesis", 1, &new_vgm_emu, &new_vgm_file, "VGZ", 1 };
// Setup
void Vgm_Emu::set_tempo_( double t )
{
if ( psg_rate )
{
vgm_rate = (long) (44100 * t + 0.5);
blip_time_factor = (long) floor( double (1L << blip_time_bits) / vgm_rate * psg_rate + 0.5 );
//dprintf( "blip_time_factor: %ld\n", blip_time_factor );
//dprintf( "vgm_rate: %ld\n", vgm_rate );
// TODO: remove? calculates vgm_rate more accurately (above differs at most by one Hz only)
//blip_time_factor = (long) floor( double (1L << blip_time_bits) * psg_rate / 44100 / t + 0.5 );
//vgm_rate = (long) floor( double (1L << blip_time_bits) * psg_rate / blip_time_factor + 0.5 );
fm_time_factor = 2 + (long) floor( fm_rate * (1L << fm_time_bits) / vgm_rate + 0.5 );
}
}
blargg_err_t Vgm_Emu::set_sample_rate_( long sample_rate )
{
RETURN_ERR( blip_buf.set_sample_rate( sample_rate, 1000 / 30 ) );
return Classic_Emu::set_sample_rate_( sample_rate );
}
void Vgm_Emu::update_eq( blip_eq_t const& eq )
{
psg.treble_eq( eq );
dac_synth.treble_eq( eq );
}
void Vgm_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{
if ( i < psg.osc_count )
psg.osc_output( i, c, l, r );
}
void Vgm_Emu::mute_voices_( int mask )
{
Classic_Emu::mute_voices_( mask );
dac_synth.output( &blip_buf );
if ( uses_fm )
{
psg.output( (mask & 0x80) ? 0 : &blip_buf );
if ( ym2612.enabled() )
{
dac_synth.volume( (mask & 0x40) ? 0.0 : 0.1115 / 256 * fm_gain * gain() );
ym2612.mute_voices( mask );
}
if ( ym2413.enabled() )
{
int m = mask & 0x3F;
if ( mask & 0x20 )
m |= 0x01E0; // channels 5-8
if ( mask & 0x40 )
m |= 0x3E00;
ym2413.mute_voices( m );
}
}
}
blargg_err_t Vgm_Emu::load_mem_( byte const* new_data, long new_size )
{
assert( offsetof (header_t,unused2 [8]) == header_size );
if ( new_size <= header_size )
return gme_wrong_file_type;
header_t const& h = *(header_t const*) new_data;
RETURN_ERR( check_vgm_header( h ) );
check( get_le32( h.version ) <= 0x150 );
// psg rate
psg_rate = get_le32( h.psg_rate );
if ( !psg_rate )
psg_rate = 3579545;
blip_buf.clock_rate( psg_rate );
data = new_data;
data_end = new_data + new_size;
// get loop
loop_begin = data_end;
if ( get_le32( h.loop_offset ) )
loop_begin = &data [get_le32( h.loop_offset ) + offsetof (header_t,loop_offset)];
set_voice_count( psg.osc_count );
RETURN_ERR( setup_fm() );
static const char* const fm_names [] = {
"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG"
};
static const char* const psg_names [] = { "Square 1", "Square 2", "Square 3", "Noise" };
set_voice_names( uses_fm ? fm_names : psg_names );
// do after FM in case output buffer is changed
return Classic_Emu::setup_buffer( psg_rate );
}
blargg_err_t Vgm_Emu::setup_fm()
{
long ym2612_rate = get_le32( header().ym2612_rate );
long ym2413_rate = get_le32( header().ym2413_rate );
if ( ym2413_rate && get_le32( header().version ) < 0x110 )
update_fm_rates( &ym2413_rate, &ym2612_rate );
uses_fm = false;
fm_rate = blip_buf.sample_rate() * oversample_factor;
if ( ym2612_rate )
{
uses_fm = true;
if ( disable_oversampling_ )
fm_rate = ym2612_rate / 144.0;
Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, fm_gain * gain() );
RETURN_ERR( ym2612.set_rate( fm_rate, ym2612_rate ) );
ym2612.enable( true );
set_voice_count( 8 );
}
if ( !uses_fm && ym2413_rate )
{
uses_fm = true;
if ( disable_oversampling_ )
fm_rate = ym2413_rate / 72.0;
Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, fm_gain * gain() );
int result = ym2413.set_rate( fm_rate, ym2413_rate );
if ( result == 2 )
return "YM2413 FM sound isn't supported";
CHECK_ALLOC( !result );
ym2413.enable( true );
set_voice_count( 8 );
}
if ( uses_fm )
{
RETURN_ERR( Dual_Resampler::reset( blip_buf.length() * blip_buf.sample_rate() / 1000 ) );
psg.volume( 0.135 * fm_gain * gain() );
}
else
{
ym2612.enable( false );
ym2413.enable( false );
psg.volume( gain() );
}
return 0;
}
// Emulation
blargg_err_t Vgm_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
psg.reset( get_le16( header().noise_feedback ), header().noise_width );
dac_disabled = -1;
pos = data + header_size;
pcm_data = pos;
pcm_pos = pos;
dac_amp = -1;
vgm_time = 0;
if ( get_le32( header().version ) >= 0x150 )
{
long data_offset = get_le32( header().data_offset );
check( data_offset );
if ( data_offset )
pos += data_offset + offsetof (header_t,data_offset) - 0x40;
}
if ( uses_fm )
{
if ( ym2413.enabled() )
ym2413.reset();
if ( ym2612.enabled() )
ym2612.reset();
fm_time_offset = 0;
blip_buf.clear();
Dual_Resampler::clear();
}
return 0;
}
blargg_err_t Vgm_Emu::run_clocks( blip_time_t& time_io, int msec )
{
time_io = run_commands( msec * vgm_rate / 1000 );
psg.end_frame( time_io );
return 0;
}
blargg_err_t Vgm_Emu::play_( long count, sample_t* out )
{
if ( !uses_fm )
return Classic_Emu::play_( count, out );
Dual_Resampler::dual_play( count, out, blip_buf );
return 0;
}

84
Frameworks/GME/gme/Vgm_Emu.h Executable file
View file

@ -0,0 +1,84 @@
// Sega Master System/Mark III, Sega Genesis/Mega Drive, BBC Micro VGM music file emulator
// Game_Music_Emu 0.5.2
#ifndef VGM_EMU_H
#define VGM_EMU_H
#include "Vgm_Emu_Impl.h"
// Emulates VGM music using SN76489/SN76496 PSG, YM2612, and YM2413 FM sound chips.
// Supports custom sound buffer and frequency equalization when VGM uses just the PSG.
// FM sound chips can be run at their proper rates, or slightly higher to reduce
// aliasing on high notes. Currently YM2413 support requires that you supply a
// YM2413 sound chip emulator. I can provide one I've modified to work with the library.
class Vgm_Emu : public Vgm_Emu_Impl {
public:
// True if custom buffer and custom equalization are supported
// TODO: move into Music_Emu and rename to something like supports_custom_buffer()
bool is_classic_emu() const { return !uses_fm; }
// Disable running FM chips at higher than normal rate. Will result in slightly
// more aliasing of high notes.
void disable_oversampling( bool disable = true ) { disable_oversampling_ = disable; }
// VGM header format
enum { header_size = 0x40 };
struct header_t
{
char tag [4];
byte data_size [4];
byte version [4];
byte psg_rate [4];
byte ym2413_rate [4];
byte gd3_offset [4];
byte track_duration [4];
byte loop_offset [4];
byte loop_duration [4];
byte frame_rate [4];
byte noise_feedback [2];
byte noise_width;
byte unused1;
byte ym2612_rate [4];
byte ym2151_rate [4];
byte data_offset [4];
byte unused2 [8];
};
// Header for currently loaded file
header_t const& header() const { return *(header_t const*) data; }
static gme_type_t static_type() { return gme_vgm_type; }
public:
// deprecated
Music_Emu::load;
blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
{ return load_remaining_( &h, sizeof h, in ); }
byte const* gd3_data( int* size_out = 0 ) const; // use track_info()
public:
Vgm_Emu();
~Vgm_Emu();
protected:
blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t load_mem_( byte const*, long );
blargg_err_t set_sample_rate_( long sample_rate );
blargg_err_t start_track_( int );
blargg_err_t play_( long count, sample_t* );
blargg_err_t run_clocks( blip_time_t&, int );
void set_tempo_( double );
void mute_voices_( int mask );
void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
void update_eq( blip_eq_t const& );
private:
// removed; use disable_oversampling() and set_tempo() instead
Vgm_Emu( bool oversample, double tempo = 1.0 );
double fm_rate;
long psg_rate;
long vgm_rate;
bool disable_oversampling_;
bool uses_fm;
blargg_err_t setup_fm();
};
#endif

View file

@ -0,0 +1,314 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Vgm_Emu.h"
#include <math.h>
#include <string.h>
#include "blargg_endian.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
enum {
cmd_gg_stereo = 0x4F,
cmd_psg = 0x50,
cmd_ym2413 = 0x51,
cmd_ym2612_port0 = 0x52,
cmd_ym2612_port1 = 0x53,
cmd_ym2151 = 0x54,
cmd_delay = 0x61,
cmd_delay_735 = 0x62,
cmd_delay_882 = 0x63,
cmd_byte_delay = 0x64,
cmd_end = 0x66,
cmd_data_block = 0x67,
cmd_short_delay = 0x70,
cmd_pcm_delay = 0x80,
cmd_pcm_seek = 0xE0,
pcm_block_type = 0x00,
ym2612_dac_port = 0x2A
};
inline int command_len( int command )
{
switch ( command >> 4 )
{
case 0x03:
case 0x04:
return 2;
case 0x05:
case 0x0A:
case 0x0B:
return 3;
case 0x0C:
case 0x0D:
return 4;
case 0x0E:
case 0x0F:
return 5;
}
check( false );
return 1;
}
template<class Emu>
inline void Ym_Emu<Emu>::begin_frame( short* p )
{
require( enabled() );
out = p;
last_time = 0;
}
template<class Emu>
inline int Ym_Emu<Emu>::run_until( int time )
{
int count = time - last_time;
if ( count > 0 )
{
if ( last_time < 0 )
return false;
last_time = time;
short* p = out;
out += count * Emu::out_chan_count;
Emu::run( count, p );
}
return true;
}
inline Vgm_Emu_Impl::fm_time_t Vgm_Emu_Impl::to_fm_time( vgm_time_t t ) const
{
return (t * fm_time_factor + fm_time_offset) >> fm_time_bits;
}
inline blip_time_t Vgm_Emu_Impl::to_blip_time( vgm_time_t t ) const
{
return (t * blip_time_factor) >> blip_time_bits;
}
void Vgm_Emu_Impl::write_pcm( vgm_time_t vgm_time, int amp )
{
blip_time_t blip_time = to_blip_time( vgm_time );
int old = dac_amp;
int delta = amp - old;
dac_amp = amp;
if ( old >= 0 )
dac_synth.offset_inline( blip_time, delta, &blip_buf );
else
dac_amp |= dac_disabled;
}
blip_time_t Vgm_Emu_Impl::run_commands( vgm_time_t end_time )
{
vgm_time_t vgm_time = this->vgm_time;
byte const* pos = this->pos;
if ( pos >= data_end )
{
set_track_ended();
if ( pos > data_end )
set_warning( "Stream lacked end event" );
}
while ( vgm_time < end_time && pos < data_end )
{
// TODO: be sure there are enough bytes left in stream for particular command
// so we don't read past end
switch ( *pos++ )
{
case cmd_end:
pos = loop_begin; // if not looped, loop_begin == data_end
break;
case cmd_delay_735:
vgm_time += 735;
break;
case cmd_delay_882:
vgm_time += 882;
break;
case cmd_gg_stereo:
psg.write_ggstereo( to_blip_time( vgm_time ), *pos++ );
break;
case cmd_psg:
psg.write_data( to_blip_time( vgm_time ), *pos++ );
break;
case cmd_delay:
vgm_time += pos [1] * 0x100L + pos [0];
pos += 2;
break;
case cmd_byte_delay:
vgm_time += *pos++;
break;
case cmd_ym2413:
if ( ym2413.run_until( to_fm_time( vgm_time ) ) )
ym2413.write( pos [0], pos [1] );
pos += 2;
break;
case cmd_ym2612_port0:
if ( pos [0] == ym2612_dac_port )
{
write_pcm( vgm_time, pos [1] );
}
else if ( ym2612.run_until( to_fm_time( vgm_time ) ) )
{
if ( pos [0] == 0x2B )
{
dac_disabled = (pos [1] >> 7 & 1) - 1;
dac_amp |= dac_disabled;
}
ym2612.write0( pos [0], pos [1] );
}
pos += 2;
break;
case cmd_ym2612_port1:
if ( ym2612.run_until( to_fm_time( vgm_time ) ) )
ym2612.write1( pos [0], pos [1] );
pos += 2;
break;
case cmd_data_block: {
check( *pos == cmd_end );
int type = pos [1];
long size = get_le32( pos + 2 );
pos += 6;
if ( type == pcm_block_type )
pcm_data = pos;
pos += size;
break;
}
case cmd_pcm_seek:
pcm_pos = pcm_data + pos [3] * 0x1000000L + pos [2] * 0x10000L +
pos [1] * 0x100L + pos [0];
pos += 4;
break;
default:
int cmd = pos [-1];
switch ( cmd & 0xF0 )
{
case cmd_pcm_delay:
write_pcm( vgm_time, *pcm_pos++ );
vgm_time += cmd & 0x0F;
break;
case cmd_short_delay:
vgm_time += (cmd & 0x0F) + 1;
break;
case 0x50:
pos += 2;
break;
default:
pos += command_len( cmd ) - 1;
set_warning( "Unknown stream event" );
}
}
}
vgm_time -= end_time;
this->pos = pos;
this->vgm_time = vgm_time;
return to_blip_time( end_time );
}
int Vgm_Emu_Impl::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf )
{
// to do: timing is working mostly by luck
int min_pairs = sample_count >> 1;
int vgm_time = ((long) min_pairs << fm_time_bits) / fm_time_factor - 1;
assert( to_fm_time( vgm_time ) <= min_pairs );
int pairs = min_pairs;
while ( (pairs = to_fm_time( vgm_time )) < min_pairs )
vgm_time++;
//dprintf( "pairs: %d, min_pairs: %d\n", pairs, min_pairs );
if ( ym2612.enabled() )
{
ym2612.begin_frame( buf );
memset( buf, 0, pairs * stereo * sizeof *buf );
}
else if ( ym2413.enabled() )
{
ym2413.begin_frame( buf );
}
run_commands( vgm_time );
ym2612.run_until( pairs );
ym2413.run_until( pairs );
fm_time_offset = (vgm_time * fm_time_factor + fm_time_offset) -
((long) pairs << fm_time_bits);
psg.end_frame( blip_time );
return pairs * stereo;
}
// Update pre-1.10 header FM rates by scanning commands
void Vgm_Emu_Impl::update_fm_rates( long* ym2413_rate, long* ym2612_rate ) const
{
byte const* p = data + 0x40;
while ( p < data_end )
{
switch ( *p )
{
case cmd_end:
return;
case cmd_psg:
case cmd_byte_delay:
p += 2;
break;
case cmd_delay:
p += 3;
break;
case cmd_data_block:
p += 7 + get_le32( p + 3 );
break;
case cmd_ym2413:
*ym2612_rate = 0;
return;
case cmd_ym2612_port0:
case cmd_ym2612_port1:
*ym2612_rate = *ym2413_rate;
*ym2413_rate = 0;
return;
case cmd_ym2151:
*ym2413_rate = 0;
*ym2612_rate = 0;
return;
default:
p += command_len( *p );
}
}
}

View file

@ -0,0 +1,71 @@
// Low-level parts of Vgm_Emu
// Game_Music_Emu 0.5.2
#ifndef VGM_EMU_IMPL_H
#define VGM_EMU_IMPL_H
#include "Dual_Resampler.h"
#include "Classic_Emu.h"
#include "Ym2413_Emu.h"
#include "Ym2612_Emu.h"
#include "Sms_Apu.h"
template<class Emu>
class Ym_Emu : public Emu {
protected:
int last_time;
short* out;
enum { disabled_time = -1 };
public:
Ym_Emu() : last_time( disabled_time ), out( NULL ) { }
void enable( bool b ) { last_time = b ? 0 : disabled_time; }
bool enabled() const { return last_time != disabled_time; }
void begin_frame( short* p );
int run_until( int time );
};
class Vgm_Emu_Impl : public Classic_Emu, private Dual_Resampler {
public:
typedef Classic_Emu::sample_t sample_t;
protected:
enum { stereo = 2 };
typedef int vgm_time_t;
enum { fm_time_bits = 12 };
typedef int fm_time_t;
long fm_time_offset;
int fm_time_factor;
fm_time_t to_fm_time( vgm_time_t ) const;
enum { blip_time_bits = 12 };
int blip_time_factor;
blip_time_t to_blip_time( vgm_time_t ) const;
byte const* data;
byte const* loop_begin;
byte const* data_end;
void update_fm_rates( long* ym2413_rate, long* ym2612_rate ) const;
vgm_time_t vgm_time;
byte const* pos;
blip_time_t run_commands( vgm_time_t );
int play_frame( blip_time_t blip_time, int sample_count, sample_t* buf );
byte const* pcm_data;
byte const* pcm_pos;
int dac_amp;
int dac_disabled; // -1 if disabled
void write_pcm( vgm_time_t, int amp );
Ym_Emu<Ym2612_Emu> ym2612;
Ym_Emu<Ym2413_Emu> ym2413;
Blip_Buffer blip_buf;
Sms_Apu psg;
Blip_Synth<blip_med_quality,1> dac_synth;
friend class Vgm_Emu;
};
#endif

View file

@ -0,0 +1,21 @@
// Use in place of Ym2413_Emu.cpp and ym2413.c to disable support for this chip
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Ym2413_Emu.h"
Ym2413_Emu::Ym2413_Emu() { }
Ym2413_Emu::~Ym2413_Emu() { }
int Ym2413_Emu::set_rate( double, double ) { return 2; }
void Ym2413_Emu::reset() { }
void Ym2413_Emu::write( int, int ) { }
void Ym2413_Emu::mute_voices( int ) { }
void Ym2413_Emu::run( int, sample_t* ) { }

33
Frameworks/GME/gme/Ym2413_Emu.h Executable file
View file

@ -0,0 +1,33 @@
// YM2413 FM sound chip emulator interface
// Game_Music_Emu 0.5.2
#ifndef YM2413_EMU_H
#define YM2413_EMU_H
class Ym2413_Emu {
struct OPLL* opll;
public:
Ym2413_Emu();
~Ym2413_Emu();
// Set output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( double sample_rate, double clock_rate );
// Reset to power-up state
void reset();
// Mute voice n if bit n (1 << n) of mask is set
enum { channel_count = 14 };
void mute_voices( int mask );
// Write 'data' to 'addr'
void write( int addr, int data );
// Run and write pair_count samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

1319
Frameworks/GME/gme/Ym2612_Emu.cpp Executable file

File diff suppressed because it is too large Load diff

38
Frameworks/GME/gme/Ym2612_Emu.h Executable file
View file

@ -0,0 +1,38 @@
// YM2612 FM sound chip emulator interface
// Game_Music_Emu 0.5.2
#ifndef YM2612_EMU_H
#define YM2612_EMU_H
struct Ym2612_Impl;
class Ym2612_Emu {
Ym2612_Impl* impl;
public:
Ym2612_Emu() { impl = 0; }
~Ym2612_Emu();
// Set output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
const char* set_rate( double sample_rate, double clock_rate );
// Reset to power-up state
void reset();
// Mute voice n if bit n (1 << n) of mask is set
enum { channel_count = 6 };
void mute_voices( int mask );
// Write addr to register 0 then data to register 1
void write0( int addr, int data );
// Write addr to register 2 then data to register 3
void write1( int addr, int data );
// Run and add pair_count samples into current output buffer contents
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -0,0 +1,175 @@
// Sets up common environment for Shay Green's libraries.
// To change configuration options, modify blargg_config.h, not this file.
#ifndef BLARGG_COMMON_H
#define BLARGG_COMMON_H
#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#undef BLARGG_COMMON_H
// allow blargg_config.h to #include blargg_common.h
#include "blargg_config.h"
#ifndef BLARGG_COMMON_H
#define BLARGG_COMMON_H
// STATIC_CAST(T,expr): Used in place of static_cast<T> (expr)
#ifndef STATIC_CAST
#define STATIC_CAST(T,expr) ((T) (expr))
#endif
// blargg_err_t (0 on success, otherwise error string)
#ifndef blargg_err_t
typedef const char* blargg_err_t;
#endif
// blargg_vector - very lightweight vector of POD types (no constructor/destructor)
template<class T>
class blargg_vector {
T* begin_;
size_t size_;
public:
blargg_vector() : begin_( 0 ), size_( 0 ) { }
~blargg_vector() { free( begin_ ); }
size_t size() const { return size_; }
T* begin() const { return begin_; }
T* end() const { return begin_ + size_; }
blargg_err_t resize( size_t n )
{
void* p = realloc( begin_, n * sizeof (T) );
if ( !p && n )
return "Out of memory";
begin_ = (T*) p;
size_ = n;
return 0;
}
void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); }
T& operator [] ( size_t n ) const
{
assert( n <= size_ ); // <= to allow past-the-end value
return begin_ [n];
}
};
#ifndef BLARGG_DISABLE_NOTHROW
#if __cplusplus < 199711
#define BLARGG_THROWS( spec )
#else
#define BLARGG_THROWS( spec ) throw spec
#endif
#define BLARGG_DISABLE_NOTHROW \
void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\
void operator delete ( void* p ) { free( p ); }
#define BLARGG_NEW new
#else
#include <new>
#define BLARGG_NEW new (std::nothrow)
#endif
#define BLARGG_4CHAR( a, b, c, d ) \
((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF))
// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0.
#ifndef BOOST_STATIC_ASSERT
#ifdef _MSC_VER
// MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified
#define BOOST_STATIC_ASSERT( expr ) \
void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] )
#else
// Some other compilers fail when declaring same function multiple times in class,
// so differentiate them by line
#define BOOST_STATIC_ASSERT( expr ) \
void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] )
#endif
#endif
// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1,
// compiler is assumed to support bool. If undefined, availability is determined.
#ifndef BLARGG_COMPILER_HAS_BOOL
#if defined (__MWERKS__)
#if !__option(bool)
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#elif defined (_MSC_VER)
#if _MSC_VER < 1100
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#elif defined (__GNUC__)
// supports bool
#elif __cplusplus < 199711
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#endif
#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL
// If you get errors here, modify your blargg_config.h file
typedef int bool;
const bool true = 1;
const bool false = 0;
#endif
// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough
#include <limits.h>
#if INT_MAX >= 0x7FFFFFFF
typedef int blargg_long;
#else
typedef long blargg_long;
#endif
#if UINT_MAX >= 0xFFFFFFFF
typedef unsigned blargg_ulong;
#else
typedef unsigned long blargg_ulong;
#endif
// BOOST::int8_t etc.
// HAVE_STDINT_H: If defined, use <stdint.h> for int8_t etc.
#if defined (HAVE_STDINT_H)
#include <stdint.h>
#define BOOST
// HAVE_INTTYPES_H: If defined, use <stdint.h> for int8_t etc.
#elif defined (HAVE_INTTYPES_H)
#include <inttypes.h>
#define BOOST
#else
struct BOOST
{
#if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F
typedef signed char int8_t;
typedef unsigned char uint8_t;
#else
// No suitable 8-bit type available
typedef struct see_blargg_common_h int8_t;
typedef struct see_blargg_common_h uint8_t;
#endif
#if USHRT_MAX == 0xFFFF
typedef short int16_t;
typedef unsigned short uint16_t;
#else
// No suitable 16-bit type available
typedef struct see_blargg_common_h int16_t;
typedef struct see_blargg_common_h uint16_t;
#endif
#if ULONG_MAX == 0xFFFFFFFF
typedef long int32_t;
typedef unsigned long uint32_t;
#elif UINT_MAX == 0xFFFFFFFF
typedef int int32_t;
typedef unsigned int uint32_t;
#else
// No suitable 32-bit type available
typedef struct see_blargg_common_h int32_t;
typedef struct see_blargg_common_h uint32_t;
#endif
};
#endif
#endif
#endif

View file

@ -0,0 +1,30 @@
// Library configuration. Modify this file as necessary.
#ifndef BLARGG_CONFIG_H
#define BLARGG_CONFIG_H
// Uncomment to use zlib for transparent decompression of gzipped files
//#define HAVE_ZLIB_H
// Uncomment to support only the listed game music types. See gme_type_list.cpp
// for a list of all types.
//#define GME_TYPE_LIST gme_nsf_type, gme_gbs_type
// Uncomment to enable platform-specific optimizations
//#define BLARGG_NONPORTABLE 1
// Uncomment to use faster, lower quality sound synthesis
//#define BLIP_BUFFER_FAST 1
// Uncomment if automatic byte-order determination doesn't work
//#define BLARGG_BIG_ENDIAN 1
// Uncomment if you get errors in the bool section of blargg_common.h
//#define BLARGG_COMPILER_HAS_BOOL 1
// Use standard config.h if present
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#endif

View file

@ -0,0 +1,158 @@
// CPU Byte Order Utilities
// Game_Music_Emu 0.5.2
#ifndef BLARGG_ENDIAN
#define BLARGG_ENDIAN
#include "blargg_common.h"
// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16)
#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \
defined (__x86_64__) || defined (__ia64__) || defined (__i386__)
#define BLARGG_CPU_X86 1
#define BLARGG_CPU_CISC 1
#endif
#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc)
#define BLARGG_CPU_POWERPC 1
#endif
// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only
// one may be #defined to 1. Only needed if something actually depends on byte order.
#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN)
#ifdef __GLIBC__
// GCC handles this for us
#include <endian.h>
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define BLARGG_LITTLE_ENDIAN 1
#elif __BYTE_ORDER == __BIG_ENDIAN
#define BLARGG_BIG_ENDIAN 1
#endif
#else
#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \
(defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234)
#define BLARGG_LITTLE_ENDIAN 1
#endif
#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \
defined (__mips__) || defined (__sparc__) || BLARGG_CPU_POWERPC || \
(defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321)
#define BLARGG_BIG_ENDIAN 1
#else
// No endian specified; assume little-endian, since it's most common
#define BLARGG_LITTLE_ENDIAN 1
#endif
#endif
#endif
#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN
#undef BLARGG_LITTLE_ENDIAN
#undef BLARGG_BIG_ENDIAN
#endif
inline void blargg_verify_byte_order()
{
#ifndef NDEBUG
#if BLARGG_BIG_ENDIAN
volatile int i = 1;
assert( *(volatile char*) &i == 0 );
#elif BLARGG_LITTLE_ENDIAN
volatile int i = 1;
assert( *(volatile char*) &i != 0 );
#endif
#endif
}
inline unsigned get_le16( void const* p ) {
return ((unsigned char const*) p) [1] * 0x100u +
((unsigned char const*) p) [0];
}
inline unsigned get_be16( void const* p ) {
return ((unsigned char const*) p) [0] * 0x100u +
((unsigned char const*) p) [1];
}
inline blargg_ulong get_le32( void const* p ) {
return ((unsigned char const*) p) [3] * 0x01000000u +
((unsigned char const*) p) [2] * 0x00010000u +
((unsigned char const*) p) [1] * 0x00000100u +
((unsigned char const*) p) [0];
}
inline blargg_ulong get_be32( void const* p ) {
return ((unsigned char const*) p) [0] * 0x01000000u +
((unsigned char const*) p) [1] * 0x00010000u +
((unsigned char const*) p) [2] * 0x00000100u +
((unsigned char const*) p) [3];
}
inline void set_le16( void* p, unsigned n ) {
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
((unsigned char*) p) [0] = (unsigned char) n;
}
inline void set_be16( void* p, unsigned n ) {
((unsigned char*) p) [0] = (unsigned char) (n >> 8);
((unsigned char*) p) [1] = (unsigned char) n;
}
inline void set_le32( void* p, blargg_ulong n ) {
((unsigned char*) p) [3] = (unsigned char) (n >> 24);
((unsigned char*) p) [2] = (unsigned char) (n >> 16);
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
((unsigned char*) p) [0] = (unsigned char) n;
}
inline void set_be32( void* p, blargg_ulong n ) {
((unsigned char*) p) [0] = (unsigned char) (n >> 24);
((unsigned char*) p) [1] = (unsigned char) (n >> 16);
((unsigned char*) p) [2] = (unsigned char) (n >> 8);
((unsigned char*) p) [3] = (unsigned char) n;
}
#if BLARGG_NONPORTABLE
// Optimized implementation if byte order is known
#if BLARGG_LITTLE_ENDIAN
#define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr))
#define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr))
#define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
#define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
#elif BLARGG_BIG_ENDIAN
#define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr))
#define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr))
#define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
#define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
#endif
#if BLARGG_CPU_POWERPC && defined (__MWERKS__)
// PowerPC has special byte-reversed instructions
// to do: assumes that PowerPC is running in big-endian mode
// to do: implement for other compilers which don't support these macros
#define GET_LE16( addr ) (__lhbrx( (addr), 0 ))
#define GET_LE32( addr ) (__lwbrx( (addr), 0 ))
#define SET_LE16( addr, data ) (__sthbrx( (data), (addr), 0 ))
#define SET_LE32( addr, data ) (__stwbrx( (data), (addr), 0 ))
#endif
#endif
#ifndef GET_LE16
#define GET_LE16( addr ) get_le16( addr )
#define GET_LE32( addr ) get_le32( addr )
#define SET_LE16( addr, data ) set_le16( addr, data )
#define SET_LE32( addr, data ) set_le32( addr, data )
#endif
#ifndef GET_BE16
#define GET_BE16( addr ) get_be16( addr )
#define GET_BE32( addr ) get_be32( addr )
#define SET_BE16( addr, data ) set_be16( addr, data )
#define SET_BE32( addr, data ) set_be32( addr, data )
#endif
// auto-selecting versions
inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); }
inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); }
inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); }
inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); }
inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); }
inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); }
inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); }
inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); }
#endif

View file

@ -0,0 +1,78 @@
// Included at the beginning of library source files, after all other #include lines
#ifndef BLARGG_SOURCE_H
#define BLARGG_SOURCE_H
// If debugging is enabled, abort program if expr is false. Meant for checking
// internal state and consistency. A failed assertion indicates a bug in the module.
// void assert( bool expr );
#include <assert.h>
// If debugging is enabled and expr is false, abort program. Meant for checking
// caller-supplied parameters and operations that are outside the control of the
// module. A failed requirement indicates a bug outside the module.
// void require( bool expr );
#undef require
#define require( expr ) assert( expr )
// Like printf() except output goes to debug log file. Might be defined to do
// nothing (not even evaluate its arguments).
// void dprintf( const char* format, ... );
inline void blargg_dprintf_( const char*, ... ) { }
#undef dprintf
#define dprintf (1) ? (void) 0 : blargg_dprintf_
// If enabled, evaluate expr and if false, make debug log entry with source file
// and line. Meant for finding situations that should be examined further, but that
// don't indicate a problem. In all cases, execution continues normally.
#undef check
#define check( expr ) ((void) 0)
// If expr yields error string, return it from current function, otherwise continue.
#undef RETURN_ERR
#define RETURN_ERR( expr ) do { \
blargg_err_t blargg_return_err_ = (expr); \
if ( blargg_return_err_ ) return blargg_return_err_; \
} while ( 0 )
// If ptr is 0, return out of memory error string.
#undef CHECK_ALLOC
#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 )
// Avoid any macros which evaluate their arguments multiple times
#undef min
#undef max
// using const references generates crappy code, and I am currenly only using these
// for built-in types, so they take arguments by value
template<class T>
inline T min( T x, T y )
{
if ( x < y )
return x;
return y;
}
template<class T>
inline T max( T x, T y )
{
if ( x < y )
return y;
return x;
}
// TODO: good idea? bad idea?
#undef byte
#define byte byte_
typedef unsigned char byte;
// deprecated
#define BLARGG_CHECK_ALLOC CHECK_ALLOC
#define BLARGG_RETURN_ERR RETURN_ERR
// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check
#ifdef BLARGG_SOURCE_BEGIN
#include BLARGG_SOURCE_BEGIN
#endif
#endif

72
Frameworks/GME/gme/gb_cpu_io.h Executable file
View file

@ -0,0 +1,72 @@
#include "Gbs_Emu.h"
#include "blargg_source.h"
int Gbs_Emu::cpu_read( gb_addr_t addr )
{
int result = *cpu::get_code( addr );
if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count )
result = apu.read_register( clock(), addr );
#ifndef NDEBUG
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
dprintf( "Read from unmapped memory $%.4x\n", (unsigned) addr );
else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 )
dprintf( "Unhandled I/O read 0x%4X\n", (unsigned) addr );
#endif
return result;
}
void Gbs_Emu::cpu_write( gb_addr_t addr, int data )
{
unsigned offset = addr - ram_addr;
if ( offset <= 0xFFFF - ram_addr )
{
ram [offset] = data;
if ( (addr ^ 0xE000) <= 0x1F80 - 1 )
{
if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count )
{
GME_APU_HOOK( this, addr - Gb_Apu::start_addr, data );
apu.write_register( clock(), addr, data );
}
else if ( (addr ^ 0xFF06) < 2 )
update_timer();
else if ( addr == joypad_addr )
ram [offset] = 0; // keep joypad return value 0
else
ram [offset] = 0xFF;
//if ( addr == 0xFFFF )
// dprintf( "Wrote interrupt mask\n" );
}
}
else if ( (addr ^ 0x2000) <= 0x2000 - 1 )
{
set_bank( data );
}
#ifndef NDEBUG
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
{
dprintf( "Wrote to unmapped memory $%.4x\n", (unsigned) addr );
}
#endif
}
#define CPU_READ_FAST( cpu, addr, time, out ) \
CPU_READ_FAST_( STATIC_CAST(Gbs_Emu*,cpu), addr, time, out )
#define CPU_READ_FAST_( emu, addr, time, out ) \
{\
out = READ_PROG( addr );\
if ( unsigned (addr - Gb_Apu::start_addr) <= Gb_Apu::register_count )\
out = emu->apu.read_register( emu->cpu_time - time * clocks_per_instr, addr );\
else\
check( out == emu->cpu_read( addr ) );\
}
#define CPU_READ( cpu, addr, time ) \
STATIC_CAST(Gbs_Emu*,cpu)->cpu_read( addr )
#define CPU_WRITE( cpu, addr, data, time ) \
STATIC_CAST(Gbs_Emu*,cpu)->cpu_write( addr, data )

256
Frameworks/GME/gme/gme.cpp Executable file
View file

@ -0,0 +1,256 @@
// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
#include "Music_Emu.h"
#if !GME_DISABLE_STEREO_DEPTH
#include "Effects_Buffer.h"
#endif
#include "blargg_endian.h"
#include <string.h>
#include <ctype.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifndef GME_TYPE_LIST
// Default list of all supported game music types (copy this to blargg_config.h
// if you want to modify it)
#define GME_TYPE_LIST \
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
#endif
static gme_type_t const gme_type_list_ [] = { GME_TYPE_LIST, 0 };
gme_type_t const* gme_type_list()
{
return gme_type_list_;
}
const char* gme_identify_header( void const* header )
{
switch ( get_be32( header ) )
{
case BLARGG_4CHAR('Z','X','A','Y'): return "AY";
case BLARGG_4CHAR('G','B','S',0x01): return "GBS";
case BLARGG_4CHAR('G','Y','M','X'): return "GYM";
case BLARGG_4CHAR('H','E','S','M'): return "HES";
case BLARGG_4CHAR('K','S','C','C'):
case BLARGG_4CHAR('K','S','S','X'): return "KSS";
case BLARGG_4CHAR('N','E','S','M'): return "NSF";
case BLARGG_4CHAR('N','S','F','E'): return "NSFE";
case BLARGG_4CHAR('S','A','P',0x0D): return "SAP";
case BLARGG_4CHAR('S','N','E','S'): return "SPC";
case BLARGG_4CHAR('V','g','m',' '): return "VGM";
}
return "";
}
static void to_uppercase( const char* in, int len, char* out )
{
for ( int i = 0; i < len; i++ )
{
if ( !(out [i] = toupper( in [i] )) )
return;
}
*out = 0; // extension too long
}
gme_type_t gme_identify_extension( const char* extension_ )
{
char const* end = strrchr( extension_, '.' );
if ( end )
extension_ = end + 1;
char extension [6];
to_uppercase( extension_, sizeof extension, extension );
for ( gme_type_t const* types = gme_type_list_; *types; types++ )
if ( !strcmp( extension, (*types)->extension_ ) )
return *types;
return 0;
}
gme_err_t gme_identify_file( const char* path, gme_type_t* type_out )
{
*type_out = gme_identify_extension( path );
// TODO: don't examine header if file has extension?
if ( !*type_out )
{
char header [4];
GME_FILE_READER in;
RETURN_ERR( in.open( path ) );
RETURN_ERR( in.read( header, sizeof header ) );
*type_out = gme_identify_extension( gme_identify_header( header ) );
}
return 0;
}
gme_err_t gme_open_data( void const* data, long size, Music_Emu** out, long sample_rate )
{
require( (data || !size) && out );
*out = 0;
gme_type_t file_type = 0;
if ( size >= 4 )
file_type = gme_identify_extension( gme_identify_header( data ) );
if ( !file_type )
return gme_wrong_file_type;
Music_Emu* emu = gme_new_emu( file_type, sample_rate );
CHECK_ALLOC( emu );
gme_err_t err = gme_load_data( emu, data, size );
if ( err )
delete emu;
else
*out = emu;
return err;
}
gme_err_t gme_open_file( const char* path, Music_Emu** out, long sample_rate )
{
require( path && out );
*out = 0;
GME_FILE_READER in;
RETURN_ERR( in.open( path ) );
char header [4];
int header_size = 0;
gme_type_t file_type = gme_identify_extension( path );
if ( !file_type )
{
header_size = sizeof header;
RETURN_ERR( in.read( header, sizeof header ) );
file_type = gme_identify_extension( gme_identify_header( header ) );
}
if ( !file_type )
return gme_wrong_file_type;
Music_Emu* emu = gme_new_emu( file_type, sample_rate );
CHECK_ALLOC( emu );
// optimization: avoids seeking/re-reading header
Remaining_Reader rem( header, header_size, &in );
gme_err_t err = emu->load( rem );
in.close();
if ( err )
delete emu;
else
*out = emu;
return err;
}
Music_Emu* gme_new_emu( gme_type_t type, long rate )
{
if ( type )
{
if ( rate == gme_info_only )
return type->new_info();
Music_Emu* me = type->new_emu();
if ( me )
{
#if !GME_DISABLE_STEREO_DEPTH
if ( type->flags_ & 1 )
{
me->effects_buffer = BLARGG_NEW Effects_Buffer;
if ( me->effects_buffer )
me->set_buffer( me->effects_buffer );
}
if ( !(type->flags_ & 1) || me->effects_buffer )
#endif
{
if ( !me->set_sample_rate( rate ) )
{
check( me->type() == type );
return me;
}
}
delete me;
}
}
return 0;
}
gme_err_t gme_load_file( Music_Emu* me, const char* path ) { return me->load_file( path ); }
gme_err_t gme_load_data( Music_Emu* me, void const* data, long size )
{
Mem_File_Reader in( data, size );
return me->load( in );
}
gme_err_t gme_load_custom( Music_Emu* me, gme_reader_t func, long size, void* data )
{
Callback_Reader in( func, size, data );
return me->load( in );
}
void gme_delete( Music_Emu* me ) { delete me; }
gme_type_t gme_type( Music_Emu const* me ) { return me->type(); }
const char* gme_warning( Music_Emu* me ) { return me->warning(); }
int gme_track_count( Music_Emu const* me ) { return me->track_count(); }
const char* gme_track_info( Music_Emu const* me, track_info_t* out, int track )
{
return me->track_info( out, track );
}
void gme_set_stereo_depth( Music_Emu* me, double depth )
{
#if !GME_DISABLE_STEREO_DEPTH
if ( me->effects_buffer )
STATIC_CAST(Effects_Buffer*,me->effects_buffer)->set_depth( depth );
#endif
}
void* gme_user_data ( Music_Emu const* me ) { return me->user_data(); }
void gme_set_user_data ( Music_Emu* me, void* new_user_data ) { me->set_user_data( new_user_data ); }
void gme_set_user_cleanup(Music_Emu* me, gme_user_cleanup_t func ) { me->set_user_cleanup( func ); }
gme_err_t gme_start_track ( Music_Emu* me, int index ) { return me->start_track( index ); }
gme_err_t gme_play ( Music_Emu* me, long n, short* p ) { return me->play( n, p ); }
void gme_set_fade ( Music_Emu* me, long start_msec ) { me->set_fade( start_msec ); }
int gme_track_ended ( Music_Emu const* me ) { return me->track_ended(); }
long gme_tell ( Music_Emu const* me ) { return me->tell(); }
gme_err_t gme_seek ( Music_Emu* me, long msec ) { return me->seek( msec ); }
int gme_voice_count ( Music_Emu const* me ) { return me->voice_count(); }
void gme_ignore_silence ( Music_Emu* me, int disable ) { me->ignore_silence( disable != 0 ); }
void gme_set_tempo ( Music_Emu* me, double t ) { me->set_tempo( t ); }
void gme_mute_voice ( Music_Emu* me, int index, int mute ) { me->mute_voice( index, mute != 0 ); }
void gme_mute_voices ( Music_Emu* me, int mask ) { me->mute_voices( mask ); }
void gme_set_equalizer ( Music_Emu* me, gme_equalizer_t const* eq ) { me->set_equalizer( *eq ); }
gme_equalizer_t gme_equalizer( Music_Emu const* me ) { return me->equalizer(); }
const char** gme_voice_names ( Music_Emu const* me ) { return me->voice_names(); }

222
Frameworks/GME/gme/gme.h Executable file
View file

@ -0,0 +1,222 @@
/* Game music emulator library C interface (also usable from C++) */
/* Game_Music_Emu 0.5.2 */
#ifndef GME_H
#define GME_H
#ifdef __cplusplus
extern "C" {
#endif
/* Error string returned by library functions, or NULL if no error (success) */
typedef const char* gme_err_t;
/* First parameter of most gme_ functions is a pointer to the Music_Emu */
typedef struct Music_Emu Music_Emu;
/******** Basic operations ********/
/* Create emulator and load game music file/data into it. Sets *out to new emulator. */
gme_err_t gme_open_file( const char* path, Music_Emu** out, long sample_rate );
/* Number of tracks available */
int gme_track_count( Music_Emu const* );
/* Start a track, where 0 is the first track */
gme_err_t gme_start_track( Music_Emu*, int index );
/* Generate 'count' 16-bit signed samples info 'out'. Output is in stereo. */
gme_err_t gme_play( Music_Emu*, long count, short* out );
/* Finish using emulator and free memory */
void gme_delete( Music_Emu* );
/******** Track position/length ********/
/* Set time to start fading track out. Once fade ends track_ended() returns true.
Fade time can be changed while track is playing. */
void gme_set_fade( Music_Emu*, long start_msec );
/* True if a track has reached its end */
int gme_track_ended( Music_Emu const* );
/* Number of milliseconds (1000 = one second) played since beginning of track */
long gme_tell( Music_Emu const* );
/* Seek to new time in track. Seeking backwards or far forward can take a while. */
gme_err_t gme_seek( Music_Emu*, long msec );
/******** Informational ********/
/* If you only need track information from a music file, pass gme_info_only for
sample_rate to open/load. */
enum { gme_info_only = -1 };
/* Most recent warning string, or NULL if none. Clears current warning after returning.
Warning is also cleared when loading a file and starting a track. */
const char* gme_warning( Music_Emu* );
/* Load m3u playlist file (must be done after loading music) */
gme_err_t gme_load_m3u( Music_Emu*, const char* path );
/* Clear any loaded m3u playlist and any internal playlist that the music format
supports (NSFE for example). */
void gme_clear_playlist( Music_Emu* );
/* Get information for a particular track (length, name, author, etc.) */
typedef struct track_info_t track_info_t;
gme_err_t gme_track_info( Music_Emu const*, track_info_t* out, int track );
struct track_info_t
{
long track_count;
/* times in milliseconds; -1 if unknown */
long length;
long intro_length;
long loop_length;
/* empty string if not available */
char system [256];
char game [256];
char song [256];
char author [256];
char copyright [256];
char comment [256];
char dumper [256];
};
enum { gme_max_field = 255 };
/******** 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 */
void gme_set_stereo_depth( Music_Emu*, double depth );
/* Disable automatic end-of-track detection and skipping of silence at beginning
if ignore is true */
void gme_ignore_silence( Music_Emu*, int ignore );
/* Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed.
Track length as returned by track_info() assumes a tempo of 1.0. */
void gme_set_tempo( Music_Emu*, double tempo );
/* Number of voices used by currently loaded file */
int gme_voice_count( Music_Emu const* );
/* Names of voices */
const char** gme_voice_names( Music_Emu const* );
/* Mute/unmute voice i, where voice 0 is first voice */
void gme_mute_voice( Music_Emu*, int index, int mute );
/* Set muting state of all voices at once using a bit mask, where -1 mutes all
voices, 0 unmutes them all, 0x01 mutes just the first voice, etc. */
void gme_mute_voices( Music_Emu*, int muting_mask );
/* Frequency equalizer parameters (see gme.txt) */
typedef struct gme_equalizer_t
{
double treble; /* -50.0 = muffled, 0 = flat, +5.0 = extra-crisp */
long bass; /* 1 = full bass, 90 = average, 16000 = almost no bass */
} gme_equalizer_t;
/* Get current frequency equalizater parameters */
gme_equalizer_t gme_equalizer( Music_Emu const* );
/* Change frequency equalizer parameters */
void gme_set_equalizer( Music_Emu*, gme_equalizer_t const* eq );
/******** Game music types ********/
/* gme_type_t is a pointer to this structure. For example, gme_nsf_type->system is
"Nintendo NES" and gme_nsf_type->new_emu() is equilvant to new Nsf_Emu (in C++). */
typedef struct gme_type_t_ const* gme_type_t;
struct gme_type_t_
{
const char* system; /* name of system this music file type is generally for */
int track_count; /* non-zero for formats with a fixed number of tracks */
Music_Emu* (*new_emu)(); /* Create new emulator for this type (useful in C++ only) */
Music_Emu* (*new_info)(); /* Create new info reader for this type */
/* internal */
const char* extension_;
int flags_;
};
/* Emulator type constants for each supported file type */
extern struct gme_type_t_ const 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 [];
/* Type of this emulator */
gme_type_t gme_type( Music_Emu const* );
/* 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. */
gme_type_t const* gme_type_list();
/******** Advanced file loading ********/
/* Error returned if file type is not supported */
extern const char gme_wrong_file_type [];
/* Same as gme_open_file(), but uses file data already in memory. Makes copy of data. */
gme_err_t gme_open_data( void const* data, long size, Music_Emu** out, long sample_rate );
/* 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. */
const char* gme_identify_header( void const* header );
/* Get corresponding music type for file path or extension passed in. */
gme_type_t gme_identify_extension( const char* path_or_extension );
/* Determine file type based on file's extension or header (if extension isn't recognized).
Sets *type_out to type, or 0 if unrecognized or error. */
gme_err_t gme_identify_file( const char* path, gme_type_t* type_out );
/* 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. */
Music_Emu* gme_new_emu( gme_type_t, long sample_rate );
/* Load music file into emulator */
gme_err_t gme_load_file( Music_Emu*, const char* path );
/* Load music file from memory into emulator. Makes a copy of data passed. */
gme_err_t gme_load_data( Music_Emu*, void const* data, long size );
/* Load music file using custom data reader function that will be called to
read file data. Most emulators load the entire file in one read call. */
typedef gme_err_t (*gme_reader_t)( void* your_data, void* out, long count );
gme_err_t gme_load_custom( Music_Emu*, gme_reader_t, long file_size, void* your_data );
/* Load m3u playlist file from memory (must be done after loading music) */
gme_err_t gme_load_m3u_data( Music_Emu*, void const* data, long size );
/******** User data ********/
/* Set/get pointer to data you want to associate with this emulator.
You can use this for whatever you want. */
void gme_set_user_data( Music_Emu*, void* new_user_data );
void* gme_user_data( Music_Emu const* );
/* Register cleanup function to be called when deleting emulator, or NULL to
clear it. Passes user_data to cleanup function. */
typedef void (*gme_user_cleanup_t)( void* user_data );
void gme_set_user_cleanup( Music_Emu*, gme_user_cleanup_t func );
#ifdef __cplusplus
}
#endif
#endif

Some files were not shown because too many files have changed in this diff Show more