1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-12 00:30:31 +00:00

Sought both to [start to] optimise the AY and correct divider reloads. It turns out that conditionals aren't that troubling. But I can probably eliminate the counters.

This commit is contained in:
Thomas Harte 2016-10-21 20:05:38 -04:00
parent 09687a2e2f
commit 782ef960e1
2 changed files with 66 additions and 56 deletions

View File

@ -82,53 +82,58 @@ void AY38910::set_clock_rate(double clock_rate)
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
{
for(int c = 0; c < number_of_samples; c++)
int offset = _master_divider;
int c = _master_divider;
_master_divider += number_of_samples;
for(; c < 16 && c < _master_divider; c++) target[c - offset] = _output_volume;
while(c < _master_divider)
{
// a master divider divides the clock by 16;
// resulting_steps will be 1 if a tick occurred, 0 otherwise
int former_master_divider = _master_divider;
_master_divider++;
int resulting_steps = ((_master_divider ^ former_master_divider) >> 4) & 1;
// Bluffer's guide to the stuff below: I wanted to avoid branches. If I avoid branches then
// I avoid stalls.
//
// Repeating patterns are:
// (1) decrement, then shift a high-order bit right and mask to get 1 for did underflow, 0 otherwise;
// (2) did_underflow * a + (did_underflow ^ 1) * b to pick between reloading and not reloading
int did_underflow;
#define shift(x, r, steps) \
x -= steps; \
did_underflow = (x >> 16)&1; \
x = did_underflow * r + (did_underflow^1) * x;
#define step_channel(c) \
shift(_channel_dividers[c], _tone_generator_controls[c], resulting_steps); \
_channel_output[c] ^= did_underflow;
if(_channel_dividers[c]) _channel_dividers[c] --; \
else { _channel_dividers[c] = _tone_generator_controls[c]; _channel_output[c] ^= 1; }
// update the tone channels
step_channel(0);
step_channel(1);
step_channel(2);
#undef step_channel
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
// it into the official 17 upon divider underflow.
shift(_noise_divider, _output_registers[6]&0x1f, resulting_steps);
_noise_output ^= did_underflow&_noise_shift_register&1;
if(_noise_divider) _noise_divider--;
else
{
_noise_divider = _output_registers[6]&0x1f;
_noise_output ^= _noise_shift_register&1;
_noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17;
_noise_shift_register >>= did_underflow;
_noise_shift_register >>= 1;
}
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of
// implementing non-repeating patterns by locking them to table position 0x1f.
// int envelope_divider = ((_master_divider ^ former_master_divider) >> 8) & 1;
shift(_envelope_divider, _envelope_period, resulting_steps);
_envelope_position += did_underflow;
int refill = _envelope_overflow_masks[_output_registers[13]] * (_envelope_position >> 5);
_envelope_position = (_envelope_position & 0x1f) | refill;
int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position];
if(_envelope_divider) _envelope_divider--;
else
{
_envelope_divider = _envelope_period;
_envelope_position ++;
if(_envelope_position == 32) _envelope_position = _envelope_overflow_masks[_output_registers[13]];
}
#undef step_channel
#undef shift
evaluate_output_volume();
for(int ic = 0; ic < 16 && c < _master_divider; ic++)
{
target[c - offset] = _output_volume;
c++;
}
}
}
void AY38910::evaluate_output_volume()
{
int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position];
// The output level for a channel is:
// 1 if neither tone nor noise is enabled;
@ -155,13 +160,12 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
};
#undef channel_volume
// Mix additively. TODO: non-linear volume.
target[c] = (int16_t)(
// Mix additively.
_output_volume = (int16_t)(
_volumes[volumes[0]] * channel_levels[0] +
_volumes[volumes[1]] * channel_levels[1] +
_volumes[volumes[2]] * channel_levels[2]
);
}
}
void AY38910::skip_samples(unsigned int number_of_samples)
@ -188,21 +192,23 @@ void AY38910::set_register_value(uint8_t value)
case 0: case 2: case 4:
_tone_generator_controls[selected_register >> 1] =
(_tone_generator_controls[selected_register >> 1] & ~0xff) | value;
_channel_dividers[selected_register >> 1] = _tone_generator_controls[selected_register >> 1];
break;
case 1: case 3: case 5:
_tone_generator_controls[selected_register >> 1] =
(_tone_generator_controls[selected_register >> 1] & 0xff) | (uint16_t)((value&0xf) << 8);
_channel_dividers[selected_register >> 1] = _tone_generator_controls[selected_register >> 1];
break;
case 11:
_envelope_period = (_envelope_period & ~0xff) | value;
// printf("e: %d", _envelope_period);
_envelope_divider = _envelope_period;
break;
case 12:
_envelope_period = (_envelope_period & 0xff) | (int)(value << 8);
// printf("e: %d", _envelope_period);
_envelope_divider = _envelope_period;
break;
case 13:
@ -211,6 +217,7 @@ void AY38910::set_register_value(uint8_t value)
break;
}
_output_registers[selected_register] = masked_value;
evaluate_output_volume();
});
}
}

View File

@ -86,6 +86,9 @@ class AY38910: public ::Outputs::Filter<AY38910> {
uint8_t get_register_value();
uint8_t _data_input, _data_output;
int16_t _output_volume;
void evaluate_output_volume();
};
};