// Copyright 2012 Olivier Gillet.
//
// Author: Olivier Gillet (olivier@mutable-instruments.net)
//
// 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 .
//
// User interface handling.
#include "edges/ui.h"
#include
#include "edges/midi_handler.h"
#include "edges/settings.h"
namespace edges {
const uint16_t kLongPressTime = 600; // ms
using namespace avrlibx;
/* */
Mode Ui::mode_;
uint8_t Ui::gate_;
uint8_t Ui::edited_channel_;
uint16_t Ui::leds_pwm_counter_;
uint16_t Ui::switch_time_counter_;
uint16_t Ui::calibration_cv_[2];
uint16_t Ui::cv_[2 * kNumChannels];
uint16_t Ui::root_cv_;
Leds Ui::leds_;
Switches Ui::switches_;
Gpio Ui::midi_learn_switch_;
Gpio Ui::midi_mode_switch_;
uint8_t Ui::debounce_history_[kNumSwitches];
/* */
/* static */
void Ui::Init() {
leds_.set_direction(OUTPUT);
switches_.set_direction(INPUT);
midi_learn_switch_.set_direction(INPUT);
midi_mode_switch_.set_direction(INPUT);
switches_.set_mode(PORT_MODE_PULL_UP);
midi_learn_switch_.set_mode(PORT_MODE_PULL_UP);
midi_mode_switch_.set_mode(PORT_MODE_PULL_UP);
mode_ = MODE_NORMAL;
leds_pwm_counter_ = 0;
edited_channel_ = 0;
memset(debounce_history_, 0xff, sizeof(debounce_history_));
memset(cv_, 0, sizeof(cv_));
}
/* static */
void Ui::OnSwitchHeld(uint8_t index) {
if (index < kNumChannels) {
if (mode_ == MODE_MENU) {
mode_ = MODE_NORMAL;
settings.Save();
} else {
mode_ = MODE_MENU;
edited_channel_ = index;
}
} else if (index == kNumChannels + 1) {
midi_handler.DisableMidiCoupling();
}
}
/* static */
void Ui::OnSwitchReleased(uint8_t index) {
if (index < kNumChannels) {
switch (mode_) {
case MODE_MENU:
switch (index) {
case 0:
settings.ToggleQuantizer(edited_channel_);
break;
case 1:
settings.ToggleArpeggio(edited_channel_);
break;
case 2:
mode_ = MODE_RECORDING;
root_cv_ = cv_[edited_channel_];
settings.mutable_channel_data(edited_channel_)->StartRecording();
break;
case 3:
mode_ = MODE_CALIBRATE_1;
break;
}
break;
case MODE_NORMAL:
settings.StepPW(index);
break;
case MODE_CALIBRATE_1:
mode_ = MODE_CALIBRATE_2;
break;
case MODE_CALIBRATE_2:
settings.Calibrate(
edited_channel_,
calibration_cv_[0],
calibration_cv_[1],
cv_[edited_channel_ + 4]);
mode_ = MODE_NORMAL;
break;
case MODE_RECORDING:
{
ChannelData* channel = settings.mutable_channel_data(edited_channel_);
uint8_t current_step = channel->num_arpeggio_steps - 1;
if (index == (current_step & 3)) {
channel->StopRecording();
settings.Save();
mode_ = MODE_NORMAL;
} else if (index == ((current_step + 1) & 3)) {
if (!channel->NextArpeggiatorStep()) {
settings.Save();
mode_ = MODE_NORMAL;
}
}
}
break;
}
} else if (index == kNumChannels) {
mode_ = MODE_LEARNING_MIDI_CHANNEL;
midi_handler.Learn();
} else {
midi_handler.ToggleMidiMode();
}
}
static const uint8_t bit_reverse[] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf
};
/* static */
void Ui::Poll() {
++leds_pwm_counter_;
++switch_time_counter_;
// Refresh leds.
uint8_t leds_value = 0;
switch (mode_) {
case MODE_NORMAL:
leds_value = gate_;
break;
case MODE_MENU:
if (settings.quantized(edited_channel_)) {
leds_value |= 0x1;
}
if (settings.arpeggio(edited_channel_)) {
leds_value |= 0x2;
}
if (leds_pwm_counter_ & 128) {
leds_value |= 0x4;
} else {
leds_value |= 0x8;
}
break;
case MODE_LEARNING_MIDI_CHANNEL:
if (leds_pwm_counter_ & 128) {
leds_value = 0xff;
}
if (!midi_handler.learning()) {
mode_ = MODE_NORMAL;
}
break;
case MODE_CALIBRATE_1:
leds_value |= 0x3;
break;
case MODE_CALIBRATE_2:
leds_value |= 0xf;
break;
case MODE_RECORDING:
leds_value |= 1 << ((settings.num_steps(edited_channel_) - 1) & 0x03);
break;
}
leds_.set_value(bit_reverse[leds_value & 0xf]);
// Scan switches.
uint8_t switches_value = bit_reverse[switches_.Read()];
for (uint8_t i = 0; i < kNumChannels; ++i) {
debounce_history_[i] = (debounce_history_[i] << 1) | (switches_value & 1);
switches_value >>= 1;
}
debounce_history_[kNumChannels] = \
(debounce_history_[kNumChannels] << 1) | midi_learn_switch_.value();
debounce_history_[kNumChannels + 1] = \
(debounce_history_[kNumChannels + 1] << 1) | midi_mode_switch_.value();
// Trigger switch events.
for (uint8_t i = 0; i < kNumSwitches; ++i) {
// When a switch is pressed, start the time counter.
if (debounce_history_[i] == 0xfe) {
switch_time_counter_ = 0;
}
// When a switch is held, enable calibration mode.
if (debounce_history_[i] == 0x00 &&
switch_time_counter_ == kLongPressTime) {
OnSwitchHeld(i);
}
// When a switch is released, do something depending on the event.
if (debounce_history_[i] == 0x01 &&
switch_time_counter_ < kLongPressTime) {
OnSwitchReleased(i);
}
}
// Update arpeggiator pattern.
if (mode_ == MODE_RECORDING) {
if (settings.num_steps(edited_channel_) == 1) {
root_cv_ = cv_[edited_channel_];
}
settings.mutable_channel_data(edited_channel_)->UpdateArpeggiatorStep(
root_cv_,
cv_[edited_channel_]
);
}
}
/* extern */
Ui ui;
} // namespace edges