Created
December 19, 2019 09:02
-
-
Save Electronza/fdde7d354992c6f87d881d2d5cf69c8b to your computer and use it in GitHub Desktop.
Flip and Click "Theremin"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************* | |
____ __ ____ ___ ____ ____ __ __ _ ____ __ | |
( __)( ) ( __)/ __)(_ _)( _ \ / \ ( ( \(__ ) / _\ | |
) _) / (_/\ ) _)( (__ )( ) /( 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