Johan_Ha
Published

Charillino - a mechanical glockenspiel

A microcontroller board plays melodies on a glockenspiel using two servo controlled mallets.

286

Things used in this project

Hardware components

A set of glockenspiel keys
I've salvaged a 25 key school glockenspiel. You can also go pro and create the keys yourself out of aluminium bars or stainless steel.
×1
Glockenspiel mallet
Buy a pair or design a 3D print.
×2
Various woodwork material
An 80 by 40 cm wooden board, 4 mm cardboard, nails, whatever you prefer for covering the electronics in a box. Hot glue or epoxy glue.
×1
Seeed Studio XIAO SAMD21 (Pre-Soldered) - Seeeduino XIAO
Seeed Studio XIAO SAMD21 (Pre-Soldered) - Seeeduino XIAO
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×4
Breadboard (generic)
Breadboard (generic)
To be replaced with proper wires and soldering, when the final circuit is ready.
×1
Power source
Whatever you prefer as a power source. It needs to power your µC and four servos. You might want two different power sources, if your servos need higher voltage, but your µC can't handle it.
×1
Grove - 16-Channel PWM Driver (PCA9685)
Seeed Studio Grove - 16-Channel PWM Driver (PCA9685)
×1
Jumper wires (generic)
Jumper wires (generic)
To be replaced with proper wires, when the final circuit is ready.
×1

Story

Read more

Custom parts and enclosures

3D printed mallet

The mallet fits to a servo horn at one end. The other end has a hole for a 9 mm nut, if you want weight. And a 5 mm hole for a M5 bolt at the servo end, if you want a counterweight.

Sketchfab still processing.

Schematics

The schematics according to the original design

Code

The main code for the Seeeduino XIAO

C/C++
Create a new project in Arduino IDE, paste this code into the main tab.
#include <PCA9685.h>
#include <Arduino.h>
#include "charillino_tunes.h"

int16_t angles[2][27][3] =
{
  { // Bass 
    // horiz, vertic lower, vertic upper
{ 186, 129, 155 },     // G4
{ 199, 129, 155 },     // G#4
{ 212, 129, 155 },     // A4
{ 225, 129, 155 },     // Bb4
{ 233, 129, 155 },     // B4
{ 246, 129, 153 },     // C5
{ 257, 129, 155 },     // C#5
{ 267, 129, 155 },     // D5
{ 280, 129, 155 },     // D#5
{ 290, 129, 155 },     // E5
{ 304, 129, 155 },     // F5
{ 315, 129, 155 },     // F#5
{ 327, 129, 155 },     // G
{ 339, 129, 155 },     // G#5
{ 350, 129, 155 },     // A
{ 362, 129, 155 },     // Bb5
{ 374, 129, 155 },     // B5
{ 386, 129, 155 },     // C6
{ 398, 129, 155 },     // C#6
{ 410, 129, 155 },     // D6
{ 422, 129, 155 },     // D#6
{ 434, 129, 155 },     // E6
{ 442, 155, 155 },     // F6
{ 442, 155, 155 },     // F#6
{ 442, 155, 155 },     // G6
{ 442, 155, 155 },     // G#6
{ 442, 155, 155 },     // A6
},
  { // Discant
    // horiz, vertic lower, vertic upper
{ 158, 246, 246 },     // G4
{ 158, 263, 246 },     // G#4
{ 170, 263, 246 },     // A4
{ 183, 263, 246 },     // Bb4
{ 195, 263, 246 },     // B4
{ 205, 263, 246 },     // C5
{ 219, 263, 246 },     // C#5
{ 231, 263, 246 },     // D5
{ 242, 263, 246 },     // D#5
{ 256, 262, 246 },     // E5
{ 269, 263, 246 },     // F5
{ 281, 263, 246 },     // F#5
{ 296, 263, 246 },     // G
{ 312, 263, 246 },     // G#5
{ 326, 263, 246 },     // A
{ 337, 263, 246 },     // Bb5
{ 351, 263, 246 },     // B5
{ 366, 263, 246 },     // C6
{ 382, 261, 247 },     // C#6
{ 395, 263, 248 },     // D6
{ 408, 263, 247 },     // D#6
{ 424, 263, 247 },     // E6
{ 440, 262, 247 },     // F6
{ 454, 262, 247 },     // F#6
{ 467, 261, 247 },     // G6
{ 483, 261, 246 },     // G#6
{ 498, 261, 247 },     // A6
}
};

bool blinker = true;

PCA9685 *servobank;

#define my_delay 400      // time between initial horizontal move and vertical strike
#define G3 55




int horiz_servo[2] = { 12, 14 };  // Bass, discant
int vertic_servo[2] = { 13, 15 }; // Bass, discant


// -----------------------------------------------------------------------------
// G4 to G6, midi nr 67 to 91
// -----------------------------------------------------------------------------



void setup()
{
  Serial.begin(115200);
  Wire.begin();
  servobank = new PCA9685(0x40);
  servobank->setPWM(14, angles[0][2][0]);
  servobank->setPWM(12, angles[1][21][0]);
  
  servobank->begin();
  servobank->setFrequency(50);

  
  pinMode(LED_BUILTIN, OUTPUT);
  for (int i = 0; i < 10; i++)
  {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(300);
    digitalWrite(LED_BUILTIN, LOW);
    delay(300);
    
  }
  delay(500);

}

bool blynker = false;

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------

Tunes *tunes[] = {  
                    julklockorna,
                    shchedryk,
                    tonttu,
                    NULL
};
Tunes *tune_now; // Pointer to the tune currently playing. A global!

void search_next(int16_t evt_ptr, int8_t st, int32_t *stamp, int16_t *horiz, int16_t *vertic)
{
  int16_t seeker;
  *stamp = 9999999;
  seeker = evt_ptr;
  while(*stamp == 9999999 && tune_now[seeker].timestamp != 9999999)
  {
    if (tune_now[seeker].stave == st && tune_now[seeker].pitch >= G3 && tune_now[seeker].pitch < G3 + 25)
      {
        *stamp = tune_now[seeker].timestamp - my_delay;
        *horiz = angles[1 - st][tune_now[seeker].pitch - G3][0];
        *vertic = angles[1 - st][tune_now[seeker].pitch - G3][1];
      }
    seeker++;  
  }
}

void loop() {
  int toggler = 0;
  int8_t tune_ptr;
  int16_t event_ptr;
  int32_t discant_stamp, bass_stamp, start_time;
  int16_t bass_horiz, bass_vertic, discant_horiz, discant_vertic; 
  tune_ptr = 0;
  tune_now = tunes[0];

  // looping through all tunes
  do 
  {
    event_ptr = 0;
    discant_stamp = 9999999;
    bass_stamp = 9999999;

    // Search for the first events for each mallet to set the horizontal movement timestamp
    search_next(event_ptr, 1, &bass_stamp, &bass_horiz, &bass_vertic);
    search_next(event_ptr, 0, &discant_stamp, &discant_horiz, &discant_vertic);
    
    // Set the start time of the tune 
    // my_delay makes sure that the first horizontal movement can happen before start time
    start_time = millis() + my_delay;
    
    // looping through one tune 
    while (tune_now[event_ptr].timestamp != 9999999)
    {
      // This WHILE section checks whether something should happen at this 
      // moment of millis(). 
      //
      // bass_stamp holds the time for next horizontal movement preparing the strike of bass mallet
      // discant_stamp ditto for discant mallet
      // tune_now[event_ptr].timestamp holds time for next strike, which might be bass or discant
      // 

      // Check if it's time for bass mallet horizontal
      if ((int32_t)millis() - start_time > bass_stamp)
      {
        servobank->setPWM(horiz_servo[0], bass_horiz);   // move to right bar
        servobank->setPWM(vertic_servo[0], bass_vertic); // move to right height over the bar
        bass_stamp = 9999999;
        digitalWrite(LED_BUILTIN, HIGH);
        // Don't bother to search for next horizontal movement.
        // It will be done after next strike
        //Serial.print("Bass: ");
        //Serial.println(millis());
        
      }

      // Check if it's time to move the discant mallet to next bar
      if ((int32_t)millis() - start_time > discant_stamp)
      {
        servobank->setPWM(horiz_servo[1], discant_horiz);
        servobank->setPWM(vertic_servo[1], discant_vertic);
        discant_stamp = 9999999;
        digitalWrite(LED_BUILTIN, HIGH);
        Serial.print("Discant: ");
        Serial.println(millis());
      }

      // Check if it's time to strike
      if ((int32_t)millis() - start_time > tune_now[event_ptr].timestamp)
      {
        servobank->setPWM(vertic_servo[1 - tune_now[event_ptr].stave], 
                                angles[1 - tune_now[event_ptr].stave][tune_now[event_ptr].pitch - G3][2]);
        digitalWrite(LED_BUILTIN, LOW);
        Serial.print(millis() - start_time);
        Serial.print(", b ");
        Serial.print(tune_now[event_ptr].timestamp);
        Serial.print(", c ");
        Serial.print(tune_now[event_ptr].timestamp);
        Serial.print(", d ");
        Serial.print(tune_now[event_ptr].pitch);
        Serial.print(", e ");
        Serial.print(tune_now[event_ptr].stave);
        Serial.print(", f ");
        Serial.println(millis());
        
        
        // The following peculiar logic goes like:
        //     If we just had a bass mallet strike, search for next bass event starting from next
        //     event in the event list. If not, search next discant event.
        //     And set the stamp etc for the next horizontal movement
        if (tune_now[event_ptr].stave)
        {
          search_next(event_ptr + 1, 1, &bass_stamp, &bass_horiz, &bass_vertic);
        }
        else
        {
          search_next(event_ptr + 1, 0, &discant_stamp, &discant_horiz, &discant_vertic);
        }
        event_ptr++;
      }

    }
    tune_ptr++;
    tune_now = tunes[tune_ptr];
    delay(5000);
  }
  while (tune_now != NULL);
  

}

charillino_tunes.h

C/C++
Defines the Tunes struct used for the note datatype and the tune listings.
Includes all tunes as Tunes struct arrays. This listing has three tunes, named in the main program on lines 120-.
struct Tunes
{
  int8_t pitch;
  int32_t timestamp;
  int8_t stave;

};


Tunes julklockorna[] = 
{
{ 67, 0, 1 },
{ 71, 0, 0 },
{ 72, 600, 0 },
{ 71, 1200, 0 },
{ 66, 1800, 1 },
{ 69, 1800, 0 },
{ 71, 2400, 0 },
{ 69, 3000, 0 },
{ 64, 3600, 1 },
{ 67, 3600, 0 },
{ 66, 4200, 1 },
{ 64, 4800, 1 },
{ 63, 5400, 1 },
{ 66, 5400, 0 },
{ 64, 7200, 1 },
{ 67, 7200, 0 },
{ 69, 7800, 0 },
{ 67, 8400, 0 },
{ 62, 9000, 1 },
{ 66, 9000, 0 },
{ 67, 9600, 0 },
{ 66, 10200, 0 },
{ 60, 10800, 1 },
{ 64, 10800, 0 },
{ 62, 11400, 1 },
{ 60, 12000, 1 },
{ 59, 12600, 1 },
{ 60, 13200, 1 },
{ 59, 13800, 1 },
{ 57, 14400, 1 },
{ 72, 14400, 0 },
{ 74, 15000, 0 },
{ 72, 15600, 0 },
{ 60, 16200, 1 },
{ 71, 16200, 0 },
{ 72, 16800, 0 },
{ 71, 17400, 0 },
{ 66, 18000, 1 },
{ 69, 18000, 0 },
{ 67, 18600, 1 },
{ 66, 19200, 1 },
{ 64, 19800, 1 },
{ 67, 19800, 0 },
{ 66, 20400, 1 },
{ 64, 21000, 1 },
{ 62, 21600, 1 },
{ 66, 21600, 0 },
{ 67, 22200, 0 },
{ 66, 22800, 0 },
{ 60, 23400, 1 },
{ 64, 23400, 0 },
{ 66, 24000, 0 },
{ 64, 24600, 0 },
{ 59, 25200, 1 },
{ 62, 25200, 0 },
{ 60, 25800, 1 },
{ 59, 26400, 1 },
{ 57, 27000, 1 },
{ 66, 27000, 0 },
{ 59, 27600, 1 },
{ 57, 28200, 1 },
{ 55, 28800, 1 },
{ 74, 28800, 0 },
{ 72, 29400, 0 },
{ 71, 30000, 0 },
{ 59, 30600, 1 },
{ 71, 30600, 0 },
{ 60, 31200, 1 },
{ 69, 31200, 0 },
{ 62, 31800, 1 },
{ 67, 31800, 0 },
{ 64, 32400, 1 },
{ 67, 32400, 0 },
{ 66, 33000, 0 },
{ 60, 33600, 1 },
{ 64, 33600, 0 },
{ 60, 34200, 1 },
{ 64, 34200, 0 },
{ 59, 35400, 1 },
{ 57, 36000, 1 },
{ 72, 36000, 0 },
{ 71, 36600, 0 },
{ 69, 37200, 0 },
{ 57, 37800, 1 },
{ 69, 37800, 0 },
{ 59, 38400, 1 },
{ 67, 38400, 0 },
{ 60, 39000, 1 },
{ 66, 39000, 0 },
{ 62, 39600, 1 },
{ 66, 39600, 0 },
{ 64, 40200, 0 },
{ 60, 40800, 1 },
{ 62, 40800, 0 },
{ 60, 41400, 1 },
{ 62, 41400, 0 },
{ 59, 42000, 1 },
{ 57, 42600, 1 },
{ 59, 43200, 1 },
{ 71, 43200, 0 },
{ 72, 43800, 0 },
{ 71, 44400, 0 },
{ 63, 45000, 1 },
{ 69, 45000, 0 },
{ 71, 45600, 0 },
{ 69, 46200, 0 },
{ 64, 46800, 1 },
{ 67, 46800, 0 },
{ 66, 47400, 1 },
{ 64, 48000, 1 },
{ 62, 48600, 1 },
{ 66, 48600, 0 },
{ 64, 49200, 1 },
{ 62, 49800, 1 },
{ 60, 50400, 1 },
{ 64, 50400, 0 },
{ 66, 51000, 0 },
{ 59, 51600, 1 },
{ 67, 51600, 0 },
{ 57, 52200, 1 },
{ 69, 52200, 0 },
{ 67, 52800, 0 },
{ 60, 53400, 1 },
{ 66, 53400, 0 },
{ 59, 54000, 1 },
{ 67, 54000, 0 },


  { 74, 9999999, 0 },
  
};

Tunes shchedryk[] = 
{
{ 70, 0, 0 },
{ 69, 500, 0 },
{ 70, 750, 0 },
{ 67, 1000, 0 },
{ 70, 3000, 0 },
{ 69, 3500, 0 },
{ 70, 3750, 0 },
{ 67, 4000, 0 },
{ 70, 6000, 0 },
{ 69, 6500, 0 },
{ 70, 6750, 0 },
{ 67, 7000, 0 },
{ 70, 7500, 0 },
{ 69, 8000, 0 },
{ 70, 8250, 0 },
{ 67, 8500, 0 },
{ 70, 9000, 0 },
{ 69, 9500, 0 },
{ 70, 9750, 0 },
{ 67, 10000, 0 },
{ 70, 10500, 0 },
{ 69, 11000, 0 },
{ 70, 11250, 0 },
{ 67, 11500, 0 },
{ 67, 12000, 1 },
{ 70, 12000, 0 },
{ 69, 12500, 0 },
{ 70, 12750, 0 },
{ 67, 13000, 1 },
{ 65, 13500, 1 },
{ 70, 13500, 0 },
{ 69, 14000, 0 },
{ 70, 14250, 0 },
{ 67, 14500, 0 },
{ 63, 15000, 1 },
{ 70, 15000, 0 },
{ 69, 15500, 0 },
{ 70, 15750, 0 },
{ 67, 16000, 0 },
{ 62, 16500, 1 },
{ 70, 16500, 0 },
{ 69, 17000, 0 },
{ 70, 17250, 0 },
{ 67, 17500, 0 },
{ 63, 18000, 1 },
{ 70, 18000, 0 },
{ 69, 18500, 0 },
{ 70, 18750, 0 },
{ 67, 19000, 0 },
{ 62, 19500, 1 },
{ 70, 19500, 0 },
{ 69, 20000, 0 },
{ 70, 20250, 0 },
{ 67, 20500, 0 },
{ 60, 21000, 1 },
{ 70, 21000, 0 },
{ 69, 21500, 0 },
{ 70, 21750, 0 },
{ 67, 22000, 0 },
{ 55, 22500, 1 },
{ 70, 22500, 0 },
{ 69, 23000, 0 },
{ 70, 23250, 0 },
{ 67, 23500, 0 },
{ 60, 24000, 1 },
{ 70, 24000, 0 },
{ 69, 24500, 0 },
{ 70, 24750, 0 },
{ 67, 25000, 0 },
{ 62, 25500, 1 },
{ 70, 25500, 0 },
{ 69, 26000, 0 },
{ 70, 26250, 0 },
{ 67, 26500, 0 },
{ 63, 27000, 1 },
{ 70, 27000, 0 },
{ 69, 27500, 0 },
{ 70, 27750, 0 },
{ 67, 28000, 0 },
{ 62, 28500, 1 },
{ 70, 28500, 0 },
{ 69, 29000, 0 },
{ 70, 29250, 0 },
{ 67, 29500, 0 },
{ 62, 30000, 1 },
{ 74, 30000, 0 },
{ 69, 30500, 1 },
{ 72, 30500, 0 },
{ 70, 30750, 1 },
{ 74, 30750, 0 },
{ 67, 31000, 1 },
{ 70, 31000, 0 },
{ 64, 31500, 1 },
{ 74, 31500, 0 },
{ 69, 32000, 1 },
{ 72, 32000, 0 },
{ 70, 32250, 1 },
{ 74, 32250, 0 },
{ 67, 32500, 1 },
{ 70, 32500, 0 },
{ 66, 33000, 1 },
{ 74, 33000, 0 },
{ 69, 33500, 1 },
{ 72, 33500, 0 },
{ 70, 33750, 1 },
{ 74, 33750, 0 },
{ 67, 34000, 1 },
{ 70, 34000, 0 },
{ 66, 34500, 1 },
{ 74, 34500, 0 },
{ 69, 35000, 1 },
{ 72, 35000, 0 },
{ 70, 35250, 1 },
{ 74, 35250, 0 },
{ 67, 35500, 1 },
{ 70, 35500, 0 },
{ 67, 36000, 1 },
{ 79, 36000, 0 },
{ 79, 36500, 0 },
{ 79, 36750, 0 },
{ 77, 37000, 0 },
{ 75, 37250, 0 },
{ 58, 37500, 1 },
{ 74, 37500, 0 },
{ 74, 38000, 0 },
{ 74, 38250, 0 },
{ 72, 38500, 0 },
{ 70, 38750, 0 },
{ 60, 39000, 1 },
{ 72, 39000, 0 },
{ 72, 39500, 0 },
{ 72, 39750, 0 },
{ 74, 40000, 0 },
{ 72, 40250, 0 },
{ 62, 40500, 1 },
{ 67, 40500, 0 },
{ 60, 41000, 1 },
{ 67, 41000, 0 },
{ 62, 41250, 1 },
{ 67, 41250, 0 },
{ 58, 41500, 1 },
{ 67, 41500, 0 },
{ 62, 42000, 1 },
{ 64, 42250, 0 },
{ 66, 42500, 0 },
{ 67, 42750, 0 },
{ 69, 43000, 0 },
{ 70, 43250, 0 },
{ 62, 43500, 1 },
{ 72, 43500, 0 },
{ 74, 43750, 0 },
{ 72, 44000, 0 },
{ 70, 44500, 0 },
{ 62, 45000, 1 },
{ 64, 45250, 0 },
{ 66, 45500, 0 },
{ 67, 45750, 0 },
{ 69, 46000, 0 },
{ 70, 46250, 0 },
{ 62, 46500, 1 },
{ 72, 46500, 0 },
{ 74, 46750, 0 },
{ 72, 47000, 0 },
{ 70, 47500, 0 },
{ 55, 48000, 1 },
{ 70, 48000, 0 },
{ 69, 48500, 0 },
{ 70, 48750, 0 },
{ 67, 49000, 0 },
{ 65, 49500, 1 },
{ 70, 49500, 0 },
{ 69, 50000, 0 },
{ 70, 50250, 0 },
{ 67, 50500, 0 },
{ 63, 51000, 1 },
{ 70, 51000, 0 },
{ 69, 51500, 0 },
{ 70, 51750, 0 },
{ 67, 52000, 0 },
{ 62, 52500, 1 },
{ 70, 52500, 0 },
{ 69, 53000, 0 },
{ 70, 53250, 0 },
{ 67, 53500, 0 },
{ 63, 54000, 1 },
{ 70, 54000, 0 },
{ 69, 54500, 0 },
{ 70, 54750, 0 },
{ 67, 55000, 0 },
{ 62, 55500, 1 },
{ 70, 55500, 0 },
{ 69, 56000, 0 },
{ 70, 56250, 0 },
{ 67, 56500, 0 },
{ 60, 57000, 1 },
{ 70, 57000, 0 },
{ 69, 57500, 0 },
{ 70, 57750, 0 },
{ 67, 58000, 0 },
{ 55, 58500, 1 },
{ 70, 58500, 0 },
{ 69, 59000, 0 },
{ 70, 59250, 0 },
{ 67, 59500, 0 },
{ 60, 60000, 1 },
{ 70, 60000, 0 },
{ 69, 60500, 0 },
{ 70, 60750, 0 },
{ 67, 61000, 0 },
{ 62, 61500, 1 },
{ 70, 61500, 0 },
{ 69, 62000, 0 },
{ 70, 62250, 0 },
{ 67, 62500, 0 },
{ 63, 63000, 1 },
{ 70, 63000, 0 },
{ 69, 63500, 0 },
{ 70, 63750, 0 },
{ 67, 64000, 0 },
{ 62, 64500, 1 },
{ 70, 64500, 0 },
{ 69, 65000, 0 },
{ 70, 65250, 0 },
{ 67, 65500, 0 },
{ 62, 66000, 1 },
{ 74, 66000, 0 },
{ 69, 66500, 1 },
{ 72, 66500, 0 },
{ 70, 66750, 1 },
{ 74, 66750, 0 },
{ 67, 67000, 1 },
{ 70, 67000, 0 },
{ 64, 67500, 1 },
{ 74, 67500, 0 },
{ 69, 68000, 1 },
{ 72, 68000, 0 },
{ 70, 68250, 1 },
{ 74, 68250, 0 },
{ 67, 68500, 1 },
{ 70, 68500, 0 },
{ 66, 69000, 1 },
{ 74, 69000, 0 },
{ 69, 69500, 1 },
{ 72, 69500, 0 },
{ 70, 69750, 1 },
{ 74, 69750, 0 },
{ 67, 70000, 1 },
{ 70, 70000, 0 },
{ 66, 70500, 1 },
{ 74, 70500, 0 },
{ 69, 71000, 1 },
{ 72, 71000, 0 },
{ 70, 71250, 1 },
{ 74, 71250, 0 },
{ 67, 71500, 1 },
{ 70, 71500, 0 },
{ 67, 72000, 1 },
{ 79, 72000, 0 },
{ 79, 72500, 0 },
{ 79, 72750, 0 },
{ 77, 73000, 0 },
{ 75, 73250, 0 },
{ 58, 73500, 1 },
{ 74, 73500, 0 },
{ 74, 74000, 0 },
{ 74, 74250, 0 },
{ 72, 74500, 0 },
{ 70, 74750, 0 },
{ 60, 75000, 1 },
{ 72, 75000, 0 },
{ 72, 75500, 0 },
{ 72, 75750, 0 },
{ 74, 76000, 0 },
{ 72, 76250, 0 },
{ 62, 76500, 1 },
{ 67, 76500, 0 },
{ 60, 77000, 1 },
{ 67, 77000, 0 },
{ 62, 77250, 1 },
{ 67, 77250, 0 },
{ 58, 77500, 1 },
{ 67, 77500, 0 },
{ 62, 78000, 1 },
{ 64, 78250, 0 },
{ 66, 78500, 0 },
{ 67, 78750, 0 },
{ 69, 79000, 0 },
{ 70, 79250, 0 },
{ 62, 79500, 1 },
{ 72, 79500, 0 },
{ 74, 79750, 0 },
{ 72, 80000, 0 },
{ 70, 80500, 0 },
{ 62, 81000, 1 },
{ 64, 81250, 0 },
{ 66, 81500, 0 },
{ 67, 81750, 0 },
{ 69, 82000, 0 },
{ 70, 82250, 0 },
{ 62, 82500, 1 },
{ 72, 82500, 0 },
{ 74, 82750, 0 },
{ 72, 83000, 0 },
{ 70, 83500, 0 },
{ 55, 84000, 1 },
{ 70, 84000, 0 },
{ 69, 84500, 0 },
{ 70, 84750, 0 },
{ 67, 85000, 0 },
{ 65, 85500, 1 },
{ 70, 85500, 0 },
{ 69, 86000, 0 },
{ 70, 86250, 0 },
{ 67, 86500, 0 },
{ 63, 87000, 1 },
{ 70, 87000, 0 },
{ 69, 87500, 0 },
{ 70, 87750, 0 },
{ 67, 88000, 0 },
{ 62, 88500, 1 },
{ 70, 88500, 0 },
{ 69, 89000, 0 },
{ 70, 89250, 0 },
{ 67, 89500, 0 },
{ 55, 90000, 1 },
{ 70, 90000, 0 },
{ 69, 90500, 0 },
{ 70, 90750, 0 },
{ 67, 91000, 0 },
{ 55, 91500, 1 },
{ 70, 91500, 0 },
{ 69, 92000, 0 },
{ 70, 92250, 0 },
{ 67, 92500, 0 },
{ 55, 93000, 1 },
{ 70, 93000, 0 },
{ 69, 93500, 0 },
{ 70, 93750, 0 },
{ 67, 94000, 0 },
{ 55, 94500, 1 },
{ 70, 94500, 0 },
{ 69, 95000, 0 },
{ 70, 95250, 0 },
{ 67, 95500, 0 },
{ 55, 96000, 1 },
{ 67, 96000, 0 },
{ 58, 97500, 1 },
{ 74, 97500, 0 },
{ 57, 99000, 1 },
{ 72, 99000, 0 },
{ 58, 99500, 1 },
{ 74, 99500, 0 },
{ 55, 100500, 1 },
{ 79, 100500, 0 },

  { 74, 9999999, 0 },
  
};

Tunes tonttu[] = 
{
{ 55, 0, 1 },
{ 59, 1000, 1 },
{ 60, 2000, 1 },
{ 62, 3000, 1 },
{ 55, 4000, 1 },
{ 59, 5000, 1 },
{ 60, 6000, 1 },
{ 62, 7000, 1 },
{ 55, 8000, 1 },
{ 71, 8500, 0 },
{ 67, 9000, 0 },
{ 59, 9000, 1 },
{ 62, 9500, 0 },
{ 64, 10000, 0 },
{ 60, 10000, 1 },
{ 67, 10500, 0 },
{ 67, 11000, 0 },
{ 62, 11000, 1 },
{ 69, 11500, 0 },
{ 71, 11833, 0 },
{ 55, 12000, 1 },
{ 59, 13000, 1 },
{ 60, 14000, 1 },
{ 62, 15000, 1 },
{ 55, 16000, 1 },
{ 71, 16500, 0 },
{ 67, 17000, 0 },
{ 59, 17000, 1 },
{ 62, 17500, 0 },
{ 64, 18000, 0 },
{ 60, 18000, 1 },
{ 67, 18500, 0 },
{ 67, 19000, 0 },
{ 62, 19000, 1 },
{ 69, 19500, 0 },
{ 67, 19833, 0 },
{ 55, 20000, 1 },
{ 59, 21000, 1 },
{ 60, 22000, 1 },
{ 62, 23000, 1 },
{ 55, 24000, 1 },
{ 71, 24500, 0 },
{ 67, 25000, 0 },
{ 59, 25000, 1 },
{ 62, 25500, 0 },
{ 64, 26000, 0 },
{ 60, 26000, 1 },
{ 67, 26500, 0 },
{ 67, 27000, 0 },
{ 62, 27000, 1 },
{ 69, 27500, 0 },
{ 71, 27833, 0 },
{ 55, 28000, 1 },
{ 59, 29000, 1 },
{ 60, 30000, 1 },
{ 62, 31000, 1 },
{ 55, 32000, 1 },
{ 71, 32500, 0 },
{ 67, 33000, 0 },
{ 59, 33000, 1 },
{ 62, 33500, 0 },
{ 64, 34000, 0 },
{ 60, 34000, 1 },
{ 67, 34500, 0 },
{ 67, 35000, 0 },
{ 62, 35000, 1 },
{ 69, 35500, 0 },
{ 67, 35833, 0 },
{ 55, 36000, 1 },
{ 59, 37000, 1 },
{ 60, 38000, 1 },
{ 65, 39000, 1 },
{ 77, 40000, 0 },
{ 58, 40000, 1 },
{ 57, 40500, 1 },
{ 55, 41000, 1 },
{ 74, 41500, 0 },
{ 62, 41500, 1 },
{ 75, 42000, 0 },
{ 60, 42000, 1 },
{ 74, 42500, 0 },
{ 63, 42500, 1 },
{ 72, 42833, 0 },
{ 65, 43000, 1 },
{ 74, 43500, 0 },
{ 57, 43500, 1 },
{ 70, 44000, 0 },
{ 58, 44000, 1 },
{ 70, 44500, 0 },
{ 57, 44500, 1 },
{ 70, 44833, 0 },
{ 55, 45000, 1 },
{ 62, 45500, 1 },
{ 60, 46000, 1 },
{ 63, 46500, 1 },
{ 65, 47000, 1 },
{ 57, 47500, 1 },
{ 77, 48000, 0 },
{ 58, 48000, 1 },
{ 57, 48500, 1 },
{ 55, 49000, 1 },
{ 74, 49500, 0 },
{ 62, 49500, 1 },
{ 75, 50000, 0 },
{ 60, 50000, 1 },
{ 74, 50500, 0 },
{ 63, 50500, 1 },
{ 72, 50833, 0 },
{ 65, 51000, 1 },
{ 74, 51500, 0 },
{ 57, 51500, 1 },
{ 70, 52000, 0 },
{ 58, 52000, 1 },
{ 70, 52500, 0 },
{ 57, 52500, 1 },
{ 55, 53000, 1 },
{ 58, 53500, 1 },
{ 57, 54000, 1 },
{ 60, 54500, 1 },
{ 62, 55000, 1 },
{ 62, 55500, 1 },
{ 55, 56000, 1 },
{ 71, 56500, 0 },
{ 67, 57000, 0 },
{ 59, 57000, 1 },
{ 62, 57500, 0 },
{ 64, 58000, 0 },
{ 60, 58000, 1 },
{ 67, 58500, 0 },
{ 67, 59000, 0 },
{ 62, 59000, 1 },
{ 69, 59500, 0 },
{ 71, 59833, 0 },
{ 55, 60000, 1 },
{ 59, 61000, 1 },
{ 60, 62000, 1 },
{ 62, 63000, 1 },
{ 55, 64000, 1 },
{ 71, 64500, 0 },
{ 67, 65000, 0 },
{ 59, 65000, 1 },
{ 62, 65500, 0 },
{ 64, 66000, 0 },
{ 60, 66000, 1 },
{ 67, 66500, 0 },
{ 67, 67000, 0 },
{ 62, 67000, 1 },
{ 69, 67500, 0 },
{ 67, 67833, 0 },
{ 55, 68000, 1 },
{ 59, 69000, 1 },
{ 60, 70000, 1 },
{ 62, 71000, 1 },
{ 55, 72000, 1 },
{ 71, 72500, 0 },
{ 67, 73000, 0 },
{ 59, 73000, 1 },
{ 62, 73500, 0 },
{ 64, 74000, 0 },
{ 60, 74000, 1 },
{ 67, 74500, 0 },
{ 67, 75000, 0 },
{ 62, 75000, 1 },
{ 69, 75500, 0 },
{ 71, 75833, 0 },
{ 55, 76000, 1 },
{ 59, 77000, 1 },
{ 60, 78000, 1 },
{ 62, 79000, 1 },
{ 55, 80000, 1 },
{ 71, 80500, 0 },
{ 67, 81000, 0 },
{ 59, 81000, 1 },
{ 62, 81500, 0 },
{ 64, 82000, 0 },
{ 60, 82000, 1 },
{ 67, 82500, 0 },
{ 67, 83000, 0 },
{ 62, 83000, 1 },
{ 69, 83500, 0 },
{ 67, 83833, 0 },
{ 55, 84000, 1 },
{ 59, 85000, 1 },
{ 60, 86000, 1 },
{ 65, 87000, 1 },
{ 77, 88000, 0 },
{ 58, 88000, 1 },
{ 57, 88500, 1 },
{ 55, 89000, 1 },
{ 74, 89500, 0 },
{ 62, 89500, 1 },
{ 75, 90000, 0 },
{ 60, 90000, 1 },
{ 74, 90500, 0 },
{ 63, 90500, 1 },
{ 72, 90833, 0 },
{ 65, 91000, 1 },
{ 74, 91500, 0 },
{ 57, 91500, 1 },
{ 70, 92000, 0 },
{ 58, 92000, 1 },
{ 70, 92500, 0 },
{ 57, 92500, 1 },
{ 70, 92833, 0 },
{ 55, 93000, 1 },
{ 62, 93500, 1 },
{ 60, 94000, 1 },
{ 63, 94500, 1 },
{ 65, 95000, 1 },
{ 57, 95500, 1 },
{ 77, 96000, 0 },
{ 58, 96000, 1 },
{ 57, 96500, 1 },
{ 55, 97000, 1 },
{ 74, 97500, 0 },
{ 62, 97500, 1 },
{ 75, 98000, 0 },
{ 60, 98000, 1 },
{ 74, 98500, 0 },
{ 63, 98500, 1 },
{ 72, 98833, 0 },
{ 65, 99000, 1 },
{ 74, 99500, 0 },
{ 57, 99500, 1 },
{ 70, 100000, 0 },
{ 58, 100000, 1 },
{ 70, 100500, 0 },
{ 57, 100500, 1 },
{ 55, 101000, 1 },
{ 62, 101500, 1 },
{ 60, 102000, 1 },
{ 63, 102500, 1 },
{ 65, 103000, 1 },
{ 57, 103500, 1 },
{ 77, 104000, 0 },
{ 58, 104000, 1 },
{ 57, 104500, 1 },
{ 55, 105000, 1 },
{ 74, 105500, 0 },
{ 62, 105500, 1 },
{ 75, 106000, 0 },
{ 60, 106000, 1 },
{ 74, 106500, 0 },
{ 63, 106500, 1 },
{ 72, 106833, 0 },
{ 65, 107000, 1 },
{ 74, 107500, 0 },
{ 57, 107500, 1 },
{ 70, 108000, 0 },
{ 58, 108000, 1 },
{ 70, 108500, 0 },
{ 57, 108500, 1 },
{ 70, 108833, 0 },
{ 55, 109000, 1 },
{ 62, 109500, 1 },
{ 60, 110000, 1 },
{ 63, 110500, 1 },
{ 65, 111000, 1 },
{ 57, 111500, 1 },
{ 77, 112000, 0 },
{ 58, 112000, 1 },
{ 57, 112500, 1 },
{ 55, 113000, 1 },
{ 74, 113500, 0 },
{ 62, 113500, 1 },
{ 75, 114000, 0 },
{ 60, 114000, 1 },
{ 74, 114500, 0 },
{ 63, 114500, 1 },
{ 72, 114833, 0 },
{ 65, 115000, 1 },
{ 74, 115500, 0 },
{ 57, 115500, 1 },
{ 70, 116000, 0 },
{ 58, 116000, 1 },
{ 70, 116500, 0 },
{ 57, 116500, 1 },
{ 55, 117000, 1 },
{ 58, 117500, 1 },
{ 57, 118000, 1 },
{ 60, 118500, 1 },
{ 62, 119000, 1 },
{ 55, 120000, 1 },

  { 74, 9999999, 0 },
  
};

A plugin for Musescore

JavaScript
Check out how to work with plugins in Musescore. Mind that a lot of stuff is left in the code from its previous purpose of colouring noteheads.
Write your music in Musescore on two staves. Open the plugin editor in Musescore. Paste this code into the code window and click Run. Your note event listing will appear in the console window. Copy-paste it to Excel or LibreofficeCalc and sort the elements according to the timestamp value. Look at charillino_tunes.h how the data should look like, remember to add one event at the end of each tune, having 9999999 as the timestamp.
//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2012 Werner Schweer
//  Copyright (C) 2013-2017 Nicolas Froment, Joachim Schmitz
//  Copyright (C) 2014 Jörn Eichler
// 
//  Edited by Johan Halmen 2024
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================

import QtQuick 2.2
import MuseScore 3.0

MuseScore {
      version:  "3.0"
      description: qsTr("This plugin makes a csv listing of note events for Charillino, a mechanical glockenspiel")
      menuPath: "Plugins.Notes.NoteEvents"

      property variant colors : [ // "#rrggbb" with rr, gg, and bb being the hex values for red, green, and blue, respectively
               "#e21c48", // C
               "#f26622", // C#/Db
               "#f99d1c", // D
               "#ffcc33", // D#/Eb
               "#fff32b", // E
               "#bcd85f", // F
               "#62bc47", // F#/Gb
               "#009c95", // G
               "#0071bb", // G#/Ab
               "#5e50a1", // A
               "#8d5ba6", // A#/Bb
               "#cf3e96"  // B
               ]
      property string black : "#000000"

      // Apply the given function to all notes in selection
      // or, if nothing is selected, in the entire score

      function applyToNotesInSelection(func) {
            var cursor = curScore.newCursor();
            cursor.rewind(1);
            var startStaff;
            var endStaff;
            var endTick;
            var fullScore = false;
            if (!cursor.segment) { // no selection
                  fullScore = true;
                  startStaff = 0; // start with 1st staff
                  endStaff = curScore.nstaves - 1; // and end with last
            } else {
                  startStaff = cursor.staffIdx;
                  cursor.rewind(2);
                  if (cursor.tick === 0) {
                        // this happens when the selection includes
                        // the last measure of the score.
                        // rewind(2) goes behind the last segment (where
                        // there's none) and sets tick=0
                        endTick = curScore.lastSegment.tick + 1;
                  } else {
                        endTick = cursor.tick;
                  }
                  endStaff = cursor.staffIdx;
            }
            console.log(startStaff + " - " + endStaff + " - " + endTick)
            for (var staff = startStaff; staff <= endStaff; staff++) {
                  for (var voice = 0; voice < 4; voice++) {
                        cursor.rewind(1); // sets voice to 0
                        cursor.voice = voice; //voice has to be set after goTo
                        cursor.staffIdx = staff;

                        if (fullScore)
                              cursor.rewind(0) // if no selection, beginning of score

                        while (cursor.segment && (fullScore || cursor.tick < endTick)) {
                              if (cursor.element && cursor.element.type === Element.CHORD) {
                                    var graceChords = cursor.element.graceNotes;
                                    for (var i = 0; i < graceChords.length; i++) {
                                          // iterate through all grace chords
                                          var graceNotes = graceChords[i].notes;
                                          for (var j = 0; j < graceNotes.length; j++)
                                                func(graceNotes[j]);
                                    }
                                    var notes = cursor.element.notes;
                                    for (var k = 0; k < notes.length; k++) {
                                          var note = notes[k];
                                          func(note, cursor, staff);
                                    }
                              }
                              cursor.next();
                        }
                  }
            }
      }

      function colorNote(note, cur, st) { 
	    console.log(note.pitch + ", " + Math.round(cur.time) + ", " + st);

         }

      onRun: {
            console.log("hello colornotes");

            applyToNotesInSelection(colorNote)

            Qt.quit();
         }
}

xiao_charillino_calibrator.ino

C/C++
Main code for calibrating the charillino. Depends on Rob Tillaart's library PCA9685_RT.
#include <PCA9685.h>
#include <Arduino.h>
#define busy_pin 11

int16_t busy_angle[2] = { 300, 400};
int8_t busy_toggler = 0;     
uint32_t busy;

char *notename[] = 
{
  "G4",
  "G#4",
  "A4",
  "Bb4",
  "B4",
  "C5",
  "C#5",
  "D5",
  "D#5",
  "E5",
  "F5",
  "F#5",
  "G",
  "G#5",
  "A",
  "Bb5",
  "B5",
  "C6",
  "C#6",
  "D6",
  "D#6",
  "E6",
  "F6",
  "F#6",
  "G6",
  "G#6",
  "A6"  
};

int16_t angles[2][27][3] =
{
  { // Bass 
    // horiz, vertic lower, vertic upper
{ 186, 129, 155 },     // G4
{ 199, 129, 155 },     // G#4
{ 212, 129, 155 },     // A4
{ 225, 129, 155 },     // Bb4
{ 233, 129, 155 },     // B4
{ 246, 129, 153 },     // C5
{ 257, 129, 155 },     // C#5
{ 267, 129, 155 },     // D5
{ 280, 129, 155 },     // D#5
{ 290, 129, 155 },     // E5
{ 304, 129, 155 },     // F5
{ 315, 129, 155 },     // F#5
{ 327, 129, 155 },     // G
{ 339, 129, 155 },     // G#5
{ 350, 129, 155 },     // A
{ 362, 129, 155 },     // Bb5
{ 374, 129, 155 },     // B5
{ 386, 129, 155 },     // C6
{ 398, 129, 155 },     // C#6
{ 410, 129, 155 },     // D6
{ 422, 129, 155 },     // D#6
{ 434, 129, 155 },     // E6
{ 442, 155, 155 },     // F6
{ 442, 155, 155 },     // F#6
{ 442, 155, 155 },     // G6
{ 442, 155, 155 },     // G#6
{ 442, 155, 155 },     // A6
},
  { // Discant
    // horiz, vertic lower, vertic upper
{ 158, 246, 246 },     // G4
{ 158, 263, 246 },     // G#4
{ 170, 263, 246 },     // A4
{ 183, 263, 246 },     // Bb4
{ 195, 263, 246 },     // B4
{ 205, 263, 246 },     // C5
{ 219, 263, 246 },     // C#5
{ 231, 263, 246 },     // D5
{ 242, 263, 246 },     // D#5
{ 256, 262, 246 },     // E5
{ 269, 263, 246 },     // F5
{ 281, 263, 246 },     // F#5
{ 296, 263, 246 },     // G
{ 312, 263, 246 },     // G#5
{ 326, 263, 246 },     // A
{ 337, 263, 246 },     // Bb5
{ 351, 263, 246 },     // B5
{ 366, 263, 246 },     // C6
{ 382, 261, 247 },     // C#6
{ 395, 263, 248 },     // D6
{ 408, 263, 247 },     // D#6
{ 424, 263, 247 },     // E6
{ 440, 262, 247 },     // F6
{ 454, 262, 247 },     // F#6
{ 467, 261, 247 },     // G6
{ 483, 261, 246 },     // G#6
{ 498, 261, 247 },     // A6
}
};

bool blinker = true;

PCA9685 *servobank;

#define my_delay 200      // time between initial horizontal move and vertical strike
#define secondhalf 120
#define return_time 70   // time between down and up command
#define G3 55




int horiz_servo[2] = { 12, 14 };  // Bass, discant
int vertic_servo[2] = { 13, 15 }; // Bass, discant


// -----------------------------------------------------------------------------
// G4 to G6, midi nr 67 to 91
// -----------------------------------------------------------------------------

char *malletname[] = {"// Bass:", "// Discant:"};

void print_arrays(int16_t mal)
{
  
  for (int m = 0; m < 2; m++)
  {
    Serial.println(malletname[mal]);
    for (int i = 0; i < 27; i++)
    {
      Serial.print("{ ");
      Serial.print(angles[mal][i][0]);
      Serial.print(", ");
      Serial.print(angles[mal][i][1]);
      Serial.print(", ");
      Serial.print(angles[mal][i][2]);
      Serial.print(" },     // ");
      Serial.println(notename[i]);      
    }
    Serial.println(" ");
    Serial.println(" ");
    mal = 1 - mal;

  }
}

void setup()
{
  Serial.begin(115200);
  Serial.println("Me too...");
  Wire.begin();
  servobank = new PCA9685(0x40);
  servobank->begin(); 
  servobank->setFrequency(50);

  
  pinMode(LED_BUILTIN, OUTPUT);
  for (int i = 0; i < 10; i++)
  {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(300);
    digitalWrite(LED_BUILTIN, LOW);
    delay(300);
    
  }
  delay(500);

}

bool blynker = false;

// -----------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------

void strike(int16_t mal, int16_t ptr)
{
  servobank->setPWM(horiz_servo[mal], angles[mal][ptr][0]);
  delay(200);
  servobank->setPWM(vertic_servo[mal], angles[mal][ptr][1]);
  delay(1200);
  servobank->setPWM(vertic_servo[mal], angles[mal][ptr][2]);
  
  
} 


void loop() {
  int16_t n;
  int16_t ptr, mal;
  ptr = 0;
  mal = 0;

  busy = millis();

  
  while(true)
  {
    if (Serial.available()) {
      n = Serial.available();
      char inByte = Serial.read();
      switch (inByte)
      {
        case '1' :
          strike(mal, ptr);
          break;

        case '3' :  // Gotta check range
          if (ptr > 1) strike(mal, ptr - 2);
          delay(800);
          if (ptr > 0) strike(mal, ptr - 1);
          delay(800);
          strike(mal, ptr - 0);
          delay(800);
          break;

        case 'a' :
          ptr--;
          if (ptr < 0)
          {
            ptr = 26;
            mal = 1 - mal;
          }
          break;
        case 'f' :
          ptr++;
          if (ptr > 26)
          {
            ptr = 0;
            mal = 1 - mal;
          }
          break;
        case 's' :
          if (n == 4)
            angles[mal][ptr][0] -= 10;
          else
            angles[mal][ptr][0] --;
          break;
        case 'd' :
          if (n == 4)
            angles[mal][ptr][0] += 10;
          else
            angles[mal][ptr][0] ++;
          break;
        case 'e' :
          if (n == 4)
            angles[mal][ptr][1] += 10;
          else
            angles[mal][ptr][1] ++;
          break;
        case 'w' :
          if (n == 4)
            angles[mal][ptr][1] -= 10;
          else
            angles[mal][ptr][1] --;
          break;
        case 'c' :
          if (n == 4)
            angles[mal][ptr][2] += 10;
          else
            angles[mal][ptr][2] ++;
          break;
        case 'x' :
          if (n == 4)
            angles[mal][ptr][2] -= 10;
          else
            angles[mal][ptr][2] --;
          break;
          
            
      }

      print_arrays(1 - mal);
        Serial.print("{ ");
        Serial.print(angles[mal][ptr][0]);
        Serial.print(", ");
        Serial.print(angles[mal][ptr][1]);
        Serial.print(", ");
        Serial.print(angles[mal][ptr][2]);
        Serial.print(" },     // ");
        Serial.println(notename[ptr]);      
      while (Serial.available())
        inByte = Serial.read();
    }

    if (millis() - busy > 10000)
    {
      busy = millis();
      servobank->setPWM(busy_pin, busy_angle[busy_toggler]);
      busy_toggler = 1 - busy_toggler;
    }
  }

}

Credits

Johan_Ha
16 projects • 26 followers
Music teacher Composer Coding for fun
Contact

Comments

Please log in or sign up to comment.