2018-02-27 03:04:34 +00:00
|
|
|
//
|
|
|
|
// SN76489.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 26/02/2018.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2018 Thomas Harte. All rights reserved.
|
2018-02-27 03:04:34 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#include "SN76489.hpp"
|
|
|
|
|
2018-03-01 23:51:05 +00:00
|
|
|
#include <cassert>
|
2018-02-28 03:25:12 +00:00
|
|
|
#include <cmath>
|
|
|
|
|
2018-02-27 03:04:34 +00:00
|
|
|
using namespace TI;
|
|
|
|
|
2022-07-16 18:41:04 +00:00
|
|
|
SN76489::SN76489(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue, int additional_divider) : task_queue_(task_queue) {
|
2018-03-09 18:23:18 +00:00
|
|
|
set_sample_volume_range(0);
|
2018-03-01 03:36:03 +00:00
|
|
|
|
|
|
|
switch(personality) {
|
|
|
|
case Personality::SN76494:
|
|
|
|
master_divider_period_ = 2;
|
|
|
|
shifter_is_16bit_ = false;
|
|
|
|
break;
|
|
|
|
case Personality::SN76489:
|
|
|
|
master_divider_period_ = 16;
|
|
|
|
shifter_is_16bit_ = false;
|
|
|
|
break;
|
|
|
|
case Personality::SMS:
|
|
|
|
master_divider_period_ = 16;
|
|
|
|
shifter_is_16bit_ = true;
|
|
|
|
break;
|
|
|
|
}
|
2018-03-01 23:51:05 +00:00
|
|
|
|
|
|
|
assert((master_divider_period_ % additional_divider) == 0);
|
|
|
|
assert(additional_divider < master_divider_period_);
|
|
|
|
master_divider_period_ /= additional_divider;
|
2018-02-28 03:25:12 +00:00
|
|
|
}
|
|
|
|
|
2018-03-09 18:23:18 +00:00
|
|
|
void SN76489::set_sample_volume_range(std::int16_t range) {
|
|
|
|
// Build a volume table.
|
|
|
|
double multiplier = pow(10.0, -0.1);
|
2020-05-10 03:00:39 +00:00
|
|
|
double volume = float(range) / 4.0f; // As there are four channels.
|
2018-03-09 18:23:18 +00:00
|
|
|
for(int c = 0; c < 16; ++c) {
|
2020-05-10 03:00:39 +00:00
|
|
|
volumes_[c] = int(round(volume));
|
2018-03-09 18:23:18 +00:00
|
|
|
volume *= multiplier;
|
|
|
|
}
|
|
|
|
volumes_[15] = 0;
|
|
|
|
evaluate_output_volume();
|
|
|
|
}
|
|
|
|
|
2020-01-05 18:40:02 +00:00
|
|
|
void SN76489::write(uint8_t value) {
|
2022-07-14 20:39:26 +00:00
|
|
|
task_queue_.enqueue([value, this] () {
|
2018-02-28 03:25:12 +00:00
|
|
|
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 {
|
2020-05-10 03:00:39 +00:00
|
|
|
channels_[channel].divider = uint16_t((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
|
2018-02-28 03:25:12 +00:00
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
|
2020-05-10 03:00:39 +00:00
|
|
|
channels_[3].divider = uint16_t(0x10 << (value & 3));
|
2018-02-28 03:25:12 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-05-09 21:57:21 +00:00
|
|
|
bool SN76489::is_zero_level() const {
|
2018-03-03 18:08:33 +00:00
|
|
|
return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf;
|
|
|
|
}
|
|
|
|
|
2018-02-28 03:25:12 +00:00
|
|
|
void SN76489::evaluate_output_volume() {
|
2020-05-10 03:00:39 +00:00
|
|
|
output_volume_ = int16_t(
|
2018-02-28 03:25:12 +00:00
|
|
|
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]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-02-12 15:55:52 +00:00
|
|
|
template <Outputs::Speaker::Action action>
|
|
|
|
void SN76489::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
|
2018-02-28 03:25:12 +00:00
|
|
|
std::size_t c = 0;
|
2018-03-01 03:36:03 +00:00
|
|
|
while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) {
|
2024-02-12 15:55:52 +00:00
|
|
|
Outputs::Speaker::apply<action>(target[c], output_volume_);
|
2018-02-28 03:25:12 +00:00
|
|
|
master_divider_++;
|
|
|
|
c++;
|
|
|
|
}
|
|
|
|
|
|
|
|
while(c < number_of_samples) {
|
|
|
|
bool did_flip = false;
|
|
|
|
|
2018-02-28 03:45:45 +00:00
|
|
|
#define step_channel(x, s) \
|
|
|
|
if(channels_[x].counter) channels_[x].counter--;\
|
2018-02-28 03:25:12 +00:00
|
|
|
else {\
|
2018-02-28 03:45:45 +00:00
|
|
|
channels_[x].level ^= 1;\
|
|
|
|
channels_[x].counter = channels_[x].divider;\
|
|
|
|
s;\
|
2018-02-28 03:25:12 +00:00
|
|
|
}
|
|
|
|
|
2018-02-28 03:45:45 +00:00
|
|
|
step_channel(0, /**/);
|
|
|
|
step_channel(1, /**/);
|
|
|
|
step_channel(2, did_flip = true);
|
2018-02-28 03:25:12 +00:00
|
|
|
|
|
|
|
#undef step_channel
|
|
|
|
|
2018-02-28 03:45:45 +00:00
|
|
|
if(channels_[3].divider != 0xffff) {
|
2018-02-28 03:25:12 +00:00
|
|
|
if(channels_[3].counter) channels_[3].counter--;
|
|
|
|
else {
|
|
|
|
did_flip = true;
|
2018-02-28 03:45:45 +00:00
|
|
|
channels_[3].counter = channels_[3].divider;
|
2018-02-28 03:25:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
2018-03-01 03:36:03 +00:00
|
|
|
for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) {
|
2024-02-12 15:55:52 +00:00
|
|
|
Outputs::Speaker::apply<action>(target[c], output_volume_);
|
2018-02-28 03:25:12 +00:00
|
|
|
c++;
|
|
|
|
master_divider_++;
|
|
|
|
}
|
|
|
|
}
|
2018-02-27 03:04:34 +00:00
|
|
|
|
2018-03-01 03:36:03 +00:00
|
|
|
master_divider_ &= (master_divider_period_ - 1);
|
2018-02-27 03:04:34 +00:00
|
|
|
}
|
2024-02-12 15:55:52 +00:00
|
|
|
template void SN76489::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
|
|
|
|
template void SN76489::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
|
|
|
|
template void SN76489::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
|