mirror of
https://github.com/TomHarte/CLK.git
synced 2024-08-21 04:29:34 +00:00
121 lines
3.3 KiB
C++
121 lines
3.3 KiB
C++
//
|
|
// PhaseGenerator.h
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 30/04/2020.
|
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include <cassert>
|
|
#include "LowFrequencyOscillator.hpp"
|
|
#include "Tables.hpp"
|
|
|
|
namespace Yamaha::OPL {
|
|
|
|
/*!
|
|
Models an OPL-style phase generator of templated precision; having been told its period ('f-num'), octave ('block') and
|
|
multiple, and whether to apply vibrato, this will then appropriately update and return phase.
|
|
*/
|
|
template <int precision> class PhaseGenerator {
|
|
public:
|
|
/*!
|
|
Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
|
|
*/
|
|
void update(const LowFrequencyOscillator &oscillator) {
|
|
constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
|
|
constexpr int vibrato_signs[2] = {1, -1};
|
|
|
|
// Get just the top three bits of the period_.
|
|
const int top_freq = period_ >> (precision - 3);
|
|
|
|
// Cacluaute applicable vibrato as a function of (i) the top three bits of the
|
|
// oscillator period; (ii) the current low-frequency oscillator vibrato output; and
|
|
// (iii) whether vibrato is enabled.
|
|
const int vibrato = (top_freq >> vibrato_shifts[oscillator.vibrato & 3]) * vibrato_signs[oscillator.vibrato >> 2] * enable_vibrato_;
|
|
|
|
// Apply phase update with vibrato from the low-frequency oscillator.
|
|
phase_ += (multiple_ * ((period_ << 1) + vibrato) << octave_) >> 1;
|
|
}
|
|
|
|
|
|
/*!
|
|
@returns Current phase; real hardware provides only the low ten bits of this result.
|
|
*/
|
|
int phase() const {
|
|
// My table if multipliers is multiplied by two, so shift by one more
|
|
// than the stated precision.
|
|
return phase_ >> precision_shift;
|
|
}
|
|
|
|
/*!
|
|
@returns Current phase, scaled up by (1 << precision).
|
|
*/
|
|
int scaled_phase() const {
|
|
return phase_ >> 1;
|
|
}
|
|
|
|
/*!
|
|
Applies feedback based on two historic samples of a total output level,
|
|
plus the degree of feedback to apply
|
|
*/
|
|
void apply_feedback(LogSign first, LogSign second, int level) {
|
|
constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
|
|
phase_ += ((second.level(precision) + first.level(precision)) >> (8 - level)) & masks[level];
|
|
}
|
|
|
|
/*!
|
|
Sets the multiple for this phase generator, in the same terms as an OPL programmer,
|
|
i.e. a 4-bit number that is used as a lookup into the internal multiples table.
|
|
*/
|
|
void set_multiple(int multiple) {
|
|
// This encodes the MUL -> multiple table given on page 12,
|
|
// multiplied by two.
|
|
constexpr int multipliers[] = {
|
|
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
|
|
};
|
|
assert(multiple < 16);
|
|
multiple_ = multipliers[multiple];
|
|
}
|
|
|
|
/*!
|
|
Sets the period of this generator, along with its current octave.
|
|
|
|
Yamaha tends to refer to the period as the 'f-number', and used both 'octave' and 'block' for octave.
|
|
*/
|
|
void set_period(int period, int octave) {
|
|
period_ = period;
|
|
octave_ = octave;
|
|
|
|
assert(octave_ < 8);
|
|
assert(period_ < (1 << precision));
|
|
}
|
|
|
|
/*!
|
|
Enables or disables vibrato.
|
|
*/
|
|
void set_vibrato_enabled(bool enabled) {
|
|
enable_vibrato_ = int(enabled);
|
|
}
|
|
|
|
/*!
|
|
Resets the current phase.
|
|
*/
|
|
void reset() {
|
|
phase_ = 0;
|
|
}
|
|
|
|
private:
|
|
static constexpr int precision_shift = 1 + precision;
|
|
|
|
int phase_ = 0;
|
|
|
|
int multiple_ = 0;
|
|
int period_ = 0;
|
|
int octave_ = 0;
|
|
int enable_vibrato_ = 0;
|
|
};
|
|
|
|
}
|