You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MicroDexed/dexed.cpp

748 lines
20 KiB

/*
MicroDexed
MicroDexed is a port of the Dexed sound engine
(https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6 with audio shield.
Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android
(c)2018,2019 H. Wirtz <wirtz@parasitstudio.de>
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, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
7 years ago
#include "config.h"
7 years ago
#include "synth.h"
7 years ago
#include "dexed.h"
#include "EngineMkI.h"
#include "EngineOpl.h"
#include "fm_core.h"
#include "exp2.h"
#include "sin.h"
#include "freqlut.h"
#include "controllers.h"
#include "PluginFx.h"
7 years ago
#include <unistd.h>
#include <limits.h>
#include "porta.h"
#ifdef USE_TEENSY_DSP
#include <Audio.h>
#endif
7 years ago
Dexed::Dexed(int rate)
7 years ago
{
uint8_t i;
Exp2::init();
Tanh::init();
Sin::init();
7 years ago
Freqlut::init(rate);
Lfo::init(rate);
PitchEnv::init(rate);
Env::init_sr(rate);
Porta::init_sr(rate);
fx.init(rate);
7 years ago
engineMkI = new EngineMkI;
engineOpl = new EngineOpl;
engineMsfa = new FmCore;
7 years ago
for (i = 0; i < MAX_ACTIVE_NOTES; i++) {
7 years ago
voices[i].dx7_note = new Dx7Note;
voices[i].keydown = false;
voices[i].sustained = false;
voices[i].live = false;
}
max_notes = MAX_NOTES;
7 years ago
currentNote = 0;
resetControllers();
controllers.masterTune = 0;
controllers.opSwitch = 0x3f; // enable all operators
7 years ago
//controllers.opSwitch=0x00;
lastKeyDown = -1;
7 years ago
lfo.reset(data + 137);
7 years ago
setMonoMode(false);
sustain = false;
setEngineType(DEXED_ENGINE);
7 years ago
}
Dexed::~Dexed()
{
currentNote = -1;
for (uint8_t note = 0; note < MAX_ACTIVE_NOTES; note++)
7 years ago
delete voices[note].dx7_note;
7 years ago
7 years ago
delete(engineMsfa);
delete(engineOpl);
delete(engineMkI);
7 years ago
}
void Dexed::activate(void)
{
panic();
controllers.values_[kControllerPitchRange] = data[155];
controllers.values_[kControllerPitchStep] = data[156];
controllers.refresh();
7 years ago
}
void Dexed::deactivate(void)
{
panic();
7 years ago
}
7 years ago
void Dexed::getSamples(uint16_t n_samples, int16_t* buffer)
7 years ago
{
7 years ago
uint16_t i;
float sumbuf[n_samples];
7 years ago
if (refreshVoice)
{
for (i = 0; i < max_notes; i++)
{
7 years ago
if ( voices[i].live )
voices[i].dx7_note->update(data, voices[i].midi_note, voices[i].velocity);
}
lfo.reset(data + 137);
7 years ago
refreshVoice = false;
}
for (i = 0; i < n_samples; i += _N_)
{
AlignedBuf<int32_t, _N_> audiobuf;
for (uint8_t j = 0; j < _N_; ++j)
{
audiobuf.get()[j] = 0;
sumbuf[i + j] = 0.0;
7 years ago
}
int32_t lfovalue = lfo.getsample();
int32_t lfodelay = lfo.getdelay();
for (uint8_t note = 0; note < max_notes; note++)
{
if (voices[note].live)
{
voices[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay, &controllers);
for (uint8_t j = 0; j < _N_; ++j)
{
int32_t val = audiobuf.get()[j];
val = val >> 4;
#ifdef USE_TEENSY_DSP
int32_t clip_val = signed_saturate_rshift(val, 24, 9);
#else
int32_t clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff : val >> 9;
#endif
6 years ago
float f = static_cast<float>(clip_val >> REDUCE_LOUDNESS) / 0x7fff;
if (f > 1.0)
{
f = 1.0;
overload++;
}
else if (f < -1.0)
{
f = -1.0;
overload++;
}
sumbuf[i + j] += f;
audiobuf.get()[j] = 0;
7 years ago
}
}
}
7 years ago
}
fx.process(sumbuf, n_samples);
//#ifdef USE_TEENSY_DSP
//arm_float_to_q15(sumbuf, buffer, AUDIO_BLOCK_SAMPLES);
//#else
for (i = 0; i < n_samples; ++i)
6 years ago
buffer[i] = static_cast<int16_t>(sumbuf[i] * 0x7fff);
//#endif
7 years ago
}
void Dexed::keydown(uint8_t pitch, uint8_t velo) {
if ( velo == 0 ) {
keyup(pitch);
return;
}
pitch += data[144] - TRANSPOSE_FIX;
int previousKeyDown = lastKeyDown;
lastKeyDown = pitch;
int porta = -1;
if ( controllers.portamento_enable_cc && previousKeyDown >= 0 )
porta = controllers.portamento_cc;
uint8_t note = currentNote;
uint8_t keydown_counter = 0;
for (uint8_t i = 0; i < max_notes; i++) {
if (!voices[note].keydown) {
currentNote = (note + 1) % max_notes;
voices[note].midi_note = pitch;
voices[note].velocity = velo;
voices[note].sustained = sustain;
voices[note].keydown = true;
voices[note].dx7_note->init(data, pitch, velo, previousKeyDown, porta);
if ( data[136] )
voices[note].dx7_note->oscSync();
break;
7 years ago
}
else
keydown_counter++;
7 years ago
note = (note + 1) % max_notes;
}
7 years ago
if (keydown_counter == 0)
lfo.keydown();
if ( monoMode ) {
for (uint8_t i = 0; i < max_notes; i++) {
if ( voices[i].live ) {
// all keys are up, only transfer signal
if ( ! voices[i].keydown ) {
voices[i].live = false;
voices[note].dx7_note->transferSignal(*voices[i].dx7_note);
break;
7 years ago
}
if ( voices[i].midi_note < pitch ) {
voices[i].live = false;
voices[note].dx7_note->transferState(*voices[i].dx7_note);
break;
}
return;
}
7 years ago
}
}
voices[note].live = true;
7 years ago
}
void Dexed::keyup(uint8_t pitch) {
pitch += data[144] - TRANSPOSE_FIX;
7 years ago
uint8_t note;
for (note = 0; note < max_notes; note++) {
if ( voices[note].midi_note == pitch && voices[note].keydown ) {
voices[note].keydown = false;
break;
7 years ago
}
}
// note not found ?
if ( note >= max_notes ) {
return;
}
if ( monoMode ) {
int8_t highNote = -1;
int8_t target = 0;
for (int8_t i = 0; i < max_notes; i++) {
if ( voices[i].keydown && voices[i].midi_note > highNote ) {
target = i;
highNote = voices[i].midi_note;
}
7 years ago
}
7 years ago
if ( highNote != -1 && voices[note].live ) {
voices[note].live = false;
voices[target].live = true;
voices[target].dx7_note->transferState(*voices[note].dx7_note);
7 years ago
}
}
if ( sustain ) {
voices[note].sustained = true;
} else {
voices[note].dx7_note->keyup();
}
7 years ago
}
void Dexed::doRefreshVoice(void)
{
refreshVoice = true;
}
void Dexed::setOPs(uint8_t ops)
{
controllers.opSwitch = ops;
}
7 years ago
uint8_t Dexed::getEngineType() {
return engineType;
7 years ago
}
void Dexed::setEngineType(uint8_t tp) {
if (engineType == tp)
return;
switch (tp) {
case DEXED_ENGINE_MARKI:
controllers.core = engineMkI;
break;
case DEXED_ENGINE_OPL:
controllers.core = engineOpl;
break;
default:
controllers.core = engineMsfa;
tp = DEXED_ENGINE_MODERN;
break;
}
engineType = tp;
panic();
controllers.refresh();
7 years ago
}
bool Dexed::isMonoMode(void) {
return monoMode;
7 years ago
}
void Dexed::setMonoMode(bool mode) {
if (monoMode == mode)
return;
7 years ago
monoMode = mode;
7 years ago
}
void Dexed::setSustain(bool s)
{
if (sustain == s)
return;
sustain = s;
}
bool Dexed::getSustain(void)
{
return sustain;
}
7 years ago
void Dexed::panic(void) {
for (uint8_t i = 0; i < MAX_ACTIVE_NOTES; i++)
7 years ago
{
if (voices[i].live == true) {
7 years ago
voices[i].keydown = false;
voices[i].live = false;
voices[i].sustained = false;
if ( voices[i].dx7_note != NULL ) {
voices[i].dx7_note->oscSync();
}
}
}
}
void Dexed::resetControllers(void)
{
controllers.values_[kControllerPitch] = 0x2000;
controllers.values_[kControllerPitchRange] = 0;
controllers.values_[kControllerPitchStep] = 0;
controllers.modwheel_cc = 0;
controllers.foot_cc = 0;
controllers.breath_cc = 0;
controllers.aftertouch_cc = 0;
controllers.portamento_enable_cc = false;
controllers.portamento_cc = 0;
controllers.refresh();
}
void Dexed::notesOff(void) {
for (uint8_t i = 0; i < MAX_ACTIVE_NOTES; i++) {
if (voices[i].live == true && voices[i].keydown == true) {
voices[i].keydown = false;
7 years ago
}
}
}
void Dexed::setMaxNotes(uint8_t n) {
if (n <= MAX_ACTIVE_NOTES)
{
notesOff();
max_notes = n;
panic();
controllers.refresh();
}
}
uint8_t Dexed::getMaxNotes(void)
{
return max_notes;
}
uint8_t Dexed::getNumNotesPlaying(void)
{
uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers
uint8_t i;
uint8_t count_playing_voices = 0;
for (i = 0; i < max_notes; i++)
{
if (voices[i].live == true)
{
uint8_t op_amp = 0;
uint8_t op_carrier_num = 0;
memset(&voiceStatus, 0, sizeof(VoiceStatus));
voices[i].dx7_note->peekVoiceStatus(voiceStatus);
for (uint8_t op = 0; op < 6; op++)
{
if ((op_carrier & (1 << op)))
{
// this voice is a carrier!
op_carrier_num++;
if (voiceStatus.amp[op] <= 1069 && voiceStatus.ampStep[op] == 4)
{
// this voice produces no audio output
op_amp++;
}
}
}
if (op_amp == op_carrier_num)
{
// all carrier-operators are silent -> disable the voice
voices[i].live = false;
voices[i].sustained = false;
voices[i].keydown = false;
#ifdef DEBUG
Serial.print(F("Shutdown voice: "));
Serial.println(i, DEC);
#endif
}
else
count_playing_voices++;
}
}
return (count_playing_voices);
}
bool Dexed::loadPackedVoiceParameters(uint8_t* new_data)
{
uint8_t* p_data = data;
uint8_t op;
uint8_t tmp;
char dexed_voice_name[11];
notesOff();
for (op = 0; op < 6; op++)
{
// DEXED_OP_EG_R1, // 0
// DEXED_OP_EG_R2, // 1
// DEXED_OP_EG_R3, // 2
// DEXED_OP_EG_R4, // 3
// DEXED_OP_EG_L1, // 4
// DEXED_OP_EG_L2, // 5
// DEXED_OP_EG_L3, // 6
// DEXED_OP_EG_L4, // 7
// DEXED_OP_LEV_SCL_BRK_PT, // 8
// DEXED_OP_SCL_LEFT_DEPTH, // 9
// DEXED_OP_SCL_RGHT_DEPTH, // 10
memcpy(&data[op * 21], &new_data[op * 17], 11);
tmp = new_data[(op * 17) + 11];
*(p_data + DEXED_OP_SCL_LEFT_CURVE + (op * 21)) = (tmp & 0x3);
*(p_data + DEXED_OP_SCL_RGHT_CURVE + (op * 21)) = (tmp & 0x0c) >> 2;
tmp = new_data[(op * 17) + 12];
*(p_data + DEXED_OP_OSC_DETUNE + (op * 21)) = (tmp & 0x78) >> 3;
*(p_data + DEXED_OP_OSC_RATE_SCALE + (op * 21)) = (tmp & 0x07);
tmp = new_data[(op * 17) + 13];
*(p_data + DEXED_OP_KEY_VEL_SENS + (op * 21)) = (tmp & 0x1c) >> 2;
*(p_data + DEXED_OP_AMP_MOD_SENS + (op * 21)) = (tmp & 0x03);
*(p_data + DEXED_OP_OUTPUT_LEV + (op * 21)) = new_data[(op * 17) + 14];
tmp = new_data[(op * 17) + 15];
*(p_data + DEXED_OP_FREQ_COARSE + (op * 21)) = (tmp & 0x3e) >> 1;
*(p_data + DEXED_OP_OSC_MODE + (op * 21)) = (tmp & 0x01);
*(p_data + DEXED_OP_FREQ_FINE + (op * 21)) = new_data[(op * 17) + 16];
}
// DEXED_PITCH_EG_R1, // 0
// DEXED_PITCH_EG_R2, // 1
// DEXED_PITCH_EG_R3, // 2
// DEXED_PITCH_EG_R4, // 3
// DEXED_PITCH_EG_L1, // 4
// DEXED_PITCH_EG_L2, // 5
// DEXED_PITCH_EG_L3, // 6
// DEXED_PITCH_EG_L4, // 7
memcpy(&data[DEXED_VOICE_OFFSET], &new_data[102], 8);
tmp = new_data[110];
*(p_data + DEXED_VOICE_OFFSET + DEXED_ALGORITHM) = (tmp & 0x1f);
tmp = new_data[111];
*(p_data + DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC) = (tmp & 0x08) >> 3;
*(p_data + DEXED_VOICE_OFFSET + DEXED_FEEDBACK) = (tmp & 0x07);
// DEXED_LFO_SPEED, // 11
// DEXED_LFO_DELAY, // 12
// DEXED_LFO_PITCH_MOD_DEP, // 13
// DEXED_LFO_AMP_MOD_DEP, // 14
memcpy(&data[DEXED_VOICE_OFFSET + DEXED_LFO_SPEED], &new_data[112], 4);
tmp = new_data[116];
*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS) = (tmp & 0x30) >> 4;
*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_WAVE) = (tmp & 0x0e) >> 1;
*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_SYNC) = (tmp & 0x01);
*(p_data + DEXED_VOICE_OFFSET + DEXED_TRANSPOSE) = new_data[117];
memcpy(&data[DEXED_VOICE_OFFSET + DEXED_NAME], &new_data[118], 10);
panic();
doRefreshVoice();
//activate();
strncpy(dexed_voice_name, (char *)&new_data[118], sizeof(dexed_voice_name) - 1);
dexed_voice_name[10] = '\0';
#ifdef DEBUG
Serial.print(F("Voice ["));
Serial.print(dexed_voice_name);
Serial.println(F("] loaded."));
#endif
return (true);
}
bool Dexed::loadVoiceParameters(uint8_t* new_data)
{
char dexed_voice_name[11];
notesOff();
memcpy(&data, new_data, 155);
panic();
doRefreshVoice();
//activate();
strncpy(dexed_voice_name, (char *)&new_data[145], sizeof(dexed_voice_name) - 1);
dexed_voice_name[10] = '\0';
#ifdef DEBUG
Serial.print(F("Voice ["));
Serial.print(dexed_voice_name);
Serial.println(F("] loaded."));
#endif
return (true);
}
bool Dexed::loadGlobalParameters(uint8_t* new_data)
{
uint8_t* p_data = data;
controllers.values_[kControllerPitchRange] = new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PITCHBEND_RANGE];
controllers.values_[kControllerPitchStep] = new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PITCHBEND_STEP];
controllers.wheel.setRange(new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MODWHEEL_RANGE]);
controllers.wheel.setTarget(new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MODWHEEL_ASSIGN]);
controllers.foot.setRange(new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_FOOTCTRL_RANGE]);
controllers.foot.setTarget(new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_FOOTCTRL_ASSIGN]);
controllers.breath.setRange(new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_BREATHCTRL_RANGE]);
controllers.breath.setTarget(new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_BREATHCTRL_ASSIGN]);
controllers.at.setRange(new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_AT_RANGE]);
controllers.at.setTarget(new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_AT_ASSIGN]);
controllers.masterTune = (new_data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MASTER_TUNE] * 0x4000 << 11) * (1.0 / 12);
controllers.refresh();
setOPs((*(p_data + DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_OP1_ENABLE) << 5) |
(*(p_data + DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_OP2_ENABLE) << 4) |
(*(p_data + DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_OP3_ENABLE) << 3) |
(*(p_data + DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_OP4_ENABLE) << 2) |
(*(p_data + DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_OP5_ENABLE) << 1) |
*(p_data + DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_OP6_ENABLE ));
setMaxNotes(*(p_data + DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MAX_NOTES));
setMaxNotes(*(p_data + DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MAX_NOTES));
//panic();
doRefreshVoice();
//activate();
#ifdef DEBUG
Serial.println(F("Global parameters loaded."));
#endif
return (true);
}
bool Dexed::initGlobalParameters(void)
{
uint8_t init_data[18];
#ifdef DEBUG
Serial.println(F("Initializing global parameters"));
#endif
init_data[DEXED_PITCHBEND_RANGE] = 1;
init_data[DEXED_PITCHBEND_STEP] = 1;
init_data[DEXED_MODWHEEL_RANGE] = 99;
init_data[DEXED_MODWHEEL_ASSIGN] = 7;
init_data[DEXED_FOOTCTRL_RANGE] = 99;
init_data[DEXED_FOOTCTRL_ASSIGN] = 7;
init_data[DEXED_BREATHCTRL_RANGE] = 99;
init_data[DEXED_BREATHCTRL_ASSIGN] = 7;
init_data[DEXED_AT_RANGE] = 99;
init_data[DEXED_AT_ASSIGN] = 7;
init_data[DEXED_MASTER_TUNE] = 0;
init_data[DEXED_OP1_ENABLE] = 1;
init_data[DEXED_OP2_ENABLE] = 1;
init_data[DEXED_OP3_ENABLE] = 1;
init_data[DEXED_OP4_ENABLE] = 1;
init_data[DEXED_OP5_ENABLE] = 1;
init_data[DEXED_OP6_ENABLE] = 1;
init_data[DEXED_MAX_NOTES] = MAX_NOTES;
loadGlobalParameters(init_data);
return (true);
}
void Dexed::setPBController(uint8_t pb_range, uint8_t pb_step)
{
#ifdef DEBUG
Serial.println(F("Dexed::setPBController"));
#endif
pb_range = constrain(pb_range, PB_RANGE_MIN, PB_RANGE_MAX);
pb_step = constrain(pb_step, PB_STEP_MIN, PB_STEP_MAX);
#ifdef DEBUG
Serial.print(F("pb_range="));
Serial.println(pb_range, DEC);
Serial.print(F("pb_step="));
Serial.println(pb_step, DEC);
#endif
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PITCHBEND_RANGE] = pb_range;
controllers.values_[kControllerPitchRange] = pb_range;
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PITCHBEND_STEP] = pb_step;
controllers.values_[kControllerPitchStep] = pb_step;
//controllers.refresh();
}
void Dexed::setMWController(uint8_t mw_range, uint8_t mw_assign)
{
#ifdef DEBUG
Serial.println(F("Dexed::setMWController"));
#endif
mw_range = constrain(mw_range, MW_RANGE_MIN, MW_RANGE_MAX);
mw_assign = constrain(mw_assign, MW_ASSIGN_MIN, MW_ASSIGN_MAX);
#ifdef DEBUG
Serial.print(F("mw_range="));
Serial.println(mw_range, DEC);
Serial.print(F("mw_assign="));
Serial.println(mw_assign, DEC);
#endif
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MODWHEEL_RANGE] = mw_range;
controllers.wheel.setRange(mw_range);
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MODWHEEL_ASSIGN] = mw_assign;
controllers.wheel.setTarget(mw_assign);
controllers.refresh();
}
void Dexed::setFCController(uint8_t fc_range, uint8_t fc_assign)
{
#ifdef DEBUG
Serial.println(F("Dexed::setFCController"));
#endif
fc_range = constrain(fc_range, FC_RANGE_MIN, FC_RANGE_MAX);
fc_assign = constrain(fc_assign, FC_ASSIGN_MIN, FC_ASSIGN_MAX);
#ifdef DEBUG
Serial.print(F("fc_range="));
Serial.println(fc_range, DEC);
Serial.print(F("fc_assign="));
Serial.println(fc_assign, DEC);
#endif
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_FOOTCTRL_RANGE] = fc_range;
controllers.foot.setRange(fc_range);
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_FOOTCTRL_ASSIGN] = fc_assign;
controllers.foot.setTarget(fc_assign);
controllers.refresh();
}
void Dexed::setBCController(uint8_t bc_range, uint8_t bc_assign)
{
#ifdef DEBUG
Serial.println(F("Dexed::setBCController"));
#endif
bc_range = constrain(bc_range, BC_RANGE_MIN, BC_RANGE_MAX);
bc_assign = constrain(bc_assign, BC_ASSIGN_MIN, BC_ASSIGN_MAX);
#ifdef DEBUG
Serial.print(F("bc_range="));
Serial.println(bc_range, DEC);
Serial.print(F("bc_assign="));
Serial.println(bc_assign, DEC);
#endif
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_BREATHCTRL_RANGE] = bc_range;
controllers.breath.setRange(bc_range);
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_BREATHCTRL_ASSIGN] = bc_assign;
controllers.breath.setTarget(bc_assign);
controllers.refresh();
}
void Dexed::setATController(uint8_t at_range, uint8_t at_assign)
{
#ifdef DEBUG
Serial.println(F("Dexed::setATController"));
#endif
at_range = constrain(at_range, AT_RANGE_MIN, AT_RANGE_MAX);
at_assign = constrain(at_assign, AT_ASSIGN_MIN, AT_ASSIGN_MAX);
#ifdef DEBUG
Serial.print(F("at_range="));
Serial.println(at_range, DEC);
Serial.print(F("at_assign="));
Serial.println(at_assign, DEC);
#endif
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_AT_RANGE] = at_range;
controllers.at.setRange(at_range);
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_AT_ASSIGN] = at_assign;
controllers.at.setTarget(at_assign);
controllers.refresh();
}
void Dexed::setPortamentoMode(uint8_t portamento_mode, uint8_t portamento_glissando, uint8_t portamento_time)
{
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_MODE] = portamento_mode;
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_GLISSANDO] = portamento_glissando;
data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_TIME] = portamento_time;
controllers.portamento_cc = portamento_time;
if (portamento_time > 0)
controllers.portamento_enable_cc = true;
else
controllers.portamento_enable_cc = false;
controllers.refresh();
}