Mercurial > audlegacy-plugins
view src/adplug/core/u6m.cxx @ 3193:93cf4d0a0dde
alsa-ng: Prefer "PCM" volume control over "Wave" on systems that have both, for consistency with OSS plugin and maybe other apps (closes AUD-34 and AUD-36).
author | John Lindgren <john.lindgren@tds.net> |
---|---|
date | Mon, 29 Jun 2009 23:05:56 -0400 |
parents | f27d73fb0bcd |
children |
line wrap: on
line source
/* * Adplug - Replayer for many OPL2/OPL3 audio file formats. * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * u6m.cpp - Ultima 6 Music Player by Marc Winterrowd. * This code extends the Adlib Winamp plug-in by Simon Peter <dn.tlp@gmx.net> */ #include "u6m.h" // Makes security checks on output buffer before writing #define SAVE_OUTPUT_ROOT(c, d, p) \ if(p < d.size) \ output_root(c, d.data, p); \ else \ return false; CPlayer * Cu6mPlayer::factory (Copl * newopl) { return new Cu6mPlayer (newopl); } bool Cu6mPlayer::load (VFSFile * fd, const CFileProvider & fp) { // file validation section // this section only checks a few *necessary* conditions unsigned long filesize, decompressed_filesize; binistream *f; f = fp.open (fd); if (!f) return false; filesize = fp.filesize (f); if (filesize >= 6) { // check if the file has a valid pseudo-header unsigned char pseudo_header[6]; f->readString ((char *) pseudo_header, 6); decompressed_filesize = pseudo_header[0] + (pseudo_header[1] << 8); if (!((pseudo_header[2] == 0) && (pseudo_header[3] == 0) && (pseudo_header[4] + ((pseudo_header[5] & 0x1) << 8) == 0x100) && (decompressed_filesize > (filesize - 4)))) { fp.close (f); return (false); } } else { fp.close (f); return (false); } // load section song_data = new unsigned char[decompressed_filesize]; unsigned char *compressed_song_data = new unsigned char[filesize - 3]; f->seek (4); f->readString ((char *) compressed_song_data, filesize - 4); fp.close (f); // attempt to decompress the song data // if unsuccessful, deallocate song_data[] on the spot, and return(false) data_block source, destination; source.size = filesize - 4; source.data = compressed_song_data; destination.size = decompressed_filesize; destination.data = song_data; if (!lzw_decompress (source, destination)) { delete[]compressed_song_data; compressed_song_data = 0; delete[]song_data; song_data = 0; return (false); } // deallocation section delete[]compressed_song_data; compressed_song_data = 0; 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 load() // // // ============================================================================================ // decompress from memory to memory bool Cu6mPlayer::lzw_decompress (Cu6mPlayer::data_block source, Cu6mPlayer::data_block dest) { bool end_marker_reached = false; int codeword_size = 9; long bits_read = 0; int next_free_codeword = 0x102; int dictionary_size = 0x200; MyDict dictionary = MyDict (); std::stack < unsigned char >root_stack; long bytes_written = 0; int cW, pW = 0; unsigned char C; while (!end_marker_reached) { cW = get_next_codeword (bits_read, source.data, codeword_size); switch (cW) { // re-init the dictionary case 0x100: codeword_size = 9; next_free_codeword = 0x102; dictionary_size = 0x200; dictionary.reset (); cW = get_next_codeword (bits_read, source.data, codeword_size); SAVE_OUTPUT_ROOT ((unsigned char) cW, dest, bytes_written); break; // end of compressed file has been reached case 0x101: end_marker_reached = true; break; // (cW <> 0x100) && (cW <> 0x101) default: if (cW < next_free_codeword) // codeword is already in the dictionary { // create the string associated with cW (on the stack) get_string (cW, dictionary, root_stack); C = root_stack.top (); // output the string represented by cW while (!root_stack.empty ()) { SAVE_OUTPUT_ROOT (root_stack.top (), dest, bytes_written); root_stack.pop (); } // add pW+C to the dictionary dictionary.add (C, pW); next_free_codeword++; if (next_free_codeword >= dictionary_size) { if (codeword_size < max_codeword_length) { codeword_size += 1; dictionary_size *= 2; } } } else // codeword is not yet defined { // create the string associated with pW (on the stack) get_string (pW, dictionary, root_stack); C = root_stack.top (); // output the string represented by pW while (!root_stack.empty ()) { SAVE_OUTPUT_ROOT (root_stack.top (), dest, bytes_written); root_stack.pop (); } // output the char C SAVE_OUTPUT_ROOT (C, dest, bytes_written); // the new dictionary entry must correspond to cW // if it doesn't, something is wrong with the lzw-compressed data. if (cW != next_free_codeword) { /* printf("cW != next_free_codeword!\n"); exit(-1); */ return false; } // add pW+C to the dictionary dictionary.add (C, pW); next_free_codeword++; if (next_free_codeword >= dictionary_size) { if (codeword_size < max_codeword_length) { codeword_size += 1; dictionary_size *= 2; } } }; break; } // shift roles - the current cW becomes the new pW pW = cW; } return (true); // indicate successful decompression } // -------------------- // Additional functions // -------------------- // Read the next code word from the source buffer int Cu6mPlayer::get_next_codeword (long &bits_read, unsigned char *source, int codeword_size) { unsigned char b0, b1, b2; int codeword; b0 = source[bits_read / 8]; b1 = source[bits_read / 8 + 1]; b2 = source[bits_read / 8 + 2]; codeword = ((b2 << 16) + (b1 << 8) + b0); codeword = codeword >> (bits_read % 8); switch (codeword_size) { case 0x9: codeword = codeword & 0x1ff; break; case 0xa: codeword = codeword & 0x3ff; break; case 0xb: codeword = codeword & 0x7ff; break; case 0xc: codeword = codeword & 0xfff; break; default: codeword = -1; // indicates that an error has occurred break; } bits_read += codeword_size; return (codeword); } // output a root to memory void Cu6mPlayer::output_root (unsigned char root, unsigned char *destination, long &position) { destination[position] = root; position++; } // output the string represented by a codeword void Cu6mPlayer::get_string (int codeword, Cu6mPlayer::MyDict & dictionary, std::stack < unsigned char >&root_stack) { unsigned char root; int current_codeword; current_codeword = codeword; while (current_codeword > 0xff) { root = dictionary.get_root (current_codeword); current_codeword = dictionary.get_codeword (current_codeword); root_stack.push (root); } // push the root at the leaf root_stack.push ((unsigned char) current_codeword); } // ============================================================================================ // // // 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 paramters // 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); } } // ============================================================================================ // // // The Dictionary // // // ============================================================================================ Cu6mPlayer::MyDict::MyDict () { dict_size = default_dict_size; dictionary = new dict_entry[dict_size - 0x100]; // don't allocate space for the roots contains = 0x102; } Cu6mPlayer::MyDict::MyDict (int max_size) { dict_size = max_size; dictionary = new dict_entry[dict_size - 0x100]; // don't allocate space for the roots contains = 0x102; } Cu6mPlayer::MyDict::~MyDict () { delete[]dictionary; dictionary = 0; } // re-initializes the dictionary void Cu6mPlayer::MyDict::reset () { contains = 0x102; } // Note: If the dictionary is already full, this function does nothing. void Cu6mPlayer::MyDict::add (unsigned char root, int codeword) { if (contains < dict_size) { dictionary[contains - 0x100].root = root; dictionary[contains - 0x100].codeword = codeword; contains++; } } unsigned char Cu6mPlayer::MyDict::get_root (int codeword) { return (dictionary[codeword - 0x100].root); } int Cu6mPlayer::MyDict::get_codeword (int codeword) { return (dictionary[codeword - 0x100].codeword); }