Introduction: Ultrasonic Theremin
This Instructable is part 2 in the series of how to create music using the ultrasonic sensor connected to and Arduino and then run through Openframeworks using the audio library ofxMaxim.
Find part 1 here:
https://www.instructables.com/id/Ultrasonic-sensor-...
Hardware
- Arduino
- Ultrasonic sensor (I am using an HC-SR04, can use others)
- Breadboard
- Jumper cables
- USB
Software
- Arduino IDE (https://www.arduino.cc/en/Main/Software)
- New Ping Library for Arduino IDE
- Visual Studios 2015 (Windows 10 and 8 - https://www.visualstudio.com/) or Xcode (Mac - https://www.visualstudio.com/)
- OpenFrameworks installed on one of the above (http://openframeworks.cc/)
- Maximillian/ ofxMaxim (github.com/micknoise/Maximilian) addon for OpenFrameworks
Step 1: Step 1: Setting Up the Arduino
To set up the Arduino you should look at my first Instructable.
You can find it here:
https://www.instructables.com/id/Ultrasonic-sensor-in-openFrameworks-using-Arduino/
Step 2: Step 2: Installing OfxMaxim
The first thing you need to do is to download the ofxMaxim library for openFrameworks from https://github.com/micknoise/Maximilian
Then you download the zip-file or you can use git clone in your terminal to get the ofxMaxim repository or download to desktop.
Then you put the ofxMaxim folder inside your addons folder.
The next step is to create a new openFrameworks project using the project generator and before you create the project you click on the addon button at the bottom and inside that you choose your new addon ofxMaxim.
Then you click on the GENERATE PROJECT button in the lower right corner.
Step 3: Step 3: Code
Download the attached files and copy and past the code into the relevant files. Adding keyClass.cpp and keyClass.h as new files.
ofApp.cpp Code
#include "ofApp.h" //int for storing the byte data from Arduino.
int byteData;
//--------------------------------------------------------------
void ofApp::setup(){
//General setup of look of window.
ofBackground(255);
ofSetColor(0);
//serial port setup. using COM3 for Windows port.
//Also using baud rate 9600, same in Arduino sketch.
sampleRate = 44100; // setting up the sampleRate
bufferSize = 512; // setting up the bufferSize
for( int i = 0; i < n.size(); i ++) { // looping through the vector of notes
keys.emplace_back( new Key(n[i])); // because I'm using a uniqe_ptr I call emplace_back instead of push_back
}
ofSoundStreamSetup(2, 0, this, sampleRate, bufferSize, 4); // here I'm setting up the connection with the sound card using ofSoundStreamSetup
serial.setup("/dev/cu.usbmodem1421", 9600); // the port number will be different on every device and this is running on a Mac and that's why the port number is different. Make sure that the port number is correct before you run the code
}
//--------------------------------------------------------------
void ofApp::update(){
//Simple if statement to inform user if Arduino is sending serial messages.
if (serial.available() < 0) {
msg = "Arduino Error";
}
else {
//While statement looping through serial messages when serial is being provided.
while (serial.available() > 0) {
//byte data is being writen into byteData as int.
byteData = serial.readByte();
//byteData is converted into a string for drawing later.
msg = "cm: " + ofToString(byteData);
}
}
}
//--------------------------------------------------------------
void ofApp::draw(){
//drawing the string version pf byteData on oF window.
ofDrawBitmapString(msg, 50, 100);
//printing byteData into console.
cout << byteData << endl;
// These if statements checks the specific distance from the ultrasonic sensor and so depending in the distance a different note will be triggered
if(byteData >= 1 && byteData <= 2 ){
keys[0]->on();
}else{
keys[0]->off();
}
if(byteData >= 3 && byteData <= 4){
keys[1]->on();
}else{
keys[1]->off(); }
if(byteData >= 5 && byteData <= 6){
keys[2]->on();
}else{
keys[2]->off();
}
if(byteData >= 7 && byteData <= 8){
keys[3]->on();
}else{
keys[3]->off();
}
if(byteData >= 9 && byteData <= 10){
keys[4]->on();
}else{
keys[4]->off();
}
if(byteData >= 11 && byteData <= 12){
keys[5]->on();
}else{
keys[5]->off();
}
if(byteData >= 13 && byteData <= 14){
keys[6]->on();
}else{
keys[6]->off();
}
if(byteData >= 15 && byteData <= 16){
keys[7]->on();
}else{
keys[7]->off();
}
}
void ofApp::audioOut( float * output, int bufferSize, int nChannels){
for(int i = 0; i < bufferSize; i ++) { // you need to loop through the bufferSize
for(int j = 0; j < n.size(); j ++) { // looping through the vector of notes
notes += keys[j]->play() / n.size();
}
myMix.stereo(notes, outputs, 0.5);
output[i * nChannels] = outputs[0];
output[i * nChannels + 1] = outputs[1];
}
}
<strong>ofApp.h</strong>
#pragma once<br>
#include "ofMain.h"
#include "ofxMaxim.h"
#include "keyClass.h"
class ofApp : public ofBaseApp{
public:
//Standard oF functions.
void setup();
void update();
void draw();
void audioOut( float * ouput, int bufferSize, int nChannels);
int bufferSize, sampleRate;
double notes;
double outputs[ 2 ];
//array of frequencies
vector n {261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25 };
vector> keys;
maxiMix myMix;
//Custom variables for on screen string and font.
string msg;
//ofTrueTypeFont font;
//New serial object.
ofSerial serial;
};
keyClass.cpp Code
#include "keyClass.h"<br>
Key::Key(double _f) {
freq = _f;
env.setAttack(1000);
env.setDecay(1);
env.setSustain(1);
env.setRelease(1000);
}
void Key::on() {
env.trigger = 1;
}
void Key::off() {
env.trigger = 0;
}
double Key:: play(){
return env.adsr(osc.sinewave(freq), env.trigger);
}
keyClass.h Code
#ifndef __keyboardofxMaxim__keyClass__<br> #define __keyboardofxMaxim__keyClass__
#include
#include "ofxMaxim.h"
class Key {
public:
maxiOsc osc;
maxiEnv env;
double freq;
Key( double _f);
double play();
void on();
void off();
};
#endif /* defined(__keyboardofxMaxim__keyClass__) */
Step 4: Step 4: Let's Play Some Music!
In the video you can see us playing with a C scale!
Have fun!!