Seems like I finally found an acceptable implementation for the rhythm mode. As I can't commit the code right now, I'll post the relevant code here.
Operator handling:
- Code: Select all
int16_t Operator::handleTopCymbal( uint8_t ws )
{
// The Top Cymbal operator uses its own phase together with the High Hat phase.
const uint16_t hhPhase = m_opl->highHatOperator()->m_phase;
const uint16_t phaseBit = ( ( ( hhPhase & 0x88 ) ^ ( ( hhPhase << 5 ) & 0x80 ) ) | ( ( hhPhase ^ ( hhPhase << 2 ) ) & 0x20 ) ) ? 0x02 : 0x00;
auto phase = ( 1 + phaseBit ) << 8;
return getOutput( phase, ws );
}
int16_t Operator::handleHighHat( uint8_t ws )
{
const uint16_t phaseBit = ( ( ( m_phase & 0x88 ) ^ ( ( m_phase << 5 ) & 0x80 ) ) | ( ( m_phase ^ ( m_phase << 2 ) ) & 0x20 ) ) ? 0x02 : 0x00;
const uint16_t noiseBit = m_opl->randBit() << 1;
auto phase = ( phaseBit << 8 ) | ( 0x34 << ( phaseBit ^ noiseBit ) );
return getOutput( phase, ws );
}
int16_t Operator::handleSnareDrum( uint8_t ws )
{
const uint16_t hhPhase = m_opl->highHatOperator()->m_phase;
const uint16_t noiseBit = m_opl->randBit() << 8;
auto phase = ( 0x100 + ( hhPhase & 0x100 ) ) ^ noiseBit;
return getOutput( phase, ws );
}
int16_t Operator::nextSample( uint16_t modulator )
{
const bool isRhythm = m_opl->ryt() && m_operatorBaseAddress>=0x10 && m_operatorBaseAddress<=0x15;
m_envelopeGenerator.advance( m_egt && !isRhythm, m_am );
m_phase = m_phaseGenerator.advance( m_vib );
// If it is in OPL2 mode, use first four waveforms only:
const uint8_t ws = m_opl->isNew() ? m_ws : ( m_ws & 0x03 );
if( isRhythm ) {
static constexpr int BassDrumOperator1 = 0x10; // Channel 7, operator 13
static constexpr int HighHatOperator = 0x11; // Channel 8, operator 14
static constexpr int TomTomOperator = 0x12; // Channel 9, operator 15
static constexpr int BassDrumOperator2 = 0x13; // Channel 7, operator 16
static constexpr int SnareDrumOperator = 0x14; // Channel 9, operator 18
static constexpr int TopCymbalOperator = 0x15; // Channel 8, operator 17
switch( m_operatorBaseAddress ) {
case BassDrumOperator1:
case BassDrumOperator2:
case TomTomOperator:
return getOutput( modulator + m_phase, ws );
case HighHatOperator:
return handleHighHat( ws ) + modulator;
case SnareDrumOperator:
return handleSnareDrum( ws ) + modulator;
case TopCymbalOperator:
return handleTopCymbal( ws ) + modulator;
}
}
return getOutput( modulator + m_phase, ws );
}
Slot handling:
- Code: Select all
void Channel::nextSample2Op( std::array< int16_t, 4 >* dest )
{
int16_t channelOutput = m_operators[0]->nextSample( feedback() );
pushFeedback( channelOutput );
if( !m_cnt ) {
// CNT = 0, the operators are in series, with the first in feedback.
channelOutput = m_operators[1]->nextSample( channelOutput );
}
else {
// CNT = 1, the operators are in parallel, with the first in feedback.
if( isRhythmChannel() && m_operators[0] == m_opl->bassDrumOp1() )
channelOutput = 0; // the first bass drum operator is ignored when in parallel
channelOutput += m_operators[1]->nextSample();
}
if(isRhythmChannel())
channelOutput *= 2;
getInFourChannels( dest, channelOutput );
}
Please note that the highhat phase is used everywhere, and that there is no phase manipulation anymore. TC and HH use the same phase bit, but different noise calculations, and SD uses a completely different noise calculation without phase bit calculation. Also, in rhythm mode, there's no feedback calculated at all, except for TT and BD, and the sample output is doubled in any case.
The SD sounds a bit off sometimes, but I believe this could be an issue of the player code.