157 lines
4.8 KiB
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.

#pragma once
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include "synth_waveform.h"
/** FMOperator defines all settable paramaters to an operator.
* An FM operator consists of:
* - An input
* - An oscillator
* - An envelope generator
* Frequency Modulation happens by chaining oscillators together,
* using the output of one to modulate the frequency of the next.
* Oscillators generate waveforms in a shape defined by
* `synth_waveform.h`. WAVEFORM_SINE is a good one to start with.
* Other sensible options are SAWTOOTH, SQUARE, and TRIANGLE.
* Frequency for an oscillator is calculated with:
* offset + (voiceFrequency × multiplier)
* Oscillator frequency is then modulated by the level obtained
* by the input mixer: level of 1.0 shifts frequency up by
* 8 octaves, level of -1.0 shifts frequency down by 8 octaves.
* The envelope modifies amplitude of the oscillator output,
* using the following rules:
* - stay at 0 until Note On
* - stay at 0 for `delayTime` milliseconds
* - linear increase to `holdAmplitude` for `attackTime` milliseconds
* - stay at `holdAmplitude` for `holdTime` milliseconds
* - linear decrease to `sustainAmplitude` for `decayTime` milliseconds
* - stay at `sustainAmplitude` until Note Off
* - linear decrease to 0 for `releaseTime` milliseconds
typedef struct FMOperator {
// Oscillator
short waveform;
float offset;
float multiplier;
// Envelope
float delayTime;
float attackTime;
float holdAmplitude;
float holdTime;
float decayTime;
float sustainAmplitude;
float releaseTime;
} FMOperator;
/** FMPatch defines all parameters to a voice patch.
* This defines the "sound" of an FM voice,
* just like a "Patch" does in a hardware synthesizer.
* I think of a "patch" being the physical cables that
* connect oscillators together, and to the output mixer.
* Each operator has NUM_OPERATORS input gains,
* one output gain (to the voice output mixer),
* and NUM_OPERATORS operators.
* Historical FM synthisizers,
* such as the DX7 or DX9,
* used "algorithms" to patch operators into one another:
* this is done with 0.0 or 1.0 values to the gains.
* The "feedback" on operator 4 of the DX9
* can be accomplished by patching an operator into itself.
typedef struct FMPatch {
const char *name;
FMOperator operators[NUM_OPERATORS];
} FMPatch;
/** FMVoice sets up all the Audio objects for a voice.
class FMVoice {
/** LoadPatch loads a patch into a voice.
void LoadPatch(FMPatch *p);
/** SetPitch sets the pitch (Hz) of a voice.
* This does not signal the envelope in any way.
* You would use this for a glissando, portamento, or pitch bend.
* In my bagpipe, this prevents "reed noise" when changing notes.
void SetPitch(float pitch);
/** GetPitch returns the pitch (Hz) of a voice.
float GetPitch();
/** SetModulation sets the modulation amount of a voice.
* What this means depends on the loaded patch.
* For a "normal" bagpipe patch, this would adjust the intensity of
* of a filter, or set the level of an oscillator.
* In an old-school keyboard patch, this would set the
* intensity of a Low Frequency Oscillator to set a vibrato.
void setModulation(float level);
/** NoteOn sets the pitch (Hz) of a voice, and starts in playing.
* This tells the envelope generators to begin.
* On a piano, this is what you would use when a key is pressed.
* In my bagpipe, this triggers "reed noise".
void NoteOn(float pitch);
/** NoteOff stops a note from playing.
* This turns the voice "off" by shutting down all the envelope generators.
* On a piano, this is what you would use when a key is released.
* In my bagpipe, this corresponds to all holes being closed.
void NoteOff();
AudioMixer4 mixers[NUM_OPERATORS];
AudioSynthWaveformModulated oscillators[NUM_OPERATORS];
AudioEffectEnvelope envelopes[NUM_OPERATORS];
AudioMixer4 outputMixer;
FMPatch *patch;
float pitch;
bool playing;
/** FMOperatorWiring outputs AudioConnection initializers to wire one FM Operator
#define FMOperatorWiring(name, i) \
{name.mixers[i], 0, name.oscillators[i], 0}, \
{name.oscillators[i], 0, name.envelopes[i], 0}, \
{name.envelopes[i], 0, name.mixers[0], i}, \
{name.envelopes[i], 0, name.mixers[1], i}, \
{name.envelopes[i], 0, name.mixers[2], i}, \
{name.envelopes[i], 0, name.mixers[3], i}, \
{name.envelopes[i], 0, name.outputMixer, i}
/** FMVoiceWiring outputs AudioConnection initializer to wire one FMVoice
#define FMVoiceWiring(name) \
FMOperatorWiring(name, 0), \
FMOperatorWiring(name, 1), \
FMOperatorWiring(name, 2), \
FMOperatorWiring(name, 3)