diff --git a/patch/ByteShift/.vscode/settings.json b/patch/ByteShift/.vscode/settings.json new file mode 100644 index 000000000..57c203072 --- /dev/null +++ b/patch/ByteShift/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "algorithm": "cpp" + } +} \ No newline at end of file diff --git a/patch/ByteShift/Makefile b/patch/ByteShift/Makefile new file mode 100644 index 000000000..25768a012 --- /dev/null +++ b/patch/ByteShift/Makefile @@ -0,0 +1,62 @@ +# Project Name +TARGET = byteshift + +# Library Locations +LIBDAISY_DIR = ../../libDaisy +DAISYSP_DIR = ../../DaisySP + +# Core location and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# Source files +CPP_SOURCES = main.cpp byteshift.cpp control_manager.cpp bytebeat_synth.cpp + +# Include the Daisy system Makefile +include $(SYSTEM_FILES_DIR)/Makefile + +# Define the compiled program binary +PROGRAM = byteshift.bin +FLASH_ADDR = 0x08000000 + +# Convert ELF to BIN +build/$(PROGRAM): build/$(TARGET).elf + arm-none-eabi-objcopy -O binary build/$(TARGET).elf build/$(PROGRAM) + +# Flash to Daisy Patch using ST-LINK V3 Mini +program: build/$(PROGRAM) + st-flash --reset write build/$(PROGRAM) $(FLASH_ADDR) + + + + + + +# # Project Name +# TARGET = byteshift + +# # Use LGPL version of DaisySP (optional, set to 1 if needed) +# USE_DAISYSP_LGPL=1 + +# # Library Locations +# LIBDAISY_DIR = ../../libDaisy +# DAISYSP_DIR = ../../DaisySP + +# # Core location and generic Makefile. +# SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# # Source files +# CPP_SOURCES = main.cpp byteshift.cpp bytebeat_synth.cpp + +# # Include the Daisy system Makefile +# include $(SYSTEM_FILES_DIR)/Makefile + +# # Ensure .bin file is created from .elf +# build/byteshift.bin: build/byteshift.elf +# arm-none-eabi-objcopy -O binary build/byteshift.elf build/byteshift.bin + +# # Flashing using STLINK-V3MINI +# PROGRAM = byteshift.bin # Ensure we're flashing the .bin, not .elf +# FLASH_ADDR = 0x08000000 + +# program: build/byteshift.bin # Make sure .bin exists before flashing +# st-flash --reset write build/byteshift.bin $(FLASH_ADDR) \ No newline at end of file diff --git a/patch/ByteShift/README.md b/patch/ByteShift/README.md new file mode 100644 index 000000000..af25d92d4 --- /dev/null +++ b/patch/ByteShift/README.md @@ -0,0 +1,43 @@ +# 🎡 ByteShift + +ByteShift is an experimental music synthesizer that generates sound using mathematical formulas known as bytebeat equations. It runs on the Daisy Patch platform, using control voltage (CV) and knob-based inputs to modify parameters in real time. + +## πŸ”₯ Features +- Bytebeat Audio Synthesis: Uses a selection of classic bytebeat formulas. +- Dynamic Formula Parameters: Modify parameters a, b, and c in real-time. Not all formulas use all all three controls, some use only two of the controls. +- Built-in Formula Switching: Cycle through 6 bytebeat equations. +- CV Input: Adjust parameters. +- Adjust Pitch in Semitones: ByteShift uses PitchShifter from the DaisySP Lib to change the pitch of output. This works positive and negative. PitcheShifter seems to have some limits when lowering pitch. At -12 semitones the sound disappears, and beyond that the picth rises. +- The OLED display shows the values for a, b, c, pitch, and current formula. + +## πŸŽ›οΈ Controls + +### Control Function + +| Control | Function | +| :------ | :------- | +| CTRL 1 | (Parameter A) Adjusts the first variable (a) in the bytebeat formula. | +| CTRL 2 | (Parameter B) Adjusts the second variable (b) in the bytebeat formula. | +| CTRL 3 | (Parameter C) Adjusts the third variable (c) in the bytebeat formula. | +| Encoder (Press) | Cycles through different bytebeat formulas. | +| Encoder (Rotate) | changes the pitch it semitones. | + +## ⚑ How to Use +1. Audio is sent to out1 and out 2. +2. Use CTRL1-3 to adjust parameters affecting bytebeat formula. +3. Rotate the encoder to change the pitch in semitones. +4. Press the encoder to change the bytebeat formula. + +## πŸ› οΈ Installation & Compilation + +To build and flash BytebeatSynth onto your Daisy Patch: +1. Clone or copy the project files. +2. Run `make` to compile the firmware. + +## 🎡 What is a Bytebeat? + +A bytebeat is a type of generative music where sound is created using simple mathematical formulas instead of traditional synthesis or sampling. These formulas operate on a time variable (t) to generate an audio waveform in real time, often resulting in glitchy, chiptune-like, and chaotic sounds. + +Bytebeats emerged in 2011 when Finnish developer viznut (aka Ville-Matias HeikkilΓ€) discovered that short arithmetic expressions could create rich and evolving sound patterns. This led to an explosion of interest in algorithmic music, with musicians and programmers experimenting with different formulas to produce everything from rhythmic patterns to bizarre noise textures. + +Since bytebeat synthesis is entirely formula-driven, modifying the equations can create new sonic landscapes, making it an ideal playground for experimentation in digital sound generation. diff --git a/patch/ByteShift/bytebeat_synth.cpp b/patch/ByteShift/bytebeat_synth.cpp new file mode 100644 index 000000000..e306552ab --- /dev/null +++ b/patch/ByteShift/bytebeat_synth.cpp @@ -0,0 +1,95 @@ +#include "bytebeat_synth.h" + +// ******************************************************************* +// +// Bytebeat formulas +// +// ******************************************************************* + +// Bytebeat Formulas (Function Pointer Table) +BytebeatSynth::BytebeatFunc BytebeatSynth::formulaTable[] = { + &BytebeatSynth::BytebeatFormula0, + &BytebeatSynth::BytebeatFormula1, + &BytebeatSynth::BytebeatFormula2, + &BytebeatSynth::BytebeatFormula3, + &BytebeatSynth::BytebeatFormula4, + &BytebeatSynth::BytebeatFormula5 +}; + + +// Bytebeat Equation Definitions (Now use a, b, c) +uint8_t BytebeatSynth::BytebeatFormula0(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; + int b = synth->b; + + uint8_t result = (t * (t >> a) & 42) | (t * (t >> b) & 84); + return result; +} + +uint8_t BytebeatSynth::BytebeatFormula1(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; + int b = synth->b; + int c = synth->c; + return (t >> a) | (t << b) | (t * (t >> c)); +} + +uint8_t BytebeatSynth::BytebeatFormula2(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; + int b = synth->b; + return (t * (t >> a) | (t * (t >> b) & 50)); +} + +uint8_t BytebeatSynth::BytebeatFormula3(uint32_t t, BytebeatSynth* synth) { + int b = synth->b; + int c = synth->c; + return ((t * 3) & (t >> b)) | (t * (t >> c)); +} + +uint8_t BytebeatSynth::BytebeatFormula4(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; + int b = synth->b; + return (t * 5 & (t >> a)) | (t * (t >> b) & 123); +} + +uint8_t BytebeatSynth::BytebeatFormula5(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; + int b = synth->b; + return (t >> a) ^ (t * (t >> b) & 32); +} + + +// ******************************************************************* +// +// Bytebeat Synth +// +// ******************************************************************* + +float BytebeatSynth::GenerateSample() { + static uint32_t t = 0; + t++; // Increment counter each sample + + // Get the current formula + BytebeatFunc currentFormula = formulaTable[formulaIndex]; + // Calculate the formula + uint8_t output = currentFormula(t, this); + float sample = ((float)output / 255.0f) * 2.0f - 1.0f; + + // Basic Bytebeat formula + // Don't lose this formula it sounded good. + // float sample = ((t * (t >> a | t >> b) & c & t >> 8)) / 255.0f; + + return sample; +} + +void BytebeatSynth::UpdateControls(ControlManager& controlManager) { + // Read control values + a = (int)(controlManager.GetCtrl1() * 16) + 1; + b = (int)(controlManager.GetCtrl2() * 32) + 1; + c = (int)(controlManager.GetCtrl3() * 64) + 1; + + // Advance the formula index if the encoder is pressed. + if (controlManager.IsEncoderPressed()) { + formulaIndex = (formulaIndex + 1) % FORMULA_COUNT; + } + +} diff --git a/patch/ByteShift/bytebeat_synth.h b/patch/ByteShift/bytebeat_synth.h new file mode 100644 index 000000000..dee4d014e --- /dev/null +++ b/patch/ByteShift/bytebeat_synth.h @@ -0,0 +1,40 @@ +#ifndef BYTEBEAT_SYNTH_H +#define BYTEBEAT_SYNTH_H + +#include "daisy_patch.h" +#include "control_manager.h" + + +// ******************************************************************* +// +// Bytebeat Synth h +// +// ******************************************************************* + +class BytebeatSynth { +public: + void Init(daisy::DaisyPatch* p) { patch = p; } + float GenerateSample(); + void UpdateControls(ControlManager& controlManager); + + int a, b, c, formulaIndex; + + static const int FORMULA_COUNT = 6; + +private: + daisy::DaisyPatch* patch; + + // Bytebeat formulas + using BytebeatFunc = uint8_t (*)(uint32_t, BytebeatSynth*); + static BytebeatFunc formulaTable[]; + + static uint8_t BytebeatFormula0(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula1(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula2(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula3(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula4(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula5(uint32_t t, BytebeatSynth* synth); + +}; + +#endif // BYTEBEAT_SYNTH_H \ No newline at end of file diff --git a/patch/ByteShift/byteshift.cpp b/patch/ByteShift/byteshift.cpp new file mode 100644 index 000000000..c6d47405a --- /dev/null +++ b/patch/ByteShift/byteshift.cpp @@ -0,0 +1,22 @@ +#include "byteshift.h" +#include + +// ******************************************************************* +// +// Byte Shift +// +// ******************************************************************* + +void ByteShift::Init(float sampleRate) { + pitchShifter.Init(sampleRate); + pitchShifter.SetTransposition(0.0f); // Default: No pitch shift +} + +void ByteShift::SetPitchShift(int encoderCount) { + float semitoneShift = static_cast(encoderCount); + pitchShifter.SetTransposition(semitoneShift); +} + +float ByteShift::ProcessSample(float inSample) { + return pitchShifter.Process(inSample); +} diff --git a/patch/ByteShift/byteshift.h b/patch/ByteShift/byteshift.h new file mode 100644 index 000000000..626216c2f --- /dev/null +++ b/patch/ByteShift/byteshift.h @@ -0,0 +1,22 @@ +#ifndef BYTESHIFT_H +#define BYTESHIFT_H + +#include "daisysp.h" + +// ******************************************************************* +// +// Byte shift +// +// ******************************************************************* + +class ByteShift { +public: + void Init(float sampleRate); + float ProcessSample(float inSample); // Takes input sample from BytebeatSynth + void SetPitchShift(int encoderCount); + +private: + daisysp::PitchShifter pitchShifter; +}; + +#endif diff --git a/patch/ByteShift/control_manager.cpp b/patch/ByteShift/control_manager.cpp new file mode 100644 index 000000000..2c7ed3dca --- /dev/null +++ b/patch/ByteShift/control_manager.cpp @@ -0,0 +1,37 @@ +#include "control_manager.h" + +// ******************************************************************* +// +// Control Manager +// +// ******************************************************************* + +void ControlManager::Init(daisy::DaisyPatch* p) { + patch = p; + patch->StartAdc(); // Must start ADC before reading controls! + + ctrl1 = ctrl2 = ctrl3 = ctrl4 = 0.0f; + encoderCount = 0; + encoderPressed = false; +} + +void ControlManager::Update() { + // Read control values + patch->ProcessAnalogControls(); + patch->ProcessDigitalControls(); + + // Store control values + ctrl1 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_1); + ctrl2 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_2); + ctrl3 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_3); + ctrl4 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_4); + + // Read the encoder + encoderCount += patch->encoder.Increment(); + encoderPressed = patch->encoder.RisingEdge(); + + // Reset pitch shift when changing formula + if (encoderPressed) { + encoderCount = 0; + } +} \ No newline at end of file diff --git a/patch/ByteShift/control_manager.h b/patch/ByteShift/control_manager.h new file mode 100644 index 000000000..24b48ea01 --- /dev/null +++ b/patch/ByteShift/control_manager.h @@ -0,0 +1,35 @@ +#ifndef CONTROL_MANAGER_H +#define CONTROL_MANAGER_H + +#include "daisy_patch.h" + +// ******************************************************************* +// +// Control Manager h +// +// ******************************************************************* + +class ControlManager { +public: + void Init(daisy::DaisyPatch* p); + void Update(); + + // Getters return raw control values + float GetCtrl1() const { return ctrl1; } + float GetCtrl2() const { return ctrl2; } + float GetCtrl3() const { return ctrl3; } + float GetCtrl4() const { return ctrl4; } + + // Getter returns the encoder count and isPressed + int GetEncoderCount() const { return encoderCount; } + bool IsEncoderPressed() const { return encoderPressed; } + +private: + daisy::DaisyPatch* patch; + + float ctrl1, ctrl2, ctrl3, ctrl4; + int encoderCount; + bool encoderPressed; +}; + +#endif \ No newline at end of file diff --git a/patch/ByteShift/main.cpp b/patch/ByteShift/main.cpp new file mode 100644 index 000000000..e1c12906e --- /dev/null +++ b/patch/ByteShift/main.cpp @@ -0,0 +1,86 @@ + +// An app for Daisy Patch that creates a Bytebeat synthesizer. + +#include "daisy_patch.h" +#include "control_manager.h" +#include "bytebeat_synth.h" +#include "byteshift.h" + +using namespace daisy; + +DaisyPatch patch; +ControlManager controlManager; +BytebeatSynth bytebeat; +ByteShift byteshift; + +// ******************************************************************* +// +// ⚑ Audio callback function +// +// ******************************************************************* + +void AudioCallback(const float* const* in, float** out, size_t size) { + for (size_t i = 0; i < size; i++) { + // TODO: pass the control values to Generate sample to decouple controlManager + float rawSample = bytebeat.GenerateSample(); // Bytebeat audio + float shiftedSample = byteshift.ProcessSample(rawSample); // Apply pitch shifting + + out[0][i] = shiftedSample; // Left Channel + out[1][i] = shiftedSample; // Right Channel + } +} + + +// ******************************************************************* +// +// Main +// +// ******************************************************************* + +int main(void) { + // initialize classes + patch.Init(); + controlManager.Init(&patch); + bytebeat.Init(&patch); // TODO: Does this need patch? + byteshift.Init(patch.AudioSampleRate()); + + patch.StartAudio(AudioCallback); + + while (true) { + controlManager.Update(); + bytebeat.UpdateControls(controlManager); // Update Bytebeat with controls + byteshift.SetPitchShift(controlManager.GetEncoderCount()); + + // Clear the display and show the app name + patch.display.Fill(false); + patch.display.SetCursor(0, 0); + patch.display.WriteString("ByteShift v1.0", Font_7x10, true); + + char buffer[32]; + + // Display control values + snprintf(buffer, sizeof(buffer), "a: %d", bytebeat.a); + patch.display.SetCursor(0, 12); + patch.display.WriteString(buffer, Font_7x10, true); + + snprintf(buffer, sizeof(buffer), "b: %d", (int)(controlManager.GetCtrl2() * 100)); + patch.display.SetCursor(42, 12); + patch.display.WriteString(buffer, Font_7x10, true); + + snprintf(buffer, sizeof(buffer), "c: %d", (int)(controlManager.GetCtrl3() * 100)); + patch.display.SetCursor(84, 12); + patch.display.WriteString(buffer, Font_7x10, true); + + // Display pitche shift + snprintf(buffer, sizeof(buffer), "Pitch: %d", controlManager.GetEncoderCount()); + patch.display.SetCursor(0, 24); + patch.display.WriteString(buffer, Font_7x10, true); + + // Display Bytebeat formula + snprintf(buffer, sizeof(buffer), "Formula: %d", bytebeat.formulaIndex); + patch.display.SetCursor(0, 36); + patch.display.WriteString(buffer, Font_7x10, true); + + patch.display.Update(); + } +} \ No newline at end of file diff --git a/patch/HarmonicOscillator/Makefile b/patch/HarmonicOscillator/Makefile new file mode 100644 index 000000000..1fc4d3818 --- /dev/null +++ b/patch/HarmonicOscillator/Makefile @@ -0,0 +1,62 @@ +# Project Name +TARGET = harmonic_osc + +# Library Locations +LIBDAISY_DIR = ../../libDaisy +DAISYSP_DIR = ../../DaisySP + +# Core location and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# Source files +CPP_SOURCES = main.cpp MorphOscillator.cpp + +# Include the Daisy system Makefile +include $(SYSTEM_FILES_DIR)/Makefile + +# Define the compiled program binary +PROGRAM = harmonic_osc.bin +FLASH_ADDR = 0x08000000 + +# Convert ELF to BIN +build/$(PROGRAM): build/$(TARGET).elf + arm-none-eabi-objcopy -O binary build/$(TARGET).elf build/$(PROGRAM) + +# Flash to Daisy Patch using ST-LINK V3 Mini +program: build/$(PROGRAM) + st-flash --reset write build/$(PROGRAM) $(FLASH_ADDR) + + + + + + +# # Project Name +# TARGET = byteshift + +# # Use LGPL version of DaisySP (optional, set to 1 if needed) +# USE_DAISYSP_LGPL=1 + +# # Library Locations +# LIBDAISY_DIR = ../../libDaisy +# DAISYSP_DIR = ../../DaisySP + +# # Core location and generic Makefile. +# SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# # Source files +# CPP_SOURCES = main.cpp byteshift.cpp bytebeat_synth.cpp + +# # Include the Daisy system Makefile +# include $(SYSTEM_FILES_DIR)/Makefile + +# # Ensure .bin file is created from .elf +# build/byteshift.bin: build/byteshift.elf +# arm-none-eabi-objcopy -O binary build/byteshift.elf build/byteshift.bin + +# # Flashing using STLINK-V3MINI +# PROGRAM = byteshift.bin # Ensure we're flashing the .bin, not .elf +# FLASH_ADDR = 0x08000000 + +# program: build/byteshift.bin # Make sure .bin exists before flashing +# st-flash --reset write build/byteshift.bin $(FLASH_ADDR) \ No newline at end of file diff --git a/patch/HarmonicOscillator/MorphOscillator.cpp b/patch/HarmonicOscillator/MorphOscillator.cpp new file mode 100644 index 000000000..7726d3bd6 --- /dev/null +++ b/patch/HarmonicOscillator/MorphOscillator.cpp @@ -0,0 +1,46 @@ +#include "MorphOscillator.h" + +using namespace daisysp; + +void MorphOscillator::Init(float sample_rate) { + for(int i = 0; i < 4; i++) { + osc[i].Init(sample_rate); + osc[i].SetAmp(1.0f); + } + + osc[0].SetWaveform(Oscillator::WAVE_SIN); + osc[1].SetWaveform(Oscillator::WAVE_TRI); + osc[2].SetWaveform(Oscillator::WAVE_SAW); + osc[3].SetWaveform(Oscillator::WAVE_SQUARE); + + freq = 440.0f; + shape = 0.0f; +} + +void MorphOscillator::SetFreq(float f) { + freq = f; + for(int i = 0; i < 4; i++) { + osc[i].SetFreq(freq); + } +} + +void MorphOscillator::SetShape(float s) { + shape = fminf(fmaxf(s, 0.0f), 1.0f); // Clamp between 0 and 1 +} + +void MorphOscillator::SetAmp(float a) { + for(int i = 0; i < 4; i++) { + osc[i].SetAmp(a); + } +} + +float MorphOscillator::Process() { + float morph = shape * 3.0f; // 0 - 3 selects 4 waveforms + int index = (int)morph; // Get the index of the starting wave soemthing like 1.23 + float frac = morph - index; // Get the get the fraction of the next wave form 0.23 + + float a = osc[index].Process(); // Get the value of the current wave form + float b = osc[(index + 1 < 4) ? (index + 1) : 3].Process(); // get the next wave form + + return a * (1.0f - frac) + b * frac; // Mix the wave with a fraction of the next wave +} \ No newline at end of file diff --git a/patch/HarmonicOscillator/MorphOscillator.h b/patch/HarmonicOscillator/MorphOscillator.h new file mode 100644 index 000000000..4177eda4d --- /dev/null +++ b/patch/HarmonicOscillator/MorphOscillator.h @@ -0,0 +1,19 @@ +#pragma once + +#include "daisysp.h" +using namespace daisysp; + +class MorphOscillator { + public: + void Init(float sample_rate); + + void SetFreq(float f); + void SetAmp(float a); + void SetShape(float s); + float Process(); + + private: + Oscillator osc[4]; + float freq; + float shape; +}; \ No newline at end of file diff --git a/patch/HarmonicOscillator/main-v1.cpp b/patch/HarmonicOscillator/main-v1.cpp new file mode 100644 index 000000000..dc0e32175 --- /dev/null +++ b/patch/HarmonicOscillator/main-v1.cpp @@ -0,0 +1,168 @@ +/* + Harmonic Oscillator Synth - v1.0 + --------------------------------- + A 4-voice harmonic oscillator using the Daisy Patch and DaisySP's VariableShapeOscillator. + + Features: + - Selectable harmonic ratio systems (Just Intonation, Harmonic Series, Subharmonics, etc.) + - Smooth morphing between waveforms + - CV pitch input with 1V/oct scaling + - Wide frequency range from LFOs to full pitch + + Controls: + - CTRL_1: Pitch offset over a 3-octave range (scaled by CTRL_4) + - CTRL_2: Harmonic ratio selection (morphs through ratio array) + - CTRL_3: Wave shape morph (sine β†’ triangle β†’ square) + - CTRL_4: Sets the maximum frequency/octave range (from ~0.2Hz to 220Hz) + - Encoder Press: Cycle through harmonic ratio systems + - CV_1: 1V/oct pitch input +*/ + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include + +#define MAX_FREQ 220.0f // Maximum frequency when CTRL_4 is at max +#define MIN_FREQ 0.1f // Minimum frequency when CTRL_4 is at min (LFO rate) + +using namespace daisy; +using namespace daisysp; + +float f = 0.0f; // TEST +float oscFreqs[4] = {0}; + +DaisyPatch patch; +VariableShapeOscillator oscillators[4]; // Using VariableShapeOscillator + +// Expanded just intonation ratio systems (at least 12 per set) +static const float kCommonChords[12] = { + 0.5f, 0.666f, 0.75f, 0.833f, 1.0f, 1.125f, 1.25f, 1.333f, 1.5f, 1.667f, 1.875f, 2.0f +}; + +static const float kHarmonicSeries[12] = { + 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f +}; + +static const float kSubharmonics[12] = { + 0.5f, 0.666f, 0.75f, 0.833f, 1.0f, 1.2f, 1.333f, 1.5f, 1.666f, 2.0f, 2.5f, 3.0f +}; + +static const float kFullRatios[12] = { + 0.5f, 0.666f, 0.75f, 0.833f, 1.0f, 1.125f, 1.25f, 1.333f, 1.5f, 1.667f, 1.875f, 2.0f +}; + +// 3-limit (Pythagorean tuning) - powers of 2 and 3 only +static const float kLimit3[12] = { + 1.0f, 3.0f/2.0f, 4.0f/3.0f, 9.0f/8.0f, 27.0f/16.0f, 16.0f/9.0f, 2.0f, 8.0f/9.0f, 32.0f/27.0f, 243.0f/128.0f, 81.0f/64.0f, 128.0f/81.0f +}; + +// 5-limit tuning - includes prime 5 intervals (pure major/minor thirds) +static const float kLimit5[12] = { + 1.0f, 5.0f/4.0f, 6.0f/5.0f, 4.0f/3.0f, 3.0f/2.0f, 5.0f/3.0f, 8.0f/5.0f, 9.0f/8.0f, 15.0f/8.0f, 10.0f/9.0f, 16.0f/15.0f, 2.0f +}; + +// Tides-style: unison center, harmonics above, subharmonics below +static const float kTidesStyle[12] = { + 1.0f, 3.0f/2.0f, 2.0f, 3.0f, 0.75f, 0.666f, 0.5f, 0.333f, 1.25f, 1.5f, 2.25f, 2.5f +}; + +int mode = 0; // Track encoder mode selection + +void InitOscillators() { + for (int i = 0; i < 4; i++) { + oscillators[i].Init(patch.AudioSampleRate()); + oscillators[i].SetPW(0.5f); + } +} + +void DisplayCurrentMode() { + patch.display.Fill(false); + std::string modeName; + switch (mode) { + case 0: modeName = "Common Chords"; break; + case 1: modeName = "Harmonic Series"; break; + case 2: modeName = "Subharmonics"; break; + case 3: modeName = "Full Ratios"; break; + case 4: modeName = "Limit 3"; break; + case 5: modeName = "Limit 5"; break; + default: modeName = "Tides Style"; break; + } + patch.display.SetCursor(0, 0); + patch.display.WriteString(modeName.c_str(), Font_7x10, true); + + char buffer[32]; + for (int i = 0; i < 4; i++) { + snprintf(buffer, sizeof(buffer), "Out %d: %d Hz", i + 1, (int)(oscFreqs[i])); + patch.display.SetCursor(0, 12 + (i * 10)); + patch.display.WriteString(buffer, Font_7x10, true); + } + + snprintf(buffer, sizeof(buffer), "Knob1: %.2f", patch.GetKnobValue(DaisyPatch::CTRL_1)); + patch.display.SetCursor(0, 54); + patch.display.WriteString(buffer, Font_7x10, true); + + patch.display.Update(); +} + +void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { + patch.ProcessAllControls(); + + float octaveShift = patch.GetKnobValue(DaisyPatch::CTRL_4); + float maxFreq = MIN_FREQ * powf(2.0f, octaveShift * log2f(MAX_FREQ / MIN_FREQ)); + + float pitchOffset = patch.GetKnobValue(DaisyPatch::CTRL_1); + float baseFreq = maxFreq * powf(0.015625f, (1.0f - pitchOffset)); + + // TEMP: ignore CV to debug knob behavior + // float cv = patch.controls[4].Value() * 5.0f; + // float cvFactor = powf(2.0f, cv); + // baseFreq *= cvFactor; + + f = baseFreq; // display for debugging + + const float* ratioSet; + int numRatios; + switch (mode) { + case 0: ratioSet = kCommonChords; numRatios = 12; break; + case 1: ratioSet = kHarmonicSeries; numRatios = 12; break; + case 2: ratioSet = kSubharmonics; numRatios = 12; break; + case 3: ratioSet = kFullRatios; numRatios = 12; break; + case 4: ratioSet = kLimit3; numRatios = 12; break; + case 5: ratioSet = kLimit5; numRatios = 12; break; + default: ratioSet = kTidesStyle; numRatios = 12; break; + } + + int ratioIndex = powf(patch.GetKnobValue(DaisyPatch::CTRL_2), 2.0f) * (numRatios - 1); + ratioIndex = fminf(fmaxf(ratioIndex, 0), numRatios - 1); + + for (int i = 0; i < 4; i++) { + int rIdx = (ratioIndex + i) % numRatios; + float freq = baseFreq * ratioSet[rIdx]; + oscillators[i].SetSync(true); + oscillators[i].SetFreq(freq); + oscillators[i].SetWaveshape(patch.GetKnobValue(DaisyPatch::CTRL_3)); + oscFreqs[i] = freq; + } + + for (size_t i = 0; i < size; i++) { + for (int j = 0; j < 4; j++) { + out[j][i] = oscillators[j].Process(); + } + } +} + +int main(void) { + patch.Init(); + patch.StartAdc(); + InitOscillators(); + patch.StartAudio(AudioCallback); + + while (1) { + patch.ProcessDigitalControls(); + if (patch.encoder.RisingEdge()) { + mode = (mode + 1) % 7; + DisplayCurrentMode(); + } + } +} diff --git a/patch/HarmonicOscillator/main.cpp b/patch/HarmonicOscillator/main.cpp new file mode 100644 index 000000000..642eb6ce3 --- /dev/null +++ b/patch/HarmonicOscillator/main.cpp @@ -0,0 +1,222 @@ +#include "daisy_patch.h" +#include "daisysp.h" +#include "ratios.h" +#include "MorphOscillator.h" +#include +#include + +#define MAX_FREQ 440.0f +#define MIN_FREQ 0.2f + +using namespace daisy; +using namespace daisysp; + +DaisyPatch patch; // Make a patch +MorphOscillator oscillators[4]; // make four oscillators +float oscFreqs[4] = {0}; +int mode = 0; +int ratioIndex = 0; +const char* currentRatioLabel = ""; + +void InitOscillators() { + for (int i = 0; i < 4; i++) { + oscillators[i].Init(patch.AudioSampleRate()); + oscillators[i].SetAmp(0.5f); + } +} + +void DisplayCurrentMode() { + patch.display.Fill(false); + std::string modeName = "Symmetric Limit 5"; + patch.display.SetCursor(0, 0); + patch.display.WriteString(modeName.c_str(), Font_7x10, true); + + char buffer[32]; + for (int i = 0; i < 4; i++) { + snprintf(buffer, sizeof(buffer), "Out %d: %d Hz", i + 1, (int)(oscFreqs[i])); + patch.display.SetCursor(0, 12 + (i * 10)); + patch.display.WriteString(buffer, Font_7x10, true); + } + + patch.display.SetCursor(0, 54); + patch.display.WriteString(currentRatioLabel, Font_7x10, true); + patch.display.Update(); +} + +void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { + patch.ProcessAllControls(); + + float octaveShift = patch.GetKnobValue(DaisyPatch::CTRL_4); + float maxFreq = MIN_FREQ * powf(2.0f, octaveShift * log2f(MAX_FREQ / MIN_FREQ)); + float pitchOffset = patch.GetKnobValue(DaisyPatch::CTRL_1); + float baseFreq = maxFreq * powf(0.015625f, (1.0f - pitchOffset)); + + ratioIndex = powf(patch.GetKnobValue(DaisyPatch::CTRL_2), 2.0f) * (kLimit5SymmetricalSize - 1); + ratioIndex = fminf(fmaxf(ratioIndex, 0), kLimit5SymmetricalSize - 1); + currentRatioLabel = kLimit5Symmetrical[ratioIndex].label; + + for (int i = 0; i < 4; i++) { + int rIdx = (ratioIndex + i) % kLimit5SymmetricalSize; + float freq = baseFreq * kLimit5Symmetrical[rIdx].value; + oscillators[i].SetFreq(freq); + oscillators[i].SetShape(patch.GetKnobValue(DaisyPatch::CTRL_3)); + oscFreqs[i] = freq; + } + + for (size_t i = 0; i < size; i++) { + for (int j = 0; j < 4; j++) { + out[j][i] = oscillators[j].Process(); + } + } +} + +int main(void) { + patch.Init(); + patch.StartAdc(); + InitOscillators(); + patch.StartAudio(AudioCallback); + + while (1) { + patch.ProcessDigitalControls(); + DisplayCurrentMode(); + } +} + + + + + + + + + + + + +// /* +// Harmonic Oscillator Synth - v2.0 +// -------------------------------- +// A 4-voice oscillator engine with CV and knob control, using MorphOscillator for waveform blending. + +// Features: +// - 4 independent voices sent to Out 1–4 +// - Smooth waveform morphing from sine β†’ triangle β†’ saw β†’ square +// - Selectable harmonic ratio systems: just intonation, 3/5/7/11-limit, harmonic series, subharmonics, and Tides-style +// - CTRL_1: Pitch sweep (over 6 octaves, ending at max frequency) +// - CTRL_2: Ratio selector (offset through chosen harmonic set) +// - CTRL_3: Waveform morph control +// - CTRL_4: Sets the top of the pitch range (from LFO to ~440Hz) +// - Encoder press: Cycles through harmonic ratio systems +// */ + +// #include "daisy_patch.h" +// #include "daisysp.h" +// #include "ratios.h" +// #include "MorphOscillator.h" +// #include +// #include + +// #define MAX_FREQ 440.0f +// #define MIN_FREQ 0.2f + +// using namespace daisy; +// using namespace daisysp; + +// DaisyPatch patch; +// MorphOscillator oscillators[4]; +// float oscFreqs[4] = {0}; +// int mode = 0; + +// void InitOscillators() { +// for (int i = 0; i < 4; i++) { +// oscillators[i].Init(patch.AudioSampleRate()); +// oscillators[i].SetAmp(0.5f); +// } +// } + +// void DisplayCurrentMode() { +// patch.display.Fill(false); +// std::string modeName; +// switch (mode) { +// case 0: modeName = "Common Chords"; break; +// case 1: modeName = "Harmonic Series"; break; +// case 2: modeName = "Subharmonics"; break; +// case 3: modeName = "Full Ratios"; break; +// case 4: modeName = "Limit 3"; break; +// case 5: modeName = "Limit 5"; break; +// case 6: modeName = "Limit 7"; break; +// case 7: modeName = "Limit 11"; break; +// default: modeName = "Tides Style"; break; +// } +// patch.display.SetCursor(0, 0); +// patch.display.WriteString(modeName.c_str(), Font_7x10, true); + +// char buffer[32]; +// for (int i = 0; i < 4; i++) { +// snprintf(buffer, sizeof(buffer), "Out %d: %d Hz", i + 1, (int)(oscFreqs[i])); +// patch.display.SetCursor(0, 12 + (i * 10)); +// patch.display.WriteString(buffer, Font_7x10, true); +// } + +// snprintf(buffer, sizeof(buffer), "Knob1: %.2f", patch.GetKnobValue(DaisyPatch::CTRL_1)); +// patch.display.SetCursor(0, 54); +// patch.display.WriteString(buffer, Font_7x10, true); + +// patch.display.Update(); +// } + +// void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { +// patch.ProcessAllControls(); + +// float octaveShift = patch.GetKnobValue(DaisyPatch::CTRL_4); +// float maxFreq = MIN_FREQ * powf(2.0f, octaveShift * log2f(MAX_FREQ / MIN_FREQ)); + +// float pitchOffset = patch.GetKnobValue(DaisyPatch::CTRL_1); +// float baseFreq = maxFreq * powf(0.015625f, (1.0f - pitchOffset)); + +// const float* ratioSet; +// int numRatios; +// switch (mode) { +// case 0: ratioSet = kCommonChords; numRatios = 12; break; +// case 1: ratioSet = kHarmonicSeries; numRatios = 12; break; +// case 2: ratioSet = kSubharmonics; numRatios = 12; break; +// case 3: ratioSet = kFullRatios; numRatios = 12; break; +// case 4: ratioSet = kLimit3; numRatios = 12; break; +// case 5: ratioSet = kLimit5; numRatios = 12; break; +// case 6: ratioSet = kLimit7; numRatios = 12; break; +// case 7: ratioSet = kLimit11; numRatios = 12; break; +// default: ratioSet = kTidesStyle; numRatios = 12; break; +// } + +// int ratioIndex = powf(patch.GetKnobValue(DaisyPatch::CTRL_2), 2.0f) * (numRatios - 1); +// ratioIndex = fminf(fmaxf(ratioIndex, 0), numRatios - 1); + +// for (int i = 0; i < 4; i++) { +// int rIdx = (ratioIndex + i) % numRatios; +// float freq = baseFreq * ratioSet[rIdx]; +// oscillators[i].SetFreq(freq); +// oscillators[i].SetShape(patch.GetKnobValue(DaisyPatch::CTRL_3)); +// oscFreqs[i] = freq; +// } + +// for (size_t i = 0; i < size; i++) { +// for (int j = 0; j < 4; j++) { +// out[j][i] = oscillators[j].Process(); +// } +// } +// } + +// int main(void) { +// patch.Init(); +// patch.StartAdc(); +// InitOscillators(); +// patch.StartAudio(AudioCallback); + +// while (1) { +// patch.ProcessDigitalControls(); +// if (patch.encoder.RisingEdge()) { +// mode = (mode + 1) % 9; +// DisplayCurrentMode(); +// } +// } +// } diff --git a/patch/HarmonicOscillator/notes/three_limit b/patch/HarmonicOscillator/notes/three_limit new file mode 100755 index 000000000..21a8b59bb Binary files /dev/null and b/patch/HarmonicOscillator/notes/three_limit differ diff --git a/patch/HarmonicOscillator/notes/three_limit.cpp b/patch/HarmonicOscillator/notes/three_limit.cpp new file mode 100644 index 000000000..e7d928593 --- /dev/null +++ b/patch/HarmonicOscillator/notes/three_limit.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +// Structure to store a ratio +struct Ratio { + float value; + int numerator; + int denominator; +}; + +// Generate all 3-limit ratios within [minRatio, maxRatio] +std::vector Generate3LimitRatios(float minRatio = 0.5f, float maxRatio = 2.0f) { + std::vector results; + + // Search powers from -6 to +6 for both primes + for (int n = -6; n <= 6; ++n) { + for (int m = -6; m <= 6; ++m) { + float ratio = powf(2.0f, n) * powf(3.0f, m); + if (ratio >= minRatio && ratio <= maxRatio) { + // Reduce to a fraction approximation + int num = static_cast(roundf(ratio * 10000)); + int den = 10000; + + // Reduce fraction + int a = num, b = den; + while (b != 0) { + int t = b; + b = a % b; + a = t; + } + int gcd = a; + + results.push_back({ ratio, num / gcd, den / gcd }); + } + } + } + + // Sort by ratio value + std::sort(results.begin(), results.end(), [](const Ratio& a, const Ratio& b) { + return a.value < b.value; + }); + + return results; +} + +int main() { + auto ratios = Generate3LimitRatios(0.5f, 2.0f); + for (const auto& r : ratios) { + std::cout << r.numerator << ":" << r.denominator << " (" << r.value << ")" << std::endl; + } + return 0; +} diff --git a/patch/HarmonicOscillator/ratios.h b/patch/HarmonicOscillator/ratios.h new file mode 100644 index 000000000..14cbd33fd --- /dev/null +++ b/patch/HarmonicOscillator/ratios.h @@ -0,0 +1,85 @@ +#pragma once + +struct Ratio { + float value; + const char* label; +}; + +// Example: 5-limit Just Intonation with symmetric structure +static const Ratio kLimit5Symmetrical[] = { + { 0.5f, "1:2 (Octave Down)" }, + { 0.666f, "2:3 (Perfect Fifth Down)" }, + { 0.75f, "3:4 (Perfect Fourth Down)" }, + { 0.8f, "4:5 (Major Third Down)" }, + { 0.833f, "5:6 (Minor Third Down)" }, + { 1.0f, "1:1 (Unison)" }, + { 1.2f, "6:5 (Minor Third Up)" }, + { 1.25f, "5:4 (Major Third Up)" }, + { 1.333f, "4:3 (Perfect Fourth Up)" }, + { 1.5f, "3:2 (Perfect Fifth Up)" }, + { 1.666f, "5:3 (Major Sixth Up)" }, + { 2.0f, "2:1 (Octave Up)" } +}; + +static const int kLimit5SymmetricalSize = sizeof(kLimit5Symmetrical) / sizeof(Ratio); + + + + + + + + + + +// #pragma once + +// // Common Chords (triads and simple intervals) +// static const float kCommonChords[12] = { +// 0.5f, 0.666f, 0.75f, 0.833f, 1.0f, 1.125f, 1.25f, 1.333f, 1.5f, 1.667f, 1.875f, 2.0f +// }; + +// // Harmonic Series (partials of a fundamental) +// static const float kHarmonicSeries[12] = { +// 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f +// }; + +// // Subharmonics (inverted harmonic series) +// static const float kSubharmonics[12] = { +// 0.5f, 0.666f, 0.75f, 0.833f, 1.0f, 1.2f, 1.333f, 1.5f, 1.666f, 2.0f, 2.5f, 3.0f +// }; + +// // Extended Just Intonation Ratios +// static const float kFullRatios[12] = { +// 0.5f, 0.666f, 0.75f, 0.833f, 1.0f, 1.125f, 1.25f, 1.333f, 1.5f, 1.667f, 1.875f, 2.0f +// }; + +// // Pythagorean / 3-limit Tuning +// static const float kLimit3[12] = { +// 1.0f, 3.0f/2.0f, 4.0f/3.0f, 9.0f/8.0f, 27.0f/16.0f, 16.0f/9.0f, +// 2.0f, 8.0f/9.0f, 32.0f/27.0f, 243.0f/128.0f, 81.0f/64.0f, 128.0f/81.0f +// }; + +// // 5-limit Just Intonation (includes pure thirds) +// static const float kLimit5[12] = { +// 1.0f, 5.0f/4.0f, 6.0f/5.0f, 4.0f/3.0f, 3.0f/2.0f, 5.0f/3.0f, +// 8.0f/5.0f, 9.0f/8.0f, 15.0f/8.0f, 10.0f/9.0f, 16.0f/15.0f, 2.0f +// }; + +// // 7-limit Just Intonation (includes septimal intervals) +// static const float kLimit7[12] = { +// 1.0f, 7.0f/6.0f, 8.0f/7.0f, 7.0f/5.0f, 10.0f/7.0f, 7.0f/4.0f, +// 9.0f/7.0f, 14.0f/9.0f, 7.0f/3.0f, 21.0f/16.0f, 28.0f/27.0f, 2.0f +// }; + +// // 11-limit Just Intonation (includes more complex prime ratios) +// static const float kLimit11[12] = { +// 1.0f, 11.0f/10.0f, 11.0f/8.0f, 9.0f/11.0f, 11.0f/6.0f, 22.0f/15.0f, +// 33.0f/22.0f, 11.0f/9.0f, 16.0f/11.0f, 11.0f/7.0f, 11.0f/12.0f, 2.0f +// }; + +// // Tides-style: harmonics above and subharmonics below +// static const float kTidesStyle[12] = { +// 1.0f, 3.0f/2.0f, 2.0f, 3.0f, 0.75f, 0.666f, +// 0.5f, 0.333f, 1.25f, 1.5f, 2.25f, 2.5f +// }; diff --git a/patch/HarmonicOscillator/vox.h b/patch/HarmonicOscillator/vox.h new file mode 100644 index 000000000..a5f72b255 --- /dev/null +++ b/patch/HarmonicOscillator/vox.h @@ -0,0 +1,33 @@ + +#include "daisy_patch.h" +#include "daisysp.h" + +using namespace daisy; +using namespace daisysp; + +class vox { +private: + Oscillator _osc; + +public: + void Init(float sampleRate, DaisyPatch &patch) { + _osc.Init(sampleRate); + _osc.SetWaveForm(Oscillator::WAVE_SAW); + } + + void Read(int pitchPin, int freqPin) { + // fmap + oscFrqKnob = (1023.0 - ) + } + // vox(/* args */); + // ~vox(); + +}; + +// vox::vox(/* args */) +// { +// } + +// vox::~vox() +// { +// } diff --git a/patch/Parametric-eq/Makefile b/patch/Parametric-eq/Makefile new file mode 100644 index 000000000..14c1a488b --- /dev/null +++ b/patch/Parametric-eq/Makefile @@ -0,0 +1,40 @@ +# Project Name +TARGET = parametric_eq + +# Use LGPL version of DaisySP (optional, set to 1 if needed) +USE_DAISYSP_LGPL=1 + +# Library Locations +LIBDAISY_DIR = ../../libDaisy +DAISYSP_DIR = ../../DaisySP + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# Source files +# CPP_SOURCES = bytebeat.cpp +CPP_SOURCES = main.cpp + +# Include the Daisy system Makefile +include $(SYSTEM_FILES_DIR)/Makefile + +# Define the compiled program binary +PROGRAM = parametric_eq.bin +FLASH_ADDR = 0x08000000 + +clean: + rm -rf build + +# Convert ELF to BIN +build/$(PROGRAM): build/$(TARGET).elf + arm-none-eabi-objcopy -O binary $< $@ + +# Flash to Daisy Patch using ST-LINK V3 Mini +program: build/$(PROGRAM) + st-flash --reset write build/$(PROGRAM) $(FLASH_ADDR) + + + + + + diff --git a/patch/Parametric-eq/main.cpp b/patch/Parametric-eq/main.cpp new file mode 100644 index 000000000..14a6dfc81 --- /dev/null +++ b/patch/Parametric-eq/main.cpp @@ -0,0 +1,134 @@ +#include "daisy_patch.h" +#include "daisysp.h" +#include + +using namespace daisy; +using namespace daisysp; + +DaisyPatch patch; +float sample_rate; + +// ------------------------- +// Custom Parametric EQ +// ------------------------- +struct ParametricEQ +{ + float a0, a1, a2, b0, b1, b2; + float z1 = 0.0f, z2 = 0.0f; + float sample_rate = 48000.0f; + + void SetSampleRate(float sr) + { + sample_rate = sr; + } + + void SetParams(float freq, float q, float gain_db) + { + float A = powf(10.0f, gain_db / 40.0f); + float omega = 2.0f * M_PI * freq / sample_rate; + float alpha = sinf(omega) / (2.0f * q); + float cosw = cosf(omega); + + float b0 = 1 + alpha * A; + float b1 = -2 * cosw; + float b2 = 1 - alpha * A; + float a0 = 1 + alpha / A; + float a1 = -2 * cosw; + float a2 = 1 - alpha / A; + + // Normalize + this->b0 = b0 / a0; + this->b1 = b1 / a0; + this->b2 = b2 / a0; + this->a1 = a1 / a0; + this->a2 = a2 / a0; + } + + + float Process(float in) + { + float out = b0 * in + b1 * z1 + b2 * z2 - a1 * z1 - a2 * z2; + z2 = z1; + z1 = out; + return out; + } +}; + +ParametricEQ eq_left, eq_right; + +// ------------------------- +// EQ Initialization & Update +// ------------------------- + +void InitEQ(ParametricEQ& eq) +{ + eq.SetSampleRate(sample_rate); + eq.SetParams(1000.0f, 1.0f, 0.0f); // center, Q, gain +} + +void UpdateEQParams() +{ + patch.ProcessAnalogControls(); + + float freq = patch.controls[DaisyPatch::CTRL_1].Process(); + float gain = patch.controls[DaisyPatch::CTRL_2].Process(); + float q = patch.controls[DaisyPatch::CTRL_3].Process(); + + // float mappedFreq = 20.0f * powf(10.0f, freq * 2.7f); // 20Hz–10kHz + // float mappedGain = (gain * 24.0f) - 12.0f; // -12dB to +12dB + // float mappedQ = 0.1f + q * 9.9f; // 0.1 to 10.0 + + float mappedFreq = 100.0f + freq * 9900.0f; // 100Hz–10kHz (linear) + float mappedGain = (gain * 40.0f) - 20.0f; // -20 to +20 dB + float mappedQ = 0.1f + q * 9.9f; + + eq_left.SetParams(mappedFreq, mappedQ, mappedGain); + eq_right.SetParams(mappedFreq, mappedQ, mappedGain); + + // eq_left.SetParams(1000.0f, 0.5f, 12.0f); // 1kHz, narrow, +12dB + // eq_right.SetParams(1000.0f, 0.5f, 12.0f); +} + +// ------------------------- +// Audio Callback +// ------------------------- + +void AudioCallback(AudioHandle::InputBuffer in, + AudioHandle::OutputBuffer out, + size_t size) +{ + patch.ProcessAllControls(); + UpdateEQParams(); + + for (size_t i = 0; i < size; i++) + { + float left = in[0][i]; + float right = in[1][i]; + + // out[0][i] = eq_left.Process(left); + // out[1][i] = eq_right.Process(right); + out[0][i] = in[0][i]; // bypass + out[1][i] = in[1][i]; + } +} + +// ------------------------- +// Main Entry Point +// ------------------------- + +int main(void) +{ + patch.Init(); + sample_rate = patch.AudioSampleRate(); + + InitEQ(eq_left); + InitEQ(eq_right); + + patch.StartAdc(); + patch.StartAudio(AudioCallback); + + while (1) + { + patch.DelayMs(10); + } +} diff --git a/patch/bytebeat/Makefile b/patch/bytebeat/Makefile new file mode 100644 index 000000000..61106b23b --- /dev/null +++ b/patch/bytebeat/Makefile @@ -0,0 +1,37 @@ +# Project Name +TARGET = bytebeat + +# Use LGPL version of DaisySP (optional, set to 1 if needed) +USE_DAISYSP_LGPL=1 + +# Library Locations +LIBDAISY_DIR = ../../libDaisy +DAISYSP_DIR = ../../DaisySP + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# Source files +# CPP_SOURCES = bytebeat.cpp +CPP_SOURCES = bytebeat.cpp bytebeat_synth.cpp + +# Include the Daisy system Makefile +include $(SYSTEM_FILES_DIR)/Makefile + +# Define the compiled program binary +PROGRAM = bytebeat.bin +FLASH_ADDR = 0x08000000 + +# Convert ELF to BIN +build/$(PROGRAM): build/$(TARGET).elf + arm-none-eabi-objcopy -O binary build/$(TARGET).elf build/$(PROGRAM) + +# Flash to Daisy Patch using ST-LINK V3 Mini +program: build/$(PROGRAM) + st-flash --reset write build/$(PROGRAM) $(FLASH_ADDR) + + + + + + diff --git a/patch/bytebeat/README.md b/patch/bytebeat/README.md new file mode 100644 index 000000000..7102f54f6 --- /dev/null +++ b/patch/bytebeat/README.md @@ -0,0 +1,47 @@ +# 🎡 BytebeatSynth + +BytebeatSynth is an experimental music synthesizer that generates sound using mathematical formulas known as bytebeat equations. It runs on the Daisy Patch platform, using control voltage (CV) and knob-based inputs to modify parameters in real time. + +## πŸ”₯ Features +- Bytebeat Audio Synthesis: Uses a selection of classic bytebeat formulas. +- Dynamic Formula Parameters: Modify parameters a, b, and c in real-time. +- Adjusts how fast the bytebeat algorithm runs, affecting pitch and timbre, with higher values creating higher tones and lower values slowing it down, sometimes to silence. +- Built-in Formula Switching: Cycle through multiple equations with the encoder. +- Analog & CV Input Controls: Adjust speed, parameters, and pitch using hardware knobs or external control voltages. + +## πŸŽ›οΈ Controls + +### Control Function +CTRL 1 (Speed/Pitch) Controls the pitch/speed of the bytebeat equation. Accepts 0-5V CV input. +CTRL 2 (Parameter A) Adjusts the first variable (a) in the bytebeat formula. +CTRL 3 (Parameter B) Adjusts the second variable (b) in the bytebeat formula. +CTRL 4 (Parameter C) Adjusts the third variable (c) in the bytebeat formula. +Encoder (Press) Cycles through different bytebeat formulas. +OLED Display Shows the current formula, pitch, and values of a, b, and c. + +## ⚑ How to Use +1. Turn CTRL 1 to change the pitch and speed of the bytebeat synthesis. +2. Adjust CTRL 2, 3, and 4 to manipulate different formula parameters. +3. Press the encoder to switch to a different bytebeat equation. +4. Use CV input on CTRL 1 to modulate pitch from an external sequencer or LFO. +5. Observe the OLED display to see current formula and parameter values. + +## πŸ› οΈ Installation & Compilation + +To build and flash BytebeatSynth onto your Daisy Patch: +1. Clone or copy the project files. +2. Run `make` to compile the firmware. +3. Flash the binary using: `st-flash --reset write build/bytebeat.bin 0x08000000` + +## 🎚️ Example Patching Ideas +- Sequenced Rhythms: Use an external CV sequencer on CTRL 1 for evolving patterns. +- Glitchy Drones: Set CTRL 1 low and tweak CTRL 2, 3, 4 for texture shifts. +- Noise Percussion: Use a gate signal on CTRL 1 to trigger bursts of sound. + +## 🎡 What is a Bytebeat? + +A bytebeat is a type of generative music where sound is created using simple mathematical formulas instead of traditional synthesis or sampling. These formulas operate on a time variable (t) to generate an audio waveform in real time, often resulting in glitchy, chiptune-like, and chaotic sounds. + +Bytebeats emerged in 2011 when Finnish developer viznut (aka Ville-Matias HeikkilΓ€) discovered that short arithmetic expressions could create rich and evolving sound patterns. This led to an explosion of interest in algorithmic music, with musicians and programmers experimenting with different formulas to produce everything from rhythmic patterns to bizarre noise textures. + +Since bytebeat synthesis is entirely formula-driven, modifying the equations can create new sonic landscapes, making it an ideal playground for experimentation in digital sound generation. diff --git a/patch/bytebeat/bytebeat.cpp b/patch/bytebeat/bytebeat.cpp new file mode 100644 index 000000000..834ce3a34 --- /dev/null +++ b/patch/bytebeat/bytebeat.cpp @@ -0,0 +1,24 @@ +#include "bytebeat_synth.h" + +// ******************************************************************* +// +// Bytebeat Synth main +// +// ******************************************************************* + +int main(void) { + // Create an instance of DaisyPatch assigned to local var patch + DaisyPatch patch; + // Creat an instance of BytebeatSynth assigned to local var synth + BytebeatSynth synth; + // Initialize synth pass patch. Is this a reference what is & ? + synth.Init(&patch); + + // Create a main program loop + while (1) { + // Update display + synth.UpdateDisplay(); + // Chill for 50ms + System::Delay(50); + } +} diff --git a/patch/bytebeat/bytebeat_synth.cpp b/patch/bytebeat/bytebeat_synth.cpp new file mode 100644 index 000000000..57133784c --- /dev/null +++ b/patch/bytebeat/bytebeat_synth.cpp @@ -0,0 +1,196 @@ +#include "bytebeat_synth.h" + +// Global instance pointer for static callback +static BytebeatSynth* synthInstance = nullptr; + +// Bytebeat Formulas (Function Pointer Table) +BytebeatSynth::BytebeatFunc BytebeatSynth::formulaTable[] = { + &BytebeatSynth::BytebeatFormula0, + &BytebeatSynth::BytebeatFormula1, + &BytebeatSynth::BytebeatFormula2, + &BytebeatSynth::BytebeatFormula3, + &BytebeatSynth::BytebeatFormula4, + &BytebeatSynth::BytebeatFormula5 +}; + +// Bytebeat Equation Definitions (Now use a, b, c) +uint8_t BytebeatSynth::BytebeatFormula0(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + + uint8_t result = (t * (t >> a) & 42) | (t * (t >> b) & 84); + return result; +} + +uint8_t BytebeatSynth::BytebeatFormula1(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + int c = synth->c; + return (t >> a) | (t << b) | (t * (t >> c)); +} + +uint8_t BytebeatSynth::BytebeatFormula2(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + return (t * (t >> a) | (t * (t >> b) & 50)); +} + +uint8_t BytebeatSynth::BytebeatFormula3(uint32_t t, BytebeatSynth* synth) { + int b = synth->b; + int c = synth->c; + return ((t * 3) & (t >> b)) | (t * (t >> c)); +} + +uint8_t BytebeatSynth::BytebeatFormula4(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + return (t * 5 & (t >> a)) | (t * (t >> b) & 123); +} + +uint8_t BytebeatSynth::BytebeatFormula5(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + return (t >> a) ^ (t * (t >> b) & 32); +} + +// Static Wrapper for Audio Callback +static void AudioCallbackWrapper(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { + synthInstance->ProcessAudio(in, out, size); +} + + +// ************************************** +// +// Initialize Synth +// +// ************************************** +void BytebeatSynth::Init(DaisyPatch* p) { + patch = p; + speed = 0.1f; + t = 0; + tScale = 1.0f; // βœ… Initialize tScale + formula_index = 0; + synthInstance = this; + + // Initialize Parameters (scaled) + a = 4; b = 6; c = 8; + + patch->Init(); + patch->StartAdc(); + patch->StartAudio(AudioCallbackWrapper); +} + +// Update Controls (Knobs & Encoder) +void BytebeatSynth::UpdateControls() { + patch->ProcessAnalogControls(); + patch->ProcessDigitalControls(); + + // Read Speed (Knob 1) - scaled to 0.1 to 4.1 + speed = patch->GetKnobValue(DaisyPatch::CTRL_1) * 4.0f + 0.1f; + + // Read dynamic parameters (CTRL_2, CTRL_3, CTRL_4) + // Map Knobs 2, 3, and 4 to parameters a, b, and c + a = (int)(patch->GetKnobValue(DaisyPatch::CTRL_2) * 16) + 1; // Range: 1 - 16 + b = (int)(patch->GetKnobValue(DaisyPatch::CTRL_3) * 32) + 1; // Range: 1 - 32 + c = (int)(patch->GetKnobValue(DaisyPatch::CTRL_4) * 64) + 1; // Range: 1 - 64 + + // Check encoder button for formula switching + if (patch->encoder.RisingEdge()) { + formula_index = (formula_index + 1) % formula_count; + } +} + +// Audio Processing Callback +// void BytebeatSynth::ProcessAudio(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { +// UpdateControls(); + +// BytebeatFunc currentFormula = formulaTable[formula_index]; +// int speed_int = static_cast(speed * 10); + +// for (size_t i = 0; i < size; i++) { +// uint8_t sample = currentFormula(t, synthInstance); +// float output = ((float)sample / 255.0f) * 2.0f - 1.0f; +// out[0][i] = output; +// out[1][i] = output; +// t += 1 + speed_int; +// } +// } + +// ***************************************************** +// +// Process Audio +// +// ***************************************************** + +// Constants for min/max frequency +constexpr float MIN_FREQUENCY = 24.0f; // 8.0 Lower limit (adjust as needed) +constexpr float MAX_FREQUENCY = 112.0f; // Upper limit (adjust as needed) + +void BytebeatSynth::ProcessAudio(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { + UpdateControls(); + + BytebeatFunc currentFormula = formulaTable[formula_index]; + + // Read the pitch CV and scale it within the min/max frequency range + float pitchCV = (1.0f - patch->GetKnobValue(DaisyPatch::CTRL_1)) * 5.0f; // Map 0-5V + float frequency = MIN_FREQUENCY * powf(2.0f, pitchCV * log2f(MAX_FREQUENCY / MIN_FREQUENCY)); // Constrain range + + // Convert frequency to a time step + uint32_t tScale = static_cast(48000.0f / frequency); + + for (size_t i = 0; i < size; i++) { + t += tScale; // Step `t` according to the musical pitch scaling + + // Generate bytebeat sample at updated `t` + uint8_t sample = currentFormula(t, this); + float output = ((float)sample / 255.0f) * 2.0f - 1.0f; + + // Send output + out[0][i] = output; + out[1][i] = output; + } +} + + +// ************************************************* +// +// Update Display +// +// ************************************************* + +// OLED Display Update (Now Shows a, b, c) +void BytebeatSynth::UpdateDisplay() { + static int last_speed = -1; + static int last_formula = -1; + static int last_a = -1, last_b = -1, last_c = -1; + + int speed_int = static_cast(speed * 100); + + if (speed_int != last_speed || formula_index != last_formula || a != last_a || b != last_b || c != last_c) { + patch->display.Fill(false); + patch->display.SetCursor(0, 0); + patch->display.WriteString("Bytebeat Synth", Font_7x10, true); + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "Speed: %d", speed_int); + patch->display.SetCursor(0, 12); + patch->display.WriteString(buffer, Font_7x10, true); + + snprintf(buffer, sizeof(buffer), "Formula: %d", formula_index); + patch->display.SetCursor(0, 24); + patch->display.WriteString(buffer, Font_7x10, true); + + // Display a, b, and c values + snprintf(buffer, sizeof(buffer), "a: %d b: %d c: %d", a, b, c); + patch->display.SetCursor(0, 36); + patch->display.WriteString(buffer, Font_7x10, true); + + patch->display.Update(); + + last_speed = speed_int; + last_formula = formula_index; + last_a = a; + last_b = b; + last_c = c; + } +} \ No newline at end of file diff --git a/patch/bytebeat/bytebeat_synth.h b/patch/bytebeat/bytebeat_synth.h new file mode 100644 index 000000000..d88e2e3f9 --- /dev/null +++ b/patch/bytebeat/bytebeat_synth.h @@ -0,0 +1,44 @@ +#ifndef BYTEBEAT_SYNTH_H +#define BYTEBEAT_SYNTH_H + +#include "daisy_patch.h" +#include "daisysp.h" + +using namespace daisy; +using namespace daisysp; + +// ******************************************************************* +// +// Bytebeat Synth h +// +// ******************************************************************* + +class BytebeatSynth { +public: + void Init(DaisyPatch* p); + void UpdateControls(); + void ProcessAudio(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size); + void UpdateDisplay(); + +private: + DaisyPatch* patch; + float speed; + uint32_t t; + float tScale; + int formula_index; + static const int formula_count = 6; + + int a, b, c; // Bytebeat parameters + + using BytebeatFunc = uint8_t (*)(uint32_t, BytebeatSynth*); + static BytebeatFunc formulaTable[]; + + static uint8_t BytebeatFormula0(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula1(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula2(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula3(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula4(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula5(uint32_t t, BytebeatSynth* synth); +}; + +#endif // BYTEBEAT_SYNTH_H \ No newline at end of file diff --git a/terrarrium/DoubleDelay/DelayUnit.h b/terrarrium/DoubleDelay/DelayUnit.h new file mode 100644 index 000000000..94b7dd278 --- /dev/null +++ b/terrarrium/DoubleDelay/DelayUnit.h @@ -0,0 +1,78 @@ +// Version 1.0.4 +#pragma once + +#include "daisysp.h" + +using namespace daisy; +using namespace daisysp; + +constexpr size_t MAX_DELAY = 48000 * 5; // 5 seconds max delay + +inline float LocalSoftClip(float x) +{ + // return x / (1.0f + fabsf(x)); + + // or + + if(x > 1.0f) + return (2.0f / 3.0f); + else if(x < -1.0f) + return (-2.0f / 3.0f); + else + return x - (x * x * x) / 3.0f; + +} + +struct DelayUnit { + DelayLine* buffer; + float currentDelay = 0.5f; + float delayTarget = 0.5f; + float feedback = 0.5f; + Tone lp_filter; + Svf hpf; + + void Init(float samplerate, DelayLine& buf) + { + buffer = &buf; + buffer->Init(); + lp_filter.Init(samplerate); + lp_filter.SetFreq(4000.0f); + hpf.Init(samplerate); + hpf.SetRes(0.7f); + hpf.SetFreq(120.0f); // Cut off sub-bass + hpf.SetDrive(0.0f); + } + + float SnapToRatio(float base_delay_time, float ratio_knob) + { + const float ratios[] = { + 1.0f, 15.0f / 16.0f, 7.0f / 8.0f, 13.0f / 16.0f, 3.0f / 4.0f, + 11.0f / 16.0f, 5.0f / 8.0f, 9.0f / 16.0f, 1.0f / 2.0f, + 7.0f / 16.0f, 3.0f / 8.0f, 5.0f / 16.0f, 1.0f / 4.0f, + 3.0f / 16.0f, 1.0f / 8.0f, 1.0f / 16.0f + }; + const int num_ratios = sizeof(ratios) / sizeof(ratios[0]); + int index = static_cast(ratio_knob * num_ratios); + if(index >= num_ratios) + index = num_ratios - 1; + + float delay_time = base_delay_time * ratios[index]; + return fminf(delay_time, MAX_DELAY); + } + + float Process(float in, float delay_time) + { + delayTarget = delay_time; + fonepole(currentDelay, delayTarget, 0.0002f); + buffer->SetDelay(currentDelay); + + float delayed = buffer->Read(); + float input = (in + delayed * feedback) * 0.7f; + input = LocalSoftClip(input); + float filtered = lp_filter.Process(input); + filtered = fminf(fmaxf(filtered, -1.0f), 1.0f); + buffer->Write(filtered); + + return delayed; + } +}; diff --git a/terrarrium/DoubleDelay/DelayUnitReverse.h b/terrarrium/DoubleDelay/DelayUnitReverse.h new file mode 100644 index 000000000..8e3dc6a45 --- /dev/null +++ b/terrarrium/DoubleDelay/DelayUnitReverse.h @@ -0,0 +1,113 @@ +#pragma once + +#include "daisysp.h" + +using namespace daisy; +using namespace daisysp; + +constexpr size_t MAX_DELAY = 48000 * 5; // 5 seconds max delay + +struct DelayUnit { + DelayLine* buffer; + float currentDelay = 0.5f; + float delayTarget = 0.5f; + float feedback = 0.5f; + Tone lp_filter; + + void Init(float samplerate, DelayLine& buf) + { + buffer = &buf; + buffer->Init(); + lp_filter.Init(samplerate); + lp_filter.SetFreq(4000.0f); + } + + float Process(float in, float delay_time) +{ + delayTarget = delay_time; + + fonepole(currentDelay, delayTarget, 0.0002f); + buffer->SetDelay(currentDelay); + + float delayed = buffer->Read(); + float input = in + delayed * feedback; + float filtered = lp_filter.Process(input); + filtered = fminf(fmaxf(filtered, -1.0f), 1.0f); + buffer->Write(filtered); + + return delayed; +} + + float Process(float in, float delay1_time, float ratio_knob) + { + float max_ratio = MAX_DELAY / delay1_time; + float log_ratio = powf(2.0f, (ratio_knob - 0.5f) * 4.0f); // ~0.25x–4x + float ratio = fminf(log_ratio, max_ratio); + delayTarget = delay1_time * ratio; + + fonepole(currentDelay, delayTarget, 0.0002f); + buffer->SetDelay(currentDelay); + + float delayed = buffer->Read(); + float input = in + delayed * feedback; + float filtered = lp_filter.Process(input); + filtered = fminf(fmaxf(filtered, -1.0f), 1.0f); + buffer->Write(filtered); + + return delayed; + } +}; + + + +// #pragma once + +// #include "delayline_reverse.h" +// #include "daisysp.h" + +// using namespace daisy; +// using namespace daisysp; + +// constexpr size_t MAX_DELAY = 48000 * 5; // 5 seconds max delay + +// struct DelayUnitReverse { +// daisysp::DelayLineReverse* buffer; +// float currentDelay = 0.5f; // in samples (after smoothing) +// float delayTarget = 0.5f; +// float feedback = 0.5f; +// Tone lp_filter; +// bool reverse = false; + +// // For this reverse unit, we always use reverse playback. +// // You could add a flag here if you want to toggle between forward and reverse. + +// void Init(float samplerate, daisysp::DelayLineReverse& buf) +// { +// buffer = &buf; +// buffer->Reset(); +// lp_filter.Init(samplerate); +// lp_filter.SetFreq(4000.0f); +// } + +// // Process the audio sample using reverse delay. +// float Process(float in) +// { +// // Smooth the delay time changes. +// fonepole(currentDelay, delayTarget, 0.002f); +// // Use SetDelay1 (as defined in DelayLineReverse) to set the delay time. +// buffer->SetDelay1(currentDelay); + +// // Choose reading method based on the reverse flag. +// float delayed = reverse ? buffer->ReadRev() : buffer->ReadFwd(); + +// // Mix input with delayed sample using feedback. +// float input = in + delayed * feedback; +// float filtered = lp_filter.Process(input); +// filtered = fminf(fmaxf(filtered, -1.0f), 1.0f); + +// // Write the processed sample into the buffer. +// buffer->Write(filtered); + +// return delayed; +// } +// }; diff --git a/terrarrium/DoubleDelay/ExtendedDelayLine.h b/terrarrium/DoubleDelay/ExtendedDelayLine.h new file mode 100644 index 000000000..0ec43dd5f --- /dev/null +++ b/terrarrium/DoubleDelay/ExtendedDelayLine.h @@ -0,0 +1,92 @@ +#pragma once +#ifndef EXTENDED_DELAY_LINE_H +#define EXTENDED_DELAY_LINE_H + +#include +#include +#include + +namespace daisysp +{ + +/** ExtendedDelayLine implements a simple circular buffer delay line, + similar to DaisySP's DelayLine, but with added support for reverse + reading. It holds an internal buffer of type T with maximum size max_size. +*/ +template +class ExtendedDelayLine +{ + public: + ExtendedDelayLine() { Reset(); } + ~ExtendedDelayLine() {} + + /** Resets the delay line by clearing the buffer and setting the write + pointer to 0, and delay to 1 sample. + */ + void Reset() + { + for (size_t i = 0; i < max_size; i++) + { + buffer_[i] = T(0); + } + write_ptr_ = 0; + delay_ = 1; + frac_ = 0.0f; + } + + /** Sets the delay time in samples. + If a float is passed, the fractional part is stored for interpolation. + */ + inline void SetDelay(float delay) + { + int32_t int_delay = static_cast(delay); + frac_ = delay - static_cast(int_delay); + delay_ = (int_delay < (int32_t)max_size) ? int_delay : max_size - 1; + } + + /** Writes a sample into the delay line and advances the write pointer. + (This implementation writes forward, unlike the original DaisySP version.) + */ + inline void Write(const T sample) + { + buffer_[write_ptr_] = sample; + write_ptr_ = (write_ptr_ + 1) % max_size; + } + + /** Forward read: + Reads the delayed sample using linear interpolation. + */ + inline T Read() const + { + // Calculate read pointer for forward mode: + size_t index1 = (write_ptr_ + max_size - delay_) % max_size; + size_t index2 = (index1 + 1) % max_size; + T a = buffer_[index1]; + T b = buffer_[index2]; + return a + (b - a) * frac_; + } + + /** Reverse read: + Reads the delayed sample "backwards" from the write pointer. + The effective read pointer is calculated as (write_ptr_ + delay_) modulo max_size. + Linear interpolation is used between two adjacent samples. + */ + inline T ReverseRead() const + { + size_t index1 = (write_ptr_ + delay_) % max_size; + size_t index2 = (index1 + max_size - 1) % max_size; + T a = buffer_[index1]; + T b = buffer_[index2]; + return a + (b - a) * frac_; + } + + private: + float frac_; // fractional part of the delay time for interpolation + size_t write_ptr_; // current write pointer index + size_t delay_; // integer delay in samples + T buffer_[max_size]; +}; + +} // namespace daisysp + +#endif // EXTENDED_DELAY_LINE_H diff --git a/terrarrium/DoubleDelay/Makefile b/terrarrium/DoubleDelay/Makefile new file mode 100644 index 000000000..e5f5596ad --- /dev/null +++ b/terrarrium/DoubleDelay/Makefile @@ -0,0 +1,30 @@ +# Project Name +TARGET = dbl_delay + +# Use LGPL version of DaisySP (optional, set to 1 if needed) +USE_DAISYSP_LGPL=1 + +# Library Locations +LIBDAISY_DIR = ../../libDaisy +DAISYSP_DIR = ../../DaisySP + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# Source files +CPP_SOURCES = main.cpp + +# Include the Daisy system Makefile +include $(SYSTEM_FILES_DIR)/Makefile + +# Define the compiled program binary +PROGRAM = dbl_delay.bin +FLASH_ADDR = 0x08000000 + +# Convert ELF to BIN +build/$(PROGRAM): build/$(TARGET).elf + arm-none-eabi-objcopy -O binary build/$(TARGET).elf build/$(PROGRAM) + +# Flash to Daisy Patch using ST-LINK V3 Mini +program: build/$(PROGRAM) + st-flash --reset write build/$(PROGRAM) $(FLASH_ADDR) diff --git a/terrarrium/DoubleDelay/delayline_reverse.h b/terrarrium/DoubleDelay/delayline_reverse.h new file mode 100644 index 000000000..42ce856a6 --- /dev/null +++ b/terrarrium/DoubleDelay/delayline_reverse.h @@ -0,0 +1,207 @@ +// Copyright 2021 Adam Fulford +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// See http://creativecommons.org/licenses/MIT/ for more information. + +#pragma once +#ifndef DSY_DELAY_REVERSE_H +#define DSY_DELAY_REVERSE_H +#include +#include +#include + +namespace daisysp + +{ +/** Reverse Delay line +By: Adam Fulford +*/ +template + +class DelayLineReverse +{ + public: + DelayLineReverse() {} + ~DelayLineReverse() {} + /** initializes the delay line by clearing the values within, and setting delay to min time. + */ + void Init() + { + Reset(); + } + /** clears buffer, sets write ptr to 0, and delay to 1 sample. + */ + void Reset() + { + + delay1_ = 25000; // min Reverse delay time + fadetime = 24000; // in samples = 0.5 seconds + + for(size_t i = 0; i < max_size; i++) + { + line_[i] = T(0); + } + write_ptr_ = 0; + read_ptr1_ = 0; + read_ptr2_ = 0; + headDiff_ = 0; + playinghead_ = false; + fadepos_ = 0.0f; + fading_ = false; + } + + /** sets the delay time in samples + */ + inline void SetDelay1(size_t delay) + { + frac1_ = 0.0f; + delay1_ = delay < max_size ? delay : max_size - 1; + } + + /** sets the delay time in samples + If a float is passed in, a fractional component will be calculated for interpolating the delay line. + */ + inline void SetDelay1(float delay) + { + int32_t int_delay = static_cast(delay); + frac1_ = delay - static_cast(int_delay); + delay1_ = static_cast(int_delay) < max_size ? int_delay + : max_size - 1; + } + + /** writes the sample of type T to the delay line, and advances the write ptr + */ + inline void Write(const T sample) + { + line_[write_ptr_] = sample; + //advance write ptr in forward direction + write_ptr_ = (write_ptr_ + 1 + max_size) % max_size; //increment forwards + + //increment head difference + headDiff_ = (headDiff_ + 1 + delay1_) % delay1_; + + //advance read ptrs in reverse direction + read_ptr1_ = (read_ptr1_ - 1 + max_size) % max_size; + read_ptr2_ = (read_ptr2_ - 1 + max_size) % max_size; + + if (headDiff_ > (delay1_ - fadetime - 1)) //start cross fade region + { + if(!fading_) + { + fading_ = true; //start fading + + if(!playinghead_) + { + //jump ptr2 to fadetime beyond write position + read_ptr2_ = write_ptr_ - 1; + } + + else + { + //jump ptr1 to fadetime beyond write position + read_ptr1_ = write_ptr_ - 1; + } + } + + else + { + //continue fading + } + } + + if(fading_) + { + if(!playinghead_) //head1 playing + { + fadepos_ = fadepos_ + (1.0f / (fadetime) ); //increment up to 1. + if (fadepos_ > 1.0f) + { + fadepos_ = 1.0f; + fading_ = false; //stop fading + playinghead_ = true; + } + } + + else //head2 playing + { + fadepos_ = fadepos_ - (1.0f / (fadetime) ); //increment down to 0 + if (fadepos_ < 0.0f) + { + fadepos_ = 0.0f; + fading_ = false; //stop fading + playinghead_ = false; + } + } + } + else //not fading + { + + } + + } + + /** returns the next sample of type T in the delay line, interpolated if necessary. + */ + inline const T ReadRev() const + { + T a1 = line_[read_ptr1_]; + T a2 = line_[(read_ptr2_)]; + + float read1 = a1; + float read2 = a2; + + float scalar_1, scalar_2; + + //hann + //scalar_1 = sinf(fadepos_ * ((float)M_PI * 0.5f)); + //scalar_2 = sinf((1.0f - fadepos_) * ((float)M_PI * 0.5f)); + + //flattenned hann + scalar_1 = 2.0f*( (9.0f/16.0f)*sinf(float(M_PI)*(1.0f/2.0f)*fadepos_) + (1.0f/16.0f)*sinf(float(M_PI)*(3.0f/2.0f)*fadepos_) ); + scalar_2 = 2.0f*( (9.0f/16.0f)*sinf(float(M_PI)*(1.0f/2.0f)*(1.0f - fadepos_)) + (1.0f/16.0f)*sinf(float(M_PI)*(3.0f/2.0f)*(1.0f - fadepos_)) ); + + return (read2 * scalar_1) + (read1 * scalar_2); + } + + /** returns the next sample of type T in the delay line, interpolated if necessary. + */ + inline const T ReadFwd() const //read forward as feedback signal + { + T a = line_[(write_ptr_ + delay1_) % max_size]; + T b = line_[(write_ptr_ + delay1_ + 1) % max_size]; + return a + (b - a) * frac1_; + } + + private: + float frac1_; + size_t write_ptr_; + size_t read_ptr1_; + size_t read_ptr2_; + size_t delay1_; + size_t headDiff_; + T line_[max_size]; + size_t fadetime; + bool playinghead_; + float fadepos_; + bool fading_; + +}; +} // namespace daisysp +#endif \ No newline at end of file diff --git a/terrarrium/DoubleDelay/main.1.0.3.cpp b/terrarrium/DoubleDelay/main.1.0.3.cpp new file mode 100644 index 000000000..d338759b9 --- /dev/null +++ b/terrarrium/DoubleDelay/main.1.0.3.cpp @@ -0,0 +1,131 @@ +// Dual Delay Pedal - Version 1.0.3 + + + +#include "daisysp.h" +#include "daisy_petal.h" +#include "terrarium.h" + +using namespace daisy; +using namespace daisysp; +using namespace terrarium; + +DaisyPetal petal; +Parameter param_feedback, param_mix; +Led led1, led2; + +Tone lp_filter; + +float samplerate; +bool effect_enabled = true; + +constexpr size_t MAX_DELAY = 48000 * 5; // 5 seconds max delay + +DSY_SDRAM_BSS DelayLine delayBuffer; + +struct DelayUnit { + DelayLine* buffer; + float currentDelay = 0.5f; + float delayTarget = 0.5f; + float feedback = 0.5f; + Tone lp_filter; + + void Init(float samplerate, DelayLine& buf) + { + buffer = &buf; + buffer->Init(); + lp_filter.Init(samplerate); + lp_filter.SetFreq(4000.0f); + } + + float Process(float in, float delay_samples) + { + fonepole(currentDelay, delayTarget, 0.0002f); + buffer->SetDelay(currentDelay); + + float delayed = buffer->Read(); + float input = in + delayed * feedback; + float filtered = lp_filter.Process(input); + filtered = fminf(fmaxf(filtered, -1.0f), 1.0f); + buffer->Write(filtered); + + return delayed; + } +}; + +DelayUnit delay; + +void InitParams() +{ + param_feedback.Init(petal.knob[Terrarium::KNOB_2], 0.0f, 1.0f, Parameter::LINEAR); + param_mix.Init(petal.knob[Terrarium::KNOB_3], 0.0f, 1.0f, Parameter::LINEAR); +} + +void InitLeds() +{ + led1.Init(petal.seed.GetPin(Terrarium::LED_1), false); + led2.Init(petal.seed.GetPin(Terrarium::LED_2), false); +} + +void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) +{ + float knob = petal.knob[Terrarium::KNOB_1].Process(); + float delay_time = knob < 0.5f + ? fmap(knob * 2.0f, 0.01f, 1.0f) + : fmap((knob - 0.5f) * 2.0f, 1.0f, 5.0f); + + float mix = param_mix.Process(); + float delay_samples = delay_time * samplerate; + + delay.delayTarget = delay_samples; + delay.feedback = param_feedback.Process(); + + for (size_t i = 0; i < size; i++) + { + float dry = in[0][i]; + + float delayed = delay.Process(dry, delay_samples); + + float wet = delayed * 0.3f; + float mixed = (1.0f - mix) * dry + mix * wet; + + float output = effect_enabled ? mixed : dry; + out[0][i] = out[1][i] = output; + } +} + +int main(void) +{ + petal.Init(); + lp_filter.Init(petal.AudioSampleRate()); + lp_filter.SetFreq(4000.0f); + samplerate = petal.AudioSampleRate(); + + InitParams(); + InitLeds(); + + delay.Init(samplerate, delayBuffer); + petal.StartAdc(); + petal.StartAudio(AudioCallback); + + while (1) + { + petal.ProcessAnalogControls(); + petal.ProcessDigitalControls(); + + if (petal.switches[Terrarium::FOOTSWITCH_1].RisingEdge()) + { + effect_enabled = !effect_enabled; + } + + // LED1 indicates if effect is enabled + led1.Set(effect_enabled ? 1.0f : 0.0f); + led1.Update(); + + // LED2 shows mix level from KNOB_3 + led2.Set(param_mix.Process()); + led2.Update(); + + System::Delay(10); + } +} diff --git a/terrarrium/DoubleDelay/main.cpp b/terrarrium/DoubleDelay/main.cpp new file mode 100644 index 000000000..638cab303 --- /dev/null +++ b/terrarrium/DoubleDelay/main.cpp @@ -0,0 +1,131 @@ +// Version 1.1.0 + +#include "daisy_petal.h" +#include "daisysp.h" +#include "terrarium.h" +#include "DelayUnit.h" + +using namespace daisy; +using namespace daisysp; +using namespace terrarium; + +DaisyPetal petal; + +DSY_SDRAM_BSS DelayLine delayBuffer1; +DSY_SDRAM_BSS DelayLine delayBuffer2; + +DelayUnit delay1; +DelayUnit delay2; + +Parameter param_feedback_1, param_feedback_2; +Parameter param_mix_1, param_mix_2; +Parameter param_time_1, param_time_2; + +bool bypassDelay1 = false; +bool bypassDelay2 = false; + +Led led1, led2; + +float samplerate; +uint32_t last_flash = 0; +bool flash_state = false; + +Tone hpf; + + +void InitControls() +{ + param_time_1.Init(petal.knob[Terrarium::KNOB_1], 0.01f, 5.0f, Parameter::EXPONENTIAL); + param_feedback_1.Init(petal.knob[Terrarium::KNOB_2], 0.0f, 1.0f, Parameter::LINEAR); + param_mix_1.Init(petal.knob[Terrarium::KNOB_3], 0.0f, 1.0f, Parameter::LINEAR); + + param_time_2.Init(petal.knob[Terrarium::KNOB_4], 0.01f, 5.0f, Parameter::EXPONENTIAL); + param_feedback_2.Init(petal.knob[Terrarium::KNOB_5], 0.0f, 1.0f, Parameter::LINEAR); + param_mix_2.Init(petal.knob[Terrarium::KNOB_6], 0.0f, 1.0f, Parameter::LINEAR); + + led1.Init(petal.seed.GetPin(terrarium::Terrarium::LED_1), false); + led2.Init(petal.seed.GetPin(terrarium::Terrarium::LED_2), false); +} + +void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) +{ + float delay_time_1 = param_time_1.Process(); + float feedback_1 = param_feedback_1.Process(); + float mix_1 = param_mix_1.Process(); + + float ratio_knob = param_time_2.Process(); + float delay_time_2 = delay2.SnapToRatio(delay_time_1, ratio_knob); + float feedback_2 = param_feedback_2.Process(); + float mix_2 = param_mix_2.Process(); + + + delay1.delayTarget = delay_time_1 * samplerate; + delay1.feedback = feedback_1; + + delay2.delayTarget = delay_time_2 * samplerate; + delay2.feedback = feedback_2; + + for (size_t i = 0; i < size; i++) + { + float dry = in[0][i]; + float delayed1 = delay1.Process(dry, delay_time_1 * samplerate); + float delayed2 = delay2.Process(dry, delay_time_2 * samplerate); + + float mixed1 = (1.0f - mix_1) * dry + mix_1 * delayed1; + float mixed2 = (1.0f - mix_2) * dry + mix_2 * delayed2; + + float output = dry; + if (!bypassDelay1) + output = mixed1; + if (!bypassDelay2) + output = (output + mixed2) * 0.5f; + + out[0][i] = out[1][i] = output; + } + + // LED Flash Timing based on Delay 1 Time + uint32_t now = System::GetNow(); + if (!bypassDelay1 && (now - last_flash) >= static_cast(delay_time_1 * 1000)) + { + flash_state = !flash_state; + led1.Set(flash_state ? 1.0f : 0.0f); + last_flash = now; + } + else if (bypassDelay1) + { + led1.Set(0.0f); + } + + led1.Update(); + led2.Set(!bypassDelay2 ? 1.0f : 0.0f); + led2.Update(); +} + +int main(void) +{ + petal.Init(); + samplerate = petal.AudioSampleRate(); + hpf.Init(samplerate); + hpf.SetFreq(80.0f); // Cut off sub-bass + + delay1.Init(samplerate, delayBuffer1); + delay2.Init(samplerate, delayBuffer2); + InitControls(); + + petal.StartAdc(); + petal.StartAudio(AudioCallback); + + while (1) + { + petal.ProcessAnalogControls(); + petal.ProcessDigitalControls(); + + if (petal.switches[Terrarium::FOOTSWITCH_1].RisingEdge()) + bypassDelay1 = !bypassDelay1; + + if (petal.switches[Terrarium::FOOTSWITCH_2].RisingEdge()) + bypassDelay2 = !bypassDelay2; + + System::Delay(10); + } +} diff --git a/terrarrium/DoubleDelay/notes/controls.cpp b/terrarrium/DoubleDelay/notes/controls.cpp new file mode 100644 index 000000000..b5e020621 --- /dev/null +++ b/terrarrium/DoubleDelay/notes/controls.cpp @@ -0,0 +1,56 @@ +#include "daisysp.h" +#include "daisy_petal.h" +#include "terrarium.h" + +using namespace daisy; +using namespace daisysp; +using namespace terrarium; + +DaisyPetal petal; + +Parameter param; + +Led led1, led2; + +void InitParam() +{ // Initialize param + // Curves: LINEAR, EXPONENTIAL, LOGARITHMIC, CUBE + param.Init(petal.knob[Terrarium::KNOB_1], 0.0, 1.0, Parameter::LINEAR); // swell is knob 6 +} + +void InitLeds(void) +{ + // Initialize the leds - these are using LED objects + led1.Init(petal.seed.GetPin(Terrarium::LED_1),false); + led2.Init(petal.seed.GetPin(Terrarium::LED_2),false); +} + +int main(void) +{ + petal.Init(); + + InitParam(); + InitLeds(); + + petal.StartAdc(); + + bool ledOn = false; + + while (1) + { + petal.ProcessAnalogControls(); + petal.ProcessDigitalControls(); + + float val = param.Process(); // swell_val = powf(swellParam.Process(),1/1.5); + led1.Set(val > 0.5f); + led1.Update(); + + if(petal.switches[Terrarium::FOOTSWITCH_2].RisingEdge()) { + ledOn = !ledOn; + } + + led2.Set(ledOn); + led2.Update(); + } +} + diff --git a/terrarrium/DoubleDelay/notes/delay_module.h b/terrarrium/DoubleDelay/notes/delay_module.h new file mode 100644 index 000000000..dc69dd820 --- /dev/null +++ b/terrarrium/DoubleDelay/notes/delay_module.h @@ -0,0 +1,166 @@ +#pragma once +#ifndef DELAY_MODULE_H +#define DELAY_MODULE_H + +#include "Delays/delayline_reverse.h" +#include "Delays/delayline_revoct.h" +#include "base_effect_module.h" +#include "daisysp.h" +#include +#ifdef __cplusplus + +/** @file _delay_module.h */ + +using namespace daisysp; + +// Delay Max Definitions (Assumes 48kHz samplerate) +constexpr size_t MAX_DELAY_NORM = + static_cast(48000.0f * 8.f); // 4 second max delay // Increased the max to 8 seconds, got horrible pop noise when set to 4 + // seconds, increasing buffer size fixes it for some reason. TODO figure out why? +constexpr size_t MAX_DELAY_REV = + static_cast(48000.0f * 8.f); // 8 second max delay (needs to be double for reverse, since read/write pointers are going + // opposite directions in the buffer) +constexpr size_t MAX_DELAY_SPREAD = static_cast(4800.0f); // 50 ms for Spread effect + +// This is the core delay struct, which actually includes two delays, +// one for forwared/octave, and one for reverse. This is required +// because the reverse delayline needs to be double the size of the +// normal delayline to have the same range of the Time control. Both +// delays are processed, with the main delay feeding into the reverse +// delay to allow for Reverse Octave. A lowpass filter is included in +// the feedback loop, which can tame the harsh high frequencies of the +// octave delay, or create a "fading into the distance" effect for the +// forward and reverse delays. A "level" param is included for modulation +// of the output volume, for stereo panning. +struct delayRevOct { + DelayLineRevOct *del; + DelayLineReverse *delreverse; + float currentDelay; + float delayTarget; + float feedback = 0.0; + float active = false; + bool reverseMode = false; + Tone toneOctLP; // Low Pass + float level = 1.0; // Level multiplier of output, added for stereo modulation + float level_reverse = 1.0; // Level multiplier of output, added for stereo modulation + bool dual_delay = false; + bool secondTapOn = false; + + float Process(float in) { + // set delay times + fonepole(currentDelay, delayTarget, .0002f); + del->SetDelay(currentDelay); + delreverse->SetDelay1(currentDelay); + + float del_read = del->Read(); + + float read_reverse = delreverse->ReadRev(); // REVERSE + + float read = + toneOctLP.Process(del_read); // LP filter, tames harsh high frequencies on octave, has fading effect for normal/reverse + + float secondTap = 0.0; + if (secondTapOn) { + secondTap = del->ReadSecondTap(); + } + // float read2 = delreverse->ReadFwd(); + if (active) { + del->Write((feedback * read) + in); + delreverse->Write((feedback * read) + + in); // Writing the read from fwd/oct delay line allows for combining oct and rev for reverse octave! + // delreverse->Write((feedback * read2) + in); + } else { + del->Write(feedback * read); // if not active, don't write any new sound to buffer + delreverse->Write(feedback * read); + // delreverse->Write((feedback * read2)); + } + + // TODO Figure out how to do dotted eighth with reverse + + if (dual_delay) { + return read_reverse * level_reverse * 0.5 + + (read + secondTap) * level * 0.5; // Half the volume to keep total level consistent + } else if (reverseMode) { + return read_reverse * level_reverse; + } else { + return (read + secondTap) * level; + } + } +}; + +// For stereo spread setting (delay the right channel signal from 0 to 50ms) +// A short, zero feedback (one repeat) delay for stereo spread + +struct delay_spread { + DelayLine *del; + float currentDelay; + float delayTarget; + float active = false; + + float Process(float in) { + // set delay times + fonepole(currentDelay, delayTarget, .0002f); + del->SetDelay(currentDelay); + + float read = del->Read(); + if (active) { + del->Write(in); + } + + return read; + } +}; + +namespace bkshepherd { + +class DelayModule : public BaseEffectModule { + public: + DelayModule(); + ~DelayModule(); + + void Init(float sample_rate) override; + void UpdateLEDRate(); + void CalculateDelayMix(); + void ParameterChanged(int parameter_id) override; + void ProcessModulation(); + void ProcessMono(float in) override; + void ProcessStereo(float inL, float inR) override; + void SetTempo(uint32_t bpm) override; + float GetBrightnessForLED(int led_id) const override; + + private: + float m_delaylpFreqMin; + float m_delaylpFreqMax; + float m_delaySamplesMin; + float m_delaySamplesMax; + float m_delaySpreadMin; + float m_delaySpreadMax; + float m_pdelRight_out; + float m_currentMod; + + Oscillator modOsc; + float m_modOscFreqMin; + float m_modOscFreqMax; + + // Delays + delayRevOct delayLeft; + delayRevOct delayRight; + delay_spread delaySpread; + + // Mix params + float delayWetMix = 0.5; + float delayDryMix = 0.5; + float WetMix = 0.5; + float DryMix = 0.5; + + float _level = 1.0; + + float effect_samplerate; + + // Oscillator for blinking tempo LED + Oscillator led_osc; + float m_LEDValue; +}; +} // namespace bkshepherd +#endif +#endif \ No newline at end of file diff --git a/terrarrium/DoubleDelay/notes/delayline.cpp b/terrarrium/DoubleDelay/notes/delayline.cpp new file mode 100644 index 000000000..2462ba5c5 --- /dev/null +++ b/terrarrium/DoubleDelay/notes/delayline.cpp @@ -0,0 +1,125 @@ +#include "daisysp.h" +#include "daisy_seed.h" + +// Interleaved audio definitions +#define LEFT (i) +#define RIGHT (i + 1) + +// Set delay time range in samples. +#define MIN_DELAY 24 +#define MAX_DELAY 63962 + +using namespace daisysp; +using namespace daisy; + +// Set up Daisy hardware seed +static DaisySeed hw; + +// Declare the button +Switch button1; + +// Declare DelayLines of MAX_DELAY number of floats. +static DelayLine(MAX_DELAY)> delL; +static DelayLine(MAX_DELAY)> delR; + +// Setup.. +const int num_adc_channels = 3; +const int blockSize = 1; + +// Scale the feedback signal +const float feedbackScalar=1.0; + + +// +// Audio Callback function (the audio loop) +// +static void AudioCallback(AudioHandle::InterleavingInputBuffer in, + AudioHandle::InterleavingOutputBuffer out, + size_t size) +{ + float feedbackL, feedbackR, delL_out, delR_out, sigL_out, sigR_out, del; + + for(size_t i = 0; i < size; i += 2) + { + + float knob = hw.adc.GetFloat(0); + float knob2 = hw.adc.GetFloat(1); + float knob3 = hw.adc.GetFloat(2); + + knob *= knob; + del = ((MAX_DELAY - MIN_DELAY) * knob)+(MIN_DELAY); + delL.SetDelay(del); + delR.SetDelay(del); + + button1.Debounce(); + float btn = button1.Pressed(); + + // Read from delay line + delL_out = delL.Read(); + delR_out = delR.Read(); + + // Calculate output mix + if(btn){ + if (knob3 <= .5){ + sigL_out = ((in[LEFT]) + (delL_out * (knob3*2.0f))); + sigR_out = ((in[RIGHT]) + (delR_out * (knob3*2.0f))); + }else{ + sigL_out = (((in[LEFT])*(1-((knob3-0.5f)*2.0f)))) + (delL_out); + sigR_out = (((in[RIGHT])*(1-((knob3-0.5f)*2.0f)))) + (delR_out); + } + }else{ + sigL_out = in[LEFT]; + sigR_out = in[RIGHT]; + } + + // Calculate feedback + feedbackL = (delL_out * knob2 * feedbackScalar) + in[LEFT]; + feedbackR = (delR_out * knob2 * feedbackScalar) + in[RIGHT]; + + // Write to the delay + delL.Write(feedbackL * btn); + delR.Write(feedbackR * btn); + + // Output + out[LEFT] = sigL_out; + out[RIGHT] = sigR_out; + } +} + +int main(void) +{ + // initialize seed hardware and daisysp modules + float sample_rate; + hw.Configure(); + hw.Init(); + hw.SetAudioBlockSize(blockSize); + sample_rate = hw.AudioSampleRate(); + delL.Init(); + delR.Init(); + + //This is our ADC configuration + AdcChannelConfig adcConfig[num_adc_channels]; + + //Configure pin 21 as an ADC input. This is where we'll read the knob. + adcConfig[0].InitSingle(hw.GetPin(21)); + adcConfig[1].InitSingle(hw.GetPin(22)); + adcConfig[2].InitSingle(hw.GetPin(23)); + + //Initialize the adc with the config we just made + hw.adc.Init(adcConfig, num_adc_channels); + + //Initialize the button + button1.Init(hw.GetPin(28),1000); + + // Set Default Delay time + delL.SetDelay(sample_rate * 0.75f); + delR.SetDelay(sample_rate * 0.75f); + + // Start reading ADC values + hw.adc.Start(); + + // Start audio loop + hw.StartAudio(AudioCallback); + + while(1) {} +} \ No newline at end of file diff --git a/terrarrium/DoubleDelay/notes/main-v1.cpp b/terrarrium/DoubleDelay/notes/main-v1.cpp new file mode 100644 index 000000000..00e7e6b1f --- /dev/null +++ b/terrarrium/DoubleDelay/notes/main-v1.cpp @@ -0,0 +1,91 @@ +// Dual Delay Pedal - Version 1.0.3 +#include "daisysp.h" +#include "daisy_petal.h" +#include "terrarium.h" +#include "DelayUnit.h" + +using namespace daisy; +using namespace daisysp; +using namespace terrarium; + +DaisyPetal petal; +Parameter param_feedback, param_mix; +Led led1, led2; +DelayUnit delay; + +Tone lp_filter; + +float samplerate; +bool effect_enabled = true; + +DSY_SDRAM_BSS DelayLine delayBuffer; + + +void InitParams() +{ + param_feedback.Init(petal.knob[Terrarium::KNOB_2], 0.0f, 1.0f, Parameter::LINEAR); + param_mix.Init(petal.knob[Terrarium::KNOB_3], 0.0f, 1.0f, Parameter::LINEAR); +} + +void InitLeds() +{ + led1.Init(petal.seed.GetPin(Terrarium::LED_1), false); + led2.Init(petal.seed.GetPin(Terrarium::LED_2), false); +} + +void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) +{ + float knob = petal.knob[Terrarium::KNOB_1].Process(); + float delay_time = knob < 0.5f + ? fmap(knob * 2.0f, 0.01f, 1.0f) + : fmap((knob - 0.5f) * 2.0f, 1.0f, 5.0f); + float mix = param_mix.Process(); + + float delay_samples = delay_time * samplerate; + delay.delayTarget = delay_samples; + delay.feedback = param_feedback.Process(); + + for (size_t i = 0; i < size; i++) + { + float dry = in[0][i]; + float delayed = delay.Process(dry, delay_samples); + + float wet = delayed * 0.3f; + float mixed = (1.0f - mix) * dry + mix * wet; + + float output = effect_enabled ? mixed : dry; + out[0][i] = out[1][i] = output; + } +} + +int main(void) +{ + petal.Init(); + lp_filter.Init(petal.AudioSampleRate()); + lp_filter.SetFreq(4000.0f); + samplerate = petal.AudioSampleRate(); + + InitParams(); + InitLeds(); + + delay.Init(samplerate, delayBuffer); + petal.StartAdc(); + petal.StartAudio(AudioCallback); + + while (1) + { + petal.ProcessAnalogControls(); + petal.ProcessDigitalControls(); + + if (petal.switches[Terrarium::FOOTSWITCH_1].RisingEdge()) + { + effect_enabled = !effect_enabled; + } + + // LED1 indicates if effect is enabled + led1.Set(effect_enabled ? 1.0f : 0.0f); + led1.Update(); + + System::Delay(10); + } +} diff --git a/terrarrium/DoubleDelay/terrarium.h b/terrarrium/DoubleDelay/terrarium.h new file mode 100644 index 000000000..9a5d27fab --- /dev/null +++ b/terrarrium/DoubleDelay/terrarium.h @@ -0,0 +1,96 @@ +// PedalPCB Terrarium Header +// Copyright (C) 2020 PedalPCB.com +// http://www.pedalpcb.com + +namespace terrarium +{ + class Terrarium + { + public: + enum Sw + { + FOOTSWITCH_1 = 4, + FOOTSWITCH_2 = 5, + SWITCH_1 = 2, + SWITCH_2 = 1, + SWITCH_3 = 0, + SWITCH_4 = 6 + }; + + enum Knob + { + KNOB_1 = 0, + KNOB_2 = 2, + KNOB_3 = 4, + KNOB_4 = 1, + KNOB_5 = 3, + KNOB_6 = 5 + }; + enum LED + { + LED_1 = 22, + LED_2 = 23 + }; + }; +} + + + + +// #include "daisy_seed.h" +// #include "daisysp.h" + +// using namespace daisy; +// using namespace daisysp; + +// class Terrarium +// { +// public: +// DaisySeed seed; +// Led led_left, led_right; + +// void Init() +// { +// seed.Configure(); +// seed.Init(); + +// // Initialize LEDs as PWM outputs +// led_left.Init(seed.GetPin(22), false); // GPIO22 = Pin 22 +// led_right.Init(seed.GetPin(23), false); // GPIO23 = Pin 23 + +// // Initialize ADC for knobs (pots) +// AdcChannelConfig adc_cfg[6]; +// // for (int i = 0; i < 6; i++) +// for (int i = 1; i < 22; i++) +// { +// adc_cfg[i].InitSingle(seed.GetPin(15 + i)); // Pins 15–20 for KNOB_1 to KNOB_6 +// } +// seed.adc.Init(adc_cfg, 6); +// seed.adc.Start(); +// } + +// float GetKnob(int index) +// { +// return seed.adc.GetFloat(index); // 0.0 – 1.0 +// } + +// void SetLedLeftBrightness(float value) +// { +// led_left.Set(value); +// led_left.Update(); +// } + +// void SetLedRightBrightness(float value) +// { +// led_right.Set(value); +// led_right.Update(); +// } + +// void ToggleLeds() +// { +// static bool toggle_state = false; +// toggle_state = !toggle_state; +// SetLedLeftBrightness(toggle_state ? 1.0f : 0.0f); +// SetLedRightBrightness(toggle_state ? 1.0f : 0.0f); +// } +// }; diff --git a/terrarrium/ParametricDistortion/Makefile b/terrarrium/ParametricDistortion/Makefile new file mode 100644 index 000000000..d6263a08a --- /dev/null +++ b/terrarrium/ParametricDistortion/Makefile @@ -0,0 +1,30 @@ +# Project Name +TARGET = parametric_distortion + +# Use LGPL version of DaisySP (optional, set to 1 if needed) +USE_DAISYSP_LGPL = 1 + +# Library Locations +LIBDAISY_DIR = ../../libDaisy +DAISYSP_DIR = ../../DaisySP + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# Source files +CPP_SOURCES = main.cpp + +# Include the Daisy system Makefile (does all the hard work) +include $(SYSTEM_FILES_DIR)/Makefile + +# Define the compiled program binary +PROGRAM = parametric_distortion.bin +FLASH_ADDR = 0x08000000 + +# Convert ELF to BIN +build/$(PROGRAM): build/$(TARGET).elf + arm-none-eabi-objcopy -O binary build/$(TARGET).elf build/$(PROGRAM) + +# Flash to Daisy Seed using ST-LINK V3 Mini +program: build/$(PROGRAM) + st-flash --reset write build/$(PROGRAM) $(FLASH_ADDR) diff --git a/terrarrium/ParametricDistortion/main.cpp b/terrarrium/ParametricDistortion/main.cpp new file mode 100644 index 000000000..5761c7bd8 --- /dev/null +++ b/terrarrium/ParametricDistortion/main.cpp @@ -0,0 +1,151 @@ +#include "daisysp.h" +#include "daisy_petal.h" +#include "terrarium.h" +#include + +using namespace daisy; +using namespace daisysp; +using namespace terrarium; + +// Knob 1 = Frequency +// Knob 2 = Gain +// Knob 3 = Q + +DaisyPetal petal; +Parameter knob_1, knob_2, knob_3, knob_4, knob_5, knob_6; +Led led1, led2; + +float sample_rate; + +struct ParametricEQ +{ + float sample_rate = 48000.0f; + float a1, a2, b0, b1, b2; + float x1, x2, y1, y2; + + void SetSampleRate(float sr) { sample_rate = sr; } + + void SetParams(float freq, float q, float gain_db) + { + float A = powf(10.0f, gain_db / 40.0f); + float omega = 2.0f * M_PI * freq / sample_rate; + float alpha = sinf(omega) / (2.0f * q); + float cosw = cosf(omega); + + float a0 = 1.0f + alpha / A; + a1 = -2.0f * cosw; + a2 = 1.0f - alpha / A; + b0 = 1.0f + alpha * A; + b1 = -2.0f * cosw; + b2 = 1.0f - alpha * A; + + // Normalize + b0 /= a0; + b1 /= a0; + b2 /= a0; + a1 /= a0; + a2 /= a0; + } + + float Process(float in) + { + float out = b0 * in + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; + x2 = x1; x1 = in; + y2 = y1; y1 = out; + return out; + } +}; + +ParametricEQ eq; + + + +// Initialize the LEDs +void InitLeds() +{ + led1.Init(petal.seed.GetPin(Terrarium::LED_1), false); + led2.Init(petal.seed.GetPin(Terrarium::LED_2), false); +} + +void InitParams() +{ + // Initialize parameters for Delay1 ----------------------- + knob_1.Init(petal.knob[Terrarium::KNOB_2], 0.0f, 1.0f, Parameter::LINEAR); + knob_2.Init(petal.knob[Terrarium::KNOB_3], 0.0f, 1.0f, Parameter::LINEAR); + knob_3.Init(petal.knob[Terrarium::KNOB_4], 0.0f, 1.0f, Parameter::LINEAR); + knob_4.Init(petal.knob[Terrarium::KNOB_5], 0.0f, 1.0f, Parameter::LINEAR); + knob_5.Init(petal.knob[Terrarium::KNOB_6], 0.0f, 1.0f, Parameter::LINEAR); + knob_6.Init(petal.knob[Terrarium::KNOB_1], 0.0f, 1.0f, Parameter::LINEAR); +} + +// //////////////////////////////////////////////////////////////////// +// Audio callback function + +void AudioCallback(AudioHandle::InputBuffer in, + AudioHandle::OutputBuffer out, + size_t size) +{ + petal.ProcessAnalogControls(); + + float freq_val = petal.knob[Terrarium::KNOB_1].Process(); + float gain_val = petal.knob[Terrarium::KNOB_2].Process(); + float q_val = petal.knob[Terrarium::KNOB_3].Process(); + + const float minGain = -0.0f; + const float maxGain = 24.0f; + + const float minFreq = 80.0f; + const float maxFreq = 3000.0f; + + const float minQ = 0.1f; + const float maxQ = 10.0f; + + float gain = (gain_val * (maxGain - minGain)) + minGain; + float freq = minFreq * powf(maxFreq / minFreq, freq_val); + float q = (q_val * (maxQ - minQ)) + minQ; + + eq.SetParams(freq, q, gain); + // eq.SetParams(1000.0f, 6.0f, 24.0f); // Default values for testing + + for (size_t i = 0; i < size; i++) + { + float input = in[0][i]; + float out_sample = eq.Process(input); + out[0][i] = out_sample; + out[1][i] = out_sample; + } +} + +int main(void) +{ + petal.Init(); + InitLeds(); + petal.StartAdc(); + InitParams(); + + led1.Set(1.0f); // Turn off LED1 + led2.Set(1.0f); // Turn off LED2 + led1.Update(); // Apply to LED pin + led2.Update(); // Apply to LED pin + + sample_rate = petal.AudioSampleRate(); + + eq.SetSampleRate(sample_rate); + eq.SetParams(1000.0f, 1.0f, 0.0f); // Default + + petal.StartAudio(AudioCallback); + + while (1) { + // petal.ProcessAnalogControls(); + + // float k1 = petal.knob[Terrarium::KNOB_1].Process(); + // led1.Set(k1 > 0.5f); // LED1 on if knob1 > 50% + // led1.Update(); + + // float k2 = petal.knob[Terrarium::KNOB_2].Process(); + // led2.Set(k2 < 0.5f); // LED2 on if knob2 > 50% + // led2.Update(); + + // petal.DelayMs(10); // allow time for hardware to respond + } +} diff --git a/terrarrium/ParametricDistortion/readme.md b/terrarrium/ParametricDistortion/readme.md new file mode 100644 index 000000000..77b88837e --- /dev/null +++ b/terrarrium/ParametricDistortion/readme.md @@ -0,0 +1,4 @@ +# Parametric EQ for Terrarium + +cd ../../libDaisy && make clean && make +cd ../../DaisySP && make clean && make diff --git a/terrarrium/ParametricDistortion/terrarium.h b/terrarrium/ParametricDistortion/terrarium.h new file mode 100644 index 000000000..dc6e3df0b --- /dev/null +++ b/terrarrium/ParametricDistortion/terrarium.h @@ -0,0 +1,96 @@ +// PedalPCB Terrarium Header +// Copyright (C) 2020 PedalPCB.com +// http://www.pedalpcb.com + +namespace terrarium +{ + class Terrarium + { + public: + enum Sw + { + FOOTSWITCH_1 = 4, + FOOTSWITCH_2 = 5, + SWITCH_1 = 2, + SWITCH_2 = 1, + SWITCH_3 = 0, + SWITCH_4 = 6 + }; + + enum Knob + { + KNOB_1 = 0, + KNOB_2 = 2, + KNOB_3 = 4, + KNOB_4 = 1, + KNOB_5 = 3, + KNOB_6 = 5 + }; + enum LED + { + LED_1 = 22, + LED_2 = 23 + }; + }; +} + + + + +// #include "daisy_seed.h" +// #include "daisysp.h" + +// using namespace daisy; +// using namespace daisysp; + +// class Terrarium +// { +// public: +// DaisySeed seed; +// Led led_left, led_right; + +// void Init() +// { +// seed.Configure(); +// seed.Init(); + +// // Initialize LEDs as PWM outputs +// led_left.Init(seed.GetPin(22), false); // GPIO22 = Pin 22 +// led_right.Init(seed.GetPin(23), false); // GPIO23 = Pin 23 + +// // Initialize ADC for knobs (pots) +// AdcChannelConfig adc_cfg[6]; +// // for (int i = 0; i < 6; i++) +// for (int i = 1; i < 22; i++) +// { +// adc_cfg[i].InitSingle(seed.GetPin(15 + i)); // Pins 15–20 for KNOB_1 to KNOB_6 +// } +// seed.adc.Init(adc_cfg, 6); +// seed.adc.Start(); +// } + +// float GetKnob(int index) +// { +// return seed.adc.GetFloat(index); // 0.0 – 1.0 +// } + +// void SetLedLeftBrightness(float value) +// { +// led_left.Set(value); +// led_left.Update(); +// } + +// void SetLedRightBrightness(float value) +// { +// led_right.Set(value); +// led_right.Update(); +// } + +// void ToggleLeds() +// { +// static bool toggle_state = false; +// toggle_state = !toggle_state; +// SetLedLeftBrightness(toggle_state ? 1.0f : 0.0f); +// SetLedRightBrightness(toggle_state ? 1.0f : 0.0f); +// } +// };