From 6d5a0c54c5c4ef909c395d62bd003f85c57acef2 Mon Sep 17 00:00:00 2001 From: Brian Balberchak Date: Sat, 1 Feb 2025 14:32:34 -0800 Subject: [PATCH 1/5] Initial implementation of a basic harmonic NCO from 2020. --- user/NCO/Makefile | 13 +++++ user/NCO/PodNcoMain.cpp | 100 +++++++++++++++++++++++++++++++++++++++ user/NCO/harmonicNco.cpp | 70 +++++++++++++++++++++++++++ user/NCO/harmonicNco.h | 32 +++++++++++++ user/NCO/nco.cpp | 70 +++++++++++++++++++++++++++ user/NCO/nco.h | 41 ++++++++++++++++ 6 files changed, 326 insertions(+) create mode 100644 user/NCO/Makefile create mode 100644 user/NCO/PodNcoMain.cpp create mode 100644 user/NCO/harmonicNco.cpp create mode 100644 user/NCO/harmonicNco.h create mode 100644 user/NCO/nco.cpp create mode 100644 user/NCO/nco.h diff --git a/user/NCO/Makefile b/user/NCO/Makefile new file mode 100644 index 000000000..6847634e1 --- /dev/null +++ b/user/NCO/Makefile @@ -0,0 +1,13 @@ +# Project Name +TARGET = PodNcoMain + +# Sources +CPP_SOURCES = nco.cpp harmonicNco.cpp PodNcoMain.cpp + +# Library Locations +LIBDAISY_DIR = ../../libdaisy +DAISYSP_DIR = ../../DaisySP + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core +include $(SYSTEM_FILES_DIR)/Makefile diff --git a/user/NCO/PodNcoMain.cpp b/user/NCO/PodNcoMain.cpp new file mode 100644 index 000000000..e6e9f834c --- /dev/null +++ b/user/NCO/PodNcoMain.cpp @@ -0,0 +1,100 @@ +#include "daisysp.h" +#include "daisy_pod.h" +#include "nco.h" +#include "harmonicNco.h" + +#define MIDI_A4 69 +#define FREQ_A4 440 + +using namespace daisysp; +using namespace daisy; + +static DaisyPod pod; +//static NCO nco; +static HarmonicNCO harmonicNco; +static uint16_t nco_kval; +static float amp = 0.5f; +static float freq = FREQ_A4; +static int midi_note = MIDI_A4; +static int prev_midi_note = MIDI_A4; + +/* +static float harmonicAmps[] = { + 1.0, + 0.8, + 0.7, + 0.5, + 0.6, + 0.3, + 0.4, + 0.6 +}; +*/ +static float harmonicAmps[] = { + 1.0, + 0.8, + 0.7, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 +}; + +static float harmonicPhases[] = { + 0.0, + 0.9, + 0.125, + 0.45, + 0.65, + 0.95, + 0.55, + 0.6 +}; + +static void AudioCallback(float *in, float *out, size_t size) +{ + float sig; + + pod.DebounceControls(); + + midi_note += pod.encoder.Increment(); + midi_note = DSY_CLAMP(midi_note, 0, 127); + + if (midi_note != prev_midi_note) { + freq = mtof(midi_note); + harmonicNco.SetFrequency(freq); + } + prev_midi_note = midi_note; + + // Audio Loop + for(size_t i = 0; i < size; i += 2) + { + sig = amp * harmonicNco.NextSample(); + // left out + out[i] = sig; + // right out + out[i + 1] = sig; + } +} + +int main(void) +{ + // initialize pod hardware and oscillator daisysp module + float sample_rate; + + pod.Init(); + sample_rate = pod.AudioSampleRate(); + harmonicNco.SetSampleRate((uint32_t)sample_rate); + + harmonicNco.SetFrequency(freq); + harmonicNco.SetAmplitudes(harmonicAmps); + harmonicNco.SetPhases(harmonicPhases); + + // start callback + pod.StartAudio(AudioCallback); + + + while(1) + ; // Infinite Loop +} diff --git a/user/NCO/harmonicNco.cpp b/user/NCO/harmonicNco.cpp new file mode 100644 index 000000000..7208d67d0 --- /dev/null +++ b/user/NCO/harmonicNco.cpp @@ -0,0 +1,70 @@ +#include "harmonicNco.h" + +// HarmonicNCO default constructor +HarmonicNCO::HarmonicNCO() { + uint8_t nHarmonic; + + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { + nco[nHarmonic].SetSampleRate(NCO::DEFAULT_FS); + amp[nHarmonic] = 1.0; + } +} + +HarmonicNCO::~HarmonicNCO() { + // TODO +} + +void HarmonicNCO::SetSampleRate(uint32_t sample_freq) { + uint8_t nHarmonic; + + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { + nco[nHarmonic].SetSampleRate(sample_freq); + } +} + +void HarmonicNCO::SetFrequency(float freq) { + uint8_t nHarmonic; + float harmonicFreq = 0.0; + + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { + harmonicFreq = (nHarmonic + 1) * freq; + nco[nHarmonic].SetFrequency(harmonicFreq); + } +} + +void HarmonicNCO::SetAmplitudes(float *amplitudes) { + uint8_t nHarmonic; + + if (amplitudes) { + + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { + amp[nHarmonic] = amplitudes[nHarmonic]; + } + } +} + +void HarmonicNCO::SetPhases(float *phases) { + uint8_t nHarmonic; + + if (phases) { + + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { + nco[nHarmonic].SetPhase(phases[nHarmonic]); + } + } +} + +float HarmonicNCO::NextSample() { + uint8_t nHarmonic; + float sample = 0.0; + float ampTotal = 0.0; + + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { + sample += (amp[nHarmonic] * nco[nHarmonic].NextSample()); + ampTotal += amp[nHarmonic]; + } + // Normalize amplitude of sample + sample /= ampTotal; + + return sample; +} diff --git a/user/NCO/harmonicNco.h b/user/NCO/harmonicNco.h new file mode 100644 index 000000000..5776d0d45 --- /dev/null +++ b/user/NCO/harmonicNco.h @@ -0,0 +1,32 @@ +// harmonicNco.h +#pragma once +#ifndef HARMONIC_NCO_H +#define HARMONIC_NCO_H + +#include +#include +#include +#include "daisysp.h" +#include "nco.h" + +using namespace std; + +class HarmonicNCO { + public: + static const uint8_t NUM_HARMONICS = 8; + + HarmonicNCO(); + ~HarmonicNCO(); + + void SetSampleRate(uint32_t sample_freq); + void SetFrequency(float freq); + void SetAmplitudes(float *amplitudes); + void SetPhases(float *phases); + float NextSample(); + + private: + NCO nco[NUM_HARMONICS]; // The NCO for each harmonic + float amp[NUM_HARMONICS]; // The amplitude for each harmonic +}; + +#endif diff --git a/user/NCO/nco.cpp b/user/NCO/nco.cpp new file mode 100644 index 000000000..27d72a4af --- /dev/null +++ b/user/NCO/nco.cpp @@ -0,0 +1,70 @@ +#include "nco.h" + +// Define/initialize NCO's static members +float NCO::sine_table[NCO_LUT_SIZE]; +bool NCO::sine_table_init = false; + +// NCO Default Constructor +NCO::NCO() { + // Set sampling frequency to default + SetSampleRate(0); + + phase_accum = 0; + k_val = 0; + + if (!sine_table_init) { + InitSineTable(); + } +} + +NCO::NCO(uint32_t sample_freq) { + // Set sampling frequency + SetSampleRate(sample_freq); + + phase_accum = 0; + k_val = 0; + + if (!sine_table_init) { + InitSineTable(); + } +} + +NCO::~NCO() { + // TODO +} + +void NCO::SetSampleRate(uint32_t sample_freq) { + sample_rate = (sample_freq > 0) ? sample_freq : DEFAULT_FS; +} + +void NCO::SetFrequency(float freq) { + k_val = (uint16_t)((freq / sample_rate) * (1 << N_PHASE_ACCUM_BITS)); +} + +void NCO::SetPhase(float phase) { + // Limit phase param between 0.0 - 1.0 + if (phase >= 0.0 && phase < 1.0) { + phase_accum = (uint16_t)(phase * (1 << N_PHASE_ACCUM_BITS)); + } +} + +float NCO::NextSample() { + uint16_t lut_ndx = 0; + + // Increment phase accumulator + phase_accum += k_val; + // Use top 10 bits of phase_accum as LUT index + lut_ndx = phase_accum >> (N_PHASE_ACCUM_BITS - N_PHASE_BITS); + lut_ndx &= NCO_LUT_MASK; + // Return the new LUT sample + return sine_table[lut_ndx]; +} + +void NCO::InitSineTable() { + int ndx; + + for (ndx = 0; ndx < NCO_LUT_SIZE; ndx++) { + sine_table[ndx] = sin(2 * M_PI * ndx / (float)NCO_LUT_SIZE); + } + sine_table_init = true; +} diff --git a/user/NCO/nco.h b/user/NCO/nco.h new file mode 100644 index 000000000..d6bed802c --- /dev/null +++ b/user/NCO/nco.h @@ -0,0 +1,41 @@ +// nco.h +#pragma once +#ifndef NCO_H +#define NCO_H + +#include +#include +#include +#include "daisysp.h" + +using namespace std; + +class NCO { + public: + static const uint8_t N_PHASE_BITS = 10; + static const uint8_t N_PHASE_ACCUM_BITS = 16; + static const uint16_t NCO_LUT_SIZE = (1 << N_PHASE_BITS); + static const uint16_t NCO_LUT_MASK = (NCO_LUT_SIZE - 1); + static const uint32_t DEFAULT_FS = 10000; + + NCO(); + NCO(uint32_t sample_freq); + ~NCO(); + + void SetSampleRate(uint32_t sample_freq); + void SetFrequency(float freq); + void SetPhase(float phase); + float NextSample(); + + private: + // Actual LUT for sampled sine wave - shared between all instances + static float sine_table[]; + static bool sine_table_init; + uint32_t sample_rate; + uint16_t phase_accum; + uint16_t k_val; + + void InitSineTable(); +}; + +#endif From 99f3c92a7dc77b3cddea68da5e7e9fc84e0daac1 Mon Sep 17 00:00:00 2001 From: Brian Balberchak Date: Sun, 2 Feb 2025 00:37:54 -0800 Subject: [PATCH 2/5] Cleaned up the NCO code, and updated the Audio callback. --- user/NCO/PodNcoMain.cpp | 93 +++++++++++++++----------------- user/NCO/harmonicNco.cpp | 112 ++++++++++++++++++++++----------------- user/NCO/harmonicNco.h | 27 +++++----- user/NCO/nco.cpp | 94 ++++++++++++++++++-------------- user/NCO/nco.h | 53 +++++++++--------- 5 files changed, 199 insertions(+), 180 deletions(-) diff --git a/user/NCO/PodNcoMain.cpp b/user/NCO/PodNcoMain.cpp index e6e9f834c..7bd4d2493 100644 --- a/user/NCO/PodNcoMain.cpp +++ b/user/NCO/PodNcoMain.cpp @@ -3,78 +3,71 @@ #include "nco.h" #include "harmonicNco.h" -#define MIDI_A4 69 -#define FREQ_A4 440 +#define MIDI_A4 69 +#define FREQ_A4 440 using namespace daisysp; using namespace daisy; -static DaisyPod pod; -//static NCO nco; -static HarmonicNCO harmonicNco; -static uint16_t nco_kval; -static float amp = 0.5f; -static float freq = FREQ_A4; -static int midi_note = MIDI_A4; -static int prev_midi_note = MIDI_A4; - -/* -static float harmonicAmps[] = { - 1.0, - 0.8, - 0.7, - 0.5, - 0.6, - 0.3, - 0.4, - 0.6 -}; -*/ -static float harmonicAmps[] = { - 1.0, - 0.8, - 0.7, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 +static DaisyPod pod; +// static NCO nco; +static HarmonicNCO harmonicNco; +static uint16_t nco_kval; +static float amp = 0.5f; +static float freq = FREQ_A4; +static int midi_note = MIDI_A4; +static int prev_midi_note = MIDI_A4; + +static float harmonicAmps[] = +{ + 1.0, + 0.8, + 0.7, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 }; -static float harmonicPhases[] = { - 0.0, - 0.9, - 0.125, - 0.45, - 0.65, - 0.95, - 0.55, - 0.6 +static float harmonicPhases[] = +{ + 0.0, + 0.9, + 0.125, + 0.45, + 0.65, + 0.95, + 0.55, + 0.6 }; -static void AudioCallback(float *in, float *out, size_t size) +static void AudioCallback(AudioHandle::InterleavingInputBuffer in, + AudioHandle::InterleavingOutputBuffer out, + size_t size) { - float sig; + float sample = 0; - pod.DebounceControls(); + pod.ProcessAllControls(); midi_note += pod.encoder.Increment(); midi_note = DSY_CLAMP(midi_note, 0, 127); - if (midi_note != prev_midi_note) { + if (midi_note != prev_midi_note) + { freq = mtof(midi_note); harmonicNco.SetFrequency(freq); } prev_midi_note = midi_note; // Audio Loop - for(size_t i = 0; i < size; i += 2) + for (size_t ndx = 0; ndx < size; ndx += 2) { - sig = amp * harmonicNco.NextSample(); + sample = amp * harmonicNco.NextSample(); // left out - out[i] = sig; + out[ndx] = sample; // right out - out[i + 1] = sig; + out[ndx + 1] = sample; } } @@ -85,8 +78,8 @@ int main(void) pod.Init(); sample_rate = pod.AudioSampleRate(); - harmonicNco.SetSampleRate((uint32_t)sample_rate); + harmonicNco.SetSampleRate((uint32_t)sample_rate); harmonicNco.SetFrequency(freq); harmonicNco.SetAmplitudes(harmonicAmps); harmonicNco.SetPhases(harmonicPhases); diff --git a/user/NCO/harmonicNco.cpp b/user/NCO/harmonicNco.cpp index 7208d67d0..76e6933f1 100644 --- a/user/NCO/harmonicNco.cpp +++ b/user/NCO/harmonicNco.cpp @@ -1,70 +1,82 @@ #include "harmonicNco.h" // HarmonicNCO default constructor -HarmonicNCO::HarmonicNCO() { - uint8_t nHarmonic; - - for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { - nco[nHarmonic].SetSampleRate(NCO::DEFAULT_FS); - amp[nHarmonic] = 1.0; - } +HarmonicNCO::HarmonicNCO() +{ + uint8_t nHarmonic; + + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) + { + nco[nHarmonic].SetSampleRate(NCO::DEFAULT_FS); + amp[nHarmonic] = 1.0; + } } HarmonicNCO::~HarmonicNCO() { // TODO } -void HarmonicNCO::SetSampleRate(uint32_t sample_freq) { - uint8_t nHarmonic; +void HarmonicNCO::SetSampleRate(uint32_t sample_freq) +{ + uint8_t nHarmonic; - for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { - nco[nHarmonic].SetSampleRate(sample_freq); - } + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) + { + nco[nHarmonic].SetSampleRate(sample_freq); + } } -void HarmonicNCO::SetFrequency(float freq) { - uint8_t nHarmonic; - float harmonicFreq = 0.0; +void HarmonicNCO::SetFrequency(float freq) +{ + uint8_t nHarmonic; + float harmonicFreq = 0.0; - for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { - harmonicFreq = (nHarmonic + 1) * freq; - nco[nHarmonic].SetFrequency(harmonicFreq); - } + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) + { + harmonicFreq = (nHarmonic + 1) * freq; + nco[nHarmonic].SetFrequency(harmonicFreq); + } } -void HarmonicNCO::SetAmplitudes(float *amplitudes) { - uint8_t nHarmonic; - - if (amplitudes) { - - for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { - amp[nHarmonic] = amplitudes[nHarmonic]; - } - } +void HarmonicNCO::SetAmplitudes(float *amplitudes) +{ + uint8_t nHarmonic; + + if (amplitudes) + { + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) + { + amp[nHarmonic] = amplitudes[nHarmonic]; + } + } } -void HarmonicNCO::SetPhases(float *phases) { - uint8_t nHarmonic; - - if (phases) { - - for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { - nco[nHarmonic].SetPhase(phases[nHarmonic]); - } - } +void HarmonicNCO::SetPhases(float *phases) +{ + uint8_t nHarmonic; + + if (phases) + { + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) + { + nco[nHarmonic].SetPhase(phases[nHarmonic]); + } + } } -float HarmonicNCO::NextSample() { - uint8_t nHarmonic; - float sample = 0.0; - float ampTotal = 0.0; - - for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) { - sample += (amp[nHarmonic] * nco[nHarmonic].NextSample()); - ampTotal += amp[nHarmonic]; - } - // Normalize amplitude of sample - sample /= ampTotal; - - return sample; +float HarmonicNCO::NextSample() +{ + uint8_t nHarmonic; + float sample = 0.0; + float ampTotal = 0.0; + + for (nHarmonic = 0; nHarmonic < NUM_HARMONICS; nHarmonic++) + { + sample += (amp[nHarmonic] * nco[nHarmonic].NextSample()); + ampTotal += amp[nHarmonic]; + } + // Normalize amplitude of sample + sample /= ampTotal; + + return sample; } diff --git a/user/NCO/harmonicNco.h b/user/NCO/harmonicNco.h index 5776d0d45..94e64f2c3 100644 --- a/user/NCO/harmonicNco.h +++ b/user/NCO/harmonicNco.h @@ -11,22 +11,23 @@ using namespace std; -class HarmonicNCO { - public: - static const uint8_t NUM_HARMONICS = 8; +class HarmonicNCO +{ + public: + static const uint8_t NUM_HARMONICS = 8; - HarmonicNCO(); - ~HarmonicNCO(); + HarmonicNCO(); + ~HarmonicNCO(); - void SetSampleRate(uint32_t sample_freq); - void SetFrequency(float freq); - void SetAmplitudes(float *amplitudes); - void SetPhases(float *phases); - float NextSample(); + void SetSampleRate(uint32_t sample_freq); + void SetFrequency(float freq); + void SetAmplitudes(float *amplitudes); + void SetPhases(float *phases); + float NextSample(); - private: - NCO nco[NUM_HARMONICS]; // The NCO for each harmonic - float amp[NUM_HARMONICS]; // The amplitude for each harmonic + private: + NCO nco[NUM_HARMONICS]; // The NCO for each harmonic + float amp[NUM_HARMONICS]; // The amplitude for each harmonic }; #endif diff --git a/user/NCO/nco.cpp b/user/NCO/nco.cpp index 27d72a4af..f4f9ad6c2 100644 --- a/user/NCO/nco.cpp +++ b/user/NCO/nco.cpp @@ -5,66 +5,78 @@ float NCO::sine_table[NCO_LUT_SIZE]; bool NCO::sine_table_init = false; // NCO Default Constructor -NCO::NCO() { - // Set sampling frequency to default - SetSampleRate(0); +NCO::NCO() +{ + // Set sampling frequency to default + SetSampleRate(DEFAULT_FS); - phase_accum = 0; - k_val = 0; + phase_accum = 0; + k_val = 0; - if (!sine_table_init) { - InitSineTable(); - } + if (!sine_table_init) + { + InitSineTable(); + } } -NCO::NCO(uint32_t sample_freq) { - // Set sampling frequency - SetSampleRate(sample_freq); +NCO::NCO(uint32_t sample_freq) +{ + // Set sampling frequency + SetSampleRate(sample_freq); - phase_accum = 0; - k_val = 0; + phase_accum = 0; + k_val = 0; - if (!sine_table_init) { - InitSineTable(); - } + if (!sine_table_init) + { + InitSineTable(); + } } -NCO::~NCO() { +NCO::~NCO() +{ // TODO } -void NCO::SetSampleRate(uint32_t sample_freq) { - sample_rate = (sample_freq > 0) ? sample_freq : DEFAULT_FS; +void NCO::SetSampleRate(uint32_t sample_freq) +{ + sample_rate = (sample_freq > 0) ? sample_freq : DEFAULT_FS; } -void NCO::SetFrequency(float freq) { - k_val = (uint16_t)((freq / sample_rate) * (1 << N_PHASE_ACCUM_BITS)); +void NCO::SetFrequency(float freq) +{ + k_val = (uint16_t)((freq / sample_rate) * (1 << N_PHASE_ACCUM_BITS)); } -void NCO::SetPhase(float phase) { - // Limit phase param between 0.0 - 1.0 - if (phase >= 0.0 && phase < 1.0) { - phase_accum = (uint16_t)(phase * (1 << N_PHASE_ACCUM_BITS)); - } +void NCO::SetPhase(float phase) +{ + // Limit phase param between 0.0 - 1.0 + if (phase >= 0.0 && phase < 1.0) + { + phase_accum = (uint16_t)(phase * (1 << N_PHASE_ACCUM_BITS)); + } } -float NCO::NextSample() { - uint16_t lut_ndx = 0; +float NCO::NextSample() +{ + uint16_t lut_ndx = 0; - // Increment phase accumulator - phase_accum += k_val; - // Use top 10 bits of phase_accum as LUT index - lut_ndx = phase_accum >> (N_PHASE_ACCUM_BITS - N_PHASE_BITS); - lut_ndx &= NCO_LUT_MASK; - // Return the new LUT sample - return sine_table[lut_ndx]; + // Increment phase accumulator + phase_accum += k_val; + // Use top 10 bits of phase_accum as LUT index + lut_ndx = phase_accum >> (N_PHASE_ACCUM_BITS - N_PHASE_BITS); + lut_ndx &= NCO_LUT_MASK; + // Return the new LUT sample + return sine_table[lut_ndx]; } -void NCO::InitSineTable() { - int ndx; +void NCO::InitSineTable() +{ + int ndx; - for (ndx = 0; ndx < NCO_LUT_SIZE; ndx++) { - sine_table[ndx] = sin(2 * M_PI * ndx / (float)NCO_LUT_SIZE); - } - sine_table_init = true; + for (ndx = 0; ndx < NCO_LUT_SIZE; ndx++) + { + sine_table[ndx] = sin(2 * M_PI * ndx / (float)NCO_LUT_SIZE); + } + sine_table_init = true; } diff --git a/user/NCO/nco.h b/user/NCO/nco.h index d6bed802c..20d94b292 100644 --- a/user/NCO/nco.h +++ b/user/NCO/nco.h @@ -10,32 +10,33 @@ using namespace std; -class NCO { - public: - static const uint8_t N_PHASE_BITS = 10; - static const uint8_t N_PHASE_ACCUM_BITS = 16; - static const uint16_t NCO_LUT_SIZE = (1 << N_PHASE_BITS); - static const uint16_t NCO_LUT_MASK = (NCO_LUT_SIZE - 1); - static const uint32_t DEFAULT_FS = 10000; - - NCO(); - NCO(uint32_t sample_freq); - ~NCO(); - - void SetSampleRate(uint32_t sample_freq); - void SetFrequency(float freq); - void SetPhase(float phase); - float NextSample(); - - private: - // Actual LUT for sampled sine wave - shared between all instances - static float sine_table[]; - static bool sine_table_init; - uint32_t sample_rate; - uint16_t phase_accum; - uint16_t k_val; - - void InitSineTable(); +class NCO +{ + public: + static const uint8_t N_PHASE_BITS = 10; + static const uint8_t N_PHASE_ACCUM_BITS = 16; + static const uint16_t NCO_LUT_SIZE = (1 << N_PHASE_BITS); + static const uint16_t NCO_LUT_MASK = (NCO_LUT_SIZE - 1); + static const uint32_t DEFAULT_FS = 10000; + + NCO(); + NCO(uint32_t sample_freq); + ~NCO(); + + void SetSampleRate(uint32_t sample_freq); + void SetFrequency(float freq); + void SetPhase(float phase); + float NextSample(); + + private: + // Actual LUT for sampled sine wave - shared between all instances + static float sine_table[]; + static bool sine_table_init; + uint32_t sample_rate; + uint16_t phase_accum; + uint16_t k_val; + + void InitSineTable(); }; #endif From bfa434c4ced96688a96541503b9245b16e17664e Mon Sep 17 00:00:00 2001 From: Brian Balberchak Date: Sat, 1 Mar 2025 17:41:11 -0800 Subject: [PATCH 3/5] Created a basic Major Chord Player. --- user/NCO/PodNcoMain.cpp | 77 +++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/user/NCO/PodNcoMain.cpp b/user/NCO/PodNcoMain.cpp index 7bd4d2493..f3acfbab2 100644 --- a/user/NCO/PodNcoMain.cpp +++ b/user/NCO/PodNcoMain.cpp @@ -9,21 +9,28 @@ using namespace daisysp; using namespace daisy; +enum NoteInterval +{ + ROOT, + THIRD, + FIFTH, + NUM_INTERVALS +}; + +static void UpdateChord(uint16_t root_midi_note); + static DaisyPod pod; -// static NCO nco; -static HarmonicNCO harmonicNco; -static uint16_t nco_kval; +static HarmonicNCO harmonicNco[NUM_INTERVALS]; static float amp = 0.5f; -static float freq = FREQ_A4; -static int midi_note = MIDI_A4; -static int prev_midi_note = MIDI_A4; +static uint16_t s_midi_note = MIDI_A4; +static uint16_t prev_midi_note = MIDI_A4; static float harmonicAmps[] = { 1.0, - 0.8, 0.7, - 0.0, + 0.6, + 0.3, 0.0, 0.0, 0.0, @@ -50,24 +57,59 @@ static void AudioCallback(AudioHandle::InterleavingInputBuffer in, pod.ProcessAllControls(); - midi_note += pod.encoder.Increment(); - midi_note = DSY_CLAMP(midi_note, 0, 127); + s_midi_note += pod.encoder.Increment(); + s_midi_note = DSY_CLAMP(s_midi_note, 0, 127); - if (midi_note != prev_midi_note) + if (s_midi_note != prev_midi_note) { - freq = mtof(midi_note); - harmonicNco.SetFrequency(freq); + UpdateChord(s_midi_note); + + prev_midi_note = s_midi_note; } - prev_midi_note = midi_note; // Audio Loop for (size_t ndx = 0; ndx < size; ndx += 2) { - sample = amp * harmonicNco.NextSample(); + for (uint8_t note = ROOT; note < NUM_INTERVALS; note++) + { + sample += (amp / NUM_INTERVALS) * harmonicNco[note].NextSample(); + } // left out out[ndx] = sample; // right out out[ndx + 1] = sample; + + sample = 0; + } +} + +static void InitChordNcos(uint16_t root_midi_note, uint32_t sample_rate) +{ + for (uint8_t note = ROOT; note < NUM_INTERVALS; note++) + { + harmonicNco[note].SetSampleRate(sample_rate); + harmonicNco[note].SetAmplitudes(harmonicAmps); + harmonicNco[note].SetPhases(harmonicPhases); + } + + // Set the frequencies of each NCO + UpdateChord(root_midi_note); +} + +// Sets the NCO's to play a major chord +static void UpdateChord(uint16_t root_midi_note) +{ + float freq = 0; + uint16_t midi_note = root_midi_note; + + for (uint8_t note = ROOT; note < NUM_INTERVALS; note++) + { + // Apply offset based on interval + midi_note = root_midi_note + (3 * note); + midi_note += (note != ROOT) ? 1 : 0; + + freq = mtof(midi_note); + harmonicNco[note].SetFrequency(freq); } } @@ -79,10 +121,7 @@ int main(void) pod.Init(); sample_rate = pod.AudioSampleRate(); - harmonicNco.SetSampleRate((uint32_t)sample_rate); - harmonicNco.SetFrequency(freq); - harmonicNco.SetAmplitudes(harmonicAmps); - harmonicNco.SetPhases(harmonicPhases); + InitChordNcos(MIDI_A4, (uint32_t)sample_rate); // start callback pod.StartAudio(AudioCallback); From a411748d4de5816a36ae6a7432ec01f20f5a4d95 Mon Sep 17 00:00:00 2001 From: Brian Balberchak Date: Sun, 30 Mar 2025 15:25:51 -0700 Subject: [PATCH 4/5] Created a basic 4-chord chord sequencer using the NCO's --- user/NCO/PodNcoMain.cpp | 108 +++++++++++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 17 deletions(-) diff --git a/user/NCO/PodNcoMain.cpp b/user/NCO/PodNcoMain.cpp index f3acfbab2..91f2c1497 100644 --- a/user/NCO/PodNcoMain.cpp +++ b/user/NCO/PodNcoMain.cpp @@ -3,8 +3,10 @@ #include "nco.h" #include "harmonicNco.h" -#define MIDI_A4 69 -#define FREQ_A4 440 +#define MIDI_C4 60 +#define MIDI_A4 69 +#define FREQ_A4 440 +#define NUM_MIDI_NOTES_PER_OCTAVE 12 using namespace daisysp; using namespace daisy; @@ -17,13 +19,30 @@ enum NoteInterval NUM_INTERVALS }; -static void UpdateChord(uint16_t root_midi_note); +enum ChordSequence +{ + CHORD_I, + CHORD_V, + CHORD_VI_M, + CHORD_IV, + CHORD_SEQ_LEN +}; + +enum BasicChordType +{ + MAJOR, + MINOR +}; + +static void UpdateChord(uint16_t root_midi_note, BasicChordType chord_type); +static void AdvanceChord(uint16_t root_midi_note, int direction); static DaisyPod pod; static HarmonicNCO harmonicNco[NUM_INTERVALS]; -static float amp = 0.5f; -static uint16_t s_midi_note = MIDI_A4; -static uint16_t prev_midi_note = MIDI_A4; +static float amp = 0.5f; +// static uint16_t s_midi_note = MIDI_A4; +static uint16_t s_key_root_note = MIDI_C4; +// static uint16_t prev_midi_note = MIDI_A4; static float harmonicAmps[] = { @@ -54,17 +73,25 @@ static void AudioCallback(AudioHandle::InterleavingInputBuffer in, size_t size) { float sample = 0; + int encoder_dir = 0; pod.ProcessAllControls(); - s_midi_note += pod.encoder.Increment(); - s_midi_note = DSY_CLAMP(s_midi_note, 0, 127); + // s_midi_note += pod.encoder.Increment(); + // s_midi_note = DSY_CLAMP(s_midi_note, 0, 127); - if (s_midi_note != prev_midi_note) - { - UpdateChord(s_midi_note); + // if (s_midi_note != prev_midi_note) + // { + // UpdateChord(s_midi_note); + + // prev_midi_note = s_midi_note; + // } - prev_midi_note = s_midi_note; + encoder_dir = pod.encoder.Increment(); + + if (encoder_dir) + { + AdvanceChord(s_key_root_note, encoder_dir); } // Audio Loop @@ -93,11 +120,11 @@ static void InitChordNcos(uint16_t root_midi_note, uint32_t sample_rate) } // Set the frequencies of each NCO - UpdateChord(root_midi_note); + UpdateChord(root_midi_note, MAJOR); } // Sets the NCO's to play a major chord -static void UpdateChord(uint16_t root_midi_note) +static void UpdateChord(uint16_t root_midi_note, BasicChordType chord_type) { float freq = 0; uint16_t midi_note = root_midi_note; @@ -106,22 +133,69 @@ static void UpdateChord(uint16_t root_midi_note) { // Apply offset based on interval midi_note = root_midi_note + (3 * note); - midi_note += (note != ROOT) ? 1 : 0; + + if (chord_type == MAJOR) + { + midi_note += (note != ROOT) ? 1 : 0; + } + else + { + midi_note += (note == FIFTH) ? 1 : 0; + } + // Keep chord notes w/in an octave of the key root note + midi_note = ((midi_note - s_key_root_note) % NUM_MIDI_NOTES_PER_OCTAVE) + s_key_root_note; freq = mtof(midi_note); harmonicNco[note].SetFrequency(freq); } } +static void AdvanceChord(uint16_t root_midi_note, int direction) +{ + static uint8_t current_chord = CHORD_I; + + // Advance to next chord + if (direction > 0) + { + current_chord = (current_chord + 1) % CHORD_SEQ_LEN; + } + else if (direction < 0) + { + current_chord = (current_chord + CHORD_SEQ_LEN - 1) % CHORD_SEQ_LEN; + } + + switch (current_chord) + { + case CHORD_I: + UpdateChord(root_midi_note, MAJOR); + break; + + case CHORD_V: + UpdateChord((root_midi_note + 7), MAJOR); + break; + + case CHORD_VI_M: + UpdateChord((root_midi_note + 9), MINOR); + break; + + case CHORD_IV: + UpdateChord((root_midi_note + 5), MAJOR); + break; + + default: + break; + } +} + int main(void) { - // initialize pod hardware and oscillator daisysp module + // Initialize pod hardware and oscillator daisysp module float sample_rate; pod.Init(); sample_rate = pod.AudioSampleRate(); - InitChordNcos(MIDI_A4, (uint32_t)sample_rate); + InitChordNcos(s_key_root_note, (uint32_t)sample_rate); // start callback pod.StartAudio(AudioCallback); From 34ebe7a929c750b637d626c416b41df802bc8206 Mon Sep 17 00:00:00 2001 From: Brian Balberchak Date: Sun, 28 Sep 2025 19:38:43 -0700 Subject: [PATCH 5/5] Added a button handler to advance the chords --- user/NCO/PodNcoMain.cpp | 48 +++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/user/NCO/PodNcoMain.cpp b/user/NCO/PodNcoMain.cpp index 91f2c1497..d2eb6be6a 100644 --- a/user/NCO/PodNcoMain.cpp +++ b/user/NCO/PodNcoMain.cpp @@ -36,15 +36,14 @@ enum BasicChordType static void UpdateChord(uint16_t root_midi_note, BasicChordType chord_type); static void AdvanceChord(uint16_t root_midi_note, int direction); +static int getButtonDir(void); static DaisyPod pod; static HarmonicNCO harmonicNco[NUM_INTERVALS]; static float amp = 0.5f; -// static uint16_t s_midi_note = MIDI_A4; static uint16_t s_key_root_note = MIDI_C4; -// static uint16_t prev_midi_note = MIDI_A4; -static float harmonicAmps[] = +static float harmonicAmps[] = { 1.0, 0.7, @@ -56,7 +55,7 @@ static float harmonicAmps[] = 0.0 }; -static float harmonicPhases[] = +static float harmonicPhases[] = { 0.0, 0.9, @@ -74,24 +73,23 @@ static void AudioCallback(AudioHandle::InterleavingInputBuffer in, { float sample = 0; int encoder_dir = 0; + int button_dir = 0; pod.ProcessAllControls(); - // s_midi_note += pod.encoder.Increment(); - // s_midi_note = DSY_CLAMP(s_midi_note, 0, 127); + button_dir = getButtonDir(); - // if (s_midi_note != prev_midi_note) - // { - // UpdateChord(s_midi_note); - - // prev_midi_note = s_midi_note; - // } + if (button_dir) + { + AdvanceChord(s_key_root_note, button_dir); + } encoder_dir = pod.encoder.Increment(); if (encoder_dir) { - AdvanceChord(s_key_root_note, encoder_dir); + s_key_root_note += encoder_dir; + AdvanceChord(s_key_root_note, 0); } // Audio Loop @@ -187,6 +185,28 @@ static void AdvanceChord(uint16_t root_midi_note, int direction) } } +static int getButtonDir(void) +{ + bool button_1_state = pod.button1.RisingEdge(); + bool button_2_state = pod.button2.RisingEdge(); + int button_dir = 0; + + if (button_1_state && !button_2_state) + { + button_dir = -1; + } + else if (!button_1_state && button_2_state) + { + button_dir = 1; + } + else + { + button_dir = 0; + } + + return button_dir; +} + int main(void) { // Initialize pod hardware and oscillator daisysp module @@ -197,7 +217,7 @@ int main(void) InitChordNcos(s_key_root_note, (uint32_t)sample_rate); - // start callback + // Start audio callback pod.StartAudio(AudioCallback);