gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

dspMultiTI.cpp (7219B)


      1 #include "dspMultiTI.h"
      2 
      3 namespace virusLib
      4 {
      5 	constexpr uint32_t g_esai1TxBlockSize = 6 * 3 * 2;		// 6 = number of TX pins, 3 = number of slots per frame, 2 = double data rate
      6 	constexpr uint32_t g_esai1TxPadding = g_esai1TxBlockSize;
      7 
      8 	constexpr uint32_t g_esai1RxBlockSize = 4 * 3 * 2;		// 4 = number of RX pins, 3 = number of slots per frame, 2 = double data rate
      9 	constexpr uint32_t g_esai1RxPadding = g_esai1RxBlockSize;
     10 
     11 	constexpr uint32_t g_magicIntervalSamples = 256;
     12 
     13 	static constexpr dsp56k::TWord g_magicNumber	= 0xedc987;
     14 	
     15 	void audioAdd(float& _dst, float _src)
     16 	{
     17 		_dst += _src;
     18 	}
     19 
     20 	void audioAdd(dsp56k::TWord& _dst, const dsp56k::TWord _src)
     21 	{
     22 		auto dst = dsp56k::signextend<int32_t, 24>(static_cast<int32_t>(_dst));
     23 		dst += dsp56k::signextend<int32_t, 24>(static_cast<int32_t>(_src));
     24 		_dst = static_cast<dsp56k::TWord>(dst) & 0xffffff;
     25 	}
     26 
     27 	template <typename T> void ensureSize(std::vector<T>& _buf, size_t _size)
     28 	{
     29 		if(_buf.size() > _size)
     30 			return;
     31 		_buf.resize(_size, 0);
     32 	}
     33 
     34 	template <typename T> void wrapPadding(std::vector<T>& _buf, const uint32_t _usedSize)
     35 	{
     36 		size_t iSrc = _usedSize;
     37 
     38 		for (size_t i=0; i<g_esai1TxPadding; ++i, ++iSrc)
     39 			_buf[i] = _buf[iSrc];
     40 	}
     41 
     42 	template <typename T> void DspMultiTI::Esai1Out::processAudioOutput(dsp56k::Esai& _esai, uint32_t _frames, const synthLib::TAudioOutputsT<T>& _outputs, uint32_t _firstOutChannel, const std::array<uint32_t, 6>& _sourceIndices)
     43 	{
     44 		ensureSize(*this, _frames * g_esai1TxBlockSize + g_esai1TxPadding);
     45 
     46 		_esai.processAudioOutput(data() + g_esai1TxPadding, _frames * 2);
     47 
     48 		if(m_blockStart == InvalidOffset)
     49 		{
     50 			for(uint32_t i=g_esai1TxPadding; i<g_esai1TxBlockSize * _frames + g_esai1TxPadding; ++i)
     51 			{
     52 				if(at(i) != g_magicNumber)
     53 					continue;
     54 
     55 				// OS writes the magic value to position $b of its internal ring buffer
     56 				const auto off = i - 0xb;
     57 
     58 				m_blockStart = off / g_esai1TxBlockSize;	
     59 				m_blockStart = off - m_blockStart * g_esai1TxBlockSize;
     60 				break;
     61 			}
     62 		}
     63 
     64 		if(m_blockStart != InvalidOffset)
     65 		{
     66 			const auto* p = &at(m_blockStart);
     67 
     68 			for(size_t i=0; i<_frames; ++i)
     69 			{
     70 				_outputs[_firstOutChannel  ][i] += dsp56k::dsp2sample<T>(p[_sourceIndices[0]]);
     71 				_outputs[_firstOutChannel+1][i] += dsp56k::dsp2sample<T>(p[_sourceIndices[1]]);
     72 				_outputs[_firstOutChannel+2][i] += dsp56k::dsp2sample<T>(p[_sourceIndices[2]]);
     73 				_outputs[_firstOutChannel+3][i] += dsp56k::dsp2sample<T>(p[_sourceIndices[3]]);
     74 				_outputs[_firstOutChannel+4][i] += dsp56k::dsp2sample<T>(p[_sourceIndices[4]]);
     75 				_outputs[_firstOutChannel+5][i] += dsp56k::dsp2sample<T>(p[_sourceIndices[5]]);
     76 
     77 				p += g_esai1TxBlockSize;
     78 			}
     79 		}
     80 
     81 		wrapPadding(*this, _frames * g_esai1TxBlockSize);
     82 	}
     83 
     84 	template <typename T> void DspMultiTI::Esai1in::processAudioinput(dsp56k::Esai& _esai, uint32_t _frames, uint32_t _latency, const synthLib::TAudioInputsT<T>& _inputs)
     85 	{
     86 		ensureSize(*this, _frames * g_esai1RxBlockSize);
     87 
     88 		uint32_t blockIdx = 0;
     89 
     90 		static volatile uint32_t offset = 1;
     91 
     92 		for(uint32_t i=0; i<_frames; ++i)
     93 		{
     94 			at(blockIdx + offset + (g_esai1RxBlockSize>>1)) = dsp56k::sample2dsp<T>(_inputs[0][i]);
     95 			at(blockIdx + offset                          ) = dsp56k::sample2dsp<T>(_inputs[1][i]);
     96 
     97 			blockIdx += g_esai1RxBlockSize;
     98 		}
     99 
    100 		_esai.processAudioInput(data(), _frames * 2, 3, _latency * 2);
    101 	}
    102 
    103 	DspMultiTI::DspMultiTI() : DspSingle(0x100000, true, "DSP A"), m_dsp2(0x100000, true, "DSP B")
    104 	{
    105 		getHDI08().writeHDR(0x0000);			// this = Master
    106 		m_dsp2.getHDI08().writeHDR(0x8000);		// m_dsp2 = Slave
    107 
    108 		getPeriphX().getEsai().writeEmptyAudioIn(2);
    109 		m_dsp2.getPeriphX().getEsai().writeEmptyAudioIn(2);
    110 	}
    111 
    112 	template <typename T>
    113 	void processAudioTI(DspMultiTI& _dsp, DspSingle& _dsp2, DspMultiTI::EsaiBufs<T>& _buffers, const synthLib::TAudioInputsT<T>& _inputs, synthLib::TAudioOutputsT<T> _outputs, const size_t _samples, const uint32_t _latency)
    114 	{
    115 		const auto s = static_cast<uint32_t>(_samples);
    116 
    117 		// ESAI inputs
    118 		ensureSize(_buffers.dummyInput, _samples << 1);
    119 		const T* in = _buffers.dummyInput.data();
    120 		const T* inputs[8] = { in, in, in, in, in, in, in, in };
    121 
    122 		// Master ESAI input might be the USB input, we don't need it as we only have one input
    123 		_dsp.getPeriphX().getEsai().processAudioInputInterleaved(inputs, s, _latency);
    124 
    125 		// Master ESAI_1 input gets the analog input from the slave, inject the interleaved input here
    126 		_buffers.in.processAudioinput(_dsp.getPeriphY().getEsai(), s, _latency, _inputs);
    127 
    128 		// Slave ESAI input gets the analog input in regular fashion
    129 
    130 		_dsp2.getPeriphX().getEsai().processAudioInput<T>(1, _latency, [&](size_t _s, dsp56k::Audio::RxFrame& _frame)
    131 		{
    132 			_frame.resize(2);
    133 			_frame[0][0] = dsp56k::sample2dsp<T>(_inputs[0][0]);
    134 			_frame[1][0] = dsp56k::sample2dsp<T>(_buffers.m_previousInput);
    135 		});
    136 
    137 		_dsp2.getPeriphX().getEsai().processAudioInput<T>(s - 1, _latency, [&](size_t _s, dsp56k::Audio::RxFrame& _frame)
    138 		{
    139 			_frame.resize(2);
    140 			_frame[0][0] = dsp56k::sample2dsp<T>(_inputs[0][_s]);
    141 			_frame[1][0] = dsp56k::sample2dsp<T>(_inputs[1][_s+1]);
    142 		});
    143 
    144 		_buffers.m_previousInput = _inputs[1][s-1];
    145 
    146 		// Slave ESAI_1 does not get the ADC at all but only data from the master, we don't need it here
    147 		_dsp2.getPeriphY().getEsai().processAudioInputInterleaved(inputs, s * 2, _latency * 2);
    148 
    149 		// ESAI outputs
    150 		ensureSize(_buffers.dummyOutput, _samples << 1);
    151 		for (auto& o : _outputs)
    152 		{
    153 			if(!o)
    154 				o = _buffers.dummyOutput.data();
    155 		}
    156 
    157 		T* out = _buffers.dummyOutput.data();
    158 		T* outputs[12] = {out, out, out, out, out, out, out, out, out, out, out, out};
    159 
    160 		// DAC outputs on the Master are sent in regular fashion
    161 		outputs[4] = _outputs[0];		outputs[5] = _outputs[1];
    162 		outputs[6] = _outputs[2];		outputs[7] = _outputs[3];
    163 		outputs[8] = _outputs[4];		outputs[9] = _outputs[5];
    164 
    165 		_dsp.getPeriphX().getEsai().processAudioOutputInterleaved(outputs, s);
    166 
    167 		// USB outputs on the Slave are sent in regular fashion
    168 		outputs[4] = _outputs[6];		outputs[5] = _outputs[7];
    169 		outputs[6] = _outputs[8];		outputs[7] = _outputs[9];
    170 		outputs[8] = _outputs[10];		outputs[9] = _outputs[11];
    171 
    172 		_dsp2.getPeriphX().getEsai().processAudioOutputInterleaved(outputs, s);
    173 
    174 		// ESAI_1 outputs
    175 		constexpr auto halfBS = g_esai1TxBlockSize >> 1;
    176 
    177 		// USB outputs on the Master are sent to the Slave via ESAI_1 in 1/3 interleaved format => unpack it
    178 		_buffers.dspA.processAudioOutput(_dsp.getPeriphY().getEsai(), s, _outputs, 6, {
    179 			5, 5+halfBS,
    180 			16+halfBS, 16,
    181 			17+halfBS, 17
    182 		});
    183 
    184 		// DAC outputs on the Slave are send via ESAI_1 to the Master in 1/3 interleaved format => unpack it
    185 		_buffers.dspB.processAudioOutput(_dsp2.getPeriphY().getEsai(), s, _outputs, 0, {
    186 			4,4+halfBS,
    187 			5,5+halfBS,
    188 			16+halfBS,16
    189 		});
    190 	}
    191 
    192 	void DspMultiTI::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples, uint32_t _latency)
    193 	{
    194 		processAudioTI(*this, m_dsp2, m_bufferF, _inputs, _outputs, _samples, _latency);
    195 	}
    196 
    197 	void DspMultiTI::processAudio(const synthLib::TAudioInputsInt& _inputs, const synthLib::TAudioOutputsInt& _outputs, size_t _samples, uint32_t _latency)
    198 	{
    199 		processAudioTI(*this, m_dsp2, m_bufferI, _inputs, _outputs, _samples, _latency);
    200 	}
    201 }