/* TurntableTest3 Julian Leland, E91B Swarthmore College, 2010 This code reads quadrature input and outputs MIDI data formatted to control a turntable in Algoriddim Software's dJay. The encoder is modeled after the Vestax Spin controller, and uses the same MIDI messages. This code operates both turntables and the fader as of 11/17/10. Unfortunately, TurntableTest4 was lost: consequently, this code does not implement the full functionality shown during the presentation of this project. However, it is working code, and all general functions used in the final project presentation (buttons, faders, encoders) are present here. To-do: - Incorporate any changes made in TurntableTest2 post 11/11/10 - Fix scratching logic so it doesn't make that horrible sound */ // Initialize variables for Channel A (left) int chanA = 0x00; // Channel number volatile int encAPos = 0; // Encoder position int curSpeedA = 0; // Current speed const int touchChanA = 12; // Channel for touch sensor int touchA = 0, chgTouchA = 0; // Variables for touch sensor const int jogChanA = 22; // Channel for jog sensor int jogA = 0, chgJogA = 0; // Variables for jog sensor // Initialize variables for Channel B (right) int chanB = 0x01; // These variables work identically to the variables for Channel A volatile int encBPos = 0; int curSpeedB = 0; const int touchChanB = 14; int touchB = 0, chgTouchB = 0; const int jogChanB = 23; int jogB = 0, chgJogB = 0; // Initialize general variables int firstPos = 0, secondPos = 0;// Encoder Position variables - used to calculate speed int voltage = 0; // Voltage measured at crossfader int curFader = 0, prevFader = 0;// Variables used for crossfader smoothing // Setup routine void setup() { Serial.begin(57600); // Start serial, 57600 baud attachInterrupt(5, ChanA1, CHANGE); // Attach interrupts on pins 18-21. This means that these pins attachInterrupt(4, ChanA2, CHANGE); // will detect rising OR falling edges attachInterrupt(3, ChanB1, CHANGE); attachInterrupt(2, ChanB2, CHANGE); } // Main routine // Pointers are used so that the same code can be used with both channels. void loop() { checkSpeed(&encAPos, &curSpeedA); // Check speed on Chan A checkTouch(touchChanA, &touchA, &chgTouchA); // Check whether Platter A is touched checkJog(jogChanA, &jogA, &chgJogA); // Check if jog wheel is active sendMIDINote(chanA, touchA, chgTouchA, jogA, chgJogA, curSpeedA); // Send appropriate MIDI commands checkSpeed(&encBPos, &curSpeedB); // Repeat for Channel B checkTouch(touchChanB, &touchB, &chgTouchB); checkJog(jogChanB, &jogB, &chgJogB); sendMIDINote(chanB, touchB, chgTouchB, jogB, chgJogB, curSpeedB); faderPos(); // Check fader position } void checkSpeed(volatile int *encPos, int *curSpeed) { // Check current speed. Input is encoder position of // channel being checked: output is current speed. int rawSpeed = 0; firstPos = *encPos; // Read encoder position delay(25); // Wait 25 ms secondPos = *encPos; // Read encoder position again rawSpeed = (secondPos - firstPos); // Speed is difference between positions if (rawSpeed > 0) { *curSpeed = map(rawSpeed, 1, 200, 65, 127); // If turning CW, map speed into range from 65 to 127 } // These speed values correspond to the permissible range // of MIDI note values else if (rawSpeed < 0) { *curSpeed = map(rawSpeed, -1, -200, 63, 0); // If turning CCW, map speed into range from 63 to 0 } else *curSpeed = 64; // Otherwise, speed = 64. /* if (analogRead(0) <= 200) { // This is all test code - preserved for later use Serial.print(secondPos); Serial.print(","); Serial.print(firstPos); Serial.print(","); Serial.print(secondTime); Serial.print(","); Serial.print(firstTime); Serial.print(","); Serial.print(dir); Serial.print(","); Serial.println(curSpeed); }*/ } void checkTouch(int readChan, int *touch, int *chgTouch) { // Check touch sensor status. Input is channel // being read, output is state and chgState variables if (analogRead(readChan) <= 200 && *touch == 0) { // The capacitive sensor outputs roughly 2.5 V, so // it is read with an analog pin *touch = 1; // If we weren't touched before, but we are now, change touch = 1 and chgTouch = 1 *chgTouch = 1; } else if(analogRead(readChan) <= 200 && *touch == 1) { *chgTouch = 0; // If we were touched before, and are still touched, change chgTouch = 0, touch still = 1 } else if(analogRead(readChan) > 200 && *touch == 1) { *touch = 0; // If we were touched before but aren't now, change touch = 0, chgTouch = 1 *chgTouch = 1; } else { *chgTouch = 0; // If we were not touched before, and still aren't touched, both variables = 0 } } void checkJog(int jogChan, int *jog, int *chgJog) { // This code works identically to checkTouch, if (digitalRead(jogChan) == HIGH && *jog == 0) { // with the exception that it reads digital *jog = 1; // pins instead of analog pins *chgJog = 1; } else if(digitalRead(jogChan) == HIGH && *jog == 1) { *chgJog = 0; } else if(digitalRead(jogChan) == LOW && *jog == 1) { *jog = 0; *chgJog = 1; } else { *chgJog = 0; } } void sendMIDINote(int chan, int touch, int chgTouch, int jog, int chgJog, int curSpeed) { if (touch == 1 && chgTouch == 1) { // 'Touching' the turntable is accomplished by sending a Note On command Serial.print((0x90 | chan), BYTE); // Note on, MIDI channel selected by chan Serial.print(0x2E, BYTE); // Play Note 0x2E Serial.print(127, BYTE); // Velocity (unused) } if (touch == 0 && chgTouch == 1) { // 'Releasing' the turntable is accomplished by sending a Note Off command Serial.print((0x80 | chan), BYTE); // Note off, MIDI channel selected by chan Serial.print(0x2E, BYTE); // Play Note 0x2E Serial.print(127, BYTE); // Velocity (unused) } if (jog == 1 && chgJog == 1) { // 'Touching' the jog wheel is accomplished by sending a Note On command Serial.print((0x90 | chan), BYTE); // Note on, MIDI channel selected by chan Serial.print(0x2D, BYTE); // Play Note 0x24 Serial.print(127, BYTE); // Velocity (unused) } if (jog == 0 && chgJog == 1) { // 'Releasing' the jog wheel is also accomplished by sending a Note On command Serial.print((0x90 | chan), BYTE); // Note off, MIDI channel selected by chan Serial.print(0x2D, BYTE); // Play Note 0x24 Serial.print(127, BYTE); // Velocity (unused) } if (curSpeed != 64) { // Only send speed data if the turntables are spinning Serial.print((0xB0 | chan), BYTE); // Controller change, MIDI channel selected by chan Serial.print(0x10, BYTE); // Address turntable scratch (coarse pan control in MIDI protocol) Serial.print(curSpeed, BYTE); // Spin speed } } void faderPos() { voltage = analogRead(4); // Read voltage on Pin 4 curFader = map(voltage, 5, 550, 0, 127); // Map voltage from permissible range onto 0 - 127 range Serial.print(0xB2, BYTE); // Controller Change, Channel 3 Serial.print(0x08, BYTE); // Controller 0x08 Serial.print(curFader, BYTE); // Controller position } void ChanA1(){ if (digitalRead(18) == digitalRead(19)) { // When an interrupt is triggered, check the current state of the // two channels ++encAPos; // If they're equal, then rotation is CW } else { --encAPos; // Otherwise, rotation is CCW } } void ChanA2(){ if (digitalRead(18) == digitalRead(19)) { // If an interrupt is triggered on the other channel, check the // current state of the two channels --encAPos; // Since we're considering the other channel, if they're equal, } // then rotation is CCW else { ++encAPos; // Otherwise, rotation is CW } } void ChanB1(){ // Code for Channel B is identical to Channel A code. if (digitalRead(20) == digitalRead(21)) { ++encBPos; } else { --encBPos; } } void ChanB2(){ if (digitalRead(20) == digitalRead(21)) { --encBPos; } else { ++encBPos; } }