Building a small Arpeggiator (almost there...)

54 views
Skip to first unread message

Daniele Di Paolo

unread,
Jun 20, 2024, 4:09:11 AMJun 20
to Mozzi-users
Hello everyone, 

I'm new to this community and I thank you all in advance for sharing help and knowledge about this exciting topic! :)

I have a basic knowledge of Arduino IDE and I'm attempting to make a little synth arpeggiator that has the following features:

- 7 buttons for 7 notes, importantly when multiple buttons are pressed the notes are arpeggiated
- 5 pots for rate, legato, pitch, phase filter, and cutoff

I managed to write the code for arpeggiating the pressed notes. Then I wanted to implement that with the pots controls as explained in a project found on here. So I combined the two (see code below)

Now, my code almost works, unfortunately the arpeggio doesn't work though and multiple buttons pressed result in notes playing unison. Also, the rate and legato pot don't work.

I'd really appreciate any kind of suggestion, thanks! 

#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <EventDelay.h>
#include <mozzi_midi.h>
#include <LowPassFilter.h>

#define CONTROL_RATE 128 // powers of 2 please

// Create two oscillators
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin1(SIN2048_DATA);
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin2(SIN2048_DATA);
LowPassFilter lpf;

// for sequencing gain
EventDelay kGainChangeDelay;
char gain = 1;

const int buttonPins[] = {2, 3, 4, 5, 6, 7, 8}; // Button pins for notes C4 to B4
const int numButtons = 7;
const int notes[] = {261, 293, 329, 349, 392, 440, 493}; // Frequencies for C4 to B4

int pressedNotes[7]; // Array to store indices of pressed notes
int numPressedNotes = 0; // Number of pressed notes
int currentNoteIndex = 0; // Index of the current note being played
unsigned long lastUpdateTime = 0; // Last time the note was updated

const int OSC_ONE_PIN = A2; // pitch
const int OSC_TWO_PIN = A3; // phase
const int MODA_PIN = A0; // rate
const int MODB_PIN = A1; // legato
const int MODC_PIN = A4; // filter

// division constants
// We're doing some common division operations ahead of time so that we can avoid division in the main loops
// which will slow things down when Mozzi is trying to do its thing. Doing the division here allows us to use
// multiplication in the main loop which is much faster
const float DIV1023 = 1.0 / 1023.0; // division constant for pot value range

// global control params
int OSC_ONE_OFFSET = 36; // amount to offset the original midi note (12 is one octave)
int OSC_TWO_OFFSET = 2; // amount to offset the second midi note from the osc one's midi note (5 is a fifth, two is a second, etc)
int ARP_RATE_MIN = 32; // minimum arpeggiator rate (in millisecs)
int ARP_RATE_MAX = 1024; // maximum arpeggiator rate (in millisecs)
int LEGATO_MIN = 32; // minimum note length (capping at 32 to avoid rollover artifacts)
int LEGATO_MAX = 1024; // maximum note length
int LPF_CUTOFF_MIN = 10; // low pass filter min cutoff frequency
int LPF_CUTOFF_MAX = 245; // low pass filter max cutoff frequency

void setup() {
for (int i = 0; i < numButtons; i++) {
pinMode(buttonPins[i], INPUT_PULLUP); // Set button pins as input with internal pull-up resistors
//IS IT BETTER A PHYSICAL RESISTOR??
}
// Initialize Mozzi objects
startMozzi(CONTROL_RATE);
aSin1.setFreq(48);
aSin2.setFreq(51);
lpf.setResonance(128u);
kGainChangeDelay.set(1000);
// Serial.begin(9600); // Serial output can cause audio artifacting so this is commented out by default. Can be uncommented for debugging purposes.
}

void updateControl() {
// Read pot values
int oscOne_val = mozziAnalogRead(OSC_ONE_PIN);
int oscTwo_val = mozziAnalogRead(OSC_TWO_PIN);
int modA_val = mozziAnalogRead(MODA_PIN);
int modB_val = mozziAnalogRead(MODB_PIN);
int modC_val = mozziAnalogRead(MODC_PIN);

// Map pot values
float oscOne_offset = OSC_ONE_OFFSET * (oscOne_val * DIV1023 - 0.5); // Offset by semitones
float oscTwo_offset = (oscTwo_val * DIV1023) * OSC_TWO_OFFSET; // Detune by semitones
float modA_freq = ARP_RATE_MIN + (ARP_RATE_MAX - ARP_RATE_MIN) * (1 - (modA_val * DIV1023)); // Arpeggiator rate from 32 milliseconds to ~= 1 second
float modB_duration = LEGATO_MIN + (LEGATO_MAX - LEGATO_MIN) * (1 - (modB_val * DIV1023)); // Legato from 32 milliseconds to 1 second
int modC_freq = LPF_CUTOFF_MIN + (LPF_CUTOFF_MAX - LPF_CUTOFF_MIN) * (modC_val * DIV1023); // Low-pass filter cutoff freq ~=100Hz-8kHz

// Set low pass filter cutoff frequency
lpf.setCutoffFreq(modC_freq);

// Check which buttons are pressed
numPressedNotes = 0;
for (int i = 0; i < numButtons; i++) {
if (digitalRead(buttonPins[i]) == LOW) { // Button pressed (active low)
pressedNotes[numPressedNotes] = notes[i]; // Store the note frequency
numPressedNotes++;
}
}

// Using an EventDelay to cycle through the sequence and play each note
kGainChangeDelay.set(modA_freq); // Set the delay frequency

if (numPressedNotes > 0 && kGainChangeDelay.ready()) {
unsigned long currentTime = mozziMicros();
if (currentTime - lastUpdateTime >= modA_freq) {
lastUpdateTime = currentTime;

// Set the frequency of the oscillators based on the current note
float currentFreq = pressedNotes[currentNoteIndex] * pow(2.0f, oscOne_offset / 12.0f);
aSin1.setFreq(static_cast<float>(currentFreq));
aSin2.setFreq(static_cast<float>(currentFreq * pow(2.0f, oscTwo_offset / 12.0f)));

currentNoteIndex = (currentNoteIndex + 1) % numPressedNotes;
}
} else {
// Stop the oscillators if no notes are pressed
aSin1.setFreq(0);
aSin2.setFreq(0);
}
}



int updateAudio() {
// Calculating the output audio signal as follows:
// 1. Summing the waveforms from the two oscillators
// 2. Shifting their bits by 2 to keep them in a valid output range
// 3. Multiplying the combined waveform by the gain (volume)
// 4. Passing the signal through the low pass filter
return lpf.next(((aSin1.next() + aSin2.next()) >> 2) * gain);
}

void loop() {
audioHook();
}


Михаил Шакаев

unread,
Jun 20, 2024, 4:59:25 AMJun 20
to Mozzi-users
Hello! 
I don't have the components to replicate the circuit, so I can only theorize, and I apologize for that. Faced with such a problem, I would first try to hardcode the variable modA_freq by assigning it a deliberately correct value. This would help localize the error, determining whether the error occurs in the calculation of modA_freq or in the updateControl() function. Additionally, I would use Serial.println() to print to the console the values of key variables used to calculate modA_freq, as well as currentTime and lastUpdateTime. This will help check if they take correct values during the program execution. Hopefully, this will help identify and fix the cause of the error. Good luck!  
четверг, 20 июня 2024 г. в 11:09:11 UTC+3, Daniele Di Paolo:

tomco...@live.fr

unread,
Jun 25, 2024, 9:53:57 AMJun 25
to Mozzi-users
Hi,
Did not try to replicate but here a few tips to help you debug this!
 - I do not understand how unisson can be produced with only two oscillators. Of course, you will have an unisson between aSin1 and aSin2. To check that the arpegiator part works already, I would start with one oscillator only.
 - there is a lot of code to compute the frequencies of the oscillators which can be very very simplified by dealing with midi notes. As a matter of fact, float should be avoided as much as possible. Mozzi is fitted with a set of functions that let you transform a midi note number into a frequency (mtof, mtof_Q16n16). Then portamento can be easily done by smoothing, or doing a line between the midi notes. Transposing can be done by simply adding values to the notes (+12 for an octave, +7 for a fifth…). This will make the code simpler but also faster, which is always a good thing. If you are on Mozzi2, here is a good example: https://github.com/sensorium/Mozzi/tree/master/examples/01.Basics/Vibrato_Midi_Note but the same principle can be done with Mozzi1, a few examples are showing it.

- you never seem to start you delay anywhere. If done correctly, you should not have to keep track of time yourself as you are doing with mozziMicros(), see https://github.com/sensorium/Mozzi/blob/master/examples/02.Control/EventDelay/EventDelay.ino

Hope this helps!

Mozzi-users

unread,
Jun 25, 2024, 10:33:27 AMJun 25
to Mozzi-users
> portamento can be easily done by ...
Mozzi has a Portamento object, the example is 05.Control_Filters>MIDI_portamento...  might be worth a look!

Daniele Di Paolo

unread,
Jun 25, 2024, 11:49:14 AMJun 25
to Mozzi-users
Thanks, I didn't know about "portamento" I will look into that! 
Reply all
Reply to author
Forward
Message has been deleted
Message has been deleted
0 new messages