scummvm/engines/ultima/nuvie/sound/origin_fx_adib_driver.cpp
2020-05-26 11:52:51 +09:00

534 lines
15 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/conf/configuration.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/u6_lib_n.h"
#include "ultima/nuvie/sound/adplug/opl.h"
#include "ultima/nuvie/sound/origin_fx_adib_driver.h"
namespace Ultima {
namespace Nuvie {
const uint8 adlib_BD_cmd_tbl[] = { 0, 1, 0, 1, 0, 1, 16, 8, 4, 2, 1 };
OriginFXAdLibDriver::OriginFXAdLibDriver(Configuration *cfg, Copl *newopl) {
const uint8 byte_73_init[] = {1, 2, 3, 4, 5, 6, 7, 8, 0xB, 0xFF, 0xFF, 0, 0xC};
config = cfg;
opl = newopl;
adlib_tim_data = NULL;
adlib_num_active_channels = 9;
memset(midi_chan_tim_ptr, 0, sizeof(midi_chan_tim_ptr));
memset(midi_chan_pitch, 0, sizeof(midi_chan_pitch));
memset(midi_chan_tim_off_10, 0, sizeof(midi_chan_tim_off_10));
memset(midi_chan_tim_off_11, 0, sizeof(midi_chan_tim_off_11));
adlib_bd_status = 0;
memcpy(byte_73, byte_73_init, sizeof(byte_73_init));
for (int i = 0; i < 29; i++) {
midi_chan_volume[i] = 0x100;
}
memset(adlib_ins, 0, sizeof(adlib_ins));
for (int i = 0; i < 11; i++) {
adlib_ins[i].note = -1;
adlib_ins[i].channel = -1;
adlib_ins[i].byte_68 = 1;
}
load_tim_file();
init();
}
OriginFXAdLibDriver::~OriginFXAdLibDriver() {
if (adlib_tim_data)
delete [] adlib_tim_data;
}
void OriginFXAdLibDriver::init() {
opl->init();
for (int i = 0; i < 256; i++) {
midi_write_adlib(i, 0);
}
midi_write_adlib(0x01, 0x20);
midi_write_adlib(0xBD, 0);
midi_write_adlib(0x8, 0);
}
sint16 OriginFXAdLibDriver::read_sint16(unsigned char *buf) {
return (buf[1] << 8) | buf[0];
}
void OriginFXAdLibDriver::load_tim_file() {
U6Lib_n f;
Std::string filename;
nuvie_game_t game_type = get_game_type(config);
if (game_type == NUVIE_GAME_SE) {
config_get_path(config, "savage.tim", filename);
} else { // game_type == NUVIE_GAME_MD
config_get_path(config, "md.tim", filename);
}
f.open(filename, 4, game_type);
unsigned char *buf = f.get_item(1);
adlib_tim_data = new unsigned char [f.get_item_size(1) - 1];
num_tim_records = buf[0];
memcpy(adlib_tim_data, &buf[1], f.get_item_size(1) - 1);
free(buf);
for (int i = 0; i < 32; i++) {
midi_chan_tim_ptr[i] = adlib_tim_data;
}
program_change(0x9 , 0x80);
program_change(0xa , 0x72);
program_change(0xb , 0x83);
program_change(0xc , 0x71);
program_change(0xd , 0x86);
program_change(0xe , 0x87);
program_change(0xf , 0x85);
program_change(0x10 , 0x84);
program_change(0x11 , 0x81);
program_change(0x12 , 0x88);
program_change(0x13 , 0x8D);
program_change(0x14 , 0x8F);
program_change(0x15 , 0x90);
program_change(0x16 , 0x91);
program_change(0x17 , 0x93);
program_change(0x18 , 0x8C);
program_change(0x19 , 0x8B);
}
unsigned char *OriginFXAdLibDriver::get_tim_data(uint8 program_number) {
for (int i = 0; i < num_tim_records; i++) {
if (adlib_tim_data[i * 48 + 0x2f] == program_number) {
return &adlib_tim_data[i * 48];
}
}
return adlib_tim_data;
}
void OriginFXAdLibDriver::midi_write_adlib(unsigned int r, unsigned char v) {
opl->write(r, v);
}
void OriginFXAdLibDriver::program_change(sint8 channel, uint8 program_number) {
unsigned char *tim_data = get_tim_data(program_number);
int i, j;
debug("Program change channel: %d program: %d", channel, program_number);
for (i = 0; i < 11; i++) {
if (adlib_ins[i].channel == channel) {
play_note(channel, adlib_ins[i].note, 0); //note off.
adlib_ins[i].channel = -1;
adlib_ins[i].tim_data = NULL;
}
}
midi_chan_tim_ptr[channel] = tim_data;
midi_chan_tim_off_10[channel] = tim_data[0x10];
midi_chan_tim_off_11[channel] = tim_data[0x11];
if (tim_data[0xb] != 0 && adlib_num_active_channels == 9) {
midi_write_adlib(0xa6, 0);
midi_write_adlib(0xb6, 0);
midi_write_adlib(0xa7, 0);
midi_write_adlib(0xb7, 0xa);
midi_write_adlib(0xa8, 0x54);
midi_write_adlib(0xb8, 0x9);
adlib_num_active_channels = 6;
for (i = 6; i < 9; i++) {
for (j = 0; j < 13; j++) {
if (byte_73[j] == i) {
byte_73[j] = byte_73[i];
byte_73[i] = (uint8)-1;
break;
}
}
}
adlib_bd_status = 0x20;
midi_write_adlib(0xbd, adlib_bd_status);
}
}
void OriginFXAdLibDriver::pitch_bend(uint8 channel, uint8 pitch_lsb, uint8 pitch_msb) {
unsigned char *cur_tim_ptr = midi_chan_tim_ptr[channel];
midi_chan_pitch[channel] = ((sint16)((pitch_msb << 7) + pitch_lsb - 8192) * cur_tim_ptr[0xe]) / 256;
debug("pitch_bend: c=%d, pitch=%d %d,%d,%d", channel, midi_chan_pitch[channel], pitch_msb, pitch_lsb, cur_tim_ptr[0xe]);
for (int i = 0; i < adlib_num_active_channels; i++) {
if (adlib_ins[i].byte_68 > 1 && adlib_ins[i].channel == channel) {
sint16 var_4 = 0;
if (adlib_ins[i].tim_data != NULL) {
var_4 = read_sint16(&adlib_ins[i].tim_data[0x24]);
}
uint16 var_2 = sub_60D(adlib_ins[i].word_3c + midi_chan_pitch[channel] + adlib_ins[i].word_cb + adlib_ins[i].word_121 + var_4);
var_2 += 0x2000;
midi_write_adlib(0xa0 + i, var_2 & 0xff);
midi_write_adlib(0xb0 + i, var_2 >> 8);
}
}
}
void OriginFXAdLibDriver::control_mode_change(uint8 channel, uint8 function, uint8 value) {
uint8 c = channel;
debug("control_mode_change: c=%d, func=%2x, value=%d", channel, function, value);
if (c == 9) {
c++;
do {
control_mode_change(c, function, value);
c++;
} while (c <= 25);
c = 9;
}
if (function == 1) {
midi_chan_tim_off_11[channel] = ((((sint16)midi_chan_tim_ptr[channel][0xf]) * value) / 128) + (sint16)midi_chan_tim_ptr[channel][0x11];
} else if (function == 7) {
midi_chan_volume[c] = value + 128;
} else if (function == 0x7b) {
bool var_6 = false;
for (int i = 0; i < 0xb; i++) {
if (adlib_ins[i].byte_68 > 1) {
if (adlib_ins[i].channel == channel) {
play_note(channel, adlib_ins[i].note, 0); //note off
} else if (i >= adlib_num_active_channels) {
var_6 = true;
}
}
}
if (var_6 && adlib_num_active_channels < 9) {
midi_write_adlib(0xbd, 0);
adlib_num_active_channels = 9;
byte_73[6] = 7;
byte_73[7] = 8;
byte_73[8] = byte_73[0xb];
byte_73[0xb] = 6;
}
} else if (function == 0x79) {
control_mode_change(channel, 1, 0);
control_mode_change(channel, 7, 0x7f);
pitch_bend(channel, 0, 0x40);
}
}
void OriginFXAdLibDriver::play_note(uint8 channel, sint8 note, uint8 velocity) {
unsigned char *cur_tim_ptr = midi_chan_tim_ptr[channel];
for (; cur_tim_ptr != NULL; cur_tim_ptr += 48) {
sint8 voice = sub_4BF(channel, note, velocity, cur_tim_ptr);
sint16 var_4 = voice;
if (voice > 8) {
var_4 = 0x11 - voice;
}
if (voice >= 0) {
sint16 var_a = read_sint16(&cur_tim_ptr[0x24]);
if (velocity != 0) {
adlib_ins[voice].word_121 = 0;
adlib_ins[voice].byte_137 = 0;
adlib_ins[voice].word_cb = read_sint16(&cur_tim_ptr[0x12]);
}
sint8 cl = cur_tim_ptr[0x27];
if (cl < 0) {
adlib_ins[voice].word_3c = (-((sint16)(note - 60) * 256) / (1 << -(cl + 1))) + 0x3c00;
} else {
adlib_ins[voice].word_3c = (((sint16)(note - 60) * 256) / (1 << cl)) + 0x3c00;
}
uint16 var_2 = sub_60D(adlib_ins[voice].word_3c + midi_chan_pitch[channel] + adlib_ins[voice].word_cb + adlib_ins[voice].word_121 + var_a);
if (velocity == 0) {
if (voice < adlib_num_active_channels || voice <= 6) {
midi_write_adlib(0xa0 + var_4, var_2 & 0xff);
midi_write_adlib(0xb0 + var_4, var_2 >> 8);
} else {
adlib_bd_status &= ~adlib_BD_cmd_tbl[voice];
}
} else {
uint16 var_6 = cur_tim_ptr[6];
if (cur_tim_ptr[0xc] != 0 || midi_chan_volume[channel] < 0x100) {
sint16 di = 0x3f - ((midi_chan_volume[channel] * (0x3f - (((sint16)(63 - velocity) / (sint16)(1 << (7 - cur_tim_ptr[0xc]))) + (var_6 & 0x3f)))) >> 8); //fixme this was 0x14 in dosbox var_6 = 8
di = 63 - velocity;
di = di / (1 << (7 - cur_tim_ptr[0xc]));
di += var_6 & 0x3f;
sint16 ax = (0x3f - di) * midi_chan_volume[channel];
ax = ax / 256;
di = 0x3f - ax;
if (di > 0x3f) {
di = 0x3f;
}
if (di < 0) {
di = 0;
}
midi_write_adlib(0x40 + adlib_voice_op1(voice), (var_6 & 0xc0) + di);
}
var_6 = cur_tim_ptr[1];
if (cur_tim_ptr[0xd] != 0) {
sint16 di = (0x3f - velocity) / (sint16)(1 << (7 - cur_tim_ptr[0xd])) + (var_6 & 0x3f);
if (di > 0x3f) {
di = 0x3f;
}
if (di < 0) {
di = 0;
}
midi_write_adlib(0x40 + adlib_voice_op(voice), (var_6 & 0xc0) + di);
}
if (cur_tim_ptr[0xb] == 0 || voice == 6) {
if (cur_tim_ptr[0xb] == 0) {
var_2 += 0x2000;
}
midi_write_adlib(0xa0 + var_4, var_2 & 0xff);
midi_write_adlib(0xb0 + var_4, var_2 >> 8);
}
if (cur_tim_ptr[0xb] != 0) {
adlib_bd_status |= adlib_BD_cmd_tbl[voice];
}
}
if (cur_tim_ptr[0xb] != 0) {
midi_write_adlib(0xbd, adlib_bd_status);
}
}
if (cur_tim_ptr[0x26] == 0)
break;
}
}
uint16 OriginFXAdLibDriver::sub_60D(sint16 val) {
static const uint16 word_20f[] = {0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287, 0x2AE, 0x2D7, 0x302, 0x330, 0x360, 0x393, 0x3CA};
sint16 var_2 = val / 256;
sint16 si = ((var_2 + 6) / 0xc) - 2;
if (si > 7) {
si = 7;
}
if (si < 0) {
si = 0;
}
uint16 di = word_20f[(var_2 + 6) % 0xc];
if ((val & 0xff) != 0) {
int offset = ((var_2 - 18) % 0xc) + 1;
// FIXME: This offset is negative near the end of the Savage Empire Origin FX
// intro.. what should it do?
if (offset >= 0)
di += ((word_20f[offset] - di) * (val & 0xff)) / 256;
}
return (si << 10) + di;
}
uint16 OriginFXAdLibDriver::sub_4BF(uint8 channel, uint8 note, uint8 velocity, unsigned char *cur_tim_ptr) {
sint16 si = -1;
if (adlib_num_active_channels >= 9 || cur_tim_ptr[0xb] == 0) {
if (velocity == 0) {
for (si = 0; si < adlib_num_active_channels; si++) {
if (adlib_ins[si].byte_68 > 1 && adlib_ins[si].note == note && adlib_ins[si].channel == channel && adlib_ins[si].tim_data == cur_tim_ptr) {
adlib_ins[si].byte_68 = 0;
sub_45E(si);
sub_48E(si, 0xb);
break;
}
}
if (si == adlib_num_active_channels) {
si = -1;
}
} else {
if (byte_73[0xb] == 0xb) {
if (midi_chan_tim_ptr[channel] == cur_tim_ptr) {
si = byte_73[0xc];
byte_73[0xc] = byte_73[si];
sub_48E(si, 0xc);
midi_write_adlib(0xa0 + si, 0);
midi_write_adlib(0xb0 + si, 0);
}
} else {
si = byte_73[0xb];
byte_73[0xb] = byte_73[si];
sub_48E(si, 0xc);
}
if (si >= 0) {
adlib_ins[si].byte_68 = 2;
adlib_ins[si].note = note;
}
}
} else {
si = cur_tim_ptr[0xb];
adlib_bd_status &= ~adlib_BD_cmd_tbl[cur_tim_ptr[0xb]];
midi_write_adlib(0xbd, adlib_bd_status);
}
if (si >= 0) {
if (adlib_ins[si].channel != channel || adlib_ins[si].tim_data != cur_tim_ptr) { //changing instruments
write_adlib_instrument(si, cur_tim_ptr);
adlib_ins[si].channel = channel;
adlib_ins[si].tim_data = cur_tim_ptr;
}
}
return si;
}
void OriginFXAdLibDriver::sub_45E(sint16 voice) {
for (int i = 0; i < 0xd; i++) {
if (byte_73[i] == voice) {
byte_73[i] = byte_73[voice];
byte_73[voice] = voice;
}
}
}
void OriginFXAdLibDriver::sub_48E(sint16 voice, uint8 val) {
for (int i = 0; i < 0xd; i++) {
if (byte_73[i] == val) {
byte_73[i] = voice;
byte_73[voice] = val;
break;
}
}
}
uint8 OriginFXAdLibDriver::adlib_voice_op(sint8 voice) {
const uint8 opp_tbl[] = {0, 1, 2, 8, 9, 0xA, 0x10, 0x11, 0x12, 0, 1, 2, 8, 9, 0xA, 0x10, 0x14, 0x12, 0x15, 0x11};
return opp_tbl[adlib_num_active_channels < 9 ? voice + 9 : voice];
}
uint8 OriginFXAdLibDriver::adlib_voice_op1(sint8 voice) {
const uint8 opp1_tbl[] = {3, 4, 5, 0xB, 0xC, 0xD, 0x13, 0x14, 0x15, 3, 4, 5, 0xB, 0xC, 0xD, 0x13, 0x14, 0x12, 0x15, 0x11};
return opp1_tbl[adlib_num_active_channels < 9 ? voice + 9 : voice];
}
void OriginFXAdLibDriver::write_adlib_instrument(sint8 voice, unsigned char *tim_data) {
uint8 opadd = adlib_voice_op(voice);
uint8 opadd1 = adlib_voice_op1(voice);
unsigned char *cur_tim_ptr = tim_data;
midi_write_adlib(0x20 + opadd, *cur_tim_ptr++);
midi_write_adlib(0x40 + opadd, *cur_tim_ptr++);
midi_write_adlib(0x60 + opadd, *cur_tim_ptr++);
midi_write_adlib(0x80 + opadd, *cur_tim_ptr++);
midi_write_adlib(0xe0 + opadd, *cur_tim_ptr++);
if (adlib_num_active_channels == 9 || tim_data[0xb] < 7) {
midi_write_adlib(0x20 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0x40 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0x60 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0x80 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0xe0 + opadd1, *cur_tim_ptr++);
midi_write_adlib(0xc0 + voice, *cur_tim_ptr++);
}
}
void OriginFXAdLibDriver::interrupt_vector() {
const uint8 byte_229[] = {24, 0, 18, 20, 22, 0, 0, 0};
for (int i = 0; i < adlib_num_active_channels; i++) {
unsigned char *cur_tim_data = NULL;
bool update_adlib = false;
sint8 channel = adlib_ins[i].channel;
if (channel < 0 || channel >= 32) {
continue;
}
uint8 var_8 = byte_229[adlib_ins[i].byte_68];
sint16 var_10 = 0;
if (adlib_ins[i].tim_data == NULL) {
cur_tim_data = adlib_tim_data;
} else {
cur_tim_data = adlib_ins[i].tim_data;
var_10 = read_sint16(&cur_tim_data[0x24]);
}
if (var_8 != 0) {
sint16 var_a = read_sint16(&cur_tim_data[var_8 * 2 - 16]);
sint16 var_c = read_sint16(&cur_tim_data[(var_8 + 1) * 2 - 16]);
sint16 tmp = (var_c > adlib_ins[i].word_cb) ? var_c - adlib_ins[i].word_cb : adlib_ins[i].word_cb - var_c;
if (tmp >= var_a) {
if (adlib_ins[i].word_cb >= var_c) {
adlib_ins[i].word_cb -= var_a;
} else {
adlib_ins[i].word_cb += var_a;
}
} else {
adlib_ins[i].word_cb = var_c;
adlib_ins[i].byte_68++;
}
update_adlib = true;
}
if (midi_chan_tim_off_10[channel] != 0) {
adlib_ins[i].byte_137 += midi_chan_tim_off_10[channel];
sint8 var_11 = adlib_ins[i].byte_137;
if (var_11 > 63 || var_11 < -64) {
var_11 = -128 - var_11;
}
adlib_ins[i].word_121 = (midi_chan_tim_off_11[channel] * var_11) / 16;
update_adlib = true;
}
if (update_adlib || var_10 != 0) {
uint16 adlib_cmd_data = sub_60D(adlib_ins[i].word_3c + midi_chan_pitch[channel] + adlib_ins[i].word_cb + adlib_ins[i].word_121 + var_10);
if (adlib_ins[i].byte_68 > 1) {
adlib_cmd_data += 0x2000;
}
midi_write_adlib(0xa0 + i, adlib_cmd_data & 0xff);
midi_write_adlib(0xb0 + i, adlib_cmd_data >> 8);
}
}
}
} // End of namespace Nuvie
} // End of namespace Ultima