1 /*
2  * libOPNMIDI is a free Software MIDI synthesizer library with OPN2 (YM2612) emulation
3  *
4  * MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
5  * OPNMIDI Library and YM2612 support:   Copyright (c) 2017-2020 Vitaly Novichkov <admin@wohlnet.ru>
6  *
7  * Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
8  * http://iki.fi/bisqwit/source/adlmidi.html
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "opnmidi_midiplay.hpp"
25 #include "opnmidi_opn2.hpp"
26 #include "opnmidi_private.hpp"
27 #include "opnmidi_cvt.hpp"
28 #include "file_reader.hpp"
29 #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
30 #include "midi_sequencer.hpp"
31 #endif
32 #include "wopn/wopn_file.h"
33 
LoadBank(const std::string & filename)34 bool OPNMIDIplay::LoadBank(const std::string &filename)
35 {
36     FileAndMemReader file;
37     file.openFile(filename.c_str());
38     return LoadBank(file);
39 }
40 
LoadBank(const void * data,size_t size)41 bool OPNMIDIplay::LoadBank(const void *data, size_t size)
42 {
43     FileAndMemReader file;
44     file.openData(data, (size_t)size);
45     return LoadBank(file);
46 }
47 
cvt_OPNI_to_FMIns(OpnInstMeta & ins,const OPN2_Instrument & in)48 void cvt_OPNI_to_FMIns(OpnInstMeta &ins, const OPN2_Instrument &in)
49 {
50     return cvt_generic_to_FMIns(ins, in);
51 }
52 
cvt_FMIns_to_OPNI(OPN2_Instrument & ins,const OpnInstMeta & in)53 void cvt_FMIns_to_OPNI(OPN2_Instrument &ins, const OpnInstMeta &in)
54 {
55     cvt_FMIns_to_generic(ins, in);
56 }
57 
LoadBank(FileAndMemReader & fr)58 bool OPNMIDIplay::LoadBank(FileAndMemReader &fr)
59 {
60     int err = 0;
61     WOPNFile *wopn = NULL;
62     char *raw_file_data = NULL;
63     size_t  fsize;
64     if(!fr.isValid())
65     {
66         errorStringOut = "Custom bank: Invalid data stream!";
67         return false;
68     }
69 
70     // Read complete bank file into the memory
71     fsize = fr.fileSize();
72     fr.seek(0, FileAndMemReader::SET);
73     // Allocate necessary memory block
74     raw_file_data = (char*)malloc(fsize);
75     if(!raw_file_data)
76     {
77         errorStringOut = "Custom bank: Out of memory before of read!";
78         return false;
79     }
80     fr.read(raw_file_data, 1, fsize);
81 
82     // Parse bank file from the memory
83     wopn = WOPN_LoadBankFromMem((void*)raw_file_data, fsize, &err);
84     //Free the buffer no more needed
85     free(raw_file_data);
86 
87     // Check for any erros
88     if(!wopn)
89     {
90         switch(err)
91         {
92         case WOPN_ERR_BAD_MAGIC:
93             errorStringOut = "Custom bank: Invalid magic!";
94             return false;
95         case WOPN_ERR_UNEXPECTED_ENDING:
96             errorStringOut = "Custom bank: Unexpected ending!";
97             return false;
98         case WOPN_ERR_INVALID_BANKS_COUNT:
99             errorStringOut = "Custom bank: Invalid banks count!";
100             return false;
101         case WOPN_ERR_NEWER_VERSION:
102             errorStringOut = "Custom bank: Version is newer than supported by this library!";
103             return false;
104         case WOPN_ERR_OUT_OF_MEMORY:
105             errorStringOut = "Custom bank: Out of memory!";
106             return false;
107         default:
108             errorStringOut = "Custom bank: Unknown error!";
109             return false;
110         }
111     }
112 
113     Synth &synth = *m_synth;
114     synth.m_insBankSetup.volumeModel = wopn->volume_model;
115     synth.m_insBankSetup.lfoEnable = (wopn->lfo_freq & 8) != 0;
116     synth.m_insBankSetup.lfoFrequency = wopn->lfo_freq & 7;
117     synth.m_insBankSetup.chipType = wopn->chip_type;
118     m_setup.VolumeModel = OPNMIDI_VolumeModel_AUTO;
119     m_setup.lfoEnable = -1;
120     m_setup.lfoFrequency = -1;
121     m_setup.chipType = -1;
122 
123     synth.m_insBanks.clear();
124 
125     uint16_t slots_counts[2] = {wopn->banks_count_melodic, wopn->banks_count_percussion};
126     WOPNBank *slots_src_ins[2] = { wopn->banks_melodic, wopn->banks_percussive };
127 
128     for(size_t ss = 0; ss < 2; ss++)
129     {
130         for(size_t i = 0; i < slots_counts[ss]; i++)
131         {
132             size_t bankno = (slots_src_ins[ss][i].bank_midi_msb * 256) +
133                             (slots_src_ins[ss][i].bank_midi_lsb) +
134                             (ss ? size_t(Synth::PercussionTag) : 0);
135             Synth::Bank &bank = synth.m_insBanks[bankno];
136             for(int j = 0; j < 128; j++)
137             {
138                 OpnInstMeta &ins = bank.ins[j];
139                 std::memset(&ins, 0, sizeof(OpnInstMeta));
140                 WOPNInstrument &inIns = slots_src_ins[ss][i].ins[j];
141                 cvt_generic_to_FMIns(ins, inIns);
142             }
143         }
144     }
145 
146     applySetup();
147 
148     WOPN_Free(wopn);
149 
150     return true;
151 }
152 
153 #ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
154 
LoadMIDI_pre()155 bool OPNMIDIplay::LoadMIDI_pre()
156 {
157     Synth &synth = *m_synth;
158     if(synth.m_insBanks.empty())
159     {
160         errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
161         return false;
162     }
163 
164     /**** Set all properties BEFORE starting of actial file reading! ****/
165     resetMIDI();
166     applySetup();
167 
168     return true;
169 }
170 
LoadMIDI_post()171 bool OPNMIDIplay::LoadMIDI_post()
172 {
173     Synth &synth = *m_synth;
174     MidiSequencer &seq = *m_sequencer;
175     MidiSequencer::FileFormat format = seq.getFormat();
176     if(format == MidiSequencer::Format_CMF)
177     {
178         errorStringOut = "OPNMIDI doesn't supports CMF, use ADLMIDI to play this file!";
179         /* As joke, why not to try implemented the converter of patches from OPL3 into OPN2? */
180         return false;
181     }
182     else if(format == MidiSequencer::Format_RSXX)
183     {
184         synth.m_musicMode     = Synth::MODE_RSXX;
185         synth.m_volumeScale   = Synth::VOLUME_Generic;
186         synth.m_numChips = 2;
187     }
188     else if(format == MidiSequencer::Format_IMF)
189     {
190         errorStringOut = "OPNMIDI doesn't supports IMF, use ADLMIDI to play this file!";
191         /* Same as for CMF */
192         return false;
193     }
194     else if(format == MidiSequencer::Format_XMIDI)
195         synth.m_musicMode = Synth::MODE_XMIDI;
196 
197     m_setup.tick_skip_samples_delay = 0;
198     synth.reset(m_setup.emulator, m_setup.PCM_RATE, synth.chipFamily(), this); // Reset OPN2 chip
199     m_chipChannels.clear();
200     m_chipChannels.resize(synth.m_numChannels);
201     resetMIDIDefaults();
202 #ifdef OPNMIDI_MIDI2VGM
203     m_sequencerInterface->onloopStart = synth.m_loopStartHook;
204     m_sequencerInterface->onloopStart_userData = synth.m_loopStartHookData;
205     m_sequencerInterface->onloopEnd = synth.m_loopEndHook;
206     m_sequencerInterface->onloopEnd_userData = synth.m_loopEndHookData;
207     m_sequencer->setLoopHooksOnly(m_sequencerInterface->onloopStart != NULL);
208 #endif
209 
210     return true;
211 }
212 
213 
LoadMIDI(const std::string & filename)214 bool OPNMIDIplay::LoadMIDI(const std::string &filename)
215 {
216     FileAndMemReader file;
217     file.openFile(filename.c_str());
218     if(!LoadMIDI_pre())
219         return false;
220     MidiSequencer &seq = *m_sequencer;
221     if(!seq.loadMIDI(file))
222     {
223         errorStringOut = seq.getErrorString();
224         return false;
225     }
226     if(!LoadMIDI_post())
227         return false;
228     return true;
229 }
230 
LoadMIDI(const void * data,size_t size)231 bool OPNMIDIplay::LoadMIDI(const void *data, size_t size)
232 {
233     FileAndMemReader file;
234     file.openData(data, size);
235     if(!LoadMIDI_pre())
236         return false;
237     MidiSequencer &seq = *m_sequencer;
238     if(!seq.loadMIDI(file))
239     {
240         errorStringOut = seq.getErrorString();
241         return false;
242     }
243     if(!LoadMIDI_post())
244         return false;
245     return true;
246 }
247 
248 #endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
249