mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-29 12:50:28 +00:00
Makes a first attempt at implementing the SN76489.
This commit is contained in:
parent
0ad2676640
commit
d4df101ab6
@ -108,6 +108,7 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
}
|
||||
|
||||
evaluate_output_volume();
|
||||
printf("%d ", output_volume_);
|
||||
|
||||
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
|
||||
target[c] = output_volume_;
|
||||
|
@ -8,9 +8,128 @@
|
||||
|
||||
#include "SN76489.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace TI;
|
||||
|
||||
SN76489::SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
|
||||
|
||||
void SN76489::write(uint8_t value) {
|
||||
SN76489::SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
|
||||
// Build a volume table.
|
||||
double multiplier = pow(10.0, -0.1);
|
||||
double volume = 8191.0f;
|
||||
for(int c = 0; c < 16; ++c) {
|
||||
volumes_[c] = (int)round(volume);
|
||||
volume *= multiplier;
|
||||
}
|
||||
volumes_[15] = 0;
|
||||
evaluate_output_volume();
|
||||
}
|
||||
|
||||
void SN76489::set_register(uint8_t value) {
|
||||
task_queue_.defer([value, this] () {
|
||||
if(value & 0x80) {
|
||||
active_register_ = value;
|
||||
}
|
||||
|
||||
const int channel = (active_register_ >> 5)&3;
|
||||
if(active_register_ & 0x10) {
|
||||
// latch for volume
|
||||
channels_[channel].volume = value & 0xf;
|
||||
evaluate_output_volume();
|
||||
} else {
|
||||
// latch for tone/data
|
||||
if(channel < 3) {
|
||||
if(value & 0x80) {
|
||||
channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf);
|
||||
} else {
|
||||
channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
|
||||
}
|
||||
} else {
|
||||
// writes to the noise register always reset the shifter
|
||||
noise_shifter_ = shifter_is_16bit_ ? 0x8000 : 0x4000;
|
||||
|
||||
if(value & 4) {
|
||||
noise_mode_ = shifter_is_16bit_ ? Noise16 : Noise15;
|
||||
} else {
|
||||
noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15;
|
||||
}
|
||||
|
||||
channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3));
|
||||
// Special case: if these bits are both set, the noise channel should track channel 2,
|
||||
// which is marked with a divider of 0xffff.
|
||||
if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SN76489::evaluate_output_volume() {
|
||||
output_volume_ = static_cast<int16_t>(
|
||||
channels_[0].level * volumes_[channels_[0].volume] +
|
||||
channels_[1].level * volumes_[channels_[1].volume] +
|
||||
channels_[2].level * volumes_[channels_[2].volume] +
|
||||
channels_[3].level * volumes_[channels_[3].volume]
|
||||
);
|
||||
}
|
||||
|
||||
void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
// For now: assume a divide by eight.
|
||||
|
||||
std::size_t c = 0;
|
||||
while((master_divider_&7) && c < number_of_samples) {
|
||||
target[c] = output_volume_;
|
||||
master_divider_++;
|
||||
c++;
|
||||
}
|
||||
|
||||
while(c < number_of_samples) {
|
||||
bool did_flip = false;
|
||||
|
||||
#define step_channel(c) \
|
||||
if(channels_[c].counter) channels_[c].counter--;\
|
||||
else {\
|
||||
channels_[c].level ^= 1;\
|
||||
channels_[c].counter = channels_[c].divider;\
|
||||
did_flip = true;\
|
||||
}
|
||||
|
||||
step_channel(0);
|
||||
step_channel(1);
|
||||
step_channel(2);
|
||||
|
||||
#undef step_channel
|
||||
|
||||
if(channels_[c].divider != 0xffff) {
|
||||
if(channels_[3].counter) channels_[3].counter--;
|
||||
else {
|
||||
did_flip = true;
|
||||
channels_[c].counter = channels_[c].divider;
|
||||
}
|
||||
}
|
||||
|
||||
if(did_flip) {
|
||||
channels_[3].level = noise_shifter_ & 1;
|
||||
int new_bit = channels_[3].level;
|
||||
switch(noise_mode_) {
|
||||
default: break;
|
||||
case Noise15:
|
||||
new_bit ^= (noise_shifter_ >> 1);
|
||||
break;
|
||||
case Noise16:
|
||||
new_bit ^= (noise_shifter_ >> 3);
|
||||
break;
|
||||
}
|
||||
noise_shifter_ >>= 1;
|
||||
noise_shifter_ |= (new_bit & 1) << (shifter_is_16bit_ ? 15 : 14);
|
||||
}
|
||||
|
||||
evaluate_output_volume();
|
||||
|
||||
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
|
||||
target[c] = output_volume_;
|
||||
c++;
|
||||
master_divider_++;
|
||||
}
|
||||
}
|
||||
|
||||
master_divider_ &= 7;
|
||||
}
|
||||
|
@ -20,10 +20,38 @@ class SN76489: public Outputs::Speaker::SampleSource {
|
||||
SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||
|
||||
/// Writes a new value to the SN76489.
|
||||
void write(uint8_t value);
|
||||
void set_register(uint8_t value);
|
||||
|
||||
// As per SampleSource.
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
|
||||
private:
|
||||
std::size_t master_divider_ = 0;
|
||||
int16_t output_volume_ = 0;;
|
||||
void evaluate_output_volume();
|
||||
int volumes_[16];
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
|
||||
struct ToneChannel {
|
||||
// Programmatically-set state; updated by the processor.
|
||||
uint16_t divider = 0;
|
||||
uint8_t volume = 0xf;
|
||||
|
||||
// Active state; self-evolving as a function of time.
|
||||
uint16_t counter = 0;
|
||||
int level = 0;
|
||||
} channels_[4];
|
||||
enum {
|
||||
Periodic15,
|
||||
Periodic16,
|
||||
Noise15,
|
||||
Noise16
|
||||
} noise_mode_ = Periodic15;
|
||||
uint16_t noise_shifter_ = 0;
|
||||
int active_register_ = 0;
|
||||
|
||||
bool shifter_is_16bit_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class Joystick: public Inputs::Joystick {
|
||||
case '6': mask = 0xe; break;
|
||||
default: break;
|
||||
}
|
||||
keypad_ = (keypad_ & 0xf0) | (mask ^ 0xf);
|
||||
keypad_ = (keypad_ & 0xf0) | mask;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -109,6 +109,7 @@ class ConcreteMachine:
|
||||
z80_(*this),
|
||||
sn76489_(audio_queue_),
|
||||
speaker_(sn76489_) {
|
||||
speaker_.set_input_rate(3579545.0f); // TODO: try to find out whether this is correct.
|
||||
set_clock_rate(3579545);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
@ -132,7 +133,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return nullptr;
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
@ -171,10 +172,15 @@ class ConcreteMachine:
|
||||
|
||||
// MARK: Z80::BusHandler
|
||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
time_since_vdp_update_ += cycle.length;
|
||||
time_since_sn76489_update_ += cycle.length;
|
||||
|
||||
if(time_until_interrupt_ > 0) {
|
||||
time_until_interrupt_ -= cycle.length;
|
||||
if(time_until_interrupt_ <= HalfCycles(0)) {
|
||||
z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_);
|
||||
update_video();
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,8 +192,8 @@ class ConcreteMachine:
|
||||
*cycle.value = bios_[address];
|
||||
} else if(address >= 0x6000 && address < 0x8000) {
|
||||
*cycle.value = ram_[address & 1023];
|
||||
} else if(address >= 0x8000 && address < 0x8000 + cartridge_.size()) {
|
||||
*cycle.value = cartridge_[address - 0x8000];
|
||||
} else if(address >= 0x8000) {
|
||||
*cycle.value = cartridge_[(address - 0x8000) % cartridge_.size()]; // This probably isn't how 24kb ROMs work?
|
||||
} else {
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
@ -202,7 +208,7 @@ class ConcreteMachine:
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
switch((address >> 5) & 7) {
|
||||
case 5:
|
||||
vdp_->run_for(time_since_vdp_update_.flush());
|
||||
update_video();
|
||||
*cycle.value = vdp_->get_register(address);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
@ -232,7 +238,7 @@ class ConcreteMachine:
|
||||
break;
|
||||
|
||||
case 5:
|
||||
vdp_->run_for(time_since_vdp_update_.flush());
|
||||
update_video();
|
||||
vdp_->set_register(address, *cycle.value);
|
||||
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
@ -240,7 +246,7 @@ class ConcreteMachine:
|
||||
|
||||
case 7:
|
||||
update_audio();
|
||||
sn76489_.write(*cycle.value);
|
||||
sn76489_.set_register(*cycle.value);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
@ -250,8 +256,6 @@ class ConcreteMachine:
|
||||
default: break;
|
||||
}
|
||||
|
||||
time_since_vdp_update_ += cycle.length;
|
||||
time_since_sn76489_update_ += cycle.length;
|
||||
return HalfCycles(0);
|
||||
}
|
||||
|
||||
@ -265,6 +269,9 @@ class ConcreteMachine:
|
||||
void update_audio() {
|
||||
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(2)));
|
||||
}
|
||||
void update_video() {
|
||||
vdp_->run_for(time_since_vdp_update_.flush());
|
||||
}
|
||||
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
std::unique_ptr<TI::TMS9918> vdp_;
|
||||
|
Loading…
Reference in New Issue
Block a user