Added GME for game music emulation.
This commit is contained in:
parent
ad844b1df3
commit
2b0eaf3369
118 changed files with 30978 additions and 0 deletions
|
@ -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;
|
||||
|
|
BIN
Frameworks/GME/English.lproj/InfoPlist.strings
Normal file
BIN
Frameworks/GME/English.lproj/InfoPlist.strings
Normal file
Binary file not shown.
675
Frameworks/GME/GME.xcodeproj/project.pbxproj
Normal file
675
Frameworks/GME/GME.xcodeproj/project.pbxproj
Normal 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
26
Frameworks/GME/Info.plist
Normal 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
395
Frameworks/GME/gme/Ay_Apu.cpp
Executable 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
107
Frameworks/GME/gme/Ay_Apu.h
Executable 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
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
92
Frameworks/GME/gme/Ay_Cpu.h
Executable 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
404
Frameworks/GME/gme/Ay_Emu.cpp
Executable 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
70
Frameworks/GME/gme/Ay_Emu.h
Executable 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
|
446
Frameworks/GME/gme/Blip_Buffer.cpp
Executable file
446
Frameworks/GME/gme/Blip_Buffer.cpp
Executable 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
489
Frameworks/GME/gme/Blip_Buffer.h
Executable 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
|
184
Frameworks/GME/gme/Classic_Emu.cpp
Executable file
184
Frameworks/GME/gme/Classic_Emu.cpp
Executable 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
127
Frameworks/GME/gme/Classic_Emu.h
Executable 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
|
315
Frameworks/GME/gme/Data_Reader.cpp
Executable file
315
Frameworks/GME/gme/Data_Reader.cpp
Executable 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
151
Frameworks/GME/gme/Data_Reader.h
Executable 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
|
131
Frameworks/GME/gme/Dual_Resampler.cpp
Executable file
131
Frameworks/GME/gme/Dual_Resampler.cpp
Executable 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 );
|
||||
}
|
||||
|
50
Frameworks/GME/gme/Dual_Resampler.h
Executable file
50
Frameworks/GME/gme/Dual_Resampler.h
Executable 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
|
529
Frameworks/GME/gme/Effects_Buffer.cpp
Executable file
529
Frameworks/GME/gme/Effects_Buffer.cpp
Executable 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] );
|
||||
}
|
||||
|
86
Frameworks/GME/gme/Effects_Buffer.h
Executable file
86
Frameworks/GME/gme/Effects_Buffer.h
Executable 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
|
199
Frameworks/GME/gme/Fir_Resampler.cpp
Executable file
199
Frameworks/GME/gme/Fir_Resampler.cpp
Executable 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;
|
||||
}
|
171
Frameworks/GME/gme/Fir_Resampler.h
Executable file
171
Frameworks/GME/gme/Fir_Resampler.h
Executable 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
306
Frameworks/GME/gme/Gb_Apu.cpp
Executable 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 = ®s [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
90
Frameworks/GME/gme/Gb_Apu.h
Executable 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
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
93
Frameworks/GME/gme/Gb_Cpu.h
Executable 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
336
Frameworks/GME/gme/Gb_Oscs.cpp
Executable 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
83
Frameworks/GME/gme/Gb_Oscs.h
Executable 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
288
Frameworks/GME/gme/Gbs_Emu.cpp
Executable 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
88
Frameworks/GME/gme/Gbs_Emu.h
Executable 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
216
Frameworks/GME/gme/Gme_File.cpp
Executable 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
145
Frameworks/GME/gme/Gme_File.h
Executable 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
379
Frameworks/GME/gme/Gym_Emu.cpp
Executable 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
82
Frameworks/GME/gme/Gym_Emu.h
Executable 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
315
Frameworks/GME/gme/Hes_Apu.cpp
Executable 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
66
Frameworks/GME/gme/Hes_Apu.h
Executable 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
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
124
Frameworks/GME/gme/Hes_Cpu.h
Executable 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
529
Frameworks/GME/gme/Hes_Emu.cpp
Executable 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
94
Frameworks/GME/gme/Hes_Emu.h
Executable 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
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
124
Frameworks/GME/gme/Kss_Cpu.h
Executable 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
414
Frameworks/GME/gme/Kss_Emu.cpp
Executable 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
96
Frameworks/GME/gme/Kss_Emu.h
Executable 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
|
97
Frameworks/GME/gme/Kss_Scc_Apu.cpp
Executable file
97
Frameworks/GME/gme/Kss_Scc_Apu.cpp
Executable 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
106
Frameworks/GME/gme/Kss_Scc_Apu.h
Executable 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
|
426
Frameworks/GME/gme/M3u_Playlist.cpp
Executable file
426
Frameworks/GME/gme/M3u_Playlist.cpp
Executable 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();
|
||||
}
|
67
Frameworks/GME/gme/M3u_Playlist.h
Executable file
67
Frameworks/GME/gme/M3u_Playlist.h
Executable 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
|
232
Frameworks/GME/gme/Multi_Buffer.cpp
Executable file
232
Frameworks/GME/gme/Multi_Buffer.cpp
Executable 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
156
Frameworks/GME/gme/Multi_Buffer.h
Executable 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
410
Frameworks/GME/gme/Music_Emu.cpp
Executable 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
211
Frameworks/GME/gme/Music_Emu.h
Executable 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
391
Frameworks/GME/gme/Nes_Apu.cpp
Executable 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] = ▵
|
||||
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
179
Frameworks/GME/gme/Nes_Apu.h
Executable 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
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
114
Frameworks/GME/gme/Nes_Cpu.h
Executable 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
|
121
Frameworks/GME/gme/Nes_Fme7_Apu.cpp
Executable file
121
Frameworks/GME/gme/Nes_Fme7_Apu.cpp
Executable 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
131
Frameworks/GME/gme/Nes_Fme7_Apu.h
Executable 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
|
145
Frameworks/GME/gme/Nes_Namco_Apu.cpp
Executable file
145
Frameworks/GME/gme/Nes_Namco_Apu.cpp
Executable 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], ® [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 = ® [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;
|
||||
}
|
||||
|
102
Frameworks/GME/gme/Nes_Namco_Apu.h
Executable file
102
Frameworks/GME/gme/Nes_Namco_Apu.h
Executable 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
551
Frameworks/GME/gme/Nes_Oscs.cpp
Executable 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
147
Frameworks/GME/gme/Nes_Oscs.h
Executable 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
|
215
Frameworks/GME/gme/Nes_Vrc6_Apu.cpp
Executable file
215
Frameworks/GME/gme/Nes_Vrc6_Apu.cpp
Executable 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;
|
||||
}
|
||||
|
95
Frameworks/GME/gme/Nes_Vrc6_Apu.h
Executable file
95
Frameworks/GME/gme/Nes_Vrc6_Apu.h
Executable 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
557
Frameworks/GME/gme/Nsf_Emu.cpp
Executable 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
106
Frameworks/GME/gme/Nsf_Emu.h
Executable 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
330
Frameworks/GME/gme/Nsfe_Emu.cpp
Executable 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
68
Frameworks/GME/gme/Nsfe_Emu.h
Executable 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
334
Frameworks/GME/gme/Sap_Apu.cpp
Executable 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
77
Frameworks/GME/gme/Sap_Apu.h
Executable 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
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
83
Frameworks/GME/gme/Sap_Cpu.h
Executable 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
442
Frameworks/GME/gme/Sap_Emu.cpp
Executable 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
69
Frameworks/GME/gme/Sap_Emu.h
Executable 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
330
Frameworks/GME/gme/Sms_Apu.cpp
Executable 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
75
Frameworks/GME/gme/Sms_Apu.h
Executable 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
48
Frameworks/GME/gme/Sms_Oscs.h
Executable 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
489
Frameworks/GME/gme/Snes_Spc.cpp
Executable 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
121
Frameworks/GME/gme/Snes_Spc.h
Executable 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
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
57
Frameworks/GME/gme/Spc_Cpu.h
Executable 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
666
Frameworks/GME/gme/Spc_Dsp.cpp
Executable 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
152
Frameworks/GME/gme/Spc_Dsp.h
Executable 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
326
Frameworks/GME/gme/Spc_Emu.cpp
Executable 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( ©right [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 = ©right [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
77
Frameworks/GME/gme/Spc_Emu.h
Executable 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
412
Frameworks/GME/gme/Vgm_Emu.cpp
Executable 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
84
Frameworks/GME/gme/Vgm_Emu.h
Executable 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
|
314
Frameworks/GME/gme/Vgm_Emu_Impl.cpp
Executable file
314
Frameworks/GME/gme/Vgm_Emu_Impl.cpp
Executable 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 );
|
||||
}
|
||||
}
|
||||
}
|
71
Frameworks/GME/gme/Vgm_Emu_Impl.h
Executable file
71
Frameworks/GME/gme/Vgm_Emu_Impl.h
Executable 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
|
21
Frameworks/GME/gme/Ym2413_Emu.cpp
Executable file
21
Frameworks/GME/gme/Ym2413_Emu.cpp
Executable 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
33
Frameworks/GME/gme/Ym2413_Emu.h
Executable 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
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
38
Frameworks/GME/gme/Ym2612_Emu.h
Executable 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
|
175
Frameworks/GME/gme/blargg_common.h
Executable file
175
Frameworks/GME/gme/blargg_common.h
Executable 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
|
30
Frameworks/GME/gme/blargg_config.h
Executable file
30
Frameworks/GME/gme/blargg_config.h
Executable 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
|
158
Frameworks/GME/gme/blargg_endian.h
Executable file
158
Frameworks/GME/gme/blargg_endian.h
Executable 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
|
78
Frameworks/GME/gme/blargg_source.h
Executable file
78
Frameworks/GME/gme/blargg_source.h
Executable 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
72
Frameworks/GME/gme/gb_cpu_io.h
Executable 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
256
Frameworks/GME/gme/gme.cpp
Executable 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
222
Frameworks/GME/gme/gme.h
Executable 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
Loading…
Reference in a new issue