Permalink
Cannot retrieve contributors at this time
| // Copyright 2012 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. | |
| #include <stm32f10x_conf.h> | |
| #include <algorithm> | |
| #include "stmlib/utils/dsp.h" | |
| #include "stmlib/utils/ring_buffer.h" | |
| #include "stmlib/system/system_clock.h" | |
| #include "stmlib/system/uid.h" | |
| #include "braids/drivers/adc.h" | |
| #include "braids/drivers/dac.h" | |
| #include "braids/drivers/debug_pin.h" | |
| #include "braids/drivers/gate_input.h" | |
| #include "braids/drivers/internal_adc.h" | |
| #include "braids/drivers/system.h" | |
| #include "braids/envelope.h" | |
| #include "braids/macro_oscillator.h" | |
| #include "braids/quantizer.h" | |
| #include "braids/signature_waveshaper.h" | |
| #include "braids/vco_jitter_source.h" | |
| #include "braids/ui.h" | |
| #include "braids/quantizer_scales.h" | |
| // #define PROFILE_RENDER 1 | |
| using namespace braids; | |
| using namespace std; | |
| using namespace stmlib; | |
| const size_t kNumBlocks = 4; | |
| const size_t kBlockSize = 24; | |
| MacroOscillator osc; | |
| Envelope envelope; | |
| Adc adc; | |
| Dac dac; | |
| DebugPin debug_pin; | |
| GateInput gate_input; | |
| InternalAdc internal_adc; | |
| Quantizer quantizer; | |
| SignatureWaveshaper ws; | |
| System sys; | |
| VcoJitterSource jitter_source; | |
| Ui ui; | |
| uint8_t current_scale = 0xff; | |
| size_t current_sample; | |
| volatile size_t playback_block; | |
| volatile size_t render_block; | |
| int16_t audio_samples[kNumBlocks][kBlockSize]; | |
| uint8_t sync_samples[kNumBlocks][kBlockSize]; | |
| bool trigger_detected_flag; | |
| volatile bool trigger_flag; | |
| uint16_t trigger_delay; | |
| extern "C" { | |
| void HardFault_Handler(void) { while (1); } | |
| void MemManage_Handler(void) { while (1); } | |
| void BusFault_Handler(void) { while (1); } | |
| void UsageFault_Handler(void) { while (1); } | |
| void NMI_Handler(void) { } | |
| void SVC_Handler(void) { } | |
| void DebugMon_Handler(void) { } | |
| void PendSV_Handler(void) { } | |
| } | |
| extern "C" { | |
| void SysTick_Handler() { | |
| ui.Poll(); | |
| } | |
| void TIM1_UP_IRQHandler(void) { | |
| if (!(TIM1->SR & TIM_IT_Update)) { | |
| return; | |
| } | |
| TIM1->SR = (uint16_t)~TIM_IT_Update; | |
| dac.Write(-audio_samples[playback_block][current_sample] + 32768); | |
| bool trigger_detected = gate_input.raised(); | |
| sync_samples[playback_block][current_sample] = trigger_detected; | |
| trigger_detected_flag = trigger_detected_flag | trigger_detected; | |
| current_sample = current_sample + 1; | |
| if (current_sample >= kBlockSize) { | |
| current_sample = 0; | |
| playback_block = (playback_block + 1) % kNumBlocks; | |
| } | |
| bool adc_scan_cycle_complete = adc.PipelinedScan(); | |
| if (adc_scan_cycle_complete) { | |
| ui.UpdateCv(adc.channel(0), adc.channel(1), adc.channel(2), adc.channel(3)); | |
| if (trigger_detected_flag) { | |
| trigger_delay = settings.trig_delay() | |
| ? (1 << settings.trig_delay()) : 0; | |
| ++trigger_delay; | |
| trigger_detected_flag = false; | |
| } | |
| if (trigger_delay) { | |
| --trigger_delay; | |
| if (trigger_delay == 0) { | |
| trigger_flag = true; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void Init() { | |
| sys.Init(F_CPU / 96000 - 1, true); | |
| settings.Init(); | |
| ui.Init(); | |
| system_clock.Init(); | |
| adc.Init(false); | |
| gate_input.Init(); | |
| #ifdef PROFILE_RENDER | |
| debug_pin.Init(); | |
| #endif | |
| dac.Init(); | |
| osc.Init(); | |
| quantizer.Init(); | |
| internal_adc.Init(); | |
| for (size_t i = 0; i < kNumBlocks; ++i) { | |
| fill(&audio_samples[i][0], &audio_samples[i][kBlockSize], 0); | |
| fill(&sync_samples[i][0], &sync_samples[i][kBlockSize], 0); | |
| } | |
| playback_block = kNumBlocks / 2; | |
| render_block = 0; | |
| current_sample = 0; | |
| envelope.Init(); | |
| ws.Init(GetUniqueId(1)); | |
| jitter_source.Init(); | |
| sys.StartTimers(); | |
| } | |
| const uint16_t bit_reduction_masks[] = { | |
| 0xc000, | |
| 0xe000, | |
| 0xf000, | |
| 0xf800, | |
| 0xff00, | |
| 0xfff0, | |
| 0xffff }; | |
| const uint16_t decimation_factors[] = { 24, 12, 6, 4, 3, 2, 1 }; | |
| void RenderBlock() { | |
| static int16_t previous_pitch = 0; | |
| static int16_t previous_shape = 0; | |
| static uint16_t gain_lp; | |
| #ifdef PROFILE_RENDER | |
| debug_pin.High(); | |
| #endif | |
| envelope.Update( | |
| settings.GetValue(SETTING_AD_ATTACK) * 8, | |
| settings.GetValue(SETTING_AD_DECAY) * 8); | |
| uint32_t ad_value = envelope.Render(); | |
| if (ui.paques()) { | |
| osc.set_shape(MACRO_OSC_SHAPE_QUESTION_MARK); | |
| } else if (settings.meta_modulation()) { | |
| int16_t shape = adc.channel(3); | |
| shape -= settings.data().fm_cv_offset; | |
| if (shape > previous_shape + 2 || shape < previous_shape - 2) { | |
| previous_shape = shape; | |
| } else { | |
| shape = previous_shape; | |
| } | |
| shape = MACRO_OSC_SHAPE_LAST * shape >> 11; | |
| shape += settings.shape(); | |
| if (shape >= MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META) { | |
| shape = MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META; | |
| } else if (shape <= 0) { | |
| shape = 0; | |
| } | |
| MacroOscillatorShape osc_shape = static_cast<MacroOscillatorShape>(shape); | |
| osc.set_shape(osc_shape); | |
| ui.set_meta_shape(osc_shape); | |
| } else { | |
| osc.set_shape(settings.shape()); | |
| } | |
| // Set timbre and color: CV + internal modulation. | |
| uint16_t parameters[2]; | |
| for (uint16_t i = 0; i < 2; ++i) { | |
| int32_t value = settings.adc_to_parameter(i, adc.channel(i)); | |
| Setting ad_mod_setting = i == 0 ? SETTING_AD_TIMBRE : SETTING_AD_COLOR; | |
| value += ad_value * settings.GetValue(ad_mod_setting) >> 5; | |
| CONSTRAIN(value, 0, 32767); | |
| parameters[i] = value; | |
| } | |
| osc.set_parameters(parameters[0], parameters[1]); | |
| // Apply hysteresis to ADC reading to prevent a single bit error to move | |
| // the quantized pitch up and down the quantization boundary. | |
| int32_t pitch = quantizer.Process( | |
| settings.adc_to_pitch(adc.channel(2)), | |
| (60 + settings.quantizer_root()) << 7); | |
| if (!settings.meta_modulation()) { | |
| pitch += settings.adc_to_fm(adc.channel(3)); | |
| } | |
| // Check if the pitch has changed to cause an auto-retrigger | |
| int32_t pitch_delta = pitch - previous_pitch; | |
| if (settings.data().auto_trig && | |
| (pitch_delta >= 0x40 || -pitch_delta >= 0x40)) { | |
| trigger_detected_flag = true; | |
| } | |
| previous_pitch = pitch; | |
| pitch += jitter_source.Render(settings.vco_drift()); | |
| pitch += internal_adc.value() >> 8; | |
| pitch += ad_value * settings.GetValue(SETTING_AD_FM) >> 7; | |
| if (pitch > 16383) { | |
| pitch = 16383; | |
| } else if (pitch < 0) { | |
| pitch = 0; | |
| } | |
| if (settings.vco_flatten()) { | |
| pitch = Interpolate88(lut_vco_detune, pitch << 2); | |
| } | |
| osc.set_pitch(pitch + settings.pitch_transposition()); | |
| if (trigger_flag) { | |
| osc.Strike(); | |
| envelope.Trigger(ENV_SEGMENT_ATTACK); | |
| ui.StepMarquee(); | |
| trigger_flag = false; | |
| } | |
| uint8_t* sync_buffer = sync_samples[render_block]; | |
| int16_t* render_buffer = audio_samples[render_block]; | |
| if (settings.GetValue(SETTING_AD_VCA) != 0 | |
| || settings.GetValue(SETTING_AD_TIMBRE) != 0 | |
| || settings.GetValue(SETTING_AD_COLOR) != 0 | |
| || settings.GetValue(SETTING_AD_FM) != 0) { | |
| memset(sync_buffer, 0, kBlockSize); | |
| } | |
| osc.Render(sync_buffer, render_buffer, kBlockSize); | |
| // Copy to DAC buffer with sample rate and bit reduction applied. | |
| int16_t sample = 0; | |
| size_t decimation_factor = decimation_factors[settings.data().sample_rate]; | |
| uint16_t bit_mask = bit_reduction_masks[settings.data().resolution]; | |
| int32_t gain = settings.GetValue(SETTING_AD_VCA) ? ad_value : 65535; | |
| uint16_t signature = settings.signature() * settings.signature() * 4095; | |
| for (size_t i = 0; i < kBlockSize; ++i) { | |
| if ((i % decimation_factor) == 0) { | |
| sample = render_buffer[i] & bit_mask; | |
| } | |
| sample = sample * gain_lp >> 16; | |
| gain_lp += (gain - gain_lp) >> 4; | |
| int16_t warped = ws.Transform(sample); | |
| render_buffer[i] = Mix(sample, warped, signature); | |
| } | |
| render_block = (render_block + 1) % kNumBlocks; | |
| #ifdef PROFILE_RENDER | |
| debug_pin.Low(); | |
| #endif | |
| } | |
| int main(void) { | |
| Init(); | |
| while (1) { | |
| if (current_scale != settings.GetValue(SETTING_QUANTIZER_SCALE)) { | |
| current_scale = settings.GetValue(SETTING_QUANTIZER_SCALE); | |
| quantizer.Configure(scales[current_scale]); | |
| } | |
| while (render_block != playback_block) { | |
| RenderBlock(); | |
| } | |
| ui.DoEvents(); | |
| } | |
| } |