630 lines
16 KiB
C++
630 lines
16 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "ultima/nuvie/core/nuvie_defs.h"
|
|
#include "ultima/nuvie/files/u6_lzw.h"
|
|
#include "ultima/nuvie/sound/adplug/u6m.h"
|
|
|
|
namespace Ultima {
|
|
namespace Nuvie {
|
|
|
|
Cu6mPlayer::~Cu6mPlayer() {
|
|
if (song_data)
|
|
free(song_data);
|
|
}
|
|
|
|
CPlayer *Cu6mPlayer::factory(Copl *newopl) {
|
|
return new Cu6mPlayer(newopl);
|
|
}
|
|
|
|
bool Cu6mPlayer::load(const Common::Path &filename) {
|
|
uint32 decompressed_filesize;
|
|
U6Lzw lzw;
|
|
|
|
song_data = lzw.decompress_file(filename, decompressed_filesize);
|
|
|
|
rewind(0);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Cu6mPlayer::update() {
|
|
if (!driver_active) {
|
|
driver_active = true;
|
|
dec_clip(read_delay);
|
|
if (read_delay == 0) {
|
|
command_loop();
|
|
}
|
|
|
|
// on all Adlib channels: freq slide/vibrato, mute factor slide
|
|
for (int i = 0; i < 9; i++) {
|
|
if (channel_freq_signed_delta[i] != 0)
|
|
// frequency slide + mute factor slide
|
|
{
|
|
// freq slide
|
|
freq_slide(i);
|
|
|
|
// mute factor slide
|
|
if (carrier_mf_signed_delta[i] != 0) {
|
|
mf_slide(i);
|
|
}
|
|
} else
|
|
// vibrato + mute factor slide
|
|
{
|
|
// vibrato
|
|
if ((vb_multiplier[i] != 0) && ((channel_freq[i].hi & 0x20) == 0x20)) {
|
|
vibrato(i);
|
|
}
|
|
|
|
// mute factor slide
|
|
if (carrier_mf_signed_delta[i] != 0) {
|
|
mf_slide(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
driver_active = false;
|
|
}
|
|
|
|
return !songend;
|
|
}
|
|
|
|
|
|
void Cu6mPlayer::rewind(int subsong) {
|
|
played_ticks = 0;
|
|
songend = false;
|
|
|
|
// set the driver's internal variables
|
|
byte_pair freq_word = {0, 0};
|
|
|
|
driver_active = false;
|
|
song_pos = 0;
|
|
loop_position = 0; // position of the loop point
|
|
read_delay = 0; // delay (in timer ticks) before further song data is read
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
// frequency
|
|
channel_freq_signed_delta[i] = 0;
|
|
channel_freq[i] = freq_word; // Adlib freq settings for each channel
|
|
|
|
// vibrato ("vb")
|
|
vb_current_value[i] = 0;
|
|
vb_double_amplitude[i] = 0;
|
|
vb_multiplier[i] = 0;
|
|
vb_direction_flag[i] = 0;
|
|
|
|
// mute factor ("mf") == ~(volume)
|
|
carrier_mf[i] = 0;
|
|
carrier_mf_signed_delta[i] = 0;
|
|
carrier_mf_mod_delay_backup[i] = 0;
|
|
carrier_mf_mod_delay[i] = 0;
|
|
}
|
|
|
|
while (!subsong_stack.empty()) // empty subsong stack
|
|
subsong_stack.pop();
|
|
|
|
opl->init();
|
|
out_adlib(1, 32); // go to OPL2 mode
|
|
}
|
|
|
|
|
|
float Cu6mPlayer::getrefresh() {
|
|
return ((float)60); // the Ultima 6 music driver expects to be called at 60 Hz
|
|
}
|
|
|
|
// ============================================================================================
|
|
//
|
|
//
|
|
// Functions called by update()
|
|
//
|
|
//
|
|
// ============================================================================================
|
|
|
|
|
|
// This function reads the song data and executes the embedded commands.
|
|
void Cu6mPlayer::command_loop() {
|
|
unsigned char command_byte; // current command byte
|
|
int command_nibble_hi; // command byte, bits 4-7
|
|
int command_nibble_lo; // command byte, bite 0-3
|
|
bool repeat_loop = true; //
|
|
|
|
do {
|
|
// extract low and high command nibbles
|
|
command_byte = read_song_byte(); // implicitly increments song_pos
|
|
command_nibble_hi = command_byte >> 4;
|
|
command_nibble_lo = command_byte & 0xf;
|
|
|
|
switch (command_nibble_hi) {
|
|
case 0x0:
|
|
command_0(command_nibble_lo);
|
|
break;
|
|
case 0x1:
|
|
command_1(command_nibble_lo);
|
|
break;
|
|
case 0x2:
|
|
command_2(command_nibble_lo);
|
|
break;
|
|
case 0x3:
|
|
command_3(command_nibble_lo);
|
|
break;
|
|
case 0x4:
|
|
command_4(command_nibble_lo);
|
|
break;
|
|
case 0x5:
|
|
command_5(command_nibble_lo);
|
|
break;
|
|
case 0x6:
|
|
command_6(command_nibble_lo);
|
|
break;
|
|
case 0x7:
|
|
command_7(command_nibble_lo);
|
|
break;
|
|
case 0x8:
|
|
switch (command_nibble_lo) {
|
|
case 1:
|
|
command_81();
|
|
break;
|
|
case 2:
|
|
command_82();
|
|
repeat_loop = false;
|
|
break;
|
|
case 3:
|
|
command_83();
|
|
break;
|
|
case 5:
|
|
command_85();
|
|
break;
|
|
case 6:
|
|
command_86();
|
|
break;
|
|
default:
|
|
break; // maybe generate an error?
|
|
}
|
|
break;
|
|
case 0xE:
|
|
command_E();
|
|
break;
|
|
case 0xF:
|
|
command_F();
|
|
break;
|
|
default:
|
|
break; // maybe generate an error?
|
|
}
|
|
|
|
} while (repeat_loop);
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------
|
|
// The commands supported by the U6 music file format
|
|
// --------------------------------------------------------
|
|
|
|
// ----------------------------------------
|
|
// Set octave and frequency, note off
|
|
// Format: 0c nn
|
|
// c = channel, nn = packed Adlib frequency
|
|
// ----------------------------------------
|
|
void Cu6mPlayer::command_0(int channel) {
|
|
unsigned char freq_byte;
|
|
byte_pair freq_word;
|
|
|
|
freq_byte = read_song_byte();
|
|
freq_word = expand_freq_byte(freq_byte);
|
|
set_adlib_freq(channel, freq_word);
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------
|
|
// Set octave and frequency, old note off, new note on
|
|
// Format: 1c nn
|
|
// c = channel, nn = packed Adlib frequency
|
|
// ---------------------------------------------------
|
|
void Cu6mPlayer::command_1(int channel) {
|
|
unsigned char freq_byte;
|
|
byte_pair freq_word;
|
|
|
|
vb_direction_flag[channel] = 0;
|
|
vb_current_value[channel] = 0;
|
|
|
|
freq_byte = read_song_byte();
|
|
freq_word = expand_freq_byte(freq_byte);
|
|
set_adlib_freq(channel, freq_word);
|
|
|
|
freq_word.hi = freq_word.hi | 0x20; // note on
|
|
set_adlib_freq(channel, freq_word);
|
|
}
|
|
|
|
|
|
// ----------------------------------------
|
|
// Set octave and frequency, note on
|
|
// Format: 2c nn
|
|
// c = channel, nn = packed Adlib frequency
|
|
// ----------------------------------------
|
|
void Cu6mPlayer::command_2(int channel) {
|
|
unsigned char freq_byte;
|
|
byte_pair freq_word;
|
|
|
|
freq_byte = read_song_byte();
|
|
freq_word = expand_freq_byte(freq_byte);
|
|
freq_word.hi = freq_word.hi | 0x20; // note on
|
|
set_adlib_freq(channel, freq_word);
|
|
}
|
|
|
|
|
|
// --------------------------------------
|
|
// Set "carrier mute factor"==not(volume)
|
|
// Format: 3c nn
|
|
// c = channel, nn = mute factor
|
|
// --------------------------------------
|
|
void Cu6mPlayer::command_3(int channel) {
|
|
unsigned char mf_byte;
|
|
|
|
carrier_mf_signed_delta[channel] = 0;
|
|
mf_byte = read_song_byte();
|
|
set_carrier_mf(channel, mf_byte);
|
|
}
|
|
|
|
|
|
// ----------------------------------------
|
|
// set "modulator mute factor"==not(volume)
|
|
// Format: 4c nn
|
|
// c = channel, nn = mute factor
|
|
// ----------------------------------------
|
|
void Cu6mPlayer::command_4(int channel) {
|
|
unsigned char mf_byte;
|
|
|
|
mf_byte = read_song_byte();
|
|
set_modulator_mf(channel, mf_byte);
|
|
}
|
|
|
|
|
|
// --------------------------------------------
|
|
// Set portamento (pitch slide)
|
|
// Format: 5c nn
|
|
// c = channel, nn = signed channel pitch delta
|
|
// --------------------------------------------
|
|
void Cu6mPlayer::command_5(int channel) {
|
|
channel_freq_signed_delta[channel] = read_signed_song_byte();
|
|
}
|
|
|
|
|
|
// --------------------------------------------
|
|
// Set vibrato parameters
|
|
// Format: 6c mn
|
|
// c = channel
|
|
// m = vibrato double amplitude
|
|
// n = vibrato multiplier
|
|
// --------------------------------------------
|
|
void Cu6mPlayer::command_6(int channel) {
|
|
unsigned char vb_parameters;
|
|
|
|
vb_parameters = read_song_byte();
|
|
vb_double_amplitude[channel] = vb_parameters >> 4; // high nibble
|
|
vb_multiplier[channel] = vb_parameters & 0xF; // low nibble
|
|
}
|
|
|
|
|
|
// ----------------------------------------
|
|
// Assign Adlib instrument to Adlib channel
|
|
// Format: 7c nn
|
|
// c = channel, nn = instrument number
|
|
// ----------------------------------------
|
|
void Cu6mPlayer::command_7(int channel) {
|
|
int instrument_offset = instrument_offsets[read_song_byte()];
|
|
out_adlib_opcell(channel, false, 0x20, *(song_data + instrument_offset + 0));
|
|
out_adlib_opcell(channel, false, 0x40, *(song_data + instrument_offset + 1));
|
|
out_adlib_opcell(channel, false, 0x60, *(song_data + instrument_offset + 2));
|
|
out_adlib_opcell(channel, false, 0x80, *(song_data + instrument_offset + 3));
|
|
out_adlib_opcell(channel, false, 0xE0, *(song_data + instrument_offset + 4));
|
|
out_adlib_opcell(channel, true, 0x20, *(song_data + instrument_offset + 5));
|
|
out_adlib_opcell(channel, true, 0x40, *(song_data + instrument_offset + 6));
|
|
out_adlib_opcell(channel, true, 0x60, *(song_data + instrument_offset + 7));
|
|
out_adlib_opcell(channel, true, 0x80, *(song_data + instrument_offset + 8));
|
|
out_adlib_opcell(channel, true, 0xE0, *(song_data + instrument_offset + 9));
|
|
out_adlib(0xC0 + channel, *(song_data + instrument_offset + 10));
|
|
}
|
|
|
|
|
|
// -------------------------------------------
|
|
// Branch to a new subsong
|
|
// Format: 81 nn aa bb
|
|
// nn == number of times to repeat the subsong
|
|
// aa == subsong offset (low byte)
|
|
// bb == subsong offset (high byte)
|
|
// -------------------------------------------
|
|
void Cu6mPlayer::command_81() {
|
|
subsong_info new_ss_info;
|
|
|
|
new_ss_info.subsong_repetitions = read_song_byte();
|
|
new_ss_info.subsong_start = read_song_byte();
|
|
new_ss_info.subsong_start += read_song_byte() << 8;
|
|
new_ss_info.continue_pos = song_pos;
|
|
|
|
subsong_stack.push(new_ss_info);
|
|
song_pos = new_ss_info.subsong_start;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
// Stop interpreting commands for this timer tick
|
|
// Format: 82 nn
|
|
// nn == delay (in timer ticks) until further data will be read
|
|
// ------------------------------------------------------------
|
|
void Cu6mPlayer::command_82() {
|
|
read_delay = read_song_byte();
|
|
}
|
|
|
|
|
|
// -----------------------------
|
|
// Adlib instrument data follows
|
|
// Format: 83 nn <11 bytes>
|
|
// nn == instrument number
|
|
// -----------------------------
|
|
void Cu6mPlayer::command_83() {
|
|
unsigned char instrument_number = read_song_byte();
|
|
instrument_offsets[instrument_number] = song_pos;
|
|
song_pos += 11;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------
|
|
// Set -1 mute factor slide (upward volume slide)
|
|
// Format: 85 cn
|
|
// c == channel
|
|
// n == slide delay
|
|
// ----------------------------------------------
|
|
void Cu6mPlayer::command_85() {
|
|
unsigned char data_byte = read_song_byte();
|
|
int channel = data_byte >> 4; // high nibble
|
|
unsigned char slide_delay = data_byte & 0xF; // low nibble
|
|
carrier_mf_signed_delta[channel] = +1;
|
|
carrier_mf_mod_delay[channel] = slide_delay + 1;
|
|
carrier_mf_mod_delay_backup[channel] = slide_delay + 1;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------
|
|
// Set +1 mute factor slide (downward volume slide)
|
|
// Format: 86 cn
|
|
// c == channel
|
|
// n == slide speed
|
|
// ------------------------------------------------
|
|
void Cu6mPlayer::command_86() {
|
|
unsigned char data_byte = read_song_byte();
|
|
int channel = data_byte >> 4; // high nibble
|
|
unsigned char slide_delay = data_byte & 0xF; // low nibble
|
|
carrier_mf_signed_delta[channel] = -1;
|
|
carrier_mf_mod_delay[channel] = slide_delay + 1;
|
|
carrier_mf_mod_delay_backup[channel] = slide_delay + 1;
|
|
}
|
|
|
|
|
|
// --------------
|
|
// Set loop point
|
|
// Format: E?
|
|
// --------------
|
|
void Cu6mPlayer::command_E() {
|
|
loop_position = song_pos;
|
|
}
|
|
|
|
|
|
// ---------------------------
|
|
// Return from current subsong
|
|
// Format: F?
|
|
// ---------------------------
|
|
void Cu6mPlayer::command_F() {
|
|
if (!subsong_stack.empty()) {
|
|
subsong_info temp = subsong_stack.top();
|
|
subsong_stack.pop();
|
|
temp.subsong_repetitions--;
|
|
if (temp.subsong_repetitions == 0) {
|
|
song_pos = temp.continue_pos;
|
|
} else {
|
|
song_pos = temp.subsong_start;
|
|
subsong_stack.push(temp);
|
|
}
|
|
} else {
|
|
song_pos = loop_position;
|
|
songend = true;
|
|
}
|
|
}
|
|
|
|
|
|
// --------------------
|
|
// Additional functions
|
|
// --------------------
|
|
|
|
// This function decrements its argument, without allowing it to become negative.
|
|
void Cu6mPlayer::dec_clip(int ¶m) {
|
|
param--;
|
|
if (param < 0) {
|
|
param = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// Returns the byte at the current song position.
|
|
// Side effect: increments song_pos.
|
|
unsigned char Cu6mPlayer::read_song_byte() {
|
|
unsigned char song_byte;
|
|
song_byte = song_data[song_pos];
|
|
song_pos++;
|
|
return song_byte;
|
|
}
|
|
|
|
|
|
// Same as read_song_byte(), except that it returns a signed byte
|
|
signed char Cu6mPlayer::read_signed_song_byte() {
|
|
unsigned char song_byte;
|
|
int signed_value;
|
|
song_byte = *(song_data + song_pos);
|
|
song_pos++;
|
|
if (song_byte <= 127) {
|
|
signed_value = song_byte;
|
|
} else {
|
|
signed_value = (int)song_byte - 0x100;
|
|
}
|
|
return ((signed char)signed_value);
|
|
}
|
|
|
|
|
|
Cu6mPlayer::byte_pair Cu6mPlayer::expand_freq_byte(unsigned char freq_byte) {
|
|
const byte_pair freq_table[24] = {
|
|
{0x00, 0x00}, {0x58, 0x01}, {0x82, 0x01}, {0xB0, 0x01},
|
|
{0xCC, 0x01}, {0x03, 0x02}, {0x41, 0x02}, {0x86, 0x02},
|
|
{0x00, 0x00}, {0x6A, 0x01}, {0x96, 0x01}, {0xC7, 0x01},
|
|
{0xE4, 0x01}, {0x1E, 0x02}, {0x5F, 0x02}, {0xA8, 0x02},
|
|
{0x00, 0x00}, {0x47, 0x01}, {0x6E, 0x01}, {0x9A, 0x01},
|
|
{0xB5, 0x01}, {0xE9, 0x01}, {0x24, 0x02}, {0x66, 0x02}
|
|
};
|
|
|
|
int packed_freq;
|
|
int octave;
|
|
byte_pair freq_word;
|
|
|
|
packed_freq = freq_byte & 0x1F;
|
|
octave = freq_byte >> 5;
|
|
|
|
// range check (not present in the original U6 music driver)
|
|
if (packed_freq >= 24) {
|
|
packed_freq = 0;
|
|
}
|
|
|
|
freq_word.hi = freq_table[packed_freq].hi + (octave << 2);
|
|
freq_word.lo = freq_table[packed_freq].lo;
|
|
|
|
return freq_word;
|
|
}
|
|
|
|
|
|
void Cu6mPlayer::set_adlib_freq(int channel, Cu6mPlayer::byte_pair freq_word) {
|
|
out_adlib(0xA0 + channel, freq_word.lo);
|
|
out_adlib(0xB0 + channel, freq_word.hi);
|
|
// update the Adlib register backups
|
|
channel_freq[channel] = freq_word;
|
|
}
|
|
|
|
|
|
// this function sets the Adlib frequency, but does not update the register backups
|
|
void Cu6mPlayer::set_adlib_freq_no_update(int channel, Cu6mPlayer::byte_pair freq_word) {
|
|
out_adlib(0xA0 + channel, freq_word.lo);
|
|
out_adlib(0xB0 + channel, freq_word.hi);
|
|
}
|
|
|
|
|
|
void Cu6mPlayer::set_carrier_mf(int channel, unsigned char mute_factor) {
|
|
out_adlib_opcell(channel, true, 0x40, mute_factor);
|
|
carrier_mf[channel] = mute_factor;
|
|
}
|
|
|
|
|
|
void Cu6mPlayer::set_modulator_mf(int channel, unsigned char mute_factor) {
|
|
out_adlib_opcell(channel, false, 0x40, mute_factor);
|
|
}
|
|
|
|
|
|
void Cu6mPlayer::freq_slide(int channel) {
|
|
byte_pair freq = channel_freq[channel];
|
|
|
|
long freq_word = freq.lo + (freq.hi << 8) + channel_freq_signed_delta[channel];
|
|
if (freq_word < 0) {
|
|
freq_word += 0x10000;
|
|
}
|
|
if (freq_word > 0xFFFF) {
|
|
freq_word -= 0x10000;
|
|
}
|
|
|
|
freq.lo = freq_word & 0xFF;
|
|
freq.hi = (freq_word >> 8) & 0xFF;
|
|
set_adlib_freq(channel, freq);
|
|
}
|
|
|
|
|
|
void Cu6mPlayer::vibrato(int channel) {
|
|
byte_pair freq;
|
|
|
|
if (vb_current_value[channel] >= vb_double_amplitude[channel]) {
|
|
vb_direction_flag[channel] = 1;
|
|
} else if (vb_current_value[channel] <= 0) {
|
|
vb_direction_flag[channel] = 0;
|
|
}
|
|
|
|
if (vb_direction_flag[channel] == 0) {
|
|
vb_current_value[channel]++;
|
|
} else {
|
|
vb_current_value[channel]--;
|
|
}
|
|
|
|
long freq_word = channel_freq[channel].lo + (channel_freq[channel].hi << 8);
|
|
freq_word += (vb_current_value[channel] - (vb_double_amplitude[channel] >> 1))
|
|
* vb_multiplier[channel];
|
|
if (freq_word < 0) {
|
|
freq_word += 0x10000;
|
|
}
|
|
if (freq_word > 0xFFFF) {
|
|
freq_word -= 0x10000;
|
|
}
|
|
|
|
freq.lo = freq_word & 0xFF;
|
|
freq.hi = (freq_word >> 8) & 0xFF;
|
|
set_adlib_freq_no_update(channel, freq);
|
|
}
|
|
|
|
|
|
void Cu6mPlayer::mf_slide(int channel) {
|
|
carrier_mf_mod_delay[channel]--;
|
|
if (carrier_mf_mod_delay[channel] == 0) {
|
|
carrier_mf_mod_delay[channel] = carrier_mf_mod_delay_backup[channel];
|
|
int current_mf = carrier_mf[channel] + carrier_mf_signed_delta[channel];
|
|
if (current_mf > 0x3F) {
|
|
current_mf = 0x3F;
|
|
carrier_mf_signed_delta[channel] = 0;
|
|
} else if (current_mf < 0) {
|
|
current_mf = 0;
|
|
carrier_mf_signed_delta[channel] = 0;
|
|
}
|
|
|
|
set_carrier_mf(channel, (unsigned char)current_mf);
|
|
}
|
|
}
|
|
|
|
|
|
void Cu6mPlayer::out_adlib(unsigned char adlib_register, unsigned char adlib_data) {
|
|
opl->write(adlib_register, adlib_data);
|
|
}
|
|
|
|
|
|
void Cu6mPlayer::out_adlib_opcell(int channel, bool carrier, unsigned char adlib_register, unsigned char out_byte) {
|
|
const unsigned char adlib_channel_to_carrier_offset[9] =
|
|
{0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15};
|
|
const unsigned char adlib_channel_to_modulator_offset[9] =
|
|
{0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};
|
|
|
|
if (carrier) {
|
|
out_adlib(adlib_register + adlib_channel_to_carrier_offset[channel], out_byte);
|
|
} else {
|
|
out_adlib(adlib_register + adlib_channel_to_modulator_offset[channel], out_byte);
|
|
}
|
|
}
|
|
|
|
} // End of namespace Nuvie
|
|
} // End of namespace Ultima
|