Files
2026-02-02 04:50:13 +01:00

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 &param) {
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