Select Git revision
daisy-looper.ino
daisy-looper.ino 11.04 KiB
#include "DaisyDuino.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <MIDI.h>
#include "leds.h"
#include "potentiometers.h"
#include "buttons.h"
#include "looper.h"
#include "env_follower.h"
#include "helpers.h"
#include "luts.h"
#include "ui.h"
MIDI_CREATE_DEFAULT_INSTANCE();
#define BUFFER_LENGTH_SECONDS 5
#define DEBUGMODE
static const size_t buffer_length = 48000 * BUFFER_LENGTH_SECONDS;
static float DSY_SDRAM_BSS buffer[buffer_length];
static float DSY_SDRAM_BSS buffer_b[buffer_length];
static float DSY_SDRAM_BSS buffer_c[buffer_length];
static float DSY_SDRAM_BSS buffer_d[buffer_length];
static float DSY_SDRAM_BSS buffer_e[buffer_length];
// Create instances of audio stuff
atoav::Looper looper_a, looper_b, looper_c, looper_d, looper_e;
static atoav::EnvelopeFollower input_envelope_follower;
DelayLine<float, 24000> delayline;
DSY_SDRAM_BSS ReverbSc reverb;
static Compressor compressor;
Oscillator lfo;
static SampleHold sample_and_hold;
static WhiteNoise noise;
static Metro tick;
static Easer easer;
// Initialize Buttons
Button button_1 = Button(D7);
Button button_2 = Button(D8);
Button button_3 = Button(D9);
Button button_4 = Button(D10);
Button button_5 = Button(D13);
Button button_6 = Button(D27);
// Initialize Potentiometers
Potentiometer pot_1 = Potentiometer(A0);
Potentiometer pot_2 = Potentiometer(A1);
Potentiometer pot_3 = Potentiometer(A3);
Potentiometer pot_4 = Potentiometer(A2);
Potentiometer pot_5 = Potentiometer(A4);
Potentiometer pot_6 = Potentiometer(A5);
Potentiometer pot_7 = Potentiometer(A6);
// RGB LED R G B
RGBLed rgb_led = RGBLed(A10, A11, A11);
// OLED Display
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // QT-PY / XIAO
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// User Interface
Ui ui;
// Daisy
DaisyHardware hw;
// Variables for the Audio-Callback
size_t num_channels;
float blocksize;
float drywetmix = 0.0f;
float delaymix = 0.0f;
float delaytime = 100.0f;
float reverbmix = 0.0f;
float lfo_amount = 0.0f;
float pitch_val = 0.5f;
float midi_pitch_offset = 0.0f;
float pressure = 0.0f;
// Actual audio-processing is orchestrated here
void AudioCallback(float **in, float **out, size_t size) {
float output = 0.0f;
float no_delay = 0.0f;
float wet_delay;
float out1, out2;
// set the delay
delayline.SetDelay(delaytime);
// Iterate through the samples in the buffer
for (size_t i = 0; i < size; i++) {
uint8_t trig = tick.Process();
float lfo_value = lfo.Process();
float noise_value = noise.Process();
float rand = easer.Process(
sample_and_hold.Process(
trig,
noise_value * 5.0f,
sample_and_hold.MODE_SAMPLE_HOLD
));
if (ui.rec_source == REC_SOURCE_NOISE) {
ui.activeLooper()->Record(noise_value * 0.7f);
}
// When the metro ticks, trigger the envelope to start.
float random_amount = (lfo_amount -0.5f) * 2.0;
if (trig) {
// tick.SetFreq(rand / (0.1 + random_amount*1000.0f) + 1);
// If the dial is over 50% jump instead
if (lfo_amount > 0.5f) {
ui.activeLooper()->addToPlayhead(rand * random_amount* 48000.0f);
ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset);
}
}
// Add the LFO to the
if (lfo_amount > 0.5f) {
ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset);
} else {
ui.activeLooper()->setPlaybackSpeed(pitch_val + lfo_value * lfo_amount + midi_pitch_offset);
}
float looper_out;
// res.SetDamping(0.1+delaymix*0.2f);
// Record into the buffer
if (ui.rec_source == REC_SOURCE_PRE) {
ui.activeLooper()->Record(in[1][i]);
}
// Process the input envelope
input_envelope_follower.Process(in[1][i]);
// Process the Looper
switch (ui.buffer_summing_mode) {
case BUFFER_SUM_MODE_SOLO:
// Only play active looper
looper_out = saturate(ui.activeLooper()->Process());
break;
case BUFFER_SUM_MODE_SUM:
// Sum all loopers
looper_out = looper_a.Process();
looper_out += looper_b.Process();
looper_out += looper_c.Process();
looper_out += looper_d.Process();
looper_out += looper_e.Process();
looper_out = saturate(looper_out);
break;
case BUFFER_SUM_MODE_RING:
// Sum all loopers and ringmodulate with input
looper_out = looper_a.Process();
looper_out += looper_b.Process();
looper_out += looper_c.Process();
looper_out += looper_d.Process();
looper_out += looper_e.Process();
float deadbanded_input;
if (in[1][i] < 0.0f) {
deadbanded_input = min(0.0f, in[1][i] + 0.05f);
} else {
deadbanded_input = max(0.0f, in[1][i] - 0.05f);
}
looper_out *= deadbanded_input*2.0f;
looper_out = saturate(looper_out);
break;
}
// looper_out = pressure * looper_out;
// Mix the dry/Wet of the looper
output = drywetmix * looper_out + in[1][i] * (1.0f - drywetmix);
// output = output * (1.0f - resmix) + res.Process(output*resmix);
no_delay = output;
wet_delay = delayline.Read();
delayline.Write((wet_delay * delaymix * 0.95f)/2.0f + (no_delay*delaymix)/2.0f);
// Add the delay
output += wet_delay * delaymix;
// Compress the signal
compressor.Process(output);
// Process reverb
reverb.Process(output, output, &out1, &out2);
// Mix reverb with the dry signal depending on the amount dialed
output = output * (1.0f - reverbmix) + out1 * reverbmix;
// Record the output if needed
if (ui.rec_source == REC_SOURCE_OUT) {
ui.activeLooper()->Record(output);
}
out[0][i] = out[1][i] = output;
}
}
// TODO: Add Voice Stealing/Multiple Playheads?
// TODO: Is USB Midi possible? https://github.com/electro-smith/DaisyExamples/blob/master/seed/USB_MIDI/USB_MIDI.cpp
void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) {
#ifdef DEBUGMODE
Serial.print("[MIDI ON] chn<");
Serial.print((int) inChannel);
Serial.print("> note<");
Serial.print((int) inNote);
Serial.print("> velocity<");
Serial.print((int) inVelocity);
Serial.println(">");
#endif
// Note Off can come in as Note On w/ 0 Velocity
if (inVelocity == 0.0f) {
midi_pitch_offset = 0.0f;
}
else {
midi_pitch_offset = (int(inNote)-36.0)/12.0f;
}
}
void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) {
#ifdef DEBUGMODE
Serial.print("[MIDI OFF] chn<");
Serial.print((int) inChannel);
Serial.print("> note<");
Serial.print((int) inNote);
Serial.print("> velocity<");
Serial.print((int) inVelocity);
Serial.println(">");
#endif
midi_pitch_offset = 0.0f;
}
void handleAftertouch(byte channel, byte channel_pressure) {
#ifdef DEBUGMODE
Serial.print("[MIDI AFTER] chn<");
Serial.print((int) channel);
Serial.print("> pressure<");
Serial.print((int) channel_pressure);
Serial.println(">");
#endif
pressure = float(channel_pressure)/127.0f;
}
void setup() {
float sample_rate;
// Initialize for Daisy pod at 48kHz
hw = DAISY.init(DAISY_SEED, AUDIO_SR_48K);
DAISY.SetAudioBlockSize(64);
num_channels = hw.num_channels;
sample_rate = DAISY.get_samplerate();
blocksize = 64.0f;
// Create a Tick and a noise source for the Sample and Hold
tick.Init(10, sample_rate);
noise.Init();
// Initialize Looper with the buffer
looper_a.Init(buffer, buffer_length);
looper_b.Init(buffer_b, buffer_length);
looper_c.Init(buffer_c, buffer_length);
looper_d.Init(buffer_d, buffer_length);
looper_e.Init(buffer_e, buffer_length);
// Initialize Envelope Follower for the Level LED
input_envelope_follower.Init(sample_rate);
input_envelope_follower.SetAttack(100.0);
input_envelope_follower.SetDecay(1000.0);
// Initialize Reverb
reverb.Init(sample_rate);
reverb.SetFeedback(0.95f);
reverb.SetLpFreq(18000.0f);
// Initialize Compressor
compressor.SetThreshold(-64.0f);
compressor.SetRatio(2.0f);
compressor.SetAttack(0.005f);
compressor.SetRelease(0.1250);
// Initialize the LFO for modulations
lfo.Init(sample_rate);
lfo.SetWaveform(Oscillator::WAVE_TRI);
lfo.SetAmp(1);
lfo.SetFreq(8.0);
// Initialize the Delay at a length of 1 second
delayline.Init();
delayline.SetDelay(sample_rate);
// Easer for the random jumps
easer.setFactor(0.001);
// Start serial communications
Serial.begin(250000);
// Initialize Display
display.begin(0x3C, true);
delay(50);
ui.Render();
// Initialize the LED
rgb_led.init();
// Set the analog read and write resolution to 12 bits
analogReadResolution(12);
// Setup MIDI handlers
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.setHandleAfterTouchChannel(handleAftertouch);
MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages
// Set Knob names and display functions
pot_1.name = "Start";
pot_2.name = "Length";
pot_3.setDisplayMode("Pitch", 1000.0f, POT_DISPLAY_MODE_PERCENT);
pot_4.setDisplayMode("Mix", 100.0f, POT_DISPLAY_MODE_PERCENT);
pot_5.setDisplayMode("LFO", 100.0f, POT_DISPLAY_MODE_PERCENT);
pot_6.setDisplayMode("Delay", 100.0f, POT_DISPLAY_MODE_PERCENT);
pot_7.setDisplayMode("Reverb", 100.0f, POT_DISPLAY_MODE_PERCENT);
// Set Knob Scaling Modes
// pot_3.setBipolar();
pot_3.setPitch();
// Initialize Buttons (callbacks are assigned in the Ui class)
button_1.init();
button_2.init();
button_3.init();
button_4.init();
button_5.init();
button_6.init();
// Start the audio Callback
DAISY.begin(AudioCallback);
}
void loop() {
// Read the values from the potentiometers
float p1 = pot_1.read();
float p2 = pot_2.read();
float p3 = pot_3.read();
float p4 = pot_4.read();
float p5 = pot_5.read();
float p6 = pot_6.read();
float p7 = pot_7.read();
// Update the UI
ui.update();
// Read the buttons
button_1.read();
button_2.read();
button_3.read();
button_4.read();
button_5.read();
button_6.read();
// Set loop-start and loop-length with the potentiometers
ui.setLoop(p1, p2);
// Tune the pitch to the ten-fold of the potentiometer 3,
// a bipolar pot, so it returns values from -1.0 to +1.0
// Pitch should go 10 octaves up/down (10*1.0 = 1000%)
if (!isnan(p3)) {pitch_val = 10.0f * p3; }
// Set other parameters (from 0.0 to 1.0)
if (!isnan(p4)) { drywetmix = p4; }
if (!isnan(p5)) { delaymix = p5; }
// Delaytime is in samples
if (!isnan(p5)) { lfo_amount = p5; }
if (!isnan(p6)) { delaytime = 100.0f + p6 * 23900.0f; }
if (!isnan(p7)) { reverbmix = p7; Serial.print("Reverb was not NaN: "); Serial.println(reverbmix); }
// Render the UI (frame rate limited by UI_MAX_FPS in ui.h)
// double start = millis();
ui.Render();
// Serial.print("ui Render took ");
// Serial.print(millis()-start);
// Serial.println("ms");
// Set the Color and brightness of the RGB LED in 8 bits
rgb_led.setAudioLevelIndicator(input_envelope_follower.getValue());
// MIDI
MIDI.read();
}