Added CV and Gate outputs via 2x i2c MCP4725 modules
This commit is contained in:
278
AcidStepSequencer.ino
Normal file
278
AcidStepSequencer.ino
Normal file
@ -0,0 +1,278 @@
|
||||
// Acid StepSequencer, a Roland TB303 step sequencer engine clone
|
||||
// author: midilab contact@midilab.co
|
||||
// under MIT license
|
||||
#include "uClock.h"
|
||||
|
||||
#define NOTE_STACK_SIZE 3
|
||||
|
||||
// MIDI clock, start, stop, note on and note off byte definitions - based on MIDI 1.0 Standards.
|
||||
#define MIDI_CLOCK 0xF8
|
||||
#define MIDI_START 0xFA
|
||||
#define MIDI_STOP 0xFC
|
||||
#define NOTE_ON 0x90
|
||||
#define NOTE_OFF 0x80
|
||||
#define MIDI_CC 0xB0
|
||||
|
||||
// sequencer data
|
||||
typedef struct
|
||||
{
|
||||
uint8_t note;
|
||||
int16_t length;
|
||||
} STACK_NOTE_DATA;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t note:7;
|
||||
uint8_t accent:1;
|
||||
uint8_t glide:1;
|
||||
uint8_t rest:1;
|
||||
uint8_t tie:1;
|
||||
uint8_t reserved:5;
|
||||
} SEQUENCER_STEP_DATA;
|
||||
// 2 bytes per step
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SEQUENCER_STEP_DATA step[STEP_MAX_SIZE];
|
||||
int8_t step_init_point;
|
||||
uint8_t step_length;
|
||||
} SEQUENCER_TRACK_DATA;
|
||||
// 32 bytes per 16 step + 2 bytes config = 34 bytes [STEP_MAX_SIZE=16]
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SEQUENCER_TRACK_DATA data;
|
||||
uint8_t step_location;
|
||||
uint8_t channel;
|
||||
bool mute;
|
||||
STACK_NOTE_DATA stack[NOTE_STACK_SIZE];
|
||||
} SEQUENCER_TRACK;
|
||||
|
||||
// main sequencer data is constantly change inside uClock 16PPQN and 96PPQN ISR callbacks, so volatile him!
|
||||
SEQUENCER_TRACK volatile _sequencer[TRACK_NUMBER];
|
||||
|
||||
uint8_t _selected_track = 0;
|
||||
uint8_t _selected_pattern = 0;
|
||||
|
||||
// make sure all above sequencer data are modified atomicly only
|
||||
// eg. ATOMIC(_sequencer[track]data.step[0].accent = 1); ATOMIC(_sequencer[track].data.step_length = 7);
|
||||
|
||||
// shared data to be used for user interface interaction and feedback
|
||||
bool _playing = false;
|
||||
uint8_t _harmonize = 0;
|
||||
uint16_t _step_edit = 0;
|
||||
uint8_t _last_octave = 3;
|
||||
uint8_t _last_note = 0;
|
||||
int8_t _transpose = 0; // zero is centered C
|
||||
uint8_t _selected_mode = 0;
|
||||
|
||||
void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2, uint8_t channel)
|
||||
{
|
||||
// send midi message
|
||||
command = command | (uint8_t)channel;
|
||||
Serial.write(command);
|
||||
Serial.write(byte1);
|
||||
Serial.write(byte2);
|
||||
}
|
||||
|
||||
// The callback function wich will be called by uClock each Pulse of 16PPQN clock resolution. Each call represents exactly one step.
|
||||
void ClockOut16PPQN(uint32_t * tick)
|
||||
{
|
||||
uint8_t step, next_step;
|
||||
uint16_t length;
|
||||
int8_t note;
|
||||
|
||||
for ( uint8_t track = 0; track < TRACK_NUMBER; track++ ) {
|
||||
|
||||
if ( _sequencer[track].mute == true ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
length = NOTE_LENGTH;
|
||||
|
||||
// get actual step location.
|
||||
_sequencer[track].step_location = uint32_t(*tick + _sequencer[track].data.step_init_point) % _sequencer[track].data.step_length;
|
||||
|
||||
// send note on only if this step are not in rest mode
|
||||
if ( _sequencer[track].data.step[_sequencer[track].step_location].rest == 0 ) {
|
||||
|
||||
// check for slide or tie event ahead of _sequencer[track].step_location
|
||||
step = _sequencer[track].step_location;
|
||||
next_step = step;
|
||||
for ( uint8_t i = 1; i < _sequencer[track].data.step_length; i++ ) {
|
||||
next_step = ++next_step % _sequencer[track].data.step_length;
|
||||
if (_sequencer[track].data.step[step].glide == 1 && _sequencer[track].data.step[next_step].rest == 0) {
|
||||
length = NOTE_LENGTH + 5;
|
||||
break;
|
||||
} else if (_sequencer[track].data.step[next_step].tie == 1 && _sequencer[track].data.step[next_step].rest == 1) {
|
||||
length = NOTE_LENGTH + (i * 6);
|
||||
} else if ( _sequencer[track].data.step[next_step].rest == 0 || _sequencer[track].data.step[next_step].tie == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// find a free note stack to fit in
|
||||
for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
|
||||
if ( _sequencer[track].stack[i].length == -1 ) {
|
||||
if ( _harmonize == 1 ) {
|
||||
note = harmonizer(_sequencer[track].data.step[_sequencer[track].step_location].note);
|
||||
} else {
|
||||
note = _sequencer[track].data.step[_sequencer[track].step_location].note;
|
||||
}
|
||||
note += _transpose;
|
||||
// in case transpose push note away from the lower or higher midi note range barrier do not play it
|
||||
if ( note < 0 || note > 127 ) {
|
||||
break;
|
||||
}
|
||||
_sequencer[track].stack[i].note = note;
|
||||
_sequencer[track].stack[i].length = length;
|
||||
// send note on
|
||||
sendMidiMessage(NOTE_ON, note, _sequencer[track].data.step[_sequencer[track].step_location].accent ? ACCENT_VELOCITY : NOTE_VELOCITY, _sequencer[track].channel);
|
||||
sendCVNote(note, track); //add accent here
|
||||
sendGateOn(track);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void clearStackNote(int8_t track = -1)
|
||||
{
|
||||
if ( track <= -1 ) {
|
||||
// clear all tracks stack note
|
||||
for ( uint8_t i = 0; i < TRACK_NUMBER; i++ ) {
|
||||
// clear and send any note off
|
||||
for ( uint8_t j = 0; j < NOTE_STACK_SIZE; j++ ) {
|
||||
ATOMIC(
|
||||
sendMidiMessage(NOTE_OFF, _sequencer[i].stack[j].note, 0, _sequencer[i].channel);
|
||||
_sequencer[i].stack[j].length = -1;
|
||||
sendGateOff(i);
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// clear and send any note off
|
||||
for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
|
||||
ATOMIC(
|
||||
sendMidiMessage(NOTE_OFF, _sequencer[track].stack[i].note, 0, _sequencer[track].channel);
|
||||
_sequencer[track].stack[i].length = -1;
|
||||
sendGateOff(track);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The callback function wich will be called by uClock each Pulse of 96PPQN clock resolution.
|
||||
void ClockOut96PPQN(uint32_t * tick)
|
||||
{
|
||||
uint8_t track;
|
||||
|
||||
// Send MIDI_CLOCK to external hardware
|
||||
Serial.write(MIDI_CLOCK);
|
||||
|
||||
for ( track = 0; track < TRACK_NUMBER; track++ ) {
|
||||
|
||||
// handle note on stack
|
||||
for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
|
||||
if ( _sequencer[track].stack[i].length != -1 ) {
|
||||
--_sequencer[track].stack[i].length;
|
||||
if ( _sequencer[track].stack[i].length == 0 ) {
|
||||
sendMidiMessage(NOTE_OFF, _sequencer[track].stack[i].note, 0, _sequencer[track].channel);
|
||||
_sequencer[track].stack[i].length = -1;
|
||||
sendGateOff(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// user feedback about sequence time events
|
||||
tempoInterface(tick);
|
||||
}
|
||||
|
||||
// The callback function wich will be called when clock starts by using Clock.start() method.
|
||||
void onClockStart()
|
||||
{
|
||||
Serial.write(MIDI_START);
|
||||
_playing = 1;
|
||||
}
|
||||
|
||||
// The callback function wich will be called when clock stops by using Clock.stop() method.
|
||||
void onClockStop()
|
||||
{
|
||||
Serial.write(MIDI_STOP);
|
||||
|
||||
// clear all tracks stack note
|
||||
clearStackNote();
|
||||
|
||||
_playing = 0;
|
||||
}
|
||||
|
||||
void setTrackChannel(uint8_t track, uint8_t channel)
|
||||
{
|
||||
--track;
|
||||
--channel;
|
||||
ATOMIC(_sequencer[track].channel = channel);
|
||||
}
|
||||
|
||||
void initAcidStepSequencer(uint8_t mode)
|
||||
{
|
||||
uint8_t track;
|
||||
|
||||
// Initialize serial communication
|
||||
if ( mode == 0 ) {
|
||||
// the default MIDI serial speed communication at 31250 bits per second
|
||||
Serial.begin(31250);
|
||||
} else if ( mode == 1 ) {
|
||||
// for usage with a PC with a serial to MIDI bridge
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
// Inits the clock
|
||||
uClock.init();
|
||||
|
||||
// Set the callback function for the clock output to send MIDI Sync message.
|
||||
uClock.setClock96PPQNOutput(ClockOut96PPQN);
|
||||
|
||||
// Set the callback function for the step sequencer on 16ppqn
|
||||
uClock.setClock16PPQNOutput(ClockOut16PPQN);
|
||||
|
||||
// Set the callback function for MIDI Start and Stop messages.
|
||||
uClock.setOnClockStartOutput(onClockStart);
|
||||
uClock.setOnClockStopOutput(onClockStop);
|
||||
|
||||
// Set the clock BPM to 126 BPM
|
||||
uClock.setTempo(126);
|
||||
|
||||
// initing sequencer memory data
|
||||
for ( track = 0; track < TRACK_NUMBER; track++ ) {
|
||||
|
||||
_sequencer[track].channel = track;
|
||||
_sequencer[track].data.step_init_point = 0;
|
||||
_sequencer[track].data.step_length = STEP_MAX_SIZE;
|
||||
_sequencer[track].step_location = 0;
|
||||
_sequencer[track].mute = false;
|
||||
|
||||
// initing note data
|
||||
for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) {
|
||||
_sequencer[track].data.step[i].note = 48;
|
||||
_sequencer[track].data.step[i].accent = 0;
|
||||
_sequencer[track].data.step[i].glide = 0;
|
||||
_sequencer[track].data.step[i].tie = 0;
|
||||
_sequencer[track].data.step[i].rest = 0;
|
||||
}
|
||||
|
||||
// initing note stack data
|
||||
for ( uint8_t i = 0; i < NOTE_STACK_SIZE; i++ ) {
|
||||
_sequencer[track].stack[i].note = 0;
|
||||
_sequencer[track].stack[i].length = -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
59
AcidTHarmonizer.ino
Normal file
59
AcidTHarmonizer.ino
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// MODE temperament
|
||||
//
|
||||
// MAJOR MODES
|
||||
uint8_t _ionian[8] = { 0, 2, 4, 5, 7, 9, 11, 12 };
|
||||
uint8_t _dorian[8] = { 0, 2, 3, 5, 7, 9, 10, 12 };
|
||||
uint8_t _phrygian[8] = { 0, 1, 3, 5, 7, 8, 10, 12 };
|
||||
uint8_t _lydian[8] = { 0, 2, 4, 6, 7, 9, 11, 12 };
|
||||
uint8_t _mixolidian[8] = { 0, 2, 4, 5, 7, 9, 10, 12 };
|
||||
uint8_t _aeolian[8] = { 0, 2, 3, 5, 7, 8, 10, 12 };
|
||||
uint8_t _locrian[8] = { 0, 1, 3, 5, 6, 8, 10, 12 };
|
||||
|
||||
// MINOR MODES
|
||||
// ascending melodic minor
|
||||
uint8_t _melodic[8] = { 0, 2, 3, 5, 7, 9, 11, 12 };
|
||||
// phrygian ♮6 (dorian ♭2)
|
||||
uint8_t _phrygian6[8] = { 0, 1, 3, 5, 7, 9, 10, 12 };
|
||||
// lydian augmented (lydian ♯5)
|
||||
uint8_t _lydian5[8] = { 0, 2, 4, 6, 8, 9, 11, 12 };
|
||||
// lydian dominant (also, "lydian ♭7", acoustic scale, or mixolydian ♯4)
|
||||
uint8_t _lydian7[8] = { 0, 2, 4, 6, 7, 9, 10, 12 };
|
||||
// mixolydian ♭6 (or melodic major or simply "fifth mode")
|
||||
uint8_t _mixolydian6[8] = { 0, 2, 4, 5, 7, 8, 10, 12 };
|
||||
// locrian ♮2 (also known as "half-diminished" scale)
|
||||
uint8_t _locrian2[8] = { 0, 2, 3, 5, 6, 8, 10, 12 };
|
||||
// super Locrian (also "altered dominant scale", or "altered scale")
|
||||
uint8_t _super_locrian[8] = { 0, 1, 3, 4, 6, 8, 10, 12 };
|
||||
|
||||
uint8_t * _mode[] = {
|
||||
// MAJOR MODES
|
||||
_ionian,
|
||||
_dorian,
|
||||
_phrygian,
|
||||
_lydian,
|
||||
_mixolidian,
|
||||
_aeolian,
|
||||
_locrian,
|
||||
// MINOR MODES
|
||||
_melodic,
|
||||
_phrygian6,
|
||||
_lydian5,
|
||||
_lydian7,
|
||||
_mixolydian6,
|
||||
_locrian2,
|
||||
_super_locrian
|
||||
};
|
||||
|
||||
#define MODES_NUMBER (sizeof(_mode) / sizeof(uint16_t)) // its array pointer we are holding here
|
||||
|
||||
uint8_t harmonizer(uint8_t note)
|
||||
{
|
||||
uint8_t octave, interval;
|
||||
|
||||
octave = floor(note/12);
|
||||
interval = floor((note%12)/1.5);
|
||||
|
||||
return (octave*12) + _mode[_selected_mode][interval];
|
||||
}
|
||||
|
||||
48
AciduinoCV.ino
Normal file
48
AciduinoCV.ino
Normal file
@ -0,0 +1,48 @@
|
||||
// Acid StepSequencer, a Roland TB303 step sequencer engine clone
|
||||
// author: midilab contact@midilab.co
|
||||
// under MIT license
|
||||
// CV functionality added by Oleksiy Hrachov
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include "MCP4725.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
MCP4725 DAC1(DAC_1_ADDR);
|
||||
MCP4725 DAC2(DAC_2_ADDR);
|
||||
|
||||
bool updateCV1 = true;
|
||||
bool updateCV2 = true;
|
||||
bool sendGate1 = false;
|
||||
bool sendGate2 = false;
|
||||
uint8_t CV1Note;
|
||||
uint8_t CV2Note;
|
||||
|
||||
void setup()
|
||||
{
|
||||
// AcidStepSequencer Interface
|
||||
initAcidStepSequencer(MIDI_MODE);
|
||||
setTrackChannel(1, TRACK1_CHANNEL);
|
||||
setTrackChannel(2, TRACK2_CHANNEL);
|
||||
|
||||
// pins, buttons, leds and pots config
|
||||
configureInterface();
|
||||
|
||||
// last pattern user had load before power off
|
||||
loadLastPattern();
|
||||
|
||||
Wire.begin();
|
||||
Wire.setClock(400000);
|
||||
DAC1.begin();
|
||||
DAC2.begin();
|
||||
DAC1.setMaxVoltage(5.1);
|
||||
DAC2.setMaxVoltage(5.1);
|
||||
}
|
||||
|
||||
// User interaction goes here
|
||||
void loop()
|
||||
{
|
||||
processInterface();
|
||||
processCV();
|
||||
}
|
||||
62
CV.ino
Normal file
62
CV.ino
Normal file
@ -0,0 +1,62 @@
|
||||
float noteToVoltage(uint8_t note) {
|
||||
float voltage = (note - 24) * 0.083;
|
||||
return voltage;
|
||||
}
|
||||
|
||||
void sendCVNote(uint8_t note, uint8_t track) {
|
||||
if (track == 0 && note != CV1Note) {
|
||||
CV1Note = note;
|
||||
updateCV1 = true;
|
||||
}
|
||||
if (track == 1 && note != CV2Note) {
|
||||
CV2Note = note;
|
||||
updateCV2 = true;
|
||||
}
|
||||
}
|
||||
|
||||
void sendGateOn(uint8_t track) {
|
||||
if (track == 0) {
|
||||
sendGate1 = true;
|
||||
}
|
||||
if (track == 1) {
|
||||
sendGate2 = true;
|
||||
}
|
||||
}
|
||||
|
||||
void sendGateOff(uint8_t track) {
|
||||
if (track == 0) {
|
||||
sendGate1 = false;
|
||||
}
|
||||
if (track == 1) {
|
||||
sendGate2 = false;
|
||||
}
|
||||
}
|
||||
|
||||
void sendVoltage(uint8_t voltage, uint8_t dac) {
|
||||
if (dac == 0) {
|
||||
DAC1.setVoltage(voltage);
|
||||
} else {
|
||||
DAC2.setVoltage(voltage);
|
||||
}
|
||||
}
|
||||
|
||||
void processCV() {
|
||||
if (updateCV1) {
|
||||
sendVoltage(noteToVoltage(CV1Note), 0);
|
||||
updateCV1 = false;
|
||||
}
|
||||
if (updateCV2) {
|
||||
//sendVoltage(noteToVoltage(CV2Note), 1);
|
||||
//updateCV2 = false;
|
||||
}
|
||||
if (sendGate1) {
|
||||
sendVoltage(5, 1);
|
||||
} else {
|
||||
sendVoltage(0, 1);
|
||||
}
|
||||
if (sendGate2) {
|
||||
//sendVoltage(5, 1);
|
||||
} else {
|
||||
//sendVoltage(0, 1);
|
||||
}
|
||||
}
|
||||
33
CV_Test/CV_Test.ino
Normal file
33
CV_Test/CV_Test.ino
Normal file
@ -0,0 +1,33 @@
|
||||
#include <Wire.h>
|
||||
#include "MCP4725.h"
|
||||
|
||||
MCP4725 DAC1(0x60);
|
||||
MCP4725 DAC2(0x61);
|
||||
|
||||
float output1 = 0;
|
||||
float output2 = 5.1;
|
||||
|
||||
void setup() {
|
||||
Wire.begin();
|
||||
DAC1.begin();
|
||||
DAC2.begin();
|
||||
DAC1.setMaxVoltage(5.1);
|
||||
DAC2.setMaxVoltage(5.1);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (output1 < 5.1) {
|
||||
output1 += 0.1;
|
||||
} else {
|
||||
output1 = 0;
|
||||
}
|
||||
DAC1.setVoltage(output1);
|
||||
|
||||
if (output2 > 0) {
|
||||
output2 -= 0.1;
|
||||
} else {
|
||||
output2 = 5.1;
|
||||
}
|
||||
DAC2.setVoltage(output2);
|
||||
delay(1);
|
||||
}
|
||||
298
HardwareInterface.ino
Normal file
298
HardwareInterface.ino
Normal file
@ -0,0 +1,298 @@
|
||||
#include <EEPROM.h>
|
||||
|
||||
// pot data
|
||||
typedef struct
|
||||
{
|
||||
uint8_t pin;
|
||||
uint16_t state;
|
||||
bool lock;
|
||||
} POT_DATA;
|
||||
|
||||
// button data
|
||||
typedef struct
|
||||
{
|
||||
uint8_t pin;
|
||||
bool state;
|
||||
uint8_t hold_seconds;
|
||||
bool hold_trigger;
|
||||
} BUTTON_DATA;
|
||||
|
||||
POT_DATA _pot[POT_NUMBER];
|
||||
BUTTON_DATA _button[BUTTON_NUMBER];
|
||||
|
||||
// pattern memory layout 72 bytes(2 tracks with 16 steps)
|
||||
// byte1: pattern_exist
|
||||
// byte2: _transpose
|
||||
// byte3: _harmonize
|
||||
// byte4: _selected_mode
|
||||
// byte5...: _sequencer[0].data, _sequencer[1].data
|
||||
// EEPROM data access: 1024 bytes total
|
||||
// total 14 patterns
|
||||
// use for load and save pattern
|
||||
#define PATTERN_SIZE (sizeof(SEQUENCER_TRACK_DATA)*TRACK_NUMBER)+4
|
||||
#define PATTERN_NUMBER 14
|
||||
|
||||
SEQUENCER_TRACK_DATA _track_data;
|
||||
|
||||
void loadLastPattern()
|
||||
{
|
||||
uint8_t last_pattern;
|
||||
|
||||
EEPROM.get(1023, last_pattern);
|
||||
|
||||
if ( last_pattern < PATTERN_NUMBER ) {
|
||||
loadPattern(last_pattern);
|
||||
}
|
||||
}
|
||||
|
||||
void resetPattern(uint8_t number)
|
||||
{
|
||||
uint16_t eeprom_address = PATTERN_SIZE;
|
||||
|
||||
// get address base for pattern
|
||||
eeprom_address *= number;
|
||||
|
||||
// load defaults
|
||||
ATOMIC(
|
||||
_transpose = 0;
|
||||
_harmonize = 0;
|
||||
_selected_mode = 0;
|
||||
);
|
||||
// reset sequencer data
|
||||
for ( uint8_t track = 0; track < TRACK_NUMBER; track++ ) {
|
||||
ATOMIC(_sequencer[track].mute = true);
|
||||
clearStackNote(track);
|
||||
_track_data.step_init_point = 0;
|
||||
_track_data.step_length = STEP_MAX_SIZE;
|
||||
// initing note data
|
||||
for ( uint8_t i = 0; i < STEP_MAX_SIZE; i++ ) {
|
||||
_track_data.step[i].note = 48;
|
||||
_track_data.step[i].accent = 0;
|
||||
_track_data.step[i].glide = 0;
|
||||
_track_data.step[i].rest = 0;
|
||||
}
|
||||
|
||||
ATOMIC(memcpy((void*)&_sequencer[track].data, &_track_data, sizeof(SEQUENCER_TRACK_DATA)));
|
||||
ATOMIC(_sequencer[track].mute = false);
|
||||
}
|
||||
// mark pattern slot as not in use
|
||||
EEPROM.write(eeprom_address, 0);
|
||||
}
|
||||
|
||||
void loadPattern(uint8_t number)
|
||||
{
|
||||
uint16_t eeprom_address = PATTERN_SIZE;
|
||||
uint8_t pattern_exist, harmonize, selected_mode = 0;
|
||||
int8_t transpose = 0;
|
||||
|
||||
if ( number >= PATTERN_NUMBER ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get address base for pattern
|
||||
eeprom_address *= number;
|
||||
|
||||
// do we have pattern data to read it here?
|
||||
EEPROM.get(eeprom_address, pattern_exist);
|
||||
if ( pattern_exist != 1 ) {
|
||||
resetPattern(number);
|
||||
// save last pattern loaded
|
||||
EEPROM.write(1023, number);
|
||||
return;
|
||||
}
|
||||
|
||||
// global pattern config
|
||||
EEPROM.get(++eeprom_address, transpose);
|
||||
EEPROM.get(++eeprom_address, harmonize);
|
||||
EEPROM.get(++eeprom_address, selected_mode);
|
||||
// constrains to avoid trash data from memory
|
||||
if ( transpose > 12 ) {
|
||||
transpose = 12;
|
||||
} else if ( transpose < -12 ) {
|
||||
transpose = -12;
|
||||
}
|
||||
if (selected_mode >= MODES_NUMBER) {
|
||||
selected_mode = MODES_NUMBER-1;
|
||||
}
|
||||
ATOMIC(
|
||||
_transpose = transpose;
|
||||
_harmonize = harmonize;
|
||||
_selected_mode = selected_mode;
|
||||
);
|
||||
// track data
|
||||
for (uint8_t track=0; track < TRACK_NUMBER; track++) {
|
||||
ATOMIC(_sequencer[track].mute = true);
|
||||
clearStackNote(track);
|
||||
EEPROM.get(++eeprom_address, _track_data); // 34 bytes long
|
||||
// constrains to avoid trash data from memory
|
||||
if ( _track_data.step_length > STEP_MAX_SIZE ) {
|
||||
_track_data.step_length = STEP_MAX_SIZE;
|
||||
}
|
||||
ATOMIC(memcpy((void*)&_sequencer[track].data, &_track_data, sizeof(SEQUENCER_TRACK_DATA)));
|
||||
ATOMIC(_sequencer[track].mute = false);
|
||||
eeprom_address += (sizeof(SEQUENCER_TRACK_DATA)-1);
|
||||
}
|
||||
|
||||
// save last pattern loaded
|
||||
EEPROM.write(1023, number);
|
||||
|
||||
_selected_pattern = number;
|
||||
|
||||
}
|
||||
|
||||
void savePattern(uint8_t number)
|
||||
{
|
||||
uint16_t eeprom_address = PATTERN_SIZE;
|
||||
|
||||
if ( number >= PATTERN_NUMBER ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get address base for pattern
|
||||
eeprom_address *= number;
|
||||
|
||||
// mark pattern slot as in use pattern_exist
|
||||
EEPROM.write(eeprom_address, 1);
|
||||
// global pattern config
|
||||
EEPROM.write(++eeprom_address, _transpose);
|
||||
EEPROM.write(++eeprom_address, _harmonize);
|
||||
EEPROM.write(++eeprom_address, _selected_mode);
|
||||
// track data
|
||||
for (uint8_t track=0; track < TRACK_NUMBER; track++) {
|
||||
memcpy(&_track_data, (void*)&_sequencer[track].data, sizeof(SEQUENCER_TRACK_DATA));
|
||||
EEPROM.put(++eeprom_address, _track_data); // 34 bytes long
|
||||
eeprom_address += (sizeof(SEQUENCER_TRACK_DATA)-1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void connectPot(uint8_t pot_id, uint8_t pot_pin)
|
||||
{
|
||||
_pot[pot_id].pin = pot_pin;
|
||||
// get first state data
|
||||
_pot[pot_id].state = analogRead(_pot[pot_id].pin);
|
||||
_pot[pot_id].lock = true;
|
||||
}
|
||||
|
||||
void connectButton(uint8_t button_id, uint8_t button_pin)
|
||||
{
|
||||
_button[button_id].pin = button_pin;
|
||||
// use internal pullup for buttons
|
||||
pinMode(_button[button_id].pin, INPUT_PULLUP);
|
||||
// get first state data
|
||||
_button[button_id].state = digitalRead(_button[button_id].pin);
|
||||
_button[button_id].hold_seconds = 0;
|
||||
_button[button_id].hold_trigger = false;
|
||||
}
|
||||
|
||||
void lockPotsState(bool lock)
|
||||
{
|
||||
for ( uint8_t i = 0; i < POT_NUMBER; i++ ) {
|
||||
_pot[i].lock = lock;
|
||||
}
|
||||
}
|
||||
|
||||
bool pressed(uint8_t button_id)
|
||||
{
|
||||
bool value;
|
||||
|
||||
value = digitalRead(_button[button_id].pin);
|
||||
|
||||
// using internal pullup pressed button goes LOW
|
||||
if ( value != _button[button_id].state && value == LOW ) {
|
||||
_button[button_id].state = value;
|
||||
return true;
|
||||
} else {
|
||||
_button[button_id].state = value;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool doublePressed(uint8_t button1_id, uint8_t button2_id)
|
||||
{
|
||||
bool value1, value2;
|
||||
|
||||
value1 = digitalRead(_button[button1_id].pin);
|
||||
value2 = digitalRead(_button[button2_id].pin);
|
||||
|
||||
// using internal pullup pressed button goes LOW
|
||||
if ( value1 == LOW && value2 == LOW ) {
|
||||
_button[button1_id].state = LOW;
|
||||
_button[button2_id].state = LOW;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool holded(uint8_t button_id, uint8_t seconds)
|
||||
{
|
||||
bool value;
|
||||
|
||||
value = digitalRead(_button[button_id].pin);
|
||||
|
||||
// using internal pullup pressed button goes LOW
|
||||
if ( _button[button_id].hold_trigger == false && value == LOW ) {
|
||||
if ( _button[button_id].hold_seconds == 0 ) {
|
||||
_button[button_id].hold_seconds = (uint8_t)(millis()/1000);
|
||||
} else if ( abs((uint8_t)(millis()/1000) - _button[button_id].hold_seconds) >= seconds ) {
|
||||
_button[button_id].hold_trigger = true; // avoid released triger after action.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if ( value == HIGH ) {
|
||||
_button[button_id].hold_trigger = false;
|
||||
_button[button_id].hold_seconds = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool released(uint8_t button_id)
|
||||
{
|
||||
bool value;
|
||||
|
||||
value = digitalRead(_button[button_id].pin);
|
||||
|
||||
// using internal pullup released button goes HIGH
|
||||
if ( value != _button[button_id].state && value == HIGH && _button[button_id].hold_trigger == false ) {
|
||||
_button[button_id].state = value;
|
||||
return true;
|
||||
} else {
|
||||
_button[button_id].state = value;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int16_t getPotChanges(uint8_t pot_id, uint16_t min_value, uint16_t max_value)
|
||||
{
|
||||
uint16_t value, value_ranged, last_value_ranged;
|
||||
uint8_t pot_sensitivity = POT_SENSITIVITY;
|
||||
|
||||
// get absolute value
|
||||
value = analogRead(_pot[pot_id].pin);
|
||||
|
||||
// range that value and our last_value
|
||||
value_ranged = (value / (ADC_RESOLUTION / ((max_value - min_value) + 1))) + min_value;
|
||||
last_value_ranged = (_pot[pot_id].state / (ADC_RESOLUTION / ((max_value - min_value) + 1))) + min_value;
|
||||
|
||||
// a lock system to not mess with some data(pots are terrible for some kinda of user interface data controls, but lets keep it low cost!)
|
||||
if ( _pot[pot_id].lock == true ) {
|
||||
// user needs to move 1/8 of total adc range to get pot unlocked
|
||||
if ( abs(value - _pot[pot_id].state) < (ADC_RESOLUTION/8) ) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( abs(value_ranged - last_value_ranged) >= pot_sensitivity ) {
|
||||
_pot[pot_id].state = value;
|
||||
if ( _pot[pot_id].lock == true ) {
|
||||
_pot[pot_id].lock = false;
|
||||
}
|
||||
if ( value_ranged > max_value ) {
|
||||
value_ranged = max_value;
|
||||
}
|
||||
return value_ranged;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
55
MidiExternalSync.ino
Normal file
55
MidiExternalSync.ino
Normal file
@ -0,0 +1,55 @@
|
||||
// MIDI clock, start, stop, note on and note off byte definitions - based on MIDI 1.0 Standards.
|
||||
#define MIDI_CLOCK 0xF8
|
||||
#define MIDI_START 0xFA
|
||||
#define MIDI_STOP 0xFC
|
||||
|
||||
byte in_data = 0X00;
|
||||
|
||||
// avr general timer2 - 8bits
|
||||
// 250usecs timmer to read midi input clock/control messages
|
||||
void setExternalSync(bool on)
|
||||
{
|
||||
if (on == true) {
|
||||
noInterrupts();
|
||||
TCCR2A = 0; // set entire TCCR2A register to 0
|
||||
TCCR2B = 0; // same for TCCR2B
|
||||
TCNT2 = 0; // initialize counter value to 0
|
||||
// set compare match register for 4000 Hz increments
|
||||
OCR2A = 124; // = 16000000 / (32 * 4000) - 1 (must be <256)
|
||||
// turn on CTC mode
|
||||
TCCR2B |= (1 << WGM21);
|
||||
// Set CS22, CS21 and CS20 bits for 32 prescaler
|
||||
TCCR2B |= (0 << CS22) | (1 << CS21) | (1 << CS20);
|
||||
// enable timer compare interrupt
|
||||
TIMSK2 |= (1 << OCIE2A);
|
||||
interrupts();
|
||||
uClock.setMode(uClock.EXTERNAL_CLOCK);
|
||||
} else {
|
||||
uClock.setMode(uClock.INTERNAL_CLOCK);
|
||||
noInterrupts();
|
||||
// disable timer compare interrupt
|
||||
TIMSK2 |= (0 << OCIE2A);
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
ISR(TIMER2_COMPA_vect, ISR_NOBLOCK)
|
||||
{
|
||||
if(Serial.available() > 0) {
|
||||
in_data = Serial.read();
|
||||
switch (in_data) {
|
||||
case MIDI_CLOCK:
|
||||
uClock.clockMe();
|
||||
return;
|
||||
case MIDI_START:
|
||||
uClock.start();
|
||||
return;
|
||||
case MIDI_STOP:
|
||||
uClock.stop();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
187
PageGenerative.ino
Normal file
187
PageGenerative.ino
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
[generative]
|
||||
knobs: ramdon low range note, ramdon high range note, number of notes to use(1 to 12 or 1 to 7 if harmonized), ramdomizer signatures
|
||||
|
||||
buttons: harmonizer scale-, harmonizer scale+, ramdomize it, shift left sequence, shift rigth sequence, play/stop
|
||||
*/
|
||||
|
||||
uint8_t _lower_note = 36;
|
||||
uint8_t _range_note = 34;
|
||||
uint8_t _accent_probability = ACCENT_PROBABILITY_GENERATION;
|
||||
uint8_t _glide_probability = GLIDE_PROBABILITY_GENERATION;
|
||||
uint8_t _rest_probability = REST_PROBABILITY_GENERATION;
|
||||
uint8_t _tie_probability = TIE_PROBABILITY_GENERATION;
|
||||
uint8_t _number_of_tones = 3;
|
||||
|
||||
uint8_t _allowed_tones[12] = {0};
|
||||
|
||||
void shiftSequence(int8_t offset)
|
||||
{
|
||||
uint8_t shift = _sequencer[_selected_track].data.step_init_point+offset;
|
||||
// clear stack note(also send any note on to off) before shift sequence init point
|
||||
//clearStackNote(_selected_track);
|
||||
ATOMIC(_sequencer[_selected_track].data.step_init_point = shift);
|
||||
}
|
||||
|
||||
void processGenerativeButtons()
|
||||
{
|
||||
|
||||
if ( released(GENERIC_BUTTON_1) ) {
|
||||
// previous harmonic mode
|
||||
if ( _selected_mode > 0 ) {
|
||||
ATOMIC(--_selected_mode);
|
||||
} else if ( _selected_mode == 0 ) {
|
||||
ATOMIC(_harmonize = 0);
|
||||
}
|
||||
}
|
||||
|
||||
if ( released(GENERIC_BUTTON_2) ) {
|
||||
// next harmonic mode
|
||||
if ( _selected_mode < MODES_NUMBER-1 ) {
|
||||
if ( _harmonize == 0 ) {
|
||||
ATOMIC(_harmonize = 1);
|
||||
} else {
|
||||
ATOMIC(++_selected_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( pressed(GENERIC_BUTTON_3) ) {
|
||||
// ramdomize it!
|
||||
acidRandomize();
|
||||
}
|
||||
|
||||
if ( pressed(GENERIC_BUTTON_4) ) {
|
||||
shiftSequence(-1);
|
||||
}
|
||||
|
||||
if ( pressed(GENERIC_BUTTON_5) ) {
|
||||
shiftSequence(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void processGenerativeLeds()
|
||||
{
|
||||
|
||||
if ( _harmonize == 1 ) {
|
||||
if ( _selected_mode == MODES_NUMBER-1 ) {
|
||||
digitalWrite(GENERIC_LED_1, LOW);
|
||||
digitalWrite(GENERIC_LED_2, HIGH);
|
||||
} else {
|
||||
digitalWrite(GENERIC_LED_1, LOW);
|
||||
digitalWrite(GENERIC_LED_2, LOW);
|
||||
}
|
||||
} else {
|
||||
digitalWrite(GENERIC_LED_1, HIGH);
|
||||
digitalWrite(GENERIC_LED_2, LOW);
|
||||
}
|
||||
|
||||
digitalWrite(GENERIC_LED_3, LOW);
|
||||
digitalWrite(GENERIC_LED_4, LOW);
|
||||
digitalWrite(GENERIC_LED_5, LOW);
|
||||
|
||||
}
|
||||
|
||||
void processGenerativePots()
|
||||
{
|
||||
uint16_t value;
|
||||
|
||||
// GENERIC_POT_1: lower range note to be generated
|
||||
value = getPotChanges(GENERIC_POT_1, 0, 127);
|
||||
if ( value != -1 ) {
|
||||
_lower_note = value;
|
||||
}
|
||||
|
||||
// GENERIC_POT_2: high range note to be generated
|
||||
value = getPotChanges(GENERIC_POT_2, 0, 127);
|
||||
if ( value != -1 ) {
|
||||
_range_note = value;
|
||||
}
|
||||
|
||||
// GENERIC_POT_3: number of notes to use on sequence(1 to 12 or 1 to 7 if harmonized)
|
||||
if ( _harmonize == 1 ) {
|
||||
value = getPotChanges(GENERIC_POT_3, 1, 7);
|
||||
} else if ( _harmonize == 0 ) {
|
||||
value = getPotChanges(GENERIC_POT_3, 1, 12);
|
||||
}
|
||||
if ( value != -1 ) {
|
||||
_number_of_tones = value;
|
||||
uint8_t note = 0;
|
||||
for ( uint8_t i=0; i < 12; i++ ) {
|
||||
if ( i%(12/_number_of_tones) == 0 && i != 0 ) {
|
||||
note += (12/_number_of_tones);
|
||||
}
|
||||
_allowed_tones[i] = note;
|
||||
}
|
||||
}
|
||||
|
||||
// GENERIC_POT_4: ramdomizer signatures
|
||||
value = getPotChanges(GENERIC_POT_4, 0, 80);
|
||||
if ( value != -1 ) {
|
||||
//_accent_probability = value???;
|
||||
//_glide_probability value???;
|
||||
_rest_probability = 80-value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uint8_t getNoteByMaxNumOfTones(uint8_t note)
|
||||
{
|
||||
uint8_t octave, relative_note;
|
||||
|
||||
octave = note/12;
|
||||
relative_note = note%12;
|
||||
return _allowed_tones[relative_note] + _lower_note + (octave*12);
|
||||
}
|
||||
|
||||
void acidRandomize()
|
||||
{
|
||||
uint8_t note, high_note, accent, glide, tie, rest, last_step;
|
||||
|
||||
// random it all
|
||||
ATOMIC(_sequencer[_selected_track].mute = true);
|
||||
clearStackNote(_selected_track);
|
||||
|
||||
for ( uint16_t i = 0; i < STEP_MAX_SIZE; i++ ) {
|
||||
|
||||
// step on/off
|
||||
_sequencer[_selected_track].data.step[i].rest = random(0, 100) < _rest_probability ? 0 : 1;
|
||||
|
||||
// random tie and reset accent and glide in case of a rest
|
||||
if (_sequencer[_selected_track].data.step[i].rest) {
|
||||
|
||||
_sequencer[_selected_track].data.step[i].note = 36;
|
||||
_sequencer[_selected_track].data.step[i].accent = 0;
|
||||
_sequencer[_selected_track].data.step[i].glide = 0;
|
||||
_sequencer[_selected_track].data.step[i].tie = 0;
|
||||
|
||||
if (i == 0) {
|
||||
last_step = STEP_MAX_SIZE-1;
|
||||
} else {
|
||||
last_step = i-1;
|
||||
}
|
||||
|
||||
// only tie probrablity when last step has a note or another tie event
|
||||
if (_sequencer[_selected_track].data.step[last_step].rest == 0 || _sequencer[_selected_track].data.step[last_step].tie == 1)
|
||||
_sequencer[_selected_track].data.step[i].tie = random(0, 100) < _tie_probability ? 1 : 0;
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
high_note = _lower_note+_range_note;
|
||||
if ( high_note > 127 ) {
|
||||
high_note = 127;
|
||||
}
|
||||
note = getNoteByMaxNumOfTones(random(_lower_note, high_note));
|
||||
|
||||
accent = random(0, 100) < _accent_probability ? 1 : 0;
|
||||
glide = random(0, 100) < _glide_probability ? 1 : 0;
|
||||
|
||||
_sequencer[_selected_track].data.step[i].note = note;
|
||||
_sequencer[_selected_track].data.step[i].accent = accent;
|
||||
_sequencer[_selected_track].data.step[i].glide = glide;
|
||||
}
|
||||
ATOMIC(_sequencer[_selected_track].mute = false);
|
||||
}
|
||||
177
PageLive.ino
Normal file
177
PageLive.ino
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
[midi controller]
|
||||
knobs: cutoff freq./decay, resonance/accent, env mod/tunning, tempo
|
||||
|
||||
buttons: previous pattern, next pattern, ctrl A/ctrl B, tempo -, tempo +, play/stop
|
||||
*/
|
||||
// TODO: implement pickup by value for controllers
|
||||
|
||||
uint8_t _selected_ctrl = 0;
|
||||
// used for led visual feedback on buttons hold action
|
||||
bool _pattern_saved = false;
|
||||
bool _pattern_cleared = false;
|
||||
uint32_t _page_live_blink_timer = 0;
|
||||
uint32_t _feedback_blink_timer = 0;
|
||||
|
||||
void processControllerButtons()
|
||||
{
|
||||
|
||||
// previous pattern
|
||||
if ( released(GENERIC_BUTTON_1) ) {
|
||||
if ( _selected_pattern != 0 ) {
|
||||
lockPotsState(true);
|
||||
// auto save?
|
||||
//savePattern(_selected_pattern);
|
||||
loadPattern(--_selected_pattern);
|
||||
}
|
||||
}
|
||||
|
||||
// next pattern
|
||||
if ( released(GENERIC_BUTTON_2) ) {
|
||||
if ( _selected_pattern < PATTERN_NUMBER-1 ) {
|
||||
lockPotsState(true);
|
||||
// auto save?
|
||||
//savePattern(_selected_pattern);
|
||||
loadPattern(++_selected_pattern);
|
||||
}
|
||||
}
|
||||
|
||||
// save pattern
|
||||
if ( holded(GENERIC_BUTTON_1, 2) ) {
|
||||
savePattern(_selected_pattern);
|
||||
_pattern_saved = true;
|
||||
_feedback_blink_timer = millis();
|
||||
}
|
||||
|
||||
// reset and delete pattern
|
||||
if ( holded(GENERIC_BUTTON_2, 2) ) {
|
||||
resetPattern(_selected_pattern);
|
||||
_pattern_cleared = true;
|
||||
_feedback_blink_timer = millis();
|
||||
}
|
||||
|
||||
// toogle between ctrl A and ctrl B setup for potentiometers
|
||||
if ( pressed(GENERIC_BUTTON_3) ) {
|
||||
lockPotsState(true);
|
||||
_selected_ctrl = !_selected_ctrl;
|
||||
}
|
||||
|
||||
// decrement 1 bpm from tempo
|
||||
if ( pressed(GENERIC_BUTTON_4) ) {
|
||||
uClock.setTempo(uClock.getTempo()-1);
|
||||
}
|
||||
|
||||
// increment 1 bpm from tempo
|
||||
if ( pressed(GENERIC_BUTTON_5) ) {
|
||||
uClock.setTempo(uClock.getTempo()+1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void processControllerLeds()
|
||||
{
|
||||
static bool blink_state = true;
|
||||
|
||||
// blink interface here for button 3 to 5
|
||||
if ( millis() - _page_live_blink_timer >= 150 ) {
|
||||
blink_state = !blink_state;
|
||||
_page_live_blink_timer = millis();
|
||||
}
|
||||
|
||||
if ( _pattern_saved == true ) {
|
||||
digitalWrite(GENERIC_LED_1 , blink_state);
|
||||
if ( millis() - _feedback_blink_timer >= 600 ) {
|
||||
_pattern_saved = false;
|
||||
}
|
||||
} else if ( _selected_pattern == 0 ) { // first pattern?
|
||||
digitalWrite(GENERIC_LED_1 , HIGH);
|
||||
} else {
|
||||
digitalWrite(GENERIC_LED_1 , LOW);
|
||||
}
|
||||
|
||||
if ( _pattern_cleared == true ) {
|
||||
digitalWrite(GENERIC_LED_2, blink_state);
|
||||
if ( millis() - _feedback_blink_timer >= 600 ) {
|
||||
_pattern_cleared = false;
|
||||
}
|
||||
} else if ( _selected_pattern == PATTERN_NUMBER-1 ) { // last pattern?
|
||||
digitalWrite(GENERIC_LED_2 , HIGH);
|
||||
} else {
|
||||
digitalWrite(GENERIC_LED_2 , LOW);
|
||||
}
|
||||
|
||||
if ( _selected_ctrl == 0 ) {
|
||||
digitalWrite(GENERIC_LED_3, LOW);
|
||||
} else if ( _selected_ctrl == 1 ) {
|
||||
digitalWrite(GENERIC_LED_3, HIGH);
|
||||
}
|
||||
|
||||
digitalWrite(GENERIC_LED_4, LOW);
|
||||
digitalWrite(GENERIC_LED_5, LOW);
|
||||
}
|
||||
|
||||
void processControllerPots()
|
||||
{
|
||||
uint16_t value;
|
||||
uint8_t ctrl;
|
||||
|
||||
#ifdef USE_MIDI_CTRL
|
||||
|
||||
// GENERIC_POT_1: cutoff freq./decay
|
||||
value = getPotChanges(GENERIC_POT_1, 0, 127);
|
||||
if ( value != -1 ) {
|
||||
// send cc
|
||||
if ( _selected_ctrl == 0 ) {
|
||||
ctrl = MIDI_CTRL_CUTOFF;
|
||||
} else if ( _selected_ctrl == 1 ) {
|
||||
ctrl = MIDI_CTRL_DECAY;
|
||||
}
|
||||
ATOMIC(sendMidiMessage(MIDI_CC, ctrl, value, _sequencer[_selected_track].channel))
|
||||
}
|
||||
|
||||
// GENERIC_POT_2: resonance/accent
|
||||
value = getPotChanges(GENERIC_POT_2, 0, 127);
|
||||
if ( value != -1 ) {
|
||||
// send cc
|
||||
if ( _selected_ctrl == 0 ) {
|
||||
ctrl = MIDI_CTRL_RESONANCE;
|
||||
} else if ( _selected_ctrl == 1 ) {
|
||||
ctrl = MIDI_CTRL_ACCENT;
|
||||
}
|
||||
ATOMIC(sendMidiMessage(MIDI_CC, ctrl, value, _sequencer[_selected_track].channel))
|
||||
}
|
||||
|
||||
// GENERIC_POT_3: env mod/wave
|
||||
value = getPotChanges(GENERIC_POT_3, 0, 127);
|
||||
if ( value != -1 ) {
|
||||
// send cc
|
||||
if ( _selected_ctrl == 0 ) {
|
||||
ctrl = MIDI_CTRL_ENVMOD;
|
||||
} else if ( _selected_ctrl == 1 ) {
|
||||
ctrl = MIDI_CTRL_WAVE;
|
||||
}
|
||||
ATOMIC(sendMidiMessage(MIDI_CC, ctrl, value, _sequencer[_selected_track].channel))
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// GENERIC_POT_4: sequencer step length/global harmonic mode transpose
|
||||
if ( _selected_ctrl == 0 ) {
|
||||
value = getPotChanges(GENERIC_POT_4, 1, STEP_MAX_SIZE);
|
||||
if ( value != -1 ) {
|
||||
//clearStackNote(_selected_track);
|
||||
ATOMIC(_sequencer[_selected_track].data.step_length = value);
|
||||
if ( _step_edit >= _sequencer[_selected_track].data.step_length ) {
|
||||
_step_edit = _sequencer[_selected_track].data.step_length-1;
|
||||
}
|
||||
}
|
||||
} else if ( _selected_ctrl == 1 ) {
|
||||
value = getPotChanges(GENERIC_POT_4, 0, 24);
|
||||
if ( value != -1 ) {
|
||||
//clearStackNote();
|
||||
// -12 (0) +12
|
||||
ATOMIC(_transpose = value-12);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
183
PageSequencer.ino
Normal file
183
PageSequencer.ino
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
[step edit]
|
||||
knobs: octave, note, global tunning, sequence length
|
||||
|
||||
buttons: prev step, next step, rest, glide/tie, accent, play/stop
|
||||
*/
|
||||
void sendPreviewNote(uint8_t step)
|
||||
{
|
||||
unsigned long milliTime, preMilliTime;
|
||||
uint8_t note;
|
||||
|
||||
// enable or disable harmonizer
|
||||
if ( _harmonize == 1 ) {
|
||||
note = harmonizer(_sequencer[_selected_track].data.step[step].note);
|
||||
} else {
|
||||
note = _sequencer[_selected_track].data.step[step].note;
|
||||
}
|
||||
ATOMIC(sendMidiMessage(NOTE_ON, note, _sequencer[_selected_track].data.step[step].accent ? ACCENT_VELOCITY : NOTE_VELOCITY, _sequencer[_selected_track].channel))
|
||||
|
||||
// avoid delay() call because of uClock timmer1 usage
|
||||
//delay(200);
|
||||
preMilliTime = millis();
|
||||
while ( true ) {
|
||||
milliTime = millis();
|
||||
if (abs(milliTime - preMilliTime) >= 200) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ATOMIC(sendMidiMessage(NOTE_OFF, note, 0, _sequencer[_selected_track].channel))
|
||||
}
|
||||
|
||||
void processSequencerPots()
|
||||
{
|
||||
static int8_t octave, note, step_note;
|
||||
static int16_t value;
|
||||
uint8_t relative_step = uint8_t(_step_edit + _sequencer[_selected_track].data.step_init_point) % _sequencer[_selected_track].data.step_length;
|
||||
|
||||
// GENERIC_POT_1: Note Octave Selector
|
||||
octave = getPotChanges(GENERIC_POT_1, 0, 10);
|
||||
if ( octave != -1 ) {
|
||||
_last_octave = octave;
|
||||
}
|
||||
|
||||
// GENERIC_POT_2: Note Selector (generic C to B, no octave)
|
||||
note = getPotChanges(GENERIC_POT_2, 0, 11);
|
||||
if ( note != -1 ) {
|
||||
_last_note = note;
|
||||
}
|
||||
|
||||
// changes on octave or note pot?
|
||||
if ( octave != -1 || note != -1 ) {
|
||||
//ATOMIC(_sequencer[_selected_track].data.step[relative_step].note = (_last_octave * 8) + _last_note);
|
||||
note = (_last_octave * 8) + _last_note;
|
||||
ATOMIC(_sequencer[_selected_track].data.step[relative_step].note = note);
|
||||
if ( _playing == false && _sequencer[_selected_track].data.step[relative_step].rest == 0 ) {
|
||||
sendPreviewNote(relative_step);
|
||||
}
|
||||
}
|
||||
|
||||
// GENERIC_POT_3: global tunning (afects booth tracks) or track tunning
|
||||
value = getPotChanges(GENERIC_POT_3, 0, 24);
|
||||
if ( value != -1 ) {
|
||||
//clearStackNote();
|
||||
// -12 (0) +12
|
||||
ATOMIC(_transpose = value-12);
|
||||
}
|
||||
|
||||
// GENERIC_POT_4: sequencer step length
|
||||
value = getPotChanges(GENERIC_POT_4, 1, STEP_MAX_SIZE);
|
||||
if ( value != -1 ) {
|
||||
//clearStackNote(_selected_track);
|
||||
ATOMIC(_sequencer[_selected_track].data.step_length = value);
|
||||
if ( relative_step >= _sequencer[_selected_track].data.step_length ) {
|
||||
_step_edit = _sequencer[_selected_track].data.step_length-1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void processSequencerButtons()
|
||||
{
|
||||
uint8_t relative_step = uint8_t(_step_edit + _sequencer[_selected_track].data.step_init_point) % _sequencer[_selected_track].data.step_length;
|
||||
|
||||
// previous step edit
|
||||
if ( released(GENERIC_BUTTON_1) ) {
|
||||
if ( _step_edit != 0 ) {
|
||||
// add a lock here for octave and note to not mess with edit mode when moving steps around
|
||||
lockPotsState(true);
|
||||
--_step_edit;
|
||||
}
|
||||
if ( _playing == false && _sequencer[_selected_track].data.step[relative_step].rest == 0 ) {
|
||||
sendPreviewNote(relative_step-1);
|
||||
}
|
||||
}
|
||||
|
||||
// next step edit
|
||||
if ( released(GENERIC_BUTTON_2) ) {
|
||||
if ( _step_edit < _sequencer[_selected_track].data.step_length-1 ) {
|
||||
// add a lock here for octave and note to not mess with edit mode when moving steps around
|
||||
lockPotsState(true);
|
||||
++_step_edit;
|
||||
}
|
||||
if ( _playing == false && _sequencer[_selected_track].data.step[relative_step].rest == 0 ) {
|
||||
sendPreviewNote(relative_step+1);
|
||||
}
|
||||
}
|
||||
|
||||
// step rest
|
||||
if ( pressed(GENERIC_BUTTON_3) ) {
|
||||
ATOMIC(_sequencer[_selected_track].data.step[relative_step].rest = !_sequencer[_selected_track].data.step[relative_step].rest);
|
||||
if ( _playing == false && _sequencer[_selected_track].data.step[relative_step].rest == 0 ) {
|
||||
sendPreviewNote(relative_step);
|
||||
}
|
||||
}
|
||||
|
||||
// step glide/tie
|
||||
if ( pressed(GENERIC_BUTTON_4) ) {
|
||||
// if last step is on or it has a tie, we manage tie data
|
||||
if ((_sequencer[_selected_track].data.step[relative_step-1].rest == 0 || _sequencer[_selected_track].data.step[relative_step-1].tie) && _sequencer[_selected_track].data.step[relative_step].rest && relative_step != 0) {
|
||||
ATOMIC(_sequencer[_selected_track].data.step[relative_step].tie = !_sequencer[_selected_track].data.step[relative_step].tie);
|
||||
// otherwise glide step
|
||||
} else {
|
||||
ATOMIC(_sequencer[_selected_track].data.step[relative_step].glide = !_sequencer[_selected_track].data.step[relative_step].glide);
|
||||
}
|
||||
}
|
||||
|
||||
// step accent
|
||||
if ( pressed(GENERIC_BUTTON_5) ) {
|
||||
ATOMIC(_sequencer[_selected_track].data.step[relative_step].accent = !_sequencer[_selected_track].data.step[relative_step].accent);
|
||||
if ( _playing == false && _sequencer[_selected_track].data.step[relative_step].rest == 0 ) {
|
||||
sendPreviewNote(relative_step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processSequencerLeds()
|
||||
{
|
||||
uint8_t relative_step = uint8_t(_step_edit + _sequencer[_selected_track].data.step_init_point) % _sequencer[_selected_track].data.step_length;
|
||||
|
||||
// Editing First Step?
|
||||
if ( _step_edit == 0 ) {
|
||||
digitalWrite(GENERIC_LED_1 , HIGH);
|
||||
} else {
|
||||
digitalWrite(GENERIC_LED_1 , LOW);
|
||||
}
|
||||
|
||||
// Editing Last Step?
|
||||
if ( _step_edit == _sequencer[_selected_track].data.step_length-1 ) {
|
||||
digitalWrite(GENERIC_LED_2 , HIGH);
|
||||
} else {
|
||||
digitalWrite(GENERIC_LED_2 , LOW);
|
||||
}
|
||||
|
||||
// Rest
|
||||
if ( _sequencer[_selected_track].data.step[relative_step].rest == 1 ) {
|
||||
digitalWrite(GENERIC_LED_3 , HIGH);
|
||||
} else {
|
||||
digitalWrite(GENERIC_LED_3 , LOW);
|
||||
}
|
||||
|
||||
// Glide/Tie
|
||||
uint8_t tie_glide_staus = 0;
|
||||
// if last step is on or it has a tie, check for tie event
|
||||
if ((_sequencer[_selected_track].data.step[relative_step-1].rest == 0 || _sequencer[_selected_track].data.step[relative_step-1].tie) && _sequencer[_selected_track].data.step[relative_step].rest && relative_step != 0) {
|
||||
tie_glide_staus = _sequencer[_selected_track].data.step[relative_step].tie;
|
||||
} else {
|
||||
tie_glide_staus = _sequencer[_selected_track].data.step[relative_step].glide;
|
||||
}
|
||||
|
||||
if ( tie_glide_staus ) {
|
||||
digitalWrite(GENERIC_LED_4 , HIGH);
|
||||
} else {
|
||||
digitalWrite(GENERIC_LED_4 , LOW);
|
||||
}
|
||||
|
||||
// Accent
|
||||
if ( _sequencer[_selected_track].data.step[relative_step].accent == 1 ) {
|
||||
digitalWrite(GENERIC_LED_5 , HIGH);
|
||||
} else {
|
||||
digitalWrite(GENERIC_LED_5 , LOW);
|
||||
}
|
||||
}
|
||||
10
README.md
10
README.md
@ -1,5 +1,7 @@
|
||||
# AciduinoCV
|
||||
|
||||
AciduinoCV
|
||||
# AciduinoCV
|
||||
|
||||
Fork of Aciduino V1 sequencer by midilab (https://github.com/midilab/aciduino/tree/master/v1/Aciduino) that adds i2c CV and Gate outputs
|
||||
Arduino-based 2-channel MIDI and CV sequencer.
|
||||
|
||||
This project is based on [Aciduino V1 by midilab](https://github.com/midilab/aciduino/tree/master/v1/Aciduino) (MIT licence).
|
||||
Original code © midilab contact@midilab.co, licensed under MIT (see below).
|
||||
Modifications © Oleksiy Hrachov, licensed under GPLv3.
|
||||
|
||||
187
UserInterface.ino
Normal file
187
UserInterface.ino
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
[page select]: press button1 and button2 together
|
||||
knobs: none, none, none, none
|
||||
|
||||
buttons: track 1, track 2, [live mode], [generative], [step edit], play/stop
|
||||
*/
|
||||
|
||||
uint32_t _page_blink_timer = 0;
|
||||
uint8_t _bpm_blink_timer = 1;
|
||||
uint8_t _selected_page = 0;
|
||||
|
||||
void configureInterface()
|
||||
{
|
||||
// Buttons config
|
||||
connectButton(GENERIC_BUTTON_1, GENERIC_BUTTON_1_PIN);
|
||||
connectButton(GENERIC_BUTTON_2, GENERIC_BUTTON_2_PIN);
|
||||
connectButton(GENERIC_BUTTON_3, GENERIC_BUTTON_3_PIN);
|
||||
connectButton(GENERIC_BUTTON_4, GENERIC_BUTTON_4_PIN);
|
||||
connectButton(GENERIC_BUTTON_5, GENERIC_BUTTON_5_PIN);
|
||||
connectButton(GENERIC_BUTTON_6, GENERIC_BUTTON_6_PIN);
|
||||
|
||||
// Pots config
|
||||
connectPot(GENERIC_POT_1, GENERIC_POT_1_PIN);
|
||||
connectPot(GENERIC_POT_2, GENERIC_POT_2_PIN);
|
||||
connectPot(GENERIC_POT_3, GENERIC_POT_3_PIN);
|
||||
connectPot(GENERIC_POT_4, GENERIC_POT_4_PIN);
|
||||
|
||||
// Leds config
|
||||
pinMode(GENERIC_LED_1, OUTPUT);
|
||||
pinMode(GENERIC_LED_2, OUTPUT);
|
||||
pinMode(GENERIC_LED_3, OUTPUT);
|
||||
pinMode(GENERIC_LED_4, OUTPUT);
|
||||
pinMode(GENERIC_LED_5, OUTPUT);
|
||||
pinMode(GENERIC_LED_6, OUTPUT);
|
||||
digitalWrite(GENERIC_LED_1, LOW);
|
||||
digitalWrite(GENERIC_LED_2, LOW);
|
||||
digitalWrite(GENERIC_LED_3, LOW);
|
||||
digitalWrite(GENERIC_LED_4, LOW);
|
||||
digitalWrite(GENERIC_LED_5, LOW);
|
||||
digitalWrite(GENERIC_LED_6, LOW);
|
||||
|
||||
// first read to fill our registers
|
||||
getPotChanges(GENERIC_POT_1, 0, ADC_RESOLUTION);
|
||||
getPotChanges(GENERIC_POT_2, 0, ADC_RESOLUTION);
|
||||
getPotChanges(GENERIC_POT_3, 0, ADC_RESOLUTION);
|
||||
getPotChanges(GENERIC_POT_4, 0, ADC_RESOLUTION);
|
||||
}
|
||||
|
||||
void processInterface()
|
||||
{
|
||||
static int16_t tempo;
|
||||
|
||||
// set external sync on/off
|
||||
if ( holded(GENERIC_BUTTON_6, 2) ) {
|
||||
if ( uClock.getMode() == uClock.INTERNAL_CLOCK ) {
|
||||
setExternalSync(true);
|
||||
} else {
|
||||
setExternalSync(false);
|
||||
}
|
||||
}
|
||||
|
||||
// global controllers play and tempo
|
||||
// play/stop
|
||||
if ( pressed(GENERIC_BUTTON_6) ) {
|
||||
if ( _playing == false ) {
|
||||
// Starts the clock, tick-tac-tick-tac...
|
||||
uClock.start();
|
||||
} else {
|
||||
// stop the clock
|
||||
uClock.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// internal/external led control
|
||||
if ( uClock.getMode() == uClock.INTERNAL_CLOCK ) {
|
||||
if ( _playing == false ) {
|
||||
digitalWrite(GENERIC_LED_6 , LOW);
|
||||
}
|
||||
} else {
|
||||
// external clock keeps the timer led always on
|
||||
digitalWrite(GENERIC_LED_6 , HIGH);
|
||||
}
|
||||
|
||||
// page select request
|
||||
if ( doublePressed(GENERIC_BUTTON_1, GENERIC_BUTTON_2) ) {
|
||||
lockPotsState(true);
|
||||
_selected_page = 0;
|
||||
}
|
||||
|
||||
switch ( _selected_page ) {
|
||||
|
||||
// Select Track/Page
|
||||
case 0:
|
||||
processPageButtons();
|
||||
processPageLeds();
|
||||
break;
|
||||
|
||||
// Midi controller
|
||||
case 1:
|
||||
processControllerButtons();
|
||||
processControllerLeds();
|
||||
processControllerPots();
|
||||
break;
|
||||
|
||||
// Generative
|
||||
case 2:
|
||||
processGenerativeButtons();
|
||||
processGenerativeLeds();
|
||||
processGenerativePots();
|
||||
break;
|
||||
|
||||
// Sequencer
|
||||
case 3:
|
||||
processSequencerButtons();
|
||||
processSequencerLeds();
|
||||
processSequencerPots();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void processPageButtons()
|
||||
{
|
||||
|
||||
if ( pressed(GENERIC_BUTTON_1) ) {
|
||||
_selected_track = 0;
|
||||
}
|
||||
|
||||
if ( pressed(GENERIC_BUTTON_2) ) {
|
||||
_selected_track = 1;
|
||||
}
|
||||
|
||||
if ( pressed(GENERIC_BUTTON_3) ) {
|
||||
lockPotsState(true);
|
||||
_selected_page = 1;
|
||||
}
|
||||
|
||||
if ( pressed(GENERIC_BUTTON_4) ) {
|
||||
lockPotsState(true);
|
||||
_selected_page = 2;
|
||||
}
|
||||
|
||||
if ( pressed(GENERIC_BUTTON_5) ) {
|
||||
lockPotsState(true);
|
||||
_selected_page = 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void processPageLeds()
|
||||
{
|
||||
static bool blink_state = true;
|
||||
|
||||
// blink interface here for button 3 to 5
|
||||
if ( millis() - _page_blink_timer >= 300 ) {
|
||||
blink_state = !blink_state;
|
||||
_page_blink_timer = millis();
|
||||
}
|
||||
|
||||
digitalWrite(GENERIC_LED_3, blink_state);
|
||||
digitalWrite(GENERIC_LED_4, blink_state);
|
||||
digitalWrite(GENERIC_LED_5, blink_state);
|
||||
|
||||
if ( _selected_track == 0 ) {
|
||||
digitalWrite(GENERIC_LED_1, HIGH);
|
||||
digitalWrite(GENERIC_LED_2, LOW);
|
||||
} else if ( _selected_track == 1 ) {
|
||||
digitalWrite(GENERIC_LED_1, LOW);
|
||||
digitalWrite(GENERIC_LED_2, HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
void tempoInterface(uint32_t * tick)
|
||||
{
|
||||
if (uClock.getMode() == uClock.INTERNAL_CLOCK) {
|
||||
// BPM led indicator
|
||||
if ( !(*tick % (96)) || (*tick == 0) ) { // first compass step will flash longer
|
||||
_bpm_blink_timer = 8;
|
||||
digitalWrite(GENERIC_LED_6 , HIGH);
|
||||
} else if ( !(*tick % (24)) ) { // each quarter led on
|
||||
digitalWrite(GENERIC_LED_6 , HIGH);
|
||||
} else if ( !(*tick % _bpm_blink_timer) ) { // get led off
|
||||
digitalWrite(GENERIC_LED_6 , LOW);
|
||||
_bpm_blink_timer = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
config.h
Normal file
99
config.h
Normal file
@ -0,0 +1,99 @@
|
||||
#ifndef __CONFIG_H__
|
||||
#define __CONFIG_H__
|
||||
|
||||
//
|
||||
// MIDI Config
|
||||
//
|
||||
// MIDI_STANDARD(0) for 31250 standard compilant MIDI devices(use with MIDI 5 pin connector)
|
||||
// MIDI_SERIAL(1) for 115200 compilant with serial devices(use with PC and a serial-to-midi converter)
|
||||
#define MIDI_MODE 0
|
||||
// MIDI Channel
|
||||
#define TRACK1_CHANNEL 1
|
||||
#define TRACK2_CHANNEL 2
|
||||
|
||||
//
|
||||
// MIDI Controller config
|
||||
//
|
||||
#define USE_MIDI_CTRL
|
||||
#define MIDI_CTRL_TUNNING 79
|
||||
#define MIDI_CTRL_CUTOFF 80
|
||||
#define MIDI_CTRL_RESONANCE 81
|
||||
#define MIDI_CTRL_ENVMOD 82
|
||||
#define MIDI_CTRL_DECAY 83
|
||||
#define MIDI_CTRL_ACCENT 84
|
||||
#define MIDI_CTRL_WAVE 85
|
||||
|
||||
//
|
||||
// User interface config
|
||||
//
|
||||
#define SEQUENCER_MIN_BPM 50
|
||||
#define SEQUENCER_MAX_BPM 177
|
||||
|
||||
//
|
||||
// Generative config
|
||||
//
|
||||
#define ACCENT_PROBABILITY_GENERATION 50
|
||||
#define GLIDE_PROBABILITY_GENERATION 30
|
||||
#define TIE_PROBABILITY_GENERATION 80
|
||||
#define REST_PROBABILITY_GENERATION 10
|
||||
|
||||
//
|
||||
// Sequencer config
|
||||
//
|
||||
#define TRACK_NUMBER 2 // you can go up to 8 but no interface ready to control it
|
||||
#define STEP_MAX_SIZE 16
|
||||
#define NOTE_LENGTH 3 // min: 1 max: 5 DO NOT EDIT BEYOND!!!
|
||||
#define NOTE_VELOCITY 90
|
||||
#define ACCENT_VELOCITY 127
|
||||
|
||||
//
|
||||
// Hardware config
|
||||
//
|
||||
#define POT_NUMBER 4
|
||||
#define BUTTON_NUMBER 6
|
||||
|
||||
// Hardware config
|
||||
#define ADC_RESOLUTION 1024
|
||||
#define POT_SENSITIVITY 2
|
||||
// Pin configuration(double check your schematic before configure those pins)
|
||||
// Pots
|
||||
#define GENERIC_POT_1_PIN A3
|
||||
#define GENERIC_POT_2_PIN A2
|
||||
#define GENERIC_POT_3_PIN A1
|
||||
#define GENERIC_POT_4_PIN A0
|
||||
// Buttons
|
||||
#define GENERIC_BUTTON_1_PIN 2
|
||||
#define GENERIC_BUTTON_2_PIN 3
|
||||
#define GENERIC_BUTTON_3_PIN 4
|
||||
#define GENERIC_BUTTON_4_PIN 5
|
||||
#define GENERIC_BUTTON_5_PIN 6
|
||||
#define GENERIC_BUTTON_6_PIN 7
|
||||
// Leds
|
||||
#define GENERIC_LED_1 8
|
||||
#define GENERIC_LED_2 9
|
||||
#define GENERIC_LED_3 10
|
||||
#define GENERIC_LED_4 11
|
||||
#define GENERIC_LED_5 12
|
||||
#define GENERIC_LED_6 13
|
||||
|
||||
typedef enum {
|
||||
GENERIC_POT_1,
|
||||
GENERIC_POT_2,
|
||||
GENERIC_POT_3,
|
||||
GENERIC_POT_4
|
||||
} POT_HARDWARE_INTERFACE;
|
||||
|
||||
typedef enum {
|
||||
GENERIC_BUTTON_1,
|
||||
GENERIC_BUTTON_2,
|
||||
GENERIC_BUTTON_3,
|
||||
GENERIC_BUTTON_4,
|
||||
GENERIC_BUTTON_5,
|
||||
GENERIC_BUTTON_6
|
||||
} BUTTON_HARDWARE_INTERFACE;
|
||||
|
||||
// CV DACs
|
||||
#define DAC_1_ADDR 0x60
|
||||
#define DAC_2_ADDR 0x61
|
||||
|
||||
#endif
|
||||
454
uClock.cpp
Normal file
454
uClock.cpp
Normal file
@ -0,0 +1,454 @@
|
||||
/*!
|
||||
* @file uClock.cpp
|
||||
* Project BPM clock generator for Arduino
|
||||
* @brief A Library to implement BPM clock tick calls using hardware timer interruption. Tested on ATmega168/328, ATmega16u4/32u4 and ATmega2560 and Teensy LC.
|
||||
* @version 1.0.0
|
||||
* @author Romulo Silva
|
||||
* @date 01/04/2022
|
||||
* @license MIT - (c) 2022 - Romulo Silva - contact@midilab.co
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include "uClock.h"
|
||||
|
||||
//
|
||||
// Timer setup for work clock
|
||||
//
|
||||
#if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__)
|
||||
IntervalTimer _uclockTimer;
|
||||
void uclockISR();
|
||||
void uclockInitTimer()
|
||||
{
|
||||
ATOMIC(
|
||||
|
||||
// begin at 120bpm (20833us)
|
||||
_uclockTimer.begin(uclockISR, 20833);
|
||||
|
||||
// Set the interrupt priority level, controlling which other interrupts
|
||||
// this timer is allowed to interrupt. Lower numbers are higher priority,
|
||||
// with 0 the highest and 255 the lowest. Most other interrupts default to 128.
|
||||
// As a general guideline, interrupt routines that run longer should be given
|
||||
// lower priority (higher numerical values).
|
||||
_uclockTimer.priority(0);
|
||||
)
|
||||
}
|
||||
#else
|
||||
void uclockInitTimer()
|
||||
{
|
||||
ATOMIC(
|
||||
// Timer1 init
|
||||
// begin at 120bpm (48.0007680122882 Hz)
|
||||
TCCR1A = 0; // set entire TCCR1A register to 0
|
||||
TCCR1B = 0; // same for TCCR1B
|
||||
TCNT1 = 0; // initialize counter value to 0
|
||||
// set compare match register for 48.0007680122882 Hz increments
|
||||
OCR1A = 41665; // = 16000000 / (8 * 48.0007680122882) - 1 (must be <65536)
|
||||
// turn on CTC mode
|
||||
TCCR1B |= (1 << WGM12);
|
||||
// Set CS12, CS11 and CS10 bits for 8 prescaler
|
||||
TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10);
|
||||
// enable timer compare interrupt
|
||||
TIMSK1 |= (1 << OCIE1A);
|
||||
)
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace umodular { namespace clock {
|
||||
|
||||
static inline uint32_t phase_mult(uint32_t val)
|
||||
{
|
||||
return (val * PHASE_FACTOR) >> 8;
|
||||
}
|
||||
|
||||
static inline uint32_t clock_diff(uint32_t old_clock, uint32_t new_clock)
|
||||
{
|
||||
if (new_clock >= old_clock) {
|
||||
return new_clock - old_clock;
|
||||
} else {
|
||||
return new_clock + (4294967295 - old_clock);
|
||||
}
|
||||
}
|
||||
|
||||
uClockClass::uClockClass()
|
||||
{
|
||||
tempo = 120;
|
||||
start_timer = 0;
|
||||
last_interval = 0;
|
||||
sync_interval = 0;
|
||||
state = PAUSED;
|
||||
mode = INTERNAL_CLOCK;
|
||||
resetCounters();
|
||||
|
||||
onClock96PPQNCallback = NULL;
|
||||
onClock32PPQNCallback = NULL;
|
||||
onClock16PPQNCallback = NULL;
|
||||
onClockStartCallback = NULL;
|
||||
onClockStopCallback = NULL;
|
||||
|
||||
// first interval calculus
|
||||
setTempo(tempo);
|
||||
}
|
||||
|
||||
void uClockClass::init()
|
||||
{
|
||||
uclockInitTimer();
|
||||
}
|
||||
|
||||
void uClockClass::start()
|
||||
{
|
||||
resetCounters();
|
||||
start_timer = millis();
|
||||
|
||||
if (onClockStartCallback) {
|
||||
onClockStartCallback();
|
||||
}
|
||||
|
||||
if (mode == INTERNAL_CLOCK) {
|
||||
state = STARTED;
|
||||
} else {
|
||||
state = STARTING;
|
||||
}
|
||||
}
|
||||
|
||||
void uClockClass::stop()
|
||||
{
|
||||
state = PAUSED;
|
||||
start_timer = 0;
|
||||
resetCounters();
|
||||
if (onClockStopCallback) {
|
||||
onClockStopCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void uClockClass::pause()
|
||||
{
|
||||
if (mode == INTERNAL_CLOCK) {
|
||||
if (state == PAUSED) {
|
||||
start();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uClockClass::setTimerTempo(float bpm)
|
||||
{
|
||||
// 96 ppqn resolution
|
||||
tick_us_interval = (60000000 / 24 / bpm);
|
||||
tick_hertz_interval = 1/((float)tick_us_interval/1000000);
|
||||
|
||||
#if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__)
|
||||
ATOMIC(
|
||||
_uclockTimer.update(tick_us_interval);
|
||||
)
|
||||
#else
|
||||
uint32_t ocr;
|
||||
uint8_t tccr = 0;
|
||||
|
||||
// 16bits avr timer setup
|
||||
if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1 )) < 65535) {
|
||||
// Set CS12, CS11 and CS10 bits for 1 prescaler
|
||||
tccr |= (0 << CS12) | (0 << CS11) | (1 << CS10);
|
||||
} else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 8 )) < 65535) {
|
||||
// Set CS12, CS11 and CS10 bits for 8 prescaler
|
||||
tccr |= (0 << CS12) | (1 << CS11) | (0 << CS10);
|
||||
} else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 64 )) < 65535) {
|
||||
// Set CS12, CS11 and CS10 bits for 64 prescaler
|
||||
tccr |= (0 << CS12) | (1 << CS11) | (1 << CS10);
|
||||
} else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 256 )) < 65535) {
|
||||
// Set CS12, CS11 and CS10 bits for 256 prescaler
|
||||
tccr |= (1 << CS12) | (0 << CS11) | (0 << CS10);
|
||||
} else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1024 )) < 65535) {
|
||||
// Set CS12, CS11 and CS10 bits for 1024 prescaler
|
||||
tccr |= (1 << CS12) | (0 << CS11) | (1 << CS10);
|
||||
} else {
|
||||
// tempo not achiavable
|
||||
return;
|
||||
}
|
||||
|
||||
ATOMIC(
|
||||
TCCR1B = 0;
|
||||
OCR1A = ocr-1;
|
||||
TCCR1B |= (1 << WGM12);
|
||||
TCCR1B |= tccr;
|
||||
)
|
||||
#endif
|
||||
}
|
||||
|
||||
void uClockClass::setTempo(float bpm)
|
||||
{
|
||||
if (mode == EXTERNAL_CLOCK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bpm < MIN_BPM || bpm > MAX_BPM) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimerTempo(bpm);
|
||||
|
||||
tempo = bpm;
|
||||
}
|
||||
|
||||
float inline uClockClass::freqToBpm(uint32_t freq)
|
||||
{
|
||||
float usecs = 1/((float)freq/1000000.0);
|
||||
return (float)((float)(usecs/24.0) * 60.0);
|
||||
}
|
||||
|
||||
float uClockClass::getTempo()
|
||||
{
|
||||
if (mode == EXTERNAL_CLOCK) {
|
||||
uint32_t acc = 0;
|
||||
for (uint8_t i=0; i < EXT_INTERVAL_BUFFER_SIZE; i++) {
|
||||
acc += ext_interval_buffer[i];
|
||||
}
|
||||
if (acc != 0) {
|
||||
return freqToBpm(acc / EXT_INTERVAL_BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
return tempo;
|
||||
}
|
||||
|
||||
void uClockClass::setMode(uint8_t tempo_mode)
|
||||
{
|
||||
mode = tempo_mode;
|
||||
}
|
||||
|
||||
uint8_t uClockClass::getMode()
|
||||
{
|
||||
return mode;
|
||||
}
|
||||
|
||||
void uClockClass::clockMe()
|
||||
{
|
||||
if (mode == EXTERNAL_CLOCK) {
|
||||
ATOMIC(
|
||||
handleExternalClock()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
void uClockClass::resetCounters()
|
||||
{
|
||||
external_clock = 0;
|
||||
internal_tick = 0;
|
||||
external_tick = 0;
|
||||
div32th_counter = 0;
|
||||
div16th_counter = 0;
|
||||
mod6_counter = 0;
|
||||
indiv32th_counter = 0;
|
||||
indiv16th_counter = 0;
|
||||
inmod6_counter = 0;
|
||||
ext_interval_idx = 0;
|
||||
}
|
||||
|
||||
// TODO: Tap stuff
|
||||
void uClockClass::tap()
|
||||
{
|
||||
// tap me
|
||||
}
|
||||
|
||||
// TODO: Shuffle stuff
|
||||
void uClockClass::shuffle()
|
||||
{
|
||||
// shuffle me
|
||||
}
|
||||
|
||||
void uClockClass::handleExternalClock()
|
||||
{
|
||||
|
||||
switch (state) {
|
||||
case PAUSED:
|
||||
break;
|
||||
|
||||
case STARTING:
|
||||
state = STARTED;
|
||||
external_clock = micros();
|
||||
break;
|
||||
|
||||
case STARTED:
|
||||
|
||||
uint32_t u_timer = micros();
|
||||
last_interval = clock_diff(external_clock, u_timer);
|
||||
external_clock = u_timer;
|
||||
|
||||
if (inmod6_counter == 0) {
|
||||
indiv16th_counter++;
|
||||
indiv32th_counter++;
|
||||
}
|
||||
|
||||
if (inmod6_counter == 3) {
|
||||
indiv32th_counter++;
|
||||
}
|
||||
|
||||
// slave tick me!
|
||||
external_tick++;
|
||||
inmod6_counter++;
|
||||
|
||||
if (inmod6_counter == 6) {
|
||||
inmod6_counter = 0;
|
||||
}
|
||||
|
||||
// accumulate interval incomming ticks data for getTempo() smooth reads on slave mode
|
||||
if(++ext_interval_idx >= EXT_INTERVAL_BUFFER_SIZE) {
|
||||
ext_interval_idx = 0;
|
||||
}
|
||||
ext_interval_buffer[ext_interval_idx] = last_interval;
|
||||
|
||||
if (external_tick == 1) {
|
||||
interval = last_interval;
|
||||
} else {
|
||||
interval = (((uint32_t)interval * (uint32_t)PLL_X) + (uint32_t)(256 - PLL_X) * (uint32_t)last_interval) >> 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void uClockClass::handleTimerInt()
|
||||
{
|
||||
if (mode == EXTERNAL_CLOCK) {
|
||||
// sync tick position with external tick clock
|
||||
if ((internal_tick < external_tick) || (internal_tick > (external_tick + 1))) {
|
||||
internal_tick = external_tick;
|
||||
div32th_counter = indiv32th_counter;
|
||||
div16th_counter = indiv16th_counter;
|
||||
mod6_counter = inmod6_counter;
|
||||
}
|
||||
|
||||
uint32_t counter = interval;
|
||||
uint32_t u_timer = micros();
|
||||
sync_interval = clock_diff(external_clock, u_timer);
|
||||
|
||||
if (internal_tick <= external_tick) {
|
||||
counter -= phase_mult(sync_interval);
|
||||
} else {
|
||||
if (counter > sync_interval) {
|
||||
counter += phase_mult(counter - sync_interval);
|
||||
}
|
||||
}
|
||||
|
||||
// update internal clock timer frequency
|
||||
float bpm = freqToBpm(counter);
|
||||
if (bpm != tempo) {
|
||||
if (bpm >= MIN_BPM && bpm <= MAX_BPM) {
|
||||
tempo = bpm;
|
||||
setTimerTempo(tempo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (onClock96PPQNCallback) {
|
||||
onClock96PPQNCallback(&internal_tick);
|
||||
}
|
||||
|
||||
if (mod6_counter == 0) {
|
||||
if (onClock32PPQNCallback) {
|
||||
onClock32PPQNCallback(&div32th_counter);
|
||||
}
|
||||
if (onClock16PPQNCallback) {
|
||||
onClock16PPQNCallback(&div16th_counter);
|
||||
}
|
||||
div16th_counter++;
|
||||
div32th_counter++;
|
||||
}
|
||||
|
||||
if (mod6_counter == 3) {
|
||||
if (onClock32PPQNCallback) {
|
||||
onClock32PPQNCallback(&div32th_counter);
|
||||
}
|
||||
div32th_counter++;
|
||||
}
|
||||
|
||||
// tick me!
|
||||
internal_tick++;
|
||||
mod6_counter++;
|
||||
|
||||
if (mod6_counter == 6) {
|
||||
mod6_counter = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// elapsed time support
|
||||
uint8_t uClockClass::getNumberOfSeconds(uint32_t time)
|
||||
{
|
||||
if ( time == 0 ) {
|
||||
return time;
|
||||
}
|
||||
return ((_timer - time) / 1000) % SECS_PER_MIN;
|
||||
}
|
||||
|
||||
uint8_t uClockClass::getNumberOfMinutes(uint32_t time)
|
||||
{
|
||||
if ( time == 0 ) {
|
||||
return time;
|
||||
}
|
||||
return (((_timer - time) / 1000) / SECS_PER_MIN) % SECS_PER_MIN;
|
||||
}
|
||||
|
||||
uint8_t uClockClass::getNumberOfHours(uint32_t time)
|
||||
{
|
||||
if ( time == 0 ) {
|
||||
return time;
|
||||
}
|
||||
return (((_timer - time) / 1000) % SECS_PER_DAY) / SECS_PER_HOUR;
|
||||
}
|
||||
|
||||
uint8_t uClockClass::getNumberOfDays(uint32_t time)
|
||||
{
|
||||
if ( time == 0 ) {
|
||||
return time;
|
||||
}
|
||||
return ((_timer - time) / 1000) / SECS_PER_DAY;
|
||||
}
|
||||
|
||||
uint32_t uClockClass::getNowTimer()
|
||||
{
|
||||
return _timer;
|
||||
}
|
||||
|
||||
uint32_t uClockClass::getPlayTime()
|
||||
{
|
||||
return start_timer;
|
||||
}
|
||||
|
||||
} } // end namespace umodular::clock
|
||||
|
||||
umodular::clock::uClockClass uClock;
|
||||
|
||||
volatile uint32_t _timer = 0;
|
||||
|
||||
//
|
||||
// TIMER INTERRUPT HANDLER
|
||||
//
|
||||
//
|
||||
#if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__)
|
||||
void uclockISR()
|
||||
#else
|
||||
ISR(TIMER1_COMPA_vect)
|
||||
#endif
|
||||
{
|
||||
// global timer counter
|
||||
_timer = millis();
|
||||
|
||||
if (uClock.state == uClock.STARTED) {
|
||||
uClock.handleTimerInt();
|
||||
}
|
||||
}
|
||||
173
uClock.h
Normal file
173
uClock.h
Normal file
@ -0,0 +1,173 @@
|
||||
/*!
|
||||
* @file uClock.h
|
||||
* Project BPM clock generator for Arduino
|
||||
* @brief A Library to implement BPM clock tick calls using hardware timer interruption. Tested on ATmega168/328, ATmega16u4/32u4 and ATmega2560 and Teensy LC.
|
||||
* @version 1.0.0
|
||||
* @author Romulo Silva
|
||||
* @date 01/04/2022
|
||||
* @license MIT - (c) 2022 - Romulo Silva - contact@midilab.co
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __U_CLOCK_H__
|
||||
#define __U_CLOCK_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
namespace umodular { namespace clock {
|
||||
|
||||
#define AVR_CLOCK_FREQ 16000000
|
||||
|
||||
#define PHASE_FACTOR 16
|
||||
|
||||
#define PLL_X 220
|
||||
|
||||
// for smooth slave tempo calculate display you should raise this value
|
||||
// in between 64 to 128.
|
||||
// note: this doesn't impact on sync time, only display time getTempo()
|
||||
// if you dont want to use it, set it to 1 for memory save
|
||||
#define EXT_INTERVAL_BUFFER_SIZE 24
|
||||
|
||||
#define SECS_PER_MIN (60UL)
|
||||
#define SECS_PER_HOUR (3600UL)
|
||||
#define SECS_PER_DAY (SECS_PER_HOUR * 24L)
|
||||
|
||||
#define MIN_BPM 1
|
||||
#define MAX_BPM 300
|
||||
|
||||
#define ATOMIC(X) noInterrupts(); X; interrupts();
|
||||
|
||||
class uClockClass {
|
||||
|
||||
private:
|
||||
|
||||
void setTimerTempo(float bpm);
|
||||
float inline freqToBpm(uint32_t freq);
|
||||
|
||||
void (*onClock96PPQNCallback)(uint32_t * tick);
|
||||
void (*onClock32PPQNCallback)(uint32_t * tick);
|
||||
void (*onClock16PPQNCallback)(uint32_t * tick);
|
||||
void (*onClockStartCallback)();
|
||||
void (*onClockStopCallback)();
|
||||
|
||||
// internal clock control
|
||||
volatile uint32_t internal_tick;
|
||||
volatile uint32_t div32th_counter;
|
||||
volatile uint32_t div16th_counter;
|
||||
volatile uint8_t mod6_counter;
|
||||
|
||||
// external clock control
|
||||
volatile uint32_t external_clock;
|
||||
volatile uint32_t external_tick;
|
||||
volatile uint32_t indiv32th_counter;
|
||||
volatile uint32_t indiv16th_counter;
|
||||
volatile uint8_t inmod6_counter;
|
||||
volatile uint32_t interval;
|
||||
volatile uint32_t last_interval;
|
||||
uint32_t sync_interval;
|
||||
|
||||
uint32_t tick_us_interval;
|
||||
float tick_hertz_interval;
|
||||
|
||||
float tempo;
|
||||
uint32_t start_timer;
|
||||
uint8_t mode;
|
||||
|
||||
volatile uint32_t ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE];
|
||||
uint16_t ext_interval_idx;
|
||||
|
||||
public:
|
||||
|
||||
enum {
|
||||
INTERNAL_CLOCK = 0,
|
||||
EXTERNAL_CLOCK
|
||||
};
|
||||
|
||||
enum {
|
||||
PAUSED = 0,
|
||||
STARTING,
|
||||
STARTED
|
||||
};
|
||||
|
||||
uint8_t state;
|
||||
|
||||
uClockClass();
|
||||
|
||||
void setClock96PPQNOutput(void (*callback)(uint32_t * tick)) {
|
||||
onClock96PPQNCallback = callback;
|
||||
}
|
||||
|
||||
void setClock32PPQNOutput(void (*callback)(uint32_t * tick)) {
|
||||
onClock32PPQNCallback = callback;
|
||||
}
|
||||
|
||||
void setClock16PPQNOutput(void (*callback)(uint32_t * tick)) {
|
||||
onClock16PPQNCallback = callback;
|
||||
}
|
||||
|
||||
void setOnClockStartOutput(void (*callback)()) {
|
||||
onClockStartCallback = callback;
|
||||
}
|
||||
|
||||
void setOnClockStopOutput(void (*callback)()) {
|
||||
onClockStopCallback = callback;
|
||||
}
|
||||
|
||||
void init();
|
||||
void handleTimerInt();
|
||||
void handleExternalClock();
|
||||
void resetCounters();
|
||||
|
||||
// external class control
|
||||
void start();
|
||||
void stop();
|
||||
void pause();
|
||||
void setTempo(float bpm);
|
||||
float getTempo();
|
||||
|
||||
// external timming control
|
||||
void setMode(uint8_t tempo_mode);
|
||||
uint8_t getMode();
|
||||
void clockMe();
|
||||
|
||||
// todo!
|
||||
void shuffle();
|
||||
void tap();
|
||||
|
||||
// elapsed time support
|
||||
uint8_t getNumberOfSeconds(uint32_t time);
|
||||
uint8_t getNumberOfMinutes(uint32_t time);
|
||||
uint8_t getNumberOfHours(uint32_t time);
|
||||
uint8_t getNumberOfDays(uint32_t time);
|
||||
uint32_t getNowTimer();
|
||||
uint32_t getPlayTime();
|
||||
};
|
||||
|
||||
} } // end namespace umodular::clock
|
||||
|
||||
extern umodular::clock::uClockClass uClock;
|
||||
|
||||
extern "C" {
|
||||
extern volatile uint16_t _clock;
|
||||
extern volatile uint32_t _timer;
|
||||
}
|
||||
|
||||
#endif /* __U_CLOCK_H__ */
|
||||
Reference in New Issue
Block a user