1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 23:52:26 +00:00
CLK/Components/OPx/OPLL.cpp

449 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// OPLL.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "OPLL.hpp"
#include <cassert>
using namespace Yamaha::OPL;
OPLL::OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider, bool is_vrc7):
OPLBase(task_queue), audio_divider_(audio_divider), is_vrc7_(is_vrc7) {
// Due to the way that sound mixing works on the OPLL, the audio divider may not
// be larger than 4.
assert(audio_divider <= 4);
// Setup the rhythm envelope generators.
// Treat the bass exactly as if it were a melodic channel.
rhythm_envelope_generators_[BassCarrier].set_should_damp([this] {
// Propagate attack mode to the modulator, and reset both phases.
rhythm_envelope_generators_[BassModulator].set_key_on(true);
phase_generators_[6 + 0].reset();
phase_generators_[6 + 9].reset();
});
// Set the other drums to damp, but only the TomTom to affect phase.
rhythm_envelope_generators_[TomTom].set_should_damp([this] {
phase_generators_[8 + 9].reset();
});
rhythm_envelope_generators_[Snare].set_should_damp({});
rhythm_envelope_generators_[Cymbal].set_should_damp({});
rhythm_envelope_generators_[HighHat].set_should_damp({});
// Crib the proper rhythm envelope generator settings by installing
// the rhythm instruments and copying them over.
rhythm_mode_enabled_ = true;
install_instrument(6);
install_instrument(7);
install_instrument(8);
rhythm_envelope_generators_[BassCarrier] = envelope_generators_[6];
rhythm_envelope_generators_[BassModulator] = envelope_generators_[6 + 9];
rhythm_envelope_generators_[HighHat] = envelope_generators_[7 + 9];
rhythm_envelope_generators_[Cymbal] = envelope_generators_[8];
rhythm_envelope_generators_[TomTom] = envelope_generators_[8 + 9];
rhythm_envelope_generators_[Snare] = envelope_generators_[7];
// Return to ordinary default mode.
rhythm_mode_enabled_ = false;
// Set up damping for the melodic channels.
for(int c = 0; c < 9; ++c) {
envelope_generators_[c].set_should_damp([this, c] {
// Propagate attack mode to the modulator, and reset both phases.
envelope_generators_[c + 9].set_key_on(true);
phase_generators_[c + 0].reset();
phase_generators_[c + 9].reset();
});
}
// Set default instrument.
for(int c = 0; c < 9; ++c) {
install_instrument(c);
}
}
// MARK: - Machine-facing programmatic input.
void OPLL::write_register(uint8_t address, uint8_t value) {
// The OPLL doesn't have timers or other non-audio functions, so all writes
// go to the audio queue.
task_queue_.enqueue([this, address, value] {
// The first 8 locations are used to define the custom instrument, and have
// exactly the same format as the patch set arrays at the head of this file.
if(address < 8) {
custom_instrument_[address] = value;
// Update all channels that refer to instrument 0.
for(int c = 0; c < 9; ++c) {
if(!channels_[c].instrument) {
install_instrument(c);
}
}
return;
}
// Register 0xe enables or disables rhythm mode and contains the
// percussion key-on bits.
if(address == 0xe) {
const bool old_rhythm_mode = rhythm_mode_enabled_;
rhythm_mode_enabled_ = value & 0x20;
if(old_rhythm_mode != rhythm_mode_enabled_) {
// Change the instlled instruments for channels 6, 7 and 8
// if this was a transition into or out of rhythm mode.
install_instrument(6);
install_instrument(7);
install_instrument(8);
}
rhythm_envelope_generators_[HighHat].set_key_on(value & 0x01);
rhythm_envelope_generators_[Cymbal].set_key_on(value & 0x02);
rhythm_envelope_generators_[TomTom].set_key_on(value & 0x04);
rhythm_envelope_generators_[Snare].set_key_on(value & 0x08);
if(value & 0x10) {
rhythm_envelope_generators_[BassCarrier].set_key_on(true);
} else {
rhythm_envelope_generators_[BassCarrier].set_key_on(false);
rhythm_envelope_generators_[BassModulator].set_key_on(false);
}
return;
}
// That leaves only per-channel selections, for which the addressing
// is completely orthogonal; check that a valid channel is being requested.
const auto index = address & 0xf;
if(index > 8) return;
switch(address & 0xf0) {
default: break;
// Address 1x sets the low 8 bits of the period for channel x.
case 0x10:
channels_[index].period = (channels_[index].period & ~0xff) | value;
set_channel_period(index);
return;
// Address 2x Sets the octave and a single bit of the frequency, as well
// as setting key on and sustain mode.
case 0x20:
channels_[index].period = (channels_[index].period & 0xff) | ((value & 1) << 8);
channels_[index].octave = (value >> 1) & 7;
set_channel_period(index);
// In this implementation the first 9 envelope generators are for
// channel carriers, and their will_attack callback is used to trigger
// key-on for modulators. But key-off needs to be set to both envelope
// generators now.
if(value & 0x10) {
envelope_generators_[index].set_key_on(true);
} else {
envelope_generators_[index + 0].set_key_on(false);
envelope_generators_[index + 9].set_key_on(false);
}
// Set sustain bit to both the relevant operators.
channels_[index].use_sustain = value & 0x20;
set_use_sustain(index);
return;
// Address 3x selects the instrument and attenuation for a channel;
// in rhythm mode some of the nibbles that ordinarily identify instruments
// instead nominate additional attenuations. This code reads those back
// from the stored instrument values.
case 0x30:
channels_[index].attenuation = value & 0xf;
// Install an instrument only if it's new.
if(channels_[index].instrument != value >> 4) {
channels_[index].instrument = value >> 4;
if(index < 6 || !rhythm_mode_enabled_) {
install_instrument(index);
}
}
return;
}
});
}
void OPLL::set_channel_period(int channel) {
phase_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
phase_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
envelope_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
envelope_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
key_level_scalers_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
key_level_scalers_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
}
const uint8_t *OPLL::instrument_definition(int instrument, int channel) {
// Divert to the appropriate rhythm instrument if in rhythm mode.
if(channel >= 6 && rhythm_mode_enabled_) {
return &percussion_patch_set[(channel - 6) * 8];
}
// Instrument 0 is the custom instrument.
if(!instrument) return custom_instrument_;
// Instruments other than 0 are taken from the fixed set.
const int index = (instrument - 1) * 8;
return is_vrc7_ ? &vrc7_patch_set[index] : &opll_patch_set[index];
}
void OPLL::install_instrument(int channel) {
auto &carrier_envelope = envelope_generators_[channel + 0];
auto &carrier_phase = phase_generators_[channel + 0];
auto &carrier_scaler = key_level_scalers_[channel + 0];
auto &modulator_envelope = envelope_generators_[channel + 9];
auto &modulator_phase = phase_generators_[channel + 9];
auto &modulator_scaler = key_level_scalers_[channel + 9];
const uint8_t *const instrument = instrument_definition(channels_[channel].instrument, channel);
// Bytes 0 (modulator) and 1 (carrier):
//
// b0-b3: multiplier;
// b4: key-scale rate enable;
// b5: sustain-level enable;
// b6: vibrato enable;
// b7: tremolo enable.
modulator_phase.set_multiple(instrument[0] & 0xf);
channels_[channel].modulator_key_rate_scale_multiplier = (instrument[0] >> 4) & 1;
modulator_phase.set_vibrato_enabled(instrument[0] & 0x40);
modulator_envelope.set_tremolo_enabled(instrument[0] & 0x80);
carrier_phase.set_multiple(instrument[1] & 0xf);
channels_[channel].carrier_key_rate_scale_multiplier = (instrument[1] >> 4) & 1;
carrier_phase.set_vibrato_enabled(instrument[1] & 0x40);
carrier_envelope.set_tremolo_enabled(instrument[1] & 0x80);
// Pass off bit 5.
set_use_sustain(channel);
// Byte 2:
//
// b0b5: modulator attenuation;
// b6b7: modulator key-scale level.
modulator_scaler.set_key_scaling_level(instrument[3] >> 6);
channels_[channel].modulator_attenuation = instrument[2] & 0x3f;
// Byte 3:
//
// b0b2: modulator feedback level;
// b3: modulator waveform selection;
// b4: carrier waveform selection;
// b5: [unused]
// b6b7: carrier key-scale level.
channels_[channel].modulator_feedback = instrument[3] & 7;
channels_[channel].modulator_waveform = Waveform((instrument[3] >> 3) & 1);
channels_[channel].carrier_waveform = Waveform((instrument[3] >> 4) & 1);
carrier_scaler.set_key_scaling_level(instrument[3] >> 6);
// Bytes 4 (modulator) and 5 (carrier):
//
// b0b3: decay rate;
// b4b7: attack rate.
modulator_envelope.set_decay_rate(instrument[4] & 0xf);
modulator_envelope.set_attack_rate(instrument[4] >> 4);
carrier_envelope.set_decay_rate(instrument[5] & 0xf);
carrier_envelope.set_attack_rate(instrument[5] >> 4);
// Bytes 6 (modulator) and 7 (carrier):
//
// b0b3: release rate;
// b4b7: sustain level.
modulator_envelope.set_release_rate(instrument[6] & 0xf);
modulator_envelope.set_sustain_level(instrument[6] >> 4);
carrier_envelope.set_release_rate(instrument[7] & 0xf);
carrier_envelope.set_sustain_level(instrument[7] >> 4);
}
void OPLL::set_use_sustain(int channel) {
const uint8_t *const instrument = instrument_definition(channels_[channel].instrument, channel);
envelope_generators_[channel + 0].set_use_sustain_level((instrument[1] & 0x20) || channels_[channel].use_sustain);
envelope_generators_[channel + 9].set_use_sustain_level((instrument[0] & 0x20) || channels_[channel].use_sustain);
}
// MARK: - Output generation.
void OPLL::set_sample_volume_range(std::int16_t range) {
total_volume_ = range;
}
template <Outputs::Speaker::Action action>
void OPLL::apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target) {
// Both the OPLL and the OPL2 divide the input clock by 72 to get the base tick frequency;
// unlike the OPL2 the OPLL time-divides the output for 'mixing'.
const int update_period = 72 / audio_divider_;
const int channel_output_period = 4 / audio_divider_;
// TODO: the conditional below is terrible. Fix.
while(number_of_samples--) {
if(!audio_offset_) update_all_channels();
Outputs::Speaker::apply<action>(*target, output_levels_[audio_offset_ / channel_output_period]);
++target;
audio_offset_ = (audio_offset_ + 1) % update_period;
}
}
template void OPLL::apply_samples<Outputs::Speaker::Action::Mix>(std::size_t, Outputs::Speaker::MonoSample *);
template void OPLL::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void OPLL::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
void OPLL::update_all_channels() {
oscillator_.update();
// Update all phase generators. That's guaranteed.
for(int c = 0; c < 18; ++c) {
phase_generators_[c].update(oscillator_);
}
// Update the ADSR envelopes that are guaranteed to be melodic.
for(int c = 0; c < 6; ++c) {
envelope_generators_[c + 0].update(oscillator_);
envelope_generators_[c + 9].update(oscillator_);
}
#define VOLUME(x) int16_t(((x) * total_volume_) >> 12)
if(rhythm_mode_enabled_) {
// Advance the rhythm envelope generators.
for(int c = 0; c < 6; ++c) {
rhythm_envelope_generators_[c].update(oscillator_);
}
// Fill in the melodic channels.
output_levels_[3] = VOLUME(melodic_output(0));
output_levels_[4] = VOLUME(melodic_output(1));
output_levels_[5] = VOLUME(melodic_output(2));
output_levels_[9] = VOLUME(melodic_output(3));
output_levels_[10] = VOLUME(melodic_output(4));
output_levels_[11] = VOLUME(melodic_output(5));
// Bass drum, which is a regular FM effect.
output_levels_[2] = output_levels_[15] = VOLUME(bass_drum());
oscillator_.update_lfsr();
// Tom tom, which is a single operator.
output_levels_[1] = output_levels_[14] = VOLUME(tom_tom());
oscillator_.update_lfsr();
// Snare.
output_levels_[6] = output_levels_[16] = VOLUME(snare_drum());
oscillator_.update_lfsr();
// Cymbal.
output_levels_[7] = output_levels_[17] = VOLUME(cymbal());
oscillator_.update_lfsr();
// High-hat.
output_levels_[0] = output_levels_[13] = VOLUME(high_hat());
oscillator_.update_lfsr();
// Unutilised slots.
output_levels_[8] = output_levels_[12] = 0;
oscillator_.update_lfsr();
} else {
for(int c = 6; c < 9; ++c) {
envelope_generators_[c + 0].update(oscillator_);
envelope_generators_[c + 9].update(oscillator_);
}
// All melodic. Fairly easy.
output_levels_[0] = output_levels_[1] = output_levels_[2] =
output_levels_[6] = output_levels_[7] = output_levels_[8] =
output_levels_[12] = output_levels_[13] = output_levels_[14] = 0;
output_levels_[3] = VOLUME(melodic_output(0));
output_levels_[4] = VOLUME(melodic_output(1));
output_levels_[5] = VOLUME(melodic_output(2));
output_levels_[9] = VOLUME(melodic_output(3));
output_levels_[10] = VOLUME(melodic_output(4));
output_levels_[11] = VOLUME(melodic_output(5));
output_levels_[15] = VOLUME(melodic_output(6));
output_levels_[16] = VOLUME(melodic_output(7));
output_levels_[17] = VOLUME(melodic_output(8));
}
#undef VOLUME
// TODO: batch updates of the LFSR.
}
// TODO: verify attenuation scales pervasively below.
#define ATTENUATION(x) ((x) << 7)
int OPLL::melodic_output(int channel) {
// The modulator always updates after the carrier, oddly enough. So calculate actual output first, based on the modulator's last value.
auto carrier = WaveformGenerator<period_precision>::wave(channels_[channel].carrier_waveform, phase_generators_[channel].scaled_phase(), channels_[channel].modulator_output);
carrier += envelope_generators_[channel].attenuation() + ATTENUATION(channels_[channel].attenuation) + key_level_scalers_[channel].attenuation();
// Get the modulator's new value.
auto modulation = WaveformGenerator<period_precision>::wave(channels_[channel].modulator_waveform, phase_generators_[channel + 9].phase());
modulation += envelope_generators_[channel + 9].attenuation() + (channels_[channel].modulator_attenuation << 5) + key_level_scalers_[channel + 9].attenuation();
// Apply feedback, if any.
phase_generators_[channel + 9].apply_feedback(channels_[channel].modulator_output, modulation, channels_[channel].modulator_feedback);
channels_[channel].modulator_output = modulation;
return carrier.level();
}
int OPLL::bass_drum() {
// Use modulator 6 and carrier 6, attenuated as per the bass-specific envelope generators and the attenuation level for channel 6.
auto modulation = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6 + 9].phase());
modulation += rhythm_envelope_generators_[RhythmIndices::BassModulator].attenuation();
auto carrier = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6].scaled_phase(), modulation);
carrier += rhythm_envelope_generators_[RhythmIndices::BassCarrier].attenuation() + ATTENUATION(channels_[6].attenuation);
return carrier.level();
}
int OPLL::tom_tom() {
// Use modulator 8 and the 'instrument' selection for channel 8 as an attenuation.
auto tom_tom = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[8 + 9].phase());
tom_tom += rhythm_envelope_generators_[RhythmIndices::TomTom].attenuation();
tom_tom += ATTENUATION(channels_[8].instrument);
return tom_tom.level();
}
int OPLL::snare_drum() {
// Use modulator 7 and the carrier attenuation level for channel 7.
LogSign snare = WaveformGenerator<period_precision>::snare(oscillator_, phase_generators_[7 + 9].phase());
snare += rhythm_envelope_generators_[RhythmIndices::Snare].attenuation();
snare += ATTENUATION(channels_[7].attenuation);
return snare.level();
}
int OPLL::cymbal() {
// Use modulator 7, carrier 8 and the attenuation level for channel 8.
LogSign cymbal = WaveformGenerator<period_precision>::cymbal(phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
cymbal += rhythm_envelope_generators_[RhythmIndices::Cymbal].attenuation();
cymbal += ATTENUATION(channels_[8].attenuation);
return cymbal.level();
}
int OPLL::high_hat() {
// Use modulator 7, carrier 8 a and the 'instrument' selection for channel 7 as an attenuation.
LogSign high_hat = WaveformGenerator<period_precision>::high_hat(oscillator_, phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
high_hat += rhythm_envelope_generators_[RhythmIndices::HighHat].attenuation();
high_hat += ATTENUATION(channels_[7].instrument);
return high_hat.level();
}
#undef ATTENUATION