diff options
-rw-r--r-- | scumm/player_v2.cpp | 319 | ||||
-rw-r--r-- | scumm/player_v2.h | 44 |
2 files changed, 274 insertions, 89 deletions
diff --git a/scumm/player_v2.cpp b/scumm/player_v2.cpp index 79c6e4aa1e..960c236c4d 100644 --- a/scumm/player_v2.cpp +++ b/scumm/player_v2.cpp @@ -24,6 +24,17 @@ #define FREQ_HZ 236 // Don't change! +#define SPK_DECAY 0xfa00 /* Depends on sample rate */ +#define PCJR_DECAY 0xf600 /* Depends on sample rate */ + +#define FIXP_SHIFT 16 +#define MAX_OUTPUT 0x7fff + +#define NG_PRESET 0x0f35 /* noise generator preset */ +#define FB_WNOISE 0x12000 /* feedback for white noise */ +#define FB_PNOISE 0x08000 /* feedback for periodic noise */ + + const uint8 note_lengths[] = { 0, 0, 0, 2, @@ -316,65 +327,133 @@ static const uint16 pcjr_freq_table[12] = { Player_V2::Player_V2() { int i; - + // This simulates the pc speaker sound, which is driven // by the 8253 (square wave generator) and a low-band filter. + + _system = g_system; + _sample_rate = _system->property(OSystem::PROP_GET_SAMPLE_RATE, 0); + _mutex = _system->create_mutex(); - sample_rate = g_system->property(OSystem::PROP_GET_SAMPLE_RATE, 0); - ticks_per_sample = 1193000*1000 / sample_rate; - last_freq = freq = 0; - samples_left = 0; - level = 0; - decay = 0xF400; // 63455 // found by try and error -// for (i = 0; (sample_rate << i) < 30000; i++) -// decay = decay * decay / 65536; - - _mixer = g_mixer; - _mixer->setupPremix(this, premix_proc); - for (i = 0; i < 4; ++i) { - clear_channel(i); - } - - pcjr = 0; - freqs_table = spk_freq_table; + // Initialize sound queue current_nr = next_nr = 0; current_data = next_data = 0; + + // Initialize channel code + for (i = 0; i < 4; ++i) + clear_channel(i); + _next_tick = 0; + _tick_len = (_sample_rate << FIXP_SHIFT) / FREQ_HZ; + + // Initialize square generator + _level = 0; + + _RNG = NG_PRESET; + + set_pcjr(true); + set_master_volume(255); + + _mixer = g_mixer; + _mixer->setupPremix(this, premix_proc); } Player_V2::~Player_V2() { + mutex_up(); // Detach the premix callback handler _mixer->setupPremix (0, 0); + mutex_down(); + _system->delete_mutex (_mutex); } -void Player_V2::chainNextSound() { +void Player_V2::set_pcjr (bool pcjr) { + mutex_up(); + _pcjr = pcjr; + + if (_pcjr) { + _decay = PCJR_DECAY; + _update_step = (_sample_rate << FIXP_SHIFT) / (111860 * 2); + _freqs_table = pcjr_freq_table; + } else { + _decay = SPK_DECAY; + _update_step = (_sample_rate << FIXP_SHIFT) / (1193000 * 2); + _freqs_table = spk_freq_table; + } + + /* adapt _decay to sample rate. It must be squared when + * sample rate doubles. + */ int i; + for (i = 0; (_sample_rate << i) < 30000; i++) + _decay = _decay * _decay / 65536; - if (next_nr) { - for (i = 0; i < 4; i++) - clear_channel(i); - current_nr = next_nr; - current_data = next_data; - for (i = 0; i < 4; i++) { - channels[i].d.next_cmd = READ_LE_UINT16(next_data+6+2*i); + + _timer_output = 0; + for (i = 0; i < 4; i++) + _timer_count[i] = 0; + + if (current_data) + restartSound(); + mutex_down(); +} + +void Player_V2::set_master_volume (int vol) { + if (vol > 255) + vol = 255; + + /* scale to int16, FIXME: find best value */ + double out = vol * 128 / 3; + + /* build volume table (2dB per step) */ + for (int i = 0; i < 15; i++) { + /* limit volume to avoid clipping */ + if (out > 0x7fff) + _volumetable[i] = 0x7fff; + else + _volumetable[i] = (int) out; + + out /= 1.258925412; /* = 10 ^ (2/20) = 2dB */ + } + _volumetable[15] = 0; +} + +void Player_V2::chainSound(int nr, byte *data) { + int offset = _pcjr ? 14 : 6; + current_nr = nr; + current_data = data; + + for (int i = 0; i < 4; i++) { + clear_channel(i); + + channels[i].d.music_script_nr = nr; + if (data) { + channels[i].d.next_cmd = READ_LE_UINT16(data+offset+2*i); if (channels[i].d.next_cmd) channels[i].d.time_left = 1; - channels[i].d.music_script_nr = current_nr; } + } +} + +void Player_V2::chainNextSound() { + if (next_nr) { + chainSound(next_nr, next_data); next_nr = 0; next_data = 0; } } void Player_V2::stopAllSounds() { + mutex_up(); for (int i = 0; i < 4; i++) { clear_channel(i); } next_nr = current_nr = 0; next_data = current_data = 0; + mutex_down(); } void Player_V2::stopSound(int nr) { + mutex_up(); if (next_nr == nr) { next_nr = 0; next_data = 0; @@ -387,32 +466,28 @@ void Player_V2::stopSound(int nr) { current_data = 0; chainNextSound(); } + mutex_down(); } void Player_V2::startSound(int nr, byte *data) { - int cprio = current_data ? READ_LE_UINT16(current_data+4) : 0; - int prio = READ_LE_UINT16(data+4); - int nprio = next_data ? READ_LE_UINT16(next_data+4) : 0; + mutex_up(); + + int cprio = current_data ? *(current_data+4) : 0; + int prio = *(data+4); + int nprio = next_data ? *(next_data+4) : 0; - if (!current_nr || (cprio & 0xff) <= (prio & 0xff)) { + int restartable = *(data+5); + + if (!current_nr || cprio <= prio) { int tnr = current_nr; int tprio = cprio; byte *tdata = current_data; - int i; - for (i = 0; i < 4; i++) - clear_channel(i); - current_nr = nr; - current_data = data; - for (i = 0; i < 4; i++) { - channels[i].d.next_cmd = READ_LE_UINT16(data+6+2*i); - if (channels[i].d.next_cmd) - channels[i].d.time_left = 1; - channels[i].d.music_script_nr = current_nr; - } + chainSound(nr, data); nr = tnr; prio = tprio; data = tdata; + restartable = data ? *(data+5) : 0; } if (!current_nr) { @@ -422,13 +497,24 @@ void Player_V2::startSound(int nr, byte *data) { } if (nr != current_nr - && (prio & 0xff00) + && restartable && (!next_nr - || (nprio & 0xff) <= (prio & 0xff))) { + || nprio <= prio)) { next_nr = nr; next_data = data; } + + mutex_down(); +} + +void Player_V2::restartSound() { + if (*(current_data + 5)) { + /* current sound is restartable */ + chainSound(current_nr, current_data); + } else { + chainNextSound(); + } } int Player_V2::getSoundStatus(int nr) { @@ -604,11 +690,11 @@ void Player_V2::execute_cmd(ChannelInfo *channel) { note = note % 12; dest_channel->d.hull_offset = 0; dest_channel->d.hull_counter = 1; - if (pcjr && dest_channel == &channels[3]) { + if (_pcjr && dest_channel == &channels[3]) { dest_channel->d.hull_curve = 180 + note * 12; myfreq = 384 - 64 * octave; } else { - myfreq = freqs_table[note] >> octave; + myfreq = _freqs_table[note] >> octave; } dest_channel->d.freq = dest_channel->d.base_freq = myfreq; if (is_last_note) @@ -694,58 +780,137 @@ void Player_V2::next_freqs(ChannelInfo *channel) { } void Player_V2::do_mix (int16 *data, int len) { + mutex_up(); int step; do { step = len; - if (step > _next_tick) - step = _next_tick; + if (step > (_next_tick >> FIXP_SHIFT)) + step = (_next_tick >> FIXP_SHIFT); - if (freq == 0 && level == 0) - memset (data, 0, sizeof(int16) * step); + if (_pcjr) + generatePCjrSamples(data, step); else - generate_samples(data, step); + generateSpkSamples(data, step); data += step; + _next_tick -= step << FIXP_SHIFT; - if (!(_next_tick -= step)) { -// if (_timer_proc) -// (*_timer_proc) (_timer_param); - int winning_channel = -1; + if (!(_next_tick >> FIXP_SHIFT)) { for (int i = 0; i < 4; i++) { if (!channels[i].d.time_left) continue; next_freqs(&channels[i]); + } + _next_tick += _tick_len; + } + } while (len -= step); + mutex_down(); +} + +void Player_V2::lowPassFilter(int16 *sample, int len) { + for (int i = 0; i < len; i++) { + _level = ((int)_level * _decay + + (int)sample[i] * (0x10000-_decay)) >> 16; + sample[i] = _level; + } +} + +void Player_V2::squareGenerator(int channel, int freq, int vol, + int noiseFeedback, int16 *sample, int len) { + int period = _update_step * freq; + if (period == 0) + period = _update_step; + + for (int i = 0; i < len; i++) { + unsigned int duration = 0; + + if (_timer_output & (1 << channel)) + duration += _timer_count[channel]; + + _timer_count[channel] -= (1 << FIXP_SHIFT); + while (_timer_count[channel] <= 0) { - if (winning_channel == -1 - && channels[i].d.volume - && channels[i].d.time_left) { - winning_channel = i; + if (noiseFeedback) { + if (_RNG & 1) { + _RNG ^= noiseFeedback; + _timer_output ^= (1 << channel); } - } - if (winning_channel != -1) { - freq = channels[winning_channel].d.freq; + _RNG >>= 1; } else { - freq = 0; + _timer_output ^= (1 << channel); } - _next_tick = 93; + + if (_timer_output & (1 << channel)) + duration += period; + + _timer_count[channel] += period; } - } while (len -= step); + + if (_timer_output & (1 << channel)) + duration -= _timer_count[channel]; + + sample[i] += (duration * _volumetable[vol]) >> FIXP_SHIFT; + if (sample[i] < 0) { + /* overflow: clip value */ + sample[i] = 0x7fff; + } + } } -void Player_V2::generate_samples(int16 *data, int step) { - int j; - for (j = 0; j < step; j++) { - level = (level * decay) >> 16; - if (ticks_counted < freq*500) - level += 0xffff - decay; +void Player_V2::generateSpkSamples(int16 *data, int len) { + int winning_channel = -1; + int freq; + for (int i = 0; i < 4; i++) { + if (winning_channel == -1 + && channels[i].d.volume + && channels[i].d.time_left) { + winning_channel = i; + } + } + + memset (data, 0, sizeof(int16) * len); + if (winning_channel != -1) { + squareGenerator(0, channels[winning_channel].d.freq, 0, + 0, data, len); + } else if (_level == 0) + /* shortcut: no sound is being played. */ + return; + + lowPassFilter(data, len); +} - data[j] = (level >> 1); - - ticks_counted += ticks_per_sample; - if (ticks_counted >= last_freq*1000) { - ticks_counted -= last_freq*1000; - last_freq = freq; +void Player_V2::generatePCjrSamples(int16 *data, int len) { + int i; + int freq, vol; + + memset(data, 0, sizeof(int16) * len); + bool hasdata = false; + + for (i = 0; i < 4; i++) { + freq = channels[i].d.freq >> 6; + vol = (65535 - channels[i].d.volume) >> 12; + if (!channels[i].d.volume || !channels[i].d.time_left) { + _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); + } else { + int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE; + int n = (freq & 3); + + freq = (n == 3) ? 2 * (channels[2].d.freq>>6) : 1 << (5 + n); + hasdata = true; + squareGenerator(i, freq, vol, noiseFB, data, len); } +#if 0 + debug(9, "channel[%d]: freq %d %.1f ; volume %d", + i, freq, 111860.0/freq, vol); +#endif } + + if (_level || hasdata) + lowPassFilter(data, len); } diff --git a/scumm/player_v2.h b/scumm/player_v2.h index 9b36e9ea82..19558ba743 100644 --- a/scumm/player_v2.h +++ b/scumm/player_v2.h @@ -62,6 +62,9 @@ public: Player_V2(); ~Player_V2(); + void set_pcjr(bool pcjr); + void set_master_volume(int vol); + void startSound(int nr, byte *data); void stopSound(int nr); void stopAllSounds(); @@ -69,18 +72,23 @@ public: private: SoundMixer *_mixer; + + + bool _pcjr; + + int _sample_rate; int _next_tick; + int _tick_len; + unsigned int _update_step; + unsigned int _decay; + unsigned int _level; + unsigned int _RNG; + unsigned int _volumetable[16]; - int sample_rate; - int ticks_per_sample; - int ticks_counted; - int samples_left; - int freq; - int last_freq; - int level; - int pcjr; - const uint16 *freqs_table; - unsigned int decay; + int _timer_count[4]; + int _timer_output; + + const uint16 *_freqs_table; ChannelInfo channels[4]; @@ -90,15 +98,27 @@ private: byte *next_data; byte *retaddr; + OSystem *_system; + void *_mutex; + void mutex_up() { _system->lock_mutex (_mutex); } + void mutex_down() { _system->unlock_mutex (_mutex); } + + void restartSound(); void execute_cmd(ChannelInfo *channel); void next_freqs(ChannelInfo *channel); - void generate_samples(int16 *buf, int len); - void on_timer(); void clear_channel(int i); + void chainSound(int nr, byte *data); void chainNextSound(); static void premix_proc(void *param, int16 *buf, uint len); void do_mix (int16 *buf, int len); + + void lowPassFilter(int16 *data, int len); + void squareGenerator(int channel, int freq, int vol, + int noiseFeedback, int16 *sample, int len); + void generateSpkSamples(int16 *data, int len); + void generatePCjrSamples(int16 *data, int len); + }; #endif |