OSC-CV Converter

Dependencies:   Bonjour OSCReceiver TextLCD mbed mbed-rpc BurstSPI DebouncedInterrupt FastIO MIDI OSC OSCtoCV ClockControl

OSC to CV Converter

http://gtbts.tumblr.com/post/125663817741/osc-to-cv-converter-ver2-mbed-osctocv

/media/uploads/casiotone401/tumblr_nsg7y4pkfg1qlle9fo1_540.png

main.cpp

Committer:
casiotone401
Date:
2015-08-07
Revision:
16:1196b8c87bb7
Parent:
15:3e4bc47d6a39
Child:
17:55e5136790a6

File content as of revision 16:1196b8c87bb7:

//-------------------------------------------------------------
//                  OSCtoCV Converter
//   Schematic, touchOSC template & VST Plug-in 
//   http://gtbts.tumblr.com/post/125663817741/osc-to-cv-converter-ver2-mbed-osctocv
//
//   DAC8568 16bit Octal DAC http://www.ti.com/product/dac8568
//
//   referred to
//   xshige's OSCReceiver
//   http://mbed.org/users/xshige/programs/OSCReceiver/
//   radiojunkbox's OSC-CV_Example
//   http://mbed.org/users/radiojunkbox/code/KAMUI_OSC-CV_Example/
//   Robin Price's Homebrew midi-cv box
//   http://crx091081gb.net/?p=69
//   Masahiro Hattori's TextLCD Module Functions
//   http://www.eleclabo.com/denshi/device/lcd1602/gcram.html
//   Dirk-Willem van Gulik's BonjourLib
//   http://mbed.org/users/dirkx/code/BonjourLib/file/bb6472f455e8/services/mDNS
//   ADDAC System & sneak-thief's Euclidean Polyrhythm generator
//   https://github.com/addacsystem/ADDAC-Library
//   https://www.muffwiggler.com/forum/viewtopic.php?p=1451228#1451228
//
// Released under the MIT License: http://mbed.org/license/mit
//-------------------------------------------------------------

#pragma O3
#pragma Otime

#include "mbed.h"
#include "FastIO.h"
#include "DebouncedInterrupt.h"
#include "BurstSPI.h"
#include "TextLCD.h"       //edit "writeCommand" "writeData" protected -> public
#include "EthernetNetIf.h"
#include "HTTPServer.h"
#include "mDNSResponder.h" // mDNS response to announce oneselve
#include "UDPSocket.h"
#include "OSCReceiver.h"
#include "mbedOSC.h"
#include "MIDI.h"
#include <stdlib.h>
#include <ctype.h>
#include <math.h>

//-------------------------------------------------------------
// Define

#define MODE_Calb           0        // Calibration (for VCO Tuning)
#define MODE_OSC            1        // Mode OSCtoCV 
#define MODE_SEQ            2        // Mode Shift Sequencer
#define MODE_185            3        // Mode M185 Sequencer
#define MODE_EUC            4        // Mode Euclidean Sequencer 

#define MODE_TOTAL          5        // Modes

#define Lin                 0        // Linear LinearCV
#define Chr                 1        // Chromatic
#define Maj                 2        // Major
#define M7                  3        // Major7
#define Min7                4        // Minor7
#define Dor                 5        // Dorian
#define Min                 6        // Minor
#define S5th                7        // 5th
#define Wht                 8        // Wholetone

#define SCALE_NUM           9        // Count Scale
#define SCALE_AOUT          (65535 / SCALE_NUM - 1)

#define QUAN_RES1           116      // Quantize voltage Steps
#define QUAN_RES2           68
#define QUAN_RES3           46
#define QUAN_RES4           40
#define QUAN_RES5           68
#define QUAN_RES6           68  
#define QUAN_RES7           16
#define QUAN_RES8           58

#define SPI_RATE            20000000 // 10Mbps SPI Clock
#define SCALING_N           32256.0f
#define INPUT_PORT          12345    // Input Port Number

#define POLLING_INTERVAL    20       // Polling Interval (us)

//-------------------------------------------------------------
// DAC8568 Control Bits (See datasheet)

#define WRITE               0x00
#define UPDATE              0x01
#define WRITE_UPDATE_ALL    0x02     // LDAC Write to Selected Update All
#define WRITE_UPDATE_N      0x03     // LDAC Write to Selected Update Respective
#define POWER               0x04
#define CLR                 0x05     // Clear Code Register
#define WRITE_LDAC_REG      0x06
#define RESET               0x07     // Software Reset DAC8568
#define SETUP_INTERNAL_REF  0x08

//------------------------------------------------------------- 
// Gate Sequencer Macros

#define _DISABLE            0
#define _ENABLE             1

#define GATE1               0
#define GATE2               1
#define GATE3               2
#define GATE4               3
#define SUBGATE             4
#define GATE_TOTAL          5

#define INVERT              1
#define NON_INVERT          0

#define GATESOUT_ON         0
#define GATESOUT_OFF        1

#define SYNC_ON             0
#define SYNC_OFF            1

//------------------------------------------------------------- 
// Beats (Note values)

#define N1ST                1   // whole
#define N2ND                2   // harf
#define N4TH                4   // quarter
#define N8TH                8
#define N16TH              16
#define N32TH              32
#define N64TH              64
#define NDOT2               3   // dotted
#define NDOT4               7
#define NDOT8               9
#define NDOT16             11
#define NDOT32             13
#define TRIP2               3   // triplets
#define TRIP4               6
#define TRIP8              12
#define TRIP16             24
#define TRIP32             48
#define SYNC24             96
#define NRESET              0   // Gate Reset

//------------------------------------------------------------- 
// Sequencer Macros

#define STEP_INDICATOR_ADDRESS "/seqstep/"   // touchOSC multi toggle(1x16(8)) for Current Step Indicator
#define RESET_COUNTER_ADDRESS  "/reset"      // touchOSC label for Sequencer reset count                 

//------------------------------------------------------------- 
// M185 Macros

#define PULSE_COUNT_ADDRESS    "/pulse"      // /pulse1 ~ pulse8        M185 Pulse Count
#define GATE_MODE_ADDRESS      "/gatemode"   // /gatemode1 ~ gatemode8  M185 Gate Mode

#define SINGLE              0
#define MUTE                1
#define MULTI               2
#define HOLD                3

//------------------------------------------------------------- 
// Euclidean Sequencer Macros

#define READ_DELAY         10 // for debouncing 
#define MAXCHANNELS         4
#define MAXSTEPS           16 // max step length
#define TRIGGER_DURATION 2200

#define DISPLAY_UPDATE   2000 // how long active channel display is shown
#define MATRIX_ADDRESS   "/matrix/" // touchOSC multi toggle(9x16) OSC address


//-------------------------------------------------------------
// Functions

void InitOSCCV(void);
inline void NetPoll(void);
inline float MapFloat(float, float, float, float, float);
inline void UpdateCV(int, int, const unsigned int*);
inline void CalibrationCV(void);
inline void SetCV(void);
inline void ShiftCVSeq(int, bool);
inline void M185Seq(int, bool);
inline void SendCtrlState(uint8_t, uint8_t, uint8_t);
inline int GateSeq(int, int, int, int, bool, bool, bool);
inline int CheckBPM(void);
inline void CheckModeSW(void);
inline void LCD();
inline void UpdateCVMeter(int, const unsigned int*);
void WriteCustomChar(unsigned char, unsigned char*);
int  SetupEthNetIf(void);
inline size_t strlength(const char *);
inline void onUDPSocketEvent(UDPSocketEvent);
void EuclideanSeq(int, bool, bool);
unsigned int Euclid(int, int, int);
inline int BitRead(uint16_t, int);
uint16_t BitReadOffset(int, uint16_t, uint16_t); 
unsigned int ConcatBin(unsigned int, unsigned int);
void Sync(int, bool);
int EncodeReadN(int);
int EncodeReadK(int);
int EncodeReadO(int);
inline char * SetMatrixAddress(int, int, bool);

//-------------------------------------------------------------
// Silentway Calibration Data Mapping
// http://www.expert-sleepers.co.uk/silentway.html

//  Chromatic Scale
const float calibMap1[QUAN_RES1] = {
0.00076928,   0.00900736,   0.01724544,   0.02548352,   0.03372160,
0.04195968,   0.05019776,   0.05843584,   0.06667392,   0.07491200,
0.08315008,   0.09138816,   0.09962624,   0.10786432,   0.11610240,
0.12434047,   0.13258974,   0.14083999,   0.14909023,   0.15734047,
0.16559070,   0.17384095,   0.18209119,   0.19034143,   0.19859168,
0.20684192,   0.21509215,   0.22334240,   0.23159264,   0.23984288,
0.24809311,   0.25634655,   0.26460093,   0.27285531,   0.28110969,
0.28936407,   0.29761845,   0.30587283,   0.31412721,   0.32238159,
0.33063596,   0.33889034,   0.34714472,   0.35539910,   0.36365348,
0.37190786,   0.38017464,   0.38844886,   0.39672306,   0.40499726,
0.41327149,   0.42154568,   0.42981988,   0.43809411,   0.44636831,
0.45464250,   0.46291673,   0.47119093,   0.47946513,   0.48773935,
0.49601355,   0.50430328,   0.51260746,   0.52091163,   0.52921581,
0.53751999,   0.54582411,   0.55412829,   0.56243247,   0.57073665,
0.57904083,   0.58734500,   0.59564912,   0.60395330,   0.61225748,
0.62056166,   0.62890279,   0.63728637,   0.64566994,   0.65405351,
0.66243708,   0.67082065,   0.67920423,   0.68758780,   0.69597137,
0.70435494,   0.71273851,   0.72112209,   0.72950566,   0.73788923,
0.74627280,   0.75476575,   0.76334614,   0.77192658,   0.78050703,
0.78908741,   0.79766786,   0.80624831,   0.81482869,   0.82340914,
0.83198959,   0.84056997,   0.84915042,   0.85773087,   0.86631125,
0.87489170,   0.88425636,   0.89363104,   0.90300572,   0.91238040,
0.92175508,   0.93112975,   0.94050443,   0.94987911,   0.95925385,
0.96862853
};

//  Major Scale
const float calibMap2[QUAN_RES2] = {
calibMap1[0], calibMap1[2], calibMap1[4], calibMap1[5], calibMap1[7],
calibMap1[9], calibMap1[11], calibMap1[12], calibMap1[14], calibMap1[16],
calibMap1[17], calibMap1[19], calibMap1[21], calibMap1[23], calibMap1[24],
calibMap1[26], calibMap1[28], calibMap1[29], calibMap1[31], calibMap1[33],
calibMap1[35], calibMap1[36], calibMap1[38], calibMap1[40], calibMap1[41],
calibMap1[43], calibMap1[45], calibMap1[47], calibMap1[48], calibMap1[50],
calibMap1[52], calibMap1[53], calibMap1[55], calibMap1[57], calibMap1[59],
calibMap1[60], calibMap1[62], calibMap1[64], calibMap1[65], calibMap1[67],
calibMap1[69], calibMap1[71], calibMap1[72], calibMap1[74], calibMap1[76],
calibMap1[77], calibMap1[79], calibMap1[81], calibMap1[83], calibMap1[84],
calibMap1[86], calibMap1[88], calibMap1[89], calibMap1[91], calibMap1[93],
calibMap1[95], calibMap1[96], calibMap1[98], calibMap1[100], calibMap1[101],
calibMap1[103], calibMap1[105], calibMap1[107], calibMap1[108], calibMap1[110],
calibMap1[112], calibMap1[113], calibMap1[115]
};

//  M7(9)
const float calibMap3[QUAN_RES3] = {
calibMap1[0],  calibMap1[4], calibMap1[7], calibMap1[11], calibMap1[12], 
calibMap1[14], calibMap1[16], calibMap1[19], calibMap1[23], calibMap1[24],
calibMap1[26], calibMap1[28], calibMap1[31], calibMap1[35], calibMap1[36], 
calibMap1[38], calibMap1[40], calibMap1[43], calibMap1[47], calibMap1[48], 
calibMap1[50], calibMap1[52], calibMap1[55], calibMap1[59], calibMap1[60], 
calibMap1[62], calibMap1[64], calibMap1[67], calibMap1[71], calibMap1[72], 
calibMap1[76], calibMap1[79], calibMap1[83], calibMap1[84], calibMap1[86], 
calibMap1[88], calibMap1[91], calibMap1[95], calibMap1[96], calibMap1[100],
calibMap1[103], calibMap1[107], calibMap1[108], calibMap1[110], calibMap1[112], 
calibMap1[115]
};

//  m7(9)
const float calibMap4[QUAN_RES4] = {
calibMap1[0], calibMap1[3], calibMap1[7], calibMap1[10], calibMap1[12], 
calibMap1[15], calibMap1[19], calibMap1[22], calibMap1[26], calibMap1[27], 
calibMap1[31], calibMap1[34], calibMap1[36], calibMap1[38], calibMap1[39], 
calibMap1[43], calibMap1[46], calibMap1[50], calibMap1[53], calibMap1[55], 
calibMap1[58], calibMap1[60], calibMap1[63], calibMap1[67], calibMap1[70], 
calibMap1[72], calibMap1[74], calibMap1[75], calibMap1[79], calibMap1[82], 
calibMap1[86], calibMap1[89], calibMap1[91], calibMap1[94], calibMap1[96], 
calibMap1[99],  calibMap1[103], calibMap1[106], calibMap1[110], calibMap1[113]
};

//  Dorian Scale
const float calibMap5[QUAN_RES5] = {
calibMap1[0], calibMap1[2], calibMap1[3], calibMap1[5], calibMap1[7],
calibMap1[9], calibMap1[10], calibMap1[12], calibMap1[14], calibMap1[15],
calibMap1[17], calibMap1[19], calibMap1[20], calibMap1[21], calibMap1[24],
calibMap1[26], calibMap1[27], calibMap1[29], calibMap1[31], calibMap1[33],
calibMap1[34], calibMap1[36], calibMap1[38], calibMap1[39], calibMap1[41],
calibMap1[43], calibMap1[45], calibMap1[46], calibMap1[48], calibMap1[50],
calibMap1[51], calibMap1[53], calibMap1[55], calibMap1[57], calibMap1[58],
calibMap1[60], calibMap1[62], calibMap1[63], calibMap1[65], calibMap1[67],
calibMap1[69], calibMap1[70], calibMap1[72], calibMap1[74], calibMap1[75],
calibMap1[77], calibMap1[79], calibMap1[81], calibMap1[82], calibMap1[84],
calibMap1[86], calibMap1[87], calibMap1[89], calibMap1[91], calibMap1[93],
calibMap1[94], calibMap1[96], calibMap1[98], calibMap1[99], calibMap1[101],
calibMap1[103], calibMap1[105], calibMap1[106], calibMap1[108], calibMap1[110],
calibMap1[111], calibMap1[113], calibMap1[115]
};

//  Minor Scale
const float calibMap6[QUAN_RES6] = {
calibMap1[0], calibMap1[2], calibMap1[3], calibMap1[5], calibMap1[7],
calibMap1[8], calibMap1[10], calibMap1[12], calibMap1[14], calibMap1[15],
calibMap1[17], calibMap1[19], calibMap1[20], calibMap1[22], calibMap1[24],
calibMap1[26], calibMap1[27], calibMap1[29], calibMap1[31], calibMap1[32],
calibMap1[34], calibMap1[36], calibMap1[38], calibMap1[39], calibMap1[41],
calibMap1[43], calibMap1[44], calibMap1[46], calibMap1[48], calibMap1[50],
calibMap1[51], calibMap1[53], calibMap1[55], calibMap1[56], calibMap1[58],
calibMap1[60], calibMap1[62], calibMap1[63], calibMap1[65], calibMap1[67],
calibMap1[68], calibMap1[70], calibMap1[72], calibMap1[74], calibMap1[75],
calibMap1[77], calibMap1[79], calibMap1[80], calibMap1[82], calibMap1[84],
calibMap1[86], calibMap1[87], calibMap1[89], calibMap1[91], calibMap1[92],
calibMap1[94], calibMap1[96], calibMap1[98], calibMap1[99], calibMap1[101],
calibMap1[103], calibMap1[104], calibMap1[106], calibMap1[108], calibMap1[110],
calibMap1[111], calibMap1[113], calibMap1[115]
};

//  5th
const float calibMap7[QUAN_RES7] = {
calibMap1[0], calibMap1[7], calibMap1[14], calibMap1[21], calibMap1[28],
calibMap1[35], calibMap1[42], calibMap1[49], calibMap1[56], calibMap1[63],
calibMap1[70], calibMap1[77], calibMap1[84], calibMap1[91], calibMap1[98],
calibMap1[105]
};

//  Whole tone
const float calibMap8[QUAN_RES8] = {
calibMap1[0], calibMap1[1], calibMap1[2], calibMap1[6], calibMap1[8],
calibMap1[10], calibMap1[12], calibMap1[14], calibMap1[16], calibMap1[18],
calibMap1[20], calibMap1[22], calibMap1[24], calibMap1[26], calibMap1[28],
calibMap1[30], calibMap1[32], calibMap1[34], calibMap1[36], calibMap1[38],
calibMap1[40], calibMap1[42], calibMap1[44], calibMap1[46], calibMap1[48],
calibMap1[50], calibMap1[52], calibMap1[54], calibMap1[56], calibMap1[58],
calibMap1[60], calibMap1[62], calibMap1[64], calibMap1[66], calibMap1[68],
calibMap1[70], calibMap1[72], calibMap1[74], calibMap1[76], calibMap1[78],
calibMap1[80], calibMap1[82], calibMap1[84], calibMap1[86], calibMap1[88],
calibMap1[90], calibMap1[92], calibMap1[94], calibMap1[96], calibMap1[98],
calibMap1[100], calibMap1[102], calibMap1[104], calibMap1[106], calibMap1[108],
calibMap1[110], calibMap1[112], calibMap1[114]
};

//-------------------------------------------------------------
// CV Meter Custom Character

unsigned char str1[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F};
unsigned char str2[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x1F};
unsigned char str3[8] = {0x00,0x00,0x00,0x00,0x00,0x1F,0x1F,0x1F};
unsigned char str4[8] = {0x00,0x00,0x00,0x00,0x1F,0x1F,0x1F,0x1F};
unsigned char str5[8] = {0x00,0x00,0x00,0x1F,0x1F,0x1F,0x1F,0x1F};
unsigned char str6[8] = {0x00,0x00,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F};
unsigned char str7[8] = {0x00,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F};
unsigned char str8[8] = {0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F};

//-------------------------------------------------------------
// Global Variables

float   gOSC_cv[8];
float   gSeq_cv[16];
float   gGlide;
int     gMode;

// Variables for Control
/*
gCtrl[0] /ctrl1 BPM
gCtrl[1] /ctrl2 Quantize mode
gCtrl[3] /ctrl4 Glide
gCtrl[4] /ctrl5 M185 Reset Count

gCtrlSW[0] /ctrlsw1 Sequencer STOP
gCtrlSW[1] /ctrlsw2 Euclidean Sequencer reset
gCtrlSW[2] /ctrlsw3 Sequencer Loop
gCtrlSW[3] /ctrlsw4 Euclid Seq ON

float gPulseCount[8] = {0};  M185 Pulse Count
float gGateMode[8] = {0};    M185 Gate Mode
float gSlide[8];             M185 Slide

gEucA[0] /euca1 Euclidean Pattern length  (n) ch1
gEucA[1] /euca2 Euclidean Pattern density (k) ch1
gEucA[2] /euca3 Euclidean Pattern offset  (o) ch1
gEucA[3] ~ [5] /euca4 ~ /euca6 Euclidean Pattern nko ch2
gEucB[0] ~ [5] /eucb1 ~ /eucb6 Euclidean Pattern nko ch3 ~ ch4
*/

float gCtrl[8];
bool  gCtrlSW[8] = {false};

// Variables for Sequencer
float gPulseCount[8] = {0};
float gGateMode[16] = {0};
float gSlide[16];

// Euclidean SEQ Variables
float gEucA[6], gEucB[6];
int channels = MAXCHANNELS;
unsigned int beat_holder[MAXCHANNELS];
unsigned int channelbeats[MAXCHANNELS][5];

bool pulses_active = false; // is active while a beat pulse is playing 
bool lights_active = false;
int pulse_length = TRIGGER_DURATION; //pulse length

unsigned int last_read[MAXCHANNELS];
unsigned int last_changed[MAXCHANNELS];
unsigned int last_sync;

unsigned int euc_time;

// Variables for Arduino
uint16_t gArdCV[4];
uint16_t gArdCtrl[4];
bool gArdSW[4] = {false};

//-------------------------------------------------------------
// mbed Functions

TextLCD      gLCD(p9, p10, p11, p12, p13, p14); // rs, e, d4-d7

BurstSPI     gSPI(p5,p6,p7);    // SPI (p6 unconnected)

FastOut<p15> gSYNCMODE;         // SYNC DAC8568
FastOut<p16> gLDAC;             // LDAC DAC8568

DigitalOut   gGATES[4] = {p21, p22, p23, p24};   // GateOut
FastOut<p19> gSUBGATE;                           // SubGateOut
FastOut<p25> gCLOCKOUT;                          // ClockOut

AnalogOut    gAOUT(p18);

AnalogIn gAIN(p17);
DebouncedInterrupt  gSW(p30);   // Mode SW

// Serial for Arduino
MIDI midi(p28, p27);

Timer        gTimer;     // Timer
Ticker       gPoller;    // Ticker for Polling

// Ethernet
EthernetNetIf   gEth;
/* static ip
EthernetNetIf   gEth(
                     IpAddr(192,168,1,2),
                     IpAddr(255,255,255,0),
                     IpAddr(192,168,1,1),
                     IpAddr(192,168,1,1)
                    );
*/
                     
UDPSocket       gUdp;

// touchOSC Address
uint8_t touchOSCAddress[] = { 192, 168, 1, 7 };
int touchOSCPort = 9000;

OSCClass osc;
OSCMessage sendMes;

//-------------------------------------------------------------
// main
 
int main() 
{ 
    float pot, _pot;
    int bpm;
    
//Clock Up --------------------------------------------------------------------
    LPC_SC->PLL0CON   = 0x00;             /* PLL0 Disable                    */
    LPC_SC->PLL0FEED  = 0xAA;
    LPC_SC->PLL0FEED  = 0x55;
 
    LPC_SC->CCLKCFG   = 0x00000003;       /* Select Clock Divisor = 4        */
    LPC_SC->PLL0CFG   = 0x00020038;       /* configure PLL0                  */
    LPC_SC->PLL0FEED  = 0xAA;             /* divide by 3 then multiply by 50 */
    LPC_SC->PLL0FEED  = 0x55;             /* PLL0 frequency = 400,000,000    */
 
    LPC_SC->PLL0CON   = 0x01;             /* PLL0 Enable                     */
    LPC_SC->PLL0FEED  = 0xAA;
    LPC_SC->PLL0FEED  = 0x55;
    while (!(LPC_SC->PLL0STAT & (1<<26)));/* Wait for PLOCK0                 */
 
    LPC_SC->PLL0CON   = 0x03;             /* PLL0 Enable & Connect           */
    LPC_SC->PLL0FEED  = 0xAA;
    LPC_SC->PLL0FEED  = 0x55;
    while (!(LPC_SC->PLL0STAT & ((1<<25) | (1<<24))));/* Wait for PLLC0_STAT & PLLE0_STAT */
    
    SystemCoreClockUpdate();
//-----------------------------------------------------------------------------
        
    if (SetupEthNetIf() == -1)
    {
        for (int i = 0; i < 4; ++i) 
        {
            gGATES[i] = true;
            wait(0.25);
        }
        
        return -1;
    }
    
    InitOSCCV();
    
    gCtrl[3] = _pot = pot = gMode = 0;
    gGlide = gAIN.read();
        
    LCD();
    gLCD.locate( 0, 1 );
    gLCD.printf("12345678 G>>%3.2f", gGlide);
    
// Main loop
    while (1) 
    {   
        LCD(); // Check Text LCD
        
        pot = gAIN.read();  // Update glide value
        
        if (!pot) // when glide pot value == 0 
        {         // use gCtrl[3] value
            if (abs(gCtrl[3] - _pot) > 0.01f)
            {
                _pot = gGlide = gCtrl[3];
            
                gLCD.locate( 9, 1 );
                gLCD.printf("G>>%3.2f", gGlide);
            }
        
        } else if (abs(pot - _pot) > 0.01f) {
            
            _pot = gGlide =  gAIN.read();
            
            gLCD.locate( 9, 1 );
            gLCD.printf("G>>%3.2f", gGlide);
        }
        
        switch (gMode)
        {               
            case MODE_OSC: // OSCtoCV mode
            
                SetCV();
                break;
                
            case MODE_SEQ: // Shift Sequencer mode
            
                bpm = CheckBPM();

                ShiftCVSeq(GateSeq(bpm, N16TH, GATE1, 3, NON_INVERT, GATESOUT_OFF, SYNC_ON), gCtrlSW[0]);
                GateSeq(bpm, N8TH, GATE2, 3, NON_INVERT, GATESOUT_ON, SYNC_OFF);
                
                if (gCtrlSW[3])
                {
                    EuclideanSeq(GateSeq(bpm, N16TH, SUBGATE, 1, NON_INVERT, GATESOUT_OFF, SYNC_OFF), gCtrlSW[0], GATESOUT_OFF);
                }
                break;
                
            case MODE_185: // M185 Sequencer mode
            
                bpm = CheckBPM();
            
                M185Seq(GateSeq(bpm, N16TH, GATE1, 3, NON_INVERT, GATESOUT_OFF, SYNC_ON), gCtrlSW[0]);
                GateSeq(bpm, N8TH, GATE2, 3, NON_INVERT, GATESOUT_ON, SYNC_OFF);
                
                if (gCtrlSW[3])
                {
                    EuclideanSeq(GateSeq(bpm, N16TH, SUBGATE, 1, NON_INVERT, GATESOUT_OFF, SYNC_OFF), gCtrlSW[0], GATESOUT_OFF);
                }
                break;
                
            case MODE_EUC: // Euclidean Sequencer mode
            
                bpm = CheckBPM();
                
                ShiftCVSeq(GateSeq(bpm, N1ST, SUBGATE, 3, NON_INVERT, GATESOUT_OFF, SYNC_OFF), gCtrlSW[0]); 
                EuclideanSeq(GateSeq(bpm, N16TH, GATE1, 1, NON_INVERT, GATESOUT_OFF, SYNC_OFF), gCtrlSW[0], GATESOUT_ON);
                break;
                
            default:       // CV Calibration mode
                
                CalibrationCV(); 
                break;
        }
        
    }
}

//-------------------------------------------------------------
// Initialize OSCtoCV

void InitOSCCV()
{
    int i;
// Write custom char LCD CGRAM
    WriteCustomChar(0x00, str1);
    WriteCustomChar(0x01, str2);
    WriteCustomChar(0x02, str3);
    WriteCustomChar(0x03, str4);
    WriteCustomChar(0x04, str5);
    WriteCustomChar(0x05, str6);
    WriteCustomChar(0x06, str7);
    WriteCustomChar(0x07, str8);
    
// Init  SPI
    gLDAC = _ENABLE;
    gSPI.format(8,1);           // Data word length 8bit, Mode=1
    gSPI.frequency(SPI_RATE);
    
    UpdateCV(CLR, 0, 0);        // Ignore CLR Pin

// Initialize Euclid Sequencer
    channelbeats[0][0] = 16;
    channelbeats[0][1] = 8;
    channelbeats[0][2] = 0;
    channelbeats[0][3] = 0;
    
    channelbeats[1][0] = 16;
    channelbeats[1][1] = 9;
    channelbeats[1][2] = 0;
    channelbeats[1][3] = 0;
    
    channelbeats[2][0] = 16;
    channelbeats[2][1] = 7;
    channelbeats[2][2] = 0;
    channelbeats[2][3] = 0;
    
    channelbeats[3][0] = 16;
    channelbeats[3][1] = 9;
    channelbeats[3][2] = 0;
    channelbeats[3][3] = 0;

    for (i = 0; i < channels; ++i) 
    {
        beat_holder[i] = Euclid(channelbeats[i][0], channelbeats[i][1], channelbeats[i][3]);
    }
    
// Init BPM
    gCtrl[0] = 0.398f;

// Init Sequence Data
    for (i = 0; i < 16; ++i) 
    {
        gSeq_cv[i] = calibMap1[69] * SCALING_N;
    }
    
// Init M185 Reset Count    
    gCtrl[4] = 1;

// mdns (Bonjour)
    HTTPServer svr;
    mDNSResponder mdns;
    
    svr.addHandler<SimpleHandler>("/");
    svr.bind(INPUT_PORT);
    IpAddr ip = gEth.getIp();
    mdns.announce(ip, "OSCtoCV", "_osc._udp", INPUT_PORT, "mbed(OSCtoCV)", (char *[]) {"path=/",NULL});

// Set OSC message for sending 
    sendMes.setIp(touchOSCAddress); 
    sendMes.setPort(touchOSCPort);
    
    gSW.attach(&CheckModeSW,IRQ_RISE, 30);     // InterruptIn rising edge(ModeSW)
    gPoller.attach_us(&NetPoll, POLLING_INTERVAL);  // Ticker Polling
    
    wait(0.4);

    // Begin Serial for Arduino
    //ardSerial.baud(115200);
}

//-------------------------------------------------------------
// Ethernet Polling

inline void NetPoll()
{
    Net::poll();
}

//-------------------------------------------------------------
// Map Function 

inline float MapFloat(float x, float in_min, float in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

//-------------------------------------------------------------
// SPI Transfer
// DAC8568 data word length 32bit (8bit shift out)

inline void UpdateCV(int control, int address, const unsigned int *data)
{

    switch (control)
    {
        case WRITE_UPDATE_N:

            gSYNCMODE = _DISABLE;
            gSPI.write(00000000|control);            // padding at beginning of byte and control bits
            gSPI.write(address << 4 | *data >> 12);  // address(ch) bits
            gSPI.write((*data << 4) >> 8);           // middle 8 bits of data
            gSPI.write((*data << 12) >> 8 | 00001111);
            gSYNCMODE = _ENABLE;
            gLDAC = _DISABLE;
            gLDAC = _ENABLE;
            
            break;

        case RESET:

            gSYNCMODE = _DISABLE;
            gSPI.write(00000111);   // Software RESET
            gSPI.write(00000000);
            gSPI.write(00000000);
            gSPI.write(00000000);
            gSYNCMODE = _ENABLE;
            
            break;

        case CLR:
        
            gSYNCMODE = _DISABLE;
            gSPI.write(00000101);   // CLR Register
            gSPI.write(00000000);
            gSPI.write(00000000);
            gSPI.write(00000011);   // Ignore CLR Pin
            gSYNCMODE = _ENABLE;
            
            break;
    }
}

//-------------------------------------------------------------
// Calibration Mode

inline void CalibrationCV()
{
    static int ch;
    unsigned int cv;

    switch (gMode) 
        {
            case MODE_Calb:
                
                cv = (unsigned int)(calibMap1[69] * SCALING_N);  // A880.0Hz
                
                gSUBGATE = gGATES[0] = gGATES[1] = gGATES[2] = gGATES[3] = true;
                
                UpdateCV(WRITE_UPDATE_N, ch, &cv);
                
                break;
        }

        UpdateCVMeter(ch, &cv);
        
        ++ch;
        ch &= 0x07;
}

//-------------------------------------------------------------
// Calculate CV

inline void SetCV()
{
    static int ch, qmode, amode, mcount;
    static float glidecv[8];
    unsigned int cv;
    static float qcv;

    qmode = (gCtrl[1] * (SCALE_NUM - 1));
    amode = SCALE_AOUT * qmode;
    
    gAOUT.write_u16(amode);

    switch (qmode) 
        {
            case Lin:
                
                glidecv[ch] = glidecv[ch] * gGlide + gOSC_cv[ch] * (1.0f - gGlide);
                cv = (unsigned int)glidecv[ch];
                    
                UpdateCV(WRITE_UPDATE_N, ch, &cv);

                break;
                    
            case Chr:

                qcv = calibMap1[(unsigned int)MapFloat(gOSC_cv[ch], 0, SCALING_N, 0, (QUAN_RES1 - 1))];
                    
                glidecv[ch] = glidecv[ch] * gGlide + (qcv * SCALING_N) * (1.0f - gGlide);
                cv = (unsigned int)glidecv[ch];
                        
                UpdateCV(WRITE_UPDATE_N, ch, &cv);

                break;
                
            case Maj:

                qcv = calibMap2[(unsigned int)MapFloat(gOSC_cv[ch], 0, SCALING_N, 0, (QUAN_RES2 - 1))];

                glidecv[ch] = glidecv[ch] * gGlide + (qcv * SCALING_N) * (1.0f - gGlide);
                cv = (unsigned int)glidecv[ch];
                                        
                UpdateCV(WRITE_UPDATE_N, ch, &cv);

                break;
            
            case M7:
                
                qcv = calibMap3[(unsigned int)MapFloat(gOSC_cv[ch], 0, SCALING_N, 0, (QUAN_RES3 - 1))];
                    
                glidecv[ch] = glidecv[ch] * gGlide + (qcv * SCALING_N) * (1.0f - gGlide);
                cv = (unsigned int)glidecv[ch];
                        
                UpdateCV(WRITE_UPDATE_N, ch, &cv);

                break;
                
            case Min7:

                qcv = calibMap4[(unsigned int)MapFloat(gOSC_cv[ch], 0, SCALING_N, 0, (QUAN_RES4 - 1))];

                glidecv[ch] = glidecv[ch] * gGlide + (qcv * SCALING_N) * (1.0f - gGlide);
                cv = (unsigned int)glidecv[ch];
                        
                UpdateCV(WRITE_UPDATE_N, ch, &cv);

                break;
                    
            case Dor:
                
                qcv = calibMap5[(unsigned int)MapFloat(gOSC_cv[ch], 0, SCALING_N, 0, (QUAN_RES5 - 1))];

                glidecv[ch] = glidecv[ch] * gGlide + (qcv * SCALING_N) * (1.0f - gGlide);
                cv = (unsigned int)glidecv[ch];
                        
                UpdateCV(WRITE_UPDATE_N, ch, &cv);

                break;
        
            case Min:
                
                qcv = calibMap6[(unsigned int)MapFloat(gOSC_cv[ch], 0, SCALING_N, 0, (QUAN_RES6 - 1))];

                glidecv[ch] = glidecv[ch] * gGlide + (qcv * SCALING_N) * (1.0f - gGlide);
                cv = (unsigned int)glidecv[ch];
                        
                UpdateCV(WRITE_UPDATE_N, ch, &cv);

                break;
                
            case S5th:

                qcv = calibMap7[(unsigned int)MapFloat(gOSC_cv[ch], 0, SCALING_N, 0, (QUAN_RES7 - 1))];

                glidecv[ch] = glidecv[ch] * gGlide + (qcv * SCALING_N) * (1.0f - gGlide);
                cv = (unsigned int)glidecv[ch];
                        
                UpdateCV(WRITE_UPDATE_N, ch, &cv);

                break;
                
            case Wht:
                
                qcv = calibMap8[(unsigned int)MapFloat(gOSC_cv[ch], 0, SCALING_N, 0, (QUAN_RES8 - 1))];

                glidecv[ch] = glidecv[ch] * gGlide + (qcv * SCALING_N) * (1.0f - gGlide);
                cv = (unsigned int)glidecv[ch];
                        
                UpdateCV(WRITE_UPDATE_N, ch, &cv);

                break;
        }

        if (mcount == 0x1F) 
        {
            UpdateCVMeter(ch, &cv);
        }

        ++ch;
        
        if (ch &= 0x07)
        {
            ++mcount;
            mcount &= 0x3F;
        }
}

//-------------------------------------------------------------
// Sequence & Shift Out CV

inline void ShiftCVSeq(int trigger, bool reset)
{
    int i, j;
    static bool triggerState = false;
    static bool stepFoward = false;
    static bool _reset = false;
    static uint8_t currentStep;
    static int _resetCount, resetCount;
    static uint8_t gateMode;
    static uint8_t _gateMode[16];
    static uint8_t ch, qmode, amode;
    static float glidecv[8], shiftcv[8];
    unsigned int cv;
    static float qcv;
        
    qmode = (gCtrl[1] * (SCALE_NUM - 1.0f));  // Sequencer Quantize Mode (gCtrl[1])
    amode = SCALE_AOUT * qmode;
    
    gAOUT.write_u16(amode);
    
    switch (qmode) 
        {
            case Lin:

                glidecv[0] = glidecv[0] * gSlide[currentStep] + gSeq_cv[currentStep] * (1.0f - gSlide[currentStep]);

                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;
    
            case Chr:

                qcv = calibMap1[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES1 - 1))];
    
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case Maj:

                qcv = calibMap2[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES2 - 1))];
    
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case M7:

                qcv = calibMap3[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES3 - 1))];
    
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;
    
            case Min7:

                qcv = calibMap4[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES4 - 1))];
            
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;
            
            case Dor:

                qcv = calibMap5[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES5 - 1))];

                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case Min:

                qcv = calibMap6[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES6 - 1))];

                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case S5th:

                qcv = calibMap7[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES7 - 1))];

                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case Wht:

                qcv = calibMap8[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES8 - 1))];
    
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;
        }
    
    for (i = 1; i < 8; ++i)
    {
        glidecv[i] = glidecv[i] * gSlide[currentStep] + shiftcv[i] * (1.0f - gSlide[currentStep]);
        cv = (unsigned int)glidecv[i];

        UpdateCV(WRITE_UPDATE_N, i, &cv);
    }   
        
    if (trigger && !triggerState) // trigger ON
    {        
        stepFoward = triggerState = true;
        
    } else if (!trigger) { // trigger OFF
        
        if (gateMode != HOLD) 
        {
            gGATES[0] = false;
        }
        
        triggerState = false;
    }
    
// check & update touchOSC ctrl parameter              
    if (_gateMode[ch] != (gGateMode[ch] * 3))
    {
        _gateMode[ch] = (gGateMode[ch] * 3);
            
        if (_gateMode[ch] == MULTI)
        {
            _gateMode[ch] = HOLD;
        }
        
        SendCtrlState(ch, _gateMode[ch], 8);
    }
    
    if (reset && !_reset) // Stop & Reset
    {
        sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
        sendMes.setArgs("i", 0);
        osc.sendOsc(&sendMes);
        
        currentStep = 0;

        sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
        sendMes.setArgs("i", 1);
        osc.sendOsc(&sendMes);
        
        _reset = true;

    } else if (!reset) {

        _reset = false;
    }
    
    if (stepFoward)
    {
        if (gateMode != HOLD) // shift CV
        {
            for (j = 1; j < 8; ++j)
            {
                shiftcv[j] = glidecv[j-1];
            }
        }
   
        sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
        sendMes.setArgs("i", 0);
        osc.sendOsc(&sendMes);
                    
        ++currentStep;

        if (gCtrlSW[2]) 
        {
            resetCount = 3;
            
        } else {
            
            resetCount = gCtrl[4] * 15;
        }
        
        if (_resetCount != resetCount)
        {
            sendMes.setTopAddress(RESET_COUNTER_ADDRESS);
            sendMes.setArgs("i", (resetCount + 1));
            osc.sendOsc(&sendMes);
        }
        
        if (currentStep > resetCount)  // reset
        {
            currentStep = 0;
        }
        
        sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
        sendMes.setArgs("i", 1);
        osc.sendOsc(&sendMes);
                    
        if (currentStep < 8)
        {
            UpdateCVMeter(currentStep, &cv);
                    
        } else {
                
            UpdateCVMeter((currentStep - 8), &cv);
        }
        
        gateMode = (gGateMode[currentStep] * 3);
    
        if (gateMode == MULTI) // omit MULTI mode
        {
            gateMode = HOLD;
        }
        
        if (gateMode != MUTE) 
        {
            gGATES[0] = true;
        }
        
        stepFoward = false;
    }
    
    ++ch;
    ch &= 0x0F;
}

//-------------------------------------------------------------
// M185 Sequencer

inline void M185Seq(int trigger, bool reset)
{
    int i, j;
    static bool triggerState = false;
    static bool stepFoward = false;
    static bool _reset = false;
    static uint8_t currentStep;
    static int stepCount;
    static int _resetCount, resetCount;
    static uint8_t gateMode;
    static uint8_t _gateMode[8];
    static uint8_t _pulseCount[8];
    static uint8_t ch, qmode, amode;
    static float glidecv[8], shiftcv[8];
    unsigned int cv;
    static float qcv;
    
    qmode = (gCtrl[1] * (SCALE_NUM - 1));  // Sequencer Quantize Mode (gCtrl[1])
    amode = SCALE_AOUT * qmode;
    
    gAOUT.write_u16(amode);
    
    switch (qmode) 
        {
            case Lin:

                glidecv[0] = glidecv[0] * gSlide[currentStep] + gSeq_cv[currentStep] * (1.0f - gSlide[currentStep]);

                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;
    
            case Chr:

                qcv = calibMap1[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES1 - 1))];
    
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case Maj:

                qcv = calibMap2[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES2 - 1))];
    
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case M7:

                qcv = calibMap3[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES3 - 1))];
    
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;
    
            case Min7:

                qcv = calibMap4[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES4 - 1))];
            
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;
            
            case Dor:

                qcv = calibMap5[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES5 - 1))];

                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case Min:

                qcv = calibMap6[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES6 - 1))];

                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case S5th:

                qcv = calibMap7[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES7 - 1))];

                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;

            case Wht:

                qcv = calibMap8[(unsigned int)MapFloat(gSeq_cv[currentStep], 0, SCALING_N, 0, (QUAN_RES8 - 1))];
    
                glidecv[0] = glidecv[0] * gSlide[currentStep] + (qcv * SCALING_N) * (1.0f - gSlide[currentStep]);
                cv = (unsigned int)glidecv[0];

                UpdateCV(WRITE_UPDATE_N, 0, &cv);

                break;
        }
    
    for (i = 1; i < 8; ++i)
    {
        glidecv[i] = glidecv[i] * gSlide[currentStep] + shiftcv[i] * (1.0f - gSlide[currentStep]);
        cv = (unsigned int)glidecv[i];

        UpdateCV(WRITE_UPDATE_N, i, &cv);
    }
    
    if (trigger && !triggerState) // trigger ON
    {
        if (gateMode == MULTI) 
        {
            gGATES[0] = true;
            
            sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
            sendMes.setArgs("i", 1);
            osc.sendOsc(&sendMes);
        }
        
        stepFoward = triggerState = true;
        
    } else if (!trigger) { // trigger OFF

        if (gateMode != HOLD) 
        {
            gGATES[0] = false;
        } 
        
        if (gateMode == MULTI)
        {
            sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
            sendMes.setArgs("i", 0);
            osc.sendOsc(&sendMes);
        }
        
        triggerState = false;
    }

// check & update touchOSC ctrl parameter        
    if (_gateMode[ch] != gGateMode[ch] * 3 || _pulseCount[ch] != gPulseCount[ch] * 7)
    {
        _gateMode[ch] = (gGateMode[ch] * 3);
        _pulseCount[ch] = (gPulseCount[ch] * 7);
        
        SendCtrlState(ch, _gateMode[ch], _pulseCount[ch]);
    }
    
    if (reset && !_reset) // Stop & Reset
    {
        sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
        sendMes.setArgs("i", 0);
        osc.sendOsc(&sendMes);
        
        currentStep = 0;

        sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
        sendMes.setArgs("i", 1);
        osc.sendOsc(&sendMes);
        
        _reset = true;

    } else if (!reset) {

        _reset = false;
    }
    
    if (stepFoward)
    {
        if (gateMode != HOLD) // shift CV
        {
            for (j = 1; j < 8; ++j) 
            {
                shiftcv[j] = glidecv[j-1];
            }
        }
        
        --stepCount;
                
        if (stepCount == -1)
        {            
            sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
            sendMes.setArgs("i", 0);
            osc.sendOsc(&sendMes); 
            
            ++currentStep;
            
            if (gCtrlSW[2]) 
            {
                resetCount = 3;
            
            } else {
            
                resetCount = gCtrl[4] * 7;
            }
        
            if (_resetCount != resetCount)
            {
                sendMes.setTopAddress(RESET_COUNTER_ADDRESS);
                sendMes.setArgs("i", (resetCount + 1));
                osc.sendOsc(&sendMes);
            }

            if (currentStep > resetCount) // reset
            {
                currentStep = 0;
            }
        
            sendMes.setTopAddress(SetMatrixAddress(0, currentStep, false));
            sendMes.setArgs("i", 1);
            osc.sendOsc(&sendMes);
                    
            UpdateCVMeter(currentStep, &cv);
            
        // check Pulse Count & Gate Mode
            stepCount = (gPulseCount[currentStep] * 7);
        
            gateMode = (gGateMode[currentStep] * 3);
            
            if (gateMode != MUTE)
            {
                gGATES[0] = true;
            }
            
        }
                
        stepFoward = false;
    }
        
    ++ch;
    ch &= 0x07;
}

//-------------------------------------------------------------
// Send M185 Sequencer Status to touchOSC

inline void SendCtrlState(uint8_t step, uint8_t gateMode, uint8_t stepCount)
{
    char pulseAddress[10] = PULSE_COUNT_ADDRESS;
    char gateModeAddress[10] = GATE_MODE_ADDRESS;
    char currentStep[2];
    
    sprintf(currentStep, "%d", step + 1);
    
    strcat(gateModeAddress, currentStep);
    
    if(stepCount != 8) 
    {
        strcat(pulseAddress, currentStep);
        sendMes.setTopAddress(pulseAddress);
        sendMes.setArgs("i", (stepCount + 1));
        osc.sendOsc(&sendMes);
    }

    sendMes.setTopAddress(gateModeAddress);
        
    switch (gateMode)
        {
            case SINGLE:
            
                sendMes.setArgs("s", "|");
                
                break;

            case MUTE:
            
                sendMes.setArgs("s", "O");
                
                break;

            case MULTI:

                sendMes.setArgs("s", "||");
                
                break;

            case HOLD:
                
                sendMes.setArgs("s", "|-");
                
                break;
        }

        osc.sendOsc(&sendMes);
}

//-------------------------------------------------------------
// Gate Sequencer  beat(Note values) length(Gate time) invert(invert Gate)

inline int GateSeq(int bpm, int beat, int ch, int length, bool invert, bool gatesoff, bool syncoff)
{
    int i;
    static int gatetime[GATE_TOTAL], oldgatetime[GATE_TOTAL];
    static int _bpm, bar, sync24, oldsynctime;
    
    int time = gTimer.read_us();
    
    if (_bpm != bpm)
    {
        if (!bpm)
        {
            beat = NRESET;

        } else {

            bar = (60.0f / bpm) * 4000000;
            //sync24 = (bar / 4) / 24; // sync24 not tested
            
            _bpm = bpm;
        }
    }
    
    switch (beat) // Calculate Note values 
        {
            case NDOT2:
                
                gatetime[ch] = (bar / 4) * 3;
                break;
                    
            case NDOT4:
            
                gatetime[ch] = (bar / 8) * 3;
                break;
                
            case NDOT8:
                
                gatetime[ch] = (bar / 16) * 3;
                break;
                    
            case NDOT16:
                
                gatetime[ch] = (bar / 32) * 3;
                break;
        
            case NDOT32:
                
                gatetime[ch] = (bar / 64) * 3;
                break;
                
            case NRESET:
            
                gTimer.reset();
                
                for (i = 0; i < GATE_TOTAL; ++i)    // Reset
                {
                    oldsynctime = oldgatetime[i] = gatetime[i] = NRESET;
                }

                return 0;

            default:
                
                gatetime[ch] = bar / beat;
                sync24 = bar / 16;
                break;
        }
        
    if (time > oldsynctime + sync24)  // sync24 not tested
    {
        if (!syncoff) 
        {
            oldsynctime = time;
            gCLOCKOUT = true;
            
            midi.sendRealTime(Clock); // MIDI Clock
        }
        
    } else if (time > oldsynctime - (sync24 - 2)) {

        if (!syncoff) 
        {
            gCLOCKOUT = false;
        }
    }

    if (ch == GATE_TOTAL) 
    {
        return -1;
        
    } else if (time > oldgatetime[ch] + gatetime[ch] && !invert) {
        
        oldgatetime[ch] = time;
        
        if (!gatesoff) 
        {
            gGATES[ch] = true;
            
        } else if (ch == SUBGATE) {
            
            gSUBGATE = true;
        }
        
        return 1;
    
    } else if (time > oldgatetime[ch] + gatetime[ch] && invert) {
        
        oldgatetime[ch] = time;
        
        if (!gatesoff) 
        {
            gGATES[ch] = false;
            
        } else if (ch == SUBGATE) {
            
            gSUBGATE = false;
        }
        
        return 0;
        
    } else if (time > oldgatetime[ch] + (gatetime[ch] - gatetime[ch] / length) && !invert) {
        
        if (!gatesoff) 
        {
            gGATES[ch] = false;
            
        } else if (ch == SUBGATE) {
            
            gSUBGATE = false;
        }
        
        return 0;
        
    } else if (time > oldgatetime[ch] + (gatetime[ch] - gatetime[ch] / length) && invert) {
        
        if (!gatesoff) 
        {
            gGATES[ch] = true;
            
        } else if (ch == SUBGATE) {
            
            gSUBGATE = true;
        }
        
        return 1;
        
    } else {
        
        return -1;          
    }
}


//-------------------------------------------------------------
// Check BPM

inline int CheckBPM()
{   
    static int _bpm = -1;
    int bpm;
    
    if (gCtrlSW[0]) 
    {
        bpm = 0;

        return bpm;
    }
    
    if (!gCtrl[0]) 
    {
        bpm = gArdCtrl[0] * 0.25f + 5;

        if (abs(bpm - _bpm) > 1)
        {
            _bpm = bpm;
            
            sendMes.setTopAddress("/bpm");
            sendMes.setArgs("i", bpm);
            osc.sendOsc(&sendMes);
        }
    
    } else if (gCtrl[0]) {

        bpm = (gCtrl[0] * 240 + 5);

        if (abs(bpm - _bpm) > 1)
        {
            _bpm = bpm;
            
            sendMes.setTopAddress("/bpm");
            sendMes.setArgs("i", bpm);
            osc.sendOsc(&sendMes);
        }
    }
                        
    return bpm;
}

//-------------------------------------------------------------
// Check Mode SW

inline void CheckModeSW()
{   
    if (gMode < MODE_TOTAL - 1) 
    {   
        ++gMode;
            
    } else {
                
        gMode = 0;
    }
    
    gCLOCKOUT = gGATES[0] = gGATES[1] = gGATES[2] = gGATES[3] = false;
    
    if (gMode == MODE_SEQ || gMode == MODE_185 || gMode == MODE_EUC)
    {
        gTimer.start();     // Sequencer Timer Start
        midi.begin(1);
        
    } else {
            
        gTimer.stop();      // Sequencer Timer Stop
    }
    
}

//-------------------------------------------------------------
// Print LCD Mode Status

inline void LCD()
{
    static int _mode = -1;
    static int _qmode = -1;
    static int qmode;
    
    if (_mode != gMode)
    {
        sendMes.setTopAddress("/mode");
        
        switch (gMode) 
            {
                case MODE_Calb: 
                    gLCD.locate( 9, 0 );
                    gLCD.printf("CLB|880");
                    
                    sendMes.setArgs("s", "Calibration");
                    osc.sendOsc(&sendMes);
                    
                    sendMes.setTopAddress("/scale");
                    sendMes.setArgs("s", "880Hz");
                    
                    _qmode = -1;
                    
                    break;
                
                case MODE_OSC:
                    gLCD.locate( 9, 0 );
                    gLCD.printf("OSC|");
                    
                    sendMes.setArgs("s", "OSCtoCV");
                    
                    break;

                case MODE_SEQ:  
                    gLCD.locate( 9, 0 );
                    gLCD.printf("ASR|");
                    
                    sendMes.setArgs("s", "ASR SEQ");
                    
                    break;
                    
                case MODE_185:  
                    gLCD.locate( 9, 0 );
                    gLCD.printf("185|");
            
                    sendMes.setArgs("s", "M185 SEQ");
            
                    break;
                        
                case MODE_EUC:  
                    gLCD.locate( 9, 0 );
                    gLCD.printf("EUC|");
                    
                    sendMes.setArgs("s", "Euclidean SEQ");
                    
                    break;
                    
                default:
                    break;
            }
            
            osc.sendOsc(&sendMes);
            _mode = gMode;
    }
    
    qmode = (gCtrl[1] * (SCALE_NUM - 1));
    
    if ((_qmode != qmode) && gMode)
    {
        sendMes.setTopAddress("/scale");
        
        switch (qmode) 
            {
                case Lin:
                    gLCD.locate( 13, 0 );
                    gLCD.printf("lin");

                    sendMes.setArgs("s", "Linear");

                    break;
                    
                case Chr:
                    gLCD.locate( 13, 0 );
                    gLCD.printf("chr");

                    sendMes.setArgs("s", "Chromatic");

                    break;
                
                case Maj:
                    gLCD.locate( 13, 0 );
                    gLCD.printf("maj");
                    
                    sendMes.setArgs("s", "Major");

                    break;
            
                case M7:
                    gLCD.locate( 13, 0 );
                    gLCD.printf("ma7");
                    
                    sendMes.setArgs("s", "Major7");

                    break;
                
                case Min7:
                    gLCD.locate( 13, 0 );
                    gLCD.printf("mi7");
                    
                    sendMes.setArgs("s", "Minor7");

                    break;
                    
                case Dor:
                    gLCD.locate( 13, 0 );
                    gLCD.printf("dor");
                    
                    sendMes.setArgs("s", "Dorian");

                    break;
        
                case Min:
                    gLCD.locate( 13, 0 );
                    gLCD.printf("min");
                    
                    sendMes.setTopAddress("/scale");
                    sendMes.setArgs("s", "Minor");

                    break;
                
                case S5th:
                    gLCD.locate( 13, 0 );
                    gLCD.printf("5th");
                    
                    sendMes.setArgs("s", "5th");

                    break;
                
                case Wht:
                    gLCD.locate( 13, 0 );
                    gLCD.printf("wht");
                    
                    sendMes.setArgs("s", "Whole Tone");
                    break;
                    
                default:
                    break;
            }
            
            osc.sendOsc(&sendMes);
            _qmode = qmode;
    }

}

//-------------------------------------------------------------
// CV Meter

inline void UpdateCVMeter(int ch, const unsigned int *level)
{       
    gLCD.locate ( ch, 0 );
    gLCD.putc(*level * 0.0002192f);     // put custom char
}


//-------------------------------------------------------------
// Write command Custom Char LCD CGRAM for CV Meter)

void WriteCustomChar(unsigned char addr, unsigned char *c)
{   
    char cnt = 0;
    addr = ((addr << 3) | 0x40);
    
    while (cnt < 0x08)
    {
        gLCD.writeCommand(addr | cnt);
        gLCD.writeData(*c);
        
        ++cnt;
        ++c;
    }
}

//-------------------------------------------------------------
// Setup Ethernet port

int SetupEthNetIf()
{
    gLCD.locate( 0, 1 );
    gLCD.printf("Setting up...   ");
//  printf("Setting up...\r\n");
    EthernetErr ethErr = gEth.setup();
    
    if (ethErr)
    {
        gLCD.locate( 0, 1 );
        gLCD.printf("Error in setup.");
//  printf("Error %d in setup.\r\n", ethErr);
        return -1;
    }
//  printf("Setup OK\r\n");
 
//  printf("IP address %d.%d.%d.%d\r\n", gEth.getIp()[0], gEth.getIp()[1], gEth.getIp()[2], gEth.getIp()[3]);
    Host broadcast(IpAddr(gEth.getIp()[0], gEth.getIp()[1], gEth.getIp()[2], 255), INPUT_PORT, NULL);
    gUdp.setOnEvent(&onUDPSocketEvent);
    gUdp.bind(broadcast);
    
    gLCD.locate( 0, 1 );
    gLCD.printf("%03d.%03d.%03d.%03d", gEth.getIp()[0], gEth.getIp()[1], gEth.getIp()[2], gEth.getIp()[3]);
    
    wait(1.0);
    
    return 0;
}

//-------------------------------------------------------------
// Fast strlen function http://www.strchr.com/optimized_strlen_function

size_t strlength(const char *s) 
{
    size_t len = 0;
    
    for (;;) 
    {
        unsigned x = *(unsigned*)s;
        if ((x & 0xFF) == 0) return len;
        if ((x & 0xFF00) == 0) return len + 1;
        if ((x & 0xFF0000) == 0) return len + 2;
        if ((x & 0xFF000000) == 0) return len + 3;
        s += 4, len += 4;
    }
}

//-------------------------------------------------------------
// Handller receive OSC UDP Packet

inline void onUDPSocketEvent(UDPSocketEvent e)
{
    static union OSCarg msg[10];
    static char buf[896] = {0};
    static int recvlen;
    static int num, len, offset;
    int messagepos = 0;
    bool bundleflag = false;
    
    Host host;
    
    switch (e)
    {
        case UDPSOCKET_READABLE: // The only event for now
        
        recvlen = gUdp.recvfrom(buf, 896, &host);  // packet length    
        
        if (recvlen <= 0)  break;
        
        if (!bundleflag && buf[0] == '#') // #bundle
        {
            messagepos += 16;     // skip #bundle & timetag
            recvlen -= 16;
            
            bundleflag = true;  
        }
        
        do {
                if (bundleflag)
                {
                    messagepos += 4;
                    recvlen -= 4;
                    
                    if (recvlen <= 8)
                    {
                        bundleflag = false;
                        break;
                    }
                }
                
                if (getOSCmsg(buf + messagepos, msg) == -1)  continue;
            
                len = strlength(msg[0].address);
        
                if (isdigit(msg[0].address[len-1])) 
                {       
                    num = msg[0].address[len-1] - '0' - 1;
                    
                    offset = 1;
                    
                    if (isdigit(msg[0].address[len-2])) 
                    {
                        offset = 2;
                        num += 10;
                    }
                        
                } else {
            
                    num = -1;
                }
        
            // address pattern SYNC & GATE (Type Tag int, float)
                if (!strncmp(msg[0].address+(len-offset)-4, "sync", 4)) 
                { 
                    if (msg[2].i != 0) gCLOCKOUT = true;
                    else              gCLOCKOUT = false;
                    continue;

                } else if (!strncmp(msg[0].address+(len-offset)-4, "gate", 4) && (num != -1)) {
                    if (num > 3) continue;
                    if (msg[2].i != 0) gGATES[num] = true;
                    else              gGATES[num] = false;
                    continue;
            // (touchOSC Control push, toggle)
                } else if (!strncmp(msg[0].address+(len-offset)-5, "fader", 5) && (num != -1)) {
                    if (num > 7) continue;                                              
                    gOSC_cv[num] = msg[2].f * (SCALING_N);
                    continue;

                } else if (!strncmp(msg[0].address+(len-offset)-9, "multixy1/", 9) && (num != -1)) {
                    if (num > 7) continue;
                    if (msg[1].typeTag[1] == 'f') gOSC_cv[num] = msg[2].f * (SCALING_N);
                    if (msg[1].typeTag[1] == 'f') gOSC_cv[++num] = msg[3].f * (SCALING_N);
                    continue;
    
                } else if (!strncmp(msg[0].address+(len-offset)-12, "multifader1/", 12) && (num != -1)) {
                    if (num > 7) continue;
                    if (msg[1].typeTag[1] == 'f') gOSC_cv[num] = msg[2].f * (SCALING_N);
                    continue;
                    
                } else if (!strncmp(msg[0].address+(len-offset)-10, "sequencer/", 10) && (num != -1)) {
                    
                    if (num > 15) continue;
                    gSeq_cv[num] = msg[2].f * (SCALING_N);
                    continue;
                            
                } else if (!strncmp(msg[0].address+(len-offset)-6, "ctrlsw", 6) && (num != -1)) {
                    if (num > 7) continue;
                    if (msg[2].i != 0) gCtrlSW[num] = true;
                    else              gCtrlSW[num] = false;
                    continue;
                                                
                } else if (!strncmp(msg[0].address+(len-offset)-4, "ctrl", 4) && (num != -1)) {
                    if (num > 7) continue;                                             
                    gCtrl[num] = msg[2].f;
                    continue;
                    
                } else if (!strncmp(msg[0].address+(len-offset)-9, "pulsecnt/", 9) && (num != -1)) {
                    if (num > 7) continue;
                    gPulseCount[num] = msg[2].f;
                    continue;
                    
                } else if (!strncmp(msg[0].address+(len-offset)-9, "gatemode/", 9) && (num != -1)) {
                    if (num > 15) continue;
                    gGateMode[num] = msg[2].f;
                    continue;
                    
                } else if (!strncmp(msg[0].address+(len-offset)-6, "slide/", 6) && (num != -1)) {
                    if (num > 15) continue;
                    gSlide[num] = msg[2].f;
                    continue;
                    
                } else if (!strncmp(msg[0].address+(len-offset)-4, "euca", 4) && (num != -1)) {
                    if (num > 5) continue;                                             
                    gEucA[num] = msg[2].f;
                    continue;
                    
                } else if (!strncmp(msg[0].address+(len-offset)-4, "eucb", 4) && (num != -1)) {
                    if (num > 5) continue;                                             
                    gEucB[num] = msg[2].f;
                    continue;
                    
                } else {
                    continue;
                }
                
            } while (bundleflag);                   
    }
}

//-------------------------------------------------------------
// Euclidean Sequencer

void EuclideanSeq(int trigger, bool reset, bool gatesoff) {
    /*
       What's in the loop: 
       Update euc_time variable 
       Check to see if it is euc_time go go to sleep 
       Changes routine - update beat_holder when channelbeats changes - triggered by changes == true
       Trigger routines - on trigget update displays and pulse
       Read encoders 
       Read switches
     */

    static uint8_t nn, kk, oo;
    static uint8_t changes[MAXCHANNELS] = {0};
    static int nknob, kknob, oknob;
    static int _nknob, _kknob, _oknob;
    static bool triggerState = false;
    
    uint8_t i, ch;
    uint8_t maxn = MAXSTEPS; // maximums and minimums for n and k
    uint8_t minn = 1;
    uint8_t mink = 1; 
    uint8_t mino = 0; 
    
    static uint8_t active_channel;  
    
    euc_time = gTimer.read_ms();

    nn = channelbeats[active_channel][0];  
    kk = channelbeats[active_channel][1]; 
    oo = channelbeats[active_channel][3];
    
    // UPDATE BEAT HOLDER WHEN KNOBS ARE MOVED
    if (changes[active_channel]) {
        
        beat_holder[active_channel] = Euclid(nn, kk, oo);
        
        switch (changes[active_channel]) 
        {
            case 1:
            case 3:
                for (int i = 0; i < MAXSTEPS; ++i) {
                
                    if (BitRead(beat_holder[active_channel], nn - 1 - i) && (i < nn)) {
                        sendMes.setTopAddress(SetMatrixAddress(active_channel * 2, i, true));
                        sendMes.setArgs("i", 1);
                        osc.sendOsc(&sendMes);          
                    
                    } else {
                        
                        sendMes.setTopAddress(SetMatrixAddress(active_channel * 2, i, true));
                        sendMes.setArgs("i", 0);
                        osc.sendOsc(&sendMes);      
                    }
                }
            
                break;
            
            case 2:
                for (int i = 0; i < MAXSTEPS; ++i) {
            
                    if (i < nn) {
                        sendMes.setTopAddress(SetMatrixAddress(active_channel * 2, i, true));
                        sendMes.setArgs("i", 1);
                        osc.sendOsc(&sendMes);          
                    
                    } else {
                        
                        sendMes.setTopAddress(SetMatrixAddress(active_channel * 2, i, true));
                        sendMes.setArgs("i", 0);
                        osc.sendOsc(&sendMes);          
                    }
                }
        
                break;
            
            default: 
                break;
        }
        
        changes[active_channel] = 0;
        last_changed[active_channel] = gTimer.read_ms();
    }
    
    // ANALOG PULSE TRIGGER
    if (trigger && !triggerState) {
        
        Sync(active_channel, gatesoff);
        triggerState = true;
    
    } else if (!trigger) {
        
        triggerState = false;
    }

    // READ K KNOB 
    kknob = EncodeReadK(active_channel);
    
    if (_kknob != kknob) {
        
        _kknob = kknob;
        
        if (kknob != 0 && (euc_time - last_read[active_channel] > READ_DELAY)) {
        
            if ((kk + kknob) > nn) {          
            
                kknob = 0;
                kk = nn;
            
            } else if ((kk + kknob) < mink) {
            
                kknob = 0;
                kk = mink;
            };

            kk = channelbeats[active_channel][1] = (kk + kknob); // update with encoder reading

            last_read[active_channel] = gTimer.read_ms();
            changes[active_channel] = 1; // k change = 1
        }
    }
    
    // READ N KNOB 
    nknob = EncodeReadN(active_channel);
    
    if (_nknob != nknob) {
        
        _nknob = nknob;
        
        if (nknob != 0 && (euc_time - last_read[active_channel] > READ_DELAY)) { 

            if ((nn + nknob) > maxn) {
            
                nknob = 0;
                nn = maxn;
            
            } else if ((nn + nknob) < minn) {
            
                nknob = 0;
                nn = minn;
            };

            if (kk > (nn + nknob)) {// check if new n is lower than k + reduce K if it is 
                channelbeats[active_channel][1] = (nn + nknob);
            }; 

            if (oo > (nn + nknob - 1)) {// check if new n is lower than o + reduce o if it is 
                channelbeats[active_channel][3] = (nn + nknob - 1);
            }; 

            nn = channelbeats[active_channel][0] = (nn + nknob); // update with encoder reading
            oo = channelbeats[active_channel][3];

            last_read[active_channel] = gTimer.read_ms();
            changes[active_channel] = 2; // n change = 2 
        }
        
    }
    
    // READ O KNOB 
    oknob = EncodeReadO(active_channel);
    
    if (_oknob != oknob) {
        
        _oknob = oknob;
            
        if (oknob != 0 && (euc_time - last_read[active_channel] > READ_DELAY)) { 
            // Sense check o encoder reading to prevent crashes 

            if ((oo + oknob) > (nn - 1)) {
            
                oknob = 0;
                oo = (nn - 1);
        
            } else if ((oo + oknob) < mino) {
            
                oknob = 0;
                oo = mino;
            }

            channelbeats[active_channel][3] = (oo + oknob);
        
            last_read[active_channel] = gTimer.read_ms();
            changes[active_channel] = 3; // o change = 3
        }
        
    }
    
    // ENABLE RESET BUTTON ** ADD FLASH RESET HERE ***
    if (gCtrlSW[1] && channelbeats[active_channel][2]) {
        
        for (ch = 0; ch < channels; ++ch) {
            channelbeats[ch][2] = 0; 
        }
    }
    
    // Stop & Reset (gCtrlSW[0])
    if (reset) {
        
        for (ch = 0; ch < channels; ++ch) {
            
            channelbeats[ch][2] = 0;
            
            for (i = 0; i < MAXSTEPS; ++i) {
            
                sendMes.setTopAddress(SetMatrixAddress(ch * 2 + 1, i, true));
                sendMes.setArgs("i", 0);
                osc.sendOsc(&sendMes);
            }
            
        }
        
    }
    
    // TURN OFF ANY LIGHTS THAT ARE ON 
    if ((euc_time - last_sync) > pulse_length && lights_active) {
        
        for (ch = 0; ch < channels; ++ch) {
            sendMes.setTopAddress(SetMatrixAddress((MAXCHANNELS * 2), 3 - ch, true));
            sendMes.setArgs("i", 0);
            osc.sendOsc(&sendMes);
            
            sendMes.setTopAddress(SetMatrixAddress((MAXCHANNELS * 2), 5, true));
            sendMes.setArgs("i", 0);
            osc.sendOsc(&sendMes);
            
      }
      
      lights_active = false; 
    }
    
    // FINISH ANY PULSES THAT ARE ACTIVE - PULSES LAST 1/4 AS LONG AS LIGHTS 
    if (euc_time - last_sync > (pulse_length / 4) && pulses_active) {
        
        for (ch = 0; ch < channels; ++ch) {
            
            if (!gatesoff)
            {
                gGATES[ch] = false;
                gCLOCKOUT = false;
                //digitalWrite(sparepin, LOW);
            }
        }
        
        pulses_active = false; 
    }
    
    ++active_channel;
    active_channel &= (channels - 1);
}

//-------------------------------------------------------------
// Euclid calculation function

unsigned int Euclid(int n, int k, int o) { // inputs: n=total, k=beats, o = offset
    int pauses = (n - k);
    int pulses = k;
    int offset = o;
    int steps = n;
    int per_pulse = (pauses / k);
    int remainder = (pauses % pulses);  
    unsigned int workbeat[n];
    unsigned int outbeat;
    uint16_t outbeat2;
    int workbeat_count = n;
    int a_remainder, b_remainder;
    int groupa, groupb;
    int i, j; 
    int trim_count;

    for (i = 0; i < n; ++i) { // Populate workbeat with unsorted pulses and pauses 

        if (i < pulses) {

            workbeat[i] = 1;

        } else {

            workbeat[i] = 0;
        }
    }

    if (per_pulse > 0 && remainder < 2) { // Handle easy cases where there is no or only one remainer

        for (i = 0; i < pulses; ++i) {

            for (j = (workbeat_count - 1); j > (workbeat_count - per_pulse - 1); --j) {
                workbeat[i]  = ConcatBin(workbeat[i], workbeat[j]);
            }

            workbeat_count = (workbeat_count - per_pulse);
            
        }

        outbeat = 0; // Concatenate workbeat into outbeat - according to workbeat_count

        for (i = 0; i < workbeat_count; ++i) {
            outbeat = ConcatBin(outbeat, workbeat[i]);
        }


        if (offset != 0) {
            
            outbeat2 = BitReadOffset(offset, outbeat, steps); // Add offset to the step pattern

        } else {
            
            outbeat2 = outbeat;
        }

        return outbeat2;

    } else { 

        groupa = pulses;
        groupb = pauses; 

        while (groupb > 1) { //main recursive loop

            if (groupa > groupb) { // more Group A than Group B
                
                a_remainder = (groupa - groupb); // what will be left of groupa once groupB is interleaved 
                trim_count = 0;

                for (i = 0; i < (groupa - a_remainder); ++i) { //count through the matching sets of A, ignoring remaindered
                    workbeat[i]  = ConcatBin(workbeat[i], workbeat[workbeat_count - 1 - i]);
                    ++trim_count;
                }
                
                workbeat_count = (workbeat_count - trim_count);

                groupa = groupb;
                groupb = a_remainder;

            } else if (groupb > groupa) { // More Group B than Group A
                
                b_remainder = (groupb - groupa); // what will be left of group once group A is interleaved 
                trim_count = 0;

                for (i = workbeat_count-1; i >= (groupa + b_remainder); --i) { //count from right back through the Bs
                    workbeat[workbeat_count - i - 1] = ConcatBin(workbeat[workbeat_count - 1 - i], workbeat[i]);

                    ++trim_count;
                }

                workbeat_count = (workbeat_count - trim_count);
                groupb = b_remainder;

            } else if (groupa == groupb) { // groupa = groupb 
                
                trim_count = 0;

                for (i = 0; i < groupa; ++i) {
                    workbeat[i] = ConcatBin(workbeat[i], workbeat[workbeat_count - 1 - i]);
                    ++trim_count;
                }

                workbeat_count = (workbeat_count - trim_count);
                groupb = 0;

            }
        }

        outbeat = 0; // Concatenate workbeat into outbeat - according to workbeat_count

        for (i = 0; i < workbeat_count; ++i) {
            
            outbeat = ConcatBin(outbeat, workbeat[i]);
        }

        if (offset != 0) {
            
            outbeat2 = BitReadOffset(offset, outbeat, steps); // Add offset to the step pattern

        } else {

            outbeat2 = outbeat;
        }

        return outbeat2;
    }
}

//-------------------------------------------------------------
// Reads a bit of a number

inline int BitRead(uint16_t b, int bitPos) {
    int x;
        
    x = b & (1 << bitPos);
    
    return x == 0 ? 0 : 1;
}

//-------------------------------------------------------------
// Function to right rotate n by d bits

uint16_t BitReadOffset(int shift, uint16_t value, uint16_t pattern_length) {
    uint16_t mask = ((1 << pattern_length) - 1);
    value &= mask;

    return ((value >> shift) | (value << (pattern_length - shift))) & mask;
}

//-------------------------------------------------------------
// Function to find the binary length of a number by counting bitwise

int findlength(unsigned int bnry) {
    bool lengthfound = false;
    int i;
    int length = 1; // no number can have a length of zero - single 0 has a length of one, but no 1s for the sytem to count

    for (i = 32; i >= 0; i--) {

        if ((BitRead(bnry, i)) && !lengthfound) {
            length = (i + 1);
            lengthfound = true;
        }
        
    }

    return length;
}

//-------------------------------------------------------------
// Function to concatenate two binary numbers bitwise

unsigned int ConcatBin(unsigned int bina, unsigned int binb) {
    int binb_len = findlength(binb);
    unsigned int sum = (bina << binb_len);

    sum = sum | binb;

    return sum;
}

//-------------------------------------------------------------
// routine triggered by each beat

void Sync(int active_channel, bool gatesoff) {
    int read_head, erase;
    int rand_vel, rand_len;
    int ch, i;
    static int masterclock;
    
    if (masterclock % 2 == 0) {
        sendMes.setTopAddress(SetMatrixAddress((MAXCHANNELS * 2), 7, true));
        sendMes.setArgs("i", 1);
        osc.sendOsc(&sendMes);
    
    } else {
        
        sendMes.setTopAddress(SetMatrixAddress((MAXCHANNELS * 2), 7, true));
        sendMes.setArgs("i", 0);
        osc.sendOsc(&sendMes);
    }

    // Cycle through channels 
    for (ch = 0; ch < channels; ++ch) {
        
        read_head = (channelbeats[ch][0] - channelbeats[ch][2] - 1);  

        if (ch != active_channel || (euc_time - last_changed[active_channel]) > DISPLAY_UPDATE) {
            
            if (channelbeats[ch][2] < MAXSTEPS) {
                
                for (i = 0; i < MAXSTEPS; ++i) {
                    
                    if (BitRead(beat_holder[ch],channelbeats[ch][0] - 1 - i) && i < channelbeats[ch][0]) {
                        
                        sendMes.setTopAddress(SetMatrixAddress(ch * 2, i, true));
                        sendMes.setArgs("i", 1);
                        osc.sendOsc(&sendMes);
                    
                    } else {
                        
                        sendMes.setTopAddress(SetMatrixAddress(ch * 2, i, true));
                        sendMes.setArgs("i", 0);
                        osc.sendOsc(&sendMes);
                    }
                    
                }
            }
        }
        
        if (channelbeats[ch][2]) {
            
            if (!masterclock) {
                
                erase = MAXSTEPS - 1;
                
            } else {
                
                erase = masterclock - 1;
            }
            
            sendMes.setTopAddress(SetMatrixAddress((ch * 2) + 1, erase, true));
            sendMes.setArgs("i", 0);
            osc.sendOsc(&sendMes);

            sendMes.setTopAddress(SetMatrixAddress((ch * 2) + 1, masterclock, true));
            sendMes.setArgs("i", 1);
            osc.sendOsc(&sendMes);
            
            }
            
        // turn on pulses on channels where a beat is present  
        if (BitRead(beat_holder[ch], read_head)) {
            
            if (!gatesoff)
            {
                gGATES[ch] = true; // pulse out
            }
            
            sendMes.setTopAddress(SetMatrixAddress((MAXCHANNELS * 2), 3 - ch, true));
            sendMes.setArgs("i", 1);
            osc.sendOsc(&sendMes);
            
            lights_active = pulses_active = true;
            
            if (!ch || (ch == 2)) { 
                
                rand_vel = 127 - (rand() / (RAND_MAX / 40)); // random velocity ch1, ch3
                
            } else {
                
                rand_vel = 95 - (rand() / (RAND_MAX / 70));  // random velocity ch2, ch4
            }
            
            rand_len = 127 - (rand() / (RAND_MAX / 110));    // random Amp EG Decay
            
            midi.sendControlChange(0x07, rand_vel, (ch + 1)); // volca sample Vol
            midi.sendControlChange(0x30, rand_len, (ch + 1)); // volca sample Amp EG Decay
            midi.sendNoteOn(0, 127, (ch + 1)); // volca sample trriger on
        }

        // send off pulses to spare output for the first channel 
        if (!(BitRead(beat_holder[ch], read_head)) && !ch) { // only relates to first channel
            
            if (!gatesoff)
            {
                gCLOCKOUT = true;
            }
            
            sendMes.setTopAddress(SetMatrixAddress((MAXCHANNELS * 2), 5, true));
            sendMes.setArgs("i", 1);
            osc.sendOsc(&sendMes);
            
            lights_active = pulses_active = true;
        }

        // move counter to next position, ready for next pulse  
        ++channelbeats[ch][2];

        if ((channelbeats[ch][2]) >= (channelbeats[ch][0])) {
            channelbeats[ch][2] = 0; 
        }
    }
    
    ++masterclock;
    masterclock &= (MAXSTEPS - 1);

    pulse_length = ((euc_time - last_sync) / 5);
    last_sync = euc_time;
}

/* 3 functions to read each encoder   
   returns +1, 0 or -1 dependent on direction 
   Contains no internal debounce, so calls should be delayed 
 */

//-------------------------------------------------------------
// Check Euclidean Seq N(length) Value

int EncodeReadN(int ch) {
    static float _enc[4];
    int result = 0;
    
    switch (ch)
    {
        case 0:
        
            if (gEucA[0] == 0) {
                _enc[ch] = result = 0;
        
            } else if (gEucA[0] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucA[0];
    
            } else if (gEucA[0] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucA[0];
            }
            
            break;
        
        case 1:
        
            if (gEucA[3] == 0) {
                _enc[ch] = result = 0;
    
            } else if (gEucA[3] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucA[3];

            } else if (gEucA[3] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucA[3];
            }
        
            break;
        
        case 2:
        
            if (gEucB[0] == 0) {
                _enc[ch] = result = 0;
    
            } else if (gEucB[0] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucB[0];

            } else if (gEucB[0] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucB[0];
            }
        
            break;
        
        case 3:
        
            if (gEucB[3] == 0) {
                _enc[ch] = result = 0;
    
            } else if (gEucB[3] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucB[3];

            } else if (gEucB[3] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucB[3];
            }
        
            break;
        
        default:
            break;
    }
    
    return result;
}

//-------------------------------------------------------------
// Check Euclidean Seq K(Density) Value

int EncodeReadK(int ch) { 
    static float _enc[4];
    int result = 0;
    
    switch (ch)
    {
        case 0:
        
            if (gEucA[1] == 0) {
                _enc[ch] = result = 0;
        
            } else if (gEucA[1] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucA[1];
    
            } else if (gEucA[1] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucA[1];
            }
            
            break;
        
        case 1:
        
            if (gEucA[4] == 0) {
                _enc[ch] = result = 0;
    
            } else if (gEucA[4] < _enc[ch]) {  
                result = -4;
                _enc[ch] = gEucA[4];

            } else if (gEucA[4] > _enc[ch]) { 
                result = 4;
                _enc[ch] = gEucA[4];
            }
        
            break;
        
        case 2:
        
            if (gEucB[1] == 0) {
                _enc[ch] = result = 0;
    
            } else if (gEucB[1] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucB[1];

            } else if (gEucB[1] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucB[1];
            }
        
            break;
        
        case 3:
        
            if (gEucB[4] == 0) {
                _enc[ch] = result = 0;
    
            } else if (gEucB[4] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucB[4];

            } else if (gEucB[4] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucB[4];
            }
        
            break;
        
        default:
            break;      
    }
    
    return result;
}

//-------------------------------------------------------------
// Check Euclidean Seq O(Offset) Value

int EncodeReadO(int ch) { 
    static float _enc[4];
    int result = 0;
    
    switch (ch)
    {
        case 0:
        
            if (gEucA[2] == 0) {
                _enc[ch] = result = 0;
        
            } else if (gEucA[2] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucA[2];
    
            } else if (gEucA[2] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucA[2];
            }
            
            break;
        
        case 1:
        
            if (gEucA[5] == 0) {
                _enc[ch] = result = 0;
    
            } else if (gEucA[5] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucA[5];

            } else if (gEucA[5] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucA[5];
            }
        
            break;
        
        case 2:
        
            if (gEucB[2] == 0) {
                _enc[ch] = result = 0;
    
            } else if (gEucB[2] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucB[2];

            } else if (gEucB[2] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucB[2];
            }
        
            break;
        
        case 3:
        
            if (gEucB[5] == 0) {
                _enc[ch] = result = 0;
    
            } else if (gEucB[5] < _enc[ch]) {  
                result = -1;
                _enc[ch] = gEucB[5];

            } else if (gEucB[5] > _enc[ch]) { 
                result = 1;
                _enc[ch] = gEucB[5];
            }
        
            break;
        
        default:
            break;
    }
    
    return result;
}

inline char * SetMatrixAddress(int row, int column, bool euclid) {
    static char address[32];
    char col[2];
    char ch[2];
    
    if(euclid)
    {
        strcpy(address, MATRIX_ADDRESS);
    
    } else {
        
        strcpy(address, STEP_INDICATOR_ADDRESS);
    }
    
    sprintf(col, "%d", column + 1);
    strcat(address, col);
    
    if(euclid)
    {
        strcat(address, "/");
        
        sprintf(ch, "%d", row + 1);
        strcat(address, ch);
    
    } else {
        
        strcat(address, "/1");
    }
    
    return address;
}