Files

copied
Last update 6 years 1 month by Olivier Gillet
Filesgrids
..
bootloader
hardware_design
resources
__init__.py
clock.cc
clock.h
grids.cc
hardware_config.h
makefile
pattern_generator.cc
pattern_generator.h
resources.cc
resources.h
grids.cc
// 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 <http://www.gnu.org/licenses/>. #include <avr/eeprom.h> #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(); } }
Report a bug