Made thread-safe.

PCjr support.
Rewrote the generator, so I can reuse more code between PCjr and speaker output.
Added function to set master volume and to switch between PCjr and speaker.

svn-id: r8185
This commit is contained in:
Jochen Hoenicke 2003-05-31 16:54:46 +00:00
parent 7497828734
commit 3290d618d6
2 changed files with 279 additions and 94 deletions

View File

@ -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;
// 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);
for (i = 0; i < 4; ++i) {
clear_channel(i);
}
pcjr = 0;
freqs_table = spk_freq_table;
current_nr = next_nr = 0;
current_data = next_data = 0;
_next_tick = 0;
}
Player_V2::~Player_V2() {
mutex_up();
// Detach the premix callback handler
_mixer->setupPremix (0, 0);
mutex_down();
_system->delete_mutex (_mutex);
}
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;
_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;
}
}
}
void Player_V2::chainNextSound() {
int i;
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);
if (channels[i].d.next_cmd)
channels[i].d.time_left = 1;
channels[i].d.music_script_nr = current_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();
if (!current_nr || (cprio & 0xff) <= (prio & 0xff)) {
int cprio = current_data ? *(current_data+4) : 0;
int prio = *(data+4);
int nprio = next_data ? *(next_data+4) : 0;
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]);
if (winning_channel == -1
&& channels[i].d.volume
&& channels[i].d.time_left) {
winning_channel = i;
}
}
if (winning_channel != -1) {
freq = channels[winning_channel].d.freq;
} else {
freq = 0;
}
_next_tick = 93;
_next_tick += _tick_len;
}
} while (len -= step);
mutex_down();
}
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::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;
}
}
data[j] = (level >> 1);
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;
ticks_counted += ticks_per_sample;
if (ticks_counted >= last_freq*1000) {
ticks_counted -= last_freq*1000;
last_freq = freq;
if (_timer_output & (1 << channel))
duration += _timer_count[channel];
_timer_count[channel] -= (1 << FIXP_SHIFT);
while (_timer_count[channel] <= 0) {
if (noiseFeedback) {
if (_RNG & 1) {
_RNG ^= noiseFeedback;
_timer_output ^= (1 << channel);
}
_RNG >>= 1;
} else {
_timer_output ^= (1 << channel);
}
if (_timer_output & (1 << channel))
duration += period;
_timer_count[channel] += period;
}
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::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);
}
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);
}

View File

@ -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;
int _next_tick;
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;
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 _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