370 lines
12 KiB
C++
370 lines
12 KiB
C++
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include "midi_driver.h"
|
|
extern "C" {
|
|
#include "opl3.c"
|
|
}
|
|
|
|
#define NUM_CHANNELS 11 /* rhythm mode */
|
|
|
|
static opl3_chip _opl3;
|
|
|
|
static const int kMusicVolume = 63; // 44;
|
|
|
|
/* Two-operator melodic and percussion mode */
|
|
static const uint8_t _adlibOperatorIndexTable[] = { 0, 3, 1, 4, 2, 5, 6, 9, 7, 0xA, 8, 0xB, 0xC, 0xF, 0x10, 0x10, 0xE, 0xE, 0x11, 0x11, 0xD, 0xD };
|
|
|
|
static const uint8_t _adlibOperatorTable[] = { 0, 1, 2, 3, 4, 5, 8, 9, 0xA, 0xB, 0xC, 0xD, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 };
|
|
|
|
static const uint16_t _adlibFrequencyTable[] = {
|
|
0x157, 0x16C, 0x181, 0x198, 0x1B1, 0x1CB, 0x1E6, 0x203, 0x222, 0x243, 0x266, 0x28A
|
|
};
|
|
|
|
static struct {
|
|
uint8_t midi_channel; // track_num
|
|
uint8_t note;
|
|
uint8_t hw_channel;
|
|
const uint8_t *instrument;
|
|
} _adlibPlayingNoteTable[NUM_CHANNELS];
|
|
|
|
static int16_t _adlibChannelPlayingTable[NUM_CHANNELS]; // could be a bitmask
|
|
static int16_t _adlibChannelVolumeTable[NUM_CHANNELS];
|
|
static uint8_t _regBD = 0x20;
|
|
|
|
static const uint8_t *_currentInstrumentData;
|
|
|
|
static uint16_t READ_LE_UINT16(const uint8_t *p) {
|
|
return p[0] | (p[1] << 8);
|
|
}
|
|
|
|
struct MidiDriver_adlib: MidiDriver {
|
|
virtual int init() {
|
|
return 0;
|
|
}
|
|
virtual void fini() {
|
|
}
|
|
|
|
virtual void reset(int rate) {
|
|
for (int i = 0; i < NUM_CHANNELS; ++i) {
|
|
_adlibPlayingNoteTable[i].midi_channel = 0xFF;
|
|
_adlibChannelPlayingTable[i] = 0;
|
|
_adlibChannelVolumeTable[i] = 10;
|
|
}
|
|
OPL3_Reset(&_opl3, rate);
|
|
adlibReset();
|
|
}
|
|
|
|
virtual void setInstrumentData(int channel, int num, const void *data) {
|
|
// fprintf(stdout, "instrument channel:%d num:%d data:%p\n", channel, num, data);
|
|
assert(data);
|
|
_currentInstrumentData = (const uint8_t *)data;
|
|
}
|
|
|
|
virtual void noteOff(int channel, int note, int velocity) {
|
|
// fprintf(stdout, "noteOff %d channel %d\n", note, channel);
|
|
for (int i = 0; i < NUM_CHANNELS; ++i) {
|
|
if (_adlibPlayingNoteTable[i].midi_channel != channel) {
|
|
continue;
|
|
}
|
|
if (_adlibPlayingNoteTable[i].note != note) {
|
|
//fprintf(stdout, "WARNING: noteOff: channel %d is playing note %d (expected %d)\n", channel, _adlibPlayingNoteTable[i].note, note);
|
|
continue;
|
|
}
|
|
_adlibPlayingNoteTable[i].midi_channel = 0xFF;
|
|
const int hw_channel = _adlibPlayingNoteTable[i].hw_channel;
|
|
//fprintf(stdout, "free hw_channel %d\n", hw_channel);
|
|
_adlibChannelPlayingTable[hw_channel] = 0;
|
|
if (_adlibPlayingNoteTable[i].instrument != _currentInstrumentData) {
|
|
// fprintf(stdout, "WARNING: noteOff: instrument changed for channel %d hw_channel %d\n", channel, hw_channel);
|
|
}
|
|
if (_adlibPlayingNoteTable[i].instrument[0] == 0) { //_currentInstrumentData[0] == 0) {
|
|
adlibMelodicKeyOff(hw_channel, note);
|
|
} else {
|
|
adlibPercussionKeyOff(hw_channel);
|
|
}
|
|
return;
|
|
}
|
|
// fprintf(stdout, "WARNING: noteOff: channel %d note %d not found\n", channel, note);
|
|
}
|
|
virtual void noteOn(int channel, int note, int velocity) {
|
|
if (_currentInstrumentData[0] == 0) {
|
|
/* melodic channel */
|
|
int i;
|
|
for (i = 0; i < 6; ++i) {
|
|
if (_adlibChannelPlayingTable[i] == 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == 6) {
|
|
// fprintf(stdout, "No free melodic channel found (A) 6 voices busy, track %d note %d\n", channel, note);
|
|
return;
|
|
}
|
|
const int hw_channel = i;
|
|
_adlibChannelPlayingTable[hw_channel] = 0xFF;
|
|
for (i = 0; i < NUM_CHANNELS; ++i) {
|
|
if (_adlibPlayingNoteTable[i].midi_channel == 0xFF) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == NUM_CHANNELS) {
|
|
_adlibChannelPlayingTable[hw_channel] = 0;
|
|
// fprintf(stdout, "No free channel found (B)\n");
|
|
return;
|
|
}
|
|
_adlibPlayingNoteTable[i].midi_channel = channel;
|
|
_adlibPlayingNoteTable[i].note = note;
|
|
_adlibPlayingNoteTable[i].hw_channel = hw_channel;
|
|
_adlibPlayingNoteTable[i].instrument = _currentInstrumentData;
|
|
_adlibChannelVolumeTable[hw_channel] = velocity;
|
|
adlibSetupInstrument(hw_channel, _currentInstrumentData);
|
|
adlibMelodicKeyOn(hw_channel, velocity, note);
|
|
} else {
|
|
/* percussion channel */
|
|
const int hw_channel = _currentInstrumentData[1]; // channel
|
|
assert(hw_channel >= 6 && hw_channel <= 10);
|
|
if (_adlibChannelPlayingTable[hw_channel] != 0) {
|
|
// fprintf(stdout, "No free channel found (C) hw_channel %d for track %d note %d\n", hw_channel, channel, note);
|
|
return;
|
|
}
|
|
_adlibChannelPlayingTable[hw_channel] = 0xFF;
|
|
int i;
|
|
for (i = 6; i < NUM_CHANNELS; ++i) {
|
|
if (_adlibPlayingNoteTable[i].midi_channel == 0xFF) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == NUM_CHANNELS) {
|
|
_adlibChannelPlayingTable[hw_channel] = 0;
|
|
// fprintf(stdout, "No free channel found (D) hw_channel %d\n", hw_channel);
|
|
return;
|
|
}
|
|
//fprintf(stdout, "Adding percussion channel %d note %d index %d track %d\n", hw_channel, note, i, channel);
|
|
_adlibPlayingNoteTable[i].midi_channel = channel;
|
|
_adlibPlayingNoteTable[i].note = note;
|
|
_adlibPlayingNoteTable[i].hw_channel = hw_channel;
|
|
_adlibPlayingNoteTable[i].instrument = _currentInstrumentData;
|
|
_adlibChannelVolumeTable[hw_channel] = velocity;
|
|
adlibSetupInstrument(hw_channel, _currentInstrumentData);
|
|
adlibPercussionKeyOn(hw_channel, velocity, note);
|
|
}
|
|
}
|
|
virtual void controlChange(int channel, int type, int value) {
|
|
}
|
|
virtual void programChange(int channel, int num) {
|
|
}
|
|
virtual void pitchBend(int channel, int lsb, int msb) {
|
|
adlibPitchBend(channel, (msb << 7) | lsb);
|
|
}
|
|
|
|
virtual void readSamples(int16_t *samples, int len) {
|
|
OPL3_GenerateStream(&_opl3, samples, len);
|
|
}
|
|
|
|
void adlibSetupInstrument(int channel, const uint8_t *arg0) {
|
|
const int volume = (_adlibChannelVolumeTable[channel] * kMusicVolume) >> 6;
|
|
// fprintf(stdout, "instrument channel:%d volume:%d\n", channel, volume);
|
|
const int mode = arg0[0];
|
|
const int hw_channel = arg0[1];
|
|
const int num = (mode != 0) ? hw_channel : channel;
|
|
const uint8_t modulatorOperator = _adlibOperatorTable[_adlibOperatorIndexTable[num * 2]];
|
|
const uint8_t carrierOperator = _adlibOperatorTable[_adlibOperatorIndexTable[num * 2 + 1]];
|
|
//fprintf(stdout, "operator modulator %d carrier %d\n", modulatorOperator, carrierOperator);
|
|
if (mode == 0 || hw_channel == 6) { // else goto loc_F24_284E5
|
|
// modulator
|
|
const uint8_t *bx = arg0 + 6;
|
|
uint16_t reg2x = 0;
|
|
if (READ_LE_UINT16(bx + 0x12) != 0) { // amplitude vibrato
|
|
reg2x |= 0x80;
|
|
}
|
|
if (READ_LE_UINT16(bx + 0x14) != 0) { // frequency vibrato
|
|
reg2x |= 0x40;
|
|
}
|
|
if (READ_LE_UINT16(bx + 0x0A) != 0) { // sustaining sound
|
|
reg2x |= 0x20;
|
|
}
|
|
if (READ_LE_UINT16(bx + 0x16) != 0) { // envelope scaling
|
|
reg2x |= 0x10;
|
|
}
|
|
reg2x |= READ_LE_UINT16(bx + 2) & 15; // frequency multiplier
|
|
writeRegister(0x20 + modulatorOperator, reg2x);
|
|
uint16_t dx;
|
|
if (READ_LE_UINT16(bx + 0x18) != 0) { // frequency modulation
|
|
dx = READ_LE_UINT16(bx + 0x10) & 63;
|
|
} else {
|
|
const int ax = (63 - (READ_LE_UINT16(bx + 0x10) & 63)) * volume; // output level
|
|
dx = 63 - (((ax + 127) + ax) / 254);
|
|
}
|
|
const uint8_t reg4x = dx | (READ_LE_UINT16(bx) << 6);
|
|
writeRegister(0x40 + modulatorOperator, reg4x);
|
|
writeRegister(0x60 + modulatorOperator, (READ_LE_UINT16(bx + 12) & 15) | (READ_LE_UINT16(bx + 6) << 4));
|
|
writeRegister(0x80 + modulatorOperator, (READ_LE_UINT16(bx + 14) & 15) | (READ_LE_UINT16(bx + 8) << 4));
|
|
uint16_t ax = 1;
|
|
if (READ_LE_UINT16(bx + 0x18) != 0) {
|
|
ax = 0;
|
|
}
|
|
dx = (READ_LE_UINT16(bx + 4) << 1) | ax;
|
|
ax = 0xC0 + ((mode != 0) ? hw_channel : channel);
|
|
writeRegister(ax, dx);
|
|
writeRegister(0xE0 + modulatorOperator, arg0[2] & 3); /* original writes to 0xE, typo */
|
|
}
|
|
// carrier
|
|
const uint8_t *bx = arg0 + 0x20; // 26 + 6
|
|
uint8_t reg2x = 0;
|
|
if (READ_LE_UINT16(bx + 0x12) != 0) {
|
|
reg2x |= 0x80;
|
|
}
|
|
if (READ_LE_UINT16(bx + 0x14) != 0) {
|
|
reg2x |= 0x40;
|
|
}
|
|
if (READ_LE_UINT16(bx + 0x0A) != 0) {
|
|
reg2x |= 0x20;
|
|
}
|
|
if (READ_LE_UINT16(bx + 0x16) != 0) {
|
|
reg2x |= 0x10;
|
|
}
|
|
reg2x |= (READ_LE_UINT16(bx + 2) & 15);
|
|
writeRegister(0x20 + carrierOperator, reg2x);
|
|
const int ax = (63 - (READ_LE_UINT16(bx + 0x10) & 63)) * volume;
|
|
int dx = 63 - (((ax + 127) + ax) / 254);
|
|
const uint8_t reg4x = dx | (READ_LE_UINT16(bx) << 6);
|
|
writeRegister(0x40 + carrierOperator, reg4x);
|
|
writeRegister(0x60 + carrierOperator, (READ_LE_UINT16(bx + 12) & 15) | (READ_LE_UINT16(bx + 6) << 4));
|
|
writeRegister(0x80 + carrierOperator, (READ_LE_UINT16(bx + 14) & 15) | (READ_LE_UINT16(bx + 8) << 4));
|
|
writeRegister(0xE0 + carrierOperator, arg0[3] & 3); /* original writes to 0xE, typo */
|
|
}
|
|
|
|
void adlibPercussionKeyOn(int channel, int var6, int note) {
|
|
// 6: Bass Drum
|
|
// 7: Snare Drum
|
|
// 8: Tom-Tom
|
|
// 9: Cymbal
|
|
// 10: Hi-Hat
|
|
assert(channel >= 6 && channel <= 10);
|
|
_regBD &= ~(1 << (10 - channel));
|
|
writeRegister(0xBD, _regBD);
|
|
int hw_channel = channel;
|
|
if (channel == 9) {
|
|
hw_channel = 8;
|
|
} else if (channel == 10) {
|
|
hw_channel = 7;
|
|
}
|
|
const uint16_t freq = _adlibFrequencyTable[note % 12];
|
|
writeRegister(0xA0 + hw_channel, freq);
|
|
const uint8_t regBx = ((note / 12) << 2) | ((freq & 0x300) >> 8);
|
|
writeRegister(0xB0 + hw_channel, regBx);
|
|
_regBD |= 1 << (10 - channel);
|
|
writeRegister(0xBD, _regBD);
|
|
}
|
|
|
|
void adlibMelodicKeyOn(int channel, int volume, int note) {
|
|
writeRegister(0xB0 + channel, 0);
|
|
const uint16_t freq = _adlibFrequencyTable[note % 12];
|
|
writeRegister(0xA0 + channel, freq & 0xFF); /* Frequency (Low) */
|
|
const uint8_t octave = (note / 12);
|
|
const uint8_t regBx = 0x20 | (octave << 2) | ((freq & 0x300) >> 8); /* Key-On | Octave | Frequency (High) */
|
|
writeRegister(0xB0 + channel, regBx);
|
|
}
|
|
|
|
void adlibMelodicKeyOff(int channel, int note) {
|
|
const uint16_t freq = _adlibFrequencyTable[note % 12];
|
|
const uint8_t octave = (note / 12);
|
|
const uint8_t regBx = (octave << 2) | ((freq & 0x300) >> 8); /* Octave | Frequency (High) */
|
|
writeRegister(0xB0 + channel, regBx);
|
|
}
|
|
|
|
void adlibPercussionKeyOff(int channel) {
|
|
assert(channel >= 6 && channel <= 10);
|
|
_regBD &= ~(1 << (10 - channel));
|
|
writeRegister(0xBD, _regBD);
|
|
}
|
|
|
|
void adlibPitchBend(int channel, int value) {
|
|
//fprintf(stdout, "pitch bend channel:%d value:%d\n", channel, value);
|
|
if (value == 0x2000) {
|
|
return;
|
|
}
|
|
if (value & 0x2000) {
|
|
value = -(value & 0x1FFF);
|
|
}
|
|
const int pitchBend = value;
|
|
for (int i = 0; i < NUM_CHANNELS; ++i) {
|
|
if (_adlibPlayingNoteTable[i].midi_channel != channel) {
|
|
continue;
|
|
}
|
|
const int note = _adlibPlayingNoteTable[i].note;
|
|
const int hw_channel = _adlibPlayingNoteTable[i].hw_channel;
|
|
static const int kVarC = 0x2000; // / byte_1B73_2BE66;
|
|
value = pitchBend / kVarC + note % 12;
|
|
int octave = note / 12;
|
|
if (value < 0) {
|
|
--octave;
|
|
value = -value;
|
|
} else if (value > 12) {
|
|
++octave;
|
|
value -= 12;
|
|
}
|
|
--octave;
|
|
const int var14 = ((pitchBend % kVarC) * 25) / kVarC;
|
|
const uint16_t freq = var14 + _adlibFrequencyTable[value % 12];
|
|
writeRegister(0xA0 + hw_channel, freq & 0xFF);
|
|
const uint8_t regBx = 0x20 | (octave << 2) | ((freq & 0x300) >> 8);
|
|
writeRegister(0xB0 + hw_channel, regBx);
|
|
}
|
|
}
|
|
|
|
void adlibReset() {
|
|
for (int i = 0; i < 6; ++i) {
|
|
adlibMelodicKeyOff(i, 0);
|
|
}
|
|
for (int i = 6; i < 11; ++i) {
|
|
adlibPercussionKeyOff(i);
|
|
}
|
|
writeRegister(8, 0x40);
|
|
for (int i = 0; i < 18; ++i) {
|
|
writeRegister(_adlibOperatorTable[i] + 0x40, 0x3F);
|
|
}
|
|
for (int i = 0; i < 9; ++i) {
|
|
writeRegister(0xB0 + i, 0);
|
|
}
|
|
for (int i = 0; i < 9; ++i) {
|
|
writeRegister(0xC0 + i, 0);
|
|
}
|
|
for (int i = 0; i < 18; ++i) {
|
|
writeRegister(_adlibOperatorTable[i] + 0x60, 0);
|
|
}
|
|
for (int i = 0; i < 18; ++i) {
|
|
writeRegister(_adlibOperatorTable[i] + 0x80, 0);
|
|
}
|
|
for (int i = 0; i < 18; ++i) {
|
|
writeRegister(_adlibOperatorTable[i] + 0x20, 0);
|
|
}
|
|
for (int i = 0; i < 18; ++i) {
|
|
writeRegister(_adlibOperatorTable[i] + 0xE0, 0);
|
|
}
|
|
writeRegister(1, 0x20);
|
|
writeRegister(1, 0);
|
|
for (int i = 0; i < 9; ++i) {
|
|
writeRegister(0xB0 + i, 0);
|
|
writeRegister(0xA0 + i, 0);
|
|
}
|
|
writeRegister(0xBD, 0);
|
|
_regBD = 0x20;
|
|
}
|
|
|
|
void writeRegister(uint8_t port, uint8_t value) {
|
|
//fprintf(stdout, "0x%02x:0x%02x\n", port, value);
|
|
OPL3_WriteReg(&_opl3, port, value);
|
|
}
|
|
};
|
|
|
|
static MidiDriver *createMidiDriver() {
|
|
return new MidiDriver_adlib;
|
|
}
|
|
|
|
const MidiDriverInfo midi_driver_adlib = {
|
|
"Adlib",
|
|
createMidiDriver
|
|
};
|