Use metronome in Mozzi

Hey ya'll,

I'm trying to program a drumsynth with a euclidean synthesizer. I'm using an OLED and four potis. While those work fine I'm having a hard time wrapping my head around the mozzi metronome. I don't get how start(), ready() and stop() are connected. It seems really easy: the time given to start() is over -> ready() gets called. There is no documentation about stop(), so i don't get what this one does.

I'm trying to have a little indicator showing me the active step. From this point i hope i'll be able to add sounds to the whole thing. Here is the complete code (yeah, i know there's loads of things to optimize :slight_smile: )

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <Metronome.h>

#define OLED_RESET 4
Adafruit_SH1106 display(OLED_RESET);

#if (SH1106_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SH1106.h!");
#endif

#define CONTROL_RATE 128

Metronome metro(1000);

int leftBorder = 0;
int rightBorder = display.width();
int upperBorder = 0;
int lowerBorder = display.height();
int vertLine = 81;
int thick = 2;
int chHeight = 9;
int counterHeight = 4;
int fullHeight = thick + chHeight + counterHeight + 1;
int fillSize = 4;

int bpm = 120;
int speed = 500;
int len = 1;
int step = 1;
int shift = 1;

int bpmOld = bpm;
int lenOld = len;
int stepOld = step;
int shiftOld = shift;

int i = 0;
int j = 0;
int k = 0;
int beat[8];
int counter = 0;

void setup() {
  startMozzi(CONTROL_RATE);
  adcDisconnectAllDigitalIns();
  Serial.begin(9600);
  Serial.println("Starting Setup...");
  pinMode(18, OUTPUT);
  pinMode(19, OUTPUT);
  pinMode(PCINT16, INPUT);
  pinMode(PCINT17, INPUT);
  pinMode(PCINT18, INPUT);
  pinMode(PCINT19, INPUT);
  display.begin(SH1106_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.fillScreen(1);
  fillChan();
  display.fillRect(vertLine + 10, 1, rightBorder - vertLine - 11, lowerBorder - 2, 0);
  display.drawChar(vertLine + 12, 5, 0x0D, 1, 0, 1);
  display.drawChar(vertLine + 12, 5 + fullHeight, 0x53, 1, 0, 1);
  display.drawChar(vertLine + 12, 5 + fullHeight * 2, 0x46, 1, 0, 1);
  display.drawChar(vertLine + 12, 5 + fullHeight * 3, 0x52, 1, 0, 1);
  display.setTextColor(1);
  display.setTextSize(1);
  display.setTextWrap(false);
  display.display();
  metro.set(speed);
  Serial.println("Setup done!");
}

void updateControl() {
  calc();
  metro.start(speed);
  if (bpmOld != bpm) {
    display.setCursor(vertLine + 20, 5);
    display.fillRect(vertLine + 18, 5, 20, 7, 0);
    display.print(bpm);
    display.display();
  }
  if (lenOld != len) {
    display.setCursor(vertLine + 20, 21);
    display.fillRect(vertLine + 18, 21, 20, 7, 0);
    display.print(len);
  }
  if (stepOld != step) {
    display.setCursor(vertLine + 20, 37);
    display.fillRect(vertLine + 18, 37, 20, 7, 0);
    display.print(step);
  }
  if (shiftOld != shift) {
    display.setCursor(vertLine + 20, 53);
    display.fillRect(vertLine + 18, 53, 20, 7, 0);
    display.print(shift);
  }

  int beatP[len];
  if (lenOld != len || stepOld != step || shiftOld != shift) {
    Serial.println("Beatchange");
    fillBeat(beatP, len, step, shift);
    fillChan();
    for (int i = 0; i < len; i++) {
      int beatPos = beatP[(len - shift + i) % len];
      display.fillRect(10 + i * (fillSize + 1), 2, fillSize, 7, 0);
      if (beatPos == 1) {
        display.fillRect(10 + i * (fillSize + 1), 2, fillSize, 7, 1);
        display.display();
      } else {
        display.drawLine(10 + i * (fillSize + 1), 5, 13 + i * (fillSize + 1), 5, 1);
        display.display();
      }
    }
  }
  if (metro.ready()) {
    display.drawRect(11 + counter * 5, chHeight + 3, 2, 2, 0);
    counter++;
    Serial.println(counter);
    display.drawRect(11 + counter * 5, chHeight + 3, 2, 2, 1);
    display.display();
    if (counter == len) { counter = 0; }
  }
  //metro.stop();
}

AudioOutput_t updateAudio() {
}


void loop() {
  audioHook();
}

void calc() {
  bpmOld = bpm;
  lenOld = len;
  stepOld = step;
  shiftOld = shift;
  bpm = mozziAnalogRead(PCINT16) / 4.4 + 33;
  speed = (60000 / bpm);
  len = mozziAnalogRead(PCINT17) / 64 + 1;
  step = ((mozziAnalogRead(PCINT18) / 64) * len / 16) + 1;
  shift = (mozziAnalogRead(PCINT19) / 64) * len / 16;
  // Serial.print(mozziAnalogRead(PCINT16));
  // Serial.print(" - ");
  // Serial.println(bpm);
  // Serial.print(mozziAnalogRead(PCINT17));
  // Serial.print(" - ");
  // Serial.println(len);
  // Serial.print(mozziAnalogRead(PCINT18));
  // Serial.print(" - ");
  // Serial.println(step);
  // Serial.print(mozziAnalogRead(PCINT19));
  // Serial.print(" - ");
  // Serial.println(shift);
  return speed, len, step, shift;
}

int fillBeat(int beat[], int len, int step, int shift) {
  k = (-1 * step) % len;
  for (int i = 0; i < len; i++) {
    k = j;
    j = (i * step) % len;
    if (j > k) beat[i] = 0;
    else if (j <= k) beat[i] = 1;
  }
}

void fillChan() {
  for (i = 0; i < 4; i++) {
    //Channel Rectangle
    display.fillRect(1, 1 + i * (fullHeight), 7, 14, 0);
    //Sequencer Rectangle
    display.fillRect(9, 1 + i * (fullHeight), vertLine, chHeight, 0);
    //Counter Rectangle
    display.fillRect(9, 1 + chHeight + 1 + i * (fullHeight), vertLine, counterHeight, 0);
    //Channel Number
    display.drawChar(2, 5 + 16 * i, 0x31 + i, 1, 0, 1);
  }
}

Mozzis updateControl() is supposed to check if pots are turned. If they are, the corresponding part of the display is updated with the new value. After that, the new beat is calculated and projected in the corresponding channel. Only channel 1 is working for now, because i wanted to finish one before replicating everything for the other ones. But this part works fine for now. What i'm talking about is the following part:

  calc();
  metro.start(speed);
  //code for display
  if (metro.ready()) {
    display.drawRect(11 + counter * 5, chHeight + 3, 2, 2, 0);
    counter++;
    Serial.println(counter);
    display.drawRect(11 + counter * 5, chHeight + 3, 2, 2, 1);
    display.display();
    if (counter == len) { counter = 0; }
  }

So this is supposed to happen here: calc(), among other things, is calculating the speed (in ms) from the potis position. This value is used in metro.start() for the "note length". Now I was expecting the program to go into the if condition as soon as metro hits the note length, delete the existing indicator (i know there is no first one), add 1 to counter, draw the indicator for the next step and continue to to that until counter hits length (the lenght of the beat, also calculated in calc()), then start from the first step. But for some reason the if conditional is only running once.

Any ideas why?

edit: btw, you need to rename mozzis twi_nonblock.cpp to make it work with the OLED library. Took me much too long to find that out.

First, for readers unfamiliar with Euclidean Rhythms, see

Online Euclidean Rhythm Demo

Is fillBeat() the function that calculates the patterns? I'm under the umbrella on a tiny window, so...

I'm not where I can properly examine your sketch. There seem to be many references to functions &c. that must be part of metronome or mozzi.

I have to ask if you feel like you know all about how the code we aren't seeing is supposed to be used and how it works.

It looks like you are relying on the looping nature of loop to execute that if statement more than once. Which is not wrong, necessarily.

Can you just write a smaller sketch to figure out the logic, to learn if you need to what and how mozzi and metro work?

Are there fora for those pieces of software? You may get lucky, there's lotsa experts here, more than one might be able to spot your error right away.

Others may take the time to learn up on the external stuff you use.

If you could do some good old fashioned high quality hand waving and describle how the various parts are meant to communicate with each other to accomplish your task it would be very helpful.

a7

Yes, fillBeat() constructs a pattern of 0's and 1's that is passed to an array beat[], which is called to fill the display with 3x7 squares and 3p lines to visualize the pattern. The shift argument is rotating the pattern, because rotation is what makes euclidean rhythms sound cool :slight_smile:

Are you talking about the mozzi library? I more or less understand the architecture of mozzi, but i'm still learning.

Thats right. I started with the formular for euclidean rhythm generator and some code to display it on a 16x2 I2C display and integrated mozzi after that. So i had to think about where to put the working code inside the mozzi-infrastructure. I'm not sure there is a better place than updateControl, because all this needs to be updated constantly but isn't providing anything soundwise.

There is a mozzi google group, but it seems more or less dead. A lot of old questions were never answered.

But there is good news: I threw the metronome away and used the approach from here.

This is the updated code:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <Metronome.h>

#define OLED_RESET 4
Adafruit_SH1106 display(OLED_RESET);

#if (SH1106_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SH1106.h!");
#endif

#define CONTROL_RATE 128

Metronome metro(1000);

int leftBorder = 0;
int rightBorder = display.width();
int upperBorder = 0;
int lowerBorder = display.height();
int vertLine = 81;
int thick = 2;
int chHeight = 9;
int counterHeight = 4;
int fullHeight = thick + chHeight + counterHeight + 1;
int fillSize = 4;

int bpm = 120;
int speed = 500;
int len = 1;
int step = 1;
int shift = 1;

int bpmOld = bpm;
int lenOld = len;
int stepOld = step;
int shiftOld = shift;

int i = 0;
int j = 0;
int k = 0;
int beat[8];
int counter = 0;
unsigned long nexttick;


void setup() {
  startMozzi(CONTROL_RATE);
  adcDisconnectAllDigitalIns();
  Serial.begin(9600);
  Serial.println("Starting Setup...");
  pinMode(18, OUTPUT);
  pinMode(19, OUTPUT);
  pinMode(PCINT16, INPUT);
  pinMode(PCINT17, INPUT);
  pinMode(PCINT18, INPUT);
  pinMode(PCINT19, INPUT);
  display.begin(SH1106_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.fillScreen(1);
  fillChan();
  display.fillRect(vertLine + 10, 1, rightBorder - vertLine - 11, lowerBorder - 2, 0);
  display.drawChar(vertLine + 12, 5, 0x0D, 1, 0, 1);
  display.drawChar(vertLine + 12, 5 + fullHeight, 0x53, 1, 0, 1);
  display.drawChar(vertLine + 12, 5 + fullHeight * 2, 0x46, 1, 0, 1);
  display.drawChar(vertLine + 12, 5 + fullHeight * 3, 0x52, 1, 0, 1);
  display.setTextColor(1);
  display.setTextSize(1);
  display.setTextWrap(false);
  display.display();
  metro.start(speed);
  Serial.println("Setup done!");
}

void updateControl() {
  calc();
  if (bpmOld != bpm) {
    display.setCursor(vertLine + 20, 5);
    display.fillRect(vertLine + 18, 5, 20, 7, 0);
    display.print(bpm);
    display.display();
  }
  if (lenOld != len) {
    display.setCursor(vertLine + 20, 21);
    display.fillRect(vertLine + 18, 21, 20, 7, 0);
    display.print(len);
    display.display();
  }
  if (stepOld != step) {
    display.setCursor(vertLine + 20, 37);
    display.fillRect(vertLine + 18, 37, 20, 7, 0);
    display.print(step);
    display.display();
  }
  if (shiftOld != shift) {
    display.setCursor(vertLine + 20, 53);
    display.fillRect(vertLine + 18, 53, 20, 7, 0);
    display.print(shift);
    display.display();
  }
  int beatP[len];
  if (lenOld != len || stepOld != step || shiftOld != shift) {
    counter = 0;
    fillBeat(beatP, len, step, shift);
    fillChan();
    for (int i = 0; i < len; i++) {
      int beatPos = beatP[(len - shift + i) % len];
      display.fillRect(10 + i * (fillSize + 1), 2, fillSize, 7, 0);
      if (beatPos == 1) {
        display.fillRect(10 + i * (fillSize + 1), 2, fillSize, 7, 1);
        //display.display();
      } else {
        display.drawLine(10 + i * (fillSize + 1), 5, 13 + i * (fillSize + 1), 5, 1);
      }
      display.display();
    }
  }

  unsigned long timenow = millis();
  Serial.print(timenow);
  Serial.print(" - ");
  Serial.println(nexttick);
  if (timenow >= nexttick) {
    nexttick = millis() + speed;
    display.drawRect(6 + counter * 5, chHeight + 3, 2, 2, 0);
    counter++;
    Serial.println(counter);
    display.drawRect(6 + counter * 5, chHeight + 3, 2, 2, 1);
    display.display();
  }
  if (counter == len) counter = 0;
}


AudioOutput_t updateAudio() {
}


void loop() {
  audioHook();
}

void calc() {
  bpmOld = bpm;
  lenOld = len;
  stepOld = step;
  shiftOld = shift;
  bpm = mozziAnalogRead(PCINT16) / 4.4 + 33;
  speed = (60000 / bpm);
  len = mozziAnalogRead(PCINT17) / 64 + 1;
  step = ((mozziAnalogRead(PCINT18) / 64) * len / 16) + 1;
  shift = (mozziAnalogRead(PCINT19) / 64) * len / 16;
  return speed, len, step, shift;
}

int fillBeat(int beat[], int len, int step, int shift) {
  k = (-1 * step) % len;
  for (int i = 0; i < len; i++) {
    k = j;
    j = (i * step) % len;
    if (j > k) beat[i] = 0;
    else if (j <= k) beat[i] = 1;
  }
}

void fillChan() {
  for (i = 0; i < 4; i++) {
    //Channel Rectangle
    display.fillRect(1, 1 + i * (fullHeight), 7, 14, 0);
    //Sequencer Rectangle
    display.fillRect(9, 1 + i * (fullHeight), vertLine, chHeight, 0);
    //Counter Rectangle
    display.fillRect(9, 1 + chHeight + 1 + i * (fullHeight), vertLine, counterHeight, 0);
    //Channel Number
    display.drawChar(2, 5 + 16 * i, 0x31 + i, 1, 0, 1);
  }
}

I'm making baby steps towards my goal. Now i have to think about how do delete the last remaining step indicator when it loops back to the start.

Please when you update, especially if you have jettisoned a pesky distraction, go ahead and post the entire sketch that compiles and works (or still doesn't, so say or remind us why).

Haha! The Umbrella Academy group had placed a bet on that. It looks not like the Euclidean generators I've seen or copied/made myself.

Not in the code I see, it doesn't. :wink:

Also, i, j and k are srsly bad variable names, especially in the case of global variables.

None of those needs to be global for use in fillBeat(), and i in fillBeat isn't the same as the global i anyway.

Declare i, j and k as local variables in fillBeat() and at least name them different tomake them a bit easier to search and destroy. I use ii, jj and kk or something like that, which at least means you can find ii without looking at every i in the entire sketch.

Did the change you made fix the original problem? Is there still a problem or a new one?

a7

OK, THX. I see some elves came by and placed all the new code.

In general it is better to just make new posts on this thread to correct things like that. In some cases edits make real trash of the following posts, so beyond typos and stuff just go ahead and use a new post. There is plenty of room (somehwhere) for al that test. :expressionless:

a7

Done.

I don't get the Umbrella Academy part, but i went by this Video and it seems to work.

Not in fillBeat(), but i rotate it later when i construct the squares to be displayed. I'm open to suggestions how to implement the shift in the function, but it seemed easier to me to "rotate" by calculating the correct pointer to the array than to rotate the array.

Will do. I still consider myself a beginner and cleaned up code is definetly not my strength.

  1. Yes 2. A whole list, but i'll figure it out. As a next step i want to build a bassdrum (in a seperate sketch) and then see if I can copy it over to trigger it with the patterns.

THX.

I see you rotate it on the fly as parameters change. That could be applied in the fillBeat() function. TBH the names beat and beatP make it hard to see whether the way you currently do it is better than doing it in fillBeat().

I suggest you look in to variable scope and the use of function parameters. It's hard to read code that needlessly passes global variables to functions that use the same names locally. It can be hard to read and understand (and modify or fix) code that uses lotsa global variables.

The shift you now pass to fillBeat() is ignored, I dropped it to test the function. Here's your rotator maths put down in the function. I left the variables as is, just in case their global nature is at all exploited:

int fillBeat(int beat[], int len, int step, int shift) {
	k = (-1 * step) % len;
	for (int i = 0; i < len; i++) {
		k = j;
		j = (i * step) % len;

int shiftedIndex  =  (len - shift + i) % len;

		if (j > k) beat[shiftedIndex] = 0;    // store it shifted 
		else if (j <= k) beat[shiftedIndex] = 1;
	}
}

I'll toss out the thing I know you aren't thinking of doing:

    if (j > k) beat[shiftedIndex] = 0;    // store it shifted 
    else if (j <= k) beat[shiftedIndex] = 1;

can be written using plain logic, viz:

    beat[shiftedIndex] = j <= k;

since j <= k is either true (1) or false (0);


The pattern you found to cut metronome out of the fun it was having with you works fine, but a functionally equivalent pattern is preferable. The demo linked and posted below shows both patterns working to do something periodically, the second pattern is more common.

I beseech you to, at some point, figure out exactly how either (or both!) patterns are functioning. Mastery of this relatively simple concept is, and I do not exaggerate, key to wringing as much as you can out of the UNO and its ilk. The second pattern is preferred, you may want to know why but can just trust me on this for the nonce. :wink:

Also see there my adjustments to fillBeat(), the real reason I went into the simulator: to check out the generator of the sequences. Which look correct, still haven't stepped myself through the algorithm used.


Wokwi_badge BLINK WITHOUT DEMO

// https://forum.arduino.cc/t/use-metronome-in-mozzi/1111957
// https://wokwi.com/projects/361300417746421761

int myBeats[64];

void setup() {
  Serial.begin(115200);
  Serial.println("Euclidean World!");
  Serial.println();

  int theLength = 13;
  int theStep = 4;

  fillBeat(myBeats, theLength, theStep);
  for (int ii = 0; ii < theLength; ii++)
    Serial.print(myBeats[ii]); Serial.print(" ");

  Serial.println();
  Serial.println();
  delay(777);
}

unsigned long nexttick;
unsigned int speed0 = 777;
unsigned int speed1 = 1300;

void loop() {

// blink without delay pattern 0:

  unsigned long timenow = millis();
 
  if (timenow >= nexttick) {
    nexttick = millis() + speed0;
    
    Serial.println("ZERO");
  }

// blink without delay pattern 1:

  static unsigned long lastTime; 
  unsigned long now = millis();
  
  if (now - lastTime >= speed1) {
    lastTime = now;

  	//  do stuff periodically (every speed0 milliseconds)
    Serial.println("          ONE");
  }
}

int fillBeat(int beat[], int len, int step) {
  int ii, jj, kk;
  kk = (-1 * step) % len;
  for (ii = 0; ii < len; ii++) {
    kk = jj;
    jj = (ii * step) % len;
        beat[ii] = jj <= kk;
//    if (jj > kk) beat[ii] = 0;
//    else if (jj <= kk) beat[ii] = 1;
  }
}

I'll put a plug in here for the wokwi simulator. Free, very faithful, lotsa parts. It is a very convenient way to focus on software, as all the hardware is perfect. The pushbuttons even bounce.

HTH

a7

1 Like

Wow, thank you!

I'll be gone for some days and will look into that on Tuesday. I can already say that everything makes sense to me :slight_smile:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.