// Copyright 2012 Olivier Gillet.
//
// Author: Olivier Gillet (ol.gillet@gmail.com)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
#include
#include "avrlib/adc.h"
#include "avrlib/boot.h"
#include "avrlib/op.h"
#include "avrlib/watchdog_timer.h"
#include "grids/clock.h"
#include "grids/hardware_config.h"
#include "grids/pattern_generator.h"
using namespace avrlib;
using namespace grids;
Leds leds;
Inputs inputs;
AdcInputScanner adc;
ShiftRegister shift_register;
MidiInput midi;
enum Parameter {
PARAMETER_NONE,
PARAMETER_WAITING,
PARAMETER_CLOCK_RESOLUTION,
PARAMETER_TAP_TEMPO,
PARAMETER_SWING,
PARAMETER_GATE_MODE,
PARAMETER_OUTPUT_MODE,
PARAMETER_CLOCK_OUTPUT
};
uint32_t tap_duration = 0;
uint8_t led_pattern ;
uint8_t led_off_timer;
int8_t swing_amount;
volatile Parameter parameter = PARAMETER_NONE;
volatile bool long_press_detected = false;
const uint8_t kUpdatePeriod = F_CPU / 32 / 8000;
inline void UpdateLeds() {
uint8_t pattern;
if (parameter == PARAMETER_NONE) {
if (led_off_timer) {
--led_off_timer;
if (!led_off_timer) {
led_pattern = 0;
}
}
pattern = led_pattern;
if (pattern_generator.tap_tempo()) {
if (pattern_generator.on_beat()) {
pattern |= LED_CLOCK;
}
} else {
if (pattern_generator.on_first_beat()) {
pattern |= LED_CLOCK;
}
}
} else {
pattern = LED_CLOCK;
switch (parameter) {
case PARAMETER_CLOCK_RESOLUTION:
pattern |= LED_BD >> pattern_generator.clock_resolution();
break;
case PARAMETER_CLOCK_OUTPUT:
if (pattern_generator.output_clock()) {
pattern |= LED_ALL;
}
break;
case PARAMETER_SWING:
if (pattern_generator.swing()) {
pattern |= LED_ALL;
}
break;
case PARAMETER_OUTPUT_MODE:
if (pattern_generator.output_mode() == OUTPUT_MODE_DRUMS) {
pattern |= LED_ALL;
}
break;
case PARAMETER_TAP_TEMPO:
if (pattern_generator.tap_tempo()) {
pattern |= LED_ALL;
}
break;
case PARAMETER_GATE_MODE:
if (pattern_generator.gate_mode()) {
pattern |= LED_ALL;
}
}
}
leds.Write(pattern);
}
inline void UpdateShiftRegister() {
static uint8_t previous_state = 0;
if (pattern_generator.state() != previous_state) {
previous_state = pattern_generator.state();
shift_register.Write(previous_state);
if (!previous_state) {
// Switch off the LEDs, but not now.
led_off_timer = 200;
} else {
// Switch on the LEDs with a new pattern.
led_pattern = pattern_generator.led_pattern();
led_off_timer = 0;
}
}
}
uint8_t ticks_granularity[] = { 6, 3, 1 };
inline void HandleClockResetInputs() {
static uint8_t previous_inputs;
uint8_t inputs_value = ~inputs.Read();
uint8_t num_ticks = 0;
uint8_t increment = ticks_granularity[pattern_generator.clock_resolution()];
// CLOCK
if (clock.bpm() < 40 && !clock.locked()) {
if ((inputs_value & INPUT_CLOCK) && !(previous_inputs & INPUT_CLOCK)) {
num_ticks = increment;
}
if (!(inputs_value & INPUT_CLOCK) && (previous_inputs & INPUT_CLOCK)) {
pattern_generator.ClockFallingEdge();
}
if (midi.readable()) {
uint8_t byte = midi.ImmediateRead();
if (byte == 0xf8) {
num_ticks = 1;
} else if (byte == 0xfa) {
pattern_generator.Reset();
}
}
} else {
clock.Tick();
clock.Wrap(swing_amount);
if (clock.raising_edge()) {
num_ticks = increment;
}
if (clock.past_falling_edge()) {
pattern_generator.ClockFallingEdge();
}
}
// RESET
if ((inputs_value & INPUT_RESET) && !(previous_inputs & INPUT_RESET)) {
pattern_generator.Reset();
// !! HACK AHEAD !!
//
// Earlier versions of the firmware retriggered the outputs whenever a
// RESET signal was received. This allowed for nice drill'n'bass effects,
// but made synchronization with another sequencer a bit glitchy (risk of
// double notes at the beginning of a pattern). It was later decided
// to remove this behaviour and make the RESET transparent (just set the
// step index without producing any trigger) - similar to the MIDI START
// message. However, the factory testing script relies on the old behaviour.
// To solve this problem, we reproduce this behaviour the first 5 times the
// module is powered. After the 5th power-on (or settings change) cycle,
// this odd behaviour disappears.
if (pattern_generator.factory_testing() ||
clock.bpm() >= 40 ||
clock.locked()) {
pattern_generator.Retrigger();
clock.Reset();
}
}
previous_inputs = inputs_value;
if (num_ticks) {
swing_amount = pattern_generator.swing_amount();
pattern_generator.TickClock(num_ticks);
}
}
enum SwitchState {
SWITCH_STATE_JUST_PRESSED = 0xfe,
SWITCH_STATE_PRESSED = 0x00,
SWITCH_STATE_JUST_RELEASED = 0x01,
SWITCH_STATE_RELEASED = 0xff
};
inline void HandleTapButton() {
static uint8_t switch_state = 0xff;
static uint16_t switch_hold_time = 0;
switch_state = switch_state << 1;
if (inputs.Read() & INPUT_SW_RESET) {
switch_state |= 1;
}
if (switch_state == SWITCH_STATE_JUST_PRESSED) {
if (parameter == PARAMETER_NONE) {
if (!pattern_generator.tap_tempo()) {
pattern_generator.Reset();
if (pattern_generator.factory_testing() ||
clock.bpm() >= 40 ||
clock.locked()) {
clock.Reset();
}
} else {
uint32_t new_bpm = (F_CPU * 60L) / (32L * kUpdatePeriod * tap_duration);
if (new_bpm >= 30 && new_bpm <= 480) {
clock.Update(new_bpm, pattern_generator.clock_resolution());
clock.Reset();
clock.Lock();
} else {
clock.Unlock();
}
tap_duration = 0;
}
}
switch_hold_time = 0;
} else if (switch_state == SWITCH_STATE_PRESSED) {
++switch_hold_time;
if (switch_hold_time == 500) {
long_press_detected = true;
}
}
}
ISR(TIMER2_COMPA_vect, ISR_NOBLOCK) {
static uint8_t switch_debounce_prescaler;
++tap_duration;
++switch_debounce_prescaler;
if (switch_debounce_prescaler >= 10) {
// Debounce RESET/TAP switch and perform switch action.
HandleTapButton();
switch_debounce_prescaler = 0;
}
HandleClockResetInputs();
adc.Scan();
pattern_generator.IncrementPulseCounter();
UpdateShiftRegister();
UpdateLeds();
}
static int16_t pot_values[8];
void ScanPots() {
if (long_press_detected) {
if (parameter == PARAMETER_NONE) {
// Freeze pot values
for (uint8_t i = 0; i < 8; ++i) {
pot_values[i] = adc.Read8(i);
}
parameter = PARAMETER_WAITING;
} else {
parameter = PARAMETER_NONE;
pattern_generator.SaveSettings();
}
long_press_detected = false;
}
if (parameter == PARAMETER_NONE) {
uint8_t bpm = adc.Read8(ADC_CHANNEL_TEMPO);
bpm = U8U8MulShift8(bpm, 220) + 20;
if (bpm != clock.bpm() && !clock.locked()) {
clock.Update(bpm, pattern_generator.clock_resolution());
}
PatternGeneratorSettings* settings = pattern_generator.mutable_settings();
settings->options.drums.x = ~adc.Read8(ADC_CHANNEL_X_CV);
settings->options.drums.y = ~adc.Read8(ADC_CHANNEL_Y_CV);
settings->options.drums.randomness = ~adc.Read8(ADC_CHANNEL_RANDOMNESS_CV);
settings->density[0] = ~adc.Read8(ADC_CHANNEL_BD_DENSITY_CV);
settings->density[1] = ~adc.Read8(ADC_CHANNEL_SD_DENSITY_CV);
settings->density[2] = ~adc.Read8(ADC_CHANNEL_HH_DENSITY_CV);
} else {
for (uint8_t i = 0; i < 8; ++i) {
int16_t value = adc.Read8(i);
int16_t delta = value - pot_values[i];
if (delta < 0) {
delta = -delta;
}
if (delta > 32) {
pot_values[i] = value;
switch (i) {
case ADC_CHANNEL_BD_DENSITY_CV:
parameter = PARAMETER_CLOCK_RESOLUTION;
pattern_generator.set_clock_resolution((255 - value) >> 6);
clock.Update(clock.bpm(), pattern_generator.clock_resolution());
pattern_generator.Reset();
break;
case ADC_CHANNEL_SD_DENSITY_CV:
parameter = PARAMETER_TAP_TEMPO;
pattern_generator.set_tap_tempo(!(value & 0x80));
if (!pattern_generator.tap_tempo()) {
clock.Unlock();
}
break;
case ADC_CHANNEL_HH_DENSITY_CV:
parameter = PARAMETER_SWING;
pattern_generator.set_swing(!(value & 0x80));
break;
case ADC_CHANNEL_X_CV:
parameter = PARAMETER_OUTPUT_MODE;
pattern_generator.set_output_mode(!(value & 0x80) ? 1 : 0);
break;
case ADC_CHANNEL_Y_CV:
parameter = PARAMETER_GATE_MODE;
pattern_generator.set_gate_mode(!(value & 0x80));
break;
case ADC_CHANNEL_RANDOMNESS_CV:
parameter = PARAMETER_CLOCK_OUTPUT;
pattern_generator.set_output_clock(!(value & 0x80));
break;
}
}
}
}
}
void Init() {
sei();
UCSR0B = 0;
leds.set_mode(DIGITAL_OUTPUT);
inputs.set_mode(DIGITAL_INPUT);
inputs.EnablePullUpResistors();
clock.Init();
adc.Init();
adc.set_num_inputs(ADC_CHANNEL_LAST);
Adc::set_reference(ADC_DEFAULT);
Adc::set_alignment(ADC_LEFT_ALIGNED);
pattern_generator.Init();
shift_register.Init();
midi.Init();
TCCR2A = _BV(WGM21);
TCCR2B = 3;
OCR2A = kUpdatePeriod - 1;
TIMSK2 |= _BV(1);
}
int main(void) {
ResetWatchdog();
Init();
clock.Update(120, pattern_generator.clock_resolution());
while (1) {
// Use any spare cycles to read the CVs and update the potentiometers
ScanPots();
}
}