music

A 4-post collection

The Hand-MIDI Interface Project - Adafruit Feather 32u4 with Magnetometer

Written by Michael Earls
 electronics  music  diy  programming

A few months ago, I had an idea to create a new musical instrument that mounted on a glove. I used a cycling glove that I had from that time that I bought the Trek bike and rode it twice (long story).

I bought the following parts from Adafruit:

When the parts arrived, I started working on my "instrument". The idea was that I'd mount a Raspberry Pi to the back of my hand and wire up the 9-DOF sensor to one hand and the accelerometer to the other. I quickly learned that the Raspberry Pi is too big to mount on the back of a hand and ended up going a different direction. So, I ordered the following parts to try again:

I also ordered the following from ebay:

Once these new parts arrived, I got busy with my project. I first started by soldering some 4-conductor twisted pair phone wire to the I2C interface of the 9-DOF sensor. I made the wire about 8 inches long and then terminated it with one of the RJ-11 connectors.

9-DOF Sensor with RJ-11 connector 9-DOF Sensor with RJ-11 Connector

I then connected the Cat-3 keystone jack to the Feather's I2C port so that the wires would match how I connected them to the 9-DOF sensor. This would allow me to "plug-and-play" my I2C sensors. I originally tried to sew the sensor onto the palm of the glove using the conductive thread, but I never could get it to solder onto the sensor properly, so I ended up eliminating the conductive thread altogether.

Adafruit Feather with Cat-3 Keystone Jack Adafruit Feather with Cat-3 Keystone Jack

Ultimately, I decided to forego the glove completely until I could prove my concept.

Once I had the hardware wired up, it was time to write the sketch for the Feather. The Adafruit Feather that I bought is based on the Arduino 32u4 processor, so I opened up the Arduino IDE and started modifying the sample code for the 9-DOF sensor (and merged in sample code from the OLED display, as well).

I was able to get my custom cerkit.com logo on the display and that made me happy.

cerkit.com Logo on the Adafruit OLED Display Featherwing

Here is the code that displays the output of the 9-DOF sensor to the serial monitor of the Arduino IDE:

#include <Adafruit_SSD1306.h>

/*********************************************************************
  This is an example for our Monochrome OLEDs based on SSD1306 drivers

  Pick one up today in the adafruit shop!
  ------> http://www.adafruit.com/category/63_98

  This example is for a 128x32 size display using I2C to communicate
  3 pins are required to interface (2 I2C and one reset)

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada  for Adafruit Industries.
  BSD license, check license.txt for more information
  All text above, and the splash screen must be included in any redistribution
*********************************************************************/

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM9DS0.h>

using namespace std;

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define BUTTON_A 9
#define BUTTON_B 6
#define BUTTON_C 5

#define CHANNEL 1

bool _invertText = false;

/* Assign a unique base ID for this sensor */
Adafruit_LSM9DS0 lsm = Adafruit_LSM9DS0(1000);  // Use I2C, ID #1000  
char _xBuf [10] = { 0 };  
char _yBuf [10] = { 0 };  
char _zBuf [10] = { 0 };  
char _lsmData [128] = { 0 };


/***************************************************************************************************
   Splash screen image generated by LCD Assistant
   http://en.radzio.dxp.pl/bitmap_converter/
   See https://learn.adafruit.com/monochrome-oled-breakouts/arduino-library-and-examples
   for more details
 **************************************************************************************************/

static const unsigned char PROGMEM cerkit_splash [] = {  
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x7F, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x01, 0x8F, 0x0F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x02, 0x1F, 0x07, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x04, 0x1F, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x08, 0x1F, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x10, 0x1F, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x38, 0x1F, 0x0F, 0xE4, 0x00, 0x00, 0x00, 0x02, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x3C, 0x0F, 0x1F, 0xE6, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x4E, 0x03, 0x3F, 0x8E, 0x0E, 0x07, 0x02, 0xC2, 0x31, 0xC1, 0xF8, 0x00, 0x07, 0x07, 0x07, 0x60,
  0x47, 0x00, 0xFF, 0x1E, 0x11, 0x08, 0x83, 0x22, 0x40, 0x40, 0x40, 0x00, 0x08, 0x88, 0x84, 0x90,
  0xC3, 0xC0, 0xFC, 0x3F, 0x21, 0x10, 0x42, 0x22, 0x80, 0x40, 0x40, 0x00, 0x10, 0x90, 0x44, 0x90,
  0x81, 0xFE, 0xE0, 0xFF, 0x20, 0x10, 0x42, 0x02, 0x80, 0x40, 0x40, 0x00, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x7F, 0x03, 0xFF, 0x20, 0x1F, 0xC2, 0x03, 0x00, 0x40, 0x40, 0x00, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x07, 0x1F, 0xFF, 0x20, 0x10, 0x02, 0x02, 0x80, 0x40, 0x40, 0x00, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x00, 0xFF, 0xFF, 0x20, 0x10, 0x02, 0x02, 0x40, 0x40, 0x4C, 0x10, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x00, 0xFF, 0xFF, 0x11, 0x08, 0x42, 0x02, 0x20, 0x40, 0x48, 0x38, 0x08, 0x88, 0x84, 0x90,
  0xC0, 0x07, 0x1F, 0xFF, 0x0E, 0x07, 0x82, 0x02, 0x10, 0x40, 0x30, 0x10, 0x07, 0x07, 0x04, 0x90,
  0x40, 0x3F, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x41, 0xFE, 0xC0, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x43, 0xE0, 0xFC, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x2F, 0x80, 0xFF, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x1E, 0x00, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x1C, 0x00, 0xFF, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x0C, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x02, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x01, 0x80, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x70, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

void setup()  
{
  Serial.begin(9600);
  Serial.println(F("cerkit.com warbly hand controller")); Serial.println("");

  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  // init done

  // Show image buffer on the display hardware.
  // Since the buffer is initialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.
  display.display();
  delay(2000);

  display.clearDisplay();
  display.display();

  showCustomSplashScreen();

  pinMode(BUTTON_A, INPUT_PULLUP);
  pinMode(BUTTON_B, INPUT_PULLUP);
  pinMode(BUTTON_C, INPUT_PULLUP);

  /* Initialize the sensor */
  lsm.begin();

  /* Display some basic information on this sensor */
  displaySensorDetails();

  /* Setup the sensor gain and integration time */
  configureSensor();

  Serial.println(F("Found LSM9DS0 9DOF"));
}


void loop() {  
  performSensorSweep();
}

void showCustomSplashScreen()  
{
  // compensate for original image being inverted
  display.invertDisplay(true);
  display.drawBitmap(0, 0, cerkit_splash, 128, 32, WHITE);
  display.display();
  delay(2000);

  // put the display back to normal
  display.invertDisplay(false);

  display.clearDisplay();
  display.display();
}

void updateScreen(char* msg, int x, int y)  
{
  display.setCursor(x, y);
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.print(msg);
  display.display();
}

void printMessage(char* msg)  
{
  printMessage(msg, 2);
}

void printMessage(char* msg, int textSize)  
{
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(textSize);
  if (_invertText)
  {
    display.setTextColor(BLACK, WHITE); // 'inverted' text
  }
  else
  {
    display.setTextColor(WHITE);
  }

  display.print(msg);
  display.display();
}

void performSensorSweep()  
{
  /* Get a new sensor event */
  sensors_event_t accel, mag, gyro, temp;

  lsm.getEvent(&accel, &mag, &gyro, &temp);

  // print out accelerometer data
  Serial.print("Accel X: "); Serial.print(accel.acceleration.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(accel.acceleration.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(accel.acceleration.z);     Serial.println("  \tm/s^2");
  Serial.println("**********************\n");

  // print out magnetometer data
  Serial.print("Magn. X: "); Serial.print(mag.magnetic.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(mag.magnetic.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(mag.magnetic.z);     Serial.println("  \tgauss");
  Serial.println("**********************\n");

  // print out gyroscopic data
  Serial.print("Gyro  X: "); Serial.print(gyro.gyro.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(gyro.gyro.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(gyro.gyro.z);     Serial.println("  \tdps");

  Serial.println("**********************\n");

  delay(250);
}

/**************************************************************************/
/*
    Displays some basic information on this sensor from the unified
    sensor API sensor_t type (see Adafruit_Sensor for more information)
*/
/**************************************************************************/
void displaySensorDetails(void)  
{
  sensor_t accel, mag, gyro, temp;

  lsm.getSensor(&accel, &mag, &gyro, &temp);

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(accel.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(accel.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(accel.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(accel.max_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Min Value:    ")); Serial.print(accel.min_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Resolution:   ")); Serial.print(accel.resolution); Serial.println(F(" m/s^2"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(mag.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(mag.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(mag.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(mag.max_value); Serial.println(F(" uT"));
  Serial.print  (F("Min Value:    ")); Serial.print(mag.min_value); Serial.println(F(" uT"));
  Serial.print  (F("Resolution:   ")); Serial.print(mag.resolution); Serial.println(F(" uT"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(gyro.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(gyro.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(gyro.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(gyro.max_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Min Value:    ")); Serial.print(gyro.min_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Resolution:   ")); Serial.print(gyro.resolution); Serial.println(F(" rad/s"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(temp.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(temp.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(temp.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(temp.max_value); Serial.println(F(" C"));
  Serial.print  (F("Min Value:    ")); Serial.print(temp.min_value); Serial.println(F(" C"));
  Serial.print  (F("Resolution:   ")); Serial.print(temp.resolution); Serial.println(F(" C"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  delay(500);
}

/**************************************************************************/
/*
    Configures the gain and integration time for the TSL2561
*/
/**************************************************************************/
void configureSensor(void)  
{
  // 1.) Set the accelerometer range
  lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_2G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_4G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_6G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_8G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_16G);

  // 2.) Set the magnetometer sensitivity
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_2GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_4GAUSS);
  lsm.setupMag(lsm.LSM9DS0_MAGGAIN_8GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_12GAUSS);

  // 3.) Setup the gyroscope
  lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_245DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_500DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_2000DPS);
}

Here's a screenshot of the serial monitor output:

Serial Monitor Output for the 9-DSO Sensor on the Adafruit Feather

Once I had this data, I could start to interface with MIDI to send note and other data to my computer.

This is where things started to fall apart. I'm not sure what exactly went wrong, but nothing sounded the way I intended. I tried to code a note threshold for a particular range of values of the magnetometer. The idea was that as the sensor got closer to a magnet that was fixed to my desk, it would raise or lower the note being played by my music software. It worked...sort of, but not at the level of detail that I imagined. I looked around for ways to come up with the correct combination of notes to data mapping, but I never really got anything that satisfied me.

After a few hours of working on the code, I gave up and put the project away. As a matter of fact, I'm writing this many months after I stopped working on it. I was so frustrated by my failure to get it working to match my imagination that I didn't bother documenting my efforts.

Update: November 19, 2017 - I decided to complete this project and get it up and running. After examining the code, I realized that I was calling the note on and note off MIDI messages in the wrong way. After a bit of twiddling with my timing, I was able to get it to work (mostly). It still stops sending the messages after a few seconds, but I was able to get it to run long enough to create a short video of the results.

#include <frequencyToNote.h>  
#include <MIDIUSB.h>
#include <pitchToFrequency.h>
#include <pitchToNote.h>

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM9DS0.h>

// Simple tutorial on how to receive and send MIDI messages.
// Here, when receiving any message on channel 4, the Arduino
// will blink a led and play back a note for 1 second.

/* Assign a unique base ID for this sensor */   
Adafruit_LSM9DS0 lsm = Adafruit_LSM9DS0(1000);  // Use I2C, ID #1000

static const unsigned ledPin = 13;      // LED pin on Arduino Uno

void setup()  
{
    pinMode(ledPin, OUTPUT);

    lsm.begin();

    /* Display some basic information on this sensor */
    displaySensorDetails();

    /* Setup the sensor gain and integration time */
    configureSensor();

    Serial.println(F("Found LSM9DS0 9DOF"));
}

void loop()  
{
    performSensorSweep();
}

void performSensorSweep()  
{
  /* Get a new sensor event */ 
  sensors_event_t accel, mag, gyro, temp;

  lsm.getEvent(&accel, &mag, &gyro, &temp);

  // print out accelerometer data
  Serial.print("Accel X: "); Serial.print(accel.acceleration.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(accel.acceleration.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(accel.acceleration.z);     Serial.println("  \tm/s^2");

  // send a MIDI note...
  int zAccelNote = map(gyro.gyro.z, -128, 80, 20, 127);
  int xAccelVelocity = map(gyro.gyro.x, -144, 144, 64, 127);

  // print out magnetometer data
  Serial.print("Magn. X: "); Serial.print(mag.magnetic.x); Serial.print(" ");

  // send a MIDI note...
  int zNote = map(mag.magnetic.z, -3.03, 0.28, 20, 100);
  //int xVelocity = map(mag.magnetic.x, -6.0, 6.0, 64, 127);
  int xVelocity = 127;

  digitalWrite(ledPin, HIGH);
  noteOn(3, zNote, xVelocity);
  yield;
  digitalWrite(ledPin, LOW);

  Serial.print("  \tY: "); Serial.print(mag.magnetic.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(mag.magnetic.z);     Serial.println("  \tgauss");

  Serial.println("**********************\n");

// set acceleration
//int accelDelay = map(accel.acceleration.x, 
 delay(5);
}

// First parameter is the event type (0x09 = note on, 0x08 = note off).
// Second parameter is note-on/note-off, combined with the channel.
// Channel can be anything between 0-15. Typically reported to the user as 1-16.
// Third parameter is the note number (48 = middle C).
// Fourth parameter is the velocity (64 = normal, 127 = fastest).

void noteOn(byte channel, byte pitch, byte velocity) {  
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
}

void noteOff(byte channel, byte pitch, byte velocity) {  
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

/**************************************************************************/
/*
    Displays some basic information on this sensor from the unified
    sensor API sensor_t type (see Adafruit_Sensor for more information)
*/
/**************************************************************************/
void displaySensorDetails(void)  
{
  sensor_t accel, mag, gyro, temp;

  lsm.getSensor(&accel, &mag, &gyro, &temp);

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(accel.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(accel.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(accel.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(accel.max_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Min Value:    ")); Serial.print(accel.min_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Resolution:   ")); Serial.print(accel.resolution); Serial.println(F(" m/s^2"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(mag.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(mag.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(mag.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(mag.max_value); Serial.println(F(" uT"));
  Serial.print  (F("Min Value:    ")); Serial.print(mag.min_value); Serial.println(F(" uT"));
  Serial.print  (F("Resolution:   ")); Serial.print(mag.resolution); Serial.println(F(" uT"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(gyro.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(gyro.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(gyro.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(gyro.max_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Min Value:    ")); Serial.print(gyro.min_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Resolution:   ")); Serial.print(gyro.resolution); Serial.println(F(" rad/s"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(temp.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(temp.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(temp.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(temp.max_value); Serial.println(F(" C"));
  Serial.print  (F("Min Value:    ")); Serial.print(temp.min_value); Serial.println(F(" C"));
  Serial.print  (F("Resolution:   ")); Serial.print(temp.resolution); Serial.println(F(" C"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  delay(500);
}

// First parameter is the event type (0x0B = control change).
// Second parameter is the event type, combined with the channel.
// Third parameter is the control number number (0-119).
// Fourth parameter is the control value (0-127).

void controlChange(byte channel, byte control, byte value) {  
  midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
  MidiUSB.sendMIDI(event);
}

/**************************************************************************/
/*
    Configures the gain and integration time for the TSL2561
*/
/**************************************************************************/
void configureSensor(void)  
{
  // 1.) Set the accelerometer range
  lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_2G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_4G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_6G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_8G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_16G);

  // 2.) Set the magnetometer sensitivity
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_2GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_4GAUSS);
  lsm.setupMag(lsm.LSM9DS0_MAGGAIN_8GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_12GAUSS);

  // 3.) Setup the gyroscope
  lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_245DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_500DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_2000DPS);
}

Overall, this was a fun project, and I enjoyed getting to hear the sounds it created.

Why modular synthesis is like programming

Written by Michael Earls
 programming  music  modular  synthesis

I am a professional software developer. Most of my spare time is spent writing code for side projects. When I'm not writing code or hanging out with my wife, I'm creating music. I'm just a music hobbyist, so my toolbox isn't as full as I'd wish for.

During my explorations of music, I've evolved from playing the piano by ear as a kid to being obsessed by modular synthesis in recent times.

I've always known that music is very much like software development. You have a certain language that you use to create a product using certain rules depending on what outcome you are trying to achieve. That's obviously oversimplifying music and development, but it makes my point.

Modular Synthesis is even closer to programming (either Object-oriented or functional) in that you have modules (objects/functions) that you patch to other modules (a function on another object), setting dials, knobs, and sliders to get the desired result (function parameters, default constructors on objects), taking the output and sending it to the amplifier (the user interface).

I get the same satisfaction out of playing with modular synthesis as I do writing code. It's instant feedback. The faster the iteration, the better I feel for the session.

When I don't hear the desired sound while working on a patch, I have to follow the wires from start to finish, finding the culprit along the way. This is no different than what I do when I debug my code. When debugging software, you have to follow the operation call from start to finish, examining the variables along the way. The lessons I've learned in my 20 years as a professional developer have prepared me for creating patches with modular synthesizers. I felt instantly at home last week when I got my hands on the Softube Modular plugin. Everything just worked the way I understood it was supposed to.

I think this is why I am so intrigued by modular synthesis. I can imagine that there's a bit of "Inception" in the Softube shop as those developers are writing code that emulates a modular synthesizer in software.

Softube Modular DAW Plugin Review

Written by Michael Earls
 synthesis  modular  music

testing.jpg

I spent a few hours with the Softube Modular over the weekend and I am very impressed.

If you're unfamiliar with modular synthesis, I highly recommend watching the video below.

Also, try searching YouTube for "Modular synthesizer performances" to hear some of what is possible.

Some of my favorites are:

This review is based on the following environment:

I created a live broadcast on Twitch during my evaluation. You can view my Twitch channel at http://twitch.tv/cerkit. It contains the archived footage of my broadcast.

Here is my entire session on YouTube. Fast forward to around 2:00:00 to watch me record the performances in Ableton (and hear my audio clipping due to CPU overload :) )

Installation

Installation of the software was a bit tricky as it involved a third-party license manager. They use iLok, so you have to have an account with iLok and install their software. I was able to breeze through it in about 5 minutes. If you're less comfortable with computer installation, then it might take a little longer to read the instructions (I'm one of those types that skips reading the instructions until I have something halfway put together backwards and I have to take it apart and start over with the instructions).

There is a 20 day demo license available for Modular. Just click on the DEMO tab on the Modular page to have the license deposited into your iLok account.

First impressions

Once the iLok license was activated, I opened up Ableton Live and added the Softube modular plugin to a MIDI track. There is also a Modular FX plugin you can use to process your audio, but I haven't used that feature yet. I clicked on the wrench icon on the plugin at the bottom of the page to open up the modular rack.

I started with an empty rack because I wanted to jump right into playing around with various things I've seen on YouTube videos. I have a moderate understanding of modular synthesis, but Eurorack has been out of my price range (I'm just a hobbyist and only have a little bit of disposable income each month. I've heard the reference to Eurorack as "Eurocrack", so I'm a bit weary of buying the hardware just yet). I wanted to try it out first, and this is a good way to understand how the different modules work together.

User Interface

The interface is really easy to use. The screen is divided into horizontal sections. The first two sections are divided by a control bar that contains the Module management buttons, performance mapping button, main output, volume, output level meter, auxiliary outputs (I haven't learned how to use these, yet), and a section labeled "Block DC On AUX Out" for each of the auxiliary outs (1-4).

Workflow

To add a module, you just click the add button and select from the menu of available modules.

Module selector

I started with the basics: Voltage Controlled Oscillator(VCO), Voltage Controlled Filter(VCF), An envelope generator, and a Voltage Controlled Amplifier(VCA). I then wired them up in the standard formation to create a sound that I could start working with.

Saving patches

I really liked that I could save my patch at different points of development so I could go back and explore other avenues (and even get back a sound I liked from earlier).

Saving your patches

To save a patch in Ableton Live, select the MIDI track that contains your Softube Modular plugin. Then, look for the legacy floppy disk icon (if you've never seen a 3.5" floppy disk, this might be confusing. It's a running joke among software developers that we're still using an "ancient" disk as a save icon (we could be using the 8" or 5.25" floppy, so count your blessings), but no one has come up with anything better). Here is the icon circled in green:

Saving a Softube Modular patch in Ableton Live

Clicking this icon will allow you to save your patch as a preset. I use the instruments folder in the User Library (I created a folder named "Modular" just for this plugin)

Loading your saved patches

Click on the folder icon to the left of the floppy disk icon to load any patches that you previously saved (or downloaded from the Internet).

Loading your patches in Ableton

Sharing your patches in the public domain

If you're interested in sharing your patches and downloading patches from others, I have created a GitHub repository to share patches in the public domain. It's a little technical to get started sharing your own patches, but downloading patches from the repository is easy to do.

Presets

I browsed through a few presets and even used them on the recording I made during my Twitch session. I haven't gone through them all, but there are quite a few. I believe that this is the greatest benefit of having a software modular synthesizer - changing patches is super easy. However, I can see where the fact that you can't change patches as easily with real world hardware would encourage you to explore more with what you have. I can also see where the limitations of real world hardware would push you to be more creative.

Recording

When recording with the Softube Modular, none of the dials are mapped to your MIDI performance (there's no automation recording capability), so to record a performance, you'll need to route your audio output to an audio channel and record it that way. There is a way to get the basic inputs from your MIDI controller (MIDI clock, pitch, velocity, aftertouch, mod wheel, and pitch bend), but anything beyond that is not possible.

I am a user of Properllerhead Reason, so I am a little unfamiliar with Ableton. I was able to get into the plugin pretty easily, but I was fumbling around trying to figure out how to set track levels.

It's also a Time Machine!

This plugin is a time machine. Unfortunately, it only goes one way - into the future. Once I started working with the Softube Modular, I lost all sense of time. Before I knew it, I had been working with it for over two hours.

CPU Utilization

It took some time, but I eventually found the limitation of my machine. I had so many modules in my rack that my CPU usage was spiking over 100%. This caused a complete cutoff in sound from Ableton. I managed to remove some modules that I wasn't using and closed some programs on my computer to get back some CPU. I have 16GB of RAM, so I wasn't wanting there.

I'm running a low-end AMD processor on a computer that I built myself. I'm going to try to get a CPU cooler and overclock it to see if I can get more out of it (there is someone who has figured out how to overclock it to 4.8GHz on the same motherboard that I have. I am running at 3.89GHz), but I'm most likely going to have to replace the CPU with an Intel i5 CPU. That means I have to invest in a new motherboard, as well. It's always something...

Summary

This plugin is addictive! It's everything I've been wishing for in a software modular synthesizer. I've had a lot of fun with Reason's rack extensions for synthesis, but I just couldn't get the workflow I wanted. I also can't get my Novation Launchpad MK2 to work with Reason as the Automapper is not recognizing it. The Launchpad "just works" with Ableton, so I had it connected with my keyboard and was recording and performing my "song" with ease.

Currently the Softube Modular is available for $75, instead of the original price of $99. I hope that it stays at $75 until next month when I can pull the money together for it and the third-party plugins from Intelligel, plus the Heartbeat plugins from Softube.

Why I really got back into electronics

Written by Michael Earls
 electronics  diy  music  hobbies  Lunetta  art

Why I really got back into this hobby

A few weeks ago, I stumbled on a DIY synthesizer community based on synthesizers called "Lunettas" (after Stanley Lunetta, the guy who pioneered the process). I'll let you read about it if you're interested.

Intro to Lunetta CMOS Synthesizers

Lunetta on Breadboard

Basically, Lunettas are CMOS-based (usually) integrated circuits that are setup to generate clock frequencies within the range of human hearing. The circuits are created using very inexpensive chips containing "oscillators", very much like the more expensive Moog or Eurorack alternatives (one Lunetta CMOS with six oscillators costs less than $0.50 while a single simple Eurorack oscillator costs about $150). However, the sound from Eurorack modules is more "mature" and musical, while Lunettas tend to be a bit "beepy" and chaotic.

An example of a Lunetta Synth

WARNING! Obnoxious beeping ahead!

I hope to change that by removing the annoying beeping sounds from the resulting output of my Lunetta device and replacing them with more musical results.

The possibilities are endless when you combine CMOS-based (along with some passive components to change clock frequencies driving the components) with a Raspberry Pi and software.

I have a lot of ideas, but every time I get a new Idea, I research the underlying theory and get caught up in the science of it all. The idea behind Lunettas is that they're supposed to be experimental and a way to just play around with circuitry to make cool sounds. I'm applying an enormous amount of brain energy thinking of ways to control the sounds based on theories like the General Theory of Generative Music (GTTM).

Honestly, I just need to sit down and play with these circuits before I can build my ultimate "smart" machine that "composes" its own music.

One of my biggest desires in life is to create a machine that can create art based on minor inputs from me and a little guidance along the way. I want it to "learn" how to listen to music, develop its own "tastes" and then create "music" based on what it has "learned". All of those words in quotes are because I'm not really trying to build a self-realizing AI, I just want to build a machine that creates art.
Google AI Deep Dream Artwork