Skip to content

Instantly share code, notes, and snippets.

@Electronza
Created December 19, 2019 09:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Electronza/fdde7d354992c6f87d881d2d5cf69c8b to your computer and use it in GitHub Desktop.
Save Electronza/fdde7d354992c6f87d881d2d5cf69c8b to your computer and use it in GitHub Desktop.
Flip and Click "Theremin"
/*******************************************************************
____ __ ____ ___ ____ ____ __ __ _ ____ __
( __)( ) ( __)/ __)(_ _)( _ \ / \ ( ( \(__ ) / _\
) _) / (_/\ ) _)( (__ )( ) /( O )/ / / _/ / \
(____)\____/(____)\___) (__) (__\_) \__/ \_)__)(____)\_/\_/
Project name: Arduino Due / Flip and Click "Theremin"
Project page: https://electronza.com/flip-click-theremin-arduino-due/
Description: Theremin with Sharp distance sensors and MIDI
********************************************************************/
#include <SPI.h>
// Pin definitions for Flip and Click socket A
#define VS_XCS 77 // Control Chip Select Pin (for accessing SPI Control/Status registers)
#define VS_XDCS 26 // Data Chip Select / BSYNC Pin
#define VS_DREQ 54 // Data Request Pin: Player asks for more data
#define VS_RESET 33 //Reset is active low de vazut???
// some calibration constants for the distance sensors
# define VOLUME_MIN_RANGE 150
# define VOLUME_MAX_RANGE 800
# define NOTE_MIN_RANGE 150
# define NOTE_MAX_RANGE 800
// instrument "gamma"
# define NOTE_MIN 40
# define NOTE_MAX 120
// min and max volume
# define VOL_MIN 30
# define VOL_MAX 90
int instrument = 42; // viola
int oldnote = 30;
int newnote;
int oldvolume = 0;
int newvolume;
int tmp;
//Write to VS10xx register
//SCI: Data transfers are always 16bit. When a new SCI operation comes in
//DREQ goes low. We then have to wait for DREQ to go high again.
//XCS should be low for the full duration of operation.
void VSWriteRegister(unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte) {
while (!digitalRead(VS_DREQ)) ; //Wait for DREQ to go high indicating IC is available
digitalWrite(VS_XCS, LOW); //Select control
//SCI consists of instruction byte, address byte, and 16-bit data word.
SPI.transfer(0x02); //Write instruction
SPI.transfer(addressbyte);
SPI.transfer(highbyte);
SPI.transfer(lowbyte);
while (!digitalRead(VS_DREQ)) ; //Wait for DREQ to go high indicating command is complete
digitalWrite(VS_XCS, HIGH); //Deselect Control
}
//
// Plugin to put VS10XX into realtime MIDI mode
// Fore more plugins visit http://www.vlsi.fi/en/support/software/vs10xxplugins.html
//
const unsigned short sVS1053b_Realtime_MIDI_Plugin[28] = { /* Compressed plugin */
0x0007, 0x0001, 0x8050, 0x0006, 0x0014, 0x0030, 0x0715, 0xb080, /* 0 */
0x3400, 0x0007, 0x9255, 0x3d00, 0x0024, 0x0030, 0x0295, 0x6890, /* 8 */
0x3400, 0x0030, 0x0495, 0x3d00, 0x0024, 0x2908, 0x4d40, 0x0030, /* 10 */
0x0200, 0x000a, 0x0001, 0x0050,
};
void VSLoadUserCode(void) {
int i = 0;
while (i < sizeof(sVS1053b_Realtime_MIDI_Plugin) / sizeof(sVS1053b_Realtime_MIDI_Plugin[0])) {
unsigned short addr, n, val;
addr = sVS1053b_Realtime_MIDI_Plugin[i++];
n = sVS1053b_Realtime_MIDI_Plugin[i++];
while (n--) {
val = sVS1053b_Realtime_MIDI_Plugin[i++];
VSWriteRegister(addr, val >> 8, val & 0xFF);
}
}
}
void setup() {
pinMode(VS_DREQ, INPUT);
pinMode(VS_XCS, OUTPUT);
pinMode(VS_XDCS, OUTPUT);
digitalWrite(VS_XCS, HIGH); //Deselect Control
digitalWrite(VS_XDCS, HIGH); //Deselect Data
pinMode(VS_RESET, OUTPUT);
//Initialize VS1053 chip
digitalWrite(VS_RESET, LOW); //Put VS1053 into hardware reset
// Setup SPI for VS1053
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
//From page 12 of datasheet, max SCI reads are CLKI/7. Input clock is 12.288MHz.
//Internal clock multiplier is 1.0x after power up.
//Therefore, max SPI speed is 1.75MHz. We will use 1MHz to be safe.
SPI.setClockDivider(SPI_CLOCK_DIV16);
SPI.transfer(0xFF); //Throw a dummy byte at the bus
delayMicroseconds(20); // just allow for some time to pass
digitalWrite(VS_RESET, HIGH); //Bring up VS1053
// Load the realtime MIDI plugin
VSLoadUserCode();
delay(50);
// Set instrument bank
talkMIDI(0xB0, 0, 0x00); //Default bank GM1
// Set the instrument
talkMIDI(0xC0, instrument, 0); //Set instrument number. 0xC0 is a 1 data byte command
// Only for debugging
//Serial.begin(9600); //Use serial for debugging
}
void sendMIDI(byte data)
{
SPI.transfer(0);
SPI.transfer(data);
}
//Plays a MIDI note. Doesn't check to see that cmd is greater than 127, or that data values are less than 127
void talkMIDI(byte cmd, byte data1, byte data2) {
// Wait for chip to be ready (Unlikely to be an issue with real time MIDI)
while (!digitalRead(VS_DREQ));
digitalWrite(VS_XDCS, LOW);
sendMIDI(cmd);
//Some commands only have one data byte. All cmds less than 0xBn have 2 data bytes
//(sort of: http://253.ccarh.org/handout/midiprotocol/)
if ( (cmd & 0xF0) <= 0xB0 || (cmd & 0xF0) >= 0xE0) {
sendMIDI(data1);
sendMIDI(data2);
} else {
sendMIDI(data1);
}
}
//Send a MIDI note-on message. Like pressing a piano key
//channel ranges from 0-15
void noteOn(byte channel, byte note, byte attack_velocity) {
talkMIDI( (0x90 | channel), note, attack_velocity);
//talkMIDI( (0xA0 | channel), note, 127);
}
//Send a MIDI note-off message. Like releasing a piano key
void noteOff(byte channel, byte note, byte release_velocity) {
talkMIDI( (0x80 | channel), note, release_velocity);
}
void loop() {
// Update note and volume
newvolume = 0;
for (int index = 0; index < 5; index++) {
tmp = analogRead(A2);
newvolume = newvolume + tmp;
}
newvolume = newvolume / 4;
//cutting down the extreme ranges
if (newvolume < VOLUME_MIN_RANGE) {
newvolume = VOLUME_MIN_RANGE;
}
if (newvolume > VOLUME_MAX_RANGE) {
newvolume = VOLUME_MAX_RANGE;
}
// same for the note
newnote = 0;
for (int index = 0; index < 5; index++) {
tmp = analogRead(A1);
newnote = newnote + tmp;
}
newnote = newnote / 4;
//cutting down the extreme ranges
if (newnote < NOTE_MIN_RANGE) {
newnote = NOTE_MIN_RANGE;
}
if (newnote > NOTE_MAX_RANGE) {
newnote = NOTE_MAX_RANGE;
}
// remapping
newnote = map(newnote, NOTE_MIN_RANGE, NOTE_MAX_RANGE, NOTE_MIN, NOTE_MAX);
newvolume = map(newvolume, VOLUME_MIN_RANGE, VOLUME_MAX_RANGE, VOL_MIN, VOL_MAX);
if ((newnote == NOTE_MIN) && (newvolume == VOL_MIN)) {
// no hands in front of the sensors
// turn it off
talkMIDI(0xB0, 0x07, 0); // set volume to 0
noteOff(0, oldnote, 0);
}
// It looks that someone is playing the Theremin
// set the volume
talkMIDI(0xB0, 0x07, newvolume); // set volume
// play the note
// the notes are supposed to overlap a little bit to enhance the "theremin feeling"
if (newnote != oldnote) {
noteOn(0, newnote, 127);
delay(100); // the higher the delay the more overlapping, but the slower the pitch change tempo
//if the delay is too small the VS1053 will hang
noteOff(0, oldnote, 0);
oldnote = newnote;
}
// else we just play the oldnote
// Just for debugging
//Serial.print(newvolume);
//Serial.print (" ");
//Serial.println (newnote);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment