Files
picogus/sw/gus-staging.h
T

408 lines
12 KiB
C++

/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Copyright (C) 2020-2022 The DOSBox Staging Team
* Copyright (C) 2002-2021 The DOSBox Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "dosboxcompat.h"
#ifdef CIRCLE
#include <circle/logger.h>
#endif
#include <array>
#include <iomanip>
#include <memory>
#include <string>
#include <unistd.h>
#include <vector>
/*
#include "control.h"
#include "dma.h"
#include "hardware.h"
#include "mixer.h"
#include "pic.h"
#include "setup.h"
#include "shell.h"
#include "string_utils.h"
*/
/*
#include "soft_limiter.h"
*/
// TODO timers
// #include "gustimer.h"
#define LOG_GUS 0 // set to 1 for detailed logging
// Global Constants
// ----------------
// AdLib emulation state constant
constexpr uint8_t ADLIB_CMD_DEFAULT = 85u;
// Buffer and memory constants
//constexpr int BUFFER_FRAMES = 48;
constexpr int BUFFER_FRAMES = 1024;
//constexpr uint32_t RAM_SIZE = 1024 * 1024; // 1 MiB
constexpr uint32_t RAM_SIZE = 1024 * 64; // 1 MiB
// DMA transfer size and rate constants
constexpr uint32_t BYTES_PER_DMA_XFER = 8 * 1024; // 8 KiB per transfer
constexpr uint32_t ISA_BUS_THROUGHPUT = 32 * 1024 * 1024; // 32 MiB/s
constexpr uint16_t DMA_TRANSFERS_PER_S = ISA_BUS_THROUGHPUT / BYTES_PER_DMA_XFER;
constexpr double MS_PER_DMA_XFER = 1000.0 / DMA_TRANSFERS_PER_S;
// Voice-channel and state related constants
constexpr uint8_t MAX_VOICES = 32u;
constexpr uint8_t MIN_VOICES = 14u;
constexpr uint8_t VOICE_DEFAULT_STATE = 3u;
// DMA and IRQ extents and quantities
constexpr uint8_t MIN_DMA_ADDRESS = 0u;
constexpr uint8_t MAX_DMA_ADDRESS = 7u;
constexpr uint8_t MIN_IRQ_ADDRESS = 0u;
constexpr uint8_t MAX_IRQ_ADDRESS = 15u;
constexpr uint8_t DMA_IRQ_ADDRESSES = 8u; // number of IRQ and DMA channels
constexpr uint16_t DMA_TC_STATUS_BITMASK = 0b100000000; // Status in 9th bit
// Pan position constants
constexpr uint8_t PAN_DEFAULT_POSITION = 7u;
constexpr uint8_t PAN_POSITIONS = 16u; // 0: -45-deg, 7: centre, 15: +45-deg
// Timer delay constants
constexpr uint32_t TIMER_1_DEFAULT_DELAY = 80;
constexpr uint32_t TIMER_2_DEFAULT_DELAY = 320;
// Volume scaling and dampening constants
constexpr auto DELTA_DB = 0.002709201; // 0.0235 dB increments
constexpr int16_t VOLUME_INC_SCALAR = 512; // Volume index increment scalar
constexpr uint16_t VOLUME_LEVELS = 4096u;
// Interwave addressing constant
constexpr int16_t WAVE_WIDTH = 1 << 9; // Wave interpolation width (9 bits)
// IO address quantities
constexpr uint8_t READ_HANDLERS = 8u;
constexpr uint8_t WRITE_HANDLERS = 9u;
// A group of parameters defining the Gus's voice IRQ control that's also shared
// (as a reference) into each instantiated voice.
struct VoiceIrq {
uint32_t vol_state = 0u;
uint32_t wave_state = 0u;
uint8_t status = 0u;
};
// A group of parameters used in the Voice class to track the Wave and Volume
// controls.
struct VoiceCtrl {
uint32_t &irq_state;
int32_t start = 0;
int32_t end = 0;
int32_t pos = 0;
int32_t inc = 0;
uint16_t rate = 0;
uint8_t state = VOICE_DEFAULT_STATE;
};
// Collection types involving constant quantities
using address_array_t = std::array<uint8_t, DMA_IRQ_ADDRESSES>;
using pan_scalars_array_t = std::array<AudioFrame, PAN_POSITIONS>;
using ram_array_t = std::array<uint8_t, RAM_SIZE>;
/* using read_io_array_t = std::array<IO_ReadHandleObject, READ_HANDLERS>; */
using vol_scalars_array_t = std::array<float, VOLUME_LEVELS>;
/* using write_io_array_t = std::array<IO_WriteHandleObject, WRITE_HANDLERS>; */
// A Voice is used by the Gus class and instantiates 32 of these.
// Each voice represents a single "mono" render_buffer of audio having its own
// characteristics defined by the running program, such as:
// - being 8bit or 16bit
// - having a "position" along a left-right axis (panned)
// - having its volume reduced by some amount (native-level down to 0)
// - having start, stop, loop, and loop-backward controls
// - informing the GUS DSP as to when an IRQ is needed to keep it playing
//
class Voice {
public:
Voice(uint8_t num, VoiceIrq &irq) noexcept;
inline void GenerateSamples(std::vector<int32_t> &render_buffer,
const ram_array_t &ram,
const vol_scalars_array_t &vol_scalars,
const pan_scalars_array_t &pan_scalars,
uint16_t requested_frames);
uint8_t ReadVolState() const noexcept;
uint8_t ReadWaveState() const noexcept;
void ResetCtrls() noexcept;
void WritePanPot(uint8_t pos) noexcept;
void WriteVolRate(uint16_t rate, int playback_rate) noexcept;
void WriteWaveRate(uint16_t rate, int playback_rate) noexcept;
bool UpdateVolState(uint8_t state) noexcept;
bool UpdateWaveState(uint8_t state) noexcept;
VoiceCtrl vol_ctrl;
VoiceCtrl wave_ctrl;
uint32_t generated_8bit_ms = 0u;
uint32_t generated_16bit_ms = 0u;
private:
Voice() = delete;
Voice(const Voice &) = delete; // prevent copying
Voice &operator=(const Voice &) = delete; // prevent assignment
bool CheckWaveRolloverCondition() noexcept;
bool Is16Bit() const noexcept;
float GetVolScalar(const vol_scalars_array_t &vol_scalars);
inline int16_t GetSample(const ram_array_t &ram) noexcept;
int32_t PopWavePos() noexcept;
float PopVolScalar(const vol_scalars_array_t &vol_scalars);
inline int16_t Read8BitSample(const ram_array_t &ram, int32_t addr) const noexcept;
inline int16_t Read16BitSample(const ram_array_t &ram, int32_t addr) const noexcept;
uint8_t ReadCtrlState(const VoiceCtrl &ctrl) const noexcept;
void IncrementCtrlPos(VoiceCtrl &ctrl, bool skip_loop) noexcept;
bool UpdateCtrlState(VoiceCtrl &ctrl, uint8_t state) noexcept;
// Control states
enum CTRL : uint8_t {
RESET = 0x01,
STOPPED = 0x02,
DISABLED = RESET | STOPPED,
BIT16 = 0x04,
LOOP = 0x08,
BIDIRECTIONAL = 0x10,
RAISEIRQ = 0x20,
DECREASING = 0x40,
};
uint32_t irq_mask = 0u;
uint8_t &shared_irq_status;
uint8_t pan_position = PAN_DEFAULT_POSITION;
};
static void GUS_TimerEvent(Bitu t);
/* static void GUS_DMA_Event(uint32_t val); */
using voice_array_t = std::array<std::unique_ptr<Voice>, MAX_VOICES>;
typedef void IRQCallback(bool raise, void *pParam);
// The Gravis UltraSound GF1 DSP (classic)
// This class:
// - Registers, receives, and responds to port address inputs, which are used
// by the emulated software to configure and control the GUS card.
// - Reads or provides audio samples via direct memory access (DMA)
// - Provides shared resources to all of the Voices, such as the volume
// reducing table, constant-power panning table, and IRQ states.
// - Accumulates the audio from each active voice into a floating point
// vector (the render_buffer), without resampling.
// - Populates an autoexec line (ULTRASND=...) with its port, irq, and dma
// addresses.
//
class Gus {
public:
#ifdef CIRCLE
Gus(uint16_t port, IRQCallback* irq_callback, void* irq_param, GusTimer &gus_timer, CLogger &logger);
#else
Gus(uint16_t port, IRQCallback* irq_callback, void* irq_param);
#endif
virtual ~Gus();
bool CheckTimer(size_t t);
void PrintStats();
// WriteToPort and ReadFromPort are private in DOSBox, but now they are the primary interfaces so need to be exposed
void WriteToPort(io_port_t port, io_val_t value, io_width_t width);
uint16_t ReadFromPort(io_port_t port, io_width_t width);
struct Timer {
uint32_t delay = 0;
uint8_t value = 0xff;
bool has_expired = true;
bool is_counting_down = false;
bool is_masked = false;
bool should_raise_irq = false;
};
Timer timer_one = {TIMER_1_DEFAULT_DELAY};
Timer timer_two = {TIMER_2_DEFAULT_DELAY};
#if 0 // no DMA yet
bool PerformDmaTransfer();
#endif
private:
Gus() = delete;
Gus(const Gus &) = delete; // prevent copying
Gus &operator=(const Gus &) = delete; // prevent assignment
void ActivateVoices(uint8_t requested_voices);
public:
void AudioCallback(uint16_t requested_frames, int16_t* play_buffer);
private:
void BeginPlayback();
void CheckIrq();
void CheckVoiceIrq();
#if 0 // no DMA yet
uint32_t GetDmaOffset() noexcept;
void UpdateDmaAddr(uint32_t offset) noexcept;
void DmaCallback(DmaChannel *chan, DMAEvent event);
void StartDmaTransfers();
bool IsDmaPcm16Bit() noexcept;
bool IsDmaXfer16Bit() noexcept;
#endif
uint16_t ReadFromRegister();
void PopulatePanScalars() noexcept;
void PopulateVolScalars() noexcept;
void PrepareForPlayback() noexcept;
/* void RegisterIoHandlers(); */
void Reset(uint8_t state);
void SetLevelCallback(const AudioFrame &levels);
void StopPlayback();
#if 0 // no DMA yet
void UpdateDmaAddress(uint8_t new_address);
#endif
void UpdateWaveMsw(int32_t &addr) const noexcept;
void UpdateWaveLsw(int32_t &addr) const noexcept;
void WriteToRegister();
// Collections
vol_scalars_array_t vol_scalars = {{}};
std::vector<int32_t> render_buffer = {};
// std::vector<int16_t> play_buffer = {};
pan_scalars_array_t pan_scalars = {{}};
ram_array_t ram = {{0u}};
/* read_io_array_t read_handlers = {}; // std::functions */
/* write_io_array_t write_handlers = {}; // std::functions */
#if 0 // no DMA yet
const address_array_t dma_addresses = {
{MIN_DMA_ADDRESS, 1, 3, 5, 6, MAX_IRQ_ADDRESS, 0, 0}};
#endif
const address_array_t irq_addresses = {
{MIN_IRQ_ADDRESS, 2, 5, 3, 7, 11, 12, MAX_IRQ_ADDRESS}};
voice_array_t voices = {{nullptr}};
// Struct and pointer members
VoiceIrq voice_irq = {};
//SoftLimiter soft_limiter;
Voice *target_voice = nullptr;
#if 0 // no DMA yet
DmaChannel *dma_channel = nullptr;
#endif
//mixer_channel_t audio_channel = nullptr;
uint8_t adlib_command_reg = ADLIB_CMD_DEFAULT;
// Port address
io_port_t port_base = 0u;
// Voice states
uint32_t active_voice_mask = 0u;
uint16_t voice_index = 0u;
public:
uint8_t active_voices = 0u;
private:
uint8_t prev_logged_voices = 0u;
// Register and playback rate
uint32_t dram_addr = 0u;
public:
int playback_rate = 0;
private:
uint16_t register_data = 0u;
public:
uint8_t selected_register = 0u;
private:
// Control states
uint8_t mix_ctrl = 0x0b; // latches enabled, LINEs disabled
uint8_t sample_ctrl = 0u;
uint8_t timer_ctrl = 0u;
// DMA states
uint16_t dma_addr = 0u;
uint8_t dma_addr_nibble = 0u;
// dma_ctrl would normally be a uint8_t as real hardware uses 8 bits,
// but we store the DMA terminal count status in the 9th bit
uint16_t dma_ctrl = 0u;
uint8_t dma1 = 0u; // playback DMA
uint8_t dma2 = 0u; // recording DMA
// IRQ states
uint8_t irq1 = 0u; // playback IRQ
uint8_t irq2 = 0u; // MIDI IRQ
uint8_t irq_status = 0u;
uint8_t prev_interrupt = 0u;
bool dac_enabled = false;
bool irq_enabled = false;
bool is_running = false;
bool should_change_irq_dma = false;
IRQCallback* irq_callback;
void* irq_param;
public:
// TODO timers
// GusTimer &m_GusTimer;
#ifdef CIRCLE
private:
CLogger &m_Logger;
#endif
};
inline uint16_t Gus::ReadFromPort(const io_port_t port, io_width_t width)
{
/* LOG_MSG("GUS: Read from port %x", port); */
switch (port - port_base) {
case 0x206: return irq_status;
case 0x208:
uint8_t time;
time = 0u;
if (timer_one.has_expired)
time |= (1 << 6);
if (timer_two.has_expired)
time |= 1 << 5;
if (time & 0x60)
time |= 1 << 7;
if (irq_status & 0x04)
time |= 1 << 2;
if (irq_status & 0x08)
time |= 1 << 1;
return time;
case 0x20a: return adlib_command_reg;
case 0x302: return static_cast<uint8_t>(voice_index);
case 0x303: return selected_register;
case 0x304:
if (width == io_width_t::word)
return ReadFromRegister() & 0xffff;
else
return ReadFromRegister() & 0xff;
case 0x305: return ReadFromRegister() >> 8;
case 0x307:
return dram_addr < ram.size() ? ram.at(dram_addr) : 0;
default:
#if LOG_GUS
LOG_MSG("GUS: Read at port %#x", port);
#endif
break;
}
return 0xff;
}
using namespace std::placeholders;
/* uint8_t adlib_commandreg = ADLIB_CMD_DEFAULT; */