├── psg_data.raw ├── gba_mus_ripper_gui ├── goldensun_synth.raw ├── GBA_Mus_Ripper_GUI.ico ├── gba-mus-ripper-gui logo.pdn ├── gba-mus-ripper-gui logo.png ├── song_ripper ├── song_ripper.h ├── midi.h ├── midi.cpp └── song_ripper.cpp ├── sound_font_ripper ├── sound_font_ripper.h ├── gba_samples.h ├── gba_instr.h ├── sf2.h ├── sf2_types.h ├── sf2.cpp ├── gba_samples.cpp ├── gba_instr.cpp ├── sound_font_ripper.cpp └── sf2_chunks.h ├── gba_mus_ripper ├── gba_mus_ripper.h └── gba_mus_ripper.cpp ├── sappy_detector ├── sappy_detector.h └── sappy_detector.cpp ├── .gitattributes ├── aboutdialog.h ├── hex_string.h ├── aboutdialog.cpp ├── main.cpp ├── progressdialog.h ├── README.md ├── mainwindow.h ├── gba-mus-ripper-gui_resource.rc ├── .gitignore ├── gba-mus-ripper-gui.pro ├── progressdialog.ui ├── aboutdialog.ui ├── mainwindow.cpp ├── progressdialog.cpp ├── mainwindow.ui └── moc_predefs.h /psg_data.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainSwag101/gba-mus-ripper-gui/HEAD/psg_data.raw -------------------------------------------------------------------------------- /gba_mus_ripper_gui: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainSwag101/gba-mus-ripper-gui/HEAD/gba_mus_ripper_gui -------------------------------------------------------------------------------- /goldensun_synth.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainSwag101/gba-mus-ripper-gui/HEAD/goldensun_synth.raw -------------------------------------------------------------------------------- /GBA_Mus_Ripper_GUI.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainSwag101/gba-mus-ripper-gui/HEAD/GBA_Mus_Ripper_GUI.ico -------------------------------------------------------------------------------- /gba-mus-ripper-gui logo.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainSwag101/gba-mus-ripper-gui/HEAD/gba-mus-ripper-gui logo.pdn -------------------------------------------------------------------------------- /gba-mus-ripper-gui logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainSwag101/gba-mus-ripper-gui/HEAD/gba-mus-ripper-gui logo.png -------------------------------------------------------------------------------- /song_ripper/song_ripper.h: -------------------------------------------------------------------------------- 1 | #ifndef SONG_RIPPER_H 2 | #define SONG_RIPPER_H 3 | 4 | #include "midi.h" 5 | 6 | int32_t song_ripper(int32_t argc, char *argv[]); 7 | 8 | #endif // SONG_RIPPER_H 9 | -------------------------------------------------------------------------------- /sound_font_ripper/sound_font_ripper.h: -------------------------------------------------------------------------------- 1 | #ifndef SOUND_FONT_RIPPER_H 2 | #define SOUND_FONT_RIPPER_H 3 | 4 | int32_t sound_font_ripper(int32_t argc, char *argv[]); 5 | 6 | #endif // SOUND_FONT_RIPPER_H 7 | -------------------------------------------------------------------------------- /gba_mus_ripper/gba_mus_ripper.h: -------------------------------------------------------------------------------- 1 | #ifndef GBA_MUS_RIPPER_H 2 | #define GBA_MUS_RIPPER_H 3 | 4 | using namespace std; 5 | 6 | int32_t mus_ripper(int32_t argc, string argv[]); 7 | 8 | #endif // GBA_MUS_RIPPER_H 9 | -------------------------------------------------------------------------------- /sappy_detector/sappy_detector.h: -------------------------------------------------------------------------------- 1 | #ifndef SAPPY_DETECTOR_H 2 | #define SAPPY_DETECTOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | int32_t sappy_detector(const int32_t argc, string argv); 13 | 14 | #endif // SAPPY_DETECTOR_H 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /aboutdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef ABOUTDIALOG_H 2 | #define ABOUTDIALOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define BUILD_DATE __DATE__ 11 | #define BUILD_TIME __TIME__ 12 | 13 | namespace Ui { 14 | class AboutDialog; 15 | } 16 | 17 | class AboutDialog : public QDialog 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit AboutDialog(QWidget *parent = 0); 23 | ~AboutDialog(); 24 | 25 | private slots: 26 | void on_okButton_clicked(); 27 | 28 | private: 29 | Ui::AboutDialog *ui; 30 | 31 | }; 32 | 33 | #endif // ABOUTDIALOG_H 34 | -------------------------------------------------------------------------------- /hex_string.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of GBA Sound ripper 3 | * (c) 2012, 2014 Bregalad 4 | * This is free and open source software 5 | * 6 | * This file provides a simple quick hex -> std::string conversion function 7 | */ 8 | 9 | #ifndef HEX_STRING_H 10 | #define HEX_STRING_H 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | 16 | static string hex(const uint32_t n) 17 | { 18 | string s; 19 | for(int32_t i=7; i >= 0; --i) 20 | { 21 | s += "0123456789abcdef"[0xf & (n >> (4*i))]; 22 | } 23 | // Remove leading zeroes 24 | return s.substr(s.find_first_not_of('0')); 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /aboutdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "aboutdialog.h" 2 | #include "ui_aboutdialog.h" 3 | 4 | AboutDialog::AboutDialog(QWidget *parent) : 5 | QDialog(parent), 6 | ui(new Ui::AboutDialog) 7 | { 8 | ui->setupUi(this); 9 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 10 | 11 | QString timestamp = QString::fromStdString((std::string)BUILD_DATE + " " + (std::string)BUILD_TIME); 12 | QDateTime buildDate = QDateTime::fromString(timestamp, "MMM d yyyy HH:mm:ss"); 13 | ui->label->setText(ui->label->text() + buildDate.toString(Qt::SystemLocaleLongDate) + ")"); 14 | } 15 | 16 | AboutDialog::~AboutDialog() 17 | { 18 | delete ui; 19 | } 20 | 21 | void AboutDialog::on_okButton_clicked() 22 | { 23 | close(); 24 | } 25 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | #include "gba_mus_ripper/gba_mus_ripper.h" 4 | #include "song_ripper/song_ripper.h" 5 | #include "sound_font_ripper/sound_font_ripper.h" 6 | 7 | int32_t main(int32_t argc, char *argv[]) 8 | { 9 | 10 | if (argc > 1) 11 | { 12 | if (argv[1] == (std::string)"song_ripper") 13 | { 14 | return song_ripper(argc - 1, argv + 1); 15 | } 16 | else if (argv[1] == (std::string)"sound_font_ripper") 17 | { 18 | return sound_font_ripper(argc - 1, argv + 1); 19 | } 20 | else 21 | { 22 | 23 | return -1; 24 | } 25 | } 26 | else 27 | { 28 | QApplication a(argc, argv); 29 | MainWindow w; 30 | w.show(); 31 | 32 | return a.exec(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /progressdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef PROGRESSDIALOG_H 2 | #define PROGRESSDIALOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "mainwindow.h" 13 | #include "gba_mus_ripper/gba_mus_ripper.h" 14 | 15 | namespace Ui { 16 | class ProgressDialog; 17 | } 18 | 19 | class ProgressDialog : public QDialog 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | explicit ProgressDialog(MainWindow *parent); 25 | ~ProgressDialog(); 26 | void StartRip(); 27 | 28 | public slots: 29 | void Finish(int32_t exitCode); 30 | 31 | private: 32 | Ui::ProgressDialog *ui; 33 | QProcess *ripper; 34 | QString romPath; 35 | QString outPath; 36 | QString nativeArgs; 37 | }; 38 | 39 | #endif // PROGRESSDIALOG_H 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gba-mus-ripper-gui 2 | A graphical frontend for my fork of Bregalad's 'gba-mus-ripper' program 3 | 4 | To build this program from source using Qt Creator, you will need Qt version 5.6 with the MinGW32 compiler v4.9.2. 5 | 6 | If you want to run this program as a standalone executable after building from source, you will need to follow the above step, 7 | and also copy the 4 executables from the source directory into the program's irectory, as well as copy the files 8 | "Qt5Core.dll", "Qt5Widgets.dll", and "Qt5Gui.dll" from "(Qt installation directory)\5.6\mingw49_32\bin\" into the program's directory. 9 | 10 | Or, if you don't want to go through all that trouble, simply download the latest release from the "Releases" tab of this page. 11 | 12 | NOTE: Currently this program only works properly under Windows, I do not know if Linux support will be possible to add due to the way 13 | this program works. 14 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class MainWindow; 8 | } 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget *parent = 0); 16 | ~MainWindow(); 17 | 18 | QString romPath; 19 | QString outputPath; 20 | 21 | bool gmFlag; 22 | bool xgFlag; 23 | bool rcFlag; 24 | bool sbFlag; 25 | bool rawFlag; 26 | bool adrFlag; 27 | QString address; 28 | 29 | private slots: 30 | 31 | void on_startButton_clicked(); 32 | 33 | void on_chooseRomButton_clicked(); 34 | 35 | void on_chooseOutputButton_clicked(); 36 | 37 | void on_manualAddressEnable_stateChanged(); 38 | 39 | void on_actionExit_triggered(); 40 | 41 | void on_actionAbout_triggered(); 42 | 43 | private: 44 | Ui::MainWindow *ui; 45 | }; 46 | 47 | #endif // MAINWINDOW_H 48 | -------------------------------------------------------------------------------- /sound_font_ripper/gba_samples.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Sound Font Samples class 3 | * 4 | * This program is part of GBA SoundFont Ripper (c) 2012 by Bregalad 5 | * This is free and open source software. 6 | */ 7 | 8 | #ifndef GBA_SAMPLES_H 9 | #define GBA_SAMPLES_H 10 | 11 | #include "sf2.h" 12 | #include 13 | 14 | class GBASamples 15 | { // List of pointers to samples within the .gba file, position is # of sample in .sf2 16 | std::vector samples_list; 17 | // Related sf2 class 18 | SF2 *sf2; 19 | 20 | public: 21 | GBASamples(SF2 *sf2) : sf2(sf2) 22 | {} 23 | 24 | // Convert a normal sample to SoundFont format 25 | int32_t build_sample(uint32_t pointer); 26 | // Convert a Game Boy channel 3 sample to SoundFont format 27 | int32_t build_GB3_samples(uint32_t pointer); 28 | // Convert a Game Boy pulse (channels 1, 2) sample 29 | int32_t build_pulse_samples(uint32_t duty_cycle); 30 | // Convert a Game Boy noise (channel 4) sample 31 | int32_t build_noise_sample(bool metallic, int32_t key); 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /gba-mus-ripper-gui_resource.rc: -------------------------------------------------------------------------------- 1 | # if defined(UNDER_CE) 2 | # include 3 | # else 4 | # include 5 | # endif 6 | 7 | IDI_ICON1 ICON DISCARDABLE "E:\\jpmac\\Documents\\GitHub\\gba-mus-ripper-gui\\GBA_Mus_Ripper_GUI.ico" 8 | 9 | VS_VERSION_INFO VERSIONINFO 10 | FILEVERSION 0,1,4,0 11 | PRODUCTVERSION 0,1,4,0 12 | FILEFLAGSMASK 0x3fL 13 | #ifdef _DEBUG 14 | FILEFLAGS VS_FF_DEBUG 15 | #else 16 | FILEFLAGS 0x0L 17 | #endif 18 | FILEOS VOS__WINDOWS32 19 | FILETYPE VFT_DLL 20 | FILESUBTYPE 0x0L 21 | BEGIN 22 | BLOCK "StringFileInfo" 23 | BEGIN 24 | BLOCK "040904b0" 25 | BEGIN 26 | VALUE "CompanyName", "\0" 27 | VALUE "FileDescription", "\0" 28 | VALUE "FileVersion", "0.1.4.0\0" 29 | VALUE "LegalCopyright", "\0" 30 | VALUE "OriginalFilename", "gba_mus_ripper_gui.exe\0" 31 | VALUE "ProductName", "gba_mus_ripper_gui\0" 32 | VALUE "ProductVersion", "0.1.4.0\0" 33 | END 34 | END 35 | BLOCK "VarFileInfo" 36 | BEGIN 37 | VALUE "Translation", 0x0409, 1200 38 | END 39 | END 40 | /* End of Version info */ 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | 3 | [Dd]ebug/ 4 | [Rr]elease/ 5 | 6 | # C++ objects and libs 7 | 8 | *.slo 9 | *.lo 10 | *.o 11 | *.a 12 | *.la 13 | *.lai 14 | *.so 15 | *.dll 16 | *.dylib 17 | 18 | # Qt-es 19 | 20 | /.qmake.cache 21 | /.qmake.stash 22 | *.pro.user 23 | *.pro.user.* 24 | *.qbs.user 25 | *.qbs.user.* 26 | *.moc 27 | moc_*.cpp 28 | qrc_*.cpp 29 | ui_*.h 30 | Makefile* 31 | *-build-* 32 | object_script.* 33 | 34 | # QtCreator 35 | 36 | *.autosave 37 | 38 | #QtCtreator Qml 39 | *.qmlproject.user 40 | *.qmlproject.user.* 41 | 42 | # ========================= 43 | # Operating System Files 44 | # ========================= 45 | 46 | # OSX 47 | # ========================= 48 | 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | 53 | # Thumbnails 54 | ._* 55 | 56 | # Files that might appear in the root of a volume 57 | .DocumentRevisions-V100 58 | .fseventsd 59 | .Spotlight-V100 60 | .TemporaryItems 61 | .Trashes 62 | .VolumeIcon.icns 63 | 64 | # Directories potentially created on remote AFP share 65 | .AppleDB 66 | .AppleDesktop 67 | Network Trash Folder 68 | Temporary Items 69 | .apdisk 70 | 71 | # Windows 72 | # ========================= 73 | 74 | # Windows image file caches 75 | Thumbs.db 76 | ehthumbs.db 77 | 78 | # Folder config file 79 | Desktop.ini 80 | 81 | # Recycle Bin used on file shares 82 | $RECYCLE.BIN/ 83 | 84 | # Windows Installer files 85 | *.cab 86 | *.msi 87 | *.msm 88 | *.msp 89 | 90 | # Windows shortcuts 91 | *.lnk 92 | -------------------------------------------------------------------------------- /gba-mus-ripper-gui.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-09-12T12:45:50 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = gba_mus_ripper_gui 12 | TEMPLATE = app 13 | 14 | HEADERS += mainwindow.h \ 15 | gba_mus_ripper/gba_mus_ripper.h \ 16 | sappy_detector/sappy_detector.h \ 17 | song_ripper/midi.h \ 18 | song_ripper/song_ripper.h \ 19 | sound_font_ripper/gba_instr.h \ 20 | sound_font_ripper/gba_samples.h \ 21 | sound_font_ripper/sf2.h \ 22 | sound_font_ripper/sf2_chunks.h \ 23 | sound_font_ripper/sf2_types.h \ 24 | sound_font_ripper/sound_font_ripper.h \ 25 | aboutdialog.h \ 26 | hex_string.h \ 27 | progressdialog.h 28 | 29 | SOURCES += main.cpp\ 30 | mainwindow.cpp \ 31 | gba_mus_ripper/gba_mus_ripper.cpp \ 32 | sappy_detector/sappy_detector.cpp \ 33 | song_ripper/midi.cpp \ 34 | song_ripper/song_ripper.cpp \ 35 | sound_font_ripper/gba_instr.cpp \ 36 | sound_font_ripper/gba_samples.cpp \ 37 | sound_font_ripper/sf2.cpp \ 38 | sound_font_ripper/sound_font_ripper.cpp \ 39 | aboutdialog.cpp \ 40 | progressdialog.cpp 41 | 42 | FORMS += mainwindow.ui \ 43 | aboutdialog.ui \ 44 | progressdialog.ui 45 | 46 | DISTFILES += GBA_Mus_Ripper_GUI.ico \ 47 | goldensun_synth.raw \ 48 | psg_data.raw 49 | 50 | win32:RC_ICONS += GBA_Mus_Ripper_GUI.ico 51 | -------------------------------------------------------------------------------- /progressdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ProgressDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 290 10 | 64 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Extracting Music... 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 10 29 | 30 30 | 271 31 | 23 32 | 33 | 34 | 35 | 0 36 | 37 | 38 | 0 39 | 40 | 41 | false 42 | 43 | 44 | 45 | 46 | 47 | 10 48 | 0 49 | 271 50 | 31 51 | 52 | 53 | 54 | Please wait... 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /aboutdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AboutDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 461 10 | 212 11 | 12 | 13 | 14 | About 15 | 16 | 17 | 18 | 19 | 190 20 | 170 21 | 85 22 | 31 23 | 24 | 25 | 26 | 27 | 0 28 | 0 29 | 30 | 31 | 32 | OK 33 | 34 | 35 | 36 | 37 | 38 | 10 39 | 10 40 | 441 41 | 141 42 | 43 | 44 | 45 | GBA Mus Ripper GUI 46 | 47 | By James "CaptainSwag101" Pelster 48 | 49 | A graphical tool for my fork of Bregalad's program, "gba_mus_riper" 50 | 51 | Version 0.1.5 (Built: 52 | 53 | 54 | Qt::AutoText 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /sound_font_ripper/gba_instr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Sound Font Instruments class 3 | * 4 | * This program is part of GBA SoundFont Ripper (c) 2012, 2014 by Bregalad 5 | * This is free and open source software. 6 | */ 7 | 8 | #ifndef GBA_INST_H 9 | #define GBA_INST_H 10 | 11 | #include 12 | #include 13 | #include "sf2.h" 14 | #include "gba_samples.h" 15 | 16 | struct inst_data 17 | { 18 | uint32_t word0; 19 | uint32_t word1; 20 | uint32_t word2; 21 | }; 22 | 23 | typedef std::map::iterator inst_it; 24 | 25 | class GBAInstr 26 | { 27 | int32_t cur_inst_index; 28 | std::map inst_map; // Contains pointers to instruments within GBA file, their position is the # of instrument in the SF2 29 | SF2 *sf2; // Related .sf2 file 30 | GBASamples samples; // Related samples class 31 | 32 | // Convert pointer from GBA memory map to ROM memory map 33 | uint32_t get_GBA_pointer(); 34 | // Apply ADSR envelope on the instrument 35 | void generate_adsr_generators(const uint32_t adsr); 36 | void generate_psg_adsr_generators(const uint32_t adsr); 37 | 38 | public: 39 | GBAInstr(SF2 *sf2) : cur_inst_index(0), sf2(sf2), samples(sf2) 40 | {} 41 | 42 | //Build a SF2 instrument form a GBA sampled instrument 43 | int32_t build_sampled_instrument(const inst_data inst); 44 | 45 | //Create new SF2 from every key split GBA instrument 46 | int32_t build_every_keysplit_instrument(const inst_data inst); 47 | 48 | //Build a SF2 instrument from a GBA key split instrument 49 | int32_t build_keysplit_instrument(const inst_data inst); 50 | 51 | //Build gameboy channel 3 instrument 52 | int32_t build_GB3_instrument(const inst_data inst); 53 | 54 | //Build GameBoy pulse wave instrument 55 | int32_t build_pulse_instrument(const inst_data inst); 56 | 57 | //Build GameBoy white noise instrument 58 | int32_t build_noise_instrument(const inst_data inst); 59 | }; 60 | #endif 61 | -------------------------------------------------------------------------------- /song_ripper/midi.h: -------------------------------------------------------------------------------- 1 | #ifndef MIDI_H 2 | #define MIDI_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum MIDIEventType 9 | { 10 | NOTEOFF=8, 11 | NOTEON=9, 12 | NOTEAFT=10, 13 | CONTROLLER=11, 14 | PCHANGE=12, 15 | CHNAFT=13, 16 | PITCHBEND=14 17 | }; 18 | 19 | class MIDI 20 | { 21 | uint16_t delta_time_per_beat; 22 | int16_t last_rpn_type[16]; 23 | int16_t last_nrpn_type[16]; 24 | int32_t last_type[16]; 25 | 26 | // Last event type and channel, used for compression 27 | int32_t last_chanel; 28 | MIDIEventType last_event_type; 29 | // Time counter 30 | uint32_t time_ctr; 31 | // Track data 32 | std::vector data; 33 | 34 | void add_vlength_code(int32_t code); // Add delta time in MIDI variable length coding 35 | // Add delta time event 36 | void add_delta_time(); 37 | // Add any MIDI event 38 | void add_event(MIDIEventType type, int32_t chn, char param1, char param2); 39 | void add_event(MIDIEventType type, int32_t chn, char param1); 40 | 41 | public: 42 | char chn_reorder[16]; // User can change the order of the channels 43 | 44 | MIDI(uint16_t delta_time); // Construct a MIDI object 45 | void write(FILE*); // Write cached data to midi file 46 | 47 | // Increment time by one clock 48 | inline void clock() 49 | { 50 | time_ctr += 1; 51 | } 52 | 53 | // Add an MIDI event to the stream 54 | void add_note_on(int32_t chn, char key, char vel); 55 | void add_note_off(int32_t chn, char key, char vel); 56 | void add_controller(int32_t chn, char ctrl, char value); 57 | void add_chanaft(int32_t chn, char value); 58 | void add_pchange(int32_t chn, char number); 59 | void add_pitch_bend(int32_t chn, int16_t value); 60 | void add_pitch_bend(int32_t chn, char value); 61 | void add_RPN(int32_t chn, int16_t type, int16_t value); 62 | //Add RPN event with only the MSB of value used 63 | inline void add_RPN(int32_t chn, int16_t type, char value) 64 | { 65 | add_RPN(chn, type, int16_t(value<<7)); 66 | } 67 | void add_NRPN(int32_t chn, int16_t type, int16_t value); 68 | //Add NRPN event with only the MSB of value used 69 | inline void add_NRPN(int32_t chn, int16_t type, char value) 70 | { 71 | add_NRPN(chn, type, int16_t(value<<7)); 72 | } 73 | void add_marker(const char *text); 74 | void add_sysex(const char sysex_data[], size_t len); 75 | void add_tempo(double tempo); 76 | }; 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /sound_font_ripper/sf2.h: -------------------------------------------------------------------------------- 1 | #ifndef SF2_HPP 2 | #define SF2_HPP 3 | 4 | #include 5 | #include 6 | #include "sf2_types.h" 7 | 8 | class InfoListChunk; 9 | class SdtaListChunk; 10 | class HydraChunk; 11 | class sfPresetHeader; 12 | class sfBag; 13 | class sfInst; 14 | 15 | // This is our own type, not part of the specification 16 | typedef enum 17 | { 18 | UNSIGNED_8, 19 | SIGNED_8, 20 | SIGNED_16, 21 | GAMEBOY_CH3, 22 | BDPCM 23 | } 24 | SampleType; 25 | 26 | class SF2 27 | { 28 | uint32_t size; 29 | // Instrument and samples objects 30 | // SF2Instr& instruments; 31 | // gba_samples& samples; 32 | 33 | //All 3 chunks of the SF2 file 34 | InfoListChunk *const infolist_chunk; 35 | SdtaListChunk *const sdtalist_chunk; 36 | HydraChunk *const pdtalist_chunk; 37 | 38 | void add_terminals(); 39 | 40 | // Forbid copy and affectation 41 | SF2(SF2&); 42 | SF2& operator=(SF2&); 43 | 44 | public: 45 | //Target file, should be assigned to a valid opened FILE in "wb" mode by user before "write()" is called. 46 | FILE *out; 47 | uint32_t default_sample_rate; 48 | 49 | SF2(uint32_t sample_rate); 50 | ~SF2(); 51 | void write(FILE *outfile); 52 | void add_new_preset(const char *name, int32_t Patch, int32_t Bank); 53 | void add_new_instrument(const char *name); 54 | void add_new_inst_bag(); 55 | void add_new_preset_bag(); 56 | void add_new_preset_modulator(); 57 | void add_new_preset_generator(); 58 | void add_new_preset_generator(SFGenerator operation, uint16_t value); 59 | void add_new_preset_generator(SFGenerator operation, uint8_t lo, uint8_t hi); 60 | void add_new_inst_modulator(); 61 | void add_new_inst_generator(); 62 | void add_new_inst_generator(SFGenerator operation, uint16_t value); 63 | void add_new_inst_generator(SFGenerator operation, uint8_t lo, uint8_t hi); 64 | void add_new_sample_header(const char *name, int32_t start, int32_t end, int32_t start_loop, int32_t end_loop, int32_t sample_rate, int32_t original_pitch, int32_t pitch_correction); 65 | 66 | void add_new_sample(FILE *file, SampleType type, const char *name, uint32_t pointer, uint32_t size, bool loop_flag, 67 | uint32_t loop_pos, uint32_t original_pitch, uint32_t pitch_correction, uint32_t sample_rate); 68 | // Add new sample using default sample rate 69 | inline void add_new_sample(FILE *file, SampleType type, const char *name, uint32_t pointer, uint32_t size, 70 | bool loop_flag, uint32_t loop_pos, uint32_t original_pitch, uint32_t pitch_correction) 71 | { 72 | add_new_sample(file, type, name, pointer, size, loop_flag, loop_pos, original_pitch, pitch_correction, default_sample_rate); 73 | } 74 | 75 | uint16_t get_ibag_size(); 76 | uint16_t get_igen_size(); 77 | uint16_t get_imod_size(); 78 | uint16_t get_pbag_size(); 79 | uint16_t get_pgen_size(); 80 | uint16_t get_pmod_size(); 81 | }; 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /sound_font_ripper/sf2_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of GBA Sound Ripper 3 | * (c) 2012, 2014 Bregalad 4 | * This is free and open source software 5 | * 6 | * This class defines types closely related to SF2 specification 7 | */ 8 | 9 | #ifndef SF2_TYPES_HPP 10 | #define SF2_TYPES_HPP 11 | #include 12 | 13 | /* From the SF2 spec v2.1, page 7 */ 14 | // typedef uint8_t BYTE; 15 | // typedef int8_t CHAR; 16 | // typedef uint32_t DWORD; 17 | // typedef int16_t SHORT; 18 | // typedef uint16_t WORD; 19 | 20 | /* From the SF2 spec v2.1 page 19 */ 21 | struct rangesType 22 | { 23 | uint8_t byLo; 24 | uint8_t byHi; 25 | 26 | rangesType(uint8_t lo, uint8_t hi) : byLo(lo), byHi(hi) 27 | {} 28 | }; 29 | 30 | // Two bytes that can handle either two 8-bit values or a single 16-bit value 31 | union genAmountType 32 | { 33 | rangesType ranges; 34 | int16_t shAmount; 35 | uint16_t wAmount; 36 | 37 | genAmountType(uint16_t value = 0) : wAmount(value) 38 | {} 39 | 40 | genAmountType(uint8_t lo, uint8_t hi) : ranges(lo, hi) 41 | {} 42 | }; 43 | 44 | // SF2 v2.1 spec page 20 45 | enum class SFSampleLink : uint16_t 46 | { 47 | monoSample = 1, 48 | rightSample = 2, 49 | leftSample = 4, 50 | linkedSample = 8 51 | }; 52 | 53 | // Generator's enumeration class 54 | // SF2 v2.1 spec page 38 55 | enum class SFGenerator : uint16_t 56 | { 57 | _null = 0, 58 | startAddrsOffset = 0, 59 | endAddrsOffset = 1, 60 | startloopAddrsOffset = 2, 61 | endloopAddrsOffset = 3, 62 | startAddrsCoarseOffset = 4, 63 | modLfoToPitch = 5, 64 | vibLfoToPitch = 6, 65 | modEnvToPitch = 7, 66 | initialFilterFc = 8, 67 | initialFilterQ = 9, 68 | modLfoToFilterFc = 10, 69 | modEnvToFilterFc = 11, 70 | endAddrsCoarseOffset = 12, 71 | modLfoToVolume = 13, 72 | chorusEffectsSend = 15, 73 | reverbEffectsSend = 16, 74 | pan = 17, 75 | delayModLFO = 21, 76 | freqModLFO = 22, 77 | delayVibLFO = 23, 78 | freqVibLFO = 24, 79 | delayModEnv = 25, 80 | attackModEnv = 26, 81 | holdModEnv = 27, 82 | decayModEnv = 28, 83 | sustainModEnv = 29, 84 | releaseModEnv = 30, 85 | keynumToModEnvHold = 31, 86 | keynumToModEnvDecay = 32, 87 | delayVolEnv = 33, 88 | attackVolEnv = 34, 89 | holdVolEnv = 35, 90 | decayVolEnv = 36, 91 | sustainVolEnv = 37, 92 | releaseVolEnv = 38, 93 | keynumToVolEnvHold = 39, 94 | keynumToVolEnvDecay = 40, 95 | instrument = 41, 96 | keyRange = 43, 97 | velRange = 44, 98 | startloopAddrsCoarseOffset = 45, 99 | keynum = 46, 100 | velocity = 47, 101 | initialAttenuation = 48, 102 | endloopAddrsCoarseOffset = 50, 103 | coarseTune = 51, 104 | fineTune = 52, 105 | sampleID = 53, 106 | sampleModes = 54, 107 | scaleTuning = 56, 108 | exclusiveClass = 57, 109 | overridingRootKey = 58, 110 | endOper = 60 111 | }; 112 | 113 | // Modulator's enumeration class 114 | // SF2 v2.1 spec page 50 115 | enum class SFModulator : uint16_t 116 | { 117 | _null = 0, 118 | none = 0, 119 | noteOnVelocity = 1, 120 | noteOnKey = 2, 121 | polyPressure = 10, 122 | chnPressure = 13, 123 | pitchWheel = 14, 124 | ptchWeelSensivity = 16 125 | }; 126 | 127 | // SF2 v2.1 spec page 52 128 | enum class SFTransform : uint16_t 129 | { 130 | _null = 0, 131 | linear = 0, 132 | concave = 1, 133 | convex = 2 134 | }; 135 | #endif 136 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "progressdialog.h" 3 | #include "aboutdialog.h" 4 | #include "ui_mainwindow.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | MainWindow::MainWindow(QWidget *parent) : 12 | QMainWindow(parent), 13 | ui(new Ui::MainWindow) 14 | { 15 | ui->setupUi(this); 16 | setWindowFlags((windowFlags() | Qt::CustomizeWindowHint) & ~Qt::WindowMaximizeButtonHint); 17 | setFixedSize(width(), height()); 18 | } 19 | 20 | MainWindow::~MainWindow() 21 | { 22 | delete ui; 23 | } 24 | 25 | void MainWindow::on_startButton_clicked() 26 | { 27 | if (ui->romPathEdit->text().isEmpty()) 28 | { 29 | QMessageBox *errorMsg = new QMessageBox(this); 30 | errorMsg->setIcon(QMessageBox::Critical); 31 | if (ui->outputPathEdit->text().isEmpty()) 32 | errorMsg->setText("No GBA ROM or output path specified."); 33 | else 34 | errorMsg->setText("No GBA ROM specified."); 35 | 36 | errorMsg->exec(); 37 | } 38 | else if (ui->outputPathEdit->text().isEmpty()) 39 | { 40 | QMessageBox *errorMsg = new QMessageBox(this); 41 | errorMsg->setIcon(QMessageBox::Critical); 42 | errorMsg->setText("No output path specified."); 43 | errorMsg->exec(); 44 | } 45 | else 46 | { 47 | romPath = ui->romPathEdit->text(); 48 | outputPath = ui->outputPathEdit->text(); 49 | gmFlag = ui->giveGMNames->isChecked(); 50 | xgFlag = ui->makeXGCompliant->isChecked(); 51 | rcFlag = ui->avoidCh10->isChecked(); 52 | sbFlag = ui->splitSoundBanks->isChecked(); 53 | rawFlag = ui->outputRaw->isChecked(); 54 | adrFlag = ui->manualAddressEnable->isChecked(); 55 | address = ui->manualAddress->text(); 56 | 57 | ProgressDialog *progDialog = new ProgressDialog(this); 58 | QCoreApplication::processEvents(); 59 | progDialog->show(); 60 | progDialog->update(); 61 | progDialog->StartRip(); 62 | } 63 | } 64 | 65 | void MainWindow::on_chooseRomButton_clicked() 66 | { 67 | QFileDialog openRom; 68 | QString initialPath; 69 | QString result; 70 | 71 | if (!ui->romPathEdit->text().isEmpty() && QDir(ui->romPathEdit->text()).exists()) 72 | initialPath = ui->romPathEdit->text(); 73 | else if (!ui->outputPathEdit->text().isEmpty() && QDir(ui->outputPathEdit->text() + "/").exists()) 74 | initialPath = ui->outputPathEdit->text(); 75 | else 76 | initialPath = QDir::currentPath(); 77 | 78 | result = openRom.getOpenFileName(this, tr("Choose a GBA ROM"), initialPath, "GBA ROMs (*.gba)"); 79 | 80 | if (!result.isEmpty()) 81 | { 82 | ui->romPathEdit->setText(result); 83 | 84 | if (ui->outputPathEdit->text().isEmpty()) 85 | ui->outputPathEdit->setText(QFileInfo(result).absolutePath()); 86 | } 87 | } 88 | 89 | void MainWindow::on_chooseOutputButton_clicked() 90 | { 91 | QFileDialog openOutput; 92 | QString initialPath; 93 | QString result; 94 | 95 | if (!ui->outputPathEdit->text().isEmpty() && QDir(ui->outputPathEdit->text()).exists()) 96 | initialPath = ui->outputPathEdit->text(); 97 | else if (!ui->romPathEdit->text().isEmpty() && QDir(ui->romPathEdit->text()).exists()) 98 | initialPath = ui->romPathEdit->text(); 99 | else 100 | initialPath = QDir::currentPath(); 101 | 102 | result = openOutput.getExistingDirectory(this, tr("Choose output directory"), initialPath, QFileDialog::ShowDirsOnly); 103 | 104 | if (!result.isEmpty()) 105 | { 106 | ui->outputPathEdit->setText(result); 107 | } 108 | } 109 | 110 | void MainWindow::on_manualAddressEnable_stateChanged() 111 | { 112 | ui->manualAddress->setEnabled(ui->manualAddressEnable->isChecked()); 113 | } 114 | 115 | void MainWindow::on_actionExit_triggered() 116 | { 117 | close(); 118 | } 119 | 120 | void MainWindow::on_actionAbout_triggered() 121 | { 122 | AboutDialog *about = new AboutDialog(this); 123 | about->exec(); 124 | } 125 | -------------------------------------------------------------------------------- /progressdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "progressdialog.h" 2 | #include "ui_progressdialog.h" 3 | 4 | ProgressDialog::ProgressDialog(MainWindow *parent) : QDialog(parent), ui(new Ui::ProgressDialog) 5 | { 6 | ui->setupUi(this); 7 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::WindowCloseButtonHint); 8 | 9 | ui->progressBar->setMaximum(0); 10 | ui->progressBar->setMinimum(0); 11 | 12 | romPath = parent->romPath; 13 | outPath = parent->outputPath; 14 | 15 | nativeArgs += romPath; 16 | nativeArgs += "\n-o\n"; 17 | nativeArgs += outPath; 18 | 19 | if (parent->gmFlag) 20 | nativeArgs += "\n-gm"; 21 | if (parent->xgFlag) 22 | nativeArgs += "\n-xg"; 23 | if (parent->rcFlag) 24 | nativeArgs += "\n-rc"; 25 | if (parent->sbFlag) 26 | nativeArgs += "\n-sb"; 27 | if (parent->rawFlag) 28 | nativeArgs += "\n-raw"; 29 | if (parent->adrFlag && !parent->address.isEmpty()) 30 | nativeArgs += "\n-adr\n" + parent->address; 31 | } 32 | 33 | void ProgressDialog::StartRip() 34 | { 35 | QStringList argList = nativeArgs.split("\n"); 36 | 37 | int32_t i = 0; 38 | int32_t size = argList.size(); 39 | string c[size]; 40 | foreach(QString s, argList) 41 | { 42 | c[i] = s.toStdString(); 43 | i++; 44 | } 45 | 46 | /* 47 | ripper = new QProcess(this); 48 | ripper->setProgram(QString(QDir::toNativeSeparators(QDir::currentPath() + '/' + "gba_mus_ripper.exe"))); 49 | ripper->setNativeArguments(nativeArgs); 50 | 51 | connect(ripper, SIGNAL(finished(int32_t, QProcess::ExitStatus)), this, SLOT(Finish())); 52 | ripper->start(); 53 | */ 54 | 55 | Finish(mus_ripper(size, c)); 56 | close(); 57 | } 58 | 59 | void ProgressDialog::Finish(int32_t exitCode) 60 | { 61 | QMessageBox *resultMsg = new QMessageBox(this); 62 | 63 | ui->progressBar->setMaximum(100); 64 | if (exitCode == 0) 65 | { 66 | ui->label->setText("Extraction completed!"); 67 | ui->progressBar->setValue(100); 68 | QString romName = romPath.split(QDir::separator()).last(); 69 | romName.truncate((romName.length() - 1) - romName.split('.').last().length()); 70 | QMessageBox::StandardButton reply; 71 | reply = QMessageBox::question(this, "Extraction complete!", "Music was successfully extracted to \"" + romName + "\".\nDo you want to open the output directory now?", QMessageBox::Yes | QMessageBox::No); 72 | if (reply == QMessageBox::Yes) 73 | { 74 | QDesktopServices::openUrl(QUrl::fromLocalFile(QDir::toNativeSeparators(romName))); 75 | } 76 | } 77 | else 78 | { 79 | ui->label->setText("An error occurred while extracting!"); 80 | ui->progressBar->setValue(0); 81 | resultMsg->setIcon(QMessageBox::Critical); 82 | 83 | switch (exitCode) 84 | { 85 | case -1: 86 | resultMsg->setText("Invalid arguments passed to gba_mus_ripper_gui!"); 87 | break; 88 | 89 | case -2: 90 | resultMsg->setText("Unable to open file \"" + romPath + "\" for reading!"); 91 | break; 92 | 93 | case -3: 94 | resultMsg->setText("Invalid song table address specified."); 95 | break; 96 | 97 | case -4: 98 | resultMsg->setText("No sound engine was found. This ROM may not use the 'Sappy' sound engine."); 99 | break; 100 | 101 | case -5: 102 | resultMsg->setText("Invalid offset within GBA ROM."); 103 | break; 104 | 105 | case -6: 106 | resultMsg->setText("Song table address is outside the bounds of the ROM."); 107 | break; 108 | 109 | case -7: 110 | resultMsg->setText("Unable to parse song table."); 111 | break; 112 | 113 | case -8: 114 | #ifdef Q_OS_WIN32 115 | resultMsg->setText("Unable to find GBA Mus Ripper GUI executable: " + QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui.exe")); 116 | #else 117 | resultMsg->setText("Unable to find GBA Mus Ripper GUI executable: " + QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui")); 118 | #endif 119 | break; 120 | } 121 | resultMsg->exec(); 122 | } 123 | 124 | close(); 125 | } 126 | 127 | ProgressDialog::~ProgressDialog() 128 | { 129 | delete ui; 130 | } 131 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 633 10 | 302 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | GBA Mus Ripper GUI 21 | 22 | 23 | 24 | 25 | 26 | 10 27 | 10 28 | 151 29 | 16 30 | 31 | 32 | 33 | ROM file path: 34 | 35 | 36 | 37 | 38 | 39 | 10 40 | 60 41 | 191 42 | 16 43 | 44 | 45 | 46 | Output directory path: 47 | 48 | 49 | 50 | 51 | 52 | 524 53 | 239 54 | 101 55 | 31 56 | 57 | 58 | 59 | Start 60 | 61 | 62 | 63 | 64 | 65 | 10 66 | 30 67 | 611 68 | 33 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Browse... 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 10 88 | 80 89 | 611 90 | 33 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Browse... 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 10 110 | 152 111 | 221 112 | 17 113 | 114 | 115 | 116 | Split sound banks 117 | 118 | 119 | 120 | 121 | 122 | 10 123 | 176 124 | 361 125 | 17 126 | 127 | 128 | 129 | Give instruments GM-compatible names 130 | 131 | 132 | 133 | 134 | 135 | 10 136 | 200 137 | 281 138 | 17 139 | 140 | 141 | 142 | Don't use MIDI channel 10 143 | 144 | 145 | 146 | 147 | 148 | 10 149 | 224 150 | 211 151 | 17 152 | 153 | 154 | 155 | Output raw MIDIs 156 | 157 | 158 | 159 | 160 | 161 | 10 162 | 249 163 | 251 164 | 17 165 | 166 | 167 | 168 | XG-compliant MIDIs 169 | 170 | 171 | 172 | 173 | 174 | 10 175 | 120 176 | 351 177 | 33 178 | 179 | 180 | 181 | 182 | 183 | 184 | Manually specify address: 185 | 186 | 187 | 188 | 189 | 190 | 191 | false 192 | 193 | 194 | 195 | 196 | 197 | layoutWidget 198 | layoutWidget 199 | layoutWidget 200 | inputLabel 201 | outputLabel 202 | startButton 203 | splitSoundBanks 204 | giveGMNames 205 | avoidCh10 206 | outputRaw 207 | makeXGCompliant 208 | 209 | 210 | 211 | 212 | 0 213 | 0 214 | 633 215 | 23 216 | 217 | 218 | 219 | 220 | File 221 | 222 | 223 | 224 | 225 | 226 | Help 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | Exit 236 | 237 | 238 | 239 | 240 | About... 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /sound_font_ripper/sf2.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Sound Font main class 3 | * 4 | * This is part of the GBA SoundFont Ripper (c) 2012 by Bregalad 5 | * This is free and open source software. 6 | * 7 | * Notes : I tried to separate the GBA-related stuff and SF2 related stuff as much as possible in different classes 8 | * that way anyone can re-use this program for building SF2s out of different data. 9 | * 10 | * SF2 is a SoundFont file format. This class (and related classes) serves to create data to build 11 | * a SF2 file. For more details look at the doccument "Sound Font(R) Technical Doccumentation" version 2.01 12 | * which was used as a reference when writing these classes. 13 | * By the way I must tell this format is incredibly stupid and complex as opposed to what it could be and 14 | * I have no idea why people started to adopt this standard. But anyways they did... 15 | * 16 | * All SF2-related classes contains a field named sf2 that links to the SF2 main class to know which 17 | * SF2 file they relates to. (this would make it possible to build multiple SF2 files at a time). 18 | * 19 | * The building of a SF2 file is done in multiple passes : 20 | * - Creating samples 21 | * - Creating instruments 22 | * - Creating presets 23 | * - Output to the target file 24 | */ 25 | 26 | #include 27 | #include 28 | #include "sf2.h" 29 | #include "sf2_chunks.h" 30 | #include "gba_samples.h" 31 | 32 | //Constructor to change the default sample rate 33 | SF2::SF2(uint32_t sample_rate = 22050) : 34 | size(0), /*instruments(this),*/ 35 | infolist_chunk(new InfoListChunk(this)), 36 | sdtalist_chunk(new SdtaListChunk(this)), 37 | pdtalist_chunk(new HydraChunk(this)), 38 | default_sample_rate(sample_rate) 39 | {} 40 | 41 | SF2::~SF2() 42 | { 43 | delete infolist_chunk; 44 | delete sdtalist_chunk; 45 | delete pdtalist_chunk; 46 | } 47 | 48 | //Write data to the target file 49 | //(should only be called once !) 50 | void SF2::write(FILE *outfile) 51 | { 52 | out = outfile; 53 | // This function adds the "terminal" data in subchunks that are required 54 | // by the (retarded) SF2 standard 55 | add_terminals(); 56 | 57 | //Compute size of the entire file 58 | //(this will also compute the size of the chunks) 59 | size = 4; 60 | size += infolist_chunk->calcSize() + 8; 61 | size += sdtalist_chunk->calcSize() + 8; 62 | size += pdtalist_chunk->calcSize() + 8; 63 | 64 | //Write RIFF header 65 | fwrite("RIFF", 1, 4, out); 66 | fwrite(&size, 4, 1, out); 67 | fwrite("sfbk", 1, 4, out); 68 | 69 | //Write all 3 chunks 70 | infolist_chunk->write(); 71 | sdtalist_chunk->write(); 72 | pdtalist_chunk->write(); 73 | 74 | //Close output file 75 | fclose(out); 76 | } 77 | 78 | //Add terminal data in subchunks where it is required by the standard 79 | void SF2::add_terminals() 80 | { 81 | add_new_sample_header("EOS", 0, 0, 0, 0, 0, 0, 0); 82 | add_new_instrument("EOI"); 83 | add_new_inst_bag(); 84 | add_new_inst_generator(); 85 | add_new_inst_modulator(); 86 | add_new_preset("EOP", 255, 255); 87 | add_new_preset_bag(); 88 | add_new_preset_generator(); 89 | add_new_preset_modulator(); 90 | } 91 | 92 | //Add a brand new preset header to the list 93 | void SF2::add_new_preset(const char *name, int32_t patch, int32_t bank) 94 | { 95 | pdtalist_chunk->phdr_subchunk.add_preset(sfPresetHeader(this, name, patch, bank)); 96 | } 97 | 98 | //Add a brand new instrument 99 | void SF2::add_new_instrument(const char *name) 100 | { 101 | pdtalist_chunk->inst_subchunk.add_instrument(sfInst(this, name)); 102 | } 103 | 104 | //Add a new instrument bag to the instrument bag list 105 | //DO NOT use this to add a preset bag ! 106 | void SF2::add_new_inst_bag() 107 | { 108 | pdtalist_chunk->ibag_subchunk.add_bag(sfBag(this, false)); 109 | } 110 | 111 | //Add a new preset bag to the preset bag list 112 | //DO NOT use this to add an instrument bag ! 113 | void SF2::add_new_preset_bag() 114 | { 115 | pdtalist_chunk->pbag_subchunk.add_bag(sfBag(this, true)); 116 | } 117 | 118 | //Add a new modulator to the list 119 | void SF2::add_new_preset_modulator() 120 | { 121 | pdtalist_chunk->pmod_subchunk.add_modulator(sfModList(this)); 122 | } 123 | 124 | //Add a new blank generator to the list 125 | void SF2::add_new_preset_generator() 126 | { 127 | pdtalist_chunk->pgen_subchunk.add_generator(sfGenList(this)); 128 | } 129 | 130 | //Add a new customized generator to the list 131 | void SF2::add_new_preset_generator(SFGenerator operation, uint16_t value) 132 | { 133 | pdtalist_chunk->pgen_subchunk.add_generator(sfGenList(this, operation, genAmountType(value))); 134 | } 135 | 136 | //Add a new customized generator to the list 137 | void SF2::add_new_preset_generator(SFGenerator operation, uint8_t lo, uint8_t hi) 138 | { 139 | pdtalist_chunk->pgen_subchunk.add_generator(sfGenList(this, operation, genAmountType(lo, hi))); 140 | } 141 | 142 | //Add a new modulator to the list 143 | void SF2::add_new_inst_modulator() 144 | { 145 | pdtalist_chunk->imod_subchunk.add_modulator(sfModList(this)); 146 | } 147 | 148 | //Add a new blank generator to the list 149 | void SF2::add_new_inst_generator() 150 | { 151 | pdtalist_chunk->igen_subchunk.add_generator(sfGenList(this)); 152 | } 153 | 154 | //Add a new customized generator to the list 155 | void SF2::add_new_inst_generator(SFGenerator operation, uint16_t value) 156 | { 157 | pdtalist_chunk->igen_subchunk.add_generator(sfGenList(this, operation, genAmountType(value))); 158 | } 159 | 160 | //Add a new customized generator to the list 161 | void SF2::add_new_inst_generator(SFGenerator operation, uint8_t lo, uint8_t hi) 162 | { 163 | pdtalist_chunk->igen_subchunk.add_generator(sfGenList(this, operation, genAmountType(lo, hi))); 164 | } 165 | 166 | //Add a brand new header 167 | void SF2::add_new_sample_header(const char *name, int32_t start, int32_t end, int32_t start_loop, int32_t end_loop, int32_t sample_rate, int32_t original_pitch, int32_t pitch_correction) 168 | { 169 | pdtalist_chunk->shdr_subchunk.add_sample(sfSample(this, name, start, end, start_loop, end_loop, sample_rate, original_pitch, pitch_correction)); 170 | } 171 | 172 | // Add a new sample and create corresponding header 173 | void SF2::add_new_sample(FILE *file, SampleType type, const char *name, uint32_t pointer, uint32_t size, bool loop_flag, 174 | uint32_t loop_pos, uint32_t original_pitch, uint32_t pitch_correction, uint32_t sample_rate) 175 | { 176 | uint32_t dir_offset = sdtalist_chunk->smpl_subchunk.add_sample(file, type, pointer, size, loop_flag, loop_pos); 177 | // If the sample is looped const SF2 standard requires we add the 8 bytes 178 | // at the start of the loop at the end (what a dumb standard) 179 | uint32_t dir_end, dir_loop_end, dir_loop_start; 180 | 181 | if (loop_flag) 182 | { 183 | dir_end = dir_offset + size + 8; 184 | dir_loop_end = dir_offset + size; 185 | dir_loop_start = dir_offset + loop_pos; 186 | } 187 | else 188 | { 189 | dir_end = dir_offset + size; 190 | dir_loop_end = 0; 191 | dir_loop_start = 0; 192 | } 193 | 194 | // Create sample header and add it to the list 195 | add_new_sample_header(name, dir_offset, dir_end, dir_loop_start, dir_loop_end, sample_rate, original_pitch, pitch_correction); 196 | } 197 | 198 | uint16_t SF2::get_ibag_size() 199 | { 200 | return pdtalist_chunk->ibag_subchunk.bag_list.size(); 201 | } 202 | 203 | uint16_t SF2::get_igen_size() 204 | { 205 | return pdtalist_chunk->igen_subchunk.generator_list.size(); 206 | } 207 | 208 | uint16_t SF2::get_imod_size() 209 | { 210 | return pdtalist_chunk->imod_subchunk.modulator_list.size(); 211 | } 212 | 213 | uint16_t SF2::get_pbag_size() 214 | { 215 | return pdtalist_chunk->pbag_subchunk.bag_list.size(); 216 | } 217 | 218 | uint16_t SF2::get_pgen_size() 219 | { 220 | return pdtalist_chunk->pgen_subchunk.generator_list.size(); 221 | } 222 | 223 | uint16_t SF2::get_pmod_size() 224 | { 225 | return pdtalist_chunk->pmod_subchunk.modulator_list.size(); 226 | } 227 | -------------------------------------------------------------------------------- /song_ripper/midi.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Standard MIDI File main class 3 | * 4 | * This is part of the GBA Songripper (c) 2012 by Bregalad 5 | * This is free and open source software. 6 | * 7 | * Notes : I tried to separate the GBA-related stuff and MIDI related stuff as much as possible in different classes 8 | * that way anyone can re-use this program for building MIDIs out of different data. 9 | * 10 | * SMF is a sequenced music file format based on MIDI interface. This class (and related classes) serves to create 11 | * data to build a format 0 MIDI file. I restricted it to format 0 MIDIS because it was simpler to only have one 12 | * single track to deal with. It wouldn't be too hard to upgrade this class to support format 1 MIDIs, though. 13 | * 14 | * All MIDI classes contains a field named midi that links to the MIDI main class to know which 15 | * MIDI file they relates to. (this would make it possible to build multiple SF2 files at a time). 16 | * Adding MIDI events is made extremely simple by a set of functions in this class. However if this is not sufficient 17 | * it is always possible to use the general purpose add_event() function, or adding data bytes directly to output. 18 | * 19 | * The MIDI standard uses the concept of delta_times before events. Since this is very much unpractical, this 20 | * class supposes the user to simply increment the time_ctr variable when needed, and not worry about delta 21 | * time values anmore. 22 | * 23 | * The building of a MIDI file is done in two passes : 24 | * - Creating the sequence data which is cached in memory 25 | * - Writing sequence data from cache to output file 26 | */ 27 | 28 | #include 29 | #include "midi.h" 30 | 31 | // Constructor : Initialise the MIDI object 32 | MIDI::MIDI(uint16_t delta_time) 33 | { 34 | delta_time_per_beat = delta_time; 35 | for(int32_t i = 15; i >= 0; --i) 36 | { 37 | last_rpn_type[i] = -1; 38 | last_nrpn_type[i] = -1; 39 | last_type[i] = -1; 40 | chn_reorder[i] = i; 41 | } 42 | last_chanel = -1; 43 | time_ctr = 0; 44 | } 45 | 46 | //Write cached data to midi file 47 | void MIDI::write(FILE *out) 48 | { 49 | //Add end-of-track meta event 50 | data.push_back(0x00); 51 | data.push_back(0xff); 52 | data.push_back(0x2f); 53 | data.push_back(0x00); 54 | 55 | //Write MIDI header 56 | struct 57 | { 58 | char name[4]; 59 | uint32_t hdr_len; 60 | uint16_t format; 61 | uint16_t ntracks; 62 | uint16_t division; 63 | } mthd_chunk = 64 | { 65 | {'M','T','h','d'}, 66 | // 6, // Length of header in bytes (always 6) 67 | // 0, // We use MIDI format 0 68 | // 1, // There is one single track 69 | 70 | // Swap the endianness 71 | 0x06000000, 72 | 0x0000, 73 | 0x0100, 74 | (delta_time_per_beat << 8) | (delta_time_per_beat >> 8) 75 | }; 76 | fwrite(&mthd_chunk, 1, 14, out); 77 | 78 | //Write MIDI track data 79 | //we use SMF-0 standard therefore there is only a single track :) 80 | uint32_t s = data.size(); 81 | struct 82 | { 83 | char name[4]; 84 | uint32_t size; 85 | } trdata = 86 | { 87 | {'M','T','r','k'}, 88 | // Again, swap endianness 89 | (s << 24) | ((s & 0x0000ff00) << 8) | ((s & 0x00ff0000) >> 8) | (s >> 24) 90 | }; 91 | fwrite(&trdata, 1, 8, out); 92 | 93 | //Write the track itself 94 | fwrite(&data[0], data.size(), 1, out); 95 | 96 | fclose(out); 97 | } 98 | 99 | //Add delta time in MIDI variable length coding 100 | void MIDI::add_vlength_code(int32_t code) 101 | { 102 | char word1 = code & 0x7f; 103 | char word2 = (code >> 7) & 0x7f; 104 | char word3 = (code >> 14) & 0x7f; 105 | char word4 = (code >> 21) & 0x7f; 106 | 107 | if (word4 != 0) 108 | { 109 | data.push_back(word4 | 0x80); 110 | data.push_back(word3 | 0x80); 111 | data.push_back(word2 | 0x80); 112 | } 113 | else if (word3 != 0) 114 | { 115 | data.push_back(word3 | 0x80); 116 | data.push_back(word2 | 0x80); 117 | } 118 | else if (word2 != 0) 119 | { 120 | data.push_back(word2 | 0x80); 121 | } 122 | data.push_back(word1); 123 | } 124 | 125 | //Add delta time event 126 | void MIDI::add_delta_time() 127 | { 128 | add_vlength_code(time_ctr); 129 | //Reset time counter to zero 130 | time_ctr = 0; 131 | } 132 | 133 | void MIDI::add_event(MIDIEventType type, int32_t chn, char param1, char param2) 134 | { 135 | add_delta_time(); 136 | if (chn != last_chanel || type != last_event_type) 137 | { 138 | last_chanel = chn; 139 | last_event_type = type; 140 | data.push_back((type << 4) | chn_reorder[chn]); 141 | } 142 | data.push_back(param1); 143 | data.push_back(param2); 144 | } 145 | 146 | void MIDI::add_event(MIDIEventType type, int32_t chn, char param) 147 | { 148 | add_delta_time(); 149 | if (chn != last_chanel || type != last_event_type) 150 | { 151 | last_chanel = chn; 152 | last_event_type = type; 153 | data.push_back((type << 4) | chn_reorder[chn]); 154 | } 155 | data.push_back(param); 156 | } 157 | 158 | //Add key on event 159 | void MIDI::add_note_on(int32_t chn, char key, char vel) 160 | { 161 | add_event(NOTEON, chn, key, vel); 162 | } 163 | 164 | //Add key off event 165 | void MIDI::add_note_off(int32_t chn, char key, char vel) 166 | { 167 | add_event(NOTEOFF, chn, key, vel); 168 | } 169 | 170 | //Add controller event 171 | void MIDI::add_controller(int32_t chn, char ctrl, char value) 172 | { 173 | add_event(CONTROLLER, chn, ctrl, value); 174 | } 175 | 176 | //Add channel aftertouch 177 | void MIDI::add_chanaft(int32_t chn, char value) 178 | { 179 | add_event(CHNAFT, chn, value); 180 | } 181 | 182 | //Add conventional program change event 183 | void MIDI::add_pchange(int32_t chn, char number) 184 | { 185 | add_event(PCHANGE, chn, number); 186 | } 187 | 188 | //Add pitch bend event 189 | void MIDI::add_pitch_bend(int32_t chn, int16_t value) 190 | { 191 | char lo = value & 0x7f; 192 | char hi = (value >> 7) & 0x7f; 193 | add_event(PITCHBEND, chn, lo, hi); 194 | } 195 | 196 | //Add pitch bend event with only the MSB used 197 | void MIDI::add_pitch_bend(int32_t chn, char value) 198 | { 199 | add_event(PITCHBEND, chn, 0, value); 200 | } 201 | 202 | //Add RPN event 203 | void MIDI::add_RPN(int32_t chn, int16_t type, int16_t value) 204 | { 205 | if (last_rpn_type[chn] != type || last_type[chn] != 0) 206 | { 207 | last_rpn_type[chn] = type; 208 | last_type[chn] = 0; 209 | add_event(CONTROLLER, chn, 101, type >> 7); 210 | add_event(CONTROLLER, chn, 100, type & 0x7f); 211 | } 212 | add_event(CONTROLLER, chn, 6, value >> 7); 213 | 214 | if ((value & 0x7f) != 0) 215 | add_event(CONTROLLER, chn, 38, value & 0x7f); 216 | } 217 | 218 | //Add NRPN event 219 | void MIDI::add_NRPN(int32_t chn, int16_t type, int16_t value) 220 | { 221 | if (last_nrpn_type[chn] != type || last_type[chn] != 1) 222 | { 223 | last_nrpn_type[chn] = type; 224 | last_type[chn] = 1; 225 | add_event(CONTROLLER, chn, 99, type >> 7); 226 | add_event(CONTROLLER, chn, 98, type & 0x7f); 227 | } 228 | add_event(CONTROLLER, chn, 6, value >> 7); 229 | if ((value & 0x7f) != 0) 230 | add_event(CONTROLLER, chn, 38, value & 0x7f); 231 | } 232 | 233 | void MIDI::add_marker(const char *text) 234 | { 235 | add_delta_time(); 236 | data.push_back(-1); 237 | //Add text meta event if marker is false, marker meta even if true 238 | data.push_back(6); 239 | size_t len = strlen(text); 240 | add_vlength_code(len); 241 | //Add text itself 242 | data.insert(data.end(), text, text + len); 243 | } 244 | 245 | void MIDI::add_sysex(const char sysex_data[], size_t len) 246 | { 247 | add_delta_time(); 248 | data.push_back(0xf0); 249 | //Actually variable length code 250 | add_vlength_code(len + 1); 251 | 252 | data.insert(data.end(), sysex_data, sysex_data + len); 253 | data.push_back(0xf7); 254 | } 255 | 256 | void MIDI::add_tempo(double tempo) 257 | { 258 | int32_t t = int32_t(60000000.0 / tempo); 259 | char t1 = char(t); 260 | char t2 = char(t >> 8); 261 | char t3 = char(t >> 16); 262 | 263 | add_delta_time(); 264 | data.push_back(0xff); 265 | data.push_back(0x51); 266 | data.push_back(0x03); 267 | data.push_back(t3); 268 | data.push_back(t2); 269 | data.push_back(t1); 270 | } 271 | -------------------------------------------------------------------------------- /sound_font_ripper/gba_samples.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of GBA Sound Riper 3 | * (c) 2012, 2014 Bregalad 4 | * This is free and open source software 5 | * 6 | * This class deals with internal representation of 7 | * GBA samples and converts them to SF2 samples 8 | */ 9 | 10 | #include "gba_samples.h" 11 | #include 12 | #include 13 | #include "../hex_string.h" 14 | #include 15 | 16 | extern FILE *inGBA; 17 | extern FILE *psg_data; 18 | extern FILE *goldensun_synth; 19 | 20 | int32_t GBASamples::build_sample(uint32_t pointer) 21 | { // Do nothing if sample already exists 22 | for(int32_t i=samples_list.size()-1; i >= 0; --i) 23 | if (samples_list[i] == pointer) return i; 24 | 25 | // Read sample data 26 | if (fseek(inGBA, pointer, SEEK_SET)) throw -1; 27 | 28 | struct 29 | { 30 | uint32_t loop; 31 | uint32_t pitch; 32 | uint32_t loop_pos; 33 | uint32_t len; 34 | } 35 | hdr; 36 | fread(&hdr, 4, 4, inGBA); 37 | 38 | //Now we should make sure the data is coherent, and reject 39 | //the samples if errors are suspected 40 | 41 | //Detect invalid samples 42 | bool loop_en; 43 | bool bdpcm_en = false; 44 | 45 | if (hdr.loop == 0x40000000) 46 | loop_en = true; 47 | else if (hdr.loop == 0x00000000) 48 | loop_en = false; 49 | else if (hdr.loop == 0x1) 50 | { 51 | bdpcm_en = true; // Detect compressed samples 52 | loop_en = false; 53 | } 54 | else 55 | throw -1; // Invalid loop -> return error 56 | 57 | // Compute SF2 base note and fine tune from GBA pitch 58 | // GBA pitch is 1024 * Mid_C frequency 59 | double delta_note = 12 * log2(sf2->default_sample_rate * 1024.0 / hdr.pitch); 60 | double int_delta_note = round(delta_note); 61 | uint32_t pitch_correction = int32_t((int_delta_note - delta_note) * 100); 62 | uint32_t original_pitch = 60 + (int32_t)int_delta_note; 63 | 64 | // Detect Golden Sun samples 65 | if (goldensun_synth && hdr.len == 0 && hdr.loop_pos == 0) 66 | { 67 | if (fgetc(inGBA) != 0x80) throw -1; 68 | uint8_t type = fgetc(inGBA); 69 | switch(type) 70 | { 71 | case 0: // Square wave 72 | { 73 | std::string name = "Square @0x" + hex(pointer); 74 | uint8_t duty_cycle = fgetc(inGBA); 75 | uint8_t change_speed = fgetc(inGBA); 76 | if (change_speed == 0) 77 | { // Square wave with constant duty cycle 78 | uint32_t base_pointer = 128 + 64 * (duty_cycle >> 2); 79 | sf2->add_new_sample(goldensun_synth, UNSIGNED_8, name.c_str(), base_pointer, 64, true, 0, original_pitch, pitch_correction); 80 | } 81 | else 82 | { // Sqaure wave with variable duty cycle, not exact, but sounds close enough 83 | sf2->add_new_sample(goldensun_synth, UNSIGNED_8, name.c_str(), 128, 8192, true, 0, original_pitch, pitch_correction); 84 | } 85 | } break; 86 | 87 | case 1: // Saw wave 88 | { 89 | std::string name = "Saw @0x" + hex(pointer); 90 | sf2->add_new_sample(goldensun_synth, UNSIGNED_8, name.c_str(), 0, 64, true, 0, original_pitch, pitch_correction); 91 | } break; 92 | 93 | case 2: // Triangle wave 94 | { 95 | std::string name = "Triangle @0x" + hex(pointer); 96 | sf2->add_new_sample(goldensun_synth, UNSIGNED_8, name.c_str(), 64, 64, true, 0, original_pitch, pitch_correction); 97 | } break; 98 | 99 | default : 100 | throw -1; 101 | } 102 | } 103 | else 104 | { 105 | //Prevent samples which are way too long or too short 106 | if (hdr.len < 16 || hdr.len > 0x3FFFFF) throw -1; 107 | 108 | //Prevent samples with illegal loop point from happening 109 | if (hdr.loop_pos > hdr.len-8) 110 | { 111 | puts("Warning : Illegal loop point detected\n"); 112 | hdr.loop_pos = 0; 113 | } 114 | 115 | // Create (poetic) instrument name 116 | std::string name = (bdpcm_en ? "BDPCM @0x" : "Sample @0x") + hex(pointer); 117 | 118 | // Add the sample to output 119 | sf2->add_new_sample(inGBA, bdpcm_en ? BDPCM : SIGNED_8, name.c_str(), pointer + 16, hdr.len, loop_en, hdr.loop_pos, original_pitch, pitch_correction); 120 | } 121 | samples_list.push_back(pointer); 122 | return samples_list.size() - 1; 123 | } 124 | 125 | //Build game boy channel 3 sample 126 | int32_t GBASamples::build_GB3_samples(uint32_t pointer) 127 | { 128 | // Do nothing if sample already exists 129 | for(int32_t i=samples_list.size()-1; i >= 0; --i) 130 | if (samples_list[i] == pointer) return i; 131 | 132 | std::string name = "GB3 @0x" + hex(pointer); 133 | 134 | sf2->add_new_sample(inGBA, GAMEBOY_CH3, (name + 'A').c_str(), pointer, 256, true, 0, 53, 24, 22050); 135 | sf2->add_new_sample(inGBA, GAMEBOY_CH3, (name + 'B').c_str(), pointer, 128, true, 0, 65, 24, 22050); 136 | sf2->add_new_sample(inGBA, GAMEBOY_CH3, (name + 'C').c_str(), pointer, 64, true, 0, 77, 24, 22050); 137 | sf2->add_new_sample(inGBA, GAMEBOY_CH3, (name + 'D').c_str(), pointer, 32, true, 0, 89, 24, 22050); 138 | 139 | // We have to to add multiple entries to have the size of the list in sync 140 | // with the numeric indexes of samples.... 141 | for(int32_t i=4; i!=0; --i) samples_list.push_back(pointer); 142 | return samples_list.size() - 1; 143 | } 144 | 145 | //Build square wave sample 146 | int32_t GBASamples::build_pulse_samples(uint32_t duty_cycle) 147 | { // Do nothing if sample already exists 148 | for(int32_t i=samples_list.size()-1; i >= 0; --i) 149 | if (samples_list[i] == duty_cycle) return i; 150 | 151 | std::string name = "square "; 152 | switch(duty_cycle) 153 | { 154 | case 0: 155 | name += "12.5%"; 156 | break; 157 | 158 | default: 159 | name += "25%"; 160 | break; 161 | 162 | case 2: 163 | name += "50%"; 164 | break; 165 | } 166 | 167 | //This data is referenced to my set of recordings 168 | //stored in "psg_data.raw" 169 | const int32_t pointer_tbl[3][5] = 170 | { 171 | {0x0000, 0x2166, 0x3c88, 0x4bd2, 0x698a}, 172 | {0x7798, 0x903e, 0xa15e, 0xb12c, 0xbf4a}, 173 | {0xc958, 0xe200, 0xf4ec, 0x10534, 0x11360} 174 | }; 175 | 176 | const int32_t size_tbl[3][5] = 177 | { 178 | {0x10b3, 0xd91, 0x7a5, 0xdec, 0x707}, 179 | {0xc53, 0x890, 0x7e7, 0x70f, 0x507}, 180 | {0xc54, 0x976, 0x824, 0x716, 0x36b} 181 | }; 182 | 183 | const int32_t loop_size[5] = {689, 344, 172, 86, 43}; 184 | 185 | for(int32_t i = 0; i < 5; i++) 186 | { 187 | sf2->add_new_sample(psg_data, SIGNED_16, (name + char('A' + i)).c_str(), pointer_tbl[duty_cycle][i], size_tbl[duty_cycle][i], 188 | true, size_tbl[duty_cycle][i]-loop_size[i], 36 + 12 * i, 38, 44100); 189 | samples_list.push_back(duty_cycle); 190 | } 191 | return samples_list.size()-1; 192 | } 193 | 194 | //Build white noise sample 195 | int32_t GBASamples::build_noise_sample(bool metallic, int32_t key) 196 | { 197 | //prevent out of range keys 198 | if (key < 42) key = 42; 199 | if (key > 77) key = 76; 200 | 201 | uint32_t num = metallic ? 3 + (key-42) : 80 + (key-42); 202 | 203 | // Do nothing if sample already exists 204 | for(int32_t i=samples_list.size()-1; i >= 0; --i) 205 | if (samples_list[i] == num) return i; 206 | 207 | std::string name = std::string("Noise ") + std::string(metallic ? "metallic " : "normal ") + std::to_string(key); 208 | 209 | const int32_t pointer_tbl[] = 210 | { 211 | 72246, 160446, 248646, 336846, 425046, 513246, 601446, 689646, 777846, 212 | 866046, 954246, 1042446, 1130646, 1218846, 1307046, 1395246, 1483446, 1571646, 1659846, 213 | 1748046, 1836246, 1924446, 2012646, 2100846, 2189046, 2277246, 2387493, 2475690, 2552863, 214 | 2619011, 2674134, 2718233, 2756819, 2789893, 2817455, 2839504, 2856041, 2867066, 2872578 215 | }; 216 | 217 | const int32_t normal_len_tbl[] = 218 | { 219 | 88200, 88200, 88200, 88200, 88200, 88200, 88200, 88200, 88200, 88200, 220 | 88200, 88200, 88200, 88200, 88200, 88200, 88200, 88200, 88200, 88200, 88200, 88200, 88200, 221 | 88200, 88200, 110247, 88197, 77173, 66148, 55123, 44099, 38586, 33074, 27562, 22049, 16537, 222 | 11025, 5512, 2756 223 | }; 224 | 225 | const int32_t metallic_len_tbl[] = 226 | { 43755, 38286, 32817, 27347, 21878, 19143, 16408, 13674, 10939, 9572, 227 | 8204, 6837, 5469, 4786, 4102, 3418, 2735, 2393, 2051, 1709, 1367, 1196, 1026, 855, 684, 228 | 598, 513, 427, 342, 299, 256, 214, 171, 150, 128, 107, 85, 64 229 | }; 230 | 231 | sf2->add_new_sample(psg_data, UNSIGNED_8, name.c_str(), pointer_tbl[key-42], 232 | metallic ? metallic_len_tbl[key-42] : normal_len_tbl[key-42], true, 0, key, 0, 44100); 233 | 234 | samples_list.push_back(num); 235 | return samples_list.size() - 1; 236 | } 237 | -------------------------------------------------------------------------------- /sappy_detector/sappy_detector.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * GBA Sappy Engine Detector (c) 2012, 2014 by Bregalad 3 | * This is free and open source software 4 | * 5 | * This programs detects if a snappy sound engine is present in a given GBA ROM. 6 | * If an engine is present it returns a pointer to the instrument list. 7 | * If no engine is present, it returns the value 0 8 | * It's not 100% accurate, so sometimes it might produce erroneous results 9 | * 10 | */ 11 | 12 | #include "sappy_detector.h" 13 | 14 | const char *const sr_lookup[16] = 15 | { 16 | "invalid", "5734 Hz", "7884 Hz", "10512 Hz", "13379 Hz", "15768 Hz", "18157 Hz", 17 | "21024 Hz", "26758 Hz", "31536 Hz", "36314 Hz", "40137 Hz", "42048 Hz", "invalid", "invalid", "invalid" 18 | }; 19 | 20 | static void print_instructions() 21 | { 22 | puts 23 | ( 24 | "GBA Sappy Engine Detector (c) 2015 by Bregalad and loveemu\n" 25 | "Usage: sappy_detector game.gba\n" 26 | ); 27 | return; 28 | } 29 | 30 | static uint8_t m4a_bin_selectsong[0x1E] = 31 | { 32 | 0x00, 0xB5, 0x00, 0x04, 0x07, 0x4A, 0x08, 0x49, 33 | 0x40, 0x0B, 0x40, 0x18, 0x83, 0x88, 0x59, 0x00, 34 | 0xC9, 0x18, 0x89, 0x00, 0x89, 0x18, 0x0A, 0x68, 35 | 0x01, 0x68, 0x10, 0x1C, 0x00, 0xF0, 36 | }; 37 | 38 | #define M4A_MAIN_PATT_COUNT 1 39 | #define M4A_MAIN_LEN 2 40 | static uint8_t m4a_bin_main[M4A_MAIN_PATT_COUNT][M4A_MAIN_LEN] = 41 | { 42 | {0x00, 0xB5} 43 | }; 44 | 45 | #define M4A_INIT_PATT_COUNT 2 46 | #define M4A_INIT_LEN 2 47 | 48 | // byte reader/writer (little-endian) 49 | static inline uint32_t read_u32 (uint8_t *data) { return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24); } 50 | 51 | static long memsearch(uint8_t *dst, size_t dstsize, uint8_t *src, size_t srcsize, size_t dst_offset, size_t alignment, int32_t diff_threshold) 52 | { 53 | if (alignment == 0) 54 | { 55 | return -1; 56 | } 57 | 58 | // alignment 59 | if (dst_offset % alignment != 0) 60 | { 61 | dst_offset += alignment - (dst_offset % alignment); 62 | } 63 | 64 | for (size_t offset = dst_offset; (offset + srcsize) <= dstsize; offset += alignment) 65 | { 66 | // memcmp(&dst[offset], src, srcsize) 67 | int32_t diff = 0; 68 | for (size_t i = 0; i < srcsize; i++) 69 | { 70 | if (dst[offset + i] != src[i]) 71 | { 72 | diff++; 73 | } 74 | if (diff > diff_threshold) 75 | { 76 | break; 77 | } 78 | } 79 | if (diff <= diff_threshold) 80 | { 81 | return offset; 82 | } 83 | } 84 | return -1; 85 | } 86 | 87 | static bool is_valid_offset(uint32_t offset, uint32_t romsize) 88 | { 89 | return (offset < romsize); 90 | } 91 | 92 | static bool is_gba_rom_address(uint32_t address) 93 | { 94 | uint8_t region = (address >> 24) & 0xFE; 95 | return (region == 8); 96 | } 97 | 98 | static uint32_t gba_address_to_offset(uint32_t address) 99 | { 100 | if (!is_gba_rom_address(address)) 101 | { 102 | fprintf(stderr, "Warning: the address $%08X is not a valid ROM address!\n", address); 103 | } 104 | return address & 0x01FFFFFF; 105 | } 106 | 107 | /* Thanks to loveeemu for this routine, more accurate than mine ! Slightly adapted. */ 108 | #define M4A_OFFSET_SONGTABLE 40 109 | static long m4a_searchblock(uint8_t *gbarom, size_t gbasize) 110 | { 111 | long m4a_selectsong_offset = -1; 112 | long m4a_main_offset = -1; 113 | 114 | long m4a_selectsong_search_offset = 0; 115 | while (m4a_selectsong_search_offset != -1) 116 | { 117 | m4a_selectsong_offset = memsearch(gbarom, gbasize, m4a_bin_selectsong, sizeof(m4a_bin_selectsong), m4a_selectsong_search_offset, 1, 0); 118 | if (m4a_selectsong_offset != -1) 119 | { 120 | #ifdef QT_DEBUG 121 | fprintf(stdout, "selectsong candidate: $%08X\n", m4a_selectsong_offset); 122 | #endif 123 | 124 | // obtain song table address 125 | uint32_t m4a_songtable_address = read_u32(&gbarom[m4a_selectsong_offset + M4A_OFFSET_SONGTABLE]); 126 | if (!is_gba_rom_address(m4a_songtable_address)) 127 | { 128 | #ifdef QT_DEBUG 129 | fprintf(stdout, "Song table address error: not a ROM address $%08X\n", m4a_songtable_address); 130 | #endif 131 | m4a_selectsong_search_offset = m4a_selectsong_offset + 1; 132 | continue; 133 | } 134 | uint32_t m4a_songtable_offset_tmp = gba_address_to_offset(m4a_songtable_address); 135 | if (!is_valid_offset(m4a_songtable_offset_tmp + 4 - 1, gbasize)) 136 | { 137 | #ifdef QT_DEBUG 138 | fprintf(stdout, "Song table address error: address out of range $%08X\n", m4a_songtable_address); 139 | #endif 140 | m4a_selectsong_search_offset = m4a_selectsong_offset + 1; 141 | continue; 142 | } 143 | 144 | // song table must have more than one song 145 | int32_t validsongcount = 0; 146 | for (int32_t songindex = 0; validsongcount < 1; songindex++) 147 | { 148 | uint32_t songaddroffset = m4a_songtable_offset_tmp + (songindex * 8); 149 | if (!is_valid_offset(songaddroffset + 4 - 1, gbasize)) 150 | { 151 | break; 152 | } 153 | 154 | uint32_t songaddr = read_u32(&gbarom[songaddroffset]); 155 | if (songaddr == 0) 156 | { 157 | continue; 158 | } 159 | 160 | if (!is_gba_rom_address(songaddr)) 161 | { 162 | #ifdef QT_DEBUG 163 | fprintf(stdout, "Song address error: not a ROM address $%08X\n", songaddr); 164 | #endif 165 | break; 166 | } 167 | if (!is_valid_offset(gba_address_to_offset(songaddr) + 4 - 1, gbasize)) 168 | { 169 | #ifdef QT_DEBUG 170 | fprintf(stdout, "Song address error: address out of range $%08X\n", songaddr); 171 | #endif 172 | break; 173 | } 174 | validsongcount++; 175 | } 176 | if (validsongcount < 1) 177 | { 178 | m4a_selectsong_search_offset = m4a_selectsong_offset + 1; 179 | continue; 180 | } 181 | break; 182 | } 183 | else 184 | { 185 | m4a_selectsong_search_offset = -1; 186 | } 187 | } 188 | if (m4a_selectsong_offset == -1) 189 | { 190 | return -1; 191 | } 192 | 193 | uint32_t m4a_main_offset_tmp = m4a_selectsong_offset; 194 | if (!is_valid_offset(m4a_main_offset_tmp + M4A_MAIN_LEN - 1, gbasize)) 195 | { 196 | return -1; 197 | } 198 | while (m4a_main_offset_tmp > 0 && m4a_main_offset_tmp > ((uint32_t) m4a_selectsong_offset - 0x20)) 199 | { 200 | for (int32_t mainpattern = 0; mainpattern < M4A_MAIN_PATT_COUNT; mainpattern++) 201 | { 202 | if (memcmp(&gbarom[m4a_main_offset_tmp], &m4a_bin_main[mainpattern][0], M4A_INIT_LEN) == 0) 203 | { 204 | m4a_main_offset = (long) m4a_main_offset_tmp; 205 | break; 206 | } 207 | } 208 | m4a_main_offset_tmp--; 209 | } 210 | return m4a_main_offset; 211 | } 212 | 213 | typedef struct 214 | { 215 | uint32_t polyphony : 4; 216 | uint32_t main_vol : 4; 217 | uint32_t sampling_rate_index : 4; 218 | uint32_t dac_bits : 4; 219 | } 220 | sound_engine_param_t; 221 | 222 | static sound_engine_param_t sound_engine_param(uint32_t data) 223 | { 224 | sound_engine_param_t s; 225 | s.polyphony = (data & 0x000F00) >> 8; 226 | s.main_vol = (data & 0x00F000) >> 12; 227 | s.sampling_rate_index = (data & 0x0F0000) >> 16; 228 | s.dac_bits = 17-((data & 0xF00000) >> 20); 229 | return s; 230 | } 231 | 232 | // Test if an area of ROM is eligible to be the base pointer 233 | static bool test_pointer_validity(uint32_t *data, uint32_t inGBA_length) 234 | { 235 | sound_engine_param_t params = sound_engine_param(data[0]); 236 | 237 | /* Compute (supposed ?) address of song table */ 238 | uint32_t song_tbl_adr = (data[2] & 0x3FFFFFF) + 12 * data[1]; 239 | 240 | /* Prevent illegal values for all fields */ 241 | return params.main_vol != 0 242 | && params.polyphony <= 12 243 | && params.dac_bits >= 6 244 | && params.dac_bits <= 9 245 | && params.sampling_rate_index >= 1 246 | && params.sampling_rate_index <= 12 247 | && song_tbl_adr < inGBA_length 248 | && data[1] < 256 249 | &&((data[0] & 0xff000000) == 0); 250 | } 251 | 252 | int32_t sappy_detector(const int32_t argc, string argv) 253 | { 254 | if (argc != 1) print_instructions(); 255 | puts("GBA Sappy Engine Detector (c) 2015 by Bregalad and loveemu\n"); 256 | 257 | FILE *inGBA = fopen(argv.c_str(), "rb"); 258 | if (!inGBA) 259 | { 260 | fprintf(stderr, "Error: File %s can't be opened for reading.\n", argv.c_str()); 261 | return -1; 262 | } 263 | 264 | /* Get the size of the input GBA file */ 265 | fseek(inGBA, 0L, SEEK_END); 266 | const size_t inGBA_length = ftell(inGBA); 267 | 268 | uint8_t *inGBA_dump = (uint8_t*)malloc(inGBA_length); 269 | if (!inGBA_dump) 270 | { 271 | fprintf(stderr, "Error: can't allocate memory for ROM dump.\n"); 272 | return -2; 273 | } 274 | 275 | fseek(inGBA, 0L, SEEK_SET); 276 | uint32_t errcode = fread(inGBA_dump, 1, inGBA_length, inGBA); 277 | if (errcode != inGBA_length) 278 | { 279 | fprintf(stderr, "Error: can't dump ROM file. %x\n", errcode); 280 | return -3; 281 | } 282 | fclose(inGBA); 283 | 284 | int32_t offset = m4a_searchblock(inGBA_dump, inGBA_length); 285 | 286 | if (offset < 0) 287 | { 288 | /* If no address were told manually and nothing was detected.... */ 289 | puts("No sound engine was found."); 290 | return -4; 291 | } 292 | printf("Sound engine detected at offset: 0x%x\n", offset); 293 | 294 | /* Test validity of engine offset with -16 and -32 */ 295 | bool valid_m16 = test_pointer_validity((uint32_t*)(inGBA_dump + offset - 16), inGBA_length); // For most games 296 | bool valid_m32 = test_pointer_validity((uint32_t*)(inGBA_dump + offset - 32), inGBA_length); // For pokémon 297 | 298 | /* If neither is found there is an error */ 299 | if (!valid_m16 && !valid_m32) 300 | { 301 | puts("Only a partial sound engine was found."); 302 | return -5; 303 | } 304 | offset -= valid_m16 ? 16 : 32; 305 | 306 | uint32_t *data = (uint32_t*)(inGBA_dump + offset); 307 | uint32_t song_tbl_adr = (data[2] & 0x3FFFFFF) + 12 * data[1]; 308 | sound_engine_param_t params = sound_engine_param(data[0]); 309 | 310 | //Read # of song levels 311 | printf("# of song levels: %d\n", data[1]); 312 | 313 | // At this point we can be certain we detected the real thing. 314 | printf 315 | ( 316 | "Engine parameters:\n" 317 | "Main Volume: %u Polyphony: %u channels, Dac: %u bits, Sampling rate: %s\n" 318 | "Song table located at: 0x%x\n", 319 | params.main_vol, 320 | params.polyphony, 321 | 17-params.dac_bits, 322 | sr_lookup[params.sampling_rate_index], 323 | song_tbl_adr 324 | ); 325 | 326 | free(inGBA_dump); 327 | 328 | /* Return the offset of sappy info to the operating system */ 329 | return offset; 330 | } 331 | -------------------------------------------------------------------------------- /moc_predefs.h: -------------------------------------------------------------------------------- 1 | #define __SSP_STRONG__ 3 2 | #define __DBL_MIN_EXP__ (-1021) 3 | #define __cpp_attributes 200809 4 | #define __UINT_LEAST16_MAX__ 0xffff 5 | #define __ATOMIC_ACQUIRE 2 6 | #define __FLT_MIN__ 1.17549435082228750797e-38F 7 | #define __GCC_IEC_559_COMPLEX 2 8 | #define __cpp_aggregate_nsdmi 201304 9 | #define __UINT_LEAST8_TYPE__ unsigned char 10 | #define __SIZEOF_FLOAT80__ 16 11 | #define __INTMAX_C(c) c ## L 12 | #define __CHAR_BIT__ 8 13 | #define __UINT8_MAX__ 0xff 14 | #define __WINT_MAX__ 0xffffffffU 15 | #define __cpp_static_assert 200410 16 | #define __ORDER_LITTLE_ENDIAN__ 1234 17 | #define __SIZE_MAX__ 0xffffffffffffffffUL 18 | #define __WCHAR_MAX__ 0x7fffffff 19 | #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1 1 20 | #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_2 1 21 | #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 1 22 | #define __DBL_DENORM_MIN__ double(4.94065645841246544177e-324L) 23 | #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 1 24 | #define __GCC_ATOMIC_CHAR_LOCK_FREE 2 25 | #define __GCC_IEC_559 2 26 | #define __FLT_EVAL_METHOD__ 0 27 | #define __unix__ 1 28 | #define __cpp_binary_literals 201304 29 | #define __GCC_ATOMIC_CHAR32_T_LOCK_FREE 2 30 | #define __x86_64 1 31 | #define __cpp_variadic_templates 200704 32 | #define __UINT_FAST64_MAX__ 0xffffffffffffffffUL 33 | #define __SIG_ATOMIC_TYPE__ int 34 | #define __DBL_MIN_10_EXP__ (-307) 35 | #define __FINITE_MATH_ONLY__ 0 36 | #define __cpp_variable_templates 201304 37 | #define __GNUC_PATCHLEVEL__ 1 38 | #define __UINT_FAST8_MAX__ 0xff 39 | #define __has_include(STR) __has_include__(STR) 40 | #define __DEC64_MAX_EXP__ 385 41 | #define __INT8_C(c) c 42 | #define __UINT_LEAST64_MAX__ 0xffffffffffffffffUL 43 | #define __SHRT_MAX__ 0x7fff 44 | #define __LDBL_MAX__ 1.18973149535723176502e+4932L 45 | #define __UINT_LEAST8_MAX__ 0xff 46 | #define __GCC_ATOMIC_BOOL_LOCK_FREE 2 47 | #define __UINTMAX_TYPE__ long unsigned int 48 | #define __linux 1 49 | #define __DEC32_EPSILON__ 1E-6DF 50 | #define __OPTIMIZE__ 1 51 | #define __unix 1 52 | #define __UINT32_MAX__ 0xffffffffU 53 | #define __GXX_EXPERIMENTAL_CXX0X__ 1 54 | #define __LDBL_MAX_EXP__ 16384 55 | #define __WINT_MIN__ 0U 56 | #define __linux__ 1 57 | #define __SCHAR_MAX__ 0x7f 58 | #define __WCHAR_MIN__ (-__WCHAR_MAX__ - 1) 59 | #define __INT64_C(c) c ## L 60 | #define __DBL_DIG__ 15 61 | #define __GCC_ATOMIC_POINTER_LOCK_FREE 2 62 | #define __SIZEOF_INT__ 4 63 | #define __SIZEOF_POINTER__ 8 64 | #define __GCC_ATOMIC_CHAR16_T_LOCK_FREE 2 65 | #define __USER_LABEL_PREFIX__ 66 | #define __STDC_HOSTED__ 1 67 | #define __LDBL_HAS_INFINITY__ 1 68 | #define __FLT_EPSILON__ 1.19209289550781250000e-7F 69 | #define __GXX_WEAK__ 1 70 | #define __LDBL_MIN__ 3.36210314311209350626e-4932L 71 | #define __DEC32_MAX__ 9.999999E96DF 72 | #define __INT32_MAX__ 0x7fffffff 73 | #define __SIZEOF_LONG__ 8 74 | #define __STDC_IEC_559__ 1 75 | #define __STDC_ISO_10646__ 201505L 76 | #define __UINT16_C(c) c 77 | #define __DECIMAL_DIG__ 21 78 | #define __gnu_linux__ 1 79 | #define __has_include_next(STR) __has_include_next__(STR) 80 | #define __LDBL_HAS_QUIET_NAN__ 1 81 | #define __GNUC__ 6 82 | #define __GXX_RTTI 1 83 | #define __MMX__ 1 84 | #define __cpp_delegating_constructors 200604 85 | #define __FLT_HAS_DENORM__ 1 86 | #define __SIZEOF_LONG_DOUBLE__ 16 87 | #define __BIGGEST_ALIGNMENT__ 16 88 | #define __STDC_UTF_16__ 1 89 | #define __DBL_MAX__ double(1.79769313486231570815e+308L) 90 | #define __cpp_raw_strings 200710 91 | #define __INT_FAST32_MAX__ 0x7fffffffffffffffL 92 | #define __DBL_HAS_INFINITY__ 1 93 | #define __INT64_MAX__ 0x7fffffffffffffffL 94 | #define __DEC32_MIN_EXP__ (-94) 95 | #define __INT_FAST16_TYPE__ long int 96 | #define __LDBL_HAS_DENORM__ 1 97 | #define __cplusplus 201402L 98 | #define __cpp_ref_qualifiers 200710 99 | #define __DEC128_MAX__ 9.999999999999999999999999999999999E6144DL 100 | #define __INT_LEAST32_MAX__ 0x7fffffff 101 | #define __DEC32_MIN__ 1E-95DF 102 | #define __DEPRECATED 1 103 | #define __cpp_rvalue_references 200610 104 | #define __DBL_MAX_EXP__ 1024 105 | #define __DEC128_EPSILON__ 1E-33DL 106 | #define __SSE2_MATH__ 1 107 | #define __ATOMIC_HLE_RELEASE 131072 108 | #define __PTRDIFF_MAX__ 0x7fffffffffffffffL 109 | #define __amd64 1 110 | #define __STDC_NO_THREADS__ 1 111 | #define __ATOMIC_HLE_ACQUIRE 65536 112 | #define __GNUG__ 6 113 | #define __LONG_LONG_MAX__ 0x7fffffffffffffffLL 114 | #define __SIZEOF_SIZE_T__ 8 115 | #define __cpp_rvalue_reference 200610 116 | #define __cpp_nsdmi 200809 117 | #define __SIZEOF_WINT_T__ 4 118 | #define __cpp_initializer_lists 200806 119 | #define __cpp_hex_float 201603 120 | #define __GCC_HAVE_DWARF2_CFI_ASM 1 121 | #define __GXX_ABI_VERSION 1010 122 | #define __FLT_MIN_EXP__ (-125) 123 | #define __cpp_lambdas 200907 124 | #define __INT_FAST64_TYPE__ long int 125 | #define __DBL_MIN__ double(2.22507385850720138309e-308L) 126 | #define __LP64__ 1 127 | #define __DECIMAL_BID_FORMAT__ 1 128 | #define __DEC128_MIN__ 1E-6143DL 129 | #define __REGISTER_PREFIX__ 130 | #define __UINT16_MAX__ 0xffff 131 | #define __DBL_HAS_DENORM__ 1 132 | #define __UINT8_TYPE__ unsigned char 133 | #define __FLT_MANT_DIG__ 24 134 | #define __VERSION__ "6.3.1 20170109" 135 | #define __UINT64_C(c) c ## UL 136 | #define __cpp_unicode_characters 200704 137 | #define _STDC_PREDEF_H 1 138 | #define __cpp_decltype_auto 201304 139 | #define __GCC_ATOMIC_INT_LOCK_FREE 2 140 | #define __FLOAT_WORD_ORDER__ __ORDER_LITTLE_ENDIAN__ 141 | #define __STDC_IEC_559_COMPLEX__ 1 142 | #define __INT32_C(c) c 143 | #define __DEC64_EPSILON__ 1E-15DD 144 | #define __ORDER_PDP_ENDIAN__ 3412 145 | #define __DEC128_MIN_EXP__ (-6142) 146 | #define __INT_FAST32_TYPE__ long int 147 | #define __UINT_LEAST16_TYPE__ short unsigned int 148 | #define unix 1 149 | #define __INT16_MAX__ 0x7fff 150 | #define __cpp_rtti 199711 151 | #define __SIZE_TYPE__ long unsigned int 152 | #define __UINT64_MAX__ 0xffffffffffffffffUL 153 | #define __INT8_TYPE__ signed char 154 | #define __cpp_digit_separators 201309 155 | #define __ELF__ 1 156 | #define __GCC_ASM_FLAG_OUTPUTS__ 1 157 | #define __FLT_RADIX__ 2 158 | #define __INT_LEAST16_TYPE__ short int 159 | #define __LDBL_EPSILON__ 1.08420217248550443401e-19L 160 | #define __UINTMAX_C(c) c ## UL 161 | #define __GLIBCXX_BITSIZE_INT_N_0 128 162 | #define __k8 1 163 | #define __SIG_ATOMIC_MAX__ 0x7fffffff 164 | #define __GCC_ATOMIC_WCHAR_T_LOCK_FREE 2 165 | #define __cpp_sized_deallocation 201309 166 | #define __SIZEOF_PTRDIFF_T__ 8 167 | #define __x86_64__ 1 168 | #define __DEC32_SUBNORMAL_MIN__ 0.000001E-95DF 169 | #define __INT_FAST16_MAX__ 0x7fffffffffffffffL 170 | #define __UINT_FAST32_MAX__ 0xffffffffffffffffUL 171 | #define __UINT_LEAST64_TYPE__ long unsigned int 172 | #define __FLT_HAS_QUIET_NAN__ 1 173 | #define __FLT_MAX_10_EXP__ 38 174 | #define __LONG_MAX__ 0x7fffffffffffffffL 175 | #define __DEC128_SUBNORMAL_MIN__ 0.000000000000000000000000000000001E-6143DL 176 | #define __FLT_HAS_INFINITY__ 1 177 | #define __cpp_unicode_literals 200710 178 | #define __UINT_FAST16_TYPE__ long unsigned int 179 | #define __DEC64_MAX__ 9.999999999999999E384DD 180 | #define __CHAR16_TYPE__ short unsigned int 181 | #define __PRAGMA_REDEFINE_EXTNAME 1 182 | #define __SEG_FS 1 183 | #define __INT_LEAST16_MAX__ 0x7fff 184 | #define __DEC64_MANT_DIG__ 16 185 | #define __UINT_LEAST32_MAX__ 0xffffffffU 186 | #define __SEG_GS 1 187 | #define __GCC_ATOMIC_LONG_LOCK_FREE 2 188 | #define __INT_LEAST64_TYPE__ long int 189 | #define __INT16_TYPE__ short int 190 | #define __INT_LEAST8_TYPE__ signed char 191 | #define __DEC32_MAX_EXP__ 97 192 | #define __INT_FAST8_MAX__ 0x7f 193 | #define __INTPTR_MAX__ 0x7fffffffffffffffL 194 | #define linux 1 195 | #define __cpp_range_based_for 200907 196 | #define __SSE2__ 1 197 | #define __EXCEPTIONS 1 198 | #define __LDBL_MANT_DIG__ 64 199 | #define __DBL_HAS_QUIET_NAN__ 1 200 | #define __SIG_ATOMIC_MIN__ (-__SIG_ATOMIC_MAX__ - 1) 201 | #define __code_model_small__ 1 202 | #define __cpp_return_type_deduction 201304 203 | #define __k8__ 1 204 | #define __INTPTR_TYPE__ long int 205 | #define __UINT16_TYPE__ short unsigned int 206 | #define __WCHAR_TYPE__ int 207 | #define __SIZEOF_FLOAT__ 4 208 | #define __UINTPTR_MAX__ 0xffffffffffffffffUL 209 | #define __DEC64_MIN_EXP__ (-382) 210 | #define __cpp_decltype 200707 211 | #define __INT_FAST64_MAX__ 0x7fffffffffffffffL 212 | #define __GCC_ATOMIC_TEST_AND_SET_TRUEVAL 1 213 | #define __FLT_DIG__ 6 214 | #define __UINT_FAST64_TYPE__ long unsigned int 215 | #define __INT_MAX__ 0x7fffffff 216 | #define __amd64__ 1 217 | #define __INT64_TYPE__ long int 218 | #define __FLT_MAX_EXP__ 128 219 | #define __ORDER_BIG_ENDIAN__ 4321 220 | #define __DBL_MANT_DIG__ 53 221 | #define __cpp_inheriting_constructors 200802 222 | #define __SIZEOF_FLOAT128__ 16 223 | #define __INT_LEAST64_MAX__ 0x7fffffffffffffffL 224 | #define __DEC64_MIN__ 1E-383DD 225 | #define __WINT_TYPE__ unsigned int 226 | #define __UINT_LEAST32_TYPE__ unsigned int 227 | #define __SIZEOF_SHORT__ 2 228 | #define __SSE__ 1 229 | #define __LDBL_MIN_EXP__ (-16381) 230 | #define __INT_LEAST8_MAX__ 0x7f 231 | #define __SIZEOF_INT128__ 16 232 | #define __LDBL_MAX_10_EXP__ 4932 233 | #define __ATOMIC_RELAXED 0 234 | #define __DBL_EPSILON__ double(2.22044604925031308085e-16L) 235 | #define _LP64 1 236 | #define __UINT8_C(c) c 237 | #define __INT_LEAST32_TYPE__ int 238 | #define __SIZEOF_WCHAR_T__ 4 239 | #define __UINT64_TYPE__ long unsigned int 240 | #define __INT_FAST8_TYPE__ signed char 241 | #define __GNUC_STDC_INLINE__ 1 242 | #define __DBL_DECIMAL_DIG__ 17 243 | #define __STDC_UTF_32__ 1 244 | #define __FXSR__ 1 245 | #define __DEC_EVAL_METHOD__ 2 246 | #define __cpp_runtime_arrays 198712 247 | #define __UINT32_C(c) c ## U 248 | #define __INTMAX_MAX__ 0x7fffffffffffffffL 249 | #define __cpp_alias_templates 200704 250 | #define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ 251 | #define __FLT_DENORM_MIN__ 1.40129846432481707092e-45F 252 | #define __INT8_MAX__ 0x7f 253 | #define __UINT_FAST32_TYPE__ long unsigned int 254 | #define __CHAR32_TYPE__ unsigned int 255 | #define __FLT_MAX__ 3.40282346638528859812e+38F 256 | #define __cpp_constexpr 201304 257 | #define __INT32_TYPE__ int 258 | #define __SIZEOF_DOUBLE__ 8 259 | #define __cpp_exceptions 199711 260 | #define __INTMAX_TYPE__ long int 261 | #define __DEC128_MAX_EXP__ 6145 262 | #define __ATOMIC_CONSUME 1 263 | #define __GNUC_MINOR__ 3 264 | #define __GLIBCXX_TYPE_INT_N_0 __int128 265 | #define __UINTMAX_MAX__ 0xffffffffffffffffUL 266 | #define __DEC32_MANT_DIG__ 7 267 | #define __DBL_MAX_10_EXP__ 308 268 | #define __LDBL_DENORM_MIN__ 3.64519953188247460253e-4951L 269 | #define __INT16_C(c) c 270 | #define __cpp_generic_lambdas 201304 271 | #define __STDC__ 1 272 | #define __PTRDIFF_TYPE__ long int 273 | #define __ATOMIC_SEQ_CST 5 274 | #define __UINT32_TYPE__ unsigned int 275 | #define __UINTPTR_TYPE__ long unsigned int 276 | #define __DEC64_SUBNORMAL_MIN__ 0.000000000000001E-383DD 277 | #define __DEC128_MANT_DIG__ 34 278 | #define __LDBL_MIN_10_EXP__ (-4931) 279 | #define __SSE_MATH__ 1 280 | #define __SIZEOF_LONG_LONG__ 8 281 | #define __cpp_user_defined_literals 200809 282 | #define __GCC_ATOMIC_LLONG_LOCK_FREE 2 283 | #define __LDBL_DIG__ 18 284 | #define __FLT_DECIMAL_DIG__ 9 285 | #define __UINT_FAST16_MAX__ 0xffffffffffffffffUL 286 | #define __FLT_MIN_10_EXP__ (-37) 287 | #define __GCC_ATOMIC_SHORT_LOCK_FREE 2 288 | #define __UINT_FAST8_TYPE__ unsigned char 289 | #define _GNU_SOURCE 1 290 | #define __cpp_init_captures 201304 291 | #define __ATOMIC_ACQ_REL 4 292 | #define __ATOMIC_RELEASE 3 293 | -------------------------------------------------------------------------------- /sound_font_ripper/gba_instr.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of GBA Sound Ripper 3 | * (c) 2012, 2014 Bregalad 4 | * This is free and open source software 5 | * 6 | * This class deals with internal representation of 7 | * GBA instruments and converts them to SF2 instruments 8 | */ 9 | #include "gba_instr.h" 10 | #include 11 | #include "../hex_string.h" 12 | #include 13 | #include 14 | 15 | extern FILE *inGBA; // Related .gba file 16 | 17 | bool operator <(const inst_data&i, const inst_data& j) 18 | { 19 | if (j.word2 != i.word2) return i.word2 < j.word2; 20 | else if (j.word1 != i.word1) return i.word1 < j.word1; 21 | else return i.word0 < j.word0; 22 | } 23 | 24 | uint32_t GBAInstr::get_GBA_pointer() 25 | { 26 | uint32_t p; 27 | fread(&p, 4, 1, inGBA); 28 | return p & 0x3FFFFFF; 29 | } 30 | 31 | void GBAInstr::generate_adsr_generators(const uint32_t adsr) 32 | { 33 | // Get separate components 34 | int32_t attack = adsr & 0xFF; 35 | int32_t decay = (adsr >> 8) & 0xFF; 36 | int32_t sustain = (adsr >> 16) & 0xFF; 37 | int32_t release = adsr >> 24; 38 | 39 | // Add generators for ADSR envelope if required 40 | if (attack != 0xFF) 41 | { 42 | // Compute attack time - the sound engine is called 60 times per second 43 | // and adds "attack" to envelope every time the engine is called 44 | double att_time = (256 / 60.0) / attack; 45 | double att = 1200 * log2(att_time); 46 | sf2->add_new_inst_generator(SFGenerator::attackVolEnv, uint16_t(att)); 47 | } 48 | 49 | if (sustain != 0xFF) 50 | { 51 | double sus; 52 | // Compute attenuation in cB if sustain is non-zero 53 | if (sustain != 0) sus = 100 * log(256.0 / sustain); 54 | // Special case where attenuation is infinite -> use max value 55 | else sus = 1000; 56 | 57 | sf2->add_new_inst_generator(SFGenerator::sustainVolEnv, uint16_t(sus)); 58 | 59 | double dec_time = (log(256.0) / (log(256) - log(decay))) / 60.0; 60 | dec_time *= 10 / log(256); 61 | double dec = 1200 * log2(dec_time); 62 | sf2->add_new_inst_generator(SFGenerator::decayVolEnv, uint16_t(dec)); 63 | } 64 | 65 | if (release != 0x00) 66 | { 67 | double rel_time = (log(256.0) / (log(256) - log(release))) / 60.0; 68 | double rel = 1200 * log2(rel_time); 69 | sf2->add_new_inst_generator(SFGenerator::releaseVolEnv, uint16_t(rel)); 70 | } 71 | } 72 | 73 | void GBAInstr::generate_psg_adsr_generators(const uint32_t adsr) 74 | { 75 | // Get separate components 76 | int32_t attack = adsr & 0xFF; 77 | int32_t decay = (adsr >> 8) & 0xFF; 78 | int32_t sustain = (adsr >> 16) & 0xFF; 79 | int32_t release = adsr >> 24; 80 | 81 | // Reject instrument if invalid values ! 82 | if (attack > 15 || decay > 15 || sustain > 15 || release > 15) throw -1; 83 | 84 | // Add generators for ADSR envelope if required 85 | if (attack != 0) 86 | { 87 | // Compute attack time - the sound engine is called 60 times per second 88 | // and adds "attack" to envelope every time the engine is called 89 | double att_time = attack / 5.0; 90 | double att = 1200 * log2(att_time); 91 | sf2->add_new_inst_generator(SFGenerator::attackVolEnv, uint16_t(att)); 92 | } 93 | 94 | if (sustain != 15) 95 | { 96 | double sus; 97 | // Compute attenuation in cB if sustain is non-zero 98 | if (sustain != 0) sus = 100 * log(15.0 / sustain); 99 | // Special case where attenuation is infinite -> use max value 100 | else sus = 1000; 101 | 102 | sf2->add_new_inst_generator(SFGenerator::sustainVolEnv, uint16_t(sus)); 103 | 104 | double dec_time = decay / 5.0; 105 | double dec = 1200 * log2(dec_time + 1); 106 | sf2->add_new_inst_generator(SFGenerator::decayVolEnv, uint16_t(dec)); 107 | } 108 | 109 | if (release != 0) 110 | { 111 | double rel_time = release / 5.0; 112 | double rel = 1200 * log2(rel_time); 113 | sf2->add_new_inst_generator(SFGenerator::releaseVolEnv, uint16_t(rel)); 114 | } 115 | } 116 | 117 | // Build a SF2 instrument form a GBA sampled instrument 118 | int32_t GBAInstr::build_sampled_instrument(const inst_data inst) 119 | { 120 | // Do nothing if this instrument already exists ! 121 | inst_it it = inst_map.find(inst); 122 | if (it != inst_map.end()) return (*it).second; 123 | 124 | // The flag is set if no scaling should be done if the instrument type is 8 125 | bool no_scale = (inst.word0 & 0xff) == 0x08; 126 | 127 | // Get sample pointer 128 | uint32_t sample_pointer = inst.word1 & 0x3ffffff; 129 | 130 | // Determine if loop is enabled (it's dumb but we have to seek just for this) 131 | if (fseek(inGBA, sample_pointer|3, SEEK_SET)) throw -1; 132 | bool loop_flag = fgetc(inGBA) == 0x40; 133 | 134 | // Build pointed sample 135 | int32_t sample_index = samples.build_sample(sample_pointer); 136 | 137 | // Instrument's name 138 | std::string name = "sample @0x" + hex(sample_pointer); 139 | 140 | // Create instrument bag 141 | sf2->add_new_instrument(name.c_str()); 142 | sf2->add_new_inst_bag(); 143 | 144 | // Add generator to prevent scaling if required 145 | if (no_scale) 146 | sf2->add_new_inst_generator(SFGenerator::scaleTuning, 0); 147 | 148 | generate_adsr_generators(inst.word2); 149 | sf2->add_new_inst_generator(SFGenerator::sampleModes, loop_flag ? 1 : 0); 150 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample_index); 151 | 152 | // Add instrument to list 153 | inst_map[inst] = cur_inst_index; 154 | return cur_inst_index++; 155 | } 156 | 157 | // Create new SF2 from every key split GBA instrument 158 | int32_t GBAInstr::build_every_keysplit_instrument(const inst_data inst) 159 | { 160 | // Do nothing if this instrument already exists ! 161 | inst_it it = inst_map.find(inst); 162 | if (it != inst_map.end()) return (*it).second; 163 | 164 | // I'm sorry for doing a dumb copy/pase of the routine right above 165 | // But there was too much differences to handles to practically handle it with flags 166 | // therefore I didn't really had a choice. 167 | uint32_t baseaddress = inst.word1 & 0x3ffffff; 168 | std::string name = "EveryKeySplit @0x" + hex(baseaddress); 169 | sf2->add_new_instrument(name.c_str()); 170 | 171 | // Loop through all keys 172 | for(int32_t key = 0; key < 128; key ++) 173 | { 174 | try 175 | { 176 | // Seek at the key's instrument 177 | if (fseek(inGBA, baseaddress + 12 * key, SEEK_SET)) throw -1; 178 | 179 | // Read instrument data 180 | int32_t instrType = fgetc(inGBA); // Instrument type 181 | int32_t keynum = fgetc(inGBA); // Key (every key split instrument only) 182 | /* int32_t unused_byte =*/ fgetc(inGBA); // Unknown/unused byte 183 | int32_t panning = fgetc(inGBA); // Panning (every key split instrument only) 184 | 185 | // The flag is set if no scaling should be done on the sample 186 | bool no_scale = false; 187 | 188 | uint32_t main_word, adsr; 189 | fread(&main_word, 4, 1, inGBA); 190 | 191 | // Get ADSR envelope 192 | fread(&adsr, 4, 1, inGBA); 193 | 194 | int32_t sample_index; 195 | bool loop_flag = true; 196 | 197 | switch(instrType & 0x0f) 198 | { 199 | case 8: 200 | no_scale = true; 201 | case 0: 202 | { 203 | // Determine if loop is enabled and read sample's pitch 204 | uint32_t sample_pointer = main_word & 0x3ffffff; 205 | if (fseek(inGBA, sample_pointer | 3, SEEK_SET)) throw -1; 206 | loop_flag = fgetc(inGBA) == 0x40; 207 | 208 | uint32_t pitch; 209 | fread(&pitch, 4, 1, inGBA); 210 | 211 | // Build pointed sample 212 | sample_index = samples.build_sample(sample_pointer); 213 | 214 | // Add a bag for this key 215 | sf2->add_new_inst_bag(); 216 | generate_adsr_generators(adsr); 217 | // Add generator to prevent scaling if required 218 | if (no_scale) 219 | sf2->add_new_inst_generator(SFGenerator::scaleTuning, 0); 220 | 221 | // Compute base note and fine tune from pitch 222 | double delta_note = 12.0 * log2(sf2->default_sample_rate * 1024.0 / pitch); 223 | int32_t rootkey = 60 + int32_t(round(delta_note)); 224 | 225 | // Override root key with the value we need 226 | sf2->add_new_inst_generator(SFGenerator::overridingRootKey, rootkey - keynum + key); 227 | // Key range is only a single key (obviously) 228 | sf2->add_new_inst_generator(SFGenerator::keyRange, key, key); 229 | } break; 230 | 231 | case 4: 232 | case 12: 233 | { 234 | // Determine whenever the note is metallic noise, normal noise, or invalid 235 | bool metal_flag; 236 | if (main_word == 0x1000000) 237 | metal_flag = true; 238 | else if (main_word == 0) 239 | metal_flag = false; 240 | else 241 | throw -1; 242 | 243 | // Build corresponding sample 244 | sample_index = samples.build_noise_sample(metal_flag, keynum); 245 | sf2->add_new_inst_bag(); 246 | generate_psg_adsr_generators(adsr); 247 | sf2->add_new_inst_generator(SFGenerator::overridingRootKey, key); 248 | sf2->add_new_inst_generator(SFGenerator::keyRange, key, key); 249 | } break; 250 | 251 | // Ignore other kind of instruments 252 | default : throw -1; 253 | } 254 | 255 | if (panning != 0) 256 | sf2->add_new_inst_generator(SFGenerator::pan, int32_t((panning - 192) * (500 / 128.0))); 257 | // Same as a normal sample 258 | sf2->add_new_inst_generator(SFGenerator::sampleModes, loop_flag ? 1 : 0); 259 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample_index); 260 | } 261 | catch(...) {} // Continue to next key when there is a major problem 262 | } 263 | // Add instrument to list 264 | inst_map[inst] = cur_inst_index; 265 | return cur_inst_index++; 266 | } 267 | 268 | // Build a SF2 instrument from a GBA key split instrument 269 | int32_t GBAInstr::build_keysplit_instrument(const inst_data inst) 270 | { 271 | // Do nothing if this instrument already exists ! 272 | inst_it it = inst_map.find(inst); 273 | if (it != inst_map.end()) return (*it).second; 274 | 275 | uint32_t base_pointer = inst.word1 & 0x3ffffff; 276 | uint32_t key_table = inst.word2 & 0x3ffffff; 277 | 278 | // Decode key-table in usable data 279 | std::vector split_list, index_list; 280 | 281 | int8_t key = 0; 282 | int32_t prev_index = -1; 283 | int32_t current_index; 284 | if (fseek(inGBA, key_table, SEEK_SET)) throw -1; 285 | 286 | // Add instrument to list 287 | std::string name = "0x" + hex(base_pointer) + " key split"; 288 | sf2->add_new_instrument(name.c_str()); 289 | 290 | do 291 | { 292 | int32_t index = fgetc(inGBA); 293 | 294 | // Detect where there is changes in the index table 295 | current_index = index; 296 | if (prev_index != current_index) 297 | { 298 | split_list.push_back(key); 299 | index_list.push_back(current_index); 300 | prev_index = current_index; 301 | } 302 | } 303 | while(++key > 0); 304 | 305 | // Final entry for the last split 306 | split_list.push_back(0x80); 307 | 308 | for(uint32_t i = 0; i < index_list.size(); i++) 309 | { 310 | try 311 | { 312 | // Seek to pointed instrument 313 | if (fseek(inGBA, base_pointer + 12 * index_list[i], SEEK_SET)) throw -1; 314 | 315 | // Once again I'm sorry for the dumb copy/pase 316 | // but doing it all with flags would have been quite complex 317 | 318 | int32_t inst_type = fgetc(inGBA); // Instrument type 319 | /* int32_t keynum = */ fgetc(inGBA); // Key (every key split instrument only) 320 | /* int32_t unused_byte = */ fgetc(inGBA); // Unknown/unused byte 321 | /* int32_t panning = */ fgetc(inGBA); // Panning (every key split instrument only) 322 | 323 | // The flag is set if no scaling should be done on the sample 324 | bool no_scale = (inst_type == 8); 325 | 326 | // Get sample pointer 327 | uint32_t sample_pointer; 328 | fread(&sample_pointer, 1, 4, inGBA); 329 | sample_pointer &= 0x3ffffff; 330 | 331 | // Get ADSR envelope 332 | uint32_t adsr; 333 | fread(&adsr, 4, 1, inGBA); 334 | 335 | // For now GameBoy instruments aren't supported 336 | // (I wonder if any game ever used this) 337 | if ((inst_type & 0x07) != 0) continue; 338 | 339 | // Determine if loop is enabled (it's dumb but we have to seek just for this) 340 | if (fseek(inGBA, sample_pointer | 3, SEEK_SET)) throw -1; 341 | bool loop_flag = fgetc(inGBA) == 0x40; 342 | 343 | // Build pointed sample 344 | int32_t sample_index = samples.build_sample(sample_pointer); 345 | 346 | // Create instrument bag 347 | sf2->add_new_inst_bag(); 348 | 349 | // Add generator to prevent scaling if required 350 | if (no_scale) 351 | sf2->add_new_inst_generator(SFGenerator::scaleTuning, 0); 352 | 353 | generate_adsr_generators(adsr); 354 | // Particularity here : An additional bag to select the key range 355 | sf2->add_new_inst_generator(SFGenerator::keyRange, split_list[i], split_list[i + 1] - 1); 356 | sf2->add_new_inst_generator(SFGenerator::sampleModes, loop_flag ? 1 : 0); 357 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample_index); 358 | } 359 | catch(...) {} // Silently continue to next key if anything bad happens 360 | } 361 | inst_map[inst] = cur_inst_index; 362 | return cur_inst_index++; 363 | } 364 | 365 | // Build gameboy channel 3 instrument 366 | int32_t GBAInstr::build_GB3_instrument(const inst_data inst) 367 | { 368 | // Do nothing if this instrument already exists ! 369 | inst_it it = inst_map.find(inst); 370 | if (it != inst_map.end()) return (*it).second; 371 | 372 | // Get sample pointer 373 | uint32_t sample_pointer = inst.word1 & 0x3ffffff; 374 | 375 | // Try to seek to see if the pointer is valid, if it's not then abort 376 | if (fseek(inGBA, sample_pointer, SEEK_SET)) throw -1; 377 | 378 | int32_t sample = samples.build_GB3_samples(sample_pointer); 379 | 380 | std::string name = "GB3 @0x" + hex(sample_pointer); 381 | sf2->add_new_instrument(name.c_str()); 382 | 383 | // Global zone 384 | sf2->add_new_inst_bag(); 385 | generate_psg_adsr_generators(inst.word2); 386 | 387 | sf2->add_new_inst_bag(); 388 | sf2->add_new_inst_generator(SFGenerator::keyRange, 0, 52); 389 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 390 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample - 3); 391 | sf2->add_new_inst_bag(); 392 | sf2->add_new_inst_generator(SFGenerator::keyRange, 53, 64); 393 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 394 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample - 2); 395 | sf2->add_new_inst_bag(); 396 | sf2->add_new_inst_generator(SFGenerator::keyRange, 65, 76); 397 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 398 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample - 1); 399 | sf2->add_new_inst_bag(); 400 | sf2->add_new_inst_generator(SFGenerator::keyRange, 77, 127); 401 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 402 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample); 403 | 404 | inst_map[inst] = cur_inst_index; 405 | return cur_inst_index++; 406 | } 407 | 408 | // Build GameBoy pulse wave instrument 409 | int32_t GBAInstr::build_pulse_instrument(const inst_data inst) 410 | { 411 | // Do nothing if this instrument already exists ! 412 | inst_it it = inst_map.find(inst); 413 | if (it != inst_map.end()) return (*it).second; 414 | 415 | uint32_t duty_cycle = inst.word1; 416 | // The difference between 75% and 25% duty cycles is inaudible therefore 417 | // I simply replace 75% duty cycles by 25% 418 | if (duty_cycle == 3) duty_cycle = 1; 419 | if (duty_cycle > 3) throw -1; 420 | 421 | int32_t sample = samples.build_pulse_samples(duty_cycle); 422 | std::string name = "pulse " + std::to_string(duty_cycle); 423 | sf2->add_new_instrument(name.c_str()); 424 | 425 | // Global zone 426 | sf2->add_new_inst_bag(); 427 | generate_psg_adsr_generators(inst.word2); 428 | 429 | sf2->add_new_inst_bag(); 430 | sf2->add_new_inst_generator(SFGenerator::keyRange, 0, 45); 431 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 432 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample - 4); 433 | sf2->add_new_inst_bag(); 434 | sf2->add_new_inst_generator(SFGenerator::keyRange, 46, 57); 435 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 436 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample - 3); 437 | sf2->add_new_inst_bag(); 438 | sf2->add_new_inst_generator(SFGenerator::keyRange, 58, 69); 439 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 440 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample - 2); 441 | sf2->add_new_inst_bag(); 442 | sf2->add_new_inst_generator(SFGenerator::keyRange, 70, 81); 443 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 444 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample - 1); 445 | sf2->add_new_inst_bag(); 446 | sf2->add_new_inst_generator(SFGenerator::keyRange, 82, 127); 447 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 448 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample); 449 | 450 | inst_map[inst] = cur_inst_index; 451 | return cur_inst_index++; 452 | } 453 | 454 | // Build GameBoy white noise instrument 455 | int32_t GBAInstr::build_noise_instrument(const inst_data inst) 456 | { 457 | // Do nothing if this instrument already exists ! 458 | inst_it it = inst_map.find(inst); 459 | if (it != inst_map.end()) return (*it).second; 460 | 461 | // 0 = normal, 1 = metallic, anything else = invalid 462 | if (inst.word1 > 1) throw -1; 463 | bool metallic = inst.word1; 464 | 465 | std::string name = metallic ? "GB metallic noise" : "GB noise"; 466 | sf2->add_new_instrument(name.c_str()); 467 | 468 | // Global zone 469 | sf2->add_new_inst_bag(); 470 | generate_psg_adsr_generators(inst.word2); 471 | 472 | sf2->add_new_inst_bag(); 473 | int32_t sample42 = samples.build_noise_sample(metallic, 42); 474 | sf2->add_new_inst_generator(SFGenerator::keyRange, 0, 42); 475 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 476 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample42); 477 | 478 | for(int32_t key = 43; key <= 77; key++) 479 | { 480 | sf2->add_new_inst_bag(); 481 | int32_t sample = samples.build_noise_sample(metallic, key); 482 | sf2->add_new_inst_generator(SFGenerator::keyRange, key, key); 483 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 484 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample); 485 | } 486 | 487 | sf2->add_new_inst_bag(); 488 | int32_t sample78 = samples.build_noise_sample(metallic, 78); 489 | sf2->add_new_inst_generator(SFGenerator::keyRange, 78, 127); 490 | sf2->add_new_inst_generator(SFGenerator::sampleModes, 1); 491 | sf2->add_new_inst_generator(SFGenerator::scaleTuning, 0); 492 | sf2->add_new_inst_generator(SFGenerator::sampleID, sample78); 493 | 494 | inst_map[inst] = cur_inst_index; 495 | return cur_inst_index++; 496 | } 497 | -------------------------------------------------------------------------------- /gba_mus_ripper/gba_mus_ripper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * GBAMusRipper (c) 2012 by Bregalad 3 | * This is free and open source software 4 | * 5 | * This program analyzes a Game Boy Advance ROM and searches for a sound engine 6 | * named "Sappy" which is used in ~90% of commercial GBA games. 7 | * 8 | * If the engine is found it rips all songs to MIDI (.mid) format and all 9 | * instruments to SoundFont 2.0 (.sf2) format. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "../hex_string.h" 23 | #include "../sappy_detector/sappy_detector.h" 24 | #include "../song_ripper/song_ripper.h" 25 | #include "../sound_font_ripper/sound_font_ripper.h" 26 | 27 | using namespace std; 28 | 29 | static FILE *inGBA; 30 | static string inGBA_path; 31 | static size_t inGBA_size; 32 | static string name; 33 | static string outPath; 34 | static bool gm = false; 35 | static bool xg = false; 36 | static bool rc = false; 37 | static bool sb = false; 38 | static bool raw = false; 39 | static uint32_t song_tbl_ptr = 0; 40 | 41 | static const int32_t sample_rates[] = {-1, 5734, 7884, 10512, 13379, 15768, 18157, 21024, 26758, 31536, 36314, 40137, 42048}; 42 | 43 | static void print_instructions() 44 | { 45 | cout << 46 | " /========================================================\\\n" 47 | "-< GBA Mus Ripper 3.5 (c) 2017 Bregalad, CaptainSwag101 >-\n" 48 | " \\========================================================/\n\n" 49 | "Usage : gba_mus_ripper game.gba [-o output path] [flags] [address]\n\n" 50 | "-o : Output path. All MIDIs and soundfonts will be ripped to a subfolder inside this directory.\n" 51 | "-gm : Give General MIDI names to presets. Note that this will only change the names and will NOT\n" 52 | " magically turn the soundfont into a General MIDI compliant soundfont.\n" 53 | "-rc : Rearrange channels in output MIDIs so channel 10 is avoided. Needed by sound\n" 54 | " cards where it's impossible to disable \"drums\" on channel 10 even with GS or XG commands.\n" 55 | "-xg : Output MIDI will be compliant to XG standard (instead of default GS standard)\n" 56 | "-sb : Separate banks. Every sound bank is ripped to a different .sf2 file and placed\n" 57 | " into different sub-folders (instead of doing it in a single .sf2 file and a single folder)\n" 58 | "-raw : Output MIDIs exactly as they're encoded in ROM, without linearizing volumes/velocities\n" 59 | " and without simulating vibratos.\n" 60 | "-adr : Force adress of the song table manually. This is required for manually dumping music data\n" 61 | " from ROMs where the location can't be detected automatically.\n" 62 | ; 63 | exit(-1); 64 | } 65 | 66 | static uint32_t get_GBA_pointer() 67 | { 68 | uint32_t p; 69 | fread(&p, 4, 1, inGBA); 70 | return p - 0x8000000; 71 | } 72 | 73 | static void mkdir(string name) 74 | { 75 | system(("mkdir -p \"" + name + '"').c_str()); 76 | } 77 | 78 | // Convert number to string with always 3 digits (even if leading zeros) 79 | static string dec3(uint32_t n) 80 | { 81 | string s; 82 | s += "0123456789"[n/100]; 83 | s += "0123456789"[n/10%10]; 84 | s += "0123456789"[n%10]; 85 | return s; 86 | } 87 | 88 | static int32_t parse_args(int32_t argc, string argv[]) 89 | { 90 | const char *args[argc]; 91 | for (int32_t c = 0; c < argc; c++) 92 | { 93 | args[c] = argv[c].c_str(); 94 | } 95 | 96 | if (argc < 1) print_instructions(); 97 | 98 | bool path_found = false, song_tbl_found = false; 99 | for(int32_t i = 0; i < argc; i++) 100 | { 101 | if (args[i][0] == '-') 102 | { 103 | if (!strcmp(args[i], "-help")) 104 | print_instructions(); 105 | else if (!strcmp(args[i], "-o") && path_found && (i + 1) <= argc) 106 | { 107 | outPath = args[i + 1]; 108 | i++; 109 | } 110 | else if (!strcmp(args[i], "-gm")) 111 | gm = true; 112 | else if (!strcmp(args[i], "-xg")) 113 | xg = true; 114 | else if (!strcmp(args[i], "-rc")) 115 | rc = true; 116 | else if (!strcmp(args[i], "-sb")) 117 | sb = true; 118 | else if (!strcmp(args[i], "-raw")) 119 | raw = true; 120 | else 121 | { 122 | cout << stderr << "Error: Unknown command line option: " << args[i] << ". Try with -help to get information.\n"; 123 | return -1; 124 | } 125 | } 126 | // Convert given address to binary, use it instead of automatically detected one 127 | else if (!path_found) 128 | { 129 | // Get GBA file 130 | inGBA = fopen(args[i], "rb"); 131 | if (!inGBA) 132 | { 133 | cout << stderr << "Error: Can't open file " << args[i] << " for reading.\n"; 134 | return -2; 135 | } 136 | 137 | // Name is filename without the extention and without path 138 | inGBA_path = args[i]; 139 | size_t separator_index = inGBA_path.find_last_of("/\\") + 1; 140 | name = inGBA_path.substr(separator_index, inGBA_path.find_last_of('.') - separator_index); 141 | 142 | // Path where the input GBA file is located 143 | outPath = inGBA_path.substr(0, separator_index); 144 | path_found = true; 145 | } 146 | else if (!song_tbl_found) 147 | { 148 | errno = 0; 149 | song_tbl_ptr = strtoul(args[i], 0, 0); 150 | if (errno) 151 | { 152 | cout << stderr << "Error: " << args[i] << " is not a valid song table address.\n"; 153 | return -3; 154 | } 155 | song_tbl_found = true; 156 | } 157 | else 158 | { 159 | cout << stderr << "Error: Don't know what to do with " << args[i] << ". Try with -help to get more information.\n"; 160 | return -4; 161 | } 162 | } 163 | if (!path_found) 164 | { 165 | cout << stderr << "Error: No input GBA file. Try with -help to get more information.\n"; 166 | return -1; 167 | } 168 | 169 | return 0; 170 | } 171 | 172 | int32_t mus_ripper(int32_t argc, string argv[]) 173 | { 174 | // Parse arguments (without program name) 175 | int32_t parse_result = parse_args(argc, argv); 176 | if (parse_result < 0) 177 | { 178 | return parse_result; 179 | } 180 | 181 | // Compute program prefix (should be "", "./", "../" or whathever) 182 | string prg_name = argv[0]; 183 | 184 | int32_t sample_rate = 0, main_volume = 0; // Use default values when those are '0' 185 | 186 | // If the user hasn't provided an address manually, we'll try to automatically detect it 187 | if (!song_tbl_ptr) 188 | { 189 | QCoreApplication::processEvents(); 190 | // Auto-detect address of sappy engine 191 | string sappy_detector_cmd = inGBA_path; 192 | #ifdef QT_DEBUG 193 | cout << "DEBUG: Going to call system(" << sappy_detector_cmd << ")\n"; 194 | #endif 195 | int32_t sound_engine_adr = sappy_detector(1, sappy_detector_cmd); 196 | 197 | // Exit if no sappy engine was found 198 | if (!sound_engine_adr) 199 | { 200 | return -4; 201 | } 202 | 203 | if (fseek(inGBA, sound_engine_adr, SEEK_SET)) 204 | { 205 | cout << stderr << "Error: Invalid offset within input GBA file: 0x" << sound_engine_adr << ".\n"; 206 | return -5; 207 | } 208 | 209 | // Engine parameter's word 210 | uint32_t parameter_word; 211 | fread(¶meter_word, 4, 1, inGBA); 212 | 213 | // Get sampling rate 214 | sample_rate = sample_rates[(parameter_word>>16) & 0xf]; 215 | main_volume = (parameter_word>>12) & 0xf; 216 | 217 | // Compute address of song table 218 | uint32_t song_levels; // Read # of song levels 219 | fread(&song_levels, 4, 1, inGBA); 220 | cout << "# of song levels: " << song_levels << "\n"; 221 | song_tbl_ptr = get_GBA_pointer() + 12 * song_levels; 222 | } 223 | 224 | // Create a directory named like the input ROM, without the .gba extention 225 | mkdir(outPath + "/" + name); 226 | 227 | // Get the size of the input GBA file 228 | fseek(inGBA, 0L, SEEK_END); 229 | inGBA_size = ftell(inGBA); 230 | 231 | if (song_tbl_ptr >= inGBA_size) 232 | { 233 | cout << stderr << "Fatal error: Song table at 0x" << song_tbl_ptr << " is past the end of the file.\n"; 234 | return -6; 235 | } 236 | 237 | cout << "Parsing song table..."; 238 | // New list of songs 239 | vector song_list; 240 | // New list of sound banks 241 | set sound_bank_list; 242 | 243 | if (fseek(inGBA, song_tbl_ptr, SEEK_SET)) 244 | { 245 | cout << stderr << "Fatal error: Can't seek to song table at: 0x" << song_tbl_ptr << ".\n"; 246 | return -7; 247 | } 248 | 249 | // Ignores entries which are made of 0s at the start of the song table 250 | // this fix was necessarily for the game Fire Emblem 251 | uint32_t song_pointer; 252 | while(true) 253 | { 254 | fread(&song_pointer, 4, 1, inGBA); 255 | if (song_pointer != 0) break; 256 | song_tbl_ptr += 4; 257 | } 258 | 259 | uint32_t i = 0; 260 | while(true) 261 | { 262 | song_pointer -= 0x8000000; // Adjust pointer 263 | 264 | // Stop as soon as we met with an invalid pointer 265 | if (song_pointer == 0 || song_pointer >= inGBA_size) break; 266 | 267 | for(int32_t j = 4; j != 0; --j) fgetc(inGBA); // Discard 4 bytes (sound group) 268 | song_list.push_back(song_pointer); // Add pointer to list 269 | i++; 270 | fread(&song_pointer, 4, 1, inGBA); 271 | }; 272 | // As soon as data that is not a valid pointer is found, the song table is terminated 273 | 274 | // End of song table 275 | uint32_t song_tbl_end_ptr = 8 * i + song_tbl_ptr; 276 | 277 | cout << "Collecting sound bank list..."; 278 | 279 | typedef set::iterator bank_t; 280 | bank_t *sound_bank_index_list = new bank_t[song_list.size()]; 281 | 282 | for(i = 0; i < song_list.size(); i++) 283 | { 284 | // Ignore unused song, which points to the end of the song table (for some reason) 285 | if (song_list[i] != song_tbl_end_ptr) 286 | { 287 | // Seek to song data 288 | if (fseek(inGBA, song_list[i] + 4, SEEK_SET)) continue; 289 | uint32_t sound_bank_ptr = get_GBA_pointer(); 290 | 291 | // Add sound bank to list if not already in the list 292 | sound_bank_index_list[i] = sound_bank_list.insert(sound_bank_ptr).first; 293 | } 294 | } 295 | 296 | // Close GBA file so that Songripper can access it 297 | fclose(inGBA); 298 | 299 | // Create directories for each sound bank if separate banks is enabled 300 | if (sb) 301 | { 302 | for(bank_t j = sound_bank_list.begin(); j != sound_bank_list.end(); ++j) 303 | { 304 | uint32_t d = distance(sound_bank_list.begin(), j); 305 | string subdir = outPath + '/' + name + '/' + "soundbank_" + dec3(d); 306 | mkdir(subdir); 307 | } 308 | } 309 | 310 | for(i = 0; i < song_list.size(); i++) 311 | { 312 | if (song_list[i] != song_tbl_end_ptr) 313 | { 314 | QCoreApplication::processEvents(); 315 | uint32_t bank_index = distance(sound_bank_list.begin(), sound_bank_index_list[i]); 316 | string seq_rip_cmd = "song_ripper\n" + inGBA_path + "\n" + outPath + "/" + name; 317 | 318 | // Add leading zeroes to file name 319 | if (sb) 320 | seq_rip_cmd += "/soundbank_" + dec3(bank_index); 321 | 322 | seq_rip_cmd += "/song" + dec3(i) + ".mid"; 323 | 324 | seq_rip_cmd += "\n0x" + hex(song_list[i]); 325 | seq_rip_cmd += rc ? "\n-rc" : (xg ? "\n-xg" : "\n-gs"); 326 | if (!raw) 327 | { 328 | seq_rip_cmd += "\n-sv"; 329 | seq_rip_cmd += "\n-lv"; 330 | } 331 | // Bank number, if banks are not separated 332 | if (!sb) 333 | seq_rip_cmd += "\n-b" + to_string(bank_index); 334 | 335 | QStringList argList = QString::fromStdString(seq_rip_cmd).split("\n"); 336 | 337 | cout << "Song " << i << "\n"; 338 | #ifdef QT_DEBUG 339 | cout << "DEBUG: Going to call system(" << seq_rip_cmd << ")\n"; 340 | #endif 341 | //if (!system(seq_rip_cmd.c_str())) 342 | QProcess *songripper = new QProcess(); 343 | 344 | #ifdef Q_OS_WIN32 345 | if (!QFile::exists(QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui.exe"))) 346 | { 347 | cout << "Unable to find the " << QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui.exe").toStdString() << " executable!\n"; 348 | return -8; 349 | } 350 | songripper->setProgram(QDir::currentPath() + "/gba_mus_ripper_gui.exe"); 351 | #else 352 | if (!QFile::exists(QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui"))) 353 | { 354 | cout << "Unable to find the " << QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui").toStdString() << " executable!\n"; 355 | return -8; 356 | } 357 | songripper->setProgram(QDir::currentPath() + "/gba_mus_ripper_gui"); 358 | #endif 359 | 360 | songripper->setArguments(argList); 361 | songripper->start(); 362 | songripper->waitForFinished(); 363 | if (songripper->exitCode() < 0) 364 | cout << "An error has occurred."; 365 | } 366 | } 367 | delete[] sound_bank_index_list; 368 | 369 | if (sb) 370 | { 371 | // Rips each sound bank in a different file/folder 372 | for(bank_t j = sound_bank_list.begin(); j != sound_bank_list.end(); ++j) 373 | { 374 | QCoreApplication::processEvents(); 375 | uint32_t bank_index = distance(sound_bank_list.begin(), j); 376 | 377 | string sbnumber = dec3(bank_index); 378 | string foldername = "soundbank_" + sbnumber; 379 | string sf_rip_args = "sound_font_ripper\n" + inGBA_path + "\n" + outPath + "/" + name + '/'; 380 | sf_rip_args += foldername + '/' + foldername /* + "_@" + hex(*j) */ + ".sf2\""; 381 | 382 | if (sample_rate) sf_rip_args += "\n-s" + to_string(sample_rate); 383 | if (main_volume) sf_rip_args += "\n-mv" + to_string(main_volume); 384 | if (gm) sf_rip_args += "\n-gm"; 385 | sf_rip_args += "\n0x" + hex(*j); 386 | 387 | QStringList argList = QString::fromStdString(sf_rip_args).split("\n"); 388 | 389 | #ifdef QT_DEBUG 390 | cout << "DEBUG: Going to call system(" << sf_rip_args << ")\n"; 391 | #endif 392 | //system(sf_rip_args.c_str()); 393 | QProcess *sf2ripper = new QProcess(); 394 | 395 | #ifdef Q_OS_WIN32 396 | if (!QFile::exists(QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui.exe"))) 397 | { 398 | cout << "Unable to find the " << QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui.exe").toStdString() << " executable!\n"; 399 | return -8; 400 | } 401 | sf2ripper->setProgram(QDir::currentPath() + "/gba_mus_ripper_gui.exe"); 402 | #else 403 | if (!QFile::exists(QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui"))) 404 | { 405 | cout << "Unable to find the " << QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui").toStdString() << " executable!\n"; 406 | return -8; 407 | } 408 | sf2ripper->setProgram(QDir::currentPath() + "/gba_mus_ripper_gui"); 409 | #endif 410 | 411 | sf2ripper->setArguments(argList); 412 | sf2ripper->start(); 413 | sf2ripper->waitForFinished(); 414 | cout << sf2ripper->readAllStandardOutput().toStdString(); 415 | if (sf2ripper->exitCode() != 0) 416 | cout << "An error has occured."; 417 | } 418 | } 419 | else 420 | { 421 | // Rips each sound bank in a single soundfont file 422 | // Build argument list to call sound_font_ripper 423 | // Output sound font named after the input ROM 424 | QCoreApplication::processEvents(); 425 | string sf_rip_args = "sound_font_ripper\n" + inGBA_path + "\n" + outPath + "/" + name + '/' + name + ".sf2"; 426 | if (sample_rate) sf_rip_args += "\n-s" + to_string(sample_rate); 427 | if (main_volume) sf_rip_args += "\n-mv" + to_string(main_volume); 428 | // Pass -gm argument if necessary 429 | if (gm) sf_rip_args += "\n-gm"; 430 | 431 | // Make sound banks addresses list. 432 | for(bank_t j = sound_bank_list.begin(); j != sound_bank_list.end(); ++j) 433 | sf_rip_args += "\n0x" + hex(*j); 434 | 435 | QStringList argList = QString::fromStdString(sf_rip_args).split("\n"); 436 | 437 | // Call sound font ripper 438 | #ifdef QT_DEBUG 439 | cout << "DEBUG: Going to call system(" << sf_rip_args << ")\n"; 440 | #endif 441 | //system(sf_rip_args.c_str()); 442 | QProcess *sf2ripper = new QProcess(); 443 | 444 | #ifdef Q_OS_WIN32 445 | if (!QFile::exists(QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui.exe"))) 446 | { 447 | cout << "Unable to find the " << QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui.exe").toStdString() << " executable!\n"; 448 | return -8; 449 | } 450 | sf2ripper->setProgram(QDir::currentPath() + "/gba_mus_ripper_gui.exe"); 451 | #else 452 | if (!QFile::exists(QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui"))) 453 | { 454 | cout << "Unable to find the " << QDir::toNativeSeparators(QDir::currentPath() + "/gba_mus_ripper_gui").toStdString() << " executable!\n"; 455 | return -8; 456 | } 457 | sf2ripper->setProgram(QDir::currentPath() + "/gba_mus_ripper_gui"); 458 | #endif 459 | 460 | sf2ripper->setProgram(QDir::currentPath() + "/gba_mus_ripper_gui"); 461 | sf2ripper->setArguments(argList); 462 | sf2ripper->start(); 463 | sf2ripper->waitForFinished(); 464 | cout << sf2ripper->readAllStandardOutput().toStdString(); 465 | if (sf2ripper->exitCode() != 0) 466 | cout << "An error has occured."; 467 | } 468 | cout << "Rip completed!\n"; 469 | return 0; 470 | } 471 | -------------------------------------------------------------------------------- /sound_font_ripper/sound_font_ripper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * GBA Sound Font Ripper (c) 2012, 2014 by Bregalad 3 | * This is free and open source software. 4 | * 5 | * This program extracts soundfont data from a GBA game using 6 | * Nintendo's "sappy" engine (which ~90% of commercial GBA games are using), 7 | * and converts it to the widely used SF2 Sound Font format. 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "sf2.h" 15 | #include "gba_instr.h" 16 | #include "../hex_string.h" 17 | #include 18 | 19 | static FILE *outSF2; 20 | static FILE *out_txt = stdout; // Log on stdout by default 21 | 22 | // Global variables 23 | FILE *psg_data; 24 | FILE *goldensun_synth; 25 | FILE *inGBA; 26 | 27 | static bool verbose_flag = false; 28 | static bool verbose_output_to_file = false; 29 | static bool change_sample_rate = false; 30 | static bool gm_preset_names = false; 31 | 32 | static uint32_t sample_rate = 22050; 33 | static std::set addresses; 34 | static uint32_t current_address; 35 | static uint32_t current_bank; 36 | static uint32_t current_instrument; 37 | static uint32_t main_volume = 15; 38 | 39 | static SF2 *sf2; 40 | static GBAInstr *instruments; 41 | 42 | static void print_instructions() 43 | { 44 | puts 45 | ( 46 | "Dumps a sound bank (or a list of sound banks) from a GBA game which is using the sappy sound engine to SoundFont 2.0 (.sf2) format\n" 47 | "Usage: sound_font_ripper [options] in.gba out.sf2 address1 [address2] ...\n" 48 | "addresses will correspond to instrument banks in increasing order...\n" 49 | "Available options :\n" 50 | "-v : verbose : Display info about the sound font in text format. If -v is followed by a file name,\n" 51 | " info is output to the specified file instead.\n" 52 | "-s : Sampling rate for samples. Default : 22050 Hz\n" 53 | "-gm : Give General MIDI names to presets. Note that this will only change the names and will NOT\n" 54 | " magically turn the soundfont into a General MIDI compliant soundfont.\n" 55 | "-mv : Main volume for sample instruments. Range : 1-15. Game Boy channels are unnaffected.\n" 56 | ); 57 | exit(-1); 58 | } 59 | 60 | 61 | // General MIDI instrument names 62 | static const char *const general_MIDI_instr_names[128] = 63 | { 64 | "Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", "Rhodes Piano", "Chorused Piano", 65 | "Harpsichord", "Clavinet", "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", 66 | "Hammond Organ", "Percussive Organ", "Rock Organ", "Church Organ", "Reed Organ", "Accordion", "Harmonica", "Tango Accordion", 67 | "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", "Electric Guitar (clean)", "Electric Guitar (muted)", 68 | "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics", "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", 69 | "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", "Violin", "Viola", "Cello", "Contrabass", 70 | "Tremelo Strings", "Pizzicato Strings", "Orchestral Harp", "Timpani", "String Ensemble 1", "String Ensemble 2", "SynthStrings 1", 71 | "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet", 72 | "French Horn", "Brass Section", "Synth Brass 1", "Synth Brass 2", "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", 73 | "Oboe", "English Horn", "Bassoon", "Clarinet", "Piccolo", "Flute", "Recorder", "Pan Flute", "Bottle Blow", "Shakuhachi", "Whistle", 74 | "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope lead)", "Lead 4 (chiff lead)", "Lead 5 (charang)", 75 | "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass + lead)", "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)", 76 | "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", 77 | "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", 78 | "Kalimba", "Bagpipe", "Fiddle", "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", 79 | "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", 80 | "Applause", "Gunshot" 81 | }; 82 | 83 | // Add initial attenuation preset to balance between GameBoy and sampled instruments 84 | static void add_attenuation_preset() 85 | { 86 | if (main_volume < 15) 87 | { 88 | const uint16_t attenuation = uint16_t(100.0 * log(15.0/main_volume)); 89 | sf2->add_new_preset_generator(SFGenerator::initialAttenuation, attenuation); 90 | } 91 | } 92 | 93 | // Convert a GBA instrument in its SF2 counterpart 94 | // if any kind of error happens, it will do nothing and exit 95 | static void build_instrument(const inst_data inst) 96 | { 97 | uint8_t instr_type = inst.word0 & 0xff; 98 | std::string name; 99 | if (gm_preset_names) 100 | name = std::string(general_MIDI_instr_names[current_instrument]); 101 | else 102 | // (poetic) name of the SF2 preset... 103 | name = "Type " + std::to_string(instr_type) + " @0x" + hex(current_address); 104 | 105 | try 106 | { 107 | switch (instr_type) 108 | { // Sampled instrument types 109 | case 0x00: 110 | case 0x08: 111 | case 0x10: 112 | case 0x18: 113 | case 0x20: 114 | case 0x28: 115 | case 0x30: 116 | case 0x38: 117 | { 118 | int32_t i = instruments->build_sampled_instrument(inst); 119 | sf2->add_new_preset(name.c_str(), current_instrument, current_bank); 120 | sf2->add_new_preset_bag(); 121 | // Add initial attenuation preset to balance volume between sampled and GB instruments 122 | add_attenuation_preset(); 123 | sf2->add_new_preset_generator(SFGenerator::instrument, i); 124 | } break; 125 | 126 | // GameBoy pulse wave instruments 127 | case 0x01: 128 | case 0x02: 129 | case 0x09: 130 | case 0x0a: 131 | { 132 | // Can only convert them if the psg_data file is found 133 | if (psg_data) 134 | { 135 | int32_t i = instruments->build_pulse_instrument(inst); 136 | sf2->add_new_preset(name.c_str(), current_instrument, current_bank); 137 | sf2->add_new_preset_bag(); 138 | sf2->add_new_preset_generator(SFGenerator::instrument, i); 139 | } 140 | } break; 141 | 142 | // GameBoy channel 3 instrument 143 | case 0x03: 144 | case 0x0b: 145 | { 146 | int32_t i = instruments->build_GB3_instrument(inst); 147 | sf2->add_new_preset(name.c_str(), current_instrument, current_bank); 148 | sf2->add_new_preset_bag(); 149 | sf2->add_new_preset_generator(SFGenerator::instrument, i); 150 | } break; 151 | 152 | // GameBoy noise instruments, not supported yet 153 | case 0x04: 154 | case 0x0c: 155 | { 156 | if (psg_data) 157 | { 158 | int32_t i = instruments->build_noise_instrument(inst); 159 | sf2->add_new_preset(name.c_str(), current_instrument, current_bank); 160 | sf2->add_new_preset_bag(); 161 | sf2->add_new_preset_generator(SFGenerator::instrument, i); 162 | } 163 | } break; 164 | 165 | // Key split instrument 166 | case 0x40: 167 | { 168 | int32_t i = instruments->build_keysplit_instrument(inst); 169 | sf2->add_new_preset(name.c_str(), current_instrument, current_bank); 170 | sf2->add_new_preset_bag(); 171 | // Add initial attenuation preset to balance volume between sampled and GB instruments 172 | add_attenuation_preset(); 173 | sf2->add_new_preset_generator(SFGenerator::instrument, i); 174 | } break; 175 | 176 | // Every key split instrument 177 | case 0x80: 178 | { 179 | int32_t i = instruments->build_every_keysplit_instrument(inst); 180 | sf2->add_new_preset(name.c_str(), current_instrument, current_bank); 181 | sf2->add_new_preset_bag(); 182 | // Add initial attenuation preset to balance volume between sampled and GB instruments 183 | add_attenuation_preset(); 184 | sf2->add_new_preset_generator(SFGenerator::instrument, i); 185 | } break; 186 | 187 | // Ignore other instrument types 188 | default: 189 | break; 190 | } 191 | 192 | // If there is any error in the process just ignore it and silently continue 193 | // In fact dozen of errors always happened all the times so I removed any form of error messages 194 | } 195 | catch (...) 196 | {} 197 | } 198 | 199 | // Display verbose to console or output to file if requested 200 | static void print(const std::string& s) 201 | { 202 | if (verbose_flag) 203 | fprintf(out_txt, s.c_str()); 204 | } 205 | 206 | static void print(const char* s) 207 | { 208 | if (verbose_flag) 209 | fprintf(out_txt, s); 210 | } 211 | 212 | // Display ADSR values used 213 | static void adsr(uint32_t adsr) 214 | { 215 | int32_t attack = adsr & 0xFF; 216 | int32_t decay = (adsr>>8) & 0xFF; 217 | int32_t sustain = (adsr>>16) & 0xFF; 218 | int32_t release = adsr>>24; 219 | // Print ADSR values 220 | fprintf(out_txt, " ADSR : %d, %d, %d, %d\n", attack, decay, sustain, release); 221 | } 222 | 223 | // Display duty cycle used 224 | static void duty_cycle(int32_t duty) 225 | { 226 | const char *const cycles[4] = {"12.5%", "25%", "50%", "75%"}; 227 | fprintf(out_txt, " Duty cycle : %s\n", cycles[duty&3]); 228 | } 229 | 230 | // This function read instrument data and outputs info on the screen or on the verbose file 231 | // it's not actually needed to convert the data to SF2 format, but is very useful for debugging 232 | static void verbose_instrument(const inst_data inst, bool recursive) 233 | { 234 | // Do nothing with unused instruments 235 | if (inst.word0 == 0x3c01 && inst.word1 == 0x02 && inst.word2 == 0x0F0000) return; 236 | 237 | uint8_t instr_type = inst.word0 & 0xff; 238 | fprintf(out_txt, " Type : 0x%x ", instr_type); 239 | switch(instr_type) 240 | { 241 | // Sampled instruments 242 | case 0 : 243 | case 8 : 244 | case 0x10 : 245 | case 0x18 : 246 | case 0x20 : 247 | case 0x28 : 248 | case 0x30 : 249 | case 0x38 : 250 | { 251 | uint32_t sadr = inst.word1 & 0x3ffffff; 252 | fprintf(out_txt, "(sample @0x%x)\n", sadr); 253 | 254 | try 255 | { 256 | if (fseek(inGBA, sadr, SEEK_SET)) throw -1; 257 | struct 258 | { 259 | uint32_t loop; 260 | uint32_t pitch; 261 | uint32_t loop_pos; 262 | uint32_t len; 263 | } 264 | ins; 265 | fread(&ins, 4, 4, inGBA); 266 | 267 | fprintf(out_txt, " Pitch : %u\n", ins.pitch/1024); 268 | fprintf(out_txt, " Length : %u\n", ins.len); 269 | 270 | if (ins.loop == 0) 271 | fputs(" Not looped\n", out_txt); 272 | else if (ins.loop == 0x40000000) 273 | fprintf(out_txt, " Loop enabled at : %u\n", ins.loop_pos); 274 | else if (ins.loop == 0x1) 275 | fputs(" BDPCM compressed\n", out_txt); 276 | else 277 | fputs(" Unknown loop type\n", out_txt); 278 | 279 | adsr(inst.word2); 280 | } 281 | catch (...) 282 | { 283 | fputs("Invalid instrument (an exception occured)", out_txt); 284 | } 285 | } break; 286 | 287 | // Pulse channel 1 instruments 288 | case 1 : 289 | case 9 : 290 | { 291 | fputs("(GB pulse channel 1)", out_txt); 292 | if ((char)inst.word0 != 8) // Display sweep if enabled on GB channel 1 293 | fprintf(out_txt, " Sweep : 0x%x\n", inst.word0 & 0xFF); 294 | 295 | adsr(inst.word2); 296 | duty_cycle(inst.word1); 297 | } break; 298 | 299 | // Pulse channel 2 instruments 300 | case 2 : 301 | case 10 : 302 | case 18 : 303 | { 304 | fputs("(GB pulse channel 2)", out_txt); 305 | adsr(inst.word2); 306 | duty_cycle(inst.word1); 307 | } break; 308 | 309 | // Channel 3 instruments 310 | case 3 : 311 | case 11 : 312 | { 313 | fputs("(GB channel 3)", out_txt); 314 | adsr(inst.word2); 315 | fputs(" Waveform : ", out_txt); 316 | 317 | try 318 | { 319 | // Seek to waveform's location 320 | if (fseek(inGBA, inst.word1&0x3ffffff, SEEK_SET)) throw -1; 321 | int32_t waveform[32]; 322 | 323 | for(int32_t j=0; j<16; j++) 324 | { 325 | uint8_t a = fgetc(inGBA); 326 | waveform[2*j] = a>>4; 327 | waveform[2*j+1] = a & 0xF; 328 | } 329 | 330 | // Display waveform in text format 331 | for(int32_t j=7; j>=0; j--) 332 | { 333 | for(int32_t k=0; k!=32; k++) 334 | { 335 | if (waveform[k] == 2*j) 336 | fputc('_', out_txt); 337 | else if (waveform[k] == 2*j+1) 338 | fputc('-', out_txt); 339 | else 340 | fputc(' ', out_txt); 341 | } 342 | fputc('\n', out_txt); 343 | } 344 | } 345 | catch(...) 346 | { 347 | fputs("Invalid instrument (an exception occured)", out_txt); 348 | } 349 | } break; 350 | 351 | // Noise instruments 352 | case 4 : 353 | case 12 : 354 | fputs("(GB noise channel 4)", out_txt); 355 | adsr(inst.word2); 356 | if (inst.word1 == 0) 357 | fputs(" long random sequence\n", out_txt); 358 | else 359 | fputs(" short random sequence\n", out_txt); 360 | break; 361 | 362 | // Key-split instruments 363 | case 0x40 : 364 | fputs("Key-split instrument", out_txt); 365 | 366 | if (!recursive) 367 | { 368 | bool *keys_used = new bool[128](); 369 | try 370 | { 371 | // seek to key table's location 372 | if (fseek(inGBA, inst.word2&0x3ffffff, SEEK_SET)) throw -1; 373 | 374 | for(int32_t k = 0; k!= 128; k++) 375 | { 376 | uint8_t c = fgetc(inGBA); 377 | if (c & 0x80) continue; // Ignore entries with MSB set (invalid) 378 | keys_used[c] = true; 379 | } 380 | 381 | int32_t instr_table = inst.word1 & 0x3ffffff; 382 | 383 | for(int32_t k = 0; k!= 128; k++) 384 | { 385 | // Decode instruments used at least once in the key table 386 | if (keys_used[k]) 387 | { 388 | try 389 | { 390 | // Seek to the addressed instrument 391 | if (fseek(inGBA, instr_table + 12*k, SEEK_SET)) throw -1; 392 | inst_data sub_instr; 393 | // Read the addressed instrument 394 | fread(&sub_instr, 4, 3, inGBA); 395 | 396 | fprintf(out_txt, "\n Sub_intrument %d", k); 397 | verbose_instrument(sub_instr, true); 398 | } 399 | catch(...) 400 | { 401 | fputs("Invalid sub-instrument (an exception occurred)", out_txt); 402 | } 403 | } 404 | } 405 | } 406 | catch (...) 407 | {} 408 | delete[] keys_used; 409 | } 410 | else 411 | fputs(" Illegal double-recursive instrument !", out_txt); 412 | break; 413 | 414 | // Every key split instruments 415 | case 0x80 : 416 | fputs("Every key split instrument", out_txt); 417 | 418 | if (!recursive) 419 | { 420 | uint32_t address = inst.word1 & 0x3ffffff; 421 | for(int32_t k = 0; k<128; ++k) 422 | { 423 | try 424 | { 425 | if (fseek(inGBA, address + k*12, SEEK_SET)) throw -1; 426 | inst_data key_instr; 427 | fread(&key_instr, 4, 3, inGBA); 428 | 429 | fprintf(out_txt, "\n Key %d", k); 430 | verbose_instrument(key_instr, true); 431 | } 432 | catch(...) 433 | { 434 | fputs("Illegal sub-instrument (an exception occured)", out_txt); 435 | } 436 | } 437 | } 438 | else // Prevent instruments with multiple recursivities 439 | fputs(" Illegal double-recursive instrument !", out_txt); 440 | break; 441 | 442 | default : 443 | fputs("Unknown instrument type", out_txt); 444 | return; 445 | } 446 | if (recursive) 447 | fprintf(out_txt, " Key : %d, Pan : %d\n", (inst.word1>>8) & 0xFF, inst.word1>>24); 448 | } 449 | 450 | static void parse_arguments(const int32_t argc, char *const argv[]) 451 | { 452 | if (argc == 0) print_instructions(); 453 | bool infile_found = false; 454 | bool outfile_found = false; 455 | 456 | for(int32_t i = 0; i15) 495 | { 496 | fprintf(stderr, "Error, main volume %u is not valid (should be 0-15).\n", volume); 497 | exit(-1); 498 | } 499 | main_volume = volume; 500 | } 501 | else if (!strcmp(argv[i], "-gm")) 502 | gm_preset_names = true; 503 | 504 | else if (!strcmp(argv[i], "-help")) 505 | print_instructions(); 506 | } 507 | 508 | // Try to parse an address and add it to list if succes 509 | else if (!infile_found) 510 | { 511 | // Input File 512 | infile_found = true; 513 | inGBA = fopen(argv[i], "rb"); 514 | if (!inGBA) 515 | { 516 | fprintf(stderr, "Can't read input GBA file : %s\n", argv[0]); 517 | exit(-1); 518 | } 519 | } 520 | else if (!outfile_found) 521 | { 522 | outfile_found = true; 523 | size_t l = strlen(argv[i]); 524 | char *buffer = argv[i]; 525 | if (l <= 4 || strcmp(argv[i] + (l-4), ".sf2")) 526 | { // Append ".sf2" after the given file name if there isn't it already 527 | buffer = new char[l+4]; 528 | strcpy(buffer, argv[i]); 529 | strcpy(buffer + l, ".sf2"); 530 | } 531 | outSF2 = fopen(buffer, "wb"); 532 | if (!outSF2) 533 | { 534 | fprintf(stderr, "Can't write on file : %s\n", argv[i]); 535 | exit(-1); 536 | } 537 | if (buffer != argv[i]) 538 | delete[] buffer; 539 | } 540 | else 541 | { 542 | uint32_t address = strtoul(argv[i], 0, 0); 543 | if (!address) print_instructions(); 544 | addresses.insert(address); 545 | } 546 | } 547 | // Diagnostize errors/missing information 548 | if (!infile_found) 549 | { 550 | fputs("An input .gba file should be given. Use -help for more information.\n", stderr); 551 | exit(-1); 552 | } 553 | if (!outfile_found) 554 | { 555 | fputs("An output .sf2 file should be given. Use -help for more information.\n", stderr); 556 | exit(-1); 557 | } 558 | if (addresses.empty()) 559 | { 560 | fputs("At least one adress should be given for decoding. Use -help for more information.\n", stderr); 561 | exit(-1); 562 | } 563 | } 564 | 565 | int32_t sound_font_ripper(int32_t argc, char *argv[]) 566 | { 567 | puts("GBA ROM sound font ripper (c) 2012 Bregalad"); 568 | 569 | // Parse arguments without the program name 570 | parse_arguments(argc-1, argv+1); 571 | 572 | // Compute prefix (path) of this program's name 573 | std::string prg_name = argv[0]; 574 | std::string prg_prefix = prg_name.substr(0, prg_name.find("sound_font_ripper")); 575 | 576 | // Create SF2 class 577 | sf2 = new SF2(sample_rate); 578 | instruments = new GBAInstr(sf2); 579 | 580 | // Attempt to access psg_data file 581 | psg_data = fopen((prg_prefix + "psg_data.raw").c_str(), "rb"); 582 | if (!psg_data) 583 | puts("psg_data.raw file not found ! PSG Instruments can't be dumped."); 584 | 585 | // Attempt to access goldensun_synth file 586 | goldensun_synth = fopen((prg_prefix + "goldensun_synth.raw").c_str(), "rb"); 587 | if (!goldensun_synth) 588 | puts("goldensun_synth.raw file not found ! Golden Sun's synth instruments can't be dumped."); 589 | 590 | // Read instrument data from input GBA file 591 | inst_data *instr_data = new inst_data[128]; 592 | 593 | // Decode all banks 594 | current_bank = 0; 595 | for(std::set::iterator it = addresses.begin(); it != addresses.end(); ++it, ++current_bank) 596 | { 597 | current_address = *it; 598 | std::set::iterator next_it = it; 599 | ++next_it; 600 | uint32_t next_address = *next_it; 601 | 602 | // Limit the # of presets if the addresses overlaps 603 | uint32_t ninstr = 128; 604 | if (addresses.end() != next_it && (next_address - current_address)/12 < 128) 605 | ninstr = (next_address - current_address)/12; 606 | 607 | // Seek at the start of the sound bank 608 | if (fseek(inGBA, current_address, SEEK_SET) != 0 609 | || fread(instr_data, 4, ninstr*3, inGBA) != ninstr*3) // Read entire sound bank in memory 610 | { 611 | fprintf(stderr, "Error : Invalid position within input GBA file : 0x%x\n", current_address); 612 | exit(-3); 613 | } 614 | 615 | // Decode all instruments 616 | for(current_instrument = 0; current_instrument < ninstr; ++current_instrument, current_address += 12) 617 | { 618 | print("\nBank : " + std::to_string(current_bank) + ", Instrument : " + std::to_string(current_instrument) + " @0x" + hex(current_address)); 619 | 620 | // Ignore unused instruments 621 | if (instr_data[current_instrument].word0 == 0x3c01 622 | && instr_data[current_instrument].word1 == 0x02 623 | && instr_data[current_instrument].word2 == 0x0F0000) 624 | { 625 | print(" (unused)"); 626 | continue; 627 | } 628 | 629 | if (verbose_flag) 630 | verbose_instrument(instr_data[current_instrument], false); 631 | 632 | // Build equivalent SF2 instrument 633 | build_instrument(instr_data[current_instrument]); 634 | } 635 | } 636 | delete[] instr_data; 637 | 638 | if (verbose_output_to_file) 639 | { 640 | print("\n\n EOF"); 641 | fclose(out_txt); 642 | } 643 | 644 | printf("\nDump complete, now outputting SF2 data..."); 645 | 646 | sf2->write(outSF2); 647 | delete instruments; 648 | delete sf2; 649 | 650 | // Close files 651 | fclose(inGBA); 652 | 653 | if (psg_data) fclose(psg_data); 654 | if (goldensun_synth) fclose(goldensun_synth); 655 | 656 | puts(" Done !"); 657 | return 0; 658 | } 659 | -------------------------------------------------------------------------------- /song_ripper/song_ripper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * GBA Songripper (c) 2012, 2014 by Bregalad 3 | * This is free and open source software 4 | * 5 | * This program convert a GBA song for the sappy sound engine into MIDI (.mid) format. 6 | */ 7 | 8 | #include "song_ripper.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | 18 | class Note; 19 | // Global variables for this program 20 | 21 | static uint32_t track_ptr[16]; 22 | static uint8_t last_cmd[16]; 23 | static char last_key[16]; 24 | static char last_vel[16]; 25 | static int32_t counter[16]; 26 | static uint32_t return_ptr[16]; 27 | static int32_t key_shift[16]; 28 | static bool return_flag[16]; 29 | static bool track_completed[16]; 30 | static bool end_flag = false; 31 | static bool loop_flag = false; 32 | static uint32_t loop_adr; 33 | 34 | static int32_t lfo_delay_ctr[16]; 35 | static int32_t lfo_delay[16]; 36 | static int32_t lfo_depth[16]; 37 | static int32_t lfo_type[16]; 38 | static bool lfo_flag[16]; 39 | static bool lfo_hack[16]; 40 | 41 | static uint32_t simultaneous_notes_ctr = 0; 42 | static uint32_t simultaneous_notes_max = 0; 43 | 44 | static forward_list notes_playing; 45 | 46 | static int32_t bank_number; 47 | static bool bank_used = false; 48 | static bool rc = false; 49 | static bool gs = false; 50 | static bool xg = false; 51 | static bool lv = false; 52 | static bool sv = false; 53 | 54 | static MIDI midi(24); 55 | static FILE *inGBA; 56 | 57 | static void process_event(int32_t track); 58 | 59 | static void print_instructions() 60 | { 61 | cout << 62 | "Rips sequence data from a GBA game using sappy sound engine to MIDI (.mid) format.\n" 63 | "\nUsage : song_ripper infile.gba outfile.mid song_adress [-b1 -gm -gs -xg]\n" 64 | "-b : Bank : forces all patches to be in the specified bank (0-127)\n" 65 | "In general MIDI, midi channel 10 is reserved for drums\n" 66 | "Unfortunately, we do not want to use any \"drums\" in the output file\n" 67 | "I have 3 modes to fix this problem\n" 68 | "-rc : Rearrange Channels. This will avoid using the channel 10, and use it at last ressort only if all 16 channels should be used\n" 69 | "-gs : This will send a GS system exclusive message to tell the player channel 10 is not drums\n" 70 | "-xg : This will send a XG system exclusive message, and force banks number which will disable \"drums\"\n" 71 | "-lv : Linearise volume and velocities. This should be used to have the output \"sound\" like the original song,\n" 72 | " but shouldn't be used to get an exact dump of sequence data." 73 | "-sv : Simulate vibrato. This will insert controllers in real time to simulate a vibrato, instead of just\n" 74 | " when commands are given. Like -lv, this should be used to have the output \"sound\" like the original song,\n" 75 | " but shouldn't be used to get an exact dump of sequence data.\n" 76 | "It is possible, but not recommended, to use more than one of these flags at a time.\n" 77 | ; 78 | exit(-1); 79 | } 80 | 81 | static void add_simultaneous_note() 82 | { 83 | // Update simultaneous notes max. 84 | if (++simultaneous_notes_ctr > simultaneous_notes_max) 85 | simultaneous_notes_max = simultaneous_notes_ctr; 86 | } 87 | 88 | // LFO logic on tick 89 | static void process_lfo(int32_t track) 90 | { 91 | if (sv && lfo_delay_ctr[track] != 0) 92 | { 93 | // Decrease counter if it's value was nonzero 94 | if (--lfo_delay_ctr[track] == 0) 95 | { 96 | // If 1->0 transition we need to add a signal to start the LFO 97 | if (lfo_type[track] == 0) 98 | // Send a controller 1 if pitch LFO 99 | midi.add_controller(track, 1, (lfo_depth[track] < 16) ? lfo_depth[track] * 8 : 127); 100 | else 101 | // Send a channel aftertouch otherwise 102 | midi.add_chanaft(track, (lfo_depth[track] < 16) ? lfo_depth[track] * 8 : 127); 103 | lfo_flag[track] = true; 104 | } 105 | } 106 | } 107 | 108 | static void start_lfo(int32_t track) 109 | { 110 | // Reset down delay counter to its initial value 111 | if (sv && lfo_delay[track] != 0) 112 | lfo_delay_ctr[track] = lfo_delay[track]; 113 | } 114 | 115 | static void stop_lfo(int32_t track) 116 | { 117 | // Cancel a LFO if it was playing, 118 | if (sv && lfo_flag[track]) 119 | { 120 | if (lfo_type[track]==0) 121 | midi.add_controller(track, 1, 0); 122 | else 123 | midi.add_chanaft(track, 0); 124 | lfo_flag[track] = false; 125 | } 126 | else 127 | { 128 | // Cancel delay counter if it wasn't playing 129 | lfo_delay_ctr[track] = 0; 130 | } 131 | } 132 | 133 | // Note class 134 | // this was needed to properly handle polyphony on all channels... 135 | 136 | class Note 137 | { 138 | MIDI& midi; 139 | int32_t counter; 140 | int32_t key; 141 | int32_t vel; 142 | int32_t chn; 143 | bool event_made; 144 | 145 | // Tick counter, if it becomes zero 146 | // then create key off event 147 | // this function returns "true" when the note should be freed from memory 148 | bool tick() 149 | { 150 | if (counter > 0 && --counter == 0) 151 | { 152 | midi.add_note_off(chn, key, vel); 153 | stop_lfo(chn); 154 | simultaneous_notes_ctr --; 155 | return true; 156 | } 157 | else 158 | return false; 159 | } 160 | friend bool countdown_is_over(Note& n); 161 | friend void make_note_on_event(Note& n); 162 | 163 | public: 164 | // Create note and key on event 165 | Note(MIDI& midi, int32_t chn, int32_t len, int32_t key, int32_t vel) : 166 | midi(midi), counter(len), key(key), vel(vel), chn(chn) 167 | { 168 | event_made = false; 169 | 170 | start_lfo(chn); 171 | add_simultaneous_note(); 172 | } 173 | }; 174 | 175 | bool countdown_is_over(Note& n) 176 | { 177 | return n.tick() || n.counter < 0; 178 | } 179 | 180 | void make_note_on_event(Note& n) 181 | { 182 | if (!n.event_made) 183 | { 184 | midi.add_note_on(n.chn, n.key, n.vel); 185 | n.event_made = true; 186 | } 187 | } 188 | 189 | static bool tick(int32_t track_amnt) 190 | { 191 | // Tick all playing notes, and remove notes which 192 | // have been keyed off OR which are infinite length from the list 193 | notes_playing.remove_if (countdown_is_over); 194 | 195 | // Process all tracks 196 | for(int32_t track = 0; track < track_amnt; track++) 197 | { 198 | counter[track]--; 199 | // Process events until counter non-null or pointer null 200 | // This might not be executed if counter both are non null. 201 | while(track_ptr[track] != 0 && !end_flag && counter[track] <= 0) 202 | { 203 | // Check if we're at loop start point 204 | if (track == 0 && loop_flag && !return_flag[0] && !track_completed[0] && track_ptr[0] == loop_adr) 205 | midi.add_marker("loopStart"); 206 | 207 | process_event(track); 208 | } 209 | } 210 | 211 | for(int32_t track = 0; track < track_amnt; track++) 212 | { 213 | process_lfo(track); 214 | } 215 | 216 | // Compute if all still active channels are completely decoded 217 | bool all_completed_flag = true; 218 | for(int32_t i = 0; i < track_amnt; i++) 219 | all_completed_flag &= track_completed[i]; 220 | 221 | // If everything is completed, the main program should quit its loop 222 | if (all_completed_flag) return false; 223 | 224 | // Make note on events for this tick 225 | //(it's important they are made after all other events) 226 | for_each(notes_playing.begin(), notes_playing.end(), make_note_on_event); 227 | 228 | // Increment MIDI time 229 | midi.clock(); 230 | return true; 231 | } 232 | 233 | static uint32_t get_GBA_pointer() 234 | { 235 | uint32_t p; 236 | fread(&p, 1, 4, inGBA); 237 | return p & 0x3FFFFFF; 238 | } 239 | 240 | static void process_event(int32_t track) 241 | { 242 | // Length table for notes and rests 243 | const int32_t lenTbl[] = 244 | { 245 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 246 | 16, 17, 18, 19, 20, 21, 22, 23, 24, 28, 30, 32, 36, 247 | 40, 42, 44, 48, 52, 54, 56, 60, 64, 66, 68, 72, 76, 78, 248 | 80, 84, 88, 90, 92, 96 249 | }; 250 | 251 | fseek(inGBA, track_ptr[track], SEEK_SET); 252 | // Read command 253 | uint8_t command = fgetc(inGBA); 254 | 255 | track_ptr[track]++; 256 | uint8_t arg1; 257 | // Repeat last command, the byte read was in fact the first argument 258 | if (command < 0x80) 259 | { 260 | arg1 = command; 261 | command = last_cmd[track]; 262 | } 263 | 264 | // Delta time command 265 | else if (command <= 0xb0) 266 | { 267 | counter[track] = lenTbl[command - 0x80]; 268 | return; 269 | } 270 | 271 | // End track command 272 | else if (command == 0xb1) 273 | { 274 | // Null pointer 275 | track_ptr[track] = 0; 276 | track_completed[track] = true; 277 | return; 278 | } 279 | 280 | // Jump command 281 | else if (command == 0xb2) 282 | { 283 | track_ptr[track] = get_GBA_pointer(); 284 | 285 | // detect the end track 286 | track_completed[track] = true; 287 | return; 288 | } 289 | 290 | // Call command 291 | else if (command == 0xb3) 292 | { 293 | uint32_t addr = get_GBA_pointer(); 294 | 295 | // Return address for the track 296 | return_ptr[track] = track_ptr[track] + 4; 297 | // Now points to called address 298 | track_ptr[track] = addr; 299 | return_flag[track] = true; 300 | return; 301 | } 302 | 303 | // Return command 304 | else if (command == 0xb4) 305 | { 306 | if (return_flag[track]) 307 | { 308 | track_ptr[track] = return_ptr[track]; 309 | return_flag[track] = false; 310 | } 311 | return; 312 | } 313 | 314 | // Tempo change 315 | else if (command == 0xbb) 316 | { 317 | int32_t tempo = 2*fgetc(inGBA); 318 | track_ptr[track]++; 319 | midi.add_tempo(tempo); 320 | return; 321 | } 322 | 323 | else 324 | { 325 | // Normal command 326 | last_cmd[track] = command; 327 | // Need argument 328 | arg1 = fgetc(inGBA); 329 | track_ptr[track]++; 330 | } 331 | 332 | // Note on with specified length command 333 | if (command >= 0xd0) 334 | { 335 | int32_t key, vel, len_ofs = 0; 336 | // Is arg1 a key value ? 337 | if (arg1 < 0x80) 338 | { // Yes -> use new key value 339 | key = arg1; 340 | last_key[track] = key; 341 | 342 | uint8_t arg2 = fgetc(inGBA); 343 | // Is arg2 a velocity ? 344 | if (arg2 < 0x80) 345 | { // Yes -> use new velocity value 346 | vel = arg2; 347 | last_vel[track] = vel; 348 | track_ptr[track]++; 349 | 350 | uint8_t arg3 = fgetc(inGBA); 351 | 352 | // Is there a length offset ? 353 | if (arg3 < 0x80) 354 | { // Yes -> read it and increment pointer 355 | len_ofs = arg3; 356 | track_ptr[track]++; 357 | } 358 | } 359 | else 360 | { // No -> use previous velocity value 361 | vel = last_vel[track]; 362 | } 363 | } 364 | else 365 | { 366 | // No -> use last value 367 | key = last_key[track]; 368 | vel = last_vel[track]; 369 | track_ptr[track]--; // Seek back, as arg 1 is unused and belong to next event ! 370 | } 371 | 372 | // Linearise velocity if needed 373 | if (lv) vel = sqrt(127.0 * vel); 374 | 375 | notes_playing.push_front(Note(midi, track, lenTbl[command - 0xd0 + 1] + len_ofs, key + key_shift[track], vel)); 376 | return; 377 | } 378 | 379 | // Other commands 380 | switch(command) 381 | { 382 | // Key shift 383 | case 0xbc : 384 | key_shift[track] = arg1; 385 | return; 386 | 387 | // Set instrument 388 | case 0xbd : 389 | if (bank_used) 390 | { 391 | if (!xg) 392 | midi.add_controller(track, 0, bank_number); 393 | else 394 | { 395 | midi.add_controller(track, 0, bank_number >> 7); 396 | midi.add_controller(track, 32, bank_number & 0x7f); 397 | } 398 | } 399 | midi.add_pchange(track, arg1); 400 | return; 401 | 402 | // Set volume 403 | case 0xbe : 404 | { // Linearise volume if needed 405 | int32_t volume = lv ? (int32_t)sqrt(127.0 * arg1) : arg1; 406 | midi.add_controller(track, 7, volume); 407 | } return; 408 | 409 | // Set panning 410 | case 0xbf : 411 | midi.add_controller(track, 10, arg1); 412 | return; 413 | 414 | // Pitch bend 415 | case 0xc0 : 416 | midi.add_pitch_bend(track, (char)arg1); 417 | return; 418 | 419 | // Pitch bend range 420 | case 0xc1 : 421 | if (sv) 422 | midi.add_RPN(track, 0, (char)arg1); 423 | else 424 | midi.add_controller(track, 20, arg1); 425 | return; 426 | 427 | // LFO Speed 428 | case 0xc2 : 429 | if (sv) 430 | midi.add_NRPN(track, 136, (char)arg1); 431 | else 432 | midi.add_controller(track, 21, arg1); 433 | return; 434 | 435 | // LFO delay 436 | case 0xc3 : 437 | if (sv) 438 | lfo_delay[track] = arg1; 439 | else 440 | midi.add_controller(track, 26, arg1); 441 | return; 442 | 443 | // LFO depth 444 | case 0xc4 : 445 | if (sv) 446 | { 447 | if (lfo_delay[track] == 0 && lfo_hack[track]) 448 | { 449 | if (lfo_type[track] == 0) 450 | midi.add_controller(track, 1, arg1 > 12 ? 127 : 10 * arg1); 451 | else 452 | midi.add_chanaft(track, arg1 > 12 ? 127 : 10 * arg1); 453 | 454 | lfo_flag[track] = true; 455 | } 456 | lfo_depth[track] = arg1; 457 | // I had a stupid bug with LFO inserting controllers I didn't want at the start of files 458 | // So I made a terrible quick fix for it, in the mean time I can find something better to prevent it. 459 | lfo_hack[track] = true; 460 | } 461 | else 462 | midi.add_controller(track, 1, arg1); 463 | return; 464 | 465 | // LFO type 466 | case 0xc5 : 467 | if (sv) 468 | lfo_type[track] = arg1; 469 | else 470 | midi.add_controller(track, 22, arg1); 471 | return; 472 | 473 | // Detune 474 | case 0xc8 : 475 | if (sv) 476 | midi.add_RPN(track, 1, (char)arg1); 477 | else 478 | midi.add_controller(track, 24, arg1); 479 | return; 480 | 481 | // Key off 482 | case 0xce : 483 | { 484 | int32_t key, vel = 0; 485 | 486 | // Is arg1 a key value ? 487 | if (arg1 < 0x80) 488 | { // Yes -> use new key value 489 | key = arg1; 490 | last_key[track] = key; 491 | } 492 | else 493 | { // No -> use last value 494 | key = last_key[track]; 495 | vel = last_vel[track]; 496 | track_ptr[track]--; // Seek back, as arg 1 is unused and belong to next event ! 497 | } 498 | 499 | midi.add_note_off(track, key + key_shift[track], vel); 500 | stop_lfo(track); 501 | simultaneous_notes_ctr--; 502 | } return; 503 | 504 | // Key on 505 | case 0xcf : 506 | { 507 | int32_t key, vel; 508 | // Is arg1 a key value ? 509 | if (arg1 < 0x80) 510 | { 511 | // Yes -> use new key value 512 | key = arg1; 513 | last_key[track] = key; 514 | 515 | uint8_t arg2 = fgetc(inGBA); 516 | // Is arg2 a velocity ? 517 | if (arg2 < 0x80) 518 | { 519 | // Yes -> use new velocity value 520 | vel = arg2; 521 | last_vel[track] = vel; 522 | track_ptr[track]++; 523 | } 524 | else // No -> use previous velocity value 525 | vel = last_vel[track]; 526 | } 527 | else 528 | { 529 | // No -> use last value 530 | key = last_key[track]; 531 | vel = last_vel[track]; 532 | track_ptr[track]--; // Seek back, as arg 1 is unused and belong to next event ! 533 | } 534 | // Linearise velocity if needed 535 | if (lv) vel = (int32_t)sqrt(127.0 * vel); 536 | 537 | // Make note of infinite length 538 | notes_playing.push_front(Note(midi, track, -1, key + key_shift[track], vel)); 539 | } return; 540 | 541 | default : 542 | break; 543 | } 544 | } 545 | 546 | static uint32_t parseArguments(const int32_t argv, const char *const args[]) 547 | { 548 | if (argv < 3) print_instructions(); 549 | 550 | // Open the input and output files 551 | inGBA = fopen(args[0], "rb"); 552 | if (!inGBA) 553 | { 554 | cout << stderr << "Can't open file " << args[0] << " for reading.\n"; 555 | exit(-2); 556 | } 557 | 558 | for(int32_t i = 3; i < argv; i++) 559 | { 560 | cout << "arg #" << i << " = " << args[i] << "\n"; 561 | cout << "\n"; 562 | if (args[i][0] == '-') 563 | { 564 | if (args[i][1] == 'b') 565 | { 566 | if (strlen(args[i]) < 3) print_instructions(); 567 | bank_number = atoi(args[i] + 2); 568 | bank_used = true; 569 | } 570 | else if (args[i][1] == 'r' && args[i][2] == 'c') 571 | rc = true; 572 | else if (args[i][1] == 'g' && args[i][2] == 's') 573 | gs = true; 574 | else if (args[i][1] == 'x' && args[i][2] == 'g') 575 | xg = true; 576 | else if (args[i][1]=='l' && args[i][2] == 'v') 577 | lv = true; 578 | else if (args[i][1] == 's' && args[i][2] == 'v') 579 | sv = true; 580 | else 581 | print_instructions(); 582 | } 583 | else 584 | print_instructions(); 585 | } 586 | // Return base adress, parsed correctly in both decimal and hex 587 | return strtoul(args[2], 0, 0); 588 | } 589 | 590 | int32_t song_ripper(int32_t argc, char *argv[]) 591 | { 592 | FILE *outMID; 593 | cout << "GBA ROM sequence ripper (c) 2012 Bregalad"; 594 | uint32_t base_address = parseArguments(argc - 1, argv + 1); 595 | 596 | if (fseek(inGBA, base_address, SEEK_SET)) 597 | { 598 | cout << stderr << "Can't seek to the base address 0x" << base_address << ".\n"; 599 | exit(-3); 600 | } 601 | 602 | int32_t track_amnt = fgetc(inGBA); 603 | if (track_amnt < 1 || track_amnt > 16) 604 | { 605 | cout << stderr << "Invalid amount of tracks: " << track_amnt << "! (must be 1-16).\n"; 606 | exit(-4); 607 | } 608 | cout << track_amnt << " tracks.\n"; 609 | 610 | // Open output file once we know the pointer points to correct data 611 | //(this avoids creating blank files when there is an error) 612 | outMID = fopen(argv[2], "wb"); 613 | if (!outMID) 614 | { 615 | cout << stderr << "Can't write on file " << argv[2] << ".\n"; 616 | exit(-5); 617 | } 618 | 619 | cout << "Converting..."; 620 | 621 | if (rc) 622 | { // Make the drum channel last in the list, hopefully reducing the risk of it being used 623 | midi.chn_reorder[9] = 15; 624 | for(uint32_t j = 10; j < 16; ++j) 625 | midi.chn_reorder[j] = j - 1; 626 | } 627 | 628 | if (gs) 629 | { // GS reset 630 | const char gs_reset_sysex[] = {0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7f, 0x00, 0x41}; 631 | midi.add_sysex(gs_reset_sysex, sizeof(gs_reset_sysex)); 632 | // Part 10 to normal 633 | const char part_10_normal_sysex[] = {0x41, 0x10, 0x42, 0x12, 0x40, 0x10, 0x15, 0x00, 0x1b}; 634 | midi.add_sysex(part_10_normal_sysex, sizeof(part_10_normal_sysex)); 635 | } 636 | 637 | if (xg) 638 | { // XG reset 639 | const char xg_sysex[] = {0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00}; 640 | midi.add_sysex(xg_sysex, sizeof xg_sysex); 641 | } 642 | 643 | midi.add_marker("Converted by SequenceRipper 2.0"); 644 | 645 | fgetc(inGBA); // Unknown byte 646 | fgetc(inGBA); // Priority 647 | int8_t reverb = fgetc(inGBA); // Reverb 648 | 649 | int32_t instr_bank_address = get_GBA_pointer(); 650 | 651 | // Read table of pointers 652 | for(int32_t i = 0; i < track_amnt; i++) 653 | { 654 | track_ptr[i] = get_GBA_pointer(); 655 | 656 | lfo_depth[i] = 0; 657 | lfo_delay[i] = 0; 658 | lfo_flag[i] = false; 659 | 660 | if (reverb < 0) // add reverb controller on all tracks 661 | midi.add_controller(i, 91, lv ? (int32_t)sqrt((reverb & 0x7f) * 127.0) : reverb & 0x7f); 662 | } 663 | 664 | // Search for loop address of track #0 665 | if (track_amnt > 1) // If 2 or more track, end of track is before start of track 2 666 | fseek(inGBA, track_ptr[1] - 9, SEEK_SET); 667 | else 668 | // If only a single track, the end is before start of header data 669 | fseek(inGBA, base_address - 9, SEEK_SET); 670 | 671 | // Read where in track 1 the loop starts 672 | for(int32_t i = 0; i < 5; i++) 673 | if (fgetc(inGBA) == 0xb2) 674 | { 675 | loop_flag = true; 676 | loop_adr = get_GBA_pointer(); 677 | break; 678 | } 679 | 680 | // This is the main loop which will process all channels 681 | // until they are all inactive 682 | int32_t i = 100000; 683 | while(tick(track_amnt)) 684 | { 685 | if (i-- == 0) 686 | { // Security thing to avoid infinite loop in case things goes wrong 687 | cout << "Time out!"; 688 | break; 689 | } 690 | } 691 | 692 | // If a loop was detected this is its end 693 | if (loop_flag) midi.add_marker("loopEnd"); 694 | 695 | cout << "Maximum simultaneous notes: " << simultaneous_notes_max << "\n"; 696 | 697 | cout << "Dump complete. Now outputting MIDI file..."; 698 | midi.write(outMID); 699 | // Close files 700 | fclose(inGBA); 701 | cout << "Done!"; 702 | return instr_bank_address; 703 | } 704 | -------------------------------------------------------------------------------- /sound_font_ripper/sf2_chunks.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Sound Font chunk classes 3 | * 4 | * This is part of the GBA SoundFont Ripper (c) 2012 by Bregalad 5 | * This is free and open source software. 6 | * 7 | * Notes : I tried to separate the GBA-related stuff and SF2 related stuff as much as possible in different classes 8 | * that way anyone can re-use this program for building SF2s out of different data. 9 | * 10 | * SF2 is a SoundFont file format. This class (and related classes) serves to create data to build 11 | * a SF2 file. For more details look at the document "Sound Font(R) Technical Documentation" version 2.01 12 | * which was used as a reference when writing these classes. 13 | * By the way I must tell this format is incredibly stupid and complex as opposed to what it could be and 14 | * I have no idea why people started to adopt this standard. But anyways they did... 15 | * 16 | * This file contains classes for all chunks and subchunks present in a SF2 file 17 | * 18 | * All SF2-related classes contains a field named sf2 that links to the SF2 main class to know which 19 | * SF2 file they relates to. (this would make it possible to build multiple SF2 files at a time). 20 | * 21 | * Note : These classes helps in the complex process of building a SF2 and make it much more simpler and automated. 22 | * Not all of SF2's features are supported, support for ROM samples, stereo samples and modulators is 23 | * incomplete Yet those classes contains nothing GBA-related and could easily be re-used in another project as-it. 24 | * 25 | * There is basically 2 ways to adds data, such as a new sample, new instrument of new preset. 26 | * 27 | * The first way is to make it all automatically by using the add_new_xxx() functions. It's recommended to do 28 | * it this way whenever possible HOWEVER to have a file which is structurally correct it is imperative to 29 | * add them in this order : 30 | * 1) Instrument/Preset header 31 | * 2) Bag 1 32 | * 3) Modulators and generators for Bag1 33 | * 4) Bag 2 (optional) 34 | * 5) Modulators and generators for Bag2 (optional) 35 | * etc... 36 | * 37 | * The second way is to create moderators, generators and bag directly using their respective classes. That way 38 | * they can be created in any order, but it's important they are created in a first step, and added in a second step 39 | * in the order they should be in the final file. 40 | */ 41 | #ifndef SF2_CHUNKS_HPP 42 | #define SF2_CHUNKS_HPP 43 | 44 | #include 45 | #include 46 | #include 47 | #include "sf2.h" 48 | #include "sf2_types.h" 49 | #include 50 | 51 | // SF2Chunks abstract class 52 | // All Chunks and SubChunks should extend this class. 53 | // They should all call this constructor first, 54 | // and they should have a write() function that calls this write() function first. 55 | class SF2Chunks 56 | { 57 | protected: 58 | char name[4]; // 4-letter name of the subchunk 59 | public: 60 | uint32_t size; // Size in bytes of the subchunk 61 | protected: 62 | SF2 *sf2; 63 | 64 | // Constructor (should be systematically called by sub-classes) 65 | SF2Chunks(SF2 *sf2, const char name[4], uint32_t size = 0) : 66 | size(size), // The chunk starts bank 67 | sf2(sf2) // Link to output SF2 file 68 | { 69 | for(uint32_t i=0; i < 4; ++i) 70 | SF2Chunks::name[i] = name[i]; 71 | } 72 | 73 | // Write the name and size of the (sub)chunk (should be systematically called by sub-classes) 74 | inline void write() 75 | { 76 | fwrite(&name, 2, 4, sf2->out); 77 | } 78 | }; 79 | 80 | 81 | /* 82 | * Helper classes 83 | */ 84 | 85 | // Preset header class 86 | class sfPresetHeader 87 | { 88 | char ach_preset_name[20]; // Preset's name 89 | uint16_t wPreset; // Patch # 90 | uint16_t wBank; // Bank # 91 | uint16_t wPresetBagNdx; // Index to "bag" of instruments (private - created automatically) 92 | const uint32_t dwLibrary = 0; // Unused values - should be kept to 0 93 | const uint32_t dwGenre = 0; 94 | const uint32_t dwMorphology = 0; 95 | SF2 *sf2; 96 | public: 97 | sfPresetHeader(SF2 *sf2, const char *name, uint16_t patch, uint16_t bank) : 98 | wPreset(patch), wBank(bank), sf2(sf2) 99 | { 100 | strncpy(ach_preset_name, name, 20); 101 | wPresetBagNdx = sf2->get_pbag_size(); 102 | } 103 | 104 | void write() 105 | { 106 | fwrite(&ach_preset_name, 1, 38, sf2->out); 107 | } 108 | }__attribute__ ((packed)); 109 | 110 | // Preset bag class 111 | class sfBag 112 | { 113 | // Private to prevent anyone from affecting the 114 | // indexes which are automatically created.... 115 | uint16_t wGenNdx; // Index to list of generators 116 | uint16_t wModNdx; // Index to list of modulators 117 | SF2 *sf2; 118 | public: 119 | // Automatically assign indexes 120 | sfBag(SF2 *sf2, bool preset) : 121 | sf2(sf2) 122 | { 123 | if (preset) 124 | { 125 | wGenNdx = sf2->get_pgen_size(); 126 | wModNdx = sf2->get_pmod_size(); 127 | } 128 | else 129 | { 130 | wGenNdx = sf2->get_igen_size(); 131 | wModNdx = sf2->get_imod_size(); 132 | } 133 | } 134 | 135 | void write() 136 | { 137 | fwrite(&wGenNdx, 2, 2, sf2->out); 138 | } 139 | }; 140 | 141 | // Modulator class (mostly unused) 142 | class sfModList 143 | { 144 | SFModulator sfModSrcOper; // Modulator source 145 | SFGenerator sfModDestOper; // Modulator destination 146 | uint16_t modAmount; // Modulator value 147 | SFModulator sfModAmtSrcOper; // Modulator source ?? 148 | SFTransform sfModTransOper; // Transformation curvative 149 | SF2 *sf2; 150 | public: 151 | sfModList(SF2 *sf2) : 152 | sfModSrcOper(SFModulator::_null), 153 | sfModDestOper(SFGenerator::_null), 154 | modAmount(0), 155 | sfModAmtSrcOper(SFModulator::_null), 156 | sfModTransOper(SFTransform::_null), 157 | sf2(sf2) 158 | {} 159 | 160 | void write() 161 | { 162 | fwrite(&sfModSrcOper, 2, 5, sf2->out); 163 | } 164 | }; 165 | 166 | // Generator class 167 | // This is extremely important 168 | class sfGenList 169 | { 170 | SFGenerator sfGenOper; 171 | genAmountType genAmount; 172 | SF2 *sf2; 173 | public: 174 | sfGenList(SF2 *sf2) : 175 | sfGenOper(SFGenerator::_null), sf2(sf2) 176 | { 177 | genAmount.shAmount = 0; 178 | } 179 | // Straightforward constructor 180 | sfGenList(SF2 *sf2, SFGenerator operation, genAmountType amount) : 181 | sfGenOper(operation), genAmount(amount), sf2(sf2) 182 | {} 183 | 184 | void write() 185 | { 186 | fwrite(&sfGenOper, 2, 2, sf2->out); 187 | } 188 | }; 189 | 190 | // Instrument zone class 191 | class sfInst 192 | { 193 | char achInstName[20]; 194 | uint16_t wInstBagNdx; 195 | SF2 *sf2; 196 | public: 197 | // Constructor that automatically points at the end of the (current) preset bag 198 | sfInst(SF2 *sf2, const char *name) : sf2(sf2) 199 | { 200 | strncpy(achInstName, name, 20); 201 | wInstBagNdx = sf2->get_ibag_size(); 202 | } 203 | 204 | void write() 205 | { 206 | fwrite(&achInstName, 1, 22, sf2->out); 207 | } 208 | }; 209 | 210 | class sfSample 211 | { 212 | char achSampleName[20]; 213 | uint32_t dwStart; 214 | uint32_t dwEnd; 215 | uint32_t dwStartloop; 216 | uint32_t dwEndloop; 217 | uint32_t dwSampleRate; 218 | int8_t byOriginalPitch; 219 | int8_t chPitchCorrection; 220 | uint16_t wSampleLink; 221 | SFSampleLink sfSampleType; 222 | SF2 *sf2; 223 | public: 224 | sfSample(SF2 *sf2, const char *name, uint32_t start, uint32_t end, uint32_t start_loop, uint32_t end_loop, uint32_t sample_rate, int8_t original_pitch, int8_t pitch_correction) : 225 | dwStart(start), 226 | dwEnd(end), 227 | dwStartloop(start_loop), 228 | dwEndloop(end_loop), 229 | dwSampleRate(sample_rate), 230 | byOriginalPitch(original_pitch), 231 | chPitchCorrection(pitch_correction), 232 | wSampleLink(0), 233 | sfSampleType(SFSampleLink::monoSample), 234 | sf2(sf2) 235 | { 236 | strncpy(achSampleName, name, 20); 237 | } 238 | 239 | void write() 240 | { 241 | fwrite(&achSampleName, 1, 46, sf2->out); 242 | } 243 | }; 244 | 245 | /* sub-chunk classes 246 | * 247 | * Those are chunks withing a chunk 248 | */ 249 | 250 | // Version sub-chunk 251 | class IFILSubChunk : public SF2Chunks 252 | { 253 | // Output format is SoundFont v2.1 254 | const uint16_t wMajor; 255 | const uint16_t wMinor; 256 | public: 257 | IFILSubChunk(SF2 *sf2) : 258 | SF2Chunks(sf2, "ifil", 4), 259 | wMajor(2), 260 | wMinor(1) 261 | {} 262 | 263 | void write() 264 | { // Write major and minor revision format 265 | SF2Chunks::write(); 266 | fwrite(&wMajor, 2, 2, sf2->out); 267 | } 268 | }; 269 | 270 | // Class for the various header chunks that just contain a string 271 | class HeaderSubChunk : public SF2Chunks 272 | { 273 | const char *field; 274 | public: 275 | HeaderSubChunk(SF2 *sf2, const char *subchunk_type, const char *s) : 276 | SF2Chunks(sf2, subchunk_type, strlen(s)+1), field(s) 277 | {} // The string is null terminated -> it takes one more byte 278 | 279 | void write() 280 | { 281 | SF2Chunks::write(); 282 | fwrite(field, 1, size, sf2->out); // Write the string followed by a null byte 283 | } 284 | }; 285 | 286 | // Class for the samples sub chunk 287 | class SMPLSubChunk : public SF2Chunks 288 | { 289 | // To prevent the program from using a lot of memory by caching all 290 | // samples before writing them (which is not useful) 291 | // I instead store a list of pointers to sample data, and the data 292 | // is directly read from the original file when the sample should 293 | // be written to output 294 | 295 | // I made this function as generic as possible, so any sample can be loaded 296 | // from any file, in various formats. 297 | 298 | std::vector file_list; // Files from which the samples must be read 299 | std::vector pointer_list; // address within files where the samples must be read 300 | std::vector size_list; // Size of the data sample 301 | std::vector loop_flag_list; // Loop flag for samples (required as we need to copy data after the loop) 302 | std::vector loop_pos_list; // Loop start data (irrelevent if loop flag is clear - add dummy data) 303 | std::vector sample_type_list; // Type of sample (unsigned / signed, 8/16 bits etc...) 304 | 305 | public: 306 | SMPLSubChunk(SF2 *sf2) : 307 | SF2Chunks(sf2, "smpl") 308 | {} 309 | 310 | // Add a sample to the package 311 | // Returns directory index of the start of the sample 312 | uint32_t add_sample(FILE *file, SampleType type, uint32_t pointer, uint32_t size, bool loop_flag, uint32_t loop_pos) 313 | { 314 | file_list.push_back(file); 315 | pointer_list.push_back(pointer); 316 | size_list.push_back(size); 317 | loop_flag_list.push_back(loop_flag); 318 | loop_pos_list.push_back(loop_pos); 319 | sample_type_list.push_back(type); 320 | 321 | uint32_t dir_offset = SF2Chunks::size >> 1; 322 | // 2 bytes per sample 323 | // Compute size including the 8 samples after loop point 324 | // and 46 dummy samples 325 | if (loop_flag) 326 | SF2Chunks::size += (size + 8 + 46) * 2; 327 | else 328 | SF2Chunks::size += (size + 46) * 2; 329 | 330 | return dir_offset; 331 | } 332 | 333 | // Write all samples to output in little Indian format 334 | void write() 335 | { 336 | SF2Chunks::write(); 337 | 338 | for(uint32_t i=0; i>4]; 393 | 394 | for(int32_t k=num_of_repts; k!=0; k--, l++) 395 | outbuf[l] = conv_tbl[data[j]&0xf]; 396 | } 397 | } break; 398 | 399 | case BDPCM: 400 | { 401 | static const int8_t delta_lut[] = {0, 1, 4, 9, 16, 25, 36, 49, -64, -49, -36, -25, -16, -9, -4, -1}; 402 | 403 | /* 404 | * A block consists of an initial signed 8 bit PCM byte 405 | * followed by 63 nibbles stored in 32 bytes. 406 | * The first of these bytes has a zero padded (unused) high nibble. 407 | * This makes up of a total block size of 65 (0x21) bytes each. 408 | * 409 | * Decoding works like this: 410 | * The initial byte can be directly read without decoding. Then each 411 | * next sample can be decoded by putting the nibble into the delta-lookup-table 412 | * and adding that value to the previously calculated sample 413 | * until the end of the block is reached. 414 | */ 415 | 416 | uint32_t nblocks = size_list[i] / 64; // 64 samples per block 417 | 418 | char (*data)[33] = new char[nblocks][33]; 419 | fread(data, 33, nblocks, file_list[i]); 420 | 421 | for(uint32_t block=0; block < nblocks; ++block) 422 | { 423 | int8_t sample = data[block][0]; 424 | outbuf[64*block] = sample << 8; 425 | sample += delta_lut[data[block][1] & 0xf]; 426 | outbuf[64*block+1] = sample << 8; 427 | for (uint32_t j = 1; j < 32; ++j) 428 | { 429 | uint8_t d = data[block][j+1]; 430 | sample += delta_lut[d >> 4]; 431 | outbuf[64*block+2*j] = sample << 8; 432 | sample += delta_lut[d & 0xf]; 433 | outbuf[64*block+2*j+1]= sample << 8; 434 | } 435 | } 436 | memset(outbuf+64*nblocks, 0, size_list[i]-64*nblocks); // Remaining samples are always 0 437 | 438 | delete[] data; 439 | } break; 440 | } 441 | 442 | // Write buffer 443 | fwrite(outbuf, 2, size_list[i], sf2->out); 444 | 445 | // If loop enabled, write 8 samples after loop point 446 | // (required by the dumb SF2 standard) 447 | if (loop_flag_list[i]) 448 | fwrite(outbuf + loop_pos_list[i], 2, 8, sf2->out); 449 | 450 | // Write 46 dummy zeroed samples at the end 451 | // which is also required by the very dumb SF2 standard 452 | for(int32_t j = 0; j < 2*46; j++) 453 | putc(0x00, sf2->out); 454 | 455 | delete[] outbuf; 456 | } 457 | } 458 | }; 459 | 460 | // Preset header list sub-chunk 461 | class PHDRSubChunk : public SF2Chunks 462 | { 463 | std::vector preset_list; 464 | public: 465 | PHDRSubChunk(SF2 *sf2) : // Init size and name 466 | SF2Chunks(sf2, "phdr") 467 | {} 468 | 469 | // Add an existing preset header to the list 470 | void add_preset(const sfPresetHeader& preset) 471 | { 472 | preset_list.push_back(preset); 473 | size += 38; // Each entry is exactly 38 bytes long 474 | } 475 | 476 | void write() 477 | { 478 | SF2Chunks::write(); 479 | 480 | // Call the write function for all elements of the list 481 | for(uint32_t i=0; i instrument_list; 490 | public: 491 | INSTSubChunk (SF2 *sf2) : 492 | SF2Chunks(sf2, "inst") 493 | {} 494 | 495 | // Add an existing instrument 496 | void add_instrument(const sfInst& instrument) 497 | { 498 | instrument_list.push_back(instrument); 499 | size += 22; 500 | } 501 | 502 | void write() 503 | { 504 | SF2Chunks::write(); 505 | 506 | for(uint32_t i=0; i bag_list; 515 | friend uint16_t SF2::get_ibag_size(); 516 | friend uint16_t SF2::get_pbag_size(); 517 | 518 | public: 519 | BAGSubChunk (SF2 *sf2, bool preset) : // Init size and name 520 | SF2Chunks(sf2, preset ? "pbag" : "ibag") 521 | {} 522 | 523 | // Add an existing bag to the list 524 | void add_bag(const sfBag& bag) 525 | { 526 | bag_list.push_back(bag); 527 | size += 4; // Each entry is exactly 4 bytes long 528 | } 529 | 530 | void write() 531 | { 532 | SF2Chunks::write(); 533 | 534 | // Call the write function for all elements of the list 535 | for(uint32_t i=0; i modulator_list; 544 | friend uint16_t SF2::get_imod_size(); 545 | friend uint16_t SF2::get_pmod_size(); 546 | 547 | public: 548 | MODSubChunk (SF2 *sf2, bool preset) : 549 | SF2Chunks(sf2, preset ? "pmod" : "imod") 550 | { 551 | // if (preset) 552 | // name = "pmod"; 553 | // else 554 | // name = "imod"; 555 | } 556 | 557 | // Add an existing modulator to the list 558 | void add_modulator(const sfModList& modulator) 559 | { 560 | modulator_list.push_back(modulator); 561 | size += 10; 562 | } 563 | 564 | void write() 565 | { 566 | SF2Chunks::write(); 567 | 568 | for(uint32_t i=0; i generator_list; 577 | friend uint16_t SF2::get_igen_size(); 578 | friend uint16_t SF2::get_pgen_size(); 579 | 580 | public: 581 | GENSubChunk (SF2 *sf2, bool preset) : 582 | SF2Chunks(sf2, preset ? "pgen" : "igen") 583 | { 584 | // if (preset) 585 | // name = "pgen"; 586 | // else 587 | // name = "igen"; 588 | } 589 | 590 | // Add an existing generator to the list 591 | void add_generator(const sfGenList& generator) 592 | { 593 | generator_list.push_back(generator); 594 | size += 4; 595 | } 596 | 597 | void write() 598 | { 599 | SF2Chunks::write(); 600 | 601 | for(uint32_t i=0; i sample_list; 610 | public: 611 | SHDRSubChunk(SF2 *sf2) : 612 | SF2Chunks(sf2, "shdr") 613 | {} 614 | 615 | // Add an existing header to the list 616 | void add_sample(const sfSample& sample) 617 | { 618 | sample_list.push_back(sample); 619 | size += 46; 620 | } 621 | 622 | void write() 623 | { 624 | SF2Chunks::write(); 625 | 626 | for(uint32_t i=0; iout); // Chunk header 672 | 673 | ifil_subchunk.write(); // Write all 5 sub-chunk elements 674 | isng_subchunk.write(); 675 | inam_subchunk.write(); 676 | ieng_subchunk.write(); 677 | icop_subchunk.write(); 678 | } 679 | }; 680 | 681 | // Sample data list chunk, contains samples 682 | class SdtaListChunk : public SF2Chunks 683 | { 684 | SMPLSubChunk smpl_subchunk; 685 | 686 | friend void SF2::add_new_sample(FILE *file, SampleType type, const char *name, uint32_t pointer, uint32_t size, bool loop_flag, 687 | uint32_t loop_pos, uint32_t original_pitch, uint32_t pitch_correction, uint32_t sample_rate); 688 | public: 689 | SdtaListChunk (SF2 *sf2) : 690 | SF2Chunks(sf2, "LIST"), 691 | smpl_subchunk(sf2) 692 | {} 693 | 694 | // Compute size of sample-data-list chunk 695 | uint32_t calcSize() 696 | { 697 | size = 4; 698 | size += smpl_subchunk.size + 8; 699 | return size; 700 | } 701 | 702 | void write() 703 | { 704 | SF2Chunks::write(); 705 | fwrite("sdta", 1, 4, sf2->out); 706 | 707 | smpl_subchunk.write(); 708 | } 709 | }; 710 | 711 | // Hydra chunk, contains data for instruments, presets and samples header 712 | class HydraChunk : public SF2Chunks 713 | { 714 | // Sub chunks in PTDA chunk 715 | PHDRSubChunk phdr_subchunk; 716 | BAGSubChunk pbag_subchunk; 717 | MODSubChunk pmod_subchunk; 718 | GENSubChunk pgen_subchunk; 719 | INSTSubChunk inst_subchunk; 720 | BAGSubChunk ibag_subchunk; 721 | MODSubChunk imod_subchunk; 722 | GENSubChunk igen_subchunk; 723 | SHDRSubChunk shdr_subchunk; 724 | 725 | friend void SF2::add_new_preset(const char *name, int32_t patch, int32_t bank); 726 | friend void SF2::add_new_instrument(const char *name); 727 | friend void SF2::add_new_inst_bag(); 728 | friend void SF2::add_new_preset_bag(); 729 | friend void SF2::add_new_preset_modulator(); 730 | friend void SF2::add_new_preset_generator(); 731 | friend void SF2::add_new_preset_generator(SFGenerator operation, uint16_t value); 732 | friend void SF2::add_new_preset_generator(SFGenerator operation, uint8_t lo, uint8_t hi); 733 | friend void SF2::add_new_inst_modulator(); 734 | friend void SF2::add_new_inst_generator(); 735 | friend void SF2::add_new_inst_generator(SFGenerator operation, uint16_t value); 736 | friend void SF2::add_new_inst_generator(SFGenerator operation, uint8_t lo, uint8_t hi); 737 | friend void SF2::add_new_sample_header(const char *name, int32_t start, int32_t end, int32_t start_loop, int32_t end_loop, int32_t sample_rate, int32_t original_pitch, int32_t pitch_correction); 738 | 739 | friend uint16_t SF2::get_ibag_size(); 740 | friend uint16_t SF2::get_imod_size(); 741 | friend uint16_t SF2::get_igen_size(); 742 | friend uint16_t SF2::get_pbag_size(); 743 | friend uint16_t SF2::get_pmod_size(); 744 | friend uint16_t SF2::get_pgen_size(); 745 | 746 | public: 747 | // Constructor 748 | HydraChunk(SF2 *sf2) : 749 | SF2Chunks(sf2, "LIST"), 750 | phdr_subchunk(sf2), 751 | pbag_subchunk(sf2, true), 752 | pmod_subchunk(sf2, true), 753 | pgen_subchunk(sf2, true), 754 | inst_subchunk(sf2), 755 | ibag_subchunk(sf2, false), 756 | imod_subchunk(sf2, false), 757 | igen_subchunk(sf2, false), 758 | shdr_subchunk(sf2) 759 | {} 760 | 761 | uint32_t calcSize() 762 | { 763 | size = 4; 764 | // Compute size of the chunk (because we don't know it in advance) 765 | size += phdr_subchunk.size + 8; 766 | size += pbag_subchunk.size + 8; 767 | size += pmod_subchunk.size + 8; 768 | size += pgen_subchunk.size + 8; 769 | size += inst_subchunk.size + 8; 770 | size += ibag_subchunk.size + 8; 771 | size += imod_subchunk.size + 8; 772 | size += igen_subchunk.size + 8; 773 | size += shdr_subchunk.size + 8; 774 | 775 | return size; 776 | } 777 | 778 | void write() 779 | { 780 | // Write chunk name and size 781 | SF2Chunks::write(); 782 | fwrite("pdta", 1, 4, sf2->out); 783 | 784 | // Write all sub-chunks 785 | phdr_subchunk.write(); 786 | pbag_subchunk.write(); 787 | pmod_subchunk.write(); 788 | pgen_subchunk.write(); 789 | inst_subchunk.write(); 790 | ibag_subchunk.write(); 791 | imod_subchunk.write(); 792 | igen_subchunk.write(); 793 | shdr_subchunk.write(); 794 | } 795 | }; 796 | 797 | #endif 798 | --------------------------------------------------------------------------------