381 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			381 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable file
		
	
	
	
	
// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
 | 
						|
//
 | 
						|
// SAAEnv.cpp: implementation of the CSAAEnv class.
 | 
						|
//
 | 
						|
//////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
#include "SAASound.h"
 | 
						|
#include "types.h"
 | 
						|
#include "SAAEnv.h"
 | 
						|
 | 
						|
 | 
						|
//////////////////////////////////////////////////////////////////////
 | 
						|
// Static member initialisation
 | 
						|
//////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
const ENVDATA CSAAEnv::cs_EnvData[8] =
 | 
						|
{
 | 
						|
	{1,false,	{	{{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},					{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
 | 
						|
					{{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},					{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}},
 | 
						|
	{1,true,	{	{{15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15},{15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15}},
 | 
						|
					{{14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14},{14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14}}}},
 | 
						|
	{1,false,	{	{{15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0},			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
 | 
						|
					{{14,14,12,12,10,10,8,8,6,6,4,4,2,2,0,0},			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}},
 | 
						|
	{1,true,	{	{{15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0},			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
 | 
						|
					{{14,14,12,12,10,10,8,8,6,6,4,4,2,2,0,0},			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}},
 | 
						|
	{2,false,	{	{{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},			{15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}},
 | 
						|
					{{0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14},			{14,14,12,12,10,10,8,8,6,6,4,4,2,2,0,0}}}},
 | 
						|
	{2,true,	{	{{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},			{15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}},
 | 
						|
					{{0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14},			{14,14,12,12,10,10,8,8,6,6,4,4,2,2,0,0}}}},
 | 
						|
	{1,false,	{	{{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
 | 
						|
					{{0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14},			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}},
 | 
						|
	{1,true,	{	{{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
 | 
						|
					{{0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14},			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}}
 | 
						|
};
 | 
						|
 | 
						|
//////////////////////////////////////////////////////////////////////
 | 
						|
// Construction/Destruction
 | 
						|
//////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
CSAAEnv::CSAAEnv()
 | 
						|
:
 | 
						|
m_bEnabled(false),
 | 
						|
m_nPhase(0),
 | 
						|
m_nPhasePosition(0),
 | 
						|
m_bEnvelopeEnded(true),
 | 
						|
m_nResolution(1),
 | 
						|
m_bNewData(false),
 | 
						|
m_nNextData(0)
 | 
						|
{
 | 
						|
	// initialise itself with the value 'zero'
 | 
						|
	SetEnvControl(0);
 | 
						|
}
 | 
						|
 | 
						|
CSAAEnv::~CSAAEnv()
 | 
						|
{
 | 
						|
	// Nothing to do
 | 
						|
}
 | 
						|
 | 
						|
void CSAAEnv::InternalClock(void)
 | 
						|
{
 | 
						|
	// will only do something if envelope clock mode is set to internal
 | 
						|
	// and the env control is enabled
 | 
						|
	if (m_bEnabled && (!m_bClockExternally)) Tick();
 | 
						|
}
 | 
						|
 | 
						|
void CSAAEnv::ExternalClock(void)
 | 
						|
{
 | 
						|
	// will only do something if envelope clock mode is set to external
 | 
						|
	// and the env control is enabled
 | 
						|
	if (m_bClockExternally && m_bEnabled) Tick();
 | 
						|
}
 | 
						|
 | 
						|
void CSAAEnv::SetEnvControl(int nData)
 | 
						|
{
 | 
						|
	// process immediate stuff first:
 | 
						|
	// start with the Enabled flag.  if env is disabled,
 | 
						|
	// there's not much to do
 | 
						|
	bool bEnabled = ((nData & 0x80)==0x80);
 | 
						|
	if (!bEnabled && !m_bEnabled)
 | 
						|
		return;
 | 
						|
	m_bEnabled = bEnabled;
 | 
						|
	if (!m_bEnabled)
 | 
						|
	{
 | 
						|
		// env control was enabled, and now disabled
 | 
						|
		// Any subsequent env control changes are immediate.
 | 
						|
		m_bEnvelopeEnded = true;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// Resolution (3bit/4bit) is also immediately processed
 | 
						|
	int new_resolution = ((nData & 0x10) == 0x10) ? 2 : 1;
 | 
						|
	// NOTE: undocumented behaviour when changing resolution mid-waveform
 | 
						|
	// Empirically, the following matches observations:
 | 
						|
	// * When ticking the env generator with 4-bit resolution, the position += 1
 | 
						|
	// * When ticking the env generator with 3-bit resolution, the position += 2
 | 
						|
	// * When changing between 4-bit resolution and 3-bit resolution
 | 
						|
	//   without ticking the env generator, the position is unchanged
 | 
						|
	//   (although, effectively, the LSB is ignored. Purely as an implementation
 | 
						|
	//    detail, I'm implementing this as clearing the LSB ie LSB=0; see next point)
 | 
						|
	// * When changing between 3-bit resolution and 4-bit resolution
 | 
						|
	//   without ticking the env generator, the position LSB is set to 1
 | 
						|
	// See test case: envext_34b
 | 
						|
	//
 | 
						|
	if (m_nResolution == 1 && new_resolution == 2)
 | 
						|
	{
 | 
						|
		// change from 4-bit to 3-bit
 | 
						|
		m_nPhasePosition &= 0xe;
 | 
						|
	}
 | 
						|
	else if (m_nResolution == 2 && new_resolution == 1)
 | 
						|
	{
 | 
						|
		// change from 3-bit to 4-bit
 | 
						|
		m_nPhasePosition |= 0x1;
 | 
						|
	}
 | 
						|
	m_nResolution = new_resolution;
 | 
						|
 | 
						|
	// now buffered stuff: but only if it's ok to, and only if the
 | 
						|
	// envgenerator is not disabled. otherwise it just stays buffered until
 | 
						|
	// the Tick() function sets m_bEnvelopeEnded to true and realises there is
 | 
						|
	// already some new data waiting
 | 
						|
	if (m_bEnvelopeEnded)
 | 
						|
	{
 | 
						|
		SetNewEnvData(nData); // also does the SetLevels() call for us.
 | 
						|
		m_bNewData=false;
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		// since the 'next resolution' changes arrive unbuffered, we
 | 
						|
		// may need to change the current level because of this:
 | 
						|
		SetLevels();
 | 
						|
	
 | 
						|
		// store current new data, and set the newdata flag:
 | 
						|
		m_bNewData = true;
 | 
						|
		m_nNextData = nData;
 | 
						|
	}
 | 
						|
			
 | 
						|
}
 | 
						|
	
 | 
						|
int CSAAEnv::LeftLevel(void) const
 | 
						|
{
 | 
						|
	return m_nLeftLevel;
 | 
						|
}
 | 
						|
 | 
						|
int CSAAEnv::RightLevel(void) const
 | 
						|
{
 | 
						|
	return m_nRightLevel;
 | 
						|
}
 | 
						|
 | 
						|
inline void CSAAEnv::Tick(void)
 | 
						|
{
 | 
						|
	// if disabled, do nothing
 | 
						|
	if (!m_bEnabled) // m_bEnabled is set directly, not buffered, so this is ok
 | 
						|
	{
 | 
						|
		// for sanity, reset stuff:
 | 
						|
		m_bEnvelopeEnded = true;
 | 
						|
		m_nPhase = 0;
 | 
						|
		m_nPhasePosition = 0;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	// else : m_bEnabled
 | 
						|
 | 
						|
 | 
						|
	if (m_bEnvelopeEnded)
 | 
						|
	{
 | 
						|
		// do nothing 
 | 
						|
		// (specifically, don't change the values of m_bEnvelopeEnded,
 | 
						|
		//  m_nPhase and m_nPhasePosition, as these will still be needed
 | 
						|
		//  by SetLevels() should it be called again)
 | 
						|
		
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	// else : !m_bEnvelopeEnded
 | 
						|
	// Continue playing the same envelope ... 
 | 
						|
	// increments the phaseposition within an envelope.
 | 
						|
	// also handles looping and resolution appropriately.
 | 
						|
	// Changes the level of the envelope accordingly
 | 
						|
	// through calling SetLevels() . This must be called after making
 | 
						|
	// any changes that will affect the output levels of the env controller!!
 | 
						|
	// SetLevels also handles left-right channel inverting
 | 
						|
 | 
						|
	// increment phase position
 | 
						|
	m_nPhasePosition += m_nResolution;
 | 
						|
 | 
						|
	// if this means we've gone past 16 (the end of a phase)
 | 
						|
	// then change phase, and if necessary, loop
 | 
						|
	// Refer to datasheet for meanings of (3) and (4) in following text
 | 
						|
	// w.r.t SAA1099 envelopes
 | 
						|
 | 
						|
	// Note that we will always reach position (3) or (4), even if we keep toggling
 | 
						|
	// resolution from 4-bit to 3-bit and back, because the counter will always wrap to 0.
 | 
						|
	// In fact it's quite elegant:
 | 
						|
	// No matter how you increment and toggle and increment and toggle, the counter
 | 
						|
	// will at some point be either 0xe (either 4-bit mode or 3-bit mode) or 0xf (4-bit mode only).
 | 
						|
	// Depending on the mode, even if you change the mode, the next increment,
 | 
						|
	// or the one after it, will then take it to 0.
 | 
						|
	// 0xe + 2  (3bit mode)  => 0x0
 | 
						|
	// 0xe + 1  (4bit mode)  => 0xf
 | 
						|
	// 0xf + 1  (4bit mode)  => 0x0
 | 
						|
	// 0xe -> (toggle 3bit mode to 4bit mode) => 0xf
 | 
						|
	// 0xe -> (toggle 4bit mode to 3bit mode) => 0xe
 | 
						|
	// 0xf -> (toggle 4bit mode to 3bit mode) => 0xe
 | 
						|
	//
 | 
						|
	// but there is a subtlety (of course), which is that any changes at point (3)
 | 
						|
	// can take place immediately you hit point (3), but changes at point (4) are actually
 | 
						|
	// only acted upon when the counter transitions from 0xe (or 0xf) to 0x0 (which also
 | 
						|
	// means that, for these looping envelopes, which are the ones that have a point(4),
 | 
						|
	// immediately after the counter wrapping to 0x0, a write to the env data register will
 | 
						|
	// NOT set the waveform and will NOT reset the phase/phaseposition (even though it
 | 
						|
	// will still let you toggle the 4bit/3bit mode, which will change the phaseposition LSB!)
 | 
						|
	// See test case: envext_34c
 | 
						|
 | 
						|
	bool bProcessNewDataIfAvailable = false;
 | 
						|
	if (m_nPhasePosition >= 16)
 | 
						|
	{
 | 
						|
		m_nPhase++;
 | 
						|
			
 | 
						|
		// if we should loop, then do so - and we've reached position (4)
 | 
						|
		// otherwise, if we shouldn't loop,
 | 
						|
		// then we've reached position (3) and so we say that
 | 
						|
		// we're ok for new data.
 | 
						|
		if (m_nPhase == m_nNumberOfPhases)
 | 
						|
		{
 | 
						|
			// at position (3) or (4)
 | 
						|
			if (!m_bLooping)
 | 
						|
			{
 | 
						|
				// position (3) only
 | 
						|
				// note that it seems that the sustain level is ALWAYS zero
 | 
						|
				// in the case of non-looping waveforms
 | 
						|
				m_bEnvelopeEnded = true;
 | 
						|
				bProcessNewDataIfAvailable = true;
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				// position (4) only
 | 
						|
				// note that any data already latched is ONLY acted upon
 | 
						|
				// at THIS point. If (after this Tick has completed) any new
 | 
						|
				// env data is written, it will NOT be acted upon, until
 | 
						|
				// we get back to position (4) again.
 | 
						|
				// this is why m_bEnvelopeEnded (which affects the behaviour
 | 
						|
				// of the SetEnvControl method) is FALSE here.
 | 
						|
				// See test case: envext_34c  (as noted earlier)
 | 
						|
				m_bEnvelopeEnded = false;
 | 
						|
				// set phase pointer to start of envelope for loop
 | 
						|
				// and reset m_nPhasePosition
 | 
						|
				m_nPhase=0;
 | 
						|
				m_nPhasePosition -= 16;
 | 
						|
				bProcessNewDataIfAvailable = true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		else // (m_nPhase < m_nNumberOfPhases)
 | 
						|
		{
 | 
						|
			// not at position (3) or (4) ... 
 | 
						|
			// (i.e., we're in the middle of an envelope with
 | 
						|
			//  more than one phase. Specifically, we're in
 | 
						|
			//  the middle of envelope 4 or 5 - the
 | 
						|
			//  triangle envelopes - but that's not important)
 | 
						|
 | 
						|
			// any commands sent to this envelope controller
 | 
						|
			// will be buffered. Set the flag to indicate this.
 | 
						|
			m_bEnvelopeEnded = false;
 | 
						|
			m_nPhasePosition -= 16;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else // (m_nPhasePosition < 16)
 | 
						|
	{
 | 
						|
		// still within the same phase;
 | 
						|
		// but, importantly, we are no longer at the start of the phase ...
 | 
						|
		// so new data cannot be acted on immediately, and must
 | 
						|
		// be buffered
 | 
						|
		m_bEnvelopeEnded = false;
 | 
						|
		// Phase and PhasePosition have already been updated.
 | 
						|
		// SetLevels() will need to be called to actually calculate
 | 
						|
		// the output 'level' of this envelope controller
 | 
						|
	}
 | 
						|
 | 
						|
	
 | 
						|
	// if we have new (buffered) data, now is the time to act on it
 | 
						|
	if (m_bNewData && bProcessNewDataIfAvailable)
 | 
						|
	{
 | 
						|
		m_bNewData = false;
 | 
						|
		SetNewEnvData(m_nNextData);
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		// ok, we didn't have any new buffered date to act on,
 | 
						|
		// so we just call SetLevels() to calculate the output level
 | 
						|
		// for whatever the current envelope is
 | 
						|
		SetLevels();
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
inline void CSAAEnv::SetLevels(void)
 | 
						|
{
 | 
						|
	// sets m_nLeftLevel 
 | 
						|
	// Also sets m_nRightLevel in terms of m_nLeftLevel
 | 
						|
	//								   and m_bInvertRightChannel
 | 
						|
 | 
						|
	// m_nResolution: 1 means 4-bit resolution; 2 means 3-bit resolution. Resolution of envelope waveform.
 | 
						|
 | 
						|
	// Note that this is handled 'immediately', and doesn't wait for synchronisation of
 | 
						|
	// the envelope waveform (this is important, see test case EnvExt_imm)
 | 
						|
	// It is therefore possible to switch between 4-bit and 3-bit resolution in the middle of
 | 
						|
	// an envelope waveform.  if you are at an 'odd' phase position, you would be able to hear
 | 
						|
	// the difference.  if you are at an 'even' phase position, the volume level for 4-bit
 | 
						|
	// and 3-bit would be the same.
 | 
						|
	// NOTE: additional test cases are required.
 | 
						|
 | 
						|
	switch (m_nResolution)
 | 
						|
	{
 | 
						|
		case 1: // 4 bit res waveforms
 | 
						|
		default:
 | 
						|
			{
 | 
						|
				// special case: if envelope is not a looping one, and we're at the end
 | 
						|
				// then our level should be zero (all of the non-looping waveforms have
 | 
						|
				// a sustain level of zero):
 | 
						|
				if (m_bEnvelopeEnded && !m_bLooping)
 | 
						|
					m_nLeftLevel = 0;
 | 
						|
				else
 | 
						|
					m_nLeftLevel = m_pEnvData->nLevels[0][m_nPhase][m_nPhasePosition];
 | 
						|
 | 
						|
				if (m_bInvertRightChannel)
 | 
						|
					m_nRightLevel = 15-m_nLeftLevel;
 | 
						|
				else
 | 
						|
					m_nRightLevel = m_nLeftLevel;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		case 2: // 3 bit res waveforms
 | 
						|
			{
 | 
						|
				// special case: if envelope is not a looping one, and we're at the end
 | 
						|
				// then our level should be zero (all of the non-looping waveforms have
 | 
						|
				// a sustain level of zero):
 | 
						|
				if (m_bEnvelopeEnded && !m_bLooping)
 | 
						|
					m_nLeftLevel = 0;
 | 
						|
				else
 | 
						|
					m_nLeftLevel = m_pEnvData->nLevels[1][m_nPhase][m_nPhasePosition];
 | 
						|
				if (m_bInvertRightChannel)
 | 
						|
					m_nRightLevel = 14-m_nLeftLevel;
 | 
						|
				else
 | 
						|
					m_nRightLevel = m_nLeftLevel;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
inline void CSAAEnv::SetNewEnvData(int nData)
 | 
						|
{
 | 
						|
	// loads envgenerator's registers according to the bits set
 | 
						|
	// in nData
 | 
						|
 | 
						|
	m_nPhase = 0;
 | 
						|
	m_nPhasePosition = 0;
 | 
						|
	m_pEnvData = &(cs_EnvData[(nData >> 1) & 0x07]);
 | 
						|
	m_bInvertRightChannel = ((nData & 0x01) == 0x01);
 | 
						|
	m_bClockExternally = ((nData & 0x20) == 0x20);
 | 
						|
	m_nNumberOfPhases = m_pEnvData->nNumberOfPhases;
 | 
						|
	m_bLooping = m_pEnvData->bLooping;
 | 
						|
	m_nResolution = (((nData & 0x10)==0x10) ? 2 : 1);
 | 
						|
	m_bEnabled = ((nData & 0x80) == 0x80);
 | 
						|
	if (m_bEnabled)
 | 
						|
	{
 | 
						|
		m_bEnvelopeEnded = false;
 | 
						|
		// is this right?
 | 
						|
		// YES.  See test case EnvExt_34c (setting data multiple times
 | 
						|
		// when at a point (3) resets the waveform so you're no longer
 | 
						|
		// at a point (3).
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		// DISABLED - so set stuff accordingly
 | 
						|
		m_bEnvelopeEnded = true;
 | 
						|
		m_nPhase = 0;
 | 
						|
		m_nPhasePosition = 0;
 | 
						|
	}
 | 
						|
	
 | 
						|
	SetLevels();
 | 
						|
}
 |