/* ScummVM - Scumm Interpreter * Copyright (C) 2001 Ludvig Strigeus * Copyright (C) 2001-2004 The ScummVM project * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ * */ #include "stdafx.h" #include "base/engine.h" #include "scumm/player_v1.h" #include "scumm/scumm.h" namespace Scumm { #define FB_WNOISE 0x12000 /* feedback for white noise */ #define FB_PNOISE 0x08000 /* feedback for periodic noise */ #define TIMER_BASE_FREQ 1193000 #define FIXP_SHIFT 16 Player_V1::Player_V1(ScummEngine *scumm, bool pcjr) : Player_V2(scumm, pcjr) { // Initialize channel code for (int i = 0; i < 4; ++i) clear_channel(i); _mplex_step = (_sample_rate << FIXP_SHIFT) / 1193000; _next_chunk = _repeat_chunk = 0; _forced_level = 0; } Player_V1::~Player_V1() { } void Player_V1::chainSound(int nr, byte *data) { uint i; for (i = 0; i < 4; ++i) clear_channel(i); _current_nr = nr; _current_data = data; _repeat_chunk = _next_chunk = data + (_pcjr ? 2 : 4); debug(4, "Chaining new sound %d", nr); if (_pcjr) parsePCjrChunk(); else parseSpeakerChunk(); } void Player_V1::startSound(int nr) { byte *data = _scumm->getResourceAddress(rtSound, nr); assert(data); mutex_up(); int offset = _pcjr ? READ_LE_UINT16(data+4) : 6; int cprio = _current_data ? *(_current_data) & 0x7f : 0; int prio = *(data + offset) & 0x7f; int restartable = *(data + offset) & 0x80; debug(4, "startSound %d: prio %d%s, cprio %d", nr, prio, restartable ? " restartable" : "", cprio); if (!_current_nr || cprio <= prio) { if (_current_data && (*(_current_data) & 0x80)) { _next_nr = _current_nr; _next_data = _current_data; } chainSound(nr, data + offset); } mutex_down(); } void Player_V1::stopAllSounds() { mutex_up(); for (int i = 0; i < 4; i++) clear_channel(i); _repeat_chunk = _next_chunk = 0; _next_nr = _current_nr = 0; _next_data = _current_data = 0; mutex_down(); } void Player_V1::stopSound(int nr) { mutex_up(); if (_next_nr == nr) { _next_nr = 0; _next_data = 0; } if (_current_nr == nr) { for (int i = 0; i < 4; i++) { clear_channel(i); } _repeat_chunk = _next_chunk = 0; _current_nr = 0; _current_data = 0; chainNextSound(); } mutex_down(); } void Player_V1::clear_channel(int i) { _channels[i].freq = 0; _channels[i].volume = 15; } int Player_V1::getMusicTimer() const { /* Do V1 games have a music timer? */ return 0; } void Player_V1::parseSpeakerChunk() { set_mplex(3000); _forced_level = 0; parse_again: _chunk_type = READ_LE_UINT16(_next_chunk); #ifndef __PALM_OS__ debug(6, "parseSpeakerChunk: sound %d, offset %4x, chunk %x", _current_nr, _next_chunk - _current_data, _chunk_type); #endif _next_chunk += 2; switch (_chunk_type) { case 0xffff: _current_nr = 0; _current_data = 0; _channels[0].freq = 0; _next_chunk = 0; chainNextSound(); break; case 0xfffe: _repeat_chunk = _next_chunk; goto parse_again; case 0xfffd: _next_chunk = _repeat_chunk; goto parse_again; case 0xfffc: /* handle reset. We don't need this do we? */ goto parse_again; case 0: _time_left = 1; set_mplex(READ_LE_UINT16(_next_chunk)); _next_chunk += 2; break; case 1: set_mplex(READ_LE_UINT16(_next_chunk)); _start = READ_LE_UINT16(_next_chunk + 2); _end = READ_LE_UINT16(_next_chunk + 4); _delta = (int16) READ_LE_UINT16(_next_chunk + 6); _repeat_ctr = READ_LE_UINT16(_next_chunk + 8); _channels[0].freq = _start; _next_chunk += 10; #ifndef __PALM_OS__ debug(6, "chunk 1: mplex %d, freq %d -> %d step %d x %d", _mplex, _start, _end, _delta, _repeat_ctr); #endif break; case 2: _start = READ_LE_UINT16(_next_chunk); _end = READ_LE_UINT16(_next_chunk + 2); _delta = (int16) READ_LE_UINT16(_next_chunk + 4); _channels[0].freq = 0; _next_chunk += 6; _forced_level = -1; #ifndef __PALM_OS__ debug(6, "chunk 2: %d -> %d step %d", _start, _end, _delta); #endif break; case 3: _start = READ_LE_UINT16(_next_chunk); _end = READ_LE_UINT16(_next_chunk + 2); _delta = (int16) READ_LE_UINT16(_next_chunk + 4); _channels[0].freq = 0; _next_chunk += 6; _forced_level = -1; #ifndef __PALM_OS__ debug(6, "chunk 3: %d -> %d step %d", _start, _end, _delta); #endif break; } } void Player_V1::nextSpeakerCmd() { uint16 lsr; switch (_chunk_type) { case 0: if (--_time_left) return; _time_left = READ_LE_UINT16(_next_chunk); _next_chunk += 2; if (_time_left == 0xfffb) { /* handle 0xfffb?? */ _time_left = READ_LE_UINT16(_next_chunk); _next_chunk += 2; } #ifndef __PALM_OS__ debug(7, "nextSpeakerCmd: chunk %d, offset %4x: notelen %d", _chunk_type, _next_chunk - 2 - _current_data, _time_left); #endif if (_time_left == 0) { parseSpeakerChunk(); } else { _channels[0].freq = READ_LE_UINT16(_next_chunk); _next_chunk += 2; #ifndef __PALM_OS__ debug(7, "freq_current: %d", _channels[0].freq); #endif } break; case 1: _channels[0].freq = (_channels[0].freq + _delta) & 0xffff; if (_channels[0].freq == _end) { if (!--_repeat_ctr) { parseSpeakerChunk(); return; } _channels[0].freq = _start; } break; case 2: _start = (_start + _delta) & 0xffff; if (_start == _end) { parseSpeakerChunk(); return; } set_mplex(_start); _forced_level = -_forced_level; break; case 3: _start = (_start + _delta) & 0xffff; if (_start == _end) { parseSpeakerChunk(); return; } lsr = _random_lsr + 0x9248; lsr = (lsr >> 3) | (lsr << 13); _random_lsr = lsr; set_mplex((_start & lsr) | 0x180); _forced_level = -_forced_level; break; } } void Player_V1::parsePCjrChunk() { uint tmp; uint i; set_mplex(3000); _forced_level = 0; parse_again: _chunk_type = READ_LE_UINT16(_next_chunk); #ifndef __PALM_OS__ debug(6, "parsePCjrChunk: sound %d, offset %4x, chunk %x", _current_nr, _next_chunk - _current_data, _chunk_type); #endif _next_chunk += 2; switch (_chunk_type) { case 0xffff: for (i = 0; i < 4; ++i) clear_channel(i); _current_nr = 0; _current_data = 0; _repeat_chunk = _next_chunk = 0; chainNextSound(); break; case 0xfffe: _repeat_chunk = _next_chunk; goto parse_again; case 0xfffd: _next_chunk = _repeat_chunk; goto parse_again; case 0xfffc: /* handle reset. We don't need this do we? */ goto parse_again; case 0: set_mplex(READ_LE_UINT16(_next_chunk)); _next_chunk += 2; for (i = 0; i < 4; i++) { tmp = READ_LE_UINT16(_next_chunk); _next_chunk += 2; if (tmp == 0xffff) { _channels[i].cmd_ptr = 0; continue; } _channels[i].attack = READ_LE_UINT16(_current_data + tmp); _channels[i].decay = READ_LE_UINT16(_current_data + tmp + 2); _channels[i].level = READ_LE_UINT16(_current_data + tmp + 4); _channels[i].sustain_1 = READ_LE_UINT16(_current_data + tmp + 6); _channels[i].sustain_2 = READ_LE_UINT16(_current_data + tmp + 8); _channels[i].notelen = 1; _channels[i].volume = 15; _channels[i].cmd_ptr = _current_data + tmp + 10; } break; case 1: set_mplex(READ_LE_UINT16(_next_chunk)); tmp = READ_LE_UINT16(_next_chunk + 2); _channels[0].cmd_ptr = tmp != 0xffff ? _current_data + tmp : NULL; tmp = READ_LE_UINT16(_next_chunk + 4); _start = READ_LE_UINT16(_next_chunk + 6); _delta = (int16) READ_LE_UINT16(_next_chunk + 8); _time_left = READ_LE_UINT16(_next_chunk + 10); _next_chunk += 12; if (tmp >= 0xe0) { _channels[3].freq = tmp & 0xf; _value_ptr = &_channels[3].volume; } else { assert(!(tmp & 0x10)); tmp = (tmp & 0x60) >> 5; _value_ptr = &_channels[tmp].freq; _channels[tmp].volume = 0; } *_value_ptr = _start; if (_channels[0].cmd_ptr) { tmp = READ_LE_UINT16(_channels[0].cmd_ptr); _start_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 2); _delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 4); _time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 6); _channels[0].cmd_ptr += 8; if (_value_ptr == &_channels[3].volume) { tmp = (tmp & 0x70) >> 4; if (tmp & 1) _value_ptr_2 = &_channels[tmp >> 1].volume; else _value_ptr_2 = &_channels[tmp >> 1].freq; } else { assert(!(tmp & 0x10)); tmp = (tmp & 0x60) >> 5; _value_ptr_2 = &_channels[tmp].freq; _channels[tmp].volume = 0; } *_value_ptr_2 = _start_2; } #ifndef __PALM_OS__ debug(6, "chunk 1: %d: %d step %d for %d, %d: %d step %d for %d", _value_ptr - (uint*)_channels, _start, _delta, _time_left, _value_ptr_2 - (uint*)_channels, _start_2, _delta_2, _time_left_2); #endif break; case 2: _start = READ_LE_UINT16(_next_chunk); _end = READ_LE_UINT16(_next_chunk + 2); _delta = (int16) READ_LE_UINT16(_next_chunk + 4); _channels[0].freq = 0; _next_chunk += 6; _forced_level = -1; #ifndef __PALM_OS__ debug(6, "chunk 2: %d -> %d step %d", _start, _end, _delta); #endif break; case 3: set_mplex(READ_LE_UINT16(_next_chunk)); tmp = READ_LE_UINT16(_next_chunk + 2); assert((tmp & 0xf0) == 0xe0); _channels[3].freq = tmp & 0xf; if ((tmp & 3) == 3) { _next_chunk += 2; _channels[2].freq = READ_LE_UINT16(_next_chunk + 2); } _channels[3].volume = READ_LE_UINT16(_next_chunk + 4); _repeat_ctr = READ_LE_UINT16(_next_chunk + 6); _delta = (int16) READ_LE_UINT16(_next_chunk + 8); _next_chunk += 10; break; } } void Player_V1::nextPCjrCmd() { uint i; int dummy; switch (_chunk_type) { case 0: for (i = 0; i < 4; i++) { if (!_channels[i].cmd_ptr) continue; if (!--_channels[i].notelen) { dummy = READ_LE_UINT16(_channels[i].cmd_ptr); if (dummy >= 0xfffe) { if (dummy == 0xfffe) _next_chunk = _current_data + 2; parsePCjrChunk(); return; } _channels[i].notelen = 4 * dummy; dummy = READ_LE_UINT16(_channels[i].cmd_ptr + 2); if (dummy == 0) { _channels[i].hull_counter = 4; _channels[i].sustctr = _channels[i].sustain_2; } else { _channels[i].hull_counter = 1; _channels[i].freq = dummy; } #ifndef __PALM_OS__ debug(7, "chunk 0: channel %d play %d for %d", i, dummy, _channels[i].notelen); #endif _channels[i].cmd_ptr += 4; } switch (_channels[i].hull_counter) { case 1: _channels[i].volume -= _channels[i].attack; if ((int) _channels[i].volume <= 0) { _channels[i].volume = 0; _channels[i].hull_counter++; } break; case 2: _channels[i].volume += _channels[i].decay; if (_channels[i].volume >= _channels[i].level) { _channels[i].volume = _channels[i].level; _channels[i].hull_counter++; } break; case 4: if (--_channels[i].sustctr < 0) { _channels[i].sustctr = _channels[i].sustain_2; _channels[i].volume += _channels[i].sustain_1; if ((int) _channels[i].volume >= 15) { _channels[i].volume = 15; _channels[i].hull_counter++; } } break; } } break; case 1: _start += _delta; *_value_ptr = _start; if (!--_time_left) { _start = READ_LE_UINT16(_next_chunk); _next_chunk += 2; if (_start == 0xffff) { parsePCjrChunk(); return; } _delta = (int16) READ_LE_UINT16(_next_chunk); _time_left = READ_LE_UINT16(_next_chunk + 2); _next_chunk += 4; *_value_ptr = _start; } if (_channels[0].cmd_ptr) { _start_2 += _delta_2; *_value_ptr_2 = _start_2; if (!--_time_left_2) { _start_2 = READ_LE_UINT16(_channels[0].cmd_ptr); if (_start_2 == 0xffff) { _next_chunk = _channels[0].cmd_ptr + 2; parsePCjrChunk(); return; } _delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 2); _time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 4); _channels[0].cmd_ptr += 6; } } break; case 2: _start += _delta; if (_start == _end) { parsePCjrChunk(); return; } set_mplex(_start); #ifndef __PALM_OS__ debug(7, "chunk 2: mplex %d curve %d", _start, _forced_level); #endif _forced_level = -_forced_level; break; case 3: dummy = _channels[3].volume + _delta; if (dummy >= 15) { _channels[3].volume = 15; } else if (dummy <= 0) { _channels[3].volume = 0; } else { _channels[3].volume = dummy; break; } if (!--_repeat_ctr) { parsePCjrChunk(); return; } _delta = READ_LE_UINT16(_next_chunk); _next_chunk += 2; break; } } void Player_V1::set_mplex(uint mplex) { if (mplex == 0) mplex = 65536; _mplex = mplex; _tick_len = _mplex_step * mplex; } void Player_V1::nextTick() { if (_next_chunk) { if (_pcjr) nextPCjrCmd(); else nextSpeakerCmd(); } } void Player_V1::generateSpkSamples(int16 *data, uint len) { uint i; memset(data, 0, 2 * sizeof(int16) * len); if (_channels[0].freq == 0) { if (_forced_level) { int sample = _forced_level * _volumetable[0]; for (i = 0; i < len; i++) data[2*i] = data[2*i+1] = sample; #ifndef __PALM_OS__ debug(9, "speaker: %8x: forced one", _tick_len); #endif } else if (!_level) { return; } } else { squareGenerator(0, _channels[0].freq, 0, 0, data, len); #ifndef __PALM_OS__ debug(9, "speaker: %8x: freq %d %.1f", _tick_len, _channels[0].freq, 1193000.0 / _channels[0].freq); #endif } lowPassFilter(data, len); } void Player_V1::generatePCjrSamples(int16 *data, uint len) { uint i, j; uint freq, vol; bool hasdata = false; memset(data, 0, 2 * sizeof(int16) * len); if (_forced_level) { int sample = _forced_level * _volumetable[0]; for (i = 0; i < len; i++) data[2*i] = data[2*i+1] = sample; hasdata = true; #ifndef __PALM_OS__ debug(9, "channel[4]: %8x: forced one", _tick_len); #endif } for (i = 1; i < 3; i++) { freq = _channels[i].freq; if (freq) { for (j = 0; j < i; j++) { if (freq == _channels[j].freq) { /* HACK: this channel is playing at * the same frequency as another. * Synchronize it to the same phase to * prevent interference. */ _timer_count[i] = _timer_count[j]; _timer_output ^= (1 << i) & (_timer_output ^ _timer_output << (i - j)); } } } } for (i = 0; i < 4; i++) { freq = _channels[i].freq; vol = _channels[i].volume; if (!_volumetable[_channels[i].volume]) { _timer_count[i] -= len << FIXP_SHIFT; if (_timer_count[i] < 0) _timer_count[i] = 0; } else if (i < 3) { hasdata = true; squareGenerator(i, freq, vol, 0, data, len); #ifndef __PALM_OS__ debug(9, "channel[%d]: %8x: freq %d %.1f ; volume %d", i, _tick_len, freq, 111860.0 / freq, vol); #endif } else { int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE; int n = (freq & 3); freq = (n == 3) ? 2 * (_channels[2].freq) : 1 << (5 + n); hasdata = true; squareGenerator(i, freq, vol, noiseFB, data, len); #ifndef __PALM_OS__ debug(9, "channel[%d]: %x: noise freq %d %.1f ; volume %d", i, _tick_len, freq, 111860.0 / freq, vol); #endif } } if (_level || hasdata) lowPassFilter(data, len); } } // End of namespace Scumm