diff --git a/pure_adsr/hysteresis_quantizer.h b/pure_adsr/hysteresis_quantizer.h new file mode 100644 index 0000000..e96ffa9 --- /dev/null +++ b/pure_adsr/hysteresis_quantizer.h @@ -0,0 +1,81 @@ +// Copyright 2015 Emilie Gillet. +// +// Author: Emilie Gillet (emilie.o.gillet@gmail.com) +// +// 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. +// +// See http://creativecommons.org/licenses/MIT/ for more information. +// +// ----------------------------------------------------------------------------- +// +// Quantize a float in [0, 1] to an integer in [0, num_steps[. Apply hysteresis +// to prevent jumps near the decision boundary. + +#ifndef STMLIB_DSP_HYSTERESIS_QUANTIZER_H_ +#define STMLIB_DSP_HYSTERESIS_QUANTIZER_H_ + +#include + +namespace pure_adsr { + +class HysteresisQuantizer { + public: + HysteresisQuantizer() { } + ~HysteresisQuantizer() { } + + void Init() { + quantized_value_ = 0; + } + + inline int Process(float value, int num_steps) { + return Process(value, num_steps, 0.25f); + } + + inline int Process(float value, int num_steps, float hysteresis) { + return Process(0, value, num_steps, hysteresis); + } + + inline int Process(int base, float value, int num_steps, float hysteresis) { + value *= static_cast(num_steps - 1); + value += static_cast(base); + float hysteresis_feedback = value > static_cast(quantized_value_) + ? -hysteresis + : hysteresis; + int q = static_cast(value + hysteresis_feedback + 0.5f); + if(q < 0) q = 0; + if(q > num_steps - 1) q = num_steps - 1; + quantized_value_ = q; + return q; + } + + template + const T& Lookup(const T* array, float value, int num_steps) { + return array[Process(value, num_steps)]; + } + + private: + int quantized_value_; + + DISALLOW_COPY_AND_ASSIGN(HysteresisQuantizer); +}; + +} // namespace stmlib + +#endif // STMLIB_DSP_HYSTERESIS_QUANTIZER_H_ + diff --git a/pure_adsr/pure_adsr.cc b/pure_adsr/pure_adsr.cc index ca34e48..27967ed 100644 --- a/pure_adsr/pure_adsr.cc +++ b/pure_adsr/pure_adsr.cc @@ -13,6 +13,7 @@ #include "pure_adsr/io_buffer.h" #include "pure_adsr/mini_sequencer.h" #include "pure_adsr/multistage_envelope.h" +#include "pure_adsr/tides.h" using namespace avrlib; using namespace pure_adsr; @@ -20,7 +21,7 @@ using namespace pure_adsr; typedef enum { MODE_ADSR, MODE_SEQ, - MODE_LFO + MODE_TIDES } Mode; typedef SpiMaster, MSB_FIRST, 2> dac1Spi; @@ -38,6 +39,7 @@ typedef AdcInputScanner AnalogInputs; MultistageEnvelope envelope; MiniSequencer sequencer; +Tides tides; IOBuffer io_buffer; int16_t output_buffer[kBlockSize]; @@ -55,6 +57,7 @@ void Process(IOBuffer::Block* block, size_t size) pot_values[i] = read_value; envelope.Configure(pot_values, CONTROL_MODE_FULL); sequencer.Configure(pot_values, CONTROL_MODE_FULL); + tides.Configure(pot_values, CONTROL_MODE_FULL); } } @@ -66,7 +69,9 @@ void Process(IOBuffer::Block* block, size_t size) break; case MODE_SEQ: sequencer.Process(block->input[i], output_buffer, size); - default: + break; + case MODE_TIDES: + tides.Process(block->input[i], output_buffer, size); break; } @@ -101,6 +106,7 @@ void Init() pots[4 - (i + 1)] = AnalogInputs::Read8(i) << 8; envelope.Configure(pots, CONTROL_MODE_FULL); sequencer.Configure(pots, CONTROL_MODE_FULL); + tides.Configure(pots, CONTROL_MODE_FULL); Timer<2>::Start(); } @@ -128,7 +134,7 @@ TIMER_0_TICK mode = MODE_SEQ; break; case 1: - mode = MODE_LFO; + mode = MODE_TIDES; break; case 2: mode = MODE_ADSR; diff --git a/pure_adsr/resources.cc b/pure_adsr/resources.cc index 207e8c9..85839e4 100644 --- a/pure_adsr/resources.cc +++ b/pure_adsr/resources.cc @@ -215,10 +215,46 @@ const prog_uint32_t lut_res_env_increments[] PROGMEM = { 253893, 249198, 244605, 240110, 235713, 231409, 227198, 223077, 219043, }; +const prog_uint32_t lut_res_env_increments_slow[] PROGMEM = { + 35046933, 33184343, 31436989, 29796754, 28256163, 26808323, 25446875, 24165949, + 22960121, 21824376, 20754076, 19744924, 18792940, 17894435, 17045985, 16244414, + 15486770, 14770309, 14092480, 13450909, 12843386, 12267850, 11722382, 11205191, + 10714605, 10249065, 9807112, 9387383, 8988604, 8609583, 8249203, 7906418, + 7580249, 7269776, 6974139, 6692528, 6424184, 6168395, 5924489, 5691837, + 5469847, 5257961, 5055654, 4862432, 4677829, 4501406, 4332750, 4171471, + 4017200, 3869589, 3728311, 3593055, 3463528, 3339453, 3220568, 3106625, + 2997389, 2892639, 2792164, 2695765, 2603253, 2514449, 2429184, 2347295, + 2268632, 2193047, 2120405, 2050573, 1983428, 1918852, 1856732, 1796962, + 1739439, 1684069, 1630758, 1579418, 1529968, 1482327, 1436419, 1392173, + 1349520, 1308394, 1268733, 1230477, 1193569, 1157955, 1123584, 1090404, + 1058371, 1027437, 997560, 968699, 940814, 913868, 887824, 862647, + 838306, 814767, 792001, 769979, 748673, 728057, 708104, 688790, + 670093, 651989, 634458, 617477, 601028, 585092, 569649, 554684, + 540178, 526116, 512482, 499262, 486440, 474003, 461939, 450233, + 438875, 427852, 417153, 406766, 396683, 386891, 377383, 368148, + 359177, 350462, 341994, 333766, 325770, 317997, 310442, 303096, + 295954, 289009, 282255, 275685, 269295, 263077, 257028, 251142, + 245413, 239837, 234409, 229126, 223981, 218972, 214094, 209343, + 204715, 200207, 195816, 191537, 187368, 183304, 179344, 175484, + 171722, 168054, 164477, 160990, 157589, 154272, 151037, 147882, + 144803, 141800, 138869, 136010, 133219, 130496, 127837, 125242, + 122709, 120236, 117821, 115462, 113160, 110911, 108714, 106568, + 104472, 102424, 100423, 98468, 96557, 94690, 92865, 91081, + 89338, 87633, 85967, 84338, 82744, 81187, 79663, 78173, + 76716, 75290, 73895, 72531, 71196, 69890, 68612, 67362, + 66138, 64940, 63767, 62620, 61496, 60396, 59320, 58265, + 57233, 56222, 55232, 54262, 53313, 52382, 51471, 50579, + 49704, 48847, 48008, 47185, 46379, 45589, 44815, 44056, + 43313, 42584, 41869, 41168, 40481, 39808, 39148, 38500, + 37865, 37243, 36632, 36033, 35446, 34869, 34304, 33750, + 33206, 32672, 32148, 31634, 31130, 30635, 30150, 29673, + 29205, +}; const prog_uint32_t* const lookup_table_32_table[] = { lut_res_env_increments, + lut_res_env_increments_slow, }; diff --git a/pure_adsr/resources.h b/pure_adsr/resources.h index 2e9f360..7504953 100644 --- a/pure_adsr/resources.h +++ b/pure_adsr/resources.h @@ -44,6 +44,7 @@ extern const prog_uint16_t lut_res_env_expo[] PROGMEM; extern const prog_uint16_t lut_res_env_quartic[] PROGMEM; extern const prog_uint16_t lut_res_raised_cosine[] PROGMEM; extern const prog_uint32_t lut_res_env_increments[] PROGMEM; +extern const prog_uint32_t lut_res_env_increments_slow[] PROGMEM; #define STR_RES_DUMMY 0 // dummy #define LUT_RES_ENV_LINEAR 0 #define LUT_RES_ENV_LINEAR_SIZE 257 @@ -55,6 +56,8 @@ extern const prog_uint32_t lut_res_env_increments[] PROGMEM; #define LUT_RES_RAISED_COSINE_SIZE 257 #define LUT_RES_ENV_INCREMENTS 0 #define LUT_RES_ENV_INCREMENTS_SIZE 257 +#define LUT_RES_ENV_INCREMENTS_SLOW 1 +#define LUT_RES_ENV_INCREMENTS_SLOW_SIZE 257 } // namespace pure_adsr diff --git a/pure_adsr/resources/lookup_tables.py b/pure_adsr/resources/lookup_tables.py index ff415cf..faefbf3 100644 --- a/pure_adsr/resources/lookup_tables.py +++ b/pure_adsr/resources/lookup_tables.py @@ -61,6 +61,20 @@ lookup_tables_32.append( ('env_increments', values) ) + +max_time = 60.0 # seconds +min_time = 0.05 +gamma = 0.175 +min_increment = excursion / (max_time * sample_rate) +max_increment = excursion / (min_time * sample_rate) + +rates = numpy.linspace(numpy.power(max_increment, -gamma), + numpy.power(min_increment, -gamma), num_values) + +values = numpy.power(rates, -1/gamma).astype(int) +lookup_tables_32.append( + ('env_increments_slow', values) +) """---------------------------------------------------------------------------- Envelope curves -----------------------------------------------------------------------------""" @@ -69,7 +83,6 @@ env_linear = numpy.arange(0, 257.0) / 256.0 env_linear[-1] = env_linear[-2] env_quartic = env_linear ** 3.32 env_expo = 1.0 - numpy.exp(-4 * env_linear) - lookup_tables.append(('env_linear', env_linear / env_linear.max() * 65535.0)) lookup_tables.append(('env_expo', env_expo / env_expo.max() * 65535.0)) lookup_tables.append( diff --git a/pure_adsr/tides.cc b/pure_adsr/tides.cc new file mode 100644 index 0000000..d9c10e0 --- /dev/null +++ b/pure_adsr/tides.cc @@ -0,0 +1,89 @@ +// Copyright 2013 Emilie Gillet. +// +// Author: Emilie Gillet (emilie.o.gillet@gmail.com) +// +// 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. +// +// See http://creativecommons.org/licenses/MIT/ for more information. +// +// ----------------------------------------------------------------------------- +// +// Multistage envelope. + +#include "pure_adsr/tides.h" + +#include "pure_adsr/resources.h" + +namespace pure_adsr { + +void Tides::Init() +{ + set_adsr(0, 8192, 16384, 32767); + segment_ = num_segments_; + phase_ = 0; + phase_increment_ = 0; + start_value_ = 0; + value_ = 0; + hard_reset_ = false; + quant.Init(); +} + +void Tides::Process( + const GateFlags* gate_flags, int16_t* out, size_t size) +{ + while (size--) { + GateFlags gate_flag = *gate_flags++; + if (gate_flag & GATE_FLAG_RISING) { + start_value_ = (segment_ == num_segments_ || hard_reset_) + ? level_[0] + : value_; + segment_ = 0; + phase_ = 0; + } else if (gate_flag & GATE_FLAG_FALLING && sustain_point_) { + start_value_ = value_; + segment_ = sustain_point_; + phase_ = 0; + } else if (phase_ < phase_increment_) { + start_value_ = level_[segment_ + 1]; + ++segment_; + phase_ = 0; + if (segment_ == loop_end_) { + segment_ = loop_start_; + } + } + + bool done = segment_ == num_segments_; + bool sustained = sustain_point_ && segment_ == sustain_point_ && gate_flag & GATE_FLAG_HIGH; + + phase_increment_ = sustained || done ? 0 : pgm_read_dword(lut_res_env_increments_slow + (time_[segment_] >> 8)); + + int32_t a = start_value_; + int32_t b = level_[segment_ + 1]; + + uint32_t x = pgm_read_word(lookup_table_table[LUT_RES_ENV_LINEAR + shape_[segment_]] + (phase_ >> 24)); + uint32_t y = pgm_read_word(lookup_table_table[LUT_RES_ENV_LINEAR + shape_[segment_]] + (phase_ >> 24) + 1); + uint16_t t = x + ((y - x) * static_cast((phase_ >> 8) & 0xffff) >> 16); + + value_ = a + ((b - a) * (t >> 1) >> 15); + phase_ += phase_increment_; + *out++ = value_; + } +} + +} // namespace peaks diff --git a/pure_adsr/tides.h b/pure_adsr/tides.h new file mode 100644 index 0000000..88dbd46 --- /dev/null +++ b/pure_adsr/tides.h @@ -0,0 +1,349 @@ +// Copyright 2013 Emilie Gillet. +// +// Author: Emilie Gillet (emilie.o.gillet@gmail.com) +// +// 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. +// +// See http://creativecommons.org/licenses/MIT/ for more information. +// +// ----------------------------------------------------------------------------- +// +// Multistage envelope. + +#ifndef PEAKS_MODULATIONS_TIDES_H_ +#define PEAKS_MODULATIONS_TIDES_H_ + +#include "pure_adsr/gate_processor.h" +#include "pure_adsr/hysteresis_quantizer.h" +#include "pure_adsr/multistage_envelope.h" +#include +#include + +namespace pure_adsr { + +class Tides { + public: + Tides() {} + ~Tides() {} + + void Init(); + void Process(const GateFlags* gate_flags, int16_t* out, size_t size); + + void Configure(uint16_t* parameter, ControlMode control_mode) + { + set_ar_loop(parameter[0], parameter[1]); + + uint8_t waveform_mode = quant.Process(parameter[2] / 65536.0f, 5); + + switch (waveform_mode) { + case 0: + shape_[0] = ENV_SHAPE_QUARTIC; + shape_[1] = ENV_SHAPE_EXPONENTIAL; + break; + case 1: + shape_[0] = ENV_SHAPE_QUARTIC; + shape_[1] = ENV_SHAPE_QUARTIC; + break; + case 2: + shape_[0] = ENV_SHAPE_LINEAR; + shape_[1] = ENV_SHAPE_LINEAR; + break; + case 3: + shape_[0] = ENV_SHAPE_EXPONENTIAL; + shape_[1] = ENV_SHAPE_EXPONENTIAL; + break; + case 4: + shape_[0] = ENV_SHAPE_EXPONENTIAL; + shape_[1] = ENV_SHAPE_QUARTIC; + break; + } + + if (segment_ > num_segments_) { + segment_ = 0; + phase_ = 0; + value_ = 0; + } + } + + inline void set_time(uint16_t segment, uint16_t time) + { + time_[segment] = time; + } + + inline void set_level(uint16_t segment, int16_t level) + { + level_[segment] = level; + } + + inline void set_num_segments(uint16_t num_segments) + { + num_segments_ = num_segments; + } + + inline void set_sustain_point(uint16_t sustain_point) + { + sustain_point_ = sustain_point; + } + + inline void set_adsr( + uint16_t attack, + uint16_t decay, + uint16_t sustain, + uint16_t release) + { + num_segments_ = 3; + sustain_point_ = 2; + + level_[0] = 0; + level_[1] = 32767; + level_[2] = sustain; + level_[3] = 0; + + time_[0] = attack; + time_[1] = decay; + time_[2] = release; + + shape_[0] = ENV_SHAPE_QUARTIC; + shape_[1] = ENV_SHAPE_EXPONENTIAL; + shape_[2] = ENV_SHAPE_EXPONENTIAL; + + loop_start_ = loop_end_ = 0; + } + + inline void set_ad(uint16_t attack, uint16_t decay) + { + num_segments_ = 2; + sustain_point_ = 0; + + level_[0] = 0; + level_[1] = 32767; + level_[2] = 0; + + time_[0] = attack; + time_[1] = decay; + + shape_[0] = ENV_SHAPE_EXPONENTIAL; + shape_[1] = ENV_SHAPE_EXPONENTIAL; + + loop_start_ = loop_end_ = 0; + } + + inline void set_adr( + uint16_t attack, + uint16_t decay, + uint16_t sustain, + uint16_t release) + { + num_segments_ = 3; + sustain_point_ = 0; + + level_[0] = 0; + level_[1] = 32767; + level_[2] = sustain; + level_[3] = 0; + + time_[0] = attack; + time_[1] = decay; + time_[2] = release; + + shape_[0] = ENV_SHAPE_LINEAR; + shape_[1] = ENV_SHAPE_LINEAR; + shape_[2] = ENV_SHAPE_LINEAR; + + loop_start_ = loop_end_ = 0; + } + + inline void set_ar(uint16_t attack, uint16_t decay) + { + num_segments_ = 2; + sustain_point_ = 1; + + level_[0] = 0; + level_[1] = 32767; + level_[2] = 0; + + time_[0] = attack; + time_[1] = decay; + + shape_[0] = ENV_SHAPE_LINEAR; + shape_[1] = ENV_SHAPE_LINEAR; + + loop_start_ = loop_end_ = 0; + } + + inline void set_adsar( + uint16_t attack, + uint16_t decay, + uint16_t sustain, + uint16_t release) + { + num_segments_ = 4; + sustain_point_ = 2; + + level_[0] = 0; + level_[1] = 32767; + level_[2] = sustain; + level_[3] = 32767; + level_[4] = 0; + + time_[0] = attack; + time_[1] = decay; + time_[2] = attack; + time_[3] = release; + + shape_[0] = ENV_SHAPE_LINEAR; + shape_[1] = ENV_SHAPE_LINEAR; + shape_[2] = ENV_SHAPE_LINEAR; + shape_[3] = ENV_SHAPE_LINEAR; + + loop_start_ = loop_end_ = 0; + } + + inline void set_adar( + uint16_t attack, + uint16_t decay, + uint16_t sustain, + uint16_t release) + { + num_segments_ = 4; + sustain_point_ = 0; + + level_[0] = 0; + level_[1] = 32767; + level_[2] = sustain; + level_[3] = 32767; + level_[4] = 0; + + time_[0] = attack; + time_[1] = decay; + time_[2] = attack; + time_[3] = release; + + shape_[0] = ENV_SHAPE_LINEAR; + shape_[1] = ENV_SHAPE_LINEAR; + shape_[2] = ENV_SHAPE_LINEAR; + shape_[3] = ENV_SHAPE_LINEAR; + + loop_start_ = loop_end_ = 0; + } + + inline void set_ad_loop(uint16_t attack, uint16_t decay) + { + num_segments_ = 2; + sustain_point_ = 0; + + level_[0] = 0; + level_[1] = 32767; + level_[2] = 0; + + time_[0] = attack; + time_[1] = decay; + + shape_[0] = ENV_SHAPE_LINEAR; + shape_[1] = ENV_SHAPE_LINEAR; + + loop_start_ = 0; + loop_end_ = 2; + } + + inline void set_adr_loop( + uint16_t attack, + uint16_t decay, + uint16_t sustain, + uint16_t release) + { + num_segments_ = 3; + sustain_point_ = 0; + + level_[0] = 0; + level_[1] = 32767; + level_[2] = sustain; + level_[3] = 0; + + time_[0] = attack; + time_[1] = decay; + time_[2] = release; + + shape_[0] = ENV_SHAPE_LINEAR; + shape_[1] = ENV_SHAPE_LINEAR; + shape_[2] = ENV_SHAPE_LINEAR; + + loop_start_ = 0; + loop_end_ = 3; + } + + inline void set_adar_loop( + uint16_t attack, + uint16_t decay, + uint16_t sustain, + uint16_t release) + { + num_segments_ = 4; + sustain_point_ = 0; + + level_[0] = 0; + level_[1] = 32767; + level_[2] = sustain; + level_[3] = 32767; + level_[4] = 0; + + time_[0] = attack; + time_[1] = decay; + time_[2] = attack; + time_[3] = release; + + shape_[0] = ENV_SHAPE_LINEAR; + shape_[1] = ENV_SHAPE_LINEAR; + shape_[2] = ENV_SHAPE_LINEAR; + shape_[3] = ENV_SHAPE_LINEAR; + + loop_start_ = 0; + loop_end_ = 4; + } + + inline void set_hard_reset(bool hard_reset) + { + hard_reset_ = hard_reset; + } + + private: + int16_t level_[kMaxNumSegments]; + uint16_t time_[kMaxNumSegments]; + EnvelopeShape shape_[kMaxNumSegments]; + + int16_t segment_; + int16_t start_value_; + int16_t value_; + + uint32_t phase_; + uint32_t phase_increment_; + + uint16_t num_segments_; + uint16_t sustain_point_; + uint16_t loop_start_; + uint16_t loop_end_; + + bool hard_reset_; + HysteresisQuantizer quant; + DISALLOW_COPY_AND_ASSIGN(Tides); +}; + +} // namespace peaks + +#endif // PEAKS_MODULATIONS_MULTISTAGE_ENVELOPE_H_