2024-03-20 18:25:20 +00:00
|
|
|
//
|
|
|
|
// Audio.hpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 20/03/2024.
|
|
|
|
// Copyright © 2024 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
2024-03-20 18:43:47 +00:00
|
|
|
#include <cstdint>
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
namespace Archimedes {
|
|
|
|
|
2024-03-20 18:43:47 +00:00
|
|
|
/// Models the Archimedes sound output; in a real machine this is a joint efort between the VIDC and the MEMC.
|
2024-03-20 18:25:20 +00:00
|
|
|
template <typename InterruptObserverT>
|
|
|
|
struct Sound {
|
2024-03-22 00:22:20 +00:00
|
|
|
Sound(InterruptObserverT &observer, const uint8_t *ram) : ram_(ram), observer_(observer) {}
|
2024-03-20 18:25:20 +00:00
|
|
|
|
|
|
|
void set_next_end(uint32_t value) {
|
|
|
|
next_.end = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_next_start(uint32_t value) {
|
|
|
|
next_.start = value;
|
|
|
|
set_buffer_valid(true); // My guess: this is triggered on next buffer start write.
|
|
|
|
}
|
|
|
|
|
|
|
|
bool interrupt() const {
|
|
|
|
return !next_buffer_valid_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void swap() {
|
|
|
|
current_.start = next_.start;
|
|
|
|
std::swap(current_.end, next_.end);
|
|
|
|
set_buffer_valid(false);
|
2024-03-21 13:47:53 +00:00
|
|
|
set_halted(false);
|
2024-03-20 18:25:20 +00:00
|
|
|
}
|
|
|
|
|
2024-03-20 18:43:47 +00:00
|
|
|
void set_frequency(uint8_t frequency) {
|
|
|
|
divider_ = reload_ = frequency;
|
|
|
|
}
|
|
|
|
|
2024-03-21 13:47:53 +00:00
|
|
|
void set_stereo_image(uint8_t channel, uint8_t value) {
|
|
|
|
if(!value) {
|
|
|
|
positions_[channel].left =
|
|
|
|
positions_[channel].right = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
positions_[channel].right = value - 1;
|
|
|
|
positions_[channel].left = 6 - positions_[channel].right;
|
2024-03-20 18:43:47 +00:00
|
|
|
}
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
void tick() {
|
2024-03-21 13:47:53 +00:00
|
|
|
// Do nothing if not currently outputting.
|
2024-03-20 18:25:20 +00:00
|
|
|
if(halted_) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-21 13:47:53 +00:00
|
|
|
// Apply user-programmed clock divider.
|
2024-03-20 18:43:47 +00:00
|
|
|
--divider_;
|
|
|
|
if(divider_) return;
|
|
|
|
divider_ = reload_;
|
|
|
|
|
2024-03-21 13:47:53 +00:00
|
|
|
// Grab a single byte from the FIFO.
|
|
|
|
// TODO: and convert to a linear value, apply stereo image, output.
|
|
|
|
++byte_;
|
|
|
|
|
|
|
|
// If the FIFO is exhausted, consider triggering a DMA request.
|
|
|
|
if(byte_ == 16) {
|
|
|
|
byte_ = 0;
|
|
|
|
|
|
|
|
current_.start += 16;
|
|
|
|
if(current_.start == current_.end) {
|
|
|
|
if(next_buffer_valid_) {
|
|
|
|
swap();
|
|
|
|
} else {
|
|
|
|
set_halted(true);
|
|
|
|
}
|
2024-03-20 18:25:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2024-03-22 00:22:20 +00:00
|
|
|
const uint8_t *ram_ = nullptr;
|
|
|
|
|
2024-03-20 18:43:47 +00:00
|
|
|
uint8_t divider_ = 0, reload_ = 0;
|
2024-03-21 13:47:53 +00:00
|
|
|
int byte_ = 0;
|
2024-03-20 18:43:47 +00:00
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
void set_buffer_valid(bool valid) {
|
|
|
|
next_buffer_valid_ = valid;
|
2024-03-21 14:02:56 +00:00
|
|
|
observer_.update_interrupts();
|
2024-03-20 18:25:20 +00:00
|
|
|
}
|
|
|
|
|
2024-03-21 13:47:53 +00:00
|
|
|
void set_halted(bool halted) {
|
|
|
|
if(halted_ != halted && !halted) {
|
|
|
|
byte_ = 0;
|
|
|
|
divider_ = reload_;
|
|
|
|
}
|
|
|
|
halted_ = halted;
|
|
|
|
}
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
bool next_buffer_valid_ = false;
|
|
|
|
bool halted_ = true; // This is a bit of a guess.
|
|
|
|
|
|
|
|
struct Buffer {
|
|
|
|
uint32_t start = 0, end = 0;
|
|
|
|
};
|
|
|
|
Buffer current_, next_;
|
|
|
|
|
2024-03-21 13:47:53 +00:00
|
|
|
struct StereoPosition {
|
|
|
|
// These are maintained as sixths, i.e. a value of 6 means 100%.
|
|
|
|
int left, right;
|
|
|
|
} positions_[8];
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
InterruptObserverT &observer_;
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|