Better-defined voices, to prepare for polyphony

This commit is contained in:
Neale Pickett 2020-10-24 20:15:18 -06:00
parent 5ad56d8e92
commit 706a00b28f
8 changed files with 390 additions and 154 deletions

8
Makefile Normal file
View File

@ -0,0 +1,8 @@
BOARD = --board adafruit:samd:adafruit_trellis_m4
verify: uilleann.ino
arduino --verify $(BOARD) $<
install: uilleann.ino
arduino --upload $(BOARD) $<

91
dx9.h Normal file
View File

@ -0,0 +1,91 @@
// FM Algorithms used by the DX9
// Excellent write-up:
// https://gist.github.com/bryc/e997954473940ad97a825da4e7a496fa
#pragma once
// Each operator has 4 input gains and one output gain:
// { 1→, 2→, 3→, 4→, →out}
// ⮎4→3→2→1→
#define DX9_ALG_1(feedback) \
{ \
{0, 1, 0, 0, 1}, \
{0, 0, 1, 0, 0}, \
{0, 0, 0, 1, 0}, \
{0, 0, 0, feedback, 0}, \
}
// ⮎4⬎
// 3→2→1→
#define DX9_ALG_2(feedback) \
{ \
{0, 1, 0, 0, 1}, \
{0, 0, 1, 1, 0}, \
{0, 0, 0, 0, 0}, \
{0, 0, 0, feedback, 0}, \
}
// ⮎4⬎
// 3→2→1→
#define DX9_ALG_3(feedback) \
{ \
{0, 1, 0, 1, 1}, \
{0, 0, 1, 0, 0}, \
{0, 0, 0, 0, 0}, \
{0, 0, 0, feedback, 0}, \
}
// ⮎4→3⬎
// 2→1→
#define DX9_ALG_4(feedback) \
{ \
{0, 1, 0, 1, 1}, \
{0, 0, 1, 0, 0}, \
{0, 0, 0, 0, 0}, \
{0, 0, 0, feedback, 0}, \
}
// ⮎4→3→
// 2→1→
#define DX9_ALG_5(feedback) \
{ \
{0, 1, 0, 0, 1}, \
{0, 0, 0, 0, 0}, \
{0, 0, 0, 1, 1}, \
{0, 0, 0, feedback, 0}, \
}
// 1→
// ⮎4→2→
// 3→
#define DX9_ALG_6(feedback) \
{ \
{0, 0, 0, 0, 1}, \
{0, 0, 0, 1, 1}, \
{0, 0, 0, 0, 1}, \
{0, 0, 0, feedback, 0}, \
}
// 1→
// 2→
// ⮎4→3→
#define DX9_ALG_7(feedback) \
{ \
{0, 0, 0, 0, 1}, \
{0, 0, 0, 0, 1}, \
{0, 0, 1, 0, 1}, \
{0, 0, 0, feedback, 0}, \
}
// 1→
// 2→
// 3→
// ⮎4→
#define DX9_ALG_8(feedback) \
{ \
{0, 0, 0, 0, 1}, \
{0, 0, 0, 0, 1}, \
{0, 0, 0, 0, 1}, \
{0, 0, 0, feedback, 1}, \
}

44
notes.cpp Normal file
View File

@ -0,0 +1,44 @@
#include <Arduino.h>
#include "notes.h"
const char *NoteNames[] {
"C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ",
};
float JustPitches[MaxNote + 1];
// Hat tip to Kyle Gann
// https://www.kylegann.com/tuning.html
void setupJustPitches(uint8_t baseNote, float basePitch) {
JustPitches[baseNote + 0] = basePitch * 1 / 1; // D
JustPitches[baseNote + 1] = basePitch * 16 / 15; // Eb
JustPitches[baseNote + 2] = basePitch * 9 / 8; // E
JustPitches[baseNote + 3] = basePitch * 6 / 5; // F
JustPitches[baseNote + 4] = basePitch * 5 / 4; // F#
JustPitches[baseNote + 5] = basePitch * 4 / 3; // G
JustPitches[baseNote + 6] = basePitch * 45 / 32; // Ab
JustPitches[baseNote + 7] = basePitch * 3 / 2; // A
JustPitches[baseNote + 8] = basePitch * 8 / 5; // Bb
JustPitches[baseNote + 9] = basePitch * 5 / 3; // B
JustPitches[baseNote + 10] = basePitch * 9 / 5; // C
JustPitches[baseNote + 11] = basePitch * 15 / 8; // C#
// Two fourths up from the base pitch, so G major scale sounds right
JustPitches[baseNote + 11] = basePitch * 8 / 3; // C#
// Octaves
for (int note = baseNote; note < baseNote + 12; note++) {
for (int i = 1; i < 9; i++) {
int multiplier = 1<<i;
int shift = i*12;
int upNote = note + shift;
int dnNote = note - shift;
if (upNote <= MaxNote) {
JustPitches[upNote] = JustPitches[note] * multiplier;
}
if (dnNote >= 0) {
JustPitches[dnNote] = JustPitches[note] / multiplier;
}
}
}
}

11
notes.h
View File

@ -1,3 +1,7 @@
#pragma once
#define PITCH_D4 293.66
enum Notes { enum Notes {
NOTE_C0, NOTE_CS0, NOTE_D0, NOTE_DS0, NOTE_E0, NOTE_F0, NOTE_FS0, NOTE_G0, NOTE_GS0, NOTE_A0, NOTE_AS0, NOTE_B0, NOTE_C0, NOTE_CS0, NOTE_D0, NOTE_DS0, NOTE_E0, NOTE_F0, NOTE_FS0, NOTE_G0, NOTE_GS0, NOTE_A0, NOTE_AS0, NOTE_B0,
NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1,
@ -11,8 +15,7 @@ enum Notes {
}; };
const uint8_t MaxNote = NOTE_B8; const uint8_t MaxNote = NOTE_B8;
const char *NoteNames[] { extern const char *NoteNames[];
"C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ", extern float JustPitches[MaxNote + 1];
};
#define PITCH_D4 293.66 void setupJustPitches(uint8_t baseNote, float basePitch);

View File

@ -1,50 +1,39 @@
typedef struct Operator { // "Factory" patches
float gain;
float delay;
float attack;
float hold;
float decay;
float sustain;
float release;
float baseFrequency;
float multiplier;
} Operator;
typedef struct Patch { #pragma once
char *name; #include "dx9.h"
Operator operators[4];
float feedback;
} Patch;
Patch Bank[] = { // Waveform, offset, multiplier, delay, attack, holdAmp, hold, decay, sustainAmp, release
FMPatch Bank[] = {
{ {
"Venus Oboe", "Venus Oboe",
DX9_ALG_5(0),
{ {
{1.0, 0, 10.5, 0, 5000, 0.75, 5.0, 0, 1.00}, // Waveform off mult del att hldA hld dec susA rel
{1.0, 0, 10.5, 0, 2000, 0.80, 5.0, 0, 4.00}, {WAVEFORM_SINE, 0, 1.00, 0, 10.5, 1.0, 10.5, 0, 0.75, 5},
{0.0, 0, 10.5, 0, 2000, 0.50, 5.0, 0, 8.00}, {WAVEFORM_SINE, 0, 4.00, 0, 10.5, 1.0, 10.5, 0, 0.80, 5},
{0.0, 0, 50.0, 0, 800, 0.75, 5.0, 0, 16.00}, {WAVEFORM_SINE, 0, 8.00, 0, 10.5, 1.0, 10.5, 0, 0.50, 5},
{WAVEFORM_SINE, 0, 16.00, 0, 10.5, 1.0, 50.0, 0, 0.75, 5},
}, },
0.0,
}, },
{ {
"IWantPizza", "IWantPizza",
DX9_ALG_1(0),
{ {
{1.0, 0, 10.5, 0, 5000, 0.35, 100, 0, 4.00}, {WAVEFORM_SINE, 0, 4.00, 0, 10.5, 1.0, 10.5, 0, 0.35, 20},
{1.0, 0, 10.5, 0, 2000, 0.30, 100, 0, 1.00}, {WAVEFORM_SINE, 0, 1.00, 0, 10.5, 1.0, 10.5, 0, 0.30, 20},
{1.0, 0, 10.5, 0, 2000, 0.50, 100, 0, 8.00}, {WAVEFORM_SINE, 0, 8.00, 0, 10.5, 1.0, 10.5, 0, 0.50, 20},
{1.0, 0, 200, 0, 800, 0.25, 100, 0, 16.00}, {WAVEFORM_SINE, 0, 16.00, 0, 10.5, 1.0, 50, 0, 0.25, 20},
}, },
0.0,
}, },
{ {
"Ray Gun", "Ray Gun",
DX9_ALG_1(0),
{ {
{1.0, 0, 10.5, 0, 5000, 0.35, 2000, 0, 1.00}, {WAVEFORM_SINE, 0, 1.00, 0, 10.5, 1.0, 10.5, 0, 0.35, 20},
{1.0, 0, 10.5, 0, 2000, 0.30, 2000, 0, 1.00}, {WAVEFORM_SINE, 0, 1.00, 0, 10.5, 1.0, 10.5, 0, 0.30, 20},
{1.0, 0, 10.5, 0, 2000, 0.00, 2000, 0, 9.00}, {WAVEFORM_SINE, 0, 9.00, 0, 10.5, 1.0, 10.5, 0, 0.00, 20},
{1.0, 0, 200, 0, 800, 0.25, 800, 0, 1.00}, {WAVEFORM_SINE, 0, 1.00, 0, 10.5, 1.0, 50, 0, 0.25, 8},
}, },
0.0,
}, },
}; };

42
synth.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "synth.h"
void FMVoiceLoadPatch(FMVoice *v, FMPatch *p) {
for (int i=0; i<NUM_OPERATORS; i++) {
FMOperator op = p->operators[i];
v->oscillators[i].begin(op.waveform);
v->envelopes[i].delay(op.delayTime);
v->envelopes[i].attack(op.attackTime);
v->oscillators[i].amplitude(op.holdAmplitude);
v->envelopes[i].hold(op.holdTime);
v->envelopes[i].decay(op.decayTime);
v->envelopes[i].sustain(op.sustainAmplitude / op.holdAmplitude);
v->envelopes[i].release(op.releaseTime);
// This feels wasteful 🙁
for (int j=0; j<NUM_OPERATORS; j++) {
v->mixers[i].gain(j, p->gains[i][j]);
}
v->outputMixer.gain(i, p->gains[i][NUM_OPERATORS]);
}
}
void FMVoiceSetPitch(FMVoice *v, float freq) {
for (int i=0; i<4; i++) {
FMOperator op = v->patch->operators[i];
v->oscillators[i].frequency(op.offset + (freq * op.multiplier));
}
}
void FMVoiceNoteOn(FMVoice *v, float freq) {
FMVoiceSetPitch(v, freq);
for (int i=0; i<4; i++) {
v->envelopes[i].noteOn();
}
}
void FMVoiceNoteOff(FMVoice *v) {
for (int i=0; i<4; i++) {
v->envelopes[i].noteOff();
}
}

163
synth.h
View File

@ -1,38 +1,135 @@
#pragma once
#include <Audio.h> #include <Audio.h>
#include <Wire.h> #include <Wire.h>
#include <SPI.h> #include <SPI.h>
#include <SD.h> #include <SD.h>
// GUItool: begin automatically generated code #define NUM_OPERATORS 4
AudioMixer4 feedback; //xy=110,37
AudioSynthWaveformSineModulated osc4; //xy=112,98 /** FMOperator defines all settable paramaters to an operator.
AudioSynthWaveformSineModulated osc2; //xy=112,194 *
AudioSynthWaveformSineModulated osc1; //xy=112,245 * An FM operator consists of:
AudioSynthWaveformSineModulated osc3; //xy=113,146 * - An input
AudioMixer4 mixOp; //xy=114,418 * - An oscillator
AudioEffectEnvelope env4; //xy=251,97 * - An envelope generator
AudioEffectEnvelope env3; //xy=251,146 *
AudioEffectEnvelope env2; //xy=252,194 * Frequency Modulation happens by chaining oscillators together,
AudioEffectEnvelope env1; //xy=252,245 * using the output of one to modulate the frequency of the next.
AudioFilterBiquad biquad1; //xy=257,418 *
AudioMixer4 mixL; //xy=472,402 * Oscillators generate waveforms in a shape defined by
AudioMixer4 mixR; //xy=473,498 * `synth_waveform.h`. WAVEFORM_SINE is a good one to start with.
AudioOutputAnalogStereo dacs1; //xy=724,452 * Other sensible options are SAWTOOTH, SQUARE, and TRIANGLE.
AudioConnection patchCord1(feedback, osc4); *
AudioConnection patchCord2(osc4, env4); * Frequency for an oscillator is calculated with:
AudioConnection patchCord3(osc4, 0, feedback, 0); * offset + (voiceFrequency × multiplier)
AudioConnection patchCord4(osc2, env2); *
AudioConnection patchCord5(osc1, env1); * Oscillator frequency is then modulated by the level obtained
AudioConnection patchCord6(osc3, env3); * by the input mixer: level of 1.0 shifts frequency up by
AudioConnection patchCord7(mixOp, biquad1); * 8 octaves, level of -1.0 shifts frequency down by 8 octaves.
AudioConnection patchCord8(env4, osc3); *
AudioConnection patchCord9(env4, 0, mixOp, 3); * The envelope modifies amplitude of the oscillator output,
AudioConnection patchCord10(env3, 0, mixOp, 2); * using the following rules:
AudioConnection patchCord11(env2, osc1); * - stay at 0 until Note On
AudioConnection patchCord12(env2, 0, mixOp, 1); * - stay at 0 for `delayTime` milliseconds
AudioConnection patchCord13(env1, 0, mixOp, 0); * - linear increase to `holdAmplitude` for `attackTime` milliseconds
AudioConnection patchCord14(biquad1, 0, mixL, 0); * - stay at `holdAmplitude` for `holdTime` milliseconds
AudioConnection patchCord15(biquad1, 0, mixR, 0); * - linear decrease to `sustainAmplitude` for `decayTime` milliseconds
AudioConnection patchCord17(mixL, 0, dacs1, 0); * - stay at `sustainAmplitude` until Note Off
AudioConnection patchCord18(mixR, 0, dacs1, 1); * - linear decrease to 0 for `releaseTime` milliseconds
// GUItool: end automatically generated code */
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 {
char *name;
float gains[NUM_OPERATORS][NUM_OPERATORS+1];
FMOperator operators[NUM_OPERATORS];
} FMPatch;
/** FMVoice sets up all the Audio objects for a voice.
*/
typedef struct FMVoice {
AudioMixer4 mixers[NUM_OPERATORS];
AudioSynthWaveformModulated oscillators[NUM_OPERATORS];
AudioEffectEnvelope envelopes[NUM_OPERATORS];
AudioMixer4 outputMixer;
FMPatch *patch;
} FMVoice;
/** 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.outputMixer, i}, \
{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}
/** FMVoiceWiring outputs AudioConnection initializer to wire one FMVoice
*/
#define FMVoiceWiring(name) \
FMOperatorWiring(name, 0), \
FMOperatorWiring(name, 1), \
FMOperatorWiring(name, 2), \
FMOperatorWiring(name, 3)
/** FMVoiceLoadPatch loads a patch into a voice.
*/
void FMVoiceLoadPatch(FMVoice *v, FMPatch *p);
/** FMVoiceSetPitch 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 FMVoiceSetPitch(FMVoice *v, float pitch);
/** FMVoiceNoteOn 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 FMVoiceNoteOn(FMVoice *v, float pitch);
/** FMVoiceNoteOff 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 FMVoiceNoteOff(FMVoice *v);

View File

@ -12,11 +12,47 @@
#define KNEE_OFFSET 0 #define KNEE_OFFSET 0
#define KEY_OFFSET 2 #define KEY_OFFSET 2
float cmaj_low[8] = { 130.81, 146.83, 164.81, 174.61, 196.00, 220.00, 246.94, 261.63 }; FMVoice Chanter;
float cmaj_high[8] = { 261.6, 293.7, 329.6, 349.2, 392.0, 440.0, 493.9, 523.3 }; FMVoice Drones[3];
FMVoice Regulators[3];
AudioEffectEnvelope *envs[] = {&env1, &env2, &env3, &env4}; AudioFilterBiquad biquad1;
AudioSynthWaveformSineModulated *oscs[] = {&osc1, &osc2, &osc3, &osc4}; AudioMixer4 mixDrones;
AudioMixer4 mixRegulators;
AudioMixer4 mixL;
AudioMixer4 mixR;
AudioOutputAnalogStereo dacs1;
AudioConnection FMVoicePatchCords[] = {
{Chanter.outputMixer, 0, biquad1, 0},
{biquad1, 0, mixL, 0},
{biquad1, 0, mixR, 0},
{Drones[0].outputMixer, 0, mixDrones, 0},
{Drones[1].outputMixer, 0, mixDrones, 1},
{Drones[2].outputMixer, 0, mixDrones, 2},
{mixDrones, 0, mixL, 1},
{mixDrones, 0, mixR, 1},
{Regulators[0].outputMixer, 0, mixRegulators, 0},
{Regulators[1].outputMixer, 0, mixRegulators, 1},
{Regulators[2].outputMixer, 0, mixRegulators, 2},
{mixRegulators, 0, mixL, 2},
{mixRegulators, 0, mixR, 2},
{mixL, 0, dacs1, 0},
{mixR, 0, dacs1, 1},
FMVoiceWiring(Chanter),
FMVoiceWiring(Drones[0]),
FMVoiceWiring(Drones[1]),
FMVoiceWiring(Drones[2]),
FMVoiceWiring(Drones[3]),
FMVoiceWiring(Regulators[0]),
FMVoiceWiring(Regulators[1]),
FMVoiceWiring(Regulators[2]),
FMVoiceWiring(Regulators[3]),
};
int currentPatch = 0; int currentPatch = 0;
@ -25,57 +61,8 @@ Adafruit_NeoTrellisM4 trellis = Adafruit_NeoTrellisM4();
MicroOLED oled(9, 1); MicroOLED oled(9, 1);
QwiicButton bag; QwiicButton bag;
// Hat tip to Kyle Gann
// https://www.kylegann.com/tuning.html
float JustPitches[MaxNote + 1];
void setupJustPitches(uint8_t baseNote, float basePitch) {
JustPitches[baseNote + 0] = basePitch * 1 / 1;
JustPitches[baseNote + 1] = basePitch * 16 / 15;
JustPitches[baseNote + 2] = basePitch * 9 / 8;
JustPitches[baseNote + 3] = basePitch * 6 / 5;
JustPitches[baseNote + 4] = basePitch * 5 / 4;
JustPitches[baseNote + 5] = basePitch * 4 / 3;
JustPitches[baseNote + 6] = basePitch * 45 / 32;
JustPitches[baseNote + 7] = basePitch * 3 / 2;
JustPitches[baseNote + 8] = basePitch * 8 / 5;
JustPitches[baseNote + 9] = basePitch * 5 / 3;
JustPitches[baseNote + 10] = basePitch * 9 / 5;
JustPitches[baseNote + 11] = basePitch * 15 / 8;
// Octaves void setup() {
for (int note = baseNote; note < baseNote + 12; note++) {
for (int i = 1; i < 9; i++) {
int multiplier = 1<<i;
int shift = i*12;
int upNote = note + shift;
int dnNote = note - shift;
if (upNote <= MaxNote) {
JustPitches[upNote] = JustPitches[note] * multiplier;
}
if (dnNote >= 0) {
JustPitches[dnNote] = JustPitches[note] / multiplier;
}
}
}
}
void loadPatch(Patch p) {
for (int i=0; i<4; i++) {
Operator op = p.operators[i];
oscs[i]->amplitude(op.gain);
envs[i]->delay(op.delay);
envs[i]->attack(op.attack);
envs[i]->hold(op.hold);
envs[i]->decay(op.decay);
envs[i]->sustain(op.sustain);
envs[i]->release(op.release);
}
feedback.gain(0, p.feedback);
}
void setup(){
setupJustPitches(NOTE_D4, PITCH_D4); setupJustPitches(NOTE_D4, PITCH_D4);
pinMode(LED_BUILTIN, OUTPUT); pinMode(LED_BUILTIN, OUTPUT);
@ -117,31 +104,6 @@ void setup(){
AudioMemoryUsageMaxReset(); AudioMemoryUsageMaxReset();
} }
void setPitch(float freq) {
for (int i=0; i<4; i++) {
Operator op = Bank[currentPatch].operators[i];
oscs[i]->frequency(op.baseFrequency + freq*op.multiplier);
}
}
void noteOn(float freq) {
AudioNoInterrupts();
for (int i=0; i<4; i++) {
Operator op = Bank[currentPatch].operators[i];
oscs[i]->frequency(op.baseFrequency + freq*op.multiplier);
envs[i]->noteOn();
}
AudioInterrupts();
}
void noteOff() {
AudioNoInterrupts();
for (int i=0; i<4; i++) {
envs[i]->noteOff();
}
AudioInterrupts();
}
#define BUTTON_UP 0 #define BUTTON_UP 0
#define BUTTON_DOWN 8 #define BUTTON_DOWN 8
#define BUTTON_PITCH 24 #define BUTTON_PITCH 24
@ -207,13 +169,13 @@ void updateTunables(uint8_t buttons, int note) {
int bankSize = sizeof(Bank) / sizeof(Bank[0]); int bankSize = sizeof(Bank) / sizeof(Bank[0]);
patch = (patch + bankSize) % bankSize; patch = (patch + bankSize) % bankSize;
Patch p = Bank[patch]; FMPatch *p = &Bank[patch];
loadPatch(p); FMVoiceLoadPatch(&Chanter, p);
oled.clear(PAGE); oled.clear(PAGE);
oled.setFontType(0); oled.setFontType(0);
oled.setCursor(0, 0); oled.setCursor(0, 0);
oled.print(p.name); oled.print(p->name);
oled.setCursor(0, 10); oled.setCursor(0, 10);
oled.print("Patch "); oled.print("Patch ");
oled.print(patch); oled.print(patch);
@ -290,7 +252,7 @@ void loop() {
} }
if (silent) { if (silent) {
noteOff(); FMVoiceNoteOff(&Chanter);
playing = false; playing = false;
} else { } else {
// Calculate pitch, and glissando pitch // Calculate pitch, and glissando pitch
@ -310,9 +272,9 @@ void loop() {
} }
if (playing) { if (playing) {
setPitch(pitch); FMVoiceSetPitch(&Chanter, pitch);
} else { } else {
noteOn(pitch); FMVoiceNoteOn(&Chanter, pitch);
} }
playing = true; playing = true;
} }