1 /*
2 * SoundFX Macs Opera CMF Player -- Copyright (c) 2017 Sebastian Kienzl <seb@knzl.de>
3 *
4 * Part of Adplug - Replayer for many OPL2/OPL3 audio file formats.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /*
22 * Most songs in this format use rhythm mode.
23 * The emus don't support rhythm mode too well, "nuked" seems good.
24 * I didn't verify with real hardware, so I don't know how it's supposed to sound.
25 */
26
27 #include <string.h>
28 #include <stddef.h>
29 #include "cmfmcsop.h"
30 #include "debug.h"
31
factory(Copl * newopl)32 CPlayer* CcmfmacsoperaPlayer::factory(Copl* newopl)
33 {
34 return new CcmfmacsoperaPlayer(newopl);
35 }
36
37 static const int16_t fNumbers[] = { 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, 0x1e5, 0x202, 0x221, 0x242, 0x264, 0x288 };
38
39 // Register offset for slot, e.g. 0x20 + slotRegisterOffsets[slotNr]
40 static const int8_t slotRegisterOffsets[] = {
41 0, 1, 2, 3, 4, 5,
42 8, 9, 10, 11, 12, 13,
43 16, 17, 18, 19, 20, 21
44 };
45
46 /*
47 Channel Slot
48 Op1 Op2
49 ===================
50 0 0 3
51 1 1 4
52 2 2 5
53 3 6 9
54 4 7 10
55 5 8 11
56 6 12 15
57 7 13 16
58 8 14 17
59
60 Rhythm Mode
61 -------------------
62 BD 12 15 <- like Channel 6 in "melody mode"
63 SD 16
64 TOM 14
65 TC 17
66 HH 13
67 */
68
69 // Slots for Op1/Op2 for channel
70 static const struct {
71 int8_t slotOp1;
72 int8_t slotOp2;
73 }
74 channelSlots[] = { {0, 3}, {1, 4}, {2, 5}, {6, 9}, {7, 10}, {8, 11}, {12, 15}, {13, 16}, {14, 17}};
75
76 /*
77 Note that
78 op_table[chan] == slotRegisterOffets[channelSlots[chan].slotOp1]
79 op_table[chan] + 3 == slotRegisterOffets[channelSlots[chan].slotOp2]
80 However, the slot-numbers are needed for rhythm mode, thus the extra tables.
81 */
82
83 // Rhythm mode mapping: Which slots for which channel (note that the BD uses 12/15 like in melody mode)
84 static const int8_t channelSlotsRhythm[] = { -1, -1, -1, -1, -1, -1, 12, 16, 14, 17, 13};
85
86 enum {
87 CHANNEL_RHY_BD = 6,
88 CHANNEL_RHY_SN = 7,
89 CHANNEL_RHY_TOM = 8,
90 CHANNEL_RHY_TC = 9,
91 CHANNEL_RHY_HH = 10
92 };
93
CcmfmacsoperaPlayer(Copl * newopl)94 CcmfmacsoperaPlayer::CcmfmacsoperaPlayer(Copl* newopl): CPlayer(newopl)
95 {
96 }
97
gettype()98 std::string CcmfmacsoperaPlayer::gettype() { return std::string("SoundFX Macs Opera CMF"); }
99
100 // helper class to close a binistream when leaving context
101 class binistream_closer {
102 public:
binistream_closer(const CFileProvider & fp,binistream * strm)103 binistream_closer(const CFileProvider& fp, binistream* strm): fp(fp), strm(strm) {}
~binistream_closer()104 ~binistream_closer() { if(strm) fp.close(strm); }
105 const CFileProvider& fp;
106 binistream* strm;
107 };
108
load(const std::string & filename,const CFileProvider & fp)109 bool CcmfmacsoperaPlayer::load(const std::string& filename, const CFileProvider& fp)
110 {
111 if (!fp.extension(filename, ".cmf"))
112 return false;
113
114 binistream* f = fp.open(filename);
115 if (!f)
116 return false;
117
118 binistream_closer _closer(fp, f);
119
120 std::string signature = f->readString();
121 if (signature != "A.H.")
122 return false;
123
124 // the pattern order is 99 shorts
125 nrOfOrders = -1;
126 for (int i = 0; i < 99; ++i) {
127 patternOrder[i] = f->readInt(2);
128 // count number of orders, only assign once
129 if (patternOrder[i] == 99 && nrOfOrders < 0)
130 nrOfOrders = i;
131 }
132
133 if (nrOfOrders == -1) // no end of song marker found, assume 99
134 nrOfOrders = 99;
135
136 nrOfPatterns = f->readInt(2);
137
138 int speed = f->readInt(2);
139 if (speed < 1 || speed > 3)
140 return false;
141
142 // 1 => 18 rows/s, 2 => 9 rows/s, 3 => 4.5 rows/s
143 // (With 18.2 the speed matches the speed with DOSBox, otherwise it's ~2 BPM too slow)
144 speedRowsPerSec = 18.2f / (1 << (speed - 1));
145
146 rhythmMode = f->readInt(2) == 1;
147 int nrOfInstruments = f->readInt(2);
148
149 if (!loadInstruments(f, nrOfInstruments))
150 return false;
151
152 if (!loadPatterns(f))
153 return false;
154
155 rewind(0);
156
157 return true;
158 }
159
loadInstruments(binistream * f,int nrOfInstruments)160 bool CcmfmacsoperaPlayer::loadInstruments(binistream* f, int nrOfInstruments)
161 {
162 if (nrOfInstruments > 0xff)
163 return false;
164
165 instruments.resize(nrOfInstruments);
166
167 static const intptr_t loadOffsets[] = {
168 offsetof(Instrument, op[0].ksl),
169 offsetof(Instrument, op[0].multiple),
170 offsetof(Instrument, feedback),
171 offsetof(Instrument, op[0].attackRate),
172 offsetof(Instrument, op[0].sustainLevel),
173 offsetof(Instrument, op[0].egType),
174 offsetof(Instrument, op[0].decayRate),
175 offsetof(Instrument, op[0].releaseRate),
176 offsetof(Instrument, op[0].totalLevel),
177 offsetof(Instrument, op[0].ampMod),
178 offsetof(Instrument, op[0].vib),
179 offsetof(Instrument, op[0].ksr),
180 offsetof(Instrument, connection),
181
182 offsetof(Instrument, op[1].ksl),
183 offsetof(Instrument, op[1].multiple),
184 -1,
185 offsetof(Instrument, op[1].attackRate),
186 offsetof(Instrument, op[1].sustainLevel),
187 offsetof(Instrument, op[1].egType),
188 offsetof(Instrument, op[1].decayRate),
189 offsetof(Instrument, op[1].releaseRate),
190 offsetof(Instrument, op[1].totalLevel),
191 offsetof(Instrument, op[1].ampMod),
192 offsetof(Instrument, op[1].vib),
193 offsetof(Instrument, op[1].ksr),
194 -1,
195 offsetof(Instrument, op[0].waveSelect),
196 offsetof(Instrument, op[1].waveSelect)
197 };
198
199 for (int i = 0; i < nrOfInstruments; ++i) {
200 for (int j = 0; j < sizeof(loadOffsets)/sizeof(loadOffsets[0]); ++j) {
201 int v = f->readInt(2);
202 if (loadOffsets[j] >= 0 ) {
203 *(int16_t*)((char*)&instruments[i] + loadOffsets[j]) = v;
204 }
205 }
206
207 f->readString(instruments[i].name, 13);
208 instruments[i].name[13] = 0;
209 }
210
211 return !f->ateof();
212 }
213
loadPatterns(binistream * f)214 bool CcmfmacsoperaPlayer::loadPatterns(binistream* f)
215 {
216 if (nrOfPatterns > 0xff)
217 return false;
218
219 patterns.resize(nrOfPatterns);
220
221 for (int i = 0; i < nrOfPatterns; ++i) {
222 while (!f->eof()) {
223 // 6 bytes per event, if the 1st byte is 0xff, end of pattern
224 NoteEvent n;
225
226 n.row = f->readInt(1);
227 if (n.row == 0xff)
228 break;
229
230 uint8_t* d = &n.col;
231 for (int j = 0; j < 5; ++j)
232 *d++ = f->readInt(1);
233
234 n.instrument--;
235 patterns[i].push_back(n);
236 }
237 }
238
239 return true;
240 }
241
isValidChannel(int channelNr) const242 bool CcmfmacsoperaPlayer::isValidChannel(int channelNr) const
243 {
244 return channelNr >= 0 && ((rhythmMode && channelNr < 11) || (!rhythmMode && channelNr < 9));
245 }
246
isRhythmChannel(int channelNr) const247 bool CcmfmacsoperaPlayer::isRhythmChannel(int channelNr) const
248 {
249 return rhythmMode && channelNr >= 6;
250 }
251
getSlotRegister(int base,int slotNr)252 static inline int getSlotRegister(int base, int slotNr)
253 {
254 return base + slotRegisterOffsets[slotNr];
255 }
256
setSlot(int slotNr,const SlotSettings & settings)257 void CcmfmacsoperaPlayer::setSlot(int slotNr, const SlotSettings& settings)
258 {
259 opl->write(getSlotRegister(0x20, slotNr), (settings.multiple & 0xf) | ((settings.ksr & 1) << 4) | ((settings.egType & 1) << 5) | ((settings.vib & 1) << 6) | ((settings.ampMod & 1) << 7));
260 opl->write(getSlotRegister(0x60, slotNr), ((settings.attackRate & 0xf) << 4) | (settings.decayRate & 0xf));
261 opl->write(getSlotRegister(0x80, slotNr), ((settings.sustainLevel & 0xf) << 4) | (settings.releaseRate & 0xf));
262 opl->write(getSlotRegister(0xe0, slotNr), settings.waveSelect & 3);
263 }
264
setInstrument(int channelNr,const Instrument & inst)265 bool CcmfmacsoperaPlayer::setInstrument(int channelNr, const Instrument& inst)
266 {
267 if (!isValidChannel(channelNr))
268 return false;
269
270 if (channelCurrentInstrument[channelNr] != &inst) {
271 if (!isRhythmChannel(channelNr) || channelNr == CHANNEL_RHY_BD) {
272 // normal 2-op channel (or BD in rhythm mode, which also has two slots)
273 opl->write(0xc0 + channelNr, ((inst.feedback & 7) << 1) | (1 - (inst.connection & 1)));
274 setSlot(channelSlots[channelNr].slotOp1, inst.op[0]);
275 setSlot(channelSlots[channelNr].slotOp2, inst.op[1]);
276 }
277 else {
278 // rhythm channel
279 setSlot(channelSlotsRhythm[channelNr], inst.op[0]);
280 }
281
282 channelCurrentInstrument[channelNr] = &inst;
283 }
284
285 return true;
286 }
287
calculateAttenuation(int attenuation,int volumeColumn)288 static int calculateAttenuation(int attenuation, int volumeColumn)
289 {
290 if (attenuation < 0 ) attenuation = 0;
291 if (attenuation > 63 ) attenuation = 63;
292 if (volumeColumn < 0 ) volumeColumn = 0;
293 if (volumeColumn > 127) volumeColumn = 127;
294
295 // Instruments have a configured attenuation.
296 // The "rest" of the attenuation (max. 63, 6 bits in "total level") is scaled by volumeColumn.
297 // (This seems right for most values I've tested. But with att=8/vol=115, we get 13 here while SOUNDFX gets 14.
298 return attenuation + ((63 - attenuation) * (127 - volumeColumn)) / 127;
299 }
300
keyOn(int channelNr)301 void CcmfmacsoperaPlayer::keyOn(int channelNr)
302 {
303 if (!isValidChannel(channelNr))
304 return;
305
306 if (!isRhythmChannel(channelNr)) {
307 current0xBx[channelNr] |= (1 << 5);
308 opl->write(0xb0 + channelNr, current0xBx[channelNr]);
309 }
310 else {
311 // Channel 6 (BD) -> D4 .. Channel 10 (HH) -> D0
312 current0xBD |= 1 << (10 - channelNr);
313 opl->write(0xbd, current0xBD);
314 }
315 }
316
keyOff(int channelNr)317 void CcmfmacsoperaPlayer::keyOff(int channelNr)
318 {
319 if (!isValidChannel(channelNr))
320 return;
321
322 if (!isRhythmChannel(channelNr)) {
323 current0xBx[channelNr] &= ~(1 << 5);
324 opl->write(0xb0 + channelNr, current0xBx[channelNr]);
325 }
326 else {
327 // Channel 6 (BD) -> D4 .. Channel 10 (HH) -> D0
328 current0xBD &= ~(1 << (10 - channelNr));
329 opl->write(0xbd, current0xBD);
330 }
331 }
332
setAxBx(int channelNr,int Ax,int Bx)333 void CcmfmacsoperaPlayer::setAxBx(int channelNr, int Ax, int Bx)
334 {
335 if (channelNr < 0 || channelNr >= 8)
336 return;
337
338 opl->write(0xa0 + channelNr, Ax);
339 current0xBx[channelNr] = Bx;
340 opl->write(0xb0 + channelNr, current0xBx[channelNr]);
341 }
342
setNote(int channelNr,int note)343 bool CcmfmacsoperaPlayer::setNote(int channelNr, int note)
344 {
345 if (!isValidChannel(channelNr))
346 return false;
347
348 if (note < 23 || note >= 120)
349 return false;
350
351 int fNumber = fNumbers[note % 12];
352
353 int Ax = fNumber & 0xff;
354 int Bx = ((fNumber >> 8) & 3) | ((note / 12 - 2) << 2);
355
356 if (!isRhythmChannel(channelNr)) {
357 setAxBx(channelNr, Ax, Bx);
358 }
359 else {
360 // not entirely right, but this is what the original player seems to do
361 if (channelNr == CHANNEL_RHY_BD)
362 setAxBx(6, Ax, Bx);
363 setAxBx(7, Ax, Bx);
364 if (channelNr == CHANNEL_RHY_SN || channelNr == CHANNEL_RHY_TOM)
365 setAxBx(8, Ax, Bx);
366 }
367
368 return true;
369 }
370
setVolume(int channelNr,int vol)371 void CcmfmacsoperaPlayer::setVolume(int channelNr, int vol)
372 {
373 if (!isValidChannel(channelNr) || !channelCurrentInstrument[channelNr])
374 return;
375
376 const Instrument& inst = *channelCurrentInstrument[channelNr];
377
378 if (!isRhythmChannel(channelNr) || channelNr == CHANNEL_RHY_BD) {
379 // normal 2-op channel (or BD in rhythm mode, which also has two slots)
380
381 // Set volume on OP1: If OP1 modulates OP2 (inst.connection != 0), don't scale its volume by n.volume
382 opl->write(
383 getSlotRegister(0x40, channelSlots[channelNr].slotOp1),
384 ((inst.op[0].ksl & 3) << 6) |
385 (inst.connection == 0 ? calculateAttenuation(inst.op[0].totalLevel, vol) : (inst.op[0].totalLevel & 0x3f))
386 );
387
388 // Set volume on OP2
389 opl->write(
390 getSlotRegister(0x40, channelSlots[channelNr].slotOp2),
391 ((inst.op[1].ksl & 3) << 6) | calculateAttenuation(inst.op[1].totalLevel, vol)
392 );
393 }
394 else {
395 // rhythm channel
396 opl->write(
397 getSlotRegister(0x40, channelSlotsRhythm[channelNr]),
398 ((inst.op[1].ksl & 3) << 6) | calculateAttenuation(inst.op[0].totalLevel, vol)
399 );
400 }
401 }
402
advanceRow()403 bool CcmfmacsoperaPlayer::advanceRow()
404 {
405 for (;;) {
406 if (currentRow < 0 || ++currentRow >= 64) {
407 // next pattern
408 currentRow = 0;
409 currentPatternIndex = 0;
410
411 do {
412 currentOrderIndex++;
413
414 // check bounds
415 if (currentOrderIndex < 0 || currentOrderIndex >= sizeof(patternOrder) / sizeof(patternOrder[0]))
416 return false;
417
418 // end of song?
419 if (patternOrder[currentOrderIndex] == 99)
420 return false;
421
422 } while (patternOrder[currentOrderIndex] >= patterns.size()); // loop to skip invalid pattern references
423
424 AdPlug_LogWrite("order %d, pattern %d\n", currentOrderIndex, patternOrder[currentOrderIndex]);
425 }
426
427 // check for pattern break
428 const Pattern &p = patterns[patternOrder[currentOrderIndex]];
429 if (currentPatternIndex < p.size() && p[currentPatternIndex].row == currentRow && p[currentPatternIndex].note == 1) {
430 currentRow = -1;
431 }
432 else // no pattern break, done!
433 break;
434 }
435
436 return true;
437 }
438
processNoteEvent(const CcmfmacsoperaPlayer::NoteEvent & n)439 void CcmfmacsoperaPlayer::processNoteEvent(const CcmfmacsoperaPlayer::NoteEvent &n)
440 {
441 int channelNr = n.col;
442
443 if (!isValidChannel(channelNr))
444 return;
445
446 keyOff(channelNr);
447
448 if (n.note == 4) // key off: done here
449 return;
450
451 if (n.instrument >= 0 && n.instrument < instruments.size())
452 setInstrument(channelNr, instruments[n.instrument]);
453
454 setVolume(channelNr, n.volume);
455
456 if (setNote(channelNr, n.note))
457 keyOn(channelNr);
458 }
459
update()460 bool CcmfmacsoperaPlayer::update()
461 {
462 AdPlug_LogWrite( "%2d: ", currentRow);
463
464 const Pattern& p = patterns[patternOrder[currentOrderIndex]];
465
466 int currentCol = 0;
467 for (; currentPatternIndex < p.size() && p[currentPatternIndex].row == currentRow; ++currentPatternIndex) {
468 const NoteEvent& n = p[currentPatternIndex];
469
470 for (; currentCol < n.col; ++currentCol)
471 AdPlug_LogWrite(" ");
472 AdPlug_LogWrite( "%2d %2d %2x %2d ", n.note, n.instrument, n.volume, n.pitch);
473
474 currentCol++;
475 processNoteEvent(n);
476 }
477 AdPlug_LogWrite("\n");
478
479 bool stillPlaying = advanceRow();
480
481 if (!stillPlaying) {
482 resetPlayer();
483 // Once the song is done, keep on returning false.
484 // TODO: returning "false" once should suffice, but it doesn't (at least with adplay-unix&SDL)
485 // "songDone" is only reset in rewind().
486 songDone = true;
487 }
488
489 return !songDone;
490 }
491
resetPlayer()492 void CcmfmacsoperaPlayer::resetPlayer()
493 {
494 currentRow = -1;
495 currentOrderIndex = -1;
496 advanceRow();
497 }
498
rewind(int subsong)499 void CcmfmacsoperaPlayer::rewind(int subsong)
500 {
501 opl->init();
502 opl->write(1, 1 << 5); // enable wave select
503
504 // 0xbd:5 (RHY) enables "rhythm mode"
505 current0xBD = rhythmMode ? (1 << 5) : 0;
506 opl->write(0xbd, current0xBD);
507
508 memset(current0xBx, 0, sizeof(current0xBx));
509 memset(channelCurrentInstrument, 0, sizeof(channelCurrentInstrument));
510
511 // This isn't right for all channels.
512 // No songs I've seen rely on the undocumented "default instrument", so ...
513 static const Instrument defaultInstrument = {
514 {{0, 1, 15, 5, 0, 1, 0, 0, 0, 0, 0, 0},
515 {0, 1, 15, 7, 0, 2, 0, 0, 0, 0, 1, 0}},
516 3, 0, {'D', 'e', 'f', 'a', 'u', 'l', 't', 0, 0, 0, 0, 0, 0, 0 }
517 };
518 for (int i = 0; i < 11; ++i)
519 setInstrument(i, defaultInstrument);
520
521 // see comment in update() why this is needed
522 songDone = false;
523
524 resetPlayer();
525 }
526