My motivation for the hexynth project is to produce a system of snap-together modules that can be programmed, that I can use to teach coding and tech skills alongside sound synthesis concepts. I use the johnny-five JavaScript Robotics framework in many of my workshops at the moment, so the JS library that I'm writing to go along with the hardware modules is building on this framework. Johnny-Five runs on the BBB using Node.js with the beaglebone-io module. This article steps through using beaglebone-io to work with the BBB's GPIO pins from Node.js.

Beaglebone-io requires the BBB to be running Debian so that it can manipulate the device tree at runtime (refer to my previous blog post for how to install Debian on the BBB).

To set up the Node.js project, I first ssh into the BBB and create a project folder, and run npm init to initialise the project from the command line. Then install the johnny-five and beaglebone-io modules using npm (the save param persists these as dependencies in the package.json file)

npm install --save johnny-five 
npm install --save beaglebone-io

We can create a JavaScript program to read and/or write from the GPIO pins on the BBB in the same folder.

Each beaglebone-io JS program looks something like this to begin with:

var five = require('johnny-five'); 
var BeagleBone = require('beaglebone-io'); 
var board = new five.Board({
  io: new BeagleBone()
}); 
board.on('ready', function () {
  // our code goes here
});

We're including the johnny-five and beaglebone-io libraries at the top of the JS program then creating a board object that corresponds to the BBB, making sure to use an instance of the BeagleBone class as the io config option (johnny-five supports a number of different platforms and uses an io instance that communicates with Arduinos running Firmata by default). We attach an event handler so that the code that does the actual work will run as soon as the communication with the board has been established.

The BBB has two sets of 46 pin headers running along both sides. The pins that run along the left hand side (with the ethernet port at the top) are the P9 set and the pins on the right hand side are known as P8. See the beaglebone docs for a diagram of the pins. I'll be using potentiometers and rotary encoders as knobs to control various audio synthesis parameters within hexynth modules. While I'm experimenting, I can connect components directly to GPIO pins on the BBB to program with them directly e.g. a potentiometer on AIN_0 (i.e. pin 39 in the P9 set). With beaglebone-io this pin is identified as "A0" (refer to the pin mapping in the beaglebone-io README).

So, to take a reading from the potentiometer the JavaScript code that goes inside the on ready handler for the board looks like this:

board.on('ready', function () {
var potentiometer = new five.Sensor({
  pin: "A0",
  freq: 250
});
potentiometer.on("data", function() {
  console.log(this.value);
});
});

After saving the program to a file in the project folder, e.g. potentiometer.js, the program can be run using node from the command line:

node potentiometer.js

The hexynth modules will be more complex than single components, and I'll be using secondary ICs on each module to implement their internal logic and then communicate via messages from the BBB. Johnny-five's support for i2c modules like this LED matrix with i2c backpack provides an example of how this might work.

To display a pattern on the matrix we can write high-level JS code:

var pattern = [ "00000000", "01100110", "01100110", "00000000", "01000010", "00111100", "00011000", "00000000" ]; 
var matrix = new five.Led.Matrix({
controller: "HT16K33",
addresses: [0x70],
isBicolor: false
});
matrix.draw(pattern);

Johnny-five's LedMatrix class takes care of establishing the i2c communication and sending messages in the format that the i2c matrix is expecting. Here's a snippet from one of the library functions:

var bytes = [0x00]; 
for (var i = 0; i < 8; i++) {
  bytes.push(this.displaybuffers[addr][i] & 0xFF);
  bytes.push(this.displaybuffers[addr][i] >> 8);
}
this.io.i2cWrite(this.addresses[addr], bytes);

Finally, the i2c backpack takes care of setting specific pins high and low to display the appropriate color LED for each pixel in the matrix to display the pattern in response to messages received.

I hope that the hexynth modules can work in a similar way: For example, the firmware that I run on a hexynth oscillator module should understand messages to set the frequency and shape of the waveform produced, and also understand messages to start and stop producing the sound. This approach also works for reading from input components e.g. instead of connecting a potentiometer directly to the GPIO pins, I'll have a module that has some knobs to represent some specific synth parameters, that module should respond to messages to get the current values of those parameters.

The firmware running on the module can also take care of mapping of and manipulation of the raw values taken from the potentiometer into values that we can use more usefully in code. These kinds of mappings could be written in the JS code, but with the motivation being to make hexynth as beginner-friendly as possible for teaching, I'd prefer if the code that students are writing could remain clean by abstracting those kinds of details away within the firmware running on the modules. This firmware provides an example of how I might implement this - this example runs on an Arduino connected to a HC-SR04 Ultrasonic sensor to provide an i2c proximity sensor that is compatible with johnny-five.