2021-11-09 12:11:23 +00:00
|
|
|
//
|
|
|
|
// Audio.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 09/11/2021.
|
|
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "Audio.hpp"
|
2021-11-12 20:30:52 +00:00
|
|
|
|
2021-11-13 16:05:39 +00:00
|
|
|
#include "Flags.hpp"
|
|
|
|
|
2021-11-12 20:30:52 +00:00
|
|
|
#define LOG_PREFIX "[Audio] "
|
|
|
|
#include "../../Outputs/Log.hpp"
|
|
|
|
|
2021-11-11 14:24:15 +00:00
|
|
|
#include <cassert>
|
2021-11-09 12:11:23 +00:00
|
|
|
|
|
|
|
using namespace Amiga;
|
|
|
|
|
2021-11-13 20:53:41 +00:00
|
|
|
bool Audio::advance_dma(int channel) {
|
2021-11-14 15:48:50 +00:00
|
|
|
switch(channels_[channel].state) {
|
|
|
|
case Channel::State::WaitingForDMA:
|
|
|
|
set_data(channel, ram_[pointer_[size_t(channel)]]);
|
|
|
|
return true;
|
|
|
|
case Channel::State::WaitingForDummyDMA:
|
|
|
|
channels_[channel].has_data = true;
|
|
|
|
return true;
|
|
|
|
default:
|
2021-11-11 14:24:15 +00:00
|
|
|
return false;
|
|
|
|
}
|
2021-11-09 12:11:23 +00:00
|
|
|
}
|
|
|
|
|
2021-11-11 14:24:15 +00:00
|
|
|
void Audio::set_length(int channel, uint16_t length) {
|
|
|
|
assert(channel >= 0 && channel < 4);
|
|
|
|
channels_[channel].length = length;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Audio::set_period(int channel, uint16_t period) {
|
|
|
|
assert(channel >= 0 && channel < 4);
|
|
|
|
channels_[channel].period = period;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Audio::set_volume(int channel, uint16_t volume) {
|
|
|
|
assert(channel >= 0 && channel < 4);
|
|
|
|
channels_[channel].volume = (volume & 0x40) ? 64 : (volume & 0x3f);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Audio::set_data(int channel, uint16_t data) {
|
|
|
|
assert(channel >= 0 && channel < 4);
|
2021-11-12 20:30:52 +00:00
|
|
|
channels_[channel].has_data = true;
|
2021-11-11 14:24:15 +00:00
|
|
|
channels_[channel].data = data;
|
2021-11-09 12:11:23 +00:00
|
|
|
}
|
|
|
|
|
2021-11-11 14:24:15 +00:00
|
|
|
void Audio::set_channel_enables(uint16_t enables) {
|
|
|
|
channels_[0].dma_enabled = enables & 1;
|
|
|
|
channels_[1].dma_enabled = enables & 2;
|
|
|
|
channels_[2].dma_enabled = enables & 4;
|
|
|
|
channels_[3].dma_enabled = enables & 8;
|
2021-11-09 12:11:23 +00:00
|
|
|
}
|
|
|
|
|
2021-11-11 14:24:15 +00:00
|
|
|
void Audio::set_modulation_flags(uint16_t) {
|
2021-11-09 12:11:23 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 16:05:39 +00:00
|
|
|
void Audio::set_interrupt_requests(uint16_t requests) {
|
|
|
|
channels_[0].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel0);
|
|
|
|
channels_[1].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel1);
|
|
|
|
channels_[2].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel2);
|
|
|
|
channels_[3].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel3);
|
|
|
|
}
|
|
|
|
|
2021-11-13 20:53:41 +00:00
|
|
|
void Audio::output() {
|
2021-11-11 14:24:15 +00:00
|
|
|
// TODO:
|
|
|
|
//
|
|
|
|
// Check whether any channel's period counter is exhausted and, if
|
|
|
|
// so, attempt to consume another sample. If there are no more samples
|
|
|
|
// and length is 0, trigger an interrupt.
|
2021-11-13 16:05:39 +00:00
|
|
|
|
2021-11-14 15:48:50 +00:00
|
|
|
constexpr InterruptFlag interrupts[] = {
|
|
|
|
InterruptFlag::AudioChannel0,
|
|
|
|
InterruptFlag::AudioChannel1,
|
|
|
|
InterruptFlag::AudioChannel2,
|
|
|
|
InterruptFlag::AudioChannel3,
|
|
|
|
};
|
2021-11-13 16:05:39 +00:00
|
|
|
|
2021-11-14 15:48:50 +00:00
|
|
|
for(int c = 0; c < 4; c++) {
|
|
|
|
if(channels_[c].output()) {
|
|
|
|
posit_interrupt(interrupts[c]);
|
2021-11-13 16:05:39 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-09 12:11:23 +00:00
|
|
|
}
|
2021-11-14 15:48:50 +00:00
|
|
|
|
|
|
|
bool Audio::Channel::output() {
|
|
|
|
switch(state) {
|
|
|
|
case State::Disabled:
|
|
|
|
// Test for top loop of Commodore's state diagram,
|
|
|
|
// which permits CPU-driven audio output.
|
|
|
|
if(has_data && !dma_enabled && !interrupt_pending) {
|
|
|
|
state = State::PlayingHigh;
|
|
|
|
|
|
|
|
data_latch = data; // i.e. pbufld1
|
|
|
|
has_data = false;
|
|
|
|
period_counter = period; // i.e. percntrld
|
|
|
|
// TODO: volcntrld (see above).
|
|
|
|
|
|
|
|
// Request an interrupt.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test for DMA-style transition.
|
|
|
|
if(dma_enabled) {
|
|
|
|
state = State::WaitingForDummyDMA;
|
|
|
|
|
|
|
|
period_counter = period; // i.e. percntrld
|
|
|
|
length_counter = length; // i.e. lenctrld
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State::WaitingForDummyDMA:
|
|
|
|
if(!dma_enabled) {
|
|
|
|
state = State::Disabled;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(dma_enabled && has_data) {
|
|
|
|
has_data = false;
|
|
|
|
state = State::WaitingForDMA;
|
|
|
|
if(length == 1) {
|
|
|
|
length_counter = length;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State::WaitingForDMA:
|
|
|
|
if(!dma_enabled) {
|
|
|
|
state = State::Disabled;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(dma_enabled && has_data) {
|
|
|
|
data_latch = data;
|
|
|
|
has_data = false;
|
|
|
|
period_counter = period; // i.e. percntrld
|
|
|
|
|
|
|
|
state = State::PlayingHigh;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case State::PlayingHigh:
|
|
|
|
// TODO: penhi (i.e. output high byte).
|
|
|
|
-- period_counter;
|
|
|
|
if(!period_counter) {
|
|
|
|
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|