Initial submission of the PCem-Experimental source code.
This commit is contained in:
457
src/resid-fp/wave.h
Normal file
457
src/resid-fp/wave.h
Normal file
@@ -0,0 +1,457 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// This file is part of reSID, a MOS6581 SID emulator engine.
|
||||
// Copyright (C) 2004 Dag Lem <resid@nimrod.no>
|
||||
//
|
||||
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#ifndef __WAVE_H__
|
||||
#define __WAVE_H__
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// A 24 bit accumulator is the basis for waveform generation. FREQ is added to
|
||||
// the lower 16 bits of the accumulator each cycle.
|
||||
// The accumulator is set to zero when TEST is set, and starts counting
|
||||
// when TEST is cleared.
|
||||
// The noise waveform is taken from intermediate bits of a 23 bit shift
|
||||
// register. This register is clocked by bit 19 of the accumulator.
|
||||
// ----------------------------------------------------------------------------
|
||||
class WaveformGeneratorFP
|
||||
{
|
||||
public:
|
||||
WaveformGeneratorFP();
|
||||
|
||||
void set_sync_source(WaveformGeneratorFP*);
|
||||
void set_chip_model(chip_model model);
|
||||
|
||||
RESID_INLINE void clock();
|
||||
RESID_INLINE void synchronize();
|
||||
void reset();
|
||||
|
||||
void writeFREQ_LO(reg8);
|
||||
void writeFREQ_HI(reg8);
|
||||
void writePW_LO(reg8);
|
||||
void writePW_HI(reg8);
|
||||
void writeCONTROL_REG(reg8);
|
||||
reg8 readOSC();
|
||||
|
||||
// 12-bit waveform output.
|
||||
RESID_INLINE reg12 output();
|
||||
|
||||
protected:
|
||||
const WaveformGeneratorFP* sync_source;
|
||||
WaveformGeneratorFP* sync_dest;
|
||||
|
||||
// Tell whether the accumulator MSB was set high on this cycle.
|
||||
bool msb_rising;
|
||||
|
||||
reg24 accumulator;
|
||||
reg24 shift_register;
|
||||
reg12 previous, noise_output_cached;
|
||||
int noise_overwrite_delay;
|
||||
|
||||
// Fout = (Fn*Fclk/16777216)Hz
|
||||
reg16 freq;
|
||||
// PWout = (PWn/40.95)%, also the same << 12 for direct comparison against acc
|
||||
reg12 pw; reg24 pw_acc_scale;
|
||||
|
||||
// The control register right-shifted 4 bits; used for output function
|
||||
// table lookup.
|
||||
reg8 waveform;
|
||||
|
||||
// The remaining control register bits.
|
||||
reg8 test;
|
||||
reg8 ring_mod;
|
||||
reg8 sync;
|
||||
// The gate bit is handled by the EnvelopeGenerator.
|
||||
|
||||
// 16 possible combinations of waveforms.
|
||||
RESID_INLINE reg12 output___T();
|
||||
RESID_INLINE reg12 output__S_();
|
||||
RESID_INLINE reg12 output__ST();
|
||||
RESID_INLINE reg12 output_P__();
|
||||
RESID_INLINE reg12 output_P_T();
|
||||
RESID_INLINE reg12 output_PS_();
|
||||
RESID_INLINE reg12 output_PST();
|
||||
RESID_INLINE reg12 outputN___();
|
||||
RESID_INLINE reg12 outputN__T();
|
||||
RESID_INLINE reg12 outputN_S_();
|
||||
RESID_INLINE reg12 outputN_ST();
|
||||
RESID_INLINE reg12 outputNP__();
|
||||
RESID_INLINE reg12 outputNP_T();
|
||||
RESID_INLINE reg12 outputNPS_();
|
||||
RESID_INLINE reg12 outputNPST();
|
||||
|
||||
// Sample data for combinations of waveforms.
|
||||
static reg8 wave6581__ST[];
|
||||
static reg8 wave6581_P_T[];
|
||||
static reg8 wave6581_PS_[];
|
||||
static reg8 wave6581_PST[];
|
||||
|
||||
static reg8 wave8580__ST[];
|
||||
static reg8 wave8580_P_T[];
|
||||
static reg8 wave8580_PS_[];
|
||||
static reg8 wave8580_PST[];
|
||||
|
||||
reg8* wave__ST;
|
||||
reg8* wave_P_T;
|
||||
reg8* wave_PS_;
|
||||
reg8* wave_PST;
|
||||
|
||||
friend class VoiceFP;
|
||||
friend class SIDFP;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// SID clocking - 1 cycle.
|
||||
// ----------------------------------------------------------------------------
|
||||
RESID_INLINE
|
||||
void WaveformGeneratorFP::clock()
|
||||
{
|
||||
/* no digital operation if test bit is set. Only emulate analog fade. */
|
||||
if (test) {
|
||||
if (noise_overwrite_delay != 0) {
|
||||
if (-- noise_overwrite_delay == 0) {
|
||||
shift_register |= 0x7ffffc;
|
||||
noise_output_cached = outputN___();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
reg24 accumulator_prev = accumulator;
|
||||
|
||||
// Calculate new accumulator value;
|
||||
accumulator += freq;
|
||||
accumulator &= 0xffffff;
|
||||
|
||||
// Check whether the MSB became set high. This is used for synchronization.
|
||||
msb_rising = !(accumulator_prev & 0x800000) && (accumulator & 0x800000);
|
||||
|
||||
// Shift noise register once for each time accumulator bit 19 is set high.
|
||||
if (!(accumulator_prev & 0x080000) && (accumulator & 0x080000)) {
|
||||
reg24 bit0 = ((shift_register >> 22) ^ (shift_register >> 17)) & 0x1;
|
||||
shift_register <<= 1;
|
||||
// optimization: fall into the bit bucket
|
||||
//shift_register &= 0x7fffff;
|
||||
shift_register |= bit0;
|
||||
|
||||
/* since noise changes relatively infrequently, we'll avoid the relatively
|
||||
* expensive bit shuffling at output time. */
|
||||
noise_output_cached = outputN___();
|
||||
}
|
||||
|
||||
// clear output bits of shift register if noise and other waveforms
|
||||
// are selected simultaneously
|
||||
if (waveform > 8) {
|
||||
shift_register &= 0x7fffff^(1<<22)^(1<<20)^(1<<16)^(1<<13)^(1<<11)^(1<<7)^(1<<4)^(1<<2);
|
||||
noise_output_cached = outputN___();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Synchronize oscillators.
|
||||
// This must be done after all the oscillators have been clock()'ed since the
|
||||
// oscillators operate in parallel.
|
||||
// Note that the oscillators must be clocked exactly on the cycle when the
|
||||
// MSB is set high for hard sync to operate correctly. See SID::clock().
|
||||
// ----------------------------------------------------------------------------
|
||||
RESID_INLINE
|
||||
void WaveformGeneratorFP::synchronize()
|
||||
{
|
||||
// A special case occurs when a sync source is synced itself on the same
|
||||
// cycle as when its MSB is set high. In this case the destination will
|
||||
// not be synced. This has been verified by sampling OSC3.
|
||||
if (msb_rising && sync_dest->sync && !(sync && sync_source->msb_rising)) {
|
||||
sync_dest->accumulator = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Output functions.
|
||||
// NB! The output from SID 8580 is delayed one cycle compared to SID 6581,
|
||||
// this is not modeled.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Triangle:
|
||||
// The upper 12 bits of the accumulator are used.
|
||||
// The MSB is used to create the falling edge of the triangle by inverting
|
||||
// the lower 11 bits. The MSB is thrown away and the lower 11 bits are
|
||||
// left-shifted (half the resolution, full amplitude).
|
||||
// Ring modulation substitutes the MSB with MSB EOR sync_source MSB.
|
||||
//
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::output___T()
|
||||
{
|
||||
reg24 msb = (ring_mod ? accumulator ^ sync_source->accumulator : accumulator)
|
||||
& 0x800000;
|
||||
return ((msb ? ~accumulator : accumulator) >> 11) & 0xfff;
|
||||
}
|
||||
|
||||
// Sawtooth:
|
||||
// The output is identical to the upper 12 bits of the accumulator.
|
||||
//
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::output__S_()
|
||||
{
|
||||
return accumulator >> 12;
|
||||
}
|
||||
|
||||
// Pulse:
|
||||
// The upper 12 bits of the accumulator are used.
|
||||
// These bits are compared to the pulse width register by a 12 bit digital
|
||||
// comparator; output is either all one or all zero bits.
|
||||
// NB! The output is actually delayed one cycle after the compare.
|
||||
// This is not modeled.
|
||||
//
|
||||
// The test bit, when set to one, holds the pulse waveform output at 0xfff
|
||||
// regardless of the pulse width setting.
|
||||
//
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::output_P__()
|
||||
{
|
||||
return (test || accumulator >= pw_acc_scale) ? 0xfff : 0x000;
|
||||
}
|
||||
|
||||
// Noise:
|
||||
// The noise output is taken from intermediate bits of a 23-bit shift register
|
||||
// which is clocked by bit 19 of the accumulator.
|
||||
// NB! The output is actually delayed 2 cycles after bit 19 is set high.
|
||||
// This is not modeled.
|
||||
//
|
||||
// Operation: Calculate EOR result, shift register, set bit 0 = result.
|
||||
//
|
||||
// ----------------------->---------------------
|
||||
// | |
|
||||
// ----EOR---- |
|
||||
// | | |
|
||||
// 2 2 2 1 1 1 1 1 1 1 1 1 1 |
|
||||
// Register bits: 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 <---
|
||||
// | | | | | | | |
|
||||
// OSC3 bits : 7 6 5 4 3 2 1 0
|
||||
//
|
||||
// Since waveform output is 12 bits the output is left-shifted 4 times.
|
||||
//
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::outputN___()
|
||||
{
|
||||
return
|
||||
((shift_register & 0x400000) >> 11) |
|
||||
((shift_register & 0x100000) >> 10) |
|
||||
((shift_register & 0x010000) >> 7) |
|
||||
((shift_register & 0x002000) >> 5) |
|
||||
((shift_register & 0x000800) >> 4) |
|
||||
((shift_register & 0x000080) >> 1) |
|
||||
((shift_register & 0x000010) << 1) |
|
||||
((shift_register & 0x000004) << 2);
|
||||
}
|
||||
|
||||
// Combined waveforms:
|
||||
// By combining waveforms, the bits of each waveform are effectively short
|
||||
// circuited. A zero bit in one waveform will result in a zero output bit
|
||||
// (thus the infamous claim that the waveforms are AND'ed).
|
||||
// However, a zero bit in one waveform will also affect the neighboring bits
|
||||
// in the output. The reason for this has not been determined.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// 1 1
|
||||
// Bit # 1 0 9 8 7 6 5 4 3 2 1 0
|
||||
// -----------------------
|
||||
// Sawtooth 0 0 0 1 1 1 1 1 1 0 0 0
|
||||
//
|
||||
// Triangle 0 0 1 1 1 1 1 1 0 0 0 0
|
||||
//
|
||||
// AND 0 0 0 1 1 1 1 1 0 0 0 0
|
||||
//
|
||||
// Output 0 0 0 0 1 1 1 0 0 0 0 0
|
||||
//
|
||||
//
|
||||
// This behavior would be quite difficult to model exactly, since the SID
|
||||
// in this case does not act as a digital state machine. Tests show that minor
|
||||
// (1 bit) differences can actually occur in the output from otherwise
|
||||
// identical samples from OSC3 when waveforms are combined. To further
|
||||
// complicate the situation the output changes slightly with time (more
|
||||
// neighboring bits are successively set) when the 12-bit waveform
|
||||
// registers are kept unchanged.
|
||||
//
|
||||
// It is probably possible to come up with a valid model for the
|
||||
// behavior, however this would be far too slow for practical use since it
|
||||
// would have to be based on the mutual influence of individual bits.
|
||||
//
|
||||
// The output is instead approximated by using the upper bits of the
|
||||
// accumulator as an index to look up the combined output in a table
|
||||
// containing actual combined waveform samples from OSC3.
|
||||
// These samples are 8 bit, so 4 bits of waveform resolution is lost.
|
||||
// All OSC3 samples are taken with FREQ=0x1000, adding a 1 to the upper 12
|
||||
// bits of the accumulator each cycle for a sample period of 4096 cycles.
|
||||
//
|
||||
// Sawtooth+Triangle:
|
||||
// The sawtooth output is used to look up an OSC3 sample.
|
||||
//
|
||||
// Pulse+Triangle:
|
||||
// The triangle output is right-shifted and used to look up an OSC3 sample.
|
||||
// The sample is output if the pulse output is on.
|
||||
// The reason for using the triangle output as the index is to handle ring
|
||||
// modulation. Only the first half of the sample is used, which should be OK
|
||||
// since the triangle waveform has half the resolution of the accumulator.
|
||||
//
|
||||
// Pulse+Sawtooth:
|
||||
// The sawtooth output is used to look up an OSC3 sample.
|
||||
// The sample is output if the pulse output is on.
|
||||
//
|
||||
// Pulse+Sawtooth+Triangle:
|
||||
// The sawtooth output is used to look up an OSC3 sample.
|
||||
// The sample is output if the pulse output is on.
|
||||
//
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::output__ST()
|
||||
{
|
||||
return wave__ST[output__S_()] << 4;
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::output_P_T()
|
||||
{
|
||||
/* ring modulation does something odd with this waveform. But I don't know
|
||||
* how to emulate it. */
|
||||
return (wave_P_T[output___T() >> 1] << 4) & output_P__();
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::output_PS_()
|
||||
{
|
||||
return (wave_PS_[output__S_()] << 4) & output_P__();
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::output_PST()
|
||||
{
|
||||
return (wave_PST[output__S_()] << 4) & output_P__();
|
||||
}
|
||||
|
||||
// Combined waveforms including noise:
|
||||
// All waveform combinations including noise output zero after a few cycles.
|
||||
// NB! The effects of such combinations are not fully explored. It is claimed
|
||||
// that the shift register may be filled with zeroes and locked up, which
|
||||
// seems to be true.
|
||||
// We have not attempted to model this behavior, suffice to say that
|
||||
// there is very little audible output from waveform combinations including
|
||||
// noise. We hope that nobody is actually using it.
|
||||
//
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::outputN__T()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::outputN_S_()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::outputN_ST()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::outputNP__()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::outputNP_T()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::outputNPS_()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::outputNPST()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Select one of 16 possible combinations of waveforms.
|
||||
// ----------------------------------------------------------------------------
|
||||
RESID_INLINE
|
||||
reg12 WaveformGeneratorFP::output()
|
||||
{
|
||||
switch (waveform) {
|
||||
case 0x1:
|
||||
previous = output___T();
|
||||
break;
|
||||
case 0x2:
|
||||
previous = output__S_();
|
||||
break;
|
||||
case 0x3:
|
||||
previous = output__ST();
|
||||
break;
|
||||
case 0x4:
|
||||
previous = output_P__();
|
||||
break;
|
||||
case 0x5:
|
||||
previous = output_P_T();
|
||||
break;
|
||||
case 0x6:
|
||||
previous = output_PS_();
|
||||
break;
|
||||
case 0x7:
|
||||
previous = output_PST();
|
||||
break;
|
||||
case 0x8:
|
||||
previous = noise_output_cached;
|
||||
break;
|
||||
case 0x9:
|
||||
previous = outputN__T();
|
||||
break;
|
||||
case 0xa:
|
||||
previous = outputN_S_();
|
||||
break;
|
||||
case 0xb:
|
||||
previous = outputN_ST();
|
||||
break;
|
||||
case 0xc:
|
||||
previous = outputNP__();
|
||||
break;
|
||||
case 0xd:
|
||||
previous = outputNP_T();
|
||||
break;
|
||||
case 0xe:
|
||||
previous = outputNPS_();
|
||||
break;
|
||||
case 0xf:
|
||||
previous = outputNPST();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
#endif // not __WAVE_H__
|
||||
Reference in New Issue
Block a user