| @@ -1,12 +0,0 @@ | |||
| [submodule "ext/nanovg"] | |||
| path = dep/nanovg | |||
| url = https://github.com/memononen/nanovg.git | |||
| [submodule "ext/nanosvg"] | |||
| path = dep/nanosvg | |||
| url = https://github.com/memononen/nanosvg.git | |||
| [submodule "ext/osdialog"] | |||
| path = dep/osdialog | |||
| url = https://github.com/AndrewBelt/osdialog.git | |||
| [submodule "ext/oui-blendish"] | |||
| path = dep/oui-blendish | |||
| url = https://github.com/AndrewBelt/oui-blendish.git | |||
| @@ -0,0 +1,12 @@ | |||
| ### 0.6.3 (2018-10-10) | |||
| - Added Segment Generator | |||
| ### 0.6.2 (2018-10-09) | |||
| - Added Random Sampler from Audible Instruments Preview | |||
| ### 0.6.1 (2018-09-12) | |||
| - Added Macro Oscillator 2 from Audible Instruments Preview | |||
| @@ -16,28 +16,20 @@ After checking out AudibleInstruments in the `plugins/` directory, get external | |||
| ### Macro Oscillator | |||
| Based on [Braids](https://mutable-instruments.net/modules/braids), [Manual](https://mutable-instruments.net/modules/braids/manual/) | |||
|  | |||
| - Sync input doesn't work | |||
| - More settings could be supported | |||
| ### Modal Synthesizer | |||
| Based on [Elements](https://mutable-instruments.net/modules/elements), [Manual](https://mutable-instruments.net/modules/elements/manual/) | |||
|  | |||
| ### Tidal Modulator | |||
| Based on [Tides](https://mutable-instruments.net/modules/tides), [Manual](https://mutable-instruments.net/modules/tides/manual/) | |||
|  | |||
| ### Wavetable Oscillator | |||
| Based on [Sheep](https://mutable-instruments.net/modules/tides/firmware/) (Tides alternative firmware) | |||
| ### Texture Synthesizer | |||
| Based on [Clouds](https://mutable-instruments.net/modules/clouds), [Manual](https://mutable-instruments.net/modules/clouds/manual/) | |||
|  | |||
| - edit buttons and lights | |||
| - freeze button | |||
| - right-click context menus to replace menu diving | |||
| @@ -45,46 +37,30 @@ Based on [Clouds](https://mutable-instruments.net/modules/clouds), [Manual](http | |||
| ### Meta Modulator | |||
| Based on [Warps](https://mutable-instruments.net/modules/warps), [Manual](https://mutable-instruments.net/modules/warps/manual/) | |||
|  | |||
| ### Resonator | |||
| Based on [Rings](https://mutable-instruments.net/modules/rings), [Manual](https://mutable-instruments.net/modules/rings/manual/) | |||
|  | |||
| ### Keyframer/Mixer | |||
| Based on [Frames](https://mutable-instruments.net/modules/frames), [Manual](https://mutable-instruments.net/modules/frames/manual/) | |||
| ### Multiples | |||
| Based on [Links](https://mutable-instruments.net/modules/links), [Manual](https://mutable-instruments.net/modules/links/manual/) | |||
|  | |||
| ### Utilities | |||
| Based on [Kinks](https://mutable-instruments.net/modules/kinks), [Manual](https://mutable-instruments.net/modules/kinks/manual/) | |||
|  | |||
| ### Mixer | |||
| Based on [Shades](https://mutable-instruments.net/modules/shades), [Manual](https://mutable-instruments.net/modules/shades/manual/) | |||
|  | |||
| ### Bernoulli Gate | |||
| Based on [Branches](https://mutable-instruments.net/modules/branches), [Manual](https://mutable-instruments.net/modules/branches/manual/) | |||
|  | |||
| ### Quad VC-polarizer | |||
| Based on [Blinds](https://mutable-instruments.net/modules/blinds), [Manual](https://mutable-instruments.net/modules/blinds/manual/) | |||
|  | |||
| ### Quad VCA | |||
| Based on [Veils](https://mutable-instruments.net/modules/veils), [Manual](https://mutable-instruments.net/modules/veils/manual/) | |||
|  | |||
| ## Not yet ported | |||
| @@ -113,9 +89,7 @@ Based on [Veils](https://mutable-instruments.net/modules/veils), [Manual](https: | |||
| ### [Edges](https://mutable-instruments.net/modules/edges) | |||
| [Manual](https://mutable-instruments.net/modules/edges/manual/) | |||
| - GPL, will not port | |||
| ### [Grids](https://mutable-instruments.net/modules/grids) | |||
| [Manual](https://mutable-instruments.net/modules/grids/manual/) | |||
| - GPL, will not port | |||
| @@ -0,0 +1,317 @@ | |||
| // Copyright 2014 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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 "stmlib/system/bootloader_utils.h" | |||
| #include "stmlib/system/system_clock.h" | |||
| #include "marbles/drivers/adc.h" | |||
| #include "marbles/drivers/dac.h" | |||
| #include "marbles/drivers/leds.h" | |||
| #include "marbles/drivers/switches.h" | |||
| #include "marbles/drivers/system.h" | |||
| #include "stm_audio_bootloader/qpsk/packet_decoder.h" | |||
| #include "stm_audio_bootloader/qpsk/demodulator.h" | |||
| #include <cstring> | |||
| using namespace marbles; | |||
| using namespace stmlib; | |||
| using namespace stm_audio_bootloader; | |||
| const double kSampleRate = 48000.0; | |||
| const double kModulationRate = 6000.0; | |||
| const double kBitRate = 12000.0; | |||
| const uint32_t kStartAddress = 0x08008000; | |||
| Adc adc; | |||
| Dac dac; | |||
| Leds leds; | |||
| Switches switches; | |||
| PacketDecoder decoder; | |||
| Demodulator demodulator; | |||
| int __errno; | |||
| void UpdateLeds(); | |||
| volatile bool switch_released = false; | |||
| // Default interrupt handlers. | |||
| extern "C" { | |||
| void NMI_Handler() { } | |||
| void HardFault_Handler() { while (1); } | |||
| void MemManage_Handler() { while (1); } | |||
| void BusFault_Handler() { while (1); } | |||
| void UsageFault_Handler() { while (1); } | |||
| void SVC_Handler() { } | |||
| void DebugMon_Handler() { } | |||
| void PendSV_Handler() { } | |||
| void SysTick_Handler() { | |||
| IWDG_ReloadCounter(); | |||
| system_clock.Tick(); | |||
| switches.Debounce(); | |||
| if (switches.released(SWITCH_T_DEJA_VU)) { | |||
| switch_released = true; | |||
| } | |||
| UpdateLeds(); | |||
| } | |||
| } | |||
| enum UiState { | |||
| UI_STATE_WAITING, | |||
| UI_STATE_RECEIVING, | |||
| UI_STATE_ERROR, | |||
| UI_STATE_WRITING | |||
| }; | |||
| volatile UiState ui_state; | |||
| volatile int32_t peak; | |||
| void UpdateLeds() { | |||
| leds.Clear(); | |||
| switch (ui_state) { | |||
| case UI_STATE_WAITING: | |||
| leds.set( | |||
| LED_T_DEJA_VU, | |||
| system_clock.milliseconds() & 128 ? LED_COLOR_GREEN : 0); | |||
| leds.set( | |||
| LED_X_DEJA_VU, | |||
| system_clock.milliseconds() & 128 ? 0 : LED_COLOR_GREEN); | |||
| break; | |||
| case UI_STATE_RECEIVING: | |||
| leds.set( | |||
| LED_T_DEJA_VU, | |||
| system_clock.milliseconds() & 32 ? LED_COLOR_GREEN : 0); | |||
| leds.set( | |||
| LED_X_DEJA_VU, | |||
| system_clock.milliseconds() & 32 ? 0 : LED_COLOR_GREEN); | |||
| break; | |||
| case UI_STATE_ERROR: | |||
| { | |||
| bool on = system_clock.milliseconds() & 256; | |||
| for (int i = 0; i < LED_LAST; ++i) { | |||
| leds.set(Led(i), on ? LED_COLOR_RED : 0); | |||
| } | |||
| } | |||
| break; | |||
| case UI_STATE_WRITING: | |||
| { | |||
| for (int i = 0; i < LED_LAST; ++i) { | |||
| leds.set(Led(i), LED_COLOR_GREEN); | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| if (ui_state != UI_STATE_WRITING) { | |||
| uint8_t pwm = system_clock.milliseconds() & 15; | |||
| if (peak < 8192) { | |||
| leds.set( | |||
| LED_T_RANGE, | |||
| (peak >> 9) > pwm ? LED_COLOR_GREEN : 0); | |||
| } else if (peak < 16384) { | |||
| leds.set( | |||
| LED_T_RANGE, | |||
| ((peak - 8192) >> 9) >= pwm ? LED_COLOR_YELLOW : LED_COLOR_GREEN); | |||
| } else if (peak < 16384 + 8192) { | |||
| leds.set( | |||
| LED_T_RANGE, | |||
| ((peak - 16384 - 8192) >> 9) >= pwm ? | |||
| LED_COLOR_RED : LED_COLOR_YELLOW); | |||
| } else { | |||
| leds.set(LED_T_RANGE, LED_COLOR_RED); | |||
| } | |||
| } | |||
| leds.Write(); | |||
| } | |||
| int32_t dc_offset = 0; | |||
| int32_t gain_pot = 16; | |||
| size_t discard_samples = 8000; | |||
| IOBuffer::Block block; | |||
| IOBuffer::Slice FillBuffer(size_t size) { | |||
| adc.Convert(); | |||
| if (!discard_samples) { | |||
| // Scan gain pot. | |||
| gain_pot = (adc.value(ADC_GROUP_POT) + 4095 * gain_pot) >> 12; | |||
| int32_t gain = ((gain_pot >> 1) * gain_pot >> 21) + 128; | |||
| // Extract sample. Note: there's a DC offset :/ | |||
| int32_t sample = 32768 - static_cast<int32_t>(adc.value(ADC_GROUP_CV)); | |||
| dc_offset += (sample - (dc_offset >> 15)); | |||
| sample = (sample - (dc_offset >> 15)) * gain >> 8; // 0.5x to 4x | |||
| CONSTRAIN(sample, -32768, 32767); | |||
| // Update peak-meter | |||
| int32_t rect = sample > 0 ? sample : -sample; | |||
| peak = rect > peak ? rect : (rect + 32767 * peak) >> 15; | |||
| // Write to DAC for monitoring | |||
| block.cv_output[0][0] = 32767 - sample; | |||
| block.cv_output[1][0] = 32767 - sample; | |||
| block.cv_output[2][0] = 32767 - sample; | |||
| block.cv_output[3][0] = 32767 - sample; | |||
| demodulator.PushSample(2048 + (sample >> 4)); | |||
| } else { | |||
| --discard_samples; | |||
| } | |||
| IOBuffer::Slice s; | |||
| s.block = █ | |||
| s.frame_index = 0; | |||
| return s; | |||
| } | |||
| static size_t current_address; | |||
| static uint16_t packet_index; | |||
| static uint32_t kSectorBaseAddress[] = { | |||
| 0x08000000, | |||
| 0x08004000, | |||
| 0x08008000, | |||
| 0x0800C000, | |||
| 0x08010000, | |||
| 0x08020000, | |||
| 0x08040000, | |||
| 0x08060000, | |||
| 0x08080000, | |||
| 0x080A0000, | |||
| 0x080C0000, | |||
| 0x080E0000 | |||
| }; | |||
| const uint32_t kBlockSize = 16384; | |||
| const uint16_t kPacketsPerBlock = ::kBlockSize / kPacketSize; | |||
| uint8_t rx_buffer[::kBlockSize]; | |||
| void ProgramPage(const uint8_t* data, size_t size) { | |||
| FLASH_Unlock(); | |||
| FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | | |||
| FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR); | |||
| for (int32_t i = 0; i < 12; ++i) { | |||
| if (current_address == kSectorBaseAddress[i]) { | |||
| FLASH_EraseSector(i * 8, VoltageRange_3); | |||
| } | |||
| } | |||
| const uint32_t* words = static_cast<const uint32_t*>( | |||
| static_cast<const void*>(data)); | |||
| for (size_t written = 0; written < size; written += 4) { | |||
| FLASH_ProgramWord(current_address, *words++); | |||
| current_address += 4; | |||
| } | |||
| } | |||
| void InitializeReception() { | |||
| decoder.Init(20000); | |||
| demodulator.Init( | |||
| kModulationRate / kSampleRate * 4294967296.0, | |||
| kSampleRate / kModulationRate, | |||
| 2.0 * kSampleRate / kBitRate); | |||
| demodulator.SyncCarrier(true); | |||
| decoder.Reset(); | |||
| current_address = kStartAddress; | |||
| packet_index = 0; | |||
| ui_state = UI_STATE_WAITING; | |||
| } | |||
| void Init() { | |||
| System sys; | |||
| switches.Init(); | |||
| sys.Init(false); | |||
| system_clock.Init(); | |||
| adc.Init(true); | |||
| dac.Init(48000, 1); | |||
| leds.Init(); | |||
| sys.StartTimers(); | |||
| dac.Start(&FillBuffer); | |||
| } | |||
| int main(void) { | |||
| Init(); | |||
| InitializeReception(); | |||
| bool exit_updater = !switches.pressed_immediate(SWITCH_T_DEJA_VU); | |||
| while (!exit_updater) { | |||
| bool error = false; | |||
| if (demodulator.state() == DEMODULATOR_STATE_OVERFLOW) { | |||
| error = true; | |||
| } else { | |||
| demodulator.ProcessAtLeast(32); | |||
| } | |||
| while (demodulator.available() && !error && !exit_updater) { | |||
| uint8_t symbol = demodulator.NextSymbol(); | |||
| PacketDecoderState state = decoder.ProcessSymbol(symbol); | |||
| switch (state) { | |||
| case PACKET_DECODER_STATE_OK: | |||
| { | |||
| ui_state = UI_STATE_RECEIVING; | |||
| memcpy( | |||
| rx_buffer + (packet_index % kPacketsPerBlock) * kPacketSize, | |||
| decoder.packet_data(), | |||
| kPacketSize); | |||
| ++packet_index; | |||
| if ((packet_index % kPacketsPerBlock) == 0) { | |||
| ui_state = UI_STATE_WRITING; | |||
| ProgramPage(rx_buffer, ::kBlockSize); | |||
| decoder.Reset(); | |||
| demodulator.SyncCarrier(false); | |||
| } else { | |||
| decoder.Reset(); | |||
| demodulator.SyncDecision(); | |||
| } | |||
| } | |||
| break; | |||
| case PACKET_DECODER_STATE_ERROR_SYNC: | |||
| case PACKET_DECODER_STATE_ERROR_CRC: | |||
| error = true; | |||
| break; | |||
| case PACKET_DECODER_STATE_END_OF_TRANSMISSION: | |||
| exit_updater = true; | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| if (error) { | |||
| ui_state = UI_STATE_ERROR; | |||
| switch_released = false; | |||
| while (!switch_released); // Polled in ISR | |||
| InitializeReception(); | |||
| } | |||
| } | |||
| adc.DeInit(); | |||
| Uninitialize(); | |||
| JumpTo(kStartAddress); | |||
| while (1) { } | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| # Copyright 2014 Olivier Gillet. | |||
| # | |||
| # Author: Olivier Gillet (ol.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. | |||
| # System specifications | |||
| F_CRYSTAL = 8000000L | |||
| F_CPU = 168000000L | |||
| SYSCLOCK = SYSCLK_FREQ_168MHz | |||
| FAMILY = f4xx | |||
| # USB = enabled | |||
| # Preferred upload command | |||
| UPLOAD_COMMAND = upload_jtag_erase_first | |||
| # Packages to build | |||
| TARGET = marbles_bootloader | |||
| PACKAGES = marbles/bootloader \ | |||
| marbles/drivers \ | |||
| stm_audio_bootloader/qpsk \ | |||
| stmlib/dsp \ | |||
| stmlib/utils \ | |||
| stmlib/system | |||
| RESOURCES = marbles/resources | |||
| TOOLCHAIN_PATH ?= /usr/local/arm-4.8.3/ | |||
| include stmlib/makefile.inc | |||
| @@ -0,0 +1,87 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Class for detecting if the t1 or t2 gate outputs are patched into the X | |||
| // clock input. This is done by comparing the number of synchronous transitions | |||
| // on the gate ouputs and the clock input. A small margin of error is allowed | |||
| // because of acquisition delays. | |||
| #ifndef MARBLES_CLOCK_SELF_PATCHING_DETECTOR_H_ | |||
| #define MARBLES_CLOCK_SELF_PATCHING_DETECTOR_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/io_buffer.h" | |||
| namespace marbles { | |||
| class ClockSelfPatchingDetector { | |||
| public: | |||
| ClockSelfPatchingDetector() { } | |||
| ~ClockSelfPatchingDetector() { } | |||
| void Init(size_t index) { | |||
| index_ = index; | |||
| error_streak_ = 0; | |||
| match_length_ = 0; | |||
| synchronous_transitions_ = 0; | |||
| } | |||
| size_t Process(IOBuffer::Block* block, size_t size) { | |||
| for (size_t i = 0; i < size; ++i) { | |||
| if (block->input[1][i] & stmlib::GATE_FLAG_RISING) { | |||
| if (match_length_ >= 12) { | |||
| ++synchronous_transitions_; | |||
| } | |||
| error_streak_ = 0; | |||
| match_length_ = 0; | |||
| } | |||
| bool output_gate = block->gate_output[index_][i]; | |||
| bool input_gate = block->input[1][i] & stmlib::GATE_FLAG_HIGH; | |||
| if (output_gate != input_gate) { | |||
| ++error_streak_; | |||
| if (error_streak_ >= 6) { | |||
| synchronous_transitions_ = 0; | |||
| } | |||
| } else { | |||
| ++match_length_; | |||
| } | |||
| } | |||
| return synchronous_transitions_; | |||
| } | |||
| private: | |||
| size_t index_; | |||
| size_t error_streak_; | |||
| size_t match_length_; | |||
| size_t synchronous_transitions_; | |||
| DISALLOW_COPY_AND_ASSIGN(ClockSelfPatchingDetector); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_CLOCK_SELF_PATCHING_DETECTOR_H_ | |||
| @@ -0,0 +1,102 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // CV reader. | |||
| #include "marbles/cv_reader.h" | |||
| #include <algorithm> | |||
| #include "stmlib/dsp/dsp.h" | |||
| namespace marbles { | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| /* static */ | |||
| const CvReaderChannel::Settings CvReader::channel_settings_[] = { | |||
| // cv_lp | pot_scale | pot_offset | pot_lp | min | max | hysteresis | |||
| // ADC_CHANNEL_DEJA_VU_AMOUNT, | |||
| { 0.05f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.00f }, | |||
| // ADC_CHANNEL_X_SPREAD_2 / ADC_CHANNEL_DEJA_VU_LENGTH, | |||
| { 0.05f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.00f }, | |||
| // ADC_CHANNEL_T_RATE, | |||
| { 0.2f, 120.0f, -60.0f, 0.01f, -120.0f, 120.0f, 0.001f }, | |||
| // ADC_CHANNEL_T_BIAS, | |||
| { 0.05f, 1.05f, -0.025f, 0.01f, 0.0f, 1.0f, 0.00f }, | |||
| // ADC_CHANNEL_T_JITTER, | |||
| { 0.05f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.00f }, | |||
| // ADC_CHANNEL_X_SPREAD, | |||
| { 0.1f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.01f }, | |||
| // ADC_CHANNEL_X_BIAS, | |||
| { 0.1f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.02f }, | |||
| // ADC_CHANNEL_X_STEPS, | |||
| { 0.05f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.02f }, | |||
| }; | |||
| void CvReader::Init(CalibrationData* calibration_data) { | |||
| calibration_data_ = calibration_data; | |||
| adc_.Init(false); | |||
| for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
| channel_[i].Init( | |||
| &calibration_data_->adc_scale[i], | |||
| &calibration_data_->adc_offset[i], | |||
| channel_settings_[i]); | |||
| } | |||
| fill(&attenuverter_[0], &attenuverter_[ADC_CHANNEL_LAST], 1.0f); | |||
| // Set virtual attenuverter to 12 o'clock to ignore the non-existing | |||
| // CV input for DEJA VU length. | |||
| attenuverter_[ADC_CHANNEL_DEJA_VU_LENGTH] = 0.5f; | |||
| // Set virtual attenuverter to a little more than 100% to | |||
| // compensate for op-amp clipping and get full parameter swing. | |||
| attenuverter_[ADC_CHANNEL_DEJA_VU_AMOUNT] = 1.01f; | |||
| attenuverter_[ADC_CHANNEL_T_BIAS] = 1.01f; | |||
| attenuverter_[ADC_CHANNEL_T_JITTER] = 1.01f; | |||
| attenuverter_[ADC_CHANNEL_X_SPREAD] = 1.01f; | |||
| attenuverter_[ADC_CHANNEL_X_BIAS] = 1.01f; | |||
| attenuverter_[ADC_CHANNEL_X_STEPS] = 1.01f; | |||
| } | |||
| void CvReader::Copy(uint16_t* output) { | |||
| const uint16_t* adc_values = adc_.values(); | |||
| copy(&adc_values[0], &adc_values[ADC_CHANNEL_LAST * 2], output); | |||
| adc_.Convert(); | |||
| } | |||
| void CvReader::Process(const uint16_t* raw_values, float* output) { | |||
| for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
| output[i] = channel_[i].Process( | |||
| static_cast<float>(raw_values[ADC_GROUP_POT + i]) / 65536.0f, | |||
| static_cast<float>(raw_values[ADC_GROUP_CV + i]) / 65536.0f, | |||
| attenuverter_[i]); | |||
| } | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,129 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // CV reader. | |||
| #ifndef MARBLES_CV_READER_H_ | |||
| #define MARBLES_CV_READER_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/drivers/adc.h" | |||
| #include "marbles/cv_reader_channel.h" | |||
| #include "marbles/settings.h" | |||
| namespace marbles { | |||
| class CvReader { | |||
| public: | |||
| CvReader() { } | |||
| ~CvReader() { } | |||
| void Init(CalibrationData* calibration_data); | |||
| inline bool ready_for_calibration() const { | |||
| return true; | |||
| } | |||
| inline void CalibrateRateC1() { | |||
| cv_c1_[0] = channel_[ADC_CHANNEL_T_RATE].unscaled_cv_lp(); | |||
| } | |||
| inline void CalibrateRateC3() { | |||
| cv_c3_[0] = channel_[ADC_CHANNEL_T_RATE].unscaled_cv_lp(); | |||
| } | |||
| inline void CalibrateSpreadC1() { | |||
| cv_c1_[1] = 0.5f * channel_[ADC_CHANNEL_X_SPREAD].unscaled_cv_lp() + \ | |||
| 0.5f * channel_[ADC_CHANNEL_X_SPREAD_2].unscaled_cv_lp(); | |||
| } | |||
| inline bool CalibrateSpreadC3() { | |||
| cv_c3_[1] = 0.5f * channel_[ADC_CHANNEL_X_SPREAD].unscaled_cv_lp() + \ | |||
| 0.5f * channel_[ADC_CHANNEL_X_SPREAD_2].unscaled_cv_lp(); | |||
| for (int i = 0; i < 2; ++i) { | |||
| float c3 = cv_c3_[i]; // 0.2 | |||
| float c1 = cv_c1_[i]; // 0.4 | |||
| float delta = c3 - c1; | |||
| float target_scale = i == 0 ? 24.0f : 0.4f; | |||
| float target_offset = i == 0 ? 12.0f : 0.2f; | |||
| if (delta > -0.3f && delta < -0.1f) { | |||
| int channel = i == 0 ? ADC_CHANNEL_T_RATE : ADC_CHANNEL_X_SPREAD; | |||
| calibration_data_->adc_scale[channel] = target_scale / (c3 - c1); | |||
| calibration_data_->adc_offset[channel] = target_offset - \ | |||
| calibration_data_->adc_scale[channel] * c1; | |||
| } else { | |||
| return false; | |||
| } | |||
| } | |||
| calibration_data_->adc_scale[ADC_CHANNEL_X_SPREAD_2] = calibration_data_->adc_scale[ADC_CHANNEL_X_SPREAD]; | |||
| calibration_data_->adc_offset[ADC_CHANNEL_X_SPREAD_2] = calibration_data_->adc_offset[ADC_CHANNEL_X_SPREAD]; | |||
| return true; | |||
| } | |||
| inline void CalibrateOffsets() { | |||
| for (size_t i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
| if (i != ADC_CHANNEL_T_RATE && i != ADC_CHANNEL_X_SPREAD) { | |||
| calibration_data_->adc_offset[i] = \ | |||
| 2.0f * channel_[i].unscaled_cv_lp(); | |||
| } | |||
| } | |||
| } | |||
| inline uint8_t adc_value(int index) const { | |||
| return adc_.value(index) >> 8; | |||
| } | |||
| void Copy(uint16_t* output); | |||
| void Process(const uint16_t* values, float* output); | |||
| inline const CvReaderChannel& channel(size_t index) { | |||
| return channel_[index]; | |||
| } | |||
| inline CvReaderChannel* mutable_channel(size_t index) { | |||
| return &channel_[index]; | |||
| } | |||
| inline void set_attenuverter(int index, float value) { | |||
| attenuverter_[index] = value; | |||
| } | |||
| private: | |||
| Adc adc_; | |||
| CalibrationData* calibration_data_; | |||
| float cv_c1_[2]; | |||
| float cv_c3_[2]; | |||
| CvReaderChannel channel_[ADC_CHANNEL_LAST]; | |||
| float attenuverter_[ADC_CHANNEL_LAST]; | |||
| static const CvReaderChannel::Settings channel_settings_[ADC_CHANNEL_LAST]; | |||
| DISALLOW_COPY_AND_ASSIGN(CvReader); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_CV_READER_H_ | |||
| @@ -0,0 +1,213 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // CV reader channel. | |||
| #ifndef MARBLES_CV_READER_CHANNEL_H_ | |||
| #define MARBLES_CV_READER_CHANNEL_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "stmlib/dsp/dsp.h" | |||
| namespace marbles { | |||
| enum PotState { | |||
| POT_STATE_TRACKING, | |||
| POT_STATE_LOCKED, | |||
| POT_STATE_CATCHING_UP | |||
| }; | |||
| class HysteresisFilter { | |||
| public: | |||
| HysteresisFilter() { } | |||
| ~HysteresisFilter() { } | |||
| void Init(float threshold) { | |||
| value_ = 0.0f; | |||
| threshold_ = threshold; | |||
| } | |||
| inline float Process(float value) { | |||
| float error = value - value_; | |||
| if (error > threshold_) { | |||
| value_ = value - threshold_; | |||
| } else if (error < -threshold_) { | |||
| value_ = value + threshold_; | |||
| } | |||
| return value_; | |||
| } | |||
| private: | |||
| float value_; | |||
| float threshold_; | |||
| DISALLOW_COPY_AND_ASSIGN(HysteresisFilter); | |||
| }; | |||
| class CvReaderChannel { | |||
| public: | |||
| CvReaderChannel() { } | |||
| ~CvReaderChannel() { } | |||
| // Because of the large number of initialization parameters, they are | |||
| // passed in one single struct. | |||
| struct Settings { | |||
| float cv_lp; | |||
| float pot_scale; | |||
| float pot_offset; | |||
| float pot_lp; | |||
| float min; | |||
| float max; | |||
| float hysteresis; | |||
| }; | |||
| void Init(float* cv_scale, float* cv_offset, const Settings& settings) { | |||
| cv_scale_ = cv_scale; | |||
| cv_offset_ = cv_offset; | |||
| cv_lp_ = settings.cv_lp; | |||
| pot_scale_ = settings.pot_scale + 2.0f * settings.hysteresis; | |||
| pot_offset_ = settings.pot_offset - settings.hysteresis; | |||
| pot_lp_ = settings.pot_lp; | |||
| min_ = settings.min; | |||
| max_ = settings.max; | |||
| raw_cv_value_ = 0.0f; | |||
| cv_value_ = 0.0f; | |||
| pot_value_ = 0.0f; | |||
| stored_pot_value_ = 0.0f; | |||
| attenuverter_value_ = 0.0f; | |||
| previous_pot_value_ = 0.0f; | |||
| pot_state_ = POT_STATE_TRACKING; | |||
| hystereis_filter_.Init(settings.hysteresis); | |||
| } | |||
| inline float Process(float pot, float cv) { | |||
| return Process(pot, cv, 1.0f); | |||
| } | |||
| inline float Process(float pot, float cv, float attenuverter) { | |||
| cv *= *cv_scale_; | |||
| cv += *cv_offset_; | |||
| attenuverter -= 0.5f; | |||
| attenuverter = attenuverter * attenuverter * attenuverter * 8.0f; | |||
| ONE_POLE(attenuverter_value_, attenuverter, pot_lp_); | |||
| raw_cv_value_ = cv; | |||
| ONE_POLE(cv_value_, cv, cv_lp_); | |||
| ONE_POLE(pot_value_, pot, pot_lp_); | |||
| switch (pot_state_) { | |||
| case POT_STATE_TRACKING: | |||
| stored_pot_value_ = pot_value_; | |||
| previous_pot_value_ = pot_value_; | |||
| break; | |||
| case POT_STATE_LOCKED: | |||
| break; | |||
| case POT_STATE_CATCHING_UP: | |||
| { | |||
| if (fabs(pot_value_ - previous_pot_value_) > 0.01f) { | |||
| float delta = pot_value_ - previous_pot_value_; | |||
| float skew_ratio = delta > 0.0f | |||
| ? (1.001f - stored_pot_value_) / (1.001f - previous_pot_value_) | |||
| : (0.001f + stored_pot_value_) / (0.001f + previous_pot_value_); | |||
| CONSTRAIN(skew_ratio, 0.1f, 10.0f); | |||
| stored_pot_value_ += skew_ratio * delta; | |||
| CONSTRAIN(stored_pot_value_, 0.0f, 1.0f); | |||
| if (fabs(stored_pot_value_ - pot_value_) < 0.01f) { | |||
| pot_state_ = POT_STATE_TRACKING; | |||
| } | |||
| previous_pot_value_ = pot_value_; | |||
| } | |||
| } | |||
| break; | |||
| }; | |||
| float value = hystereis_filter_.Process( | |||
| cv_value_ * attenuverter_value_ + this->pot()); | |||
| CONSTRAIN(value, min_, max_); | |||
| return value; | |||
| } | |||
| inline float cv() const { return cv_value_; } | |||
| inline float scaled_raw_cv() const { return raw_cv_value_; } | |||
| inline float unscaled_cv_lp() const { | |||
| return (cv_value_ - *cv_offset_) / (*cv_scale_); | |||
| } | |||
| inline float pot() const { | |||
| return stored_pot_value_ * pot_scale_ + pot_offset_; | |||
| } | |||
| inline float unscaled_pot() const { return pot_value_; } | |||
| inline void LockPot() { | |||
| pot_state_ = POT_STATE_LOCKED; | |||
| } | |||
| inline void UnlockPot() { | |||
| if (pot_state_ == POT_STATE_LOCKED) { | |||
| previous_pot_value_ = pot_value_; | |||
| pot_state_ = POT_STATE_CATCHING_UP; | |||
| } | |||
| } | |||
| private: | |||
| float* cv_scale_; | |||
| float* cv_offset_; | |||
| float raw_cv_value_; | |||
| float cv_lp_; | |||
| float pot_scale_; | |||
| float pot_offset_; | |||
| float pot_lp_; | |||
| float attenuverter_lp_; | |||
| float min_; | |||
| float max_; | |||
| PotState pot_state_; | |||
| float cv_value_; | |||
| float pot_value_; // Value after low-pass filtering. | |||
| float previous_pot_value_; | |||
| float stored_pot_value_; // The actual parameter value. | |||
| float attenuverter_value_; | |||
| HysteresisFilter hystereis_filter_; | |||
| DISALLOW_COPY_AND_ASSIGN(CvReaderChannel); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_CV_READER_CHANNEL_H_ | |||
| @@ -0,0 +1,162 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for ADC. ADC1 is used for the 8 pots ; ADC2 for the 8 CV inputs. | |||
| #include "marbles/drivers/adc.h" | |||
| #include <stm32f4xx_conf.h> | |||
| namespace marbles { | |||
| void Adc::Init(bool single_channel) { | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); | |||
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE); | |||
| DMA_InitTypeDef dma_init; | |||
| ADC_CommonInitTypeDef adc_common_init; | |||
| ADC_InitTypeDef adc_init; | |||
| GPIO_InitTypeDef gpio_init; | |||
| // Initialize A0..A7 (ADC0..ADC7) | |||
| gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; | |||
| gpio_init.GPIO_Pin |= GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| // Initialize B0..B1 (ADC8..ADC9) | |||
| gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
| GPIO_Init(GPIOB, &gpio_init); | |||
| // Initialize C0..C5 (ADC10..ADC11) | |||
| gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; | |||
| gpio_init.GPIO_Pin |= GPIO_Pin_4 | GPIO_Pin_5; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
| GPIO_Init(GPIOC, &gpio_init); | |||
| // Use DMA to automatically copy ADC data register to values_ buffer. | |||
| dma_init.DMA_Channel = DMA_Channel_0; | |||
| dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; | |||
| dma_init.DMA_Memory0BaseAddr = (uint32_t)&values_[ADC_GROUP_POT]; | |||
| dma_init.DMA_DIR = DMA_DIR_PeripheralToMemory; | |||
| dma_init.DMA_BufferSize = single_channel ? 1 : ADC_CHANNEL_LAST; | |||
| dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
| dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
| dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
| dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
| dma_init.DMA_Mode = DMA_Mode_Circular; | |||
| dma_init.DMA_Priority = DMA_Priority_High; | |||
| dma_init.DMA_FIFOMode = DMA_FIFOMode_Disable; | |||
| dma_init.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; | |||
| dma_init.DMA_MemoryBurst = DMA_MemoryBurst_Single; | |||
| dma_init.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; | |||
| DMA_Init(DMA2_Stream0, &dma_init); | |||
| DMA_Cmd(DMA2_Stream0, ENABLE); | |||
| dma_init.DMA_Channel = DMA_Channel_1; | |||
| dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC2->DR; | |||
| dma_init.DMA_Memory0BaseAddr = (uint32_t)&values_[ADC_GROUP_CV]; | |||
| DMA_Init(DMA2_Stream2, &dma_init); | |||
| DMA_Cmd(DMA2_Stream2, ENABLE); | |||
| adc_common_init.ADC_Mode = ADC_Mode_Independent; | |||
| adc_common_init.ADC_Prescaler = ADC_Prescaler_Div8; | |||
| adc_common_init.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; | |||
| adc_common_init.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles; | |||
| ADC_CommonInit(&adc_common_init); | |||
| adc_init.ADC_Resolution = ADC_Resolution_12b; | |||
| adc_init.ADC_ScanConvMode = ENABLE; | |||
| adc_init.ADC_ContinuousConvMode = DISABLE; | |||
| adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; | |||
| adc_init.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; | |||
| adc_init.ADC_DataAlign = ADC_DataAlign_Left; | |||
| adc_init.ADC_NbrOfConversion = single_channel ? 1 : ADC_CHANNEL_LAST; | |||
| ADC_Init(ADC1, &adc_init); | |||
| ADC_Init(ADC2, &adc_init); | |||
| // 168M / 2 / 8 / (8 x (144 + 20)) = 8.001kHz. | |||
| if (single_channel) { | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 1, ADC_SampleTime_144Cycles); | |||
| } else { | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 1, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 2, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_12,3, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_2,4, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_15,5, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_10,6, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 7, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 8, ADC_SampleTime_144Cycles); | |||
| } | |||
| if (single_channel) { | |||
| ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 1, ADC_SampleTime_144Cycles); | |||
| } else { | |||
| ADC_RegularChannelConfig(ADC2, ADC_Channel_5, 1, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC2, ADC_Channel_0, 2, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 3, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC2, ADC_Channel_1, 4, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC2, ADC_Channel_4, 5, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC2, ADC_Channel_7, 6, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC2, ADC_Channel_14,7, ADC_SampleTime_144Cycles); | |||
| ADC_RegularChannelConfig(ADC2, ADC_Channel_6, 8, ADC_SampleTime_144Cycles); | |||
| } | |||
| ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE); | |||
| ADC_DMARequestAfterLastTransferCmd(ADC2, ENABLE); | |||
| ADC_Cmd(ADC1, ENABLE); | |||
| ADC_Cmd(ADC2, ENABLE); | |||
| ADC_DMACmd(ADC1, ENABLE); | |||
| ADC_DMACmd(ADC2, ENABLE); | |||
| Convert(); | |||
| } | |||
| void Adc::DeInit() { | |||
| DMA_Cmd(DMA2_Stream0, DISABLE); | |||
| DMA_Cmd(DMA2_Stream2, DISABLE); | |||
| ADC_DMARequestAfterLastTransferCmd(ADC1, DISABLE); | |||
| ADC_DMARequestAfterLastTransferCmd(ADC2, DISABLE); | |||
| ADC_Cmd(ADC1, DISABLE); | |||
| ADC_Cmd(ADC2, DISABLE); | |||
| ADC_DMACmd(ADC1, DISABLE); | |||
| ADC_DMACmd(ADC2, DISABLE); | |||
| ADC_DeInit(); | |||
| } | |||
| void Adc::Convert() { | |||
| ADC_SoftwareStartConv(ADC1); | |||
| ADC_SoftwareStartConv(ADC2); | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,81 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for ADC. ADC1 is used for the 8 pots ; ADC2 for the 8 CV inputs. | |||
| #ifndef MARBLES_DRIVERS_ADC_H_ | |||
| #define MARBLES_DRIVERS_ADC_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace marbles { | |||
| enum AdcParameter { | |||
| ADC_CHANNEL_DEJA_VU_AMOUNT, | |||
| ADC_CHANNEL_DEJA_VU_LENGTH, | |||
| ADC_CHANNEL_X_SPREAD_2 = ADC_CHANNEL_DEJA_VU_LENGTH, | |||
| ADC_CHANNEL_T_RATE, | |||
| ADC_CHANNEL_T_BIAS, | |||
| ADC_CHANNEL_T_JITTER, | |||
| ADC_CHANNEL_X_SPREAD, | |||
| ADC_CHANNEL_X_BIAS, | |||
| ADC_CHANNEL_X_STEPS, | |||
| ADC_CHANNEL_LAST | |||
| }; | |||
| enum AdcGroup { | |||
| ADC_GROUP_POT = 0, | |||
| ADC_GROUP_CV = ADC_CHANNEL_LAST | |||
| }; | |||
| class Adc { | |||
| public: | |||
| Adc() { } | |||
| ~Adc() { } | |||
| void Init(bool single_channel); | |||
| void DeInit(); | |||
| void Convert(); | |||
| inline float float_value(int channel) const { | |||
| return static_cast<float>(values_[channel]) / 65536.0f; | |||
| } | |||
| inline uint16_t value(int channel) const { | |||
| return values_[channel]; | |||
| } | |||
| inline const uint16_t* values() const { | |||
| return &values_[0]; | |||
| } | |||
| private: | |||
| uint16_t values_[ADC_CHANNEL_LAST * 2]; | |||
| DISALLOW_COPY_AND_ASSIGN(Adc); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_ADC_H_ | |||
| @@ -0,0 +1,122 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the two clock inputs and their normalization probe. | |||
| #include "marbles/drivers/clock_inputs.h" | |||
| #include <stm32f4xx_conf.h> | |||
| namespace marbles { | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| struct ClockInputDefinition { | |||
| GPIO_TypeDef* gpio; | |||
| uint16_t pin; | |||
| }; | |||
| const ClockInputDefinition clock_input_definition[] = { | |||
| { GPIOC, GPIO_Pin_9 }, // CLOCK_INPUT_T, | |||
| { GPIOA, GPIO_Pin_8 }, // CLOCK_INPUT_X, | |||
| }; | |||
| void ClockInputs::Init() { | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
| // Initialize probe. | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_8; | |||
| GPIO_Init(GPIOC, &gpio_init); | |||
| // Initialize inputs. | |||
| gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
| gpio_init.GPIO_Pin = clock_input_definition[i].pin; | |||
| GPIO_Init(clock_input_definition[i].gpio, &gpio_init); | |||
| previous_flags_[i] = 0; | |||
| normalization_mismatch_count_[i] = 0; | |||
| normalized_[i] = false; | |||
| } | |||
| normalization_probe_state_ = 0; | |||
| normalization_decision_count_ = 0; | |||
| } | |||
| void ClockInputs::ReadNormalization(IOBuffer::Block* block) { | |||
| ++normalization_decision_count_; | |||
| if (normalization_decision_count_ >= kProbeSequenceDuration) { | |||
| normalization_decision_count_ = 0; | |||
| for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
| normalized_[i] = \ | |||
| normalization_mismatch_count_[i] < kProbeSequenceDuration / 8; | |||
| normalization_mismatch_count_[i] = 0; | |||
| } | |||
| } | |||
| int expected_value = normalization_probe_state_ >> 31; | |||
| for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
| int read_value = previous_flags_[i] & GATE_FLAG_HIGH; | |||
| normalization_mismatch_count_[i] += read_value ^ expected_value; | |||
| block->input_patched[i] = !normalized_[i]; | |||
| } | |||
| normalization_probe_state_ = 1103515245 * normalization_probe_state_ + 12345; | |||
| if (normalization_probe_state_ >> 31) { | |||
| GPIOC->BSRRL = GPIO_Pin_8; | |||
| } else { | |||
| GPIOC->BSRRH = GPIO_Pin_8; | |||
| } | |||
| } | |||
| void ClockInputs::Read(const IOBuffer::Slice& slice, size_t size) { | |||
| for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
| previous_flags_[i] = ExtractGateFlags( | |||
| previous_flags_[i], | |||
| !(clock_input_definition[i].gpio->IDR & clock_input_definition[i].pin)); | |||
| slice.block->input[i][slice.frame_index] = previous_flags_[i]; | |||
| } | |||
| // Extend gate input data to the next samples. | |||
| for (size_t j = 1; j < size; ++j) { | |||
| for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
| slice.block->input[i][slice.frame_index + j] = \ | |||
| previous_flags_[i] & GATE_FLAG_HIGH; | |||
| } | |||
| } | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,75 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the two clock inputs and their normalization probe. | |||
| #ifndef MARBLES_DRIVERS_CLOCK_INPUTS_H_ | |||
| #define MARBLES_DRIVERS_CLOCK_INPUTS_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/io_buffer.h" | |||
| namespace marbles { | |||
| enum ClockInput { | |||
| CLOCK_INPUT_T, | |||
| CLOCK_INPUT_X, | |||
| CLOCK_INPUT_LAST | |||
| }; | |||
| class ClockInputs { | |||
| public: | |||
| ClockInputs() { } | |||
| ~ClockInputs() { } | |||
| void Init(); | |||
| void Read(const IOBuffer::Slice& slice, size_t size); | |||
| void ReadNormalization(IOBuffer::Block* block); | |||
| bool is_normalized(ClockInput input) { | |||
| return normalized_[input]; | |||
| } | |||
| bool value(ClockInput input) { | |||
| return previous_flags_[input] & stmlib::GATE_FLAG_HIGH; | |||
| } | |||
| private: | |||
| static const int kProbeSequenceDuration = 64; | |||
| stmlib::GateFlags previous_flags_[CLOCK_INPUT_LAST]; | |||
| uint32_t normalization_probe_state_; | |||
| bool normalized_[CLOCK_INPUT_LAST]; | |||
| int normalization_mismatch_count_[CLOCK_INPUT_LAST]; | |||
| int normalization_decision_count_; | |||
| DISALLOW_COPY_AND_ASSIGN(ClockInputs); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_CLOCK_INPUTS_H_ | |||
| @@ -0,0 +1,160 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for DAC. | |||
| #include "marbles/drivers/dac.h" | |||
| #include <algorithm> | |||
| namespace marbles { | |||
| /* static */ | |||
| Dac* Dac::instance_; | |||
| void Dac::Init(int sample_rate, size_t block_size) { | |||
| instance_ = this; | |||
| block_size_ = block_size; | |||
| callback_ = NULL; | |||
| InitializeGPIO(); | |||
| InitializeAudioInterface(sample_rate); | |||
| InitializeDMA(block_size); | |||
| } | |||
| void Dac::InitializeGPIO() { | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
| // Initialize SS pin. | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_25MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_15; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| // Initialize MOSI and SCK pins. | |||
| gpio_init.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_12; | |||
| GPIO_Init(GPIOC, &gpio_init); | |||
| GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SPI3); | |||
| GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SPI3); | |||
| GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_SPI3); | |||
| } | |||
| void Dac::InitializeAudioInterface(int sample_rate) { | |||
| RCC_I2SCLKConfig(RCC_I2S2CLKSource_PLLI2S); | |||
| RCC_PLLI2SCmd(DISABLE); | |||
| // Best results for multiples of 32kHz. | |||
| RCC_PLLI2SConfig(258, 3); | |||
| RCC_PLLI2SCmd(ENABLE); | |||
| while (RCC_GetFlagStatus(RCC_FLAG_PLLI2SRDY) == RESET); | |||
| RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE); | |||
| SPI_I2S_DeInit(SPI3); | |||
| I2S_InitTypeDef i2s_init; | |||
| i2s_init.I2S_Mode = I2S_Mode_MasterTx; | |||
| i2s_init.I2S_Standard = I2S_Standard_PCMShort; | |||
| i2s_init.I2S_DataFormat = I2S_DataFormat_32b; | |||
| i2s_init.I2S_MCLKOutput = I2S_MCLKOutput_Disable; | |||
| i2s_init.I2S_AudioFreq = sample_rate * kNumDacChannels >> 1; | |||
| i2s_init.I2S_CPOL = I2S_CPOL_Low; | |||
| I2S_Init(SPI3, &i2s_init); | |||
| I2S_Cmd(SPI3, ENABLE); | |||
| } | |||
| void Dac::InitializeDMA(size_t block_size) { | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); | |||
| DMA_Cmd(DMA1_Stream5, DISABLE); | |||
| DMA_DeInit(DMA1_Stream5); | |||
| DMA_InitTypeDef dma_init; | |||
| dma_init.DMA_Channel = DMA_Channel_0; | |||
| dma_init.DMA_PeripheralBaseAddr = (uint32_t)&(SPI3->DR); | |||
| dma_init.DMA_Memory0BaseAddr = (uint32_t)(&tx_dma_buffer_[0]); | |||
| dma_init.DMA_DIR = DMA_DIR_MemoryToPeripheral; | |||
| dma_init.DMA_BufferSize = 2 * block_size * kNumDacChannels * 2; | |||
| dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
| dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
| dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
| dma_init.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord; | |||
| dma_init.DMA_Mode = DMA_Mode_Circular; | |||
| dma_init.DMA_Priority = DMA_Priority_High; | |||
| dma_init.DMA_FIFOMode = DMA_FIFOMode_Disable; | |||
| dma_init.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; | |||
| dma_init.DMA_MemoryBurst = DMA_MemoryBurst_Single; | |||
| dma_init.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; | |||
| DMA_Init(DMA1_Stream5, &dma_init); | |||
| // Enable the interrupts. | |||
| DMA_ITConfig(DMA1_Stream5, DMA_IT_TC | DMA_IT_HT, ENABLE); | |||
| // Enable the IRQ. | |||
| NVIC_EnableIRQ(DMA1_Stream5_IRQn); | |||
| // Start DMA from/to codec. | |||
| SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE); | |||
| } | |||
| void Dac::Start(FillBufferCallback callback) { | |||
| callback_ = callback; | |||
| DMA_Cmd(DMA1_Stream5, ENABLE); | |||
| } | |||
| void Dac::Stop() { | |||
| DMA_Cmd(DMA1_Stream5, DISABLE); | |||
| } | |||
| void Dac::Fill(size_t offset) { | |||
| // Fill the buffer. | |||
| IOBuffer::Slice slice = (*callback_)(block_size_); | |||
| uint16_t* p = &tx_dma_buffer_[offset * block_size_ * kNumDacChannels * 2]; | |||
| for (size_t i = 0; i < block_size_; ++i) { | |||
| for (size_t j = 0; j < kNumDacChannels; ++j) { | |||
| uint16_t sample = slice.block->cv_output[j][slice.frame_index + i]; | |||
| *p++ = 0x1000 | (j << 9) | (sample >> 8); | |||
| *p++ = sample << 8; | |||
| } | |||
| } | |||
| } | |||
| } // namespace marbles | |||
| extern "C" { | |||
| void DMA1_Stream5_IRQHandler(void) { | |||
| uint32_t flags = DMA1->HISR; | |||
| DMA1->HIFCR = DMA_FLAG_TCIF5 | DMA_FLAG_HTIF5; | |||
| if (flags & DMA_FLAG_TCIF5) { | |||
| marbles::Dac::GetInstance()->Fill(1); | |||
| } else if (flags & DMA_FLAG_HTIF5) { | |||
| marbles::Dac::GetInstance()->Fill(0); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,82 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for quad SPI DAC. | |||
| #ifndef MARBLES_DRIVERS_DAC_H_ | |||
| #define MARBLES_DRIVERS_DAC_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <stm32f4xx_conf.h> | |||
| #include "marbles/io_buffer.h" | |||
| namespace marbles { | |||
| enum DacChannel { | |||
| DAC_CHANNEL_X_1, | |||
| DAC_CHANNEL_X_2, | |||
| DAC_CHANNEL_X_3, | |||
| DAC_CHANNEL_Y, | |||
| DAC_CHANNEL_LAST | |||
| }; | |||
| const size_t kMaxDacBlockSize = 8; | |||
| const size_t kNumDacChannels = 4; | |||
| class Dac { | |||
| public: | |||
| Dac() { } | |||
| ~Dac() { } | |||
| typedef IOBuffer::Slice (*FillBufferCallback)(size_t size); | |||
| void Init(int sample_rate, size_t block_size); | |||
| void Start(FillBufferCallback callback); | |||
| void Stop(); | |||
| void Fill(size_t offset); | |||
| static Dac* GetInstance() { return instance_; } | |||
| private: | |||
| void InitializeGPIO(); | |||
| void InitializeAudioInterface(int sample_rate); | |||
| void InitializeDMA(size_t block_size); | |||
| static Dac* instance_; | |||
| size_t block_size_; | |||
| FillBufferCallback callback_; | |||
| // There are 8 16-bit words per frame. | |||
| uint16_t tx_dma_buffer_[2 * kMaxDacBlockSize * kNumDacChannels * 2]; | |||
| DISALLOW_COPY_AND_ASSIGN(Dac); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_DAC_H_ | |||
| @@ -0,0 +1,76 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the debug (timing) pin. | |||
| #ifndef MARBLES_DRIVERS_DEBUG_PIN_H_ | |||
| #define MARBLES_DRIVERS_DEBUG_PIN_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #ifndef TEST | |||
| #include <stm32f4xx_conf.h> | |||
| #endif | |||
| namespace marbles { | |||
| class DebugPin { | |||
| public: | |||
| DebugPin() { } | |||
| ~DebugPin() { } | |||
| #ifdef TEST | |||
| static void Init() { } | |||
| static void High() { } | |||
| static void Low() { } | |||
| #else | |||
| static void Init() { | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_9; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| } | |||
| static inline void High() { | |||
| GPIOA->BSRRL = GPIO_Pin_9; | |||
| } | |||
| static inline void Low() { | |||
| GPIOA->BSRRH = GPIO_Pin_9; | |||
| } | |||
| #endif | |||
| private: | |||
| DISALLOW_COPY_AND_ASSIGN(DebugPin); | |||
| }; | |||
| #define TIC DebugPin::High(); | |||
| #define TOC DebugPin::Low(); | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_DEBUG_PIN_H_ | |||
| @@ -0,0 +1,94 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // UART driver for conversing with the factory testing program. | |||
| #ifndef MARBLES_DRIVERS_DEBUG_PORT_H_ | |||
| #define MARBLES_DRIVERS_DEBUG_PORT_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <stm32f4xx_conf.h> | |||
| namespace marbles { | |||
| class DebugPort { | |||
| public: | |||
| DebugPort() { } | |||
| ~DebugPort() { } | |||
| void Init() { | |||
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
| // Initialize TX and RX pins. | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); | |||
| GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); | |||
| // Initialize USART. | |||
| USART_InitTypeDef usart_init; | |||
| usart_init.USART_BaudRate = 9600; | |||
| usart_init.USART_WordLength = USART_WordLength_8b; | |||
| usart_init.USART_StopBits = USART_StopBits_1; | |||
| usart_init.USART_Parity = USART_Parity_No; | |||
| usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None; | |||
| usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; | |||
| USART_Init(USART1, &usart_init); | |||
| USART_Cmd(USART1, ENABLE); | |||
| } | |||
| bool writable() { | |||
| return USART1->SR & USART_FLAG_TXE; | |||
| } | |||
| bool readable() { | |||
| return USART1->SR & USART_FLAG_RXNE; | |||
| } | |||
| void Write(uint8_t byte) { | |||
| USART1->DR = byte; | |||
| } | |||
| uint8_t Read() { | |||
| return USART1->DR; | |||
| } | |||
| private: | |||
| DISALLOW_COPY_AND_ASSIGN(DebugPort); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_DEBUG_PORT_H_ | |||
| @@ -0,0 +1,86 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the two gate outputs. | |||
| #ifndef MARBLES_DRIVERS_GATE_OUTPUTS_H_ | |||
| #define MARBLES_DRIVERS_GATE_OUTPUTS_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <stm32f4xx_conf.h> | |||
| #include "marbles/io_buffer.h" | |||
| namespace marbles { | |||
| class GateOutputs { | |||
| public: | |||
| GateOutputs() { } | |||
| ~GateOutputs() { } | |||
| void Init() { | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| gpio_init.GPIO_Pin = GPIO_Pin_11; | |||
| GPIO_Init(GPIOC, &gpio_init); | |||
| } | |||
| inline void Write(IOBuffer::Slice s) { | |||
| if (s.block->gate_output[0][s.frame_index]) { | |||
| GPIOC->BSRRL = GPIO_Pin_11; | |||
| } else { | |||
| GPIOC->BSRRH = GPIO_Pin_11; | |||
| } | |||
| if (s.block->gate_output[1][s.frame_index]) { | |||
| GPIOA->BSRRL = GPIO_Pin_11; | |||
| } else { | |||
| GPIOA->BSRRH = GPIO_Pin_11; | |||
| } | |||
| if (s.block->gate_output[2][s.frame_index]) { | |||
| GPIOA->BSRRL = GPIO_Pin_12; | |||
| } else { | |||
| GPIOA->BSRRH = GPIO_Pin_12; | |||
| } | |||
| } | |||
| private: | |||
| DISALLOW_COPY_AND_ASSIGN(GateOutputs); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_GATE_OUTPUTS_H_ | |||
| @@ -0,0 +1,86 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for all the LEDs. | |||
| #include "marbles/drivers/leds.h" | |||
| #include <algorithm> | |||
| #include <stm32f4xx_conf.h> | |||
| namespace marbles { | |||
| using namespace std; | |||
| struct LedDefinition { | |||
| GPIO_TypeDef* gpio; | |||
| uint16_t pin[3]; // pins for R, G, B - assumed to be on the same GPIO | |||
| }; | |||
| const LedDefinition led_definition[] = { | |||
| { GPIOB, { 0, GPIO_Pin_10, 0 } }, // LED_T_DEJA_VU | |||
| { GPIOB, { GPIO_Pin_13, GPIO_Pin_12, 0 } }, // LED_T_MODEL | |||
| { GPIOC, { GPIO_Pin_7, GPIO_Pin_6, 0 } }, // LED_T_RANGE | |||
| { GPIOB, { 0, GPIO_Pin_9, 0 } }, // LED_X_DEJA_VU | |||
| { GPIOB, { GPIO_Pin_8, GPIO_Pin_7, 0 } }, // LED_X_CONTROL_MODE, | |||
| { GPIOB, { GPIO_Pin_4, GPIO_Pin_5, 0 } }, // LED_X_RANGE | |||
| { GPIOB, { 0, GPIO_Pin_3, 0 } } // LED_X_EXT | |||
| }; | |||
| void Leds::Init() { | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| for (int i = 0; i < LED_LAST; ++i) { | |||
| LedDefinition d = led_definition[i]; | |||
| gpio_init.GPIO_Pin = d.pin[0] | d.pin[1] | d.pin[2]; | |||
| GPIO_Init(d.gpio, &gpio_init); | |||
| } | |||
| Clear(); | |||
| } | |||
| void Leds::Clear() { | |||
| fill(&colors_[0], &colors_[LED_LAST], LED_COLOR_OFF); | |||
| } | |||
| void Leds::Write() { | |||
| for (int i = 0; i < LED_LAST; ++i) { | |||
| LedDefinition d = led_definition[i]; | |||
| GPIO_WriteBit(d.gpio, d.pin[0], static_cast<BitAction>( | |||
| (colors_[i] & 0x800000) >> 23)); | |||
| GPIO_WriteBit(d.gpio, d.pin[1], static_cast<BitAction>( | |||
| (colors_[i] & 0x008000) >> 15)); | |||
| } | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,75 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for all the LEDs. | |||
| #ifndef MARBLES_DRIVERS_LEDS_H_ | |||
| #define MARBLES_DRIVERS_LEDS_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace marbles { | |||
| enum Led { | |||
| LED_T_DEJA_VU, | |||
| LED_T_MODEL, | |||
| LED_T_RANGE, | |||
| LED_X_DEJA_VU, | |||
| LED_X_CONTROL_MODE, | |||
| LED_X_RANGE, | |||
| LED_X_EXT, | |||
| LED_LAST | |||
| }; | |||
| enum LedColor { | |||
| LED_COLOR_OFF = 0, | |||
| LED_COLOR_RED = 0xff0000, | |||
| LED_COLOR_GREEN = 0x00ff00, | |||
| LED_COLOR_YELLOW = 0xffff00, | |||
| }; | |||
| class Leds { | |||
| public: | |||
| Leds() { } | |||
| ~Leds() { } | |||
| void Init(); | |||
| void Write(); | |||
| void Clear(); | |||
| void set(Led led, uint32_t color) { | |||
| colors_[led] = color; | |||
| } | |||
| private: | |||
| uint32_t colors_[LED_LAST]; | |||
| DISALLOW_COPY_AND_ASSIGN(Leds); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_LEDS_H_ | |||
| @@ -0,0 +1,62 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for built-in random number generator. | |||
| #ifndef MARBLES_DRIVERS_RNG_H_ | |||
| #define MARBLES_DRIVERS_RNG_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <stm32f4xx_conf.h> | |||
| namespace marbles { | |||
| class Rng { | |||
| public: | |||
| Rng() { } | |||
| ~Rng() { } | |||
| void Init() { | |||
| RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_RNG, ENABLE); | |||
| RNG_Cmd(ENABLE); | |||
| } | |||
| inline bool readable() { | |||
| return RNG->SR & RNG_FLAG_DRDY; | |||
| } | |||
| inline uint32_t data() { | |||
| return RNG->DR; | |||
| } | |||
| private: | |||
| DISALLOW_COPY_AND_ASSIGN(Rng); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_RNG_H_ | |||
| @@ -0,0 +1,79 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the front panel switch. | |||
| #include "marbles/drivers/switches.h" | |||
| #include <algorithm> | |||
| namespace marbles { | |||
| using namespace std; | |||
| struct SwitchDefinition { | |||
| GPIO_TypeDef* gpio; | |||
| uint16_t pin; | |||
| }; | |||
| const SwitchDefinition switch_definitions[] = { | |||
| { GPIOB, GPIO_Pin_11 }, // SWITCH_T_DEJA_VU, | |||
| { GPIOB, GPIO_Pin_14 }, // SWITCH_T_MODE | |||
| { GPIOB, GPIO_Pin_15 }, // SWITCH_T_RANGE | |||
| { GPIOC, GPIO_Pin_15 }, // SWITCH_X_DEJA_VU, | |||
| { GPIOC, GPIO_Pin_13 }, // SWITCH_X_MODE, | |||
| { GPIOB, GPIO_Pin_6 }, // SWITCH_X_RANGE | |||
| { GPIOD, GPIO_Pin_2 } // SWITCH_X_EXT | |||
| }; | |||
| void Switches::Init() { | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
| RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_UP; | |||
| for (int i = 0; i < SWITCH_LAST; ++i) { | |||
| SwitchDefinition definition = switch_definitions[i]; | |||
| gpio_init.GPIO_Pin = definition.pin; | |||
| GPIO_Init(definition.gpio, &gpio_init); | |||
| } | |||
| fill(&switch_state_[0], &switch_state_[SWITCH_LAST], 0xff); | |||
| } | |||
| void Switches::Debounce() { | |||
| for (int i = 0; i < SWITCH_LAST; ++i) { | |||
| SwitchDefinition definition = switch_definitions[i]; | |||
| switch_state_[i] = (switch_state_[i] << 1) | \ | |||
| GPIO_ReadInputDataBit(definition.gpio, definition.pin); | |||
| } | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,87 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the 6 front panel switches. | |||
| #ifndef MARBLES_DRIVERS_SWITCHES_H_ | |||
| #define MARBLES_DRIVERS_SWITCHES_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <stm32f4xx_conf.h> | |||
| namespace marbles { | |||
| enum Switch { | |||
| SWITCH_T_DEJA_VU, | |||
| SWITCH_T_MODEL, | |||
| SWITCH_T_RANGE, | |||
| SWITCH_X_DEJA_VU, | |||
| SWITCH_X_MODE, | |||
| SWITCH_X_RANGE, | |||
| SWITCH_X_EXT, | |||
| SWITCH_LAST | |||
| }; | |||
| class Switches { | |||
| public: | |||
| Switches() { } | |||
| ~Switches() { } | |||
| void Init(); | |||
| void Debounce(); | |||
| inline bool released(Switch s) const { | |||
| return switch_state_[s] == 0x7f; | |||
| } | |||
| inline bool just_pressed(Switch s) const { | |||
| return switch_state_[s] == 0x80; | |||
| } | |||
| inline bool pressed(Switch s) const { | |||
| return switch_state_[s] == 0x00; | |||
| } | |||
| inline bool pressed_immediate(Switch s) const { | |||
| if (s == SWITCH_T_DEJA_VU) { | |||
| return !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); | |||
| } else if (s == SWITCH_X_MODE) { | |||
| return !GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13); | |||
| } else { | |||
| return false; | |||
| } | |||
| } | |||
| private: | |||
| uint8_t switch_state_[SWITCH_LAST]; | |||
| DISALLOW_COPY_AND_ASSIGN(Switches); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_SWITCHES_H_ | |||
| @@ -0,0 +1,48 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // System level initialization. | |||
| #include "marbles/drivers/system.h" | |||
| #include <stm32f4xx_conf.h> | |||
| namespace marbles { | |||
| void System::Init(bool application) { | |||
| if (application) { | |||
| NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x8000); | |||
| } | |||
| IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); | |||
| IWDG_SetPrescaler(IWDG_Prescaler_32); | |||
| } | |||
| void System::StartTimers() { | |||
| SysTick_Config(F_CPU / 1000); | |||
| IWDG_Enable(); | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,50 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // System-level initialization. | |||
| #ifndef MARBLES_DRIVERS_SYSTEM_H_ | |||
| #define MARBLES_DRIVERS_SYSTEM_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace marbles { | |||
| class System { | |||
| public: | |||
| System() { } | |||
| ~System() { } | |||
| void Init(bool application); | |||
| void StartTimers(); | |||
| private: | |||
| DISALLOW_COPY_AND_ASSIGN(System); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_DRIVERS_SYSTEM_H_ | |||
| @@ -0,0 +1,109 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // I/O Buffering. | |||
| #ifndef MARBLES_IO_BUFFER_H_ | |||
| #define MARBLES_IO_BUFFER_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "stmlib/utils/gate_flags.h" | |||
| #include <algorithm> | |||
| namespace marbles { | |||
| const size_t kNumBlocks = 2; | |||
| const size_t kBlockSize = 5; | |||
| const size_t kNumInputs = 2; | |||
| const size_t kNumCvOutputs = 4; | |||
| const size_t kNumGateOutputs = 3; | |||
| const size_t kNumParameters = 8; | |||
| class IOBuffer { | |||
| public: | |||
| struct Block { | |||
| uint16_t adc_value[kNumParameters * 2]; | |||
| bool input_patched[kNumInputs]; | |||
| stmlib::GateFlags input[kNumInputs][kBlockSize]; | |||
| uint16_t cv_output[kNumCvOutputs][kBlockSize]; | |||
| bool gate_output[kNumGateOutputs][kBlockSize + 2]; | |||
| }; | |||
| struct Slice { | |||
| Block* block; | |||
| size_t frame_index; | |||
| }; | |||
| typedef void ProcessFn(Block* block, size_t size); | |||
| IOBuffer() { } | |||
| ~IOBuffer() { } | |||
| void Init() { | |||
| io_block_ = 0; | |||
| render_block_ = kNumBlocks / 2; | |||
| io_frame_ = 0; | |||
| } | |||
| inline void Process(ProcessFn* fn) { | |||
| while (render_block_ != io_block_) { | |||
| (*fn)(&block_[render_block_], kBlockSize); | |||
| render_block_ = (render_block_ + 1) % kNumBlocks; | |||
| } | |||
| } | |||
| inline Slice NextSlice(size_t size) { | |||
| Slice s; | |||
| s.block = &block_[io_block_]; | |||
| s.frame_index = io_frame_; | |||
| io_frame_ += size; | |||
| if (io_frame_ >= kBlockSize) { | |||
| io_frame_ -= kBlockSize; | |||
| io_block_ = (io_block_ + 1) % kNumBlocks; | |||
| } | |||
| return s; | |||
| } | |||
| inline bool new_block() const { | |||
| return io_frame_ == 0; | |||
| } | |||
| private: | |||
| Block block_[kNumBlocks]; | |||
| size_t io_frame_; | |||
| volatile size_t io_block_; | |||
| volatile size_t render_block_; | |||
| DISALLOW_COPY_AND_ASSIGN(IOBuffer); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_IO_BUFFER_H_ | |||
| @@ -0,0 +1,57 @@ | |||
| # Copyright 2015 Olivier Gillet. | |||
| # | |||
| # Author: Olivier Gillet (ol.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. | |||
| # System specifications | |||
| F_CRYSTAL = 8000000L | |||
| F_CPU = 168000000L | |||
| SYSCLOCK = SYSCLK_FREQ_168MHz | |||
| FAMILY = f4xx | |||
| # USB = enabled | |||
| APPLICATION_LARGE = TRUE | |||
| BOOTLOADER = marbles_bootloader | |||
| # Prefered upload command | |||
| UPLOAD_COMMAND = upload_combo_jtag_erase_first | |||
| # Packages to build | |||
| TARGET = marbles | |||
| PACKAGES = marbles \ | |||
| marbles/drivers \ | |||
| marbles/ramp \ | |||
| marbles/random \ | |||
| stmlib/dsp \ | |||
| stmlib/utils \ | |||
| stmlib/system | |||
| RESOURCES = marbles/resources | |||
| TOOLCHAIN_PATH ?= /usr/local/arm-4.8.3/ | |||
| include stmlib/makefile.inc | |||
| # Rule for building the firmware update file | |||
| wav: $(TARGET_BIN) | |||
| python stm_audio_bootloader/qpsk/encoder.py \ | |||
| -t stm32f4 -s 48000 -b 12000 -c 6000 -p 256 \ | |||
| $(TARGET_BIN) | |||
| @@ -0,0 +1,447 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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 <stm32f4xx_conf.h> | |||
| #include "marbles/drivers/clock_inputs.h" | |||
| #include "marbles/drivers/dac.h" | |||
| #include "marbles/drivers/debug_pin.h" | |||
| #include "marbles/drivers/debug_port.h" | |||
| #include "marbles/drivers/gate_outputs.h" | |||
| #include "marbles/drivers/rng.h" | |||
| #include "marbles/drivers/system.h" | |||
| #include "marbles/ramp/ramp_extractor.h" | |||
| #include "marbles/random/random_generator.h" | |||
| #include "marbles/random/random_stream.h" | |||
| #include "marbles/random/t_generator.h" | |||
| #include "marbles/random/x_y_generator.h" | |||
| #include "marbles/clock_self_patching_detector.h" | |||
| #include "marbles/cv_reader.h" | |||
| #include "marbles/io_buffer.h" | |||
| #include "marbles/note_filter.h" | |||
| #include "marbles/resources.h" | |||
| #include "marbles/scale_recorder.h" | |||
| #include "marbles/settings.h" | |||
| #include "marbles/ui.h" | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/dsp/hysteresis_quantizer.h" | |||
| #include "stmlib/dsp/units.h" | |||
| #define PROFILE_INTERRUPT 0 | |||
| #define PROFILE_RENDER 0 | |||
| using namespace marbles; | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| const bool test_adc_noise = false; | |||
| const int kSampleRate = 32000; | |||
| const int kGateDelay = 2; | |||
| ClockInputs clock_inputs; | |||
| ClockSelfPatchingDetector self_patching_detector[kNumGateOutputs]; | |||
| CvReader cv_reader; | |||
| Dac dac; | |||
| DebugPort debug_port; | |||
| GateOutputs gate_outputs; | |||
| HysteresisQuantizer deja_vu_length_quantizer; | |||
| IOBuffer io_buffer; | |||
| NoteFilter note_filter; | |||
| Rng rng; | |||
| ScaleRecorder scale_recorder; | |||
| Settings settings; | |||
| Ui ui; | |||
| RandomGenerator random_generator; | |||
| RandomStream random_stream; | |||
| TGenerator t_generator; | |||
| XYGenerator xy_generator; | |||
| // Default interrupt handlers. | |||
| extern "C" { | |||
| int __errno; | |||
| void NMI_Handler() { } | |||
| void HardFault_Handler() { while (1); } | |||
| void MemManage_Handler() { while (1); } | |||
| void BusFault_Handler() { while (1); } | |||
| void UsageFault_Handler() { while (1); } | |||
| void SVC_Handler() { } | |||
| void DebugMon_Handler() { } | |||
| void PendSV_Handler() { } | |||
| void SysTick_Handler() { | |||
| IWDG_ReloadCounter(); | |||
| ui.Poll(); | |||
| if (settings.freshly_baked()) { | |||
| if (debug_port.readable()) { | |||
| uint8_t command = debug_port.Read(); | |||
| uint8_t response = ui.HandleFactoryTestingRequest(command); | |||
| debug_port.Write(response); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| IOBuffer::Slice FillBuffer(size_t size) { | |||
| if (PROFILE_INTERRUPT) { | |||
| TIC; | |||
| } | |||
| IOBuffer::Slice s = io_buffer.NextSlice(size); | |||
| gate_outputs.Write(s); | |||
| clock_inputs.Read(s, size); | |||
| if (io_buffer.new_block()) { | |||
| cv_reader.Copy(&s.block->adc_value[0]); | |||
| clock_inputs.ReadNormalization(s.block); | |||
| } | |||
| if (rng.readable()) { | |||
| random_stream.Write(rng.data()); | |||
| } | |||
| if (PROFILE_INTERRUPT) { | |||
| TOC; | |||
| } | |||
| return s; | |||
| } | |||
| inline uint16_t DacCode(int index, float voltage) { | |||
| CONSTRAIN(voltage, -5.0f, 5.0f); | |||
| const float scale = settings.calibration_data().dac_scale[index]; | |||
| const float offset = settings.calibration_data().dac_offset[index]; | |||
| return ClipU16(static_cast<int32_t>(voltage * scale + offset)); | |||
| } | |||
| void ProcessTest(IOBuffer::Block* block, size_t size) { | |||
| float parameters[kNumParameters]; | |||
| static float phase; | |||
| cv_reader.Process(&block->adc_value[0], parameters); | |||
| for (size_t i = 0; i < size; ++i) { | |||
| phase += 100.0f / static_cast<float>(kSampleRate); | |||
| if (phase >= 1.0f) { | |||
| phase -= 1.0f; | |||
| } | |||
| block->cv_output[0][i] = DacCode( | |||
| 0, 4.0 * Interpolate(lut_sine, phase, 256.0f)); | |||
| block->cv_output[1][i] = DacCode( | |||
| 1, -8.0f * phase + 4.0f); | |||
| block->cv_output[2][i] = DacCode( | |||
| 2, (phase < 0.5f ? phase : 1.0f - phase) * 16.0f - 4.0f); | |||
| block->cv_output[3][i] = DacCode( | |||
| 3, phase < 0.5f ? -4.0f : 4.0f); | |||
| for (int j = 0; j < 4; ++j) { | |||
| uint16_t dac_code = ui.output_test_forced_dac_code(j); | |||
| if (dac_code) { | |||
| block->cv_output[j][i] = dac_code; | |||
| } | |||
| } | |||
| block->gate_output[0][i] = block->input_patched[0] | |||
| ? block->input[0][i] | |||
| : phase < 0.2f; | |||
| block->gate_output[1][i] = phase < 0.5f; | |||
| block->gate_output[2][i] = block->input_patched[1] | |||
| ? block->input[1][i] | |||
| : phase < 0.8f; | |||
| } | |||
| } | |||
| Ratio y_divider_ratios[] = { | |||
| { 1, 64 }, | |||
| { 1, 48 }, | |||
| { 1, 32 }, | |||
| { 1, 24 }, | |||
| { 1, 16 }, | |||
| { 1, 12 }, | |||
| { 1, 8 }, | |||
| { 1, 6 }, | |||
| { 1, 4 }, | |||
| { 1, 3 }, | |||
| { 1, 2 }, | |||
| { 1, 1 }, | |||
| }; | |||
| int loop_length[] = { | |||
| 1, | |||
| 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, | |||
| 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, | |||
| 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, | |||
| 5, 5, 5, 5, | |||
| 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, | |||
| 7, 7, | |||
| 8, 8, 8, 8, 8, 8, 8, 8, 8, | |||
| 10, 10, 10, | |||
| 12, 12, 12, 12, 12, 12, 12, | |||
| 14, | |||
| 16 | |||
| }; | |||
| float parameters[kNumParameters]; | |||
| float ramp_buffer[kBlockSize * 4]; | |||
| bool gates[kBlockSize * 2]; | |||
| float voltages[kBlockSize * 4]; | |||
| Ramps ramps; | |||
| GroupSettings x, y; | |||
| bool gate_delay_tail[kNumGateOutputs][kGateDelay]; | |||
| float SineOscillator(float voltage) { | |||
| static float phase = 0.0f; | |||
| CONSTRAIN(voltage, -5.0f, 5.0f); | |||
| float frequency = stmlib::SemitonesToRatio(voltage * 12.0f) * 220.0f / kSampleRate; | |||
| phase += frequency; | |||
| if (phase >= 1.0f) { | |||
| phase -= 1.0f; | |||
| } | |||
| return 5.0f * Interpolate(lut_sine, phase, 256.0f); | |||
| } | |||
| void Process(IOBuffer::Block* block, size_t size) { | |||
| if (PROFILE_RENDER) { | |||
| TIC; | |||
| } | |||
| // Filter CV values (3.5%) | |||
| cv_reader.Process(&block->adc_value[0], parameters); | |||
| float deja_vu = parameters[ADC_CHANNEL_DEJA_VU_AMOUNT]; | |||
| // Deadband near 12 o'clock for the deja vu parameter. | |||
| const float d = fabsf(deja_vu - 0.5f); | |||
| if (d > 0.03f) { | |||
| ui.set_deja_vu_lock(false); | |||
| } else if (d < 0.02f) { | |||
| ui.set_deja_vu_lock(true); | |||
| } | |||
| if (deja_vu < 0.47f) { | |||
| deja_vu *= 1.06382978723f; | |||
| } else if (deja_vu > 0.53f) { | |||
| deja_vu = 0.5f + (deja_vu - 0.53f) * 1.06382978723f; | |||
| } else { | |||
| deja_vu = 0.5f; | |||
| } | |||
| GateFlags* t_clock = block->input[0]; | |||
| GateFlags* xy_clock = block->input[1]; | |||
| // Determine the clock source for the XY section (2%) | |||
| ClockSource xy_clock_source = CLOCK_SOURCE_INTERNAL_T1_T2_T3; | |||
| if (block->input_patched[1]) { | |||
| xy_clock_source = CLOCK_SOURCE_EXTERNAL; | |||
| size_t best_score = 8; | |||
| for (size_t i = 0; i < kNumGateOutputs; ++i) { | |||
| size_t score = self_patching_detector[i].Process(block, size); | |||
| if (score >= best_score) { | |||
| xy_clock_source = ClockSource(CLOCK_SOURCE_INTERNAL_T1 + i); | |||
| best_score = score; | |||
| } | |||
| } | |||
| } | |||
| // Generate gates for T-section (16%). | |||
| ramps.master = &ramp_buffer[0]; | |||
| ramps.external = &ramp_buffer[kBlockSize]; | |||
| ramps.slave[0] = &ramp_buffer[kBlockSize * 2]; | |||
| ramps.slave[1] = &ramp_buffer[kBlockSize * 3]; | |||
| const State& state = settings.state(); | |||
| int deja_vu_length = deja_vu_length_quantizer.Lookup( | |||
| loop_length, | |||
| parameters[ADC_CHANNEL_DEJA_VU_LENGTH], | |||
| sizeof(loop_length) / sizeof(int)); | |||
| t_generator.set_model(TGeneratorModel(state.t_model)); | |||
| t_generator.set_range(TGeneratorRange(state.t_range)); | |||
| t_generator.set_rate(parameters[ADC_CHANNEL_T_RATE]); | |||
| t_generator.set_bias(parameters[ADC_CHANNEL_T_BIAS]); | |||
| t_generator.set_jitter(parameters[ADC_CHANNEL_T_JITTER]); | |||
| t_generator.set_deja_vu(state.t_deja_vu ? deja_vu : 0.0f); | |||
| t_generator.set_length(deja_vu_length); | |||
| t_generator.set_pulse_width_mean(float(state.t_pulse_width_mean) / 256.0f); | |||
| t_generator.set_pulse_width_std(float(state.t_pulse_width_std) / 256.0f); | |||
| t_generator.Process( | |||
| block->input_patched[0], | |||
| t_clock, | |||
| ramps, | |||
| gates, | |||
| size); | |||
| // Generate voltages for X-section (40%). | |||
| float note_cv_1 = cv_reader.channel(ADC_CHANNEL_X_SPREAD).scaled_raw_cv(); | |||
| float note_cv_2 = cv_reader.channel(ADC_CHANNEL_X_SPREAD_2).scaled_raw_cv(); | |||
| float note_cv = 0.5f * (note_cv_1 + note_cv_2); | |||
| float u = note_filter.Process(0.5f * (note_cv + 1.0f)); | |||
| if (test_adc_noise) { | |||
| static float note_lp = 0.0f; | |||
| float note = note_cv_1; | |||
| ONE_POLE(note_lp, note, 0.0001f); | |||
| float cents = (note - note_lp) * 1200.0f * 5.0f; | |||
| fill(&voltages[0], &voltages[4 * size], cents); | |||
| } else if (ui.recording_scale()) { | |||
| float voltage = (u - 0.5f) * 10.0f; | |||
| for (size_t i = 0; i < size; ++i) { | |||
| GateFlags gate = block->input_patched[1] | |||
| ? block->input[1][i] | |||
| : GATE_FLAG_LOW; | |||
| if (gate & GATE_FLAG_RISING) { | |||
| scale_recorder.NewNote(voltage); | |||
| } | |||
| if (gate & GATE_FLAG_HIGH) { | |||
| scale_recorder.UpdateVoltage(voltage); | |||
| } | |||
| if (gate & GATE_FLAG_FALLING) { | |||
| scale_recorder.AcceptNote(); | |||
| } | |||
| } | |||
| fill(&voltages[0], &voltages[4 * size], voltage); | |||
| } else { | |||
| x.control_mode = ControlMode(state.x_control_mode); | |||
| x.voltage_range = VoltageRange(state.x_range % 3); | |||
| x.register_mode = state.x_register_mode; | |||
| x.register_value = u; | |||
| cv_reader.set_attenuverter( | |||
| ADC_CHANNEL_X_SPREAD, state.x_register_mode ? 0.5f : 1.0f); | |||
| x.spread = parameters[ADC_CHANNEL_X_SPREAD]; | |||
| x.bias = parameters[ADC_CHANNEL_X_BIAS]; | |||
| x.steps = parameters[ADC_CHANNEL_X_STEPS]; | |||
| x.deja_vu = state.x_deja_vu ? deja_vu : 0.0f; | |||
| x.length = deja_vu_length; | |||
| x.ratio.p = 1; | |||
| x.ratio.q = 1; | |||
| y.control_mode = CONTROL_MODE_IDENTICAL; | |||
| y.voltage_range = VoltageRange(state.y_range); | |||
| y.register_mode = false; | |||
| y.register_value = 0.0f; | |||
| y.spread = float(state.y_spread) / 256.0f; | |||
| y.bias = float(state.y_bias) / 256.0f; | |||
| y.steps = float(state.y_steps) / 256.0f; | |||
| y.deja_vu = 0.0f; | |||
| y.length = 1; | |||
| y.ratio = y_divider_ratios[ | |||
| static_cast<uint16_t>(state.y_divider) * 12 >> 8]; | |||
| if (settings.dirty_scale_index() != -1) { | |||
| int i = settings.dirty_scale_index(); | |||
| xy_generator.LoadScale(i, settings.persistent_data().scale[i]); | |||
| settings.set_dirty_scale_index(-1); | |||
| } | |||
| y.scale_index = x.scale_index = state.x_scale; | |||
| xy_generator.Process( | |||
| xy_clock_source, | |||
| x, | |||
| y, | |||
| xy_clock, | |||
| ramps, | |||
| voltages, | |||
| size); | |||
| } | |||
| const float* v = voltages; | |||
| const bool* g = gates; | |||
| for (size_t i = 0; i < size; ++i) { | |||
| block->cv_output[1][i] = DacCode(1, *v++); | |||
| block->cv_output[2][i] = DacCode(2, *v++); | |||
| block->cv_output[3][i] = DacCode(3, *v++); | |||
| block->cv_output[0][i] = DacCode(0, *v++); | |||
| block->gate_output[0][i + kGateDelay] = *g++; | |||
| block->gate_output[1][i + kGateDelay] = ramps.master[i] < 0.5f; | |||
| block->gate_output[2][i + kGateDelay] = *g++; | |||
| } | |||
| for (size_t i = 0; i < kNumGateOutputs; ++i) { | |||
| for (size_t j = 0; j < kGateDelay; ++j) { | |||
| block->gate_output[i][j] = gate_delay_tail[i][j]; | |||
| gate_delay_tail[i][j] = block->gate_output[i][size + j]; | |||
| } | |||
| } | |||
| if (PROFILE_RENDER) { | |||
| TOC; | |||
| } | |||
| } | |||
| void Init() { | |||
| System sys; | |||
| sys.Init(true); | |||
| settings.Init(); | |||
| clock_inputs.Init(); | |||
| dac.Init(kSampleRate, 1); | |||
| rng.Init(); | |||
| note_filter.Init(); | |||
| gate_outputs.Init(); | |||
| io_buffer.Init(); | |||
| deja_vu_length_quantizer.Init(); | |||
| cv_reader.Init(settings.mutable_calibration_data()); | |||
| scale_recorder.Init(); | |||
| ui.Init(&settings, &cv_reader, &scale_recorder, &clock_inputs); | |||
| if (settings.freshly_baked()) { | |||
| settings.ProgramOptionBytes(); | |||
| if (PROFILE_INTERRUPT || PROFILE_RENDER) { | |||
| DebugPin::Init(); | |||
| } else { | |||
| debug_port.Init(); | |||
| } | |||
| } | |||
| random_generator.Init(1); | |||
| random_stream.Init(&random_generator); | |||
| t_generator.Init(&random_stream, static_cast<float>(kSampleRate)); | |||
| xy_generator.Init(&random_stream, static_cast<float>(kSampleRate)); | |||
| for (size_t i = 0; i < kNumScales; ++i) { | |||
| xy_generator.LoadScale(i, settings.persistent_data().scale[i]); | |||
| } | |||
| for (size_t i = 0; i < kNumGateOutputs; ++i) { | |||
| self_patching_detector[i].Init(i); | |||
| } | |||
| sys.StartTimers(); | |||
| dac.Start(&FillBuffer); | |||
| } | |||
| int main(void) { | |||
| Init(); | |||
| while (1) { | |||
| ui.DoEvents(); | |||
| io_buffer.Process(ui.output_test_mode() ? &ProcessTest : &Process); | |||
| } | |||
| } | |||
| @@ -0,0 +1,82 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Low pass filter for getting stable pitch data. | |||
| #ifndef MARBLES_NOTE_FILTER_H_ | |||
| #define MARBLES_NOTE_FILTER_H_ | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/dsp/delay_line.h" | |||
| namespace marbles { | |||
| // Note: this is a "slow" filter, since it takes about 10 samples to catch | |||
| // up with an edge in the input signal. | |||
| class NoteFilter { | |||
| public: | |||
| enum { | |||
| N = 7 // Median filter order | |||
| }; | |||
| NoteFilter() { } | |||
| ~NoteFilter() { } | |||
| void Init() { | |||
| lp_1_ = 0.0f; | |||
| lp_2_ = 0.0f; | |||
| std::fill(&previous_values_[0], &previous_values_[N], 0.0f); | |||
| } | |||
| inline float Process(float value) { | |||
| float sorted_values[N]; | |||
| std::rotate( | |||
| &previous_values_[0], | |||
| &previous_values_[1], | |||
| &previous_values_[N]); | |||
| previous_values_[N - 1] = value; | |||
| std::copy(&previous_values_[0], &previous_values_[N], &sorted_values[0]); | |||
| std::sort(&sorted_values[0], &sorted_values[N]); | |||
| value = sorted_values[(N - 1) / 2]; | |||
| const float kLPCoefficient = 0.65f; | |||
| ONE_POLE(lp_1_, value, kLPCoefficient); | |||
| ONE_POLE(lp_2_, lp_1_, kLPCoefficient); | |||
| return lp_2_; | |||
| } | |||
| private: | |||
| float previous_values_[N]; | |||
| float lp_1_; | |||
| float lp_2_; | |||
| DISALLOW_COPY_AND_ASSIGN(NoteFilter); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_NOTE_FILTER_H_ | |||
| @@ -0,0 +1,40 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Timing information is represented as a ramp from 0.0 to kMaxRampValue | |||
| #ifndef MARBLES_RAMP_RAMP_H_ | |||
| #define MARBLES_RAMP_RAMP_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace marbles { | |||
| const float kMaxRampValue = 0.9999f; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RAMP_RAMP_H_ | |||
| @@ -0,0 +1,107 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Generates a ramp whose frequency is p/q times the frequency of the input. | |||
| // Phase is synchronized. | |||
| #ifndef MARBLES_RAMP_RAMP_DIVIDER_H_ | |||
| #define MARBLES_RAMP_RAMP_DIVIDER_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <algorithm> | |||
| #include "marbles/ramp/ramp.h" | |||
| namespace marbles { | |||
| struct Ratio { | |||
| float to_float() { return static_cast<float>(p) / static_cast<float>(q); } | |||
| int p; | |||
| int q; | |||
| template<int n> void Simplify() { | |||
| while ((p % n) == 0 && (q % n) == 0) { | |||
| p /= n; | |||
| q /= n; | |||
| } | |||
| } | |||
| }; | |||
| class RampDivider { | |||
| public: | |||
| RampDivider() { } | |||
| ~RampDivider() { } | |||
| void Init() { | |||
| phase_ = 0.0f; | |||
| train_phase_ = 0.0f; | |||
| max_train_phase_ = 1.0f; | |||
| f_ratio_ = 0.99999f; | |||
| reset_counter_ = 1; | |||
| } | |||
| void Process(Ratio ratio, const float* in, float* out, size_t size) { | |||
| while (size--) { | |||
| float new_phase = *in++; | |||
| float frequency = new_phase - phase_; | |||
| if (frequency < 0.0f) { | |||
| frequency += 1.0f; | |||
| --reset_counter_; | |||
| if (!reset_counter_) { | |||
| train_phase_ = new_phase; | |||
| reset_counter_ = ratio.q; | |||
| f_ratio_ = ratio.to_float() * kMaxRampValue; | |||
| frequency = 0.0f; | |||
| max_train_phase_ = static_cast<float>(ratio.q); | |||
| } | |||
| } | |||
| train_phase_ += frequency; | |||
| if (train_phase_ >= max_train_phase_) { | |||
| train_phase_ = max_train_phase_; | |||
| } | |||
| float output_phase = train_phase_ * f_ratio_; | |||
| output_phase -= static_cast<float>(static_cast<int>(output_phase)); | |||
| *out++ = output_phase; | |||
| phase_ = new_phase; | |||
| } | |||
| } | |||
| private: | |||
| float phase_; | |||
| float train_phase_; | |||
| float max_train_phase_; | |||
| float f_ratio_; | |||
| int reset_counter_; | |||
| DISALLOW_COPY_AND_ASSIGN(RampDivider); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RAMP_RAMP_DIVIDER_H_ | |||
| @@ -0,0 +1,312 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Recovers a ramp from a clock input by guessing at what time the next edge | |||
| // will occur. Prediction strategies: | |||
| // - Moving average of previous intervals. | |||
| // - Trigram model on quantized intervals. | |||
| // - Periodic rhythmic pattern. | |||
| // - Assume that the pulse width is constant, deduct the period from the on time | |||
| // and the pulse width. | |||
| #include "marbles/ramp/ramp_extractor.h" | |||
| #include <algorithm> | |||
| #include "marbles/ramp/ramp.h" | |||
| #include "stmlib/dsp/dsp.h" | |||
| namespace marbles { | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| const float kLogOneFourth = 1.189207115f; | |||
| const float kPulseWidthTolerance = 0.05f; | |||
| inline bool IsWithinTolerance(float x, float y, float error) { | |||
| return x >= y * (1.0f - error) && x <= y * (1.0f + error); | |||
| } | |||
| void RampExtractor::Init(float max_frequency) { | |||
| max_frequency_ = max_frequency; | |||
| audio_rate_period_ = 1.0f / (100.0f / 32000.0f); | |||
| audio_rate_period_hysteresis_ = audio_rate_period_; | |||
| Reset(); | |||
| } | |||
| void RampExtractor::Reset() { | |||
| audio_rate_ = false; | |||
| train_phase_ = 0.0f; | |||
| target_frequency_ = frequency_ = 0.0001f; | |||
| lp_coefficient_ = 0.5f; | |||
| next_max_train_phase_ = max_train_phase_ = 0.999f; | |||
| next_f_ratio_ = f_ratio_ = 1.0f; | |||
| reset_counter_ = 1; | |||
| reset_frequency_ = 0.0f; | |||
| reset_interval_ = 32000 * 3; | |||
| Pulse p; | |||
| p.bucket = 1; | |||
| p.on_duration = 2000; | |||
| p.total_duration = 4000; | |||
| p.pulse_width = 0.5f; | |||
| fill(&history_[0], &history_[kHistorySize], p); | |||
| current_pulse_ = 0; | |||
| next_bucket_ = 48.0f; | |||
| average_pulse_width_ = 0.0f; | |||
| fill(&predicted_period_[0], &predicted_period_[PREDICTOR_LAST], 4000.0f); | |||
| fill(&prediction_accuracy_[0], &prediction_accuracy_[PREDICTOR_LAST], 0.0f); | |||
| fill( | |||
| &prediction_hash_table_[0], | |||
| &prediction_hash_table_[kHashTableSize], | |||
| 0.0f); | |||
| } | |||
| float RampExtractor::ComputeAveragePulseWidth(float tolerance) const { | |||
| float sum = 0.0f; | |||
| for (size_t i = 0; i < kHistorySize; ++i) { | |||
| if (!IsWithinTolerance(history_[i].pulse_width, | |||
| history_[current_pulse_].pulse_width, | |||
| tolerance)) { | |||
| return 0.0f; | |||
| } | |||
| sum += history_[i].pulse_width; | |||
| } | |||
| return sum / static_cast<float>(kHistorySize); | |||
| } | |||
| RampExtractor::Prediction RampExtractor::PredictNextPeriod() { | |||
| float last_period = static_cast<float>(history_[current_pulse_].total_duration); | |||
| Predictor best_predictor = PREDICTOR_FAST_MOVING_AVERAGE; | |||
| for (int i = PREDICTOR_FAST_MOVING_AVERAGE; i < PREDICTOR_LAST; ++i) { | |||
| float error = (predicted_period_[i] - last_period) / (last_period + 0.01f); | |||
| // Scoring function: 10% error is half as good as 0% error. | |||
| float accuracy = 1.0f / (1.0f + 100.0f * error * error); | |||
| // Slowly trust good predictors, quickly demote predictors who make errors. | |||
| SLOPE(prediction_accuracy_[i], accuracy, 0.1f, 0.5f); | |||
| // (Ugly code but I don't want virtuals for these.) | |||
| switch (i) { | |||
| case PREDICTOR_SLOW_MOVING_AVERAGE: | |||
| ONE_POLE(predicted_period_[i], last_period, 0.1f); | |||
| break; | |||
| case PREDICTOR_FAST_MOVING_AVERAGE: | |||
| ONE_POLE(predicted_period_[i], last_period, 0.5f); | |||
| break; | |||
| case PREDICTOR_HASH: | |||
| { | |||
| size_t t_2 = (current_pulse_ - 2 + kHistorySize) % kHistorySize; | |||
| size_t t_1 = (current_pulse_ - 1 + kHistorySize) % kHistorySize; | |||
| size_t t_0 = current_pulse_; | |||
| size_t hash = history_[t_1].bucket + 17 * history_[t_2].bucket; | |||
| ONE_POLE( | |||
| prediction_hash_table_[hash % kHashTableSize], | |||
| last_period, | |||
| 0.5f); | |||
| hash = history_[t_0].bucket + 17 * history_[t_1].bucket; | |||
| predicted_period_[i] = prediction_hash_table_[hash % kHashTableSize]; | |||
| if (predicted_period_[i] == 0.0f) { | |||
| predicted_period_[i] = last_period; | |||
| } | |||
| } | |||
| break; | |||
| default: | |||
| { | |||
| // Periodicity detector. | |||
| size_t candidate_period = i - PREDICTOR_PERIOD_1 + 1; | |||
| size_t t = current_pulse_ + 1 + kHistorySize - candidate_period; | |||
| predicted_period_[i] = history_[t % kHistorySize].total_duration; | |||
| } | |||
| break; | |||
| } | |||
| if (prediction_accuracy_[i] >= prediction_accuracy_[best_predictor]) { | |||
| best_predictor = Predictor(i); | |||
| } | |||
| } | |||
| Prediction p; | |||
| p.period = predicted_period_[best_predictor]; | |||
| p.accuracy = prediction_accuracy_[best_predictor]; | |||
| return p; | |||
| } | |||
| void RampExtractor::Process( | |||
| Ratio ratio, | |||
| bool always_ramp_to_maximum, | |||
| const GateFlags* gate_flags, | |||
| float* ramp, | |||
| size_t size) { | |||
| while (size--) { | |||
| GateFlags flags = *gate_flags++; | |||
| // We are done with the previous pulse. | |||
| if (flags & GATE_FLAG_RISING) { | |||
| Pulse& p = history_[current_pulse_]; | |||
| const bool record_pulse = p.total_duration < reset_interval_; | |||
| if (!record_pulse) { | |||
| // Quite a long pause - the clock has probably been stopped | |||
| // and restarted. | |||
| reset_frequency_ = 0.0f; | |||
| train_phase_ = 0.0f; | |||
| reset_counter_ = ratio.q; | |||
| reset_interval_ = 4 * p.total_duration; | |||
| } else { | |||
| float period = float(p.total_duration); | |||
| if (period <= audio_rate_period_hysteresis_) { | |||
| audio_rate_ = true; | |||
| audio_rate_period_hysteresis_ = audio_rate_period_ * 1.1f; | |||
| average_pulse_width_ = 0.0f; | |||
| bool no_glide = f_ratio_ != ratio.to_float(); | |||
| f_ratio_ = ratio.to_float(); | |||
| float frequency = 1.0f / period; | |||
| target_frequency_ = std::min(f_ratio_ * frequency, max_frequency_); | |||
| float up_tolerance = (1.02f + 2.0f * frequency) * frequency_; | |||
| float down_tolerance = (0.98f - 2.0f * frequency) * frequency_; | |||
| no_glide |= target_frequency_ > up_tolerance || | |||
| target_frequency_ < down_tolerance; | |||
| lp_coefficient_ = no_glide ? 1.0f : period * 0.00001f; | |||
| } else { | |||
| audio_rate_ = false; | |||
| audio_rate_period_hysteresis_ = audio_rate_period_; | |||
| // Compute the pulse width of the previous pulse, and check if the | |||
| // PW has been consistent over the past pulses. | |||
| p.pulse_width = static_cast<float>(p.on_duration) / period; | |||
| average_pulse_width_ = ComputeAveragePulseWidth(kPulseWidthTolerance); | |||
| if (p.on_duration < 32) { | |||
| average_pulse_width_ = 0.0f; | |||
| } | |||
| // Try to predict the next interval between pulses. If the prediction | |||
| // has been reliable over the past pulses, or if the PW is steady, | |||
| // we'll be able to make reliable prediction about the time at which | |||
| // the next pulse will occur | |||
| Prediction prediction = PredictNextPeriod(); | |||
| frequency_ = 1.0f / prediction.period; | |||
| --reset_counter_; | |||
| if (!reset_counter_) { | |||
| next_f_ratio_ = ratio.to_float() * kMaxRampValue; | |||
| next_max_train_phase_ = static_cast<float>(ratio.q); | |||
| if (always_ramp_to_maximum && train_phase_ < max_train_phase_) { | |||
| reset_frequency_ = \ | |||
| (0.01f + max_train_phase_ - train_phase_) * 0.0625f; | |||
| } else { | |||
| reset_frequency_ = 0.0f; | |||
| train_phase_ = 0.0f; | |||
| f_ratio_ = next_f_ratio_; | |||
| max_train_phase_ = next_max_train_phase_; | |||
| } | |||
| reset_counter_ = ratio.q; | |||
| } else { | |||
| float expected = max_train_phase_ - static_cast<float>(reset_counter_); | |||
| float warp = expected - train_phase_ + 1.0f; | |||
| frequency_ *= max(warp, 0.01f); | |||
| } | |||
| } | |||
| reset_interval_ = static_cast<uint32_t>( | |||
| std::max(4.0f / target_frequency_, 32000 * 3.0f)); | |||
| current_pulse_ = (current_pulse_ + 1) % kHistorySize; | |||
| } | |||
| history_[current_pulse_].on_duration = 0; | |||
| history_[current_pulse_].total_duration = 0; | |||
| history_[current_pulse_].bucket = 0; | |||
| next_bucket_ = 48.0f; | |||
| } | |||
| // Update history buffer with total duration and on duration. | |||
| ++history_[current_pulse_].total_duration; | |||
| if (flags & GATE_FLAG_HIGH) { | |||
| ++history_[current_pulse_].on_duration; | |||
| } | |||
| if (float(history_[current_pulse_].total_duration) >= next_bucket_) { | |||
| ++history_[current_pulse_].bucket; | |||
| next_bucket_ *= kLogOneFourth; | |||
| } | |||
| // If the pulse width is constant, and if a clock falling edge is | |||
| // detected, estimate the period using the on time and the pulse width, | |||
| // and correct the phase increment accordingly. | |||
| if ((flags & GATE_FLAG_FALLING) && | |||
| average_pulse_width_ > 0.0f) { | |||
| float t_on = static_cast<float>(history_[current_pulse_].on_duration); | |||
| float next = max_train_phase_ - static_cast<float>(reset_counter_) + 1.0f; | |||
| float pw = average_pulse_width_; | |||
| frequency_ = max((next - train_phase_), 0.0f) * pw / ((1.0f - pw) * t_on); | |||
| } | |||
| if (audio_rate_) { | |||
| ONE_POLE(frequency_, target_frequency_, lp_coefficient_); | |||
| train_phase_ += frequency_; | |||
| if (train_phase_ >= 1.0f) { | |||
| train_phase_ -= 1.0f; | |||
| } | |||
| *ramp++ = train_phase_; | |||
| } else { | |||
| if (reset_frequency_) { | |||
| train_phase_ += reset_frequency_; | |||
| if (train_phase_ >= max_train_phase_) { | |||
| train_phase_ = 0.0f; | |||
| reset_frequency_ = 0.0f; | |||
| f_ratio_ = next_f_ratio_; | |||
| max_train_phase_ = next_max_train_phase_; | |||
| } | |||
| } else { | |||
| train_phase_ += frequency_; | |||
| if (train_phase_ >= max_train_phase_) { | |||
| if (frequency_ == max_frequency_) { | |||
| train_phase_ -= max_train_phase_; | |||
| } else { | |||
| train_phase_ = max_train_phase_; | |||
| } | |||
| } | |||
| } | |||
| float output_phase = train_phase_ * f_ratio_; | |||
| output_phase -= static_cast<float>(static_cast<int>(output_phase)); | |||
| *ramp++ = output_phase; | |||
| } | |||
| } | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,132 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Recovers a ramp from a clock input by guessing at what time the next edge | |||
| // will occur. Prediction strategies: | |||
| // - Moving average of previous intervals. | |||
| // - Trigram model on quantized intervals. | |||
| // - Periodic rhythmic pattern. | |||
| // - Assume that the pulse width is constant, deduct the period from the on time | |||
| // and the pulse width. | |||
| // | |||
| // All prediction strategies are concurrently tested, and the output from the | |||
| // best performing one is selected (Ă la early Scheirer/Goto beat trackers). | |||
| #ifndef MARBLES_RAMP_RAMP_EXTRACTOR_H_ | |||
| #define MARBLES_RAMP_RAMP_EXTRACTOR_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "stmlib/utils/gate_flags.h" | |||
| #include "marbles/ramp/ramp_divider.h" | |||
| namespace marbles { | |||
| class RampExtractor { | |||
| public: | |||
| RampExtractor() { } | |||
| ~RampExtractor() { } | |||
| void Init(float max_frequency); | |||
| void Process( | |||
| Ratio r, | |||
| bool always_ramp_to_maximum, | |||
| const stmlib::GateFlags* gate_flags, | |||
| float* ramp, | |||
| size_t size); | |||
| void Reset(); | |||
| private: | |||
| struct Pulse { | |||
| uint32_t on_duration; | |||
| uint32_t total_duration; | |||
| uint32_t bucket; // 4xlog2(total_duration). | |||
| float pulse_width; | |||
| }; | |||
| struct Prediction { | |||
| float period; | |||
| float accuracy; | |||
| }; | |||
| enum Predictor { | |||
| PREDICTOR_SLOW_MOVING_AVERAGE, | |||
| PREDICTOR_FAST_MOVING_AVERAGE, | |||
| PREDICTOR_HASH, | |||
| PREDICTOR_PERIOD_1, | |||
| PREDICTOR_PERIOD_2, | |||
| PREDICTOR_PERIOD_3, | |||
| PREDICTOR_PERIOD_4, | |||
| PREDICTOR_PERIOD_5, | |||
| PREDICTOR_PERIOD_6, | |||
| PREDICTOR_PERIOD_7, | |||
| PREDICTOR_PERIOD_8, | |||
| PREDICTOR_PERIOD_9, | |||
| PREDICTOR_PERIOD_10, | |||
| PREDICTOR_LAST | |||
| }; | |||
| static const size_t kHistorySize = 16; | |||
| static const size_t kHashTableSize = 256; | |||
| float ComputeAveragePulseWidth(float tolerance) const; | |||
| Prediction PredictNextPeriod(); | |||
| size_t current_pulse_; | |||
| Pulse history_[kHistorySize]; | |||
| float next_bucket_; | |||
| float prediction_hash_table_[kHashTableSize]; | |||
| float predicted_period_[PREDICTOR_LAST]; | |||
| float prediction_accuracy_[PREDICTOR_LAST]; | |||
| float average_pulse_width_; | |||
| float train_phase_; | |||
| float frequency_; | |||
| float max_output_phase_; | |||
| float max_train_phase_; | |||
| float reset_frequency_; | |||
| float target_frequency_; | |||
| float lp_coefficient_; | |||
| float f_ratio_; | |||
| float next_f_ratio_; | |||
| float next_max_train_phase_; | |||
| int reset_counter_; | |||
| uint32_t reset_interval_; | |||
| bool audio_rate_; | |||
| float max_frequency_; | |||
| float audio_rate_period_; | |||
| float audio_rate_period_hysteresis_; | |||
| DISALLOW_COPY_AND_ASSIGN(RampExtractor); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RAMP_RAMP_EXTRACTOR_H_ | |||
| @@ -0,0 +1,63 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Simple ramp generator. | |||
| #ifndef MARBLES_RAMP_RAMP_GENERATOR_H_ | |||
| #define MARBLES_RAMP_RAMP_GENERATOR_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace marbles { | |||
| class RampGenerator { | |||
| public: | |||
| RampGenerator() { } | |||
| ~RampGenerator() { } | |||
| void Init() { | |||
| phase_ = 0.0f; | |||
| } | |||
| void Render(float frequency, float* out, size_t size) { | |||
| while (size--) { | |||
| phase_ += frequency; | |||
| if (phase_ >= 1.0f) { | |||
| phase_ -= 1.0f; | |||
| } | |||
| *out++ = phase_; | |||
| } | |||
| } | |||
| private: | |||
| float phase_; | |||
| DISALLOW_COPY_AND_ASSIGN(RampGenerator); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RAMP_RAMP_GENERATOR_H_ | |||
| @@ -0,0 +1,132 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // A ramp that follows a mater ramp through division/multiplication. | |||
| #ifndef MARBLES_RAMP_SLAVE_RAMP_H_ | |||
| #define MARBLES_RAMP_SLAVE_RAMP_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/ramp/ramp.h" | |||
| namespace marbles { | |||
| class SlaveRamp { | |||
| public: | |||
| SlaveRamp() { } | |||
| ~SlaveRamp() { } | |||
| inline void Init() { | |||
| phase_ = 0.0f; | |||
| max_phase_ = kMaxRampValue; | |||
| ratio_ = 1.0f; | |||
| pulse_width_ = 0.0f; | |||
| target_ = 1.0f; | |||
| pulse_length_ = 0; | |||
| bernoulli_ = false; | |||
| must_complete_ = false; | |||
| } | |||
| // Initialize with a multiplied/divided rate compared to the master. | |||
| inline void Init(int pattern_length, Ratio ratio, float pulse_width) { | |||
| bernoulli_ = false; | |||
| phase_ = 0.0f; | |||
| max_phase_ = static_cast<float>(pattern_length) * kMaxRampValue; | |||
| ratio_ = ratio.to_float(); | |||
| pulse_width_ = pulse_width; | |||
| target_ = 1.0f; | |||
| pulse_length_ = 0; | |||
| } | |||
| // Initialize with an adaptive slope: divide the frequency by 2 every time | |||
| // we know we won't have to reach 1.0 at the next tick. | |||
| inline void Init( | |||
| bool must_complete, | |||
| float pulse_width, | |||
| float expected_value) { | |||
| bernoulli_ = true; | |||
| if (must_complete_) { | |||
| phase_ = 0.0f; | |||
| pulse_width_ = pulse_width; | |||
| ratio_ = 1.0f; | |||
| pulse_length_ = 0; | |||
| } | |||
| if (!must_complete) { | |||
| ratio_ = (1.0f - phase_) * expected_value; | |||
| } else { | |||
| ratio_ = 1.0f - phase_; | |||
| } | |||
| must_complete_ = must_complete; | |||
| } | |||
| inline void Process(float frequency, float* phase, bool* gate) { | |||
| float output_phase; | |||
| if (bernoulli_) { | |||
| phase_ += frequency * ratio_; | |||
| output_phase = phase_; | |||
| if (output_phase >= 1.0f) { | |||
| output_phase = 1.0f; | |||
| } | |||
| } else { | |||
| phase_ += frequency; | |||
| if (phase_ >= max_phase_) { | |||
| phase_ = max_phase_; | |||
| } | |||
| output_phase = phase_ * ratio_; | |||
| if (output_phase > target_) { | |||
| pulse_length_ = 0; | |||
| target_ += 1.0f; | |||
| } | |||
| output_phase -= static_cast<float>(static_cast<int>(output_phase)); | |||
| } | |||
| *phase = output_phase; | |||
| *gate = pulse_width_ == 0.0f | |||
| ? pulse_length_ < 32 && output_phase <= 0.5f | |||
| : output_phase < pulse_width_; | |||
| ++pulse_length_; | |||
| } | |||
| private: | |||
| float phase_; | |||
| float max_phase_; | |||
| float ratio_; | |||
| float pulse_width_; | |||
| float target_; | |||
| int pulse_length_; | |||
| bool bernoulli_; | |||
| bool must_complete_; | |||
| DISALLOW_COPY_AND_ASSIGN(SlaveRamp); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RAMP_SLAVE_RAMP_H_ | |||
| @@ -0,0 +1,115 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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 voltages by sampling from a discrete distribution. | |||
| #include "marbles/random/discrete_distribution_quantizer.h" | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include <cmath> | |||
| #include <algorithm> | |||
| namespace marbles { | |||
| using namespace stmlib; | |||
| using namespace std; | |||
| void DiscreteDistributionQuantizer::Init(const Scale& scale) { | |||
| int n = scale.num_degrees; | |||
| // We don't want garbage scale data here... | |||
| if (!n || n > kMaxDegrees || scale.base_interval == 0.0f) { | |||
| return; | |||
| } | |||
| base_interval_ = scale.base_interval; | |||
| base_interval_reciprocal_ = 1.0f / scale.base_interval; | |||
| num_cells_ = n + 1; | |||
| for (int i = 0; i <= n; ++i) { | |||
| float previous_voltage = scale.cell_voltage(i == 0 ? 0 : i - 1); | |||
| float next_voltage = scale.cell_voltage(i == n ? n : i + 1); | |||
| cells_[i].center = scale.cell_voltage(i); | |||
| cells_[i].width = 0.5f * (next_voltage - previous_voltage); | |||
| cells_[i].weight = static_cast<float>(scale.degree[i % n].weight) / 256.0f; | |||
| } | |||
| } | |||
| float DiscreteDistributionQuantizer::Process(float value, float amount) { | |||
| if (amount < 0.0f) { | |||
| return value; | |||
| } | |||
| float raw_value = value; | |||
| // Assuming 1V/Octave and a scale repeating every octave, note_integral | |||
| // will store the octave number, and note_fractional the fractional | |||
| // pitch class. | |||
| const float note = value * base_interval_reciprocal_; | |||
| MAKE_INTEGRAL_FRACTIONAL(note); | |||
| if (value < 0.0f) { | |||
| note_integral -= 1; | |||
| note_fractional += 1.0f; | |||
| } | |||
| // For amount ranging between 0 and 0.25, do not remove notes from the scale | |||
| // just crossfade from the unquantized output to the quantized output. | |||
| const float scaled_amount = amount < 0.25f ? 0.0f : (amount - 0.25f) * 1.333f; | |||
| distribution_.Init(); | |||
| for (int i = 0; i < num_cells_ - 1; ++i) { | |||
| distribution_.AddToken(i, cells_[i].scaled_width(scaled_amount)); | |||
| } | |||
| distribution_.NoMoreTokens(); | |||
| Distribution::Result r = distribution_.Sample(note_fractional); | |||
| float quantized_value = cells_[r.token_id].center; | |||
| float offset = static_cast<float>(note_integral) * base_interval_; | |||
| quantized_value += offset; | |||
| r.start *= base_interval_; | |||
| r.start += offset; | |||
| if (amount < 0.25f) { | |||
| amount *= 4.0f; | |||
| float x; | |||
| if (r.token_id == 0) { | |||
| x = r.fraction - 1.0f; | |||
| } else if (r.token_id == num_cells_ - 1) { | |||
| x = -r.fraction; | |||
| } else { | |||
| x = 2.0f * (fabs(r.fraction - 0.5f) - 0.5f); | |||
| } | |||
| const float slope = amount / (1.01f - amount); | |||
| const float y = max(x * slope + 1.0f, 0.0f); | |||
| quantized_value -= y * (quantized_value - raw_value); | |||
| } | |||
| return quantized_value; | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,75 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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 voltages by sampling from a discrete distribution. | |||
| #ifndef MARBLES_RANDOM_DISCRETE_DISTRIBUTION_QUANTIZER_H_ | |||
| #define MARBLES_RANDOM_DISCRETE_DISTRIBUTION_QUANTIZER_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/random/distributions.h" | |||
| #include "marbles/random/quantizer.h" | |||
| namespace marbles { | |||
| class DiscreteDistributionQuantizer { | |||
| public: | |||
| typedef DiscreteDistribution<kMaxDegrees> Distribution; | |||
| struct Cell { | |||
| float center; | |||
| float width; | |||
| float weight; | |||
| inline float scaled_width(float amount) { | |||
| float w = 8.0f * (weight - amount) + 0.5f; | |||
| CONSTRAIN(w, 0.0f, 1.0f); | |||
| return w * width; | |||
| } | |||
| }; | |||
| DiscreteDistributionQuantizer() { } | |||
| ~DiscreteDistributionQuantizer() { } | |||
| void Init(const Scale& scale); | |||
| float Process(float value, float amount); | |||
| private: | |||
| float base_interval_; | |||
| float base_interval_reciprocal_; | |||
| int num_cells_; | |||
| Cell cells_[kMaxDegrees + 1]; | |||
| Distribution distribution_; | |||
| DISALLOW_COPY_AND_ASSIGN(DiscreteDistributionQuantizer); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_DISCRETE_DISTRIBUTION_QUANTIZER_H_ | |||
| @@ -0,0 +1,182 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Generates samples from various kinds of random distributions. | |||
| #ifndef MARBLES_RANDOM_DISTRIBUTIONS_H_ | |||
| #define MARBLES_RANDOM_DISTRIBUTIONS_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <algorithm> | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "marbles/resources.h" | |||
| namespace marbles { | |||
| const size_t kNumBiasValues = 5; | |||
| const size_t kNumRangeValues = 9; | |||
| const float kIcdfTableSize = 128.0f; | |||
| // Generates samples from beta distribution, from uniformly distributed samples. | |||
| // For higher throughput, uses pre-computed tables of inverse cdfs. | |||
| inline float BetaDistributionSample(float uniform, float spread, float bias) { | |||
| // Tables are pre-computed only for bias <= 0.5. For values above 0.5, | |||
| // symmetry is used. | |||
| bool flip_result = bias > 0.5f; | |||
| if (flip_result) { | |||
| uniform = 1.0f - uniform; | |||
| bias = 1.0f - bias; | |||
| } | |||
| bias *= (static_cast<float>(kNumBiasValues) - 1.0f) * 2.0f; | |||
| spread *= (static_cast<float>(kNumRangeValues) - 1.0f); | |||
| MAKE_INTEGRAL_FRACTIONAL(bias); | |||
| MAKE_INTEGRAL_FRACTIONAL(spread); | |||
| size_t cell = bias_integral * (kNumRangeValues + 1) + spread_integral; | |||
| // Lower 5% and 95% percentiles use a different table with higher resolution. | |||
| size_t offset = 0; | |||
| if (uniform <= 0.05f) { | |||
| offset = kIcdfTableSize + 1; | |||
| uniform *= 20.0f; | |||
| } else if (uniform >= 0.95f) { | |||
| offset = 2 * (kIcdfTableSize + 1); | |||
| uniform = (uniform - 0.95f) * 20.0f; | |||
| } | |||
| float x1y1 = stmlib::Interpolate( | |||
| distributions_table[cell] + offset, | |||
| uniform, | |||
| kIcdfTableSize); | |||
| float x2y1 = stmlib::Interpolate( | |||
| distributions_table[cell + 1] + offset, | |||
| uniform, | |||
| kIcdfTableSize); | |||
| float x1y2 = stmlib::Interpolate( | |||
| distributions_table[cell + kNumRangeValues + 1] + offset, | |||
| uniform, | |||
| kIcdfTableSize); | |||
| float x2y2 = stmlib::Interpolate( | |||
| distributions_table[cell + kNumRangeValues + 2] + offset, | |||
| uniform, | |||
| kIcdfTableSize); | |||
| float y1 = x1y1 + (x2y1 - x1y1) * spread_fractional; | |||
| float y2 = x1y2 + (x2y2 - x1y2) * spread_fractional; | |||
| float y = y1 + (y2 - y1) * bias_fractional; | |||
| if (flip_result) { | |||
| y = 1.0f - y; | |||
| } | |||
| return y; | |||
| } | |||
| // Pre-computed beta(3, 3) with a fatter tail. | |||
| inline float FastBetaDistributionSample(float uniform) { | |||
| return stmlib::Interpolate(dist_icdf_4_3, uniform, kIcdfTableSize); | |||
| } | |||
| // Draws samples from a discrete distribution. Used for the quantizer. | |||
| // Example: | |||
| // * 1 with probability 0.2 | |||
| // * 20 with probability 0.7 | |||
| // * 666 with probability 0.1 | |||
| // | |||
| // DiscreteDistribution d; | |||
| // d.Init(); | |||
| // d.AddToken(1, 0.2); | |||
| // d.AddToken(20, 0.7); | |||
| // d.AddToken(666, 0.1); | |||
| // d.NoMoreTokens(); | |||
| // Result r = d.Sample(u); | |||
| // cout << r.token_id; | |||
| // | |||
| // Weights do not have to add to 1.0f - the class handles normalization. | |||
| // | |||
| template<size_t size> | |||
| class DiscreteDistribution { | |||
| public: | |||
| DiscreteDistribution() { } | |||
| ~DiscreteDistribution() { } | |||
| void Init() { | |||
| sum_ = 0.0f; | |||
| num_tokens_ = 1; | |||
| cdf_[0] = 0.0f; | |||
| token_ids_[0] = 0; | |||
| } | |||
| void AddToken(int token_id, float weight) { | |||
| if (weight <= 0.0f) { | |||
| return; | |||
| } | |||
| sum_ += weight; | |||
| token_ids_[num_tokens_] = token_id; | |||
| cdf_[num_tokens_] = sum_; | |||
| ++num_tokens_; | |||
| } | |||
| void NoMoreTokens() { | |||
| token_ids_[num_tokens_] = token_ids_[num_tokens_ - 1]; | |||
| cdf_[num_tokens_] = sum_ + 1.0f; | |||
| } | |||
| struct Result { | |||
| int token_id; | |||
| float fraction; | |||
| float start; | |||
| float width; | |||
| }; | |||
| inline Result Sample(float u) const { | |||
| Result r; | |||
| u *= sum_; | |||
| int n = std::upper_bound(&cdf_[1], &cdf_[num_tokens_ + 1], u) - &cdf_[0]; | |||
| float norm = 1.0f / sum_; | |||
| r.token_id = token_ids_[n]; | |||
| r.width = (cdf_[n] - cdf_[n - 1]) * norm; | |||
| r.start = (cdf_[n - 1]) * norm; | |||
| r.fraction = (u - cdf_[n - 1]) / (cdf_[n] - cdf_[n - 1]); | |||
| return r; | |||
| } | |||
| float sum_; | |||
| float cdf_[size + 2]; | |||
| int token_ids_[size + 2]; | |||
| int num_tokens_; | |||
| DISALLOW_COPY_AND_ASSIGN(DiscreteDistribution); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_DISTRIBUTIONS_H_ | |||
| @@ -0,0 +1,88 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Lag processor for the STEPS control. | |||
| #include "marbles/random/lag_processor.h" | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/dsp/units.h" | |||
| #include "marbles/resources.h" | |||
| namespace marbles { | |||
| using namespace stmlib; | |||
| void LagProcessor::Init() { | |||
| ramp_start_ = 0.0f; | |||
| ramp_value_ = 0.0f; | |||
| lp_state_ = 0.0f; | |||
| previous_phase_ = 0.0f; | |||
| } | |||
| float LagProcessor::Process(float value, float smoothness, float phase) { | |||
| float frequency = phase - previous_phase_; | |||
| if (frequency < 0.0f) { | |||
| frequency += 1.0f; | |||
| } | |||
| previous_phase_ = phase; | |||
| // The frequency of the portamento/glide LP filter follows an exponential | |||
| // scale, with a minimum frequency corresponding to half the clock pulse | |||
| // frequency (giving a roughly linear glide), and a maximum value 7 octaves | |||
| // above. | |||
| // | |||
| // When smoothness approaches 0, the response curve is tweaked to give | |||
| // immediate voltage changes, without any lag. | |||
| frequency *= 0.25f; | |||
| frequency *= SemitonesToRatio(84.0f * (1.0f - smoothness)); | |||
| if (frequency >= 1.0f) { | |||
| frequency = 1.0f; | |||
| } | |||
| if (smoothness <= 0.05f) { | |||
| frequency += 20.f * (0.05f - smoothness) * (1.0f - frequency); | |||
| } | |||
| ONE_POLE(lp_state_, value, frequency); | |||
| // The final output is a crossfade between a variable shape interpolation and | |||
| // the low-pass glide/lag. | |||
| float interp_amount = (smoothness - 0.6f) * 5.0f; | |||
| CONSTRAIN(interp_amount, 0.0f, 1.0f); | |||
| float interp_linearity = (1.0f - smoothness) * 5.0f; | |||
| CONSTRAIN(interp_linearity, 0.0f, 1.0f); | |||
| float warped_phase = Interpolate(lut_raised_cosine, phase, 256.0f); | |||
| float interp_phase = Crossfade(warped_phase, phase, interp_linearity); | |||
| float interp = Crossfade(ramp_start_, value, interp_phase); | |||
| ramp_value_ = interp; | |||
| return Crossfade(lp_state_, interp, interp_amount); | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,61 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Lag processor for the STEPS control. | |||
| #ifndef MARBLES_RANDOM_LAG_PROCESSOR_H_ | |||
| #define MARBLES_RANDOM_LAG_PROCESSOR_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <cstdio> | |||
| namespace marbles { | |||
| class LagProcessor { | |||
| public: | |||
| LagProcessor() { } | |||
| ~LagProcessor() { } | |||
| void Init(); | |||
| inline void ResetRamp() { | |||
| ramp_start_ = ramp_value_; | |||
| } | |||
| float Process(float value, float smoothness, float phase); | |||
| private: | |||
| float ramp_start_; | |||
| float ramp_value_; | |||
| float lp_state_; | |||
| float previous_phase_; | |||
| DISALLOW_COPY_AND_ASSIGN(LagProcessor); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_LAG_PROCESSOR_H_ | |||
| @@ -0,0 +1,145 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Random generation channel. | |||
| #include "marbles/random/output_channel.h" | |||
| #include "marbles/random/distributions.h" | |||
| #include "marbles/random/random_sequence.h" | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/dsp/parameter_interpolator.h" | |||
| #include "stmlib/utils/random.h" | |||
| namespace marbles { | |||
| using namespace stmlib; | |||
| const size_t kNumReacquisitions = 20; // 6.4 samples per millisecond | |||
| void OutputChannel::Init() { | |||
| spread_ = 0.5f; | |||
| bias_ = 0.5f; | |||
| steps_ = 0.5f; | |||
| scale_index_ = 0; | |||
| register_mode_ = false; | |||
| register_value_ = 0.0f; | |||
| register_transposition_ = 0.0f; | |||
| previous_steps_ = 0.0f; | |||
| previous_phase_ = 0.0f; | |||
| reacquisition_counter_ = 0; | |||
| previous_voltage_ = 0.0f; | |||
| voltage_ = 0.0f; | |||
| quantized_voltage_ = 0.0f; | |||
| scale_offset_ = ScaleOffset(10.0f, -5.0f); | |||
| lag_processor_.Init(); | |||
| Scale scale; | |||
| scale.Init(); | |||
| for (int i = 0; i < 6; ++i) { | |||
| quantizer_[i].Init(scale); | |||
| } | |||
| } | |||
| float OutputChannel::GenerateNewVoltage(RandomSequence* random_sequence) { | |||
| float u = random_sequence->NextValue(register_mode_, register_value_); | |||
| if (register_mode_) { | |||
| return 10.0f * (u - 0.5f) + register_transposition_; | |||
| } else { | |||
| float degenerate_amount = 1.25f - spread_ * 25.0f; | |||
| float bernoulli_amount = spread_ * 25.0f - 23.75f; | |||
| CONSTRAIN(degenerate_amount, 0.0f, 1.0f); | |||
| CONSTRAIN(bernoulli_amount, 0.0f, 1.0f); | |||
| float value = BetaDistributionSample(u, spread_, bias_); | |||
| float bernoulli_value = u >= (1.0f - bias_) ? 0.999999f : 0.0f; | |||
| value += degenerate_amount * (bias_ - value); | |||
| value += bernoulli_amount * (bernoulli_value - value); | |||
| return scale_offset_(value); | |||
| } | |||
| } | |||
| void OutputChannel::Process( | |||
| RandomSequence* random_sequence, | |||
| const float* phase, | |||
| float* output, | |||
| size_t size, | |||
| size_t stride) { | |||
| ParameterInterpolator steps_modulation( | |||
| &previous_steps_, steps_, size); | |||
| // This is a horrible hack that wouldn't be here if all the sequencers | |||
| // and MIDI/CV interfaces in this world didn't have *horrible* slew on | |||
| // their CV output (I'm looking at you KORG). | |||
| // Without this hack, the shift register gets its value as soon as the | |||
| // rising edge is observed on the GATE input. Problem: the CV input is | |||
| // probably still slewing up, so we acquire the wrong value in the shift | |||
| // register. What to do then? Over the next 2ms, we'll just track the CV | |||
| // input until it reaches its final value - which means that Marbles | |||
| // output will be slewed too. Another option would have been to wait 2ms | |||
| // between the rising edge and the actual acquisition, but we don't want | |||
| // to penalize people who use tighter sequencers. | |||
| if (reacquisition_counter_) { | |||
| --reacquisition_counter_; | |||
| float u = random_sequence->RewriteValue(register_value_); | |||
| voltage_ = 10.0f * (u - 0.5f) + register_transposition_; | |||
| quantized_voltage_ = Quantize(voltage_, 2.0f * steps_ - 1.0f); | |||
| } | |||
| while (size--) { | |||
| const float steps = steps_modulation.Next(); | |||
| if (*phase < previous_phase_) { | |||
| previous_voltage_ = voltage_; | |||
| voltage_ = GenerateNewVoltage(random_sequence); | |||
| lag_processor_.ResetRamp(); | |||
| quantized_voltage_ = Quantize(voltage_, 2.0f * steps - 1.0f); | |||
| if (register_mode_) { | |||
| reacquisition_counter_ = kNumReacquisitions; | |||
| } | |||
| } | |||
| if (steps >= 0.5f) { | |||
| *output = quantized_voltage_; | |||
| } else { | |||
| const float smoothness = 1.0f - 2.0f * steps; | |||
| *output = lag_processor_.Process(voltage_, smoothness, *phase); | |||
| } | |||
| output += stride; | |||
| previous_phase_ = *phase++; | |||
| } | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,139 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Random generation channel. | |||
| #ifndef MARBLES_RANDOM_OUTPUT_CHANNEL_H_ | |||
| #define MARBLES_RANDOM_OUTPUT_CHANNEL_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/random/lag_processor.h" | |||
| #include "marbles/random/quantizer.h" | |||
| namespace marbles { | |||
| class RandomSequence; | |||
| struct ScaleOffset { | |||
| ScaleOffset(float s, float o) { | |||
| scale = s; | |||
| offset = o; | |||
| } | |||
| ScaleOffset() { scale = 1.0f; offset = 0.0f; } | |||
| float scale; | |||
| float offset; | |||
| inline float operator()(float x) { return x * scale + offset; } | |||
| }; | |||
| class OutputChannel { | |||
| public: | |||
| OutputChannel() { } | |||
| ~OutputChannel() { } | |||
| void Init(); | |||
| void LoadScale(int i, const Scale& scale) { | |||
| quantizer_[i].Init(scale); | |||
| } | |||
| void Process( | |||
| RandomSequence* random_sequence, | |||
| const float* phase, | |||
| float* output, | |||
| size_t size, | |||
| size_t stride); | |||
| inline void set_spread(float spread) { | |||
| spread_ = spread; | |||
| } | |||
| inline void set_bias(float bias) { | |||
| bias_ = bias; | |||
| } | |||
| inline void set_scale_index(int i) { | |||
| scale_index_ = i; | |||
| } | |||
| inline void set_steps(float steps) { | |||
| steps_ = steps; | |||
| } | |||
| inline void set_register_mode(bool register_mode) { | |||
| register_mode_ = register_mode; | |||
| } | |||
| inline void set_register_value(float register_value) { | |||
| register_value_ = register_value; | |||
| } | |||
| inline void set_register_transposition(float register_transposition) { | |||
| register_transposition_ = register_transposition; | |||
| } | |||
| inline void set_scale_offset(const ScaleOffset& scale_offset) { | |||
| scale_offset_ = scale_offset; | |||
| } | |||
| inline float Quantize(float voltage, float amount) { | |||
| return quantizer_[scale_index_].Process(voltage, amount, false); | |||
| } | |||
| private: | |||
| float GenerateNewVoltage(RandomSequence* random_sequence); | |||
| float spread_; | |||
| float bias_; | |||
| float steps_; | |||
| int scale_index_; | |||
| bool register_mode_; | |||
| float register_value_; | |||
| float register_transposition_; | |||
| float previous_steps_; | |||
| float previous_phase_; | |||
| uint32_t reacquisition_counter_; | |||
| float previous_voltage_; | |||
| float voltage_; | |||
| float quantized_voltage_; | |||
| ScaleOffset scale_offset_; | |||
| LagProcessor lag_processor_; | |||
| Quantizer quantizer_[6]; | |||
| DISALLOW_COPY_AND_ASSIGN(OutputChannel); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_OUTPUT_CHANNEL_H_ | |||
| @@ -0,0 +1,138 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Variable resolution quantizer. | |||
| #include "marbles/random/quantizer.h" | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include <cmath> | |||
| #include <algorithm> | |||
| namespace marbles { | |||
| using namespace std; | |||
| void Quantizer::Init(const Scale& scale) { | |||
| int n = scale.num_degrees; | |||
| // We don't want garbage scale data here... | |||
| if (!n || n > kMaxDegrees || scale.base_interval == 0.0f) { | |||
| return; | |||
| } | |||
| num_degrees_ = n; | |||
| base_interval_ = scale.base_interval; | |||
| base_interval_reciprocal_ = 1.0f / scale.base_interval; | |||
| uint8_t second_largest_threshold = 0; | |||
| for (int i = 0; i < n; ++i) { | |||
| voltage_[i] = scale.degree[i].voltage; | |||
| if (scale.degree[i].weight != 255 && \ | |||
| scale.degree[i].weight >= second_largest_threshold) { | |||
| second_largest_threshold = scale.degree[i].weight; | |||
| } | |||
| } | |||
| uint8_t thresholds_[kNumThresholds] = { | |||
| 0, 16, 32, 64, 128, 192, 255 | |||
| }; | |||
| if (second_largest_threshold > 192) { | |||
| // Be more selective to only include the notes at rank 1 and 2 at | |||
| // the last but one position. | |||
| thresholds_[kNumThresholds - 2] = second_largest_threshold; | |||
| } | |||
| for (int t = 0; t < kNumThresholds; ++t) { | |||
| uint16_t bitmask = 0; | |||
| uint8_t first = 0xff; | |||
| uint8_t last = 0; | |||
| for (int i = 0; i < n; ++i) { | |||
| if (scale.degree[i].weight >= thresholds_[t]) { | |||
| bitmask |= 1 << i; | |||
| if (first == 0xff) first = i; | |||
| last = i; | |||
| } | |||
| } | |||
| level_[t].bitmask = bitmask; | |||
| level_[t].first = first; | |||
| level_[t].last = last; | |||
| } | |||
| level_quantizer_.Init(); | |||
| fill(&feedback_[0], &feedback_[kNumThresholds], 0.0f); | |||
| } | |||
| float Quantizer::Process(float value, float amount, bool hysteresis) { | |||
| int level = level_quantizer_.Process(amount, kNumThresholds + 1); | |||
| float quantized_voltage = value; | |||
| if (level > 0) { | |||
| level -= 1; | |||
| float raw_value = value; | |||
| if (hysteresis) { | |||
| value += feedback_[level]; | |||
| } | |||
| const float note = value * base_interval_reciprocal_; | |||
| MAKE_INTEGRAL_FRACTIONAL(note); | |||
| if (value < 0.0f) { | |||
| note_integral -= 1; | |||
| note_fractional += 1.0f; | |||
| } | |||
| note_fractional *= base_interval_; | |||
| // Search for the tightest upper/lower bound in the set of available | |||
| // voltages. stl::upper_bound / stl::lower_bound wouldn't work here | |||
| // because some entries are masked. | |||
| Level l = level_[level]; | |||
| float a = voltage_[l.last] - base_interval_; | |||
| float b = voltage_[l.first] + base_interval_; | |||
| uint16_t bitmask = l.bitmask; | |||
| for (int i = 0; i < num_degrees_; ++i) { | |||
| if (bitmask & 1) { | |||
| float v = voltage_[i]; | |||
| if (note_fractional > v) { | |||
| a = v; | |||
| } else { | |||
| b = v; | |||
| break; | |||
| } | |||
| } | |||
| bitmask >>= 1; | |||
| } | |||
| quantized_voltage = note_fractional < (a + b) * 0.5f ? a : b; | |||
| quantized_voltage += static_cast<float>(note_integral) * base_interval_; | |||
| feedback_[level] = (quantized_voltage - raw_value) * 0.25f; | |||
| } | |||
| return quantized_voltage; | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,122 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Variable resolution quantizer. | |||
| #ifndef MARBLES_RANDOM_QUANTIZER_H_ | |||
| #define MARBLES_RANDOM_QUANTIZER_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "stmlib/dsp/hysteresis_quantizer.h" | |||
| #include "marbles/random/distributions.h" | |||
| #include "marbles/random/quantizer.h" | |||
| namespace marbles { | |||
| const int kMaxDegrees = 16; | |||
| const int kNumThresholds = 7; | |||
| struct Degree { | |||
| float voltage; | |||
| uint8_t weight; | |||
| }; | |||
| struct Scale { | |||
| float base_interval; | |||
| int num_degrees; | |||
| Degree degree[kMaxDegrees]; | |||
| inline float cell_voltage(int i) const { | |||
| float transposition = static_cast<float>(i / num_degrees) * base_interval; | |||
| return degree[i % num_degrees].voltage + transposition; | |||
| } | |||
| void Init() { | |||
| base_interval = 1.0f; | |||
| num_degrees = 1; | |||
| degree[0].voltage = 0.0f; | |||
| degree[0].weight = 0.0f; | |||
| } | |||
| void InitMajor() { | |||
| const uint8_t major_scale_weights[] = { | |||
| 255, 16, 128, 16, 192, 64, 8, 224, 16, 96, 32, 160, | |||
| }; | |||
| base_interval = 1.0f; | |||
| num_degrees = 12; | |||
| for (size_t i = 0; i < 12; ++i) { | |||
| degree[i].voltage = static_cast<float>(i) * 0.0833333333f; | |||
| degree[i].weight = major_scale_weights[i]; | |||
| } | |||
| } | |||
| void InitTenth() { | |||
| const uint8_t major_scale_weights[] = { | |||
| 255, 255, 255, 255, 255, 255, 255, 255, 255, 25 | |||
| }; | |||
| base_interval = 1.0f; | |||
| num_degrees = 10; | |||
| for (size_t i = 0; i < 10; ++i) { | |||
| degree[i].voltage = static_cast<float>(i) * 0.1f; | |||
| degree[i].weight = major_scale_weights[i]; | |||
| } | |||
| } | |||
| }; | |||
| class Quantizer { | |||
| public: | |||
| Quantizer() { } | |||
| ~Quantizer() { } | |||
| void Init(const Scale& scale); | |||
| float Process(float value, float amount, bool hysteresis); | |||
| private: | |||
| struct Level { | |||
| uint16_t bitmask; // bitmask of active degrees. | |||
| uint8_t first; // index of the first active degree. | |||
| uint8_t last; // index of the last active degree. | |||
| }; | |||
| float voltage_[kMaxDegrees]; | |||
| Level level_[kNumThresholds]; | |||
| float feedback_[kNumThresholds]; | |||
| float base_interval_; | |||
| float base_interval_reciprocal_; | |||
| int num_degrees_; | |||
| stmlib::HysteresisQuantizer level_quantizer_; | |||
| DISALLOW_COPY_AND_ASSIGN(Quantizer); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_QUANTIZER_H_ | |||
| @@ -0,0 +1,65 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Pseudo-random generator used as a fallback when we need more random values | |||
| // than available in the hardware RNG buffer. | |||
| #ifndef MARBLES_RANDOM_RANDOM_GENERATOR_H_ | |||
| #define MARBLES_RANDOM_RANDOM_GENERATOR_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "stmlib/utils/ring_buffer.h" | |||
| namespace marbles { | |||
| class RandomGenerator { | |||
| public: | |||
| RandomGenerator() { } | |||
| ~RandomGenerator() { } | |||
| inline void Init(uint32_t seed) { | |||
| state_ = seed; | |||
| } | |||
| inline void Mix(uint32_t word) { | |||
| // state_ ^= word; | |||
| } | |||
| inline uint32_t GetWord() { | |||
| state_ = state_ * 1664525L + 1013904223L; | |||
| return state_; | |||
| } | |||
| private: | |||
| uint32_t state_; | |||
| DISALLOW_COPY_AND_ASSIGN(RandomGenerator); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_RANDOM_GENERATOR_H_ | |||
| @@ -0,0 +1,228 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Sequence of random values. | |||
| #ifndef MARBLES_RANDOM_RANDOM_SEQUENCE_H_ | |||
| #define MARBLES_RANDOM_RANDOM_SEQUENCE_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/random/random_stream.h" | |||
| #include <algorithm> | |||
| namespace marbles { | |||
| const int kDejaVuBufferSize = 16; | |||
| const int kHistoryBufferSize = 16; | |||
| const float kMaxUint32 = 4294967296.0f; | |||
| class RandomSequence { | |||
| public: | |||
| RandomSequence() { } | |||
| ~RandomSequence() { } | |||
| inline void Init(RandomStream* random_stream) { | |||
| random_stream_ = random_stream; | |||
| for (int i = 0; i < kDejaVuBufferSize; ++i) { | |||
| loop_[i] = random_stream_->GetFloat(); | |||
| } | |||
| std::fill(&history_[0], &history_[kHistoryBufferSize], 0.0f); | |||
| loop_write_head_ = 0; | |||
| length_ = 8; | |||
| step_ = 0; | |||
| record_head_ = 0; | |||
| replay_head_ = -1; | |||
| replay_start_ = 0; | |||
| deja_vu_ = 0.0f; | |||
| replay_hash_ = replay_shift_ = 0; | |||
| redo_read_ptr_ = &loop_[0]; | |||
| redo_write_ptr_ = NULL; | |||
| redo_write_history_ptr_ = NULL; | |||
| } | |||
| inline void Record() { | |||
| replay_start_ = record_head_; | |||
| replay_head_ = -1; | |||
| } | |||
| inline void ReplayPseudoRandom(uint32_t hash) { | |||
| replay_head_ = replay_start_; | |||
| replay_hash_ = hash; | |||
| replay_shift_ = 0; | |||
| } | |||
| inline void ReplayShifted(uint32_t shift) { | |||
| replay_head_ = replay_start_; | |||
| replay_hash_ = 0; | |||
| replay_shift_ = shift; | |||
| } | |||
| inline float GetReplayValue() const { | |||
| uint32_t h = (replay_head_ - 1 - replay_shift_ + \ | |||
| 2 * kHistoryBufferSize) % kHistoryBufferSize; | |||
| if (!replay_hash_) { | |||
| return history_[h]; | |||
| } else { | |||
| uint32_t word = static_cast<float>(history_[h] * kMaxUint32); | |||
| word = (word ^ replay_hash_) * 1664525L + 1013904223L; | |||
| return static_cast<float>(word) / kMaxUint32; | |||
| } | |||
| } | |||
| inline float RewriteValue(float value) { | |||
| // RewriteValue(x) returns what the most recent call to NextValue would have | |||
| // returned if its second argument were x instead. This is used to "rewrite | |||
| // history" when the module acquires data from an external source (ASR, | |||
| // randomizer or quantizer mode). | |||
| if (replay_head_ >= 0) { | |||
| return GetReplayValue(); | |||
| } | |||
| if (redo_write_ptr_) { | |||
| *redo_write_ptr_ = 1.0f + value; | |||
| } | |||
| float result = *redo_read_ptr_; | |||
| if (result >= 1.0f) { | |||
| result -= 1.0f; | |||
| } else { | |||
| result = 0.5f; | |||
| } | |||
| if (redo_write_history_ptr_) { | |||
| *redo_write_history_ptr_ = result; | |||
| } | |||
| return result; | |||
| } | |||
| inline float NextValue(bool deterministic, float value) { | |||
| if (replay_head_ >= 0) { | |||
| replay_head_ = (replay_head_ + 1) % kHistoryBufferSize; | |||
| return GetReplayValue(); | |||
| } | |||
| const float p_sqrt = 2.0f * deja_vu_ - 1.0f; | |||
| const float p = p_sqrt * p_sqrt; | |||
| if (random_stream_->GetFloat() <= p && deja_vu_ <= 0.5f) { | |||
| // Generate a new value and put it at the end of the loop. | |||
| redo_write_ptr_ = &loop_[loop_write_head_]; | |||
| *redo_write_ptr_ = deterministic | |||
| ? 1.0f + value | |||
| : random_stream_->GetFloat(); | |||
| loop_write_head_ = (loop_write_head_ + 1) % kDejaVuBufferSize; | |||
| step_ = length_ - 1; | |||
| } else { | |||
| // Do not generate a new value, just replay the loop or jump randomly. | |||
| // through it. | |||
| redo_write_ptr_ = NULL; | |||
| if (random_stream_->GetFloat() <= p) { | |||
| step_ = static_cast<int>( | |||
| random_stream_->GetFloat() * static_cast<float>(length_)); | |||
| } else { | |||
| step_ = step_ + 1; | |||
| if (step_ >= length_) { | |||
| step_ = 0; | |||
| } | |||
| } | |||
| } | |||
| uint32_t i = loop_write_head_ + kDejaVuBufferSize - length_ + step_; | |||
| redo_read_ptr_ = &loop_[i % kDejaVuBufferSize]; | |||
| float result = *redo_read_ptr_; | |||
| if (result >= 1.0f) { | |||
| result -= 1.0f; | |||
| } else if (deterministic) { | |||
| // We ask for a deterministic value (shift register), but the loop | |||
| // contain random values. return 0.5f in this case! | |||
| result = 0.5f; | |||
| } | |||
| redo_write_history_ptr_ = &history_[record_head_]; | |||
| *redo_write_history_ptr_ = result; | |||
| record_head_ = (record_head_ + 1) % kHistoryBufferSize; | |||
| return result; | |||
| } | |||
| inline void NextVector(float* destination, size_t size) { | |||
| float seed = NextValue(false, 0.0f); | |||
| uint32_t word = static_cast<float>(seed * kMaxUint32); | |||
| while (size--) { | |||
| *destination++ = static_cast<float>(word) / kMaxUint32; | |||
| word = word * 1664525L + 1013904223L; | |||
| } | |||
| } | |||
| inline void set_deja_vu(float deja_vu) { | |||
| deja_vu_ = deja_vu; | |||
| } | |||
| inline void set_length(int length) { | |||
| if (length < 1 || length > kDejaVuBufferSize) { | |||
| return; | |||
| } | |||
| length_ = length; | |||
| step_ = step_ % length; | |||
| } | |||
| inline float deja_vu() const { | |||
| return deja_vu_; | |||
| } | |||
| inline int length() const { | |||
| return length_; | |||
| } | |||
| private: | |||
| RandomStream* random_stream_; | |||
| float loop_[kDejaVuBufferSize]; | |||
| float history_[kHistoryBufferSize]; | |||
| int loop_write_head_; | |||
| int length_; | |||
| int step_; | |||
| // Allows to go back in the past and get the same results again from NextValue | |||
| // calls. Allows the 3 X channels to be locked to the same random loop. | |||
| int record_head_; | |||
| int replay_head_; | |||
| int replay_start_; | |||
| uint32_t replay_hash_; | |||
| uint32_t replay_shift_; | |||
| float deja_vu_; | |||
| float* redo_read_ptr_; | |||
| float* redo_write_ptr_; | |||
| float* redo_write_history_ptr_; | |||
| DISALLOW_COPY_AND_ASSIGN(RandomSequence); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_RANDOM_SEQUENCE_H_ | |||
| @@ -0,0 +1,82 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Stream of random values, filled from a hardware RNG, with a fallback | |||
| // mechanism. | |||
| #ifndef MARBLES_RANDOM_RANDOM_STREAM_H_ | |||
| #define MARBLES_RANDOM_RANDOM_STREAM_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "stmlib/utils/ring_buffer.h" | |||
| #include "marbles/random/random_generator.h" | |||
| namespace marbles { | |||
| class RandomStream { | |||
| public: | |||
| RandomStream() { } | |||
| ~RandomStream() { } | |||
| inline void Init(RandomGenerator* fallback_generator) { | |||
| fallback_generator_ = fallback_generator; | |||
| buffer_.Init(); | |||
| } | |||
| inline void Write(uint32_t value) { | |||
| // buffer_.Swallow(1); | |||
| // buffer_.Overwrite(value); | |||
| if (buffer_.writable()) { | |||
| buffer_.Overwrite(value); | |||
| } | |||
| fallback_generator_->Mix(value); | |||
| } | |||
| inline uint32_t GetWord() { | |||
| if (buffer_.readable()) { | |||
| return buffer_.ImmediateRead(); | |||
| } else { | |||
| return fallback_generator_->GetWord(); | |||
| } | |||
| } | |||
| inline float GetFloat() { | |||
| uint32_t word = GetWord(); | |||
| return static_cast<float>(word) / 4294967296.0f; | |||
| } | |||
| private: | |||
| stmlib::RingBuffer<uint32_t, 128> buffer_; | |||
| RandomGenerator* fallback_generator_; | |||
| DISALLOW_COPY_AND_ASSIGN(RandomStream); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_RANDOM_STREAM_H_ | |||
| @@ -0,0 +1,414 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Generator for the T outputs. | |||
| #include "marbles/random/t_generator.h" | |||
| #include <algorithm> | |||
| #include "stmlib/dsp/units.h" | |||
| #include "marbles/resources.h" | |||
| namespace marbles { | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| /* static */ | |||
| DividerPattern TGenerator::divider_patterns[kNumDividerPatterns] = { | |||
| { { { 1, 1 }, { 1, 1 } }, 1 }, | |||
| { { { 1, 1 }, { 2, 1 } }, 1 }, | |||
| { { { 1, 2 }, { 1, 1 } }, 2 }, | |||
| { { { 1, 1 }, { 4, 1 } }, 1 }, | |||
| { { { 1, 2 }, { 2, 1 } }, 2 }, | |||
| { { { 1, 1 }, { 3, 2 } }, 2 }, | |||
| { { { 1, 4 }, { 4, 1 } }, 4 }, | |||
| { { { 1, 4 }, { 2, 1 } }, 4 }, | |||
| { { { 1, 2 }, { 3, 2 } }, 2 }, | |||
| { { { 1, 1 }, { 8, 1 } }, 1 }, | |||
| { { { 1, 1 }, { 3, 1 } }, 1 }, | |||
| { { { 1, 3 }, { 1, 1 } }, 3 }, | |||
| { { { 1, 1 }, { 5, 4 } }, 4 }, | |||
| { { { 1, 2 }, { 5, 4 } }, 4 }, | |||
| { { { 1, 1 }, { 6, 1 } }, 1 }, | |||
| { { { 1, 3 }, { 2, 1 } }, 3 }, | |||
| { { { 1, 1 }, { 16, 1 } }, 1 }, | |||
| }; | |||
| /* static */ | |||
| DividerPattern TGenerator::fixed_divider_patterns[kNumDividerPatterns] = { | |||
| { { { 8, 1 }, { 1, 8 } }, 8 }, | |||
| { { { 6, 1 }, { 1, 6 } }, 6 }, | |||
| { { { 4, 1 }, { 1, 4 } }, 4 }, | |||
| { { { 3, 1 }, { 1, 3 } }, 3 }, | |||
| { { { 2, 1 }, { 1, 2 } }, 2 }, | |||
| { { { 3, 2 }, { 2, 3 } }, 6 }, | |||
| { { { 4, 3 }, { 3, 4 } }, 12 }, | |||
| { { { 5, 4 }, { 4, 5 } }, 20 }, | |||
| { { { 1, 1 }, { 1, 1 } }, 1 }, | |||
| { { { 4, 5 }, { 5, 4 } }, 20 }, | |||
| { { { 3, 4 }, { 4, 3 } }, 12 }, | |||
| { { { 2, 2 }, { 3, 2 } }, 6 }, | |||
| { { { 1, 2 }, { 2, 1 } }, 2 }, | |||
| { { { 1, 3 }, { 3, 1 } }, 3 }, | |||
| { { { 1, 4 }, { 4, 1 } }, 4 }, | |||
| { { { 1, 6 }, { 6, 1 } }, 6 }, | |||
| { { { 1, 8 }, { 8, 1 } }, 8 }, | |||
| }; | |||
| /* static */ | |||
| Ratio TGenerator::input_divider_ratios[kNumInputDividerRatios] = { | |||
| { 1, 4 }, | |||
| { 1, 3 }, | |||
| { 1, 2 }, | |||
| { 2, 3 }, | |||
| { 1, 1 }, | |||
| { 3, 2 }, | |||
| { 2, 1 }, | |||
| { 3, 1 }, | |||
| { 4, 1 }, | |||
| }; | |||
| /* static */ | |||
| uint8_t TGenerator::drum_patterns[kNumDrumPatterns][kDrumPatternSize] = { | |||
| { 1, 0, 0, 0, 2, 0, 0, 0 }, | |||
| { 0, 0, 1, 0, 2, 0, 0, 0 }, | |||
| { 1, 0, 1, 0, 2, 0, 0, 0 }, | |||
| { 0, 0, 1, 0, 2, 0, 0, 2 }, | |||
| { 1, 0, 1, 0, 2, 0, 1, 0 }, | |||
| { 0, 2, 1, 0, 2, 0, 0, 2 }, | |||
| { 1, 0, 0, 0, 2, 0, 1, 0 }, | |||
| { 0, 2, 1, 0, 2, 0, 1, 2 }, | |||
| { 1, 0, 0, 1, 2, 0, 0, 0 }, | |||
| { 0, 2, 1, 1, 2, 0, 1, 2 }, | |||
| { 1, 0, 0, 1, 2, 0, 1, 0 }, | |||
| { 0, 2, 1, 1, 2, 2, 1, 2 }, | |||
| { 1, 0, 0, 1, 2, 0, 1, 2 }, | |||
| { 0, 2, 0, 1, 2, 0, 1, 2 }, | |||
| { 1, 0, 1, 1, 2, 0, 1, 2 }, | |||
| { 2, 0, 1, 2, 0, 1, 2, 0 }, | |||
| { 1, 2, 1, 1, 2, 0, 1, 2 }, | |||
| { 2, 0, 1, 2, 0, 1, 2, 2 } | |||
| }; | |||
| void TGenerator::Init(RandomStream* random_stream, float sr) { | |||
| one_hertz_ = 1.0f / static_cast<float>(sr); | |||
| model_ = T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI; | |||
| range_ = T_GENERATOR_RANGE_1X; | |||
| rate_ = 0.0f; | |||
| bias_ = 0.5f; | |||
| jitter_ = 0.0f; | |||
| pulse_width_mean_ = 0.0f; | |||
| pulse_width_std_ = 0.0f; | |||
| master_phase_ = 0.0f; | |||
| jitter_multiplier_ = 1.0f; | |||
| phase_difference_ = 0.0f; | |||
| previous_external_ramp_value_ = 0.0f; | |||
| divider_pattern_length_ = 0; | |||
| fill(&streak_counter_[0], &streak_counter_[kMarkovHistorySize], 0); | |||
| fill(&markov_history_[0], &markov_history_[kMarkovHistorySize], 0); | |||
| markov_history_ptr_ = 0; | |||
| drum_pattern_step_ = 0; | |||
| drum_pattern_index_ = 0; | |||
| sequence_.Init(random_stream); | |||
| ramp_divider_.Init(); | |||
| ramp_extractor_.Init(1000.0f / sr); | |||
| ramp_generator_.Init(); | |||
| for (size_t i = 0; i < kNumTChannels; ++i) { | |||
| slave_ramp_[i].Init(); | |||
| } | |||
| bias_quantizer_.Init(); | |||
| rate_quantizer_.Init(); | |||
| use_external_clock_ = false; | |||
| } | |||
| int TGenerator::GenerateComplementaryBernoulli(const RandomVector& x) { | |||
| int bitmask = 0; | |||
| for (size_t i = 0; i < kNumTChannels; ++i) { | |||
| if ((x.variables.u[i >> 1] > bias_) ^ (i & 1)) { | |||
| bitmask |= 1 << i; | |||
| } | |||
| } | |||
| return bitmask; | |||
| } | |||
| int TGenerator::GenerateIndependentBernoulli(const RandomVector& x) { | |||
| int bitmask = 0; | |||
| for (size_t i = 0; i < kNumTChannels; ++i) { | |||
| if ((x.variables.u[i] > bias_) ^ (i & 1)) { | |||
| bitmask |= 1 << i; | |||
| } | |||
| } | |||
| return bitmask; | |||
| } | |||
| int TGenerator::GenerateThreeStates(const RandomVector& x) { | |||
| int bitmask = 0; | |||
| float p_none = 0.75f - fabs(bias_ - 0.5f); | |||
| float threshold = p_none + (1.0f - p_none) * (0.25f + (bias_ * 0.5f)); | |||
| for (size_t i = 0; i < kNumTChannels; ++i) { | |||
| float u = x.variables.u[i >> 1]; | |||
| if (u > p_none && ((u > threshold) ^ (i & 1))) { | |||
| bitmask |= 1 << i; | |||
| } | |||
| } | |||
| return bitmask; | |||
| } | |||
| int TGenerator::GenerateDrums(const RandomVector& x) { | |||
| ++drum_pattern_step_; | |||
| if (drum_pattern_step_ >= kDrumPatternSize) { | |||
| drum_pattern_step_ = 0; | |||
| float u = x.variables.u[0] * 2.0f * fabs(bias_ - 0.5f); | |||
| drum_pattern_index_ = static_cast<int32_t>(kNumDrumPatterns * u); | |||
| if (bias_ <= 0.5f) { | |||
| drum_pattern_index_ -= drum_pattern_index_ % 2; | |||
| } | |||
| } | |||
| return drum_patterns[drum_pattern_index_][drum_pattern_step_]; | |||
| } | |||
| int TGenerator::GenerateMarkov(const RandomVector& x) { | |||
| int bitmask = 0; | |||
| float b = 1.5f * bias_ - 0.5f; | |||
| markov_history_[markov_history_ptr_] = 0; | |||
| const int32_t p = markov_history_ptr_; | |||
| for (size_t i = 0; i < kNumTChannels; ++i) { | |||
| int32_t mask = 1 << i; | |||
| // 4 rules: | |||
| // * We favor repeating what we played 8 ticks ago. | |||
| // * We do not favor pulses appearing on both channels. | |||
| // * We favor sparse patterns (no consecutive hits). | |||
| // * We favor patterns in which one channel "echoes" what the other | |||
| // channel played 4 ticks before. | |||
| bool periodic = markov_history_[(p + 8) % kMarkovHistorySize] & mask; | |||
| bool simultaneous = markov_history_[(p + 8) % kMarkovHistorySize] & ~mask; | |||
| bool dense = markov_history_[(p + 1) % kMarkovHistorySize] & mask; | |||
| bool alternate = markov_history_[(p + 4) % kMarkovHistorySize] & ~mask; | |||
| float logit = -1.5f; | |||
| logit += streak_counter_[i] > 24 ? 10.0f : 0.0f; | |||
| logit += 8.0f * fabs(b) * (periodic ? b : -b); | |||
| logit -= 2.0f * (simultaneous ? b : -b); | |||
| logit -= 1.0f * (dense ? b : 0.0f); | |||
| logit += 1.0f * (alternate ? b : 0.0f); | |||
| CONSTRAIN(logit, -10.0f, 10.0f); | |||
| float probability = lut_logit[static_cast<int>(logit * 12.8f + 128.0f)]; | |||
| bool state = x.variables.u[i] < probability; | |||
| if (sequence_.deja_vu() >= x.variables.p) { | |||
| state = markov_history_[(p + sequence_.length()) % kMarkovHistorySize] & mask; | |||
| } | |||
| if (state) { | |||
| bitmask |= mask; | |||
| streak_counter_[i] = 0; | |||
| } else { | |||
| ++streak_counter_[i]; | |||
| } | |||
| } | |||
| markov_history_[p] |= bitmask; | |||
| markov_history_ptr_ = (p + kMarkovHistorySize - 1) % kMarkovHistorySize; | |||
| return bitmask; | |||
| } | |||
| void TGenerator::ScheduleOutputPulses(const RandomVector& x, int bitmask) { | |||
| for (size_t i = 0; i < kNumTChannels; ++i) { | |||
| slave_ramp_[i].Init( | |||
| bitmask & 1, | |||
| RandomPulseWidth(i, x.variables.pulse_width[i]), | |||
| 0.5f); | |||
| bitmask >>= 1; | |||
| } | |||
| } | |||
| void TGenerator::ConfigureSlaveRamps(const RandomVector& x) { | |||
| switch (model_) { | |||
| // Generate a bitmask that will describe which outputs are active | |||
| // at this clock tick. Use this bitmask to actually schedule pulses on the | |||
| // outputs. | |||
| case T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI: | |||
| ScheduleOutputPulses(x, GenerateComplementaryBernoulli(x)); | |||
| break; | |||
| case T_GENERATOR_MODEL_INDEPENDENT_BERNOULLI: | |||
| ScheduleOutputPulses(x, GenerateIndependentBernoulli(x)); | |||
| break; | |||
| case T_GENERATOR_MODEL_THREE_STATES: | |||
| ScheduleOutputPulses(x, GenerateThreeStates(x)); | |||
| break; | |||
| case T_GENERATOR_MODEL_DRUMS: | |||
| ScheduleOutputPulses(x, GenerateDrums(x)); | |||
| break; | |||
| case T_GENERATOR_MODEL_MARKOV: | |||
| ScheduleOutputPulses(x, GenerateMarkov(x)); | |||
| break; | |||
| case T_GENERATOR_MODEL_CLUSTERS: | |||
| case T_GENERATOR_MODEL_DIVIDER: | |||
| --divider_pattern_length_; | |||
| if (divider_pattern_length_ <= 0) { | |||
| DividerPattern pattern; | |||
| if (model_ == T_GENERATOR_MODEL_DIVIDER) { | |||
| pattern = bias_quantizer_.Lookup( | |||
| fixed_divider_patterns, | |||
| bias_, | |||
| kNumDividerPatterns); | |||
| } else { | |||
| float strength = fabs(bias_ - 0.5f) * 2.0f; | |||
| float u = x.variables.u[0]; | |||
| u *= (u + strength * strength * (1.0f - u)); | |||
| u *= strength; | |||
| pattern = divider_patterns[static_cast<size_t>( | |||
| u * kNumDividerPatterns)]; | |||
| if (bias_ < 0.5f) { | |||
| for (size_t i = 0; i < kNumTChannels / 2; ++i) { | |||
| swap(pattern.ratios[i], pattern.ratios[kNumTChannels - 1 - i]); | |||
| } | |||
| } | |||
| } | |||
| for (size_t i = 0; i < kNumTChannels; ++i) { | |||
| slave_ramp_[i].Init( | |||
| pattern.length, | |||
| pattern.ratios[i], | |||
| RandomPulseWidth(i, x.variables.pulse_width[i])); | |||
| } | |||
| divider_pattern_length_ = pattern.length; | |||
| } | |||
| break; | |||
| } | |||
| } | |||
| void TGenerator::Process( | |||
| bool use_external_clock, | |||
| const GateFlags* external_clock, | |||
| Ramps ramps, | |||
| bool* gate, | |||
| size_t size) { | |||
| float internal_frequency; | |||
| if (use_external_clock) { | |||
| if (!use_external_clock_) { | |||
| ramp_extractor_.Reset(); | |||
| } | |||
| Ratio ratio = rate_quantizer_.Lookup( | |||
| input_divider_ratios, | |||
| 1.05f * rate_ / 96.0f + 0.5f, | |||
| kNumInputDividerRatios); | |||
| if (range_ == T_GENERATOR_RANGE_0_25X) { | |||
| ratio.q *= 4; | |||
| } else if (range_ == T_GENERATOR_RANGE_4X) { | |||
| ratio.p *= 4; | |||
| } | |||
| ratio.Simplify<2>(); | |||
| ramp_extractor_.Process(ratio, true, external_clock, ramps.external, size); | |||
| internal_frequency = 0.0f; | |||
| } else { | |||
| float rate = 2.0f; | |||
| if (range_ == T_GENERATOR_RANGE_4X) { | |||
| rate = 8.0f; | |||
| } else if (range_ == T_GENERATOR_RANGE_0_25X) { | |||
| rate = 0.5f; | |||
| } | |||
| internal_frequency = rate * one_hertz_ * SemitonesToRatio(rate_); | |||
| } | |||
| use_external_clock_ = use_external_clock; | |||
| while (size--) { | |||
| float frequency = use_external_clock | |||
| ? *ramps.external - previous_external_ramp_value_ | |||
| : internal_frequency; | |||
| frequency += frequency < 0.0f ? 1.0f : 0.0f; | |||
| float jittery_frequency = frequency * jitter_multiplier_; | |||
| master_phase_ += jittery_frequency; | |||
| phase_difference_ += frequency - jittery_frequency; | |||
| if (master_phase_ > 1.0f) { | |||
| master_phase_ -= 1.0f; | |||
| RandomVector random_vector; | |||
| sequence_.NextVector( | |||
| random_vector.x, | |||
| sizeof(random_vector.x) / sizeof(float)); | |||
| float jitter_amount = jitter_ * jitter_ * jitter_ * jitter_ * 36.0f; | |||
| float x = FastBetaDistributionSample(random_vector.variables.jitter); | |||
| float multiplier = SemitonesToRatio((x * 2.0f - 1.0f) * jitter_amount); | |||
| // This step is crucial in making sure that the jittered clock does not | |||
| // deviate too much from the master clock. The larger the phase difference | |||
| // difference between the two, the more likely the jittery clock will | |||
| // speed up or down to catch up with the straight clock. | |||
| multiplier *= phase_difference_ > 0.0f | |||
| ? 1.0f + phase_difference_ | |||
| : 1.0f / (1.0f - phase_difference_); | |||
| jitter_multiplier_ = multiplier; | |||
| ConfigureSlaveRamps(random_vector); | |||
| } | |||
| if (internal_frequency) { | |||
| *ramps.external = master_phase_; | |||
| } | |||
| previous_external_ramp_value_ = *ramps.external; | |||
| ramps.external++; | |||
| *ramps.master++ = master_phase_; | |||
| for (size_t j = 0; j < kNumTChannels; ++j) { | |||
| slave_ramp_[j].Process( | |||
| frequency * jitter_multiplier_, | |||
| ramps.slave[j], | |||
| gate); | |||
| ramps.slave[j]++; | |||
| gate++; | |||
| } | |||
| } | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,206 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Generator for the T outputs. | |||
| #ifndef MARBLES_RANDOM_T_GENERATOR_H_ | |||
| #define MARBLES_RANDOM_T_GENERATOR_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/ramp/ramp_divider.h" | |||
| #include "marbles/ramp/ramp_extractor.h" | |||
| #include "marbles/ramp/ramp_generator.h" | |||
| #include "marbles/ramp/slave_ramp.h" | |||
| #include "marbles/random/distributions.h" | |||
| #include "marbles/random/random_sequence.h" | |||
| #include "stmlib/dsp/hysteresis_quantizer.h" | |||
| namespace marbles { | |||
| enum TGeneratorModel { | |||
| T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI, | |||
| T_GENERATOR_MODEL_CLUSTERS, | |||
| T_GENERATOR_MODEL_DRUMS, | |||
| T_GENERATOR_MODEL_INDEPENDENT_BERNOULLI, | |||
| T_GENERATOR_MODEL_DIVIDER, | |||
| T_GENERATOR_MODEL_THREE_STATES, | |||
| T_GENERATOR_MODEL_MARKOV, | |||
| }; | |||
| enum TGeneratorRange { | |||
| T_GENERATOR_RANGE_0_25X, | |||
| T_GENERATOR_RANGE_1X, | |||
| T_GENERATOR_RANGE_4X, | |||
| }; | |||
| const size_t kNumTChannels = 2; | |||
| const size_t kMarkovHistorySize = 16; | |||
| const size_t kNumDrumPatterns = 18; | |||
| const size_t kDrumPatternSize = 8; | |||
| struct DividerPattern { | |||
| Ratio ratios[kNumTChannels]; | |||
| int32_t length; | |||
| }; | |||
| struct Ramps { | |||
| float* external; | |||
| float* master; | |||
| float* slave[kNumTChannels]; | |||
| }; | |||
| const size_t kNumDividerPatterns = 17; | |||
| const size_t kNumInputDividerRatios = 9; | |||
| class TGenerator { | |||
| public: | |||
| TGenerator() { } | |||
| ~TGenerator() { } | |||
| void Init(RandomStream* random_stream, float sr); | |||
| void Process( | |||
| bool use_external_clock, | |||
| const stmlib::GateFlags* external_clock, | |||
| Ramps ramps, | |||
| bool* gate, | |||
| size_t size); | |||
| inline void set_model(TGeneratorModel model) { | |||
| model_ = model; | |||
| } | |||
| inline void set_range(TGeneratorRange range) { | |||
| range_ = range; | |||
| } | |||
| inline void set_rate(float rate) { | |||
| rate_ = rate; | |||
| } | |||
| inline void set_bias(float bias) { | |||
| bias_ = bias; | |||
| } | |||
| inline void set_jitter(float jitter) { | |||
| jitter_ = jitter; | |||
| } | |||
| inline void set_deja_vu(float deja_vu) { | |||
| sequence_.set_deja_vu(deja_vu); | |||
| } | |||
| inline void set_length(int length) { | |||
| sequence_.set_length(length); | |||
| } | |||
| inline void set_pulse_width_mean(float pulse_width_mean) { | |||
| pulse_width_mean_ = pulse_width_mean; | |||
| } | |||
| inline void set_pulse_width_std(float pulse_width_std) { | |||
| pulse_width_std_ = pulse_width_std; | |||
| } | |||
| private: | |||
| union RandomVector { | |||
| struct { | |||
| float pulse_width[kNumTChannels]; | |||
| float u[kNumTChannels]; | |||
| float p; | |||
| float jitter; | |||
| } variables; | |||
| float x[2 * kNumTChannels + 2]; | |||
| }; | |||
| void ConfigureSlaveRamps(const RandomVector& v); | |||
| int GenerateComplementaryBernoulli(const RandomVector& v); | |||
| int GenerateIndependentBernoulli(const RandomVector& v); | |||
| int GenerateThreeStates(const RandomVector& v); | |||
| int GenerateDrums(const RandomVector& v); | |||
| int GenerateMarkov(const RandomVector& v); | |||
| void ScheduleOutputPulses(const RandomVector& v, int bitmask); | |||
| float RandomPulseWidth(int i, float u) { | |||
| if (pulse_width_std_ == 0.0f) { | |||
| return 0.05f + 0.9f * pulse_width_mean_; | |||
| } else { | |||
| return 0.05f + 0.9f * BetaDistributionSample( | |||
| u, | |||
| pulse_width_std_, | |||
| pulse_width_mean_); // Jon Brooks | |||
| // i & 1 ? 1.0f - pulse_width_mean_); | |||
| } | |||
| } | |||
| float one_hertz_; | |||
| TGeneratorModel model_; | |||
| TGeneratorRange range_; | |||
| float rate_; | |||
| float bias_; | |||
| float jitter_; | |||
| float pulse_width_mean_; | |||
| float pulse_width_std_; | |||
| float master_phase_; | |||
| float jitter_multiplier_; | |||
| float phase_difference_; | |||
| float previous_external_ramp_value_; | |||
| bool use_external_clock_; | |||
| int32_t divider_pattern_length_; | |||
| int32_t streak_counter_[kMarkovHistorySize]; | |||
| int32_t markov_history_[kMarkovHistorySize]; | |||
| int32_t markov_history_ptr_; | |||
| size_t drum_pattern_step_; | |||
| size_t drum_pattern_index_; | |||
| RandomSequence sequence_; | |||
| RampDivider ramp_divider_; | |||
| RampExtractor ramp_extractor_; | |||
| RampGenerator ramp_generator_; | |||
| SlaveRamp slave_ramp_[kNumTChannels]; | |||
| stmlib::HysteresisQuantizer bias_quantizer_; | |||
| stmlib::HysteresisQuantizer rate_quantizer_; | |||
| static DividerPattern divider_patterns[kNumDividerPatterns]; | |||
| static DividerPattern fixed_divider_patterns[kNumDividerPatterns]; | |||
| static Ratio input_divider_ratios[kNumInputDividerRatios]; | |||
| static uint8_t drum_patterns[kNumDrumPatterns][kDrumPatternSize]; | |||
| DISALLOW_COPY_AND_ASSIGN(TGenerator); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_T_GENERATOR_H_ | |||
| @@ -0,0 +1,184 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Generator for the X/Y outputs. | |||
| #include "marbles/random/x_y_generator.h" | |||
| #include <algorithm> | |||
| #include "stmlib/dsp/dsp.h" | |||
| namespace marbles { | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| void XYGenerator::Init(RandomStream* random_stream, float sr) { | |||
| for (size_t i = 0; i < kNumChannels; ++i) { | |||
| random_sequence_[i].Init(random_stream); | |||
| output_channel_[i].Init(); | |||
| } | |||
| ramp_extractor_.Init(8000.0f / sr); | |||
| ramp_divider_.Init(); | |||
| external_clock_stabilization_counter_ = 16; | |||
| } | |||
| const uint32_t hashes[kNumXChannels] = { | |||
| 0, 0xbeca55e5, 0xf0cacc1a | |||
| }; | |||
| void XYGenerator::Process( | |||
| ClockSource clock_source, | |||
| const GroupSettings& x_settings, | |||
| const GroupSettings& y_settings, | |||
| const GateFlags* external_clock, | |||
| const Ramps& ramps, | |||
| float* output, | |||
| size_t size) { | |||
| float* channel_ramp[kNumChannels]; | |||
| if (clock_source != CLOCK_SOURCE_EXTERNAL) { | |||
| // For a couple of upcoming blocks, we'll still be receiving garbage from | |||
| // the normalization pin that we need to ignore. | |||
| external_clock_stabilization_counter_ = 16; | |||
| } else { | |||
| if (external_clock_stabilization_counter_) { | |||
| --external_clock_stabilization_counter_; | |||
| if (external_clock_stabilization_counter_ == 0) { | |||
| ramp_extractor_.Reset(); | |||
| } | |||
| } | |||
| } | |||
| switch (clock_source) { | |||
| case CLOCK_SOURCE_EXTERNAL: | |||
| { | |||
| Ratio r = { 1, 1 }; | |||
| ramp_extractor_.Process(r, false, external_clock, ramps.slave[0], size); | |||
| if (external_clock_stabilization_counter_) { | |||
| fill(&ramps.slave[0][0], &ramps.slave[0][size], 0.0f); | |||
| } | |||
| } | |||
| channel_ramp[0] = ramps.slave[0]; | |||
| channel_ramp[1] = ramps.slave[0]; | |||
| channel_ramp[2] = ramps.slave[0]; | |||
| break; | |||
| case CLOCK_SOURCE_INTERNAL_T1: | |||
| channel_ramp[0] = ramps.slave[0]; | |||
| channel_ramp[1] = ramps.slave[0]; | |||
| channel_ramp[2] = ramps.slave[0]; | |||
| break; | |||
| case CLOCK_SOURCE_INTERNAL_T2: | |||
| channel_ramp[0] = ramps.master; | |||
| channel_ramp[1] = ramps.master; | |||
| channel_ramp[2] = ramps.master; | |||
| break; | |||
| case CLOCK_SOURCE_INTERNAL_T3: | |||
| channel_ramp[0] = ramps.slave[1]; | |||
| channel_ramp[1] = ramps.slave[1]; | |||
| channel_ramp[2] = ramps.slave[1]; | |||
| break; | |||
| default: | |||
| channel_ramp[0] = ramps.slave[0]; | |||
| channel_ramp[1] = ramps.master; | |||
| channel_ramp[2] = ramps.slave[1]; | |||
| break; | |||
| } | |||
| ramp_divider_.Process(y_settings.ratio, channel_ramp[1], ramps.external, size); | |||
| channel_ramp[kNumChannels - 1] = ramps.external; | |||
| for (size_t i = 0; i < kNumChannels; ++i) { | |||
| OutputChannel& channel = output_channel_[i]; | |||
| const GroupSettings& settings = i < kNumXChannels ? x_settings : y_settings; | |||
| switch (settings.voltage_range) { | |||
| case VOLTAGE_RANGE_NARROW: | |||
| channel.set_scale_offset(ScaleOffset(2.0f, 0.0f)); | |||
| break; | |||
| case VOLTAGE_RANGE_POSITIVE: | |||
| channel.set_scale_offset(ScaleOffset(5.0f, 0.0f)); | |||
| break; | |||
| case VOLTAGE_RANGE_FULL: | |||
| channel.set_scale_offset(ScaleOffset(10.0f, -5.0f)); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| float amount = 1.0f; | |||
| if (settings.control_mode == CONTROL_MODE_BUMP) { | |||
| amount = i == kNumXChannels / 2 ? 1.0f : -1.0f; | |||
| } else if (settings.control_mode == CONTROL_MODE_TILT) { | |||
| amount = 2.0f * static_cast<float>(i) / float(kNumXChannels - 1) - 1.0f; | |||
| } | |||
| channel.set_spread(0.5f + (settings.spread - 0.5f) * amount); | |||
| channel.set_bias(0.5f + (settings.bias - 0.5f) * amount); | |||
| channel.set_steps(0.5f + (settings.steps - 0.5f) * \ | |||
| (settings.register_mode ? 1.0f : amount)); | |||
| channel.set_scale_index(settings.scale_index); | |||
| channel.set_register_mode(settings.register_mode); | |||
| channel.set_register_value(settings.register_value); | |||
| channel.set_register_transposition( | |||
| 4.0f * settings.spread * (settings.bias - 0.5f) * amount); | |||
| RandomSequence* sequence = &random_sequence_[i]; | |||
| sequence->Record(); | |||
| sequence->set_length(settings.length); | |||
| sequence->set_deja_vu(settings.deja_vu); | |||
| // When all channels follow the same clock, the deja-vu random looping will | |||
| // follow the same pattern and the constant-mode input will be shifted! | |||
| if (clock_source != CLOCK_SOURCE_INTERNAL_T1_T2_T3 | |||
| && i > 0 && i < kNumXChannels) { | |||
| sequence = &random_sequence_[0]; | |||
| if (settings.register_mode) { | |||
| if (settings.control_mode == CONTROL_MODE_IDENTICAL) { | |||
| sequence->ReplayShifted(i); | |||
| } else if (settings.control_mode == CONTROL_MODE_BUMP) { | |||
| sequence->ReplayShifted(i == 2 ? 1 : 0); | |||
| } else { | |||
| sequence->ReplayShifted(0); | |||
| } | |||
| } else { | |||
| sequence->ReplayPseudoRandom(hashes[i]); | |||
| } | |||
| } | |||
| channel.Process(sequence, channel_ramp[i], &output[i], size, kNumChannels); | |||
| } | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,123 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Generator for the X/Y outputs. | |||
| #ifndef MARBLES_RANDOM_X_Y_GENERATOR_H_ | |||
| #define MARBLES_RANDOM_X_Y_GENERATOR_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/ramp/ramp_divider.h" | |||
| #include "marbles/ramp/ramp_extractor.h" | |||
| #include "marbles/random/output_channel.h" | |||
| #include "marbles/random/random_sequence.h" | |||
| #include "marbles/random/t_generator.h" | |||
| namespace marbles { | |||
| enum VoltageRange { | |||
| VOLTAGE_RANGE_NARROW, // +2V | |||
| VOLTAGE_RANGE_POSITIVE, // +5V | |||
| VOLTAGE_RANGE_FULL // +/- 5V | |||
| }; | |||
| enum ClockSource { | |||
| CLOCK_SOURCE_INTERNAL_T1_T2_T3, | |||
| CLOCK_SOURCE_INTERNAL_T1, | |||
| CLOCK_SOURCE_INTERNAL_T2, | |||
| CLOCK_SOURCE_INTERNAL_T3, | |||
| CLOCK_SOURCE_EXTERNAL | |||
| }; | |||
| enum ControlMode { | |||
| CONTROL_MODE_IDENTICAL, | |||
| CONTROL_MODE_BUMP, | |||
| CONTROL_MODE_TILT | |||
| }; | |||
| enum OutputGroup { | |||
| OUTPUT_GROUP_X, | |||
| OUTPUT_GROUP_Y, | |||
| OUTPUT_GROUP_LAST | |||
| }; | |||
| const size_t kNumXChannels = 3; | |||
| const size_t kNumYChannels = 1; | |||
| const size_t kNumChannels = kNumXChannels + kNumYChannels; | |||
| struct GroupSettings { | |||
| ControlMode control_mode; | |||
| VoltageRange voltage_range; | |||
| bool register_mode; | |||
| float register_value; | |||
| float spread; | |||
| float bias; | |||
| float steps; | |||
| float deja_vu; | |||
| int scale_index; | |||
| int length; | |||
| Ratio ratio; | |||
| }; | |||
| class XYGenerator { | |||
| public: | |||
| XYGenerator() { } | |||
| ~XYGenerator() { } | |||
| void Init(RandomStream* random_stream, float sr); | |||
| void Process( | |||
| ClockSource clock_source, | |||
| const GroupSettings& x_settings, | |||
| const GroupSettings& y_settings, | |||
| const stmlib::GateFlags* external_clock, | |||
| const Ramps& ramps, | |||
| float* output, | |||
| size_t size); | |||
| void LoadScale(int channel, int scale_index, const Scale& scale) { | |||
| output_channel_[channel].LoadScale(scale_index, scale); | |||
| } | |||
| void LoadScale(int scale_index, const Scale& scale) { | |||
| for (size_t i = 0; i < kNumXChannels; ++i) { | |||
| output_channel_[i].LoadScale(scale_index, scale); | |||
| } | |||
| } | |||
| private: | |||
| RandomSequence random_sequence_[kNumChannels]; | |||
| OutputChannel output_channel_[kNumChannels]; | |||
| RampExtractor ramp_extractor_; | |||
| RampDivider ramp_divider_; | |||
| int external_clock_stabilization_counter_; | |||
| DISALLOW_COPY_AND_ASSIGN(XYGenerator); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_RANDOM_X_Y_GENERATOR_H_ | |||
| @@ -0,0 +1,226 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Resources definitions. | |||
| // | |||
| // Automatically generated with: | |||
| // make resources | |||
| #ifndef MARBLES_RESOURCES_H_ | |||
| #define MARBLES_RESOURCES_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace marbles { | |||
| typedef uint8_t ResourceId; | |||
| extern const float* lookup_table_table[]; | |||
| extern const float* distributions_table[]; | |||
| extern const float lut_raised_cosine[]; | |||
| extern const float lut_sine[]; | |||
| extern const float lut_logit[]; | |||
| extern const float dist_icdf_0_0[]; | |||
| extern const float dist_icdf_0_1[]; | |||
| extern const float dist_icdf_0_2[]; | |||
| extern const float dist_icdf_0_3[]; | |||
| extern const float dist_icdf_0_4[]; | |||
| extern const float dist_icdf_0_5[]; | |||
| extern const float dist_icdf_0_6[]; | |||
| extern const float dist_icdf_0_7[]; | |||
| extern const float dist_icdf_0_8[]; | |||
| extern const float dist_icdf_1_0[]; | |||
| extern const float dist_icdf_1_1[]; | |||
| extern const float dist_icdf_1_2[]; | |||
| extern const float dist_icdf_1_3[]; | |||
| extern const float dist_icdf_1_4[]; | |||
| extern const float dist_icdf_1_5[]; | |||
| extern const float dist_icdf_1_6[]; | |||
| extern const float dist_icdf_1_7[]; | |||
| extern const float dist_icdf_1_8[]; | |||
| extern const float dist_icdf_2_0[]; | |||
| extern const float dist_icdf_2_1[]; | |||
| extern const float dist_icdf_2_2[]; | |||
| extern const float dist_icdf_2_3[]; | |||
| extern const float dist_icdf_2_4[]; | |||
| extern const float dist_icdf_2_5[]; | |||
| extern const float dist_icdf_2_6[]; | |||
| extern const float dist_icdf_2_7[]; | |||
| extern const float dist_icdf_2_8[]; | |||
| extern const float dist_icdf_3_0[]; | |||
| extern const float dist_icdf_3_1[]; | |||
| extern const float dist_icdf_3_2[]; | |||
| extern const float dist_icdf_3_3[]; | |||
| extern const float dist_icdf_3_4[]; | |||
| extern const float dist_icdf_3_5[]; | |||
| extern const float dist_icdf_3_6[]; | |||
| extern const float dist_icdf_3_7[]; | |||
| extern const float dist_icdf_3_8[]; | |||
| extern const float dist_icdf_4_0[]; | |||
| extern const float dist_icdf_4_1[]; | |||
| extern const float dist_icdf_4_2[]; | |||
| extern const float dist_icdf_4_3[]; | |||
| extern const float dist_icdf_4_4[]; | |||
| extern const float dist_icdf_4_5[]; | |||
| extern const float dist_icdf_4_6[]; | |||
| extern const float dist_icdf_4_7[]; | |||
| extern const float dist_icdf_4_8[]; | |||
| #define LUT_RAISED_COSINE 0 | |||
| #define LUT_RAISED_COSINE_SIZE 257 | |||
| #define LUT_SINE 1 | |||
| #define LUT_SINE_SIZE 257 | |||
| #define LUT_LOGIT 2 | |||
| #define LUT_LOGIT_SIZE 257 | |||
| #define DIST_ICDF_0_0 0 | |||
| #define DIST_ICDF_0_0_SIZE 387 | |||
| #define DIST_ICDF_0_1 1 | |||
| #define DIST_ICDF_0_1_SIZE 387 | |||
| #define DIST_ICDF_0_2 2 | |||
| #define DIST_ICDF_0_2_SIZE 387 | |||
| #define DIST_ICDF_0_3 3 | |||
| #define DIST_ICDF_0_3_SIZE 387 | |||
| #define DIST_ICDF_0_4 4 | |||
| #define DIST_ICDF_0_4_SIZE 387 | |||
| #define DIST_ICDF_0_5 5 | |||
| #define DIST_ICDF_0_5_SIZE 387 | |||
| #define DIST_ICDF_0_6 6 | |||
| #define DIST_ICDF_0_6_SIZE 387 | |||
| #define DIST_ICDF_0_7 7 | |||
| #define DIST_ICDF_0_7_SIZE 387 | |||
| #define DIST_ICDF_0_8 8 | |||
| #define DIST_ICDF_0_8_SIZE 387 | |||
| #define DIST_ICDF_0_8_GUARD 9 | |||
| #define DIST_ICDF_0_8_GUARD_SIZE 387 | |||
| #define DIST_ICDF_1_0 10 | |||
| #define DIST_ICDF_1_0_SIZE 387 | |||
| #define DIST_ICDF_1_1 11 | |||
| #define DIST_ICDF_1_1_SIZE 387 | |||
| #define DIST_ICDF_1_2 12 | |||
| #define DIST_ICDF_1_2_SIZE 387 | |||
| #define DIST_ICDF_1_3 13 | |||
| #define DIST_ICDF_1_3_SIZE 387 | |||
| #define DIST_ICDF_1_4 14 | |||
| #define DIST_ICDF_1_4_SIZE 387 | |||
| #define DIST_ICDF_1_5 15 | |||
| #define DIST_ICDF_1_5_SIZE 387 | |||
| #define DIST_ICDF_1_6 16 | |||
| #define DIST_ICDF_1_6_SIZE 387 | |||
| #define DIST_ICDF_1_7 17 | |||
| #define DIST_ICDF_1_7_SIZE 387 | |||
| #define DIST_ICDF_1_8 18 | |||
| #define DIST_ICDF_1_8_SIZE 387 | |||
| #define DIST_ICDF_1_8_GUARD 19 | |||
| #define DIST_ICDF_1_8_GUARD_SIZE 387 | |||
| #define DIST_ICDF_2_0 20 | |||
| #define DIST_ICDF_2_0_SIZE 387 | |||
| #define DIST_ICDF_2_1 21 | |||
| #define DIST_ICDF_2_1_SIZE 387 | |||
| #define DIST_ICDF_2_2 22 | |||
| #define DIST_ICDF_2_2_SIZE 387 | |||
| #define DIST_ICDF_2_3 23 | |||
| #define DIST_ICDF_2_3_SIZE 387 | |||
| #define DIST_ICDF_2_4 24 | |||
| #define DIST_ICDF_2_4_SIZE 387 | |||
| #define DIST_ICDF_2_5 25 | |||
| #define DIST_ICDF_2_5_SIZE 387 | |||
| #define DIST_ICDF_2_6 26 | |||
| #define DIST_ICDF_2_6_SIZE 387 | |||
| #define DIST_ICDF_2_7 27 | |||
| #define DIST_ICDF_2_7_SIZE 387 | |||
| #define DIST_ICDF_2_8 28 | |||
| #define DIST_ICDF_2_8_SIZE 387 | |||
| #define DIST_ICDF_2_8_GUARD 29 | |||
| #define DIST_ICDF_2_8_GUARD_SIZE 387 | |||
| #define DIST_ICDF_3_0 30 | |||
| #define DIST_ICDF_3_0_SIZE 387 | |||
| #define DIST_ICDF_3_1 31 | |||
| #define DIST_ICDF_3_1_SIZE 387 | |||
| #define DIST_ICDF_3_2 32 | |||
| #define DIST_ICDF_3_2_SIZE 387 | |||
| #define DIST_ICDF_3_3 33 | |||
| #define DIST_ICDF_3_3_SIZE 387 | |||
| #define DIST_ICDF_3_4 34 | |||
| #define DIST_ICDF_3_4_SIZE 387 | |||
| #define DIST_ICDF_3_5 35 | |||
| #define DIST_ICDF_3_5_SIZE 387 | |||
| #define DIST_ICDF_3_6 36 | |||
| #define DIST_ICDF_3_6_SIZE 387 | |||
| #define DIST_ICDF_3_7 37 | |||
| #define DIST_ICDF_3_7_SIZE 387 | |||
| #define DIST_ICDF_3_8 38 | |||
| #define DIST_ICDF_3_8_SIZE 387 | |||
| #define DIST_ICDF_3_8_GUARD 39 | |||
| #define DIST_ICDF_3_8_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_0 40 | |||
| #define DIST_ICDF_4_0_SIZE 387 | |||
| #define DIST_ICDF_4_1 41 | |||
| #define DIST_ICDF_4_1_SIZE 387 | |||
| #define DIST_ICDF_4_2 42 | |||
| #define DIST_ICDF_4_2_SIZE 387 | |||
| #define DIST_ICDF_4_3 43 | |||
| #define DIST_ICDF_4_3_SIZE 387 | |||
| #define DIST_ICDF_4_4 44 | |||
| #define DIST_ICDF_4_4_SIZE 387 | |||
| #define DIST_ICDF_4_5 45 | |||
| #define DIST_ICDF_4_5_SIZE 387 | |||
| #define DIST_ICDF_4_6 46 | |||
| #define DIST_ICDF_4_6_SIZE 387 | |||
| #define DIST_ICDF_4_7 47 | |||
| #define DIST_ICDF_4_7_SIZE 387 | |||
| #define DIST_ICDF_4_8 48 | |||
| #define DIST_ICDF_4_8_SIZE 387 | |||
| #define DIST_ICDF_4_8_GUARD 49 | |||
| #define DIST_ICDF_4_8_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_0_GUARD 50 | |||
| #define DIST_ICDF_4_0_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_1_GUARD 51 | |||
| #define DIST_ICDF_4_1_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_2_GUARD 52 | |||
| #define DIST_ICDF_4_2_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_3_GUARD 53 | |||
| #define DIST_ICDF_4_3_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_4_GUARD 54 | |||
| #define DIST_ICDF_4_4_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_5_GUARD 55 | |||
| #define DIST_ICDF_4_5_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_6_GUARD 56 | |||
| #define DIST_ICDF_4_6_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_7_GUARD 57 | |||
| #define DIST_ICDF_4_7_GUARD_SIZE 387 | |||
| #define DIST__ICDF_4_8_GUARD 58 | |||
| #define DIST__ICDF_4_8_GUARD_SIZE 387 | |||
| #define DIST_ICDF_4_8_GUARD_GUARD 59 | |||
| #define DIST_ICDF_4_8_GUARD_GUARD_SIZE 387 | |||
| } // namespace marbles | |||
| #endif // MARBLES_RESOURCES_H_ | |||
| @@ -0,0 +1,109 @@ | |||
| #!/usr/bin/python2.5 | |||
| # | |||
| # Copyright 2015 Olivier Gillet. | |||
| # | |||
| # Author: Olivier Gillet (ol.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. | |||
| # | |||
| # ----------------------------------------------------------------------------- | |||
| # | |||
| # Lookup table definitions. | |||
| import numpy | |||
| import scipy.stats | |||
| lookup_tables = [] | |||
| distributions = [] | |||
| """---------------------------------------------------------------------------- | |||
| Raised cosine | |||
| ----------------------------------------------------------------------------""" | |||
| x = numpy.arange(0, 257) / 256.0 | |||
| c = 1.0 - (0.5 * numpy.cos(x * numpy.pi) + 0.5) | |||
| lookup_tables += [('raised_cosine', c)] | |||
| x = numpy.arange(0, 257) / 256.0 | |||
| c = numpy.sin(x * numpy.pi * 2) | |||
| lookup_tables += [('sine', c)] | |||
| """---------------------------------------------------------------------------- | |||
| Logit table | |||
| ----------------------------------------------------------------------------""" | |||
| x = numpy.arange(0, 257) / 256.0 | |||
| log_odds = x * 20.0 - 10.0 | |||
| odds = 2 ** log_odds | |||
| p = odds / (1 + odds) | |||
| lookup_tables += [('logit', p)] | |||
| """---------------------------------------------------------------------------- | |||
| Inverse CDF of Beta distribution for various combinations of alpha/beta. | |||
| Used as a LUT for inverse transform sampling. | |||
| ----------------------------------------------------------------------------""" | |||
| N_nu = 9 | |||
| N_mu = 5 | |||
| def squash(x): | |||
| return x / (1 + x ** 2) ** 0.5 | |||
| nu_values = 2 ** numpy.array([9, 5, 3, 2.5, 2, 1.5, 1, 0.5, -1]) | |||
| mu_values = numpy.linspace(0, 0.5, N_mu) | |||
| mu_values[0] = 0.05 | |||
| plot = False | |||
| if plot: | |||
| import pylab | |||
| VOLTAGE_RANGE = 8 | |||
| for i, mu in enumerate(mu_values): | |||
| row = [] | |||
| for j, nu in enumerate(nu_values): | |||
| error = numpy.exp(-(numpy.log2(nu) - 1) ** 2 / 20.0) | |||
| corrected_mu = 0.5 * (2 * mu) ** (1 / (1 + 3.0 * error)) | |||
| alpha, beta = corrected_mu * nu, (1 - corrected_mu) * nu | |||
| if plot: | |||
| x = numpy.arange(-VOLTAGE_RANGE, VOLTAGE_RANGE, 0.1) | |||
| p = scipy.stats.beta.pdf(0.5 * (x / VOLTAGE_RANGE + 1.0), alpha, beta) | |||
| pylab.subplot(N_mu, N_nu, i * N_nu + j + 1) | |||
| pylab.plot(x, p) | |||
| body = numpy.arange(0, 129) / 128.0 | |||
| head = body / 20.0 | |||
| tail = body / 20.0 + 0.95 | |||
| values = numpy.hstack((body, head, tail)) | |||
| ppf = scipy.stats.beta.ppf(values, alpha, beta) | |||
| row += [('icdf_%d_%d' % (i, j), ppf)] | |||
| if j == N_nu - 1: | |||
| row += [('icdf_%d_%d_guard' % (i, j), ppf)] | |||
| distributions += row | |||
| if i == N_mu - 1: | |||
| distributions += [(name + '_guard', values) for (name, values) in row] | |||
| if plot: | |||
| pylab.show() | |||
| @@ -0,0 +1,79 @@ | |||
| #!/usr/bin/python2.5 | |||
| # | |||
| # Copyright 2015 Olivier Gillet. | |||
| # | |||
| # Author: Olivier Gillet (ol.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. | |||
| # | |||
| # ----------------------------------------------------------------------------- | |||
| # | |||
| # Master resources file. | |||
| header = """// Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Resources definitions. | |||
| // | |||
| // Automatically generated with: | |||
| // make resources | |||
| """ | |||
| namespace = 'marbles' | |||
| target = 'marbles' | |||
| types = ['uint8_t', 'uint16_t'] | |||
| includes = """ | |||
| #include "stmlib/stmlib.h" | |||
| """ | |||
| import lookup_tables | |||
| create_specialized_manager = True | |||
| resources = [ | |||
| (lookup_tables.lookup_tables, | |||
| 'lookup_table', 'LUT', 'float', float, False), | |||
| (lookup_tables.distributions, | |||
| 'distributions', 'DIST', 'float', float, False) | |||
| ] | |||
| @@ -0,0 +1,149 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Record a note CV distribution - to be used for the quantizer. | |||
| #ifndef MARBLES_SCALE_RECORDER_H_ | |||
| #define MARBLES_SCALE_RECORDER_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "marbles/random/quantizer.h" | |||
| namespace marbles { | |||
| class ScaleRecorder { | |||
| public: | |||
| ScaleRecorder() { } | |||
| ~ScaleRecorder() { } | |||
| struct Degree { | |||
| float average_voltage; | |||
| float total_voltage; | |||
| float count; | |||
| bool operator< (const Degree& rhs) const { | |||
| return average_voltage < rhs.average_voltage; | |||
| } | |||
| }; | |||
| void Init() { | |||
| Clear(); | |||
| } | |||
| void Clear() { | |||
| num_degrees_ = 0; | |||
| current_voltage_ = 0.0f; | |||
| total_count_ = 0.0f; | |||
| } | |||
| void NewNote(float v) { | |||
| current_voltage_ = v; | |||
| } | |||
| void UpdateVoltage(float v) { | |||
| ONE_POLE(current_voltage_, v, 0.01f); | |||
| } | |||
| void AcceptNote() { | |||
| const float base_interval = 1.0f; | |||
| float v = current_voltage_; | |||
| while (v < 0.0f) { | |||
| v += base_interval; | |||
| } | |||
| float octave = static_cast<float>( | |||
| static_cast<int>(v / base_interval)) * base_interval; | |||
| v -= octave; | |||
| int nearest_degree = -1; | |||
| for (int i = 0; i < num_degrees_; ++i) { | |||
| float av = degrees_[i].average_voltage; | |||
| const float tolerance = 1.0f / 36.0f; | |||
| if (fabsf(v - av) < tolerance) { | |||
| nearest_degree = i; | |||
| break; | |||
| } | |||
| if (fabsf((v - base_interval) - av) < tolerance) { | |||
| v -= base_interval; | |||
| nearest_degree = i; | |||
| break; | |||
| } | |||
| } | |||
| if (nearest_degree == -1 && num_degrees_ != kMaxDegrees) { | |||
| nearest_degree = num_degrees_; | |||
| Degree* d = °rees_[nearest_degree]; | |||
| d->total_voltage = 0.0f; | |||
| d->average_voltage = 0.0f; | |||
| d->count = 0.0f; | |||
| ++num_degrees_; | |||
| } | |||
| if (nearest_degree != -1) { | |||
| Degree* d = °rees_[nearest_degree]; | |||
| d->total_voltage += v; | |||
| d->count += 1.0f; | |||
| d->average_voltage = d->total_voltage / d->count; | |||
| total_count_ += 1.0f; | |||
| } | |||
| } | |||
| bool ExtractScale(Scale* scale) { | |||
| if (num_degrees_ < 2) { | |||
| return false; | |||
| } | |||
| std::sort(°rees_[0], °rees_[num_degrees_]); | |||
| float max_count = 0.0f; | |||
| for (int i = 0; i < num_degrees_; ++i) { | |||
| max_count = std::max(degrees_[i].count, max_count); | |||
| } | |||
| scale->base_interval = 1.0f; | |||
| scale->num_degrees = num_degrees_; | |||
| for (int i = 0; i < num_degrees_; ++i) { | |||
| Degree* d = °rees_[i]; | |||
| scale->degree[i].voltage = d->average_voltage; | |||
| scale->degree[i].weight = static_cast<uint8_t>( | |||
| 255.0f * d->count / max_count); | |||
| if (scale->degree[i].weight == 0) { | |||
| ++scale->degree[i].weight; | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| private: | |||
| int num_degrees_; | |||
| float current_voltage_; | |||
| float total_count_; | |||
| Degree degrees_[kMaxDegrees]; | |||
| DISALLOW_COPY_AND_ASSIGN(ScaleRecorder); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_SCALE_RECORDER_H_ | |||
| @@ -0,0 +1,246 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Settings storage. | |||
| #include "marbles/settings.h" | |||
| #include <algorithm> | |||
| #include "stmlib/system/storage.h" | |||
| namespace marbles { | |||
| using namespace std; | |||
| const Scale preset_scales[6] = { | |||
| // C major | |||
| { | |||
| 1.0f, | |||
| 12, | |||
| { | |||
| { 0.0000f, 255 }, // C | |||
| { 0.0833f, 16 }, // C# | |||
| { 0.1667f, 96 }, // D | |||
| { 0.2500f, 24 }, // D# | |||
| { 0.3333f, 128 }, // E | |||
| { 0.4167f, 64 }, // F | |||
| { 0.5000f, 8 }, // F# | |||
| { 0.5833f, 192 }, // G | |||
| { 0.6667f, 16 }, // G# | |||
| { 0.7500f, 96 }, // A | |||
| { 0.8333f, 24 }, // A# | |||
| { 0.9167f, 128 }, // B | |||
| } | |||
| }, | |||
| // C minor | |||
| { | |||
| 1.0f, | |||
| 12, | |||
| { | |||
| { 0.0000f, 255 }, // C | |||
| { 0.0833f, 16 }, // C# | |||
| { 0.1667f, 96 }, // D | |||
| { 0.2500f, 128 }, // Eb | |||
| { 0.3333f, 8 }, // E | |||
| { 0.4167f, 64 }, // F | |||
| { 0.5000f, 4 }, // F# | |||
| { 0.5833f, 192 }, // G | |||
| { 0.6667f, 96 }, // G# | |||
| { 0.7500f, 16 }, // A | |||
| { 0.8333f, 128 }, // Bb | |||
| { 0.9167f, 16 }, // B | |||
| } | |||
| }, | |||
| // Pentatonic | |||
| { | |||
| 1.0f, | |||
| 12, | |||
| { | |||
| { 0.0000f, 255 }, // C | |||
| { 0.0833f, 4 }, // C# | |||
| { 0.1667f, 96 }, // D | |||
| { 0.2500f, 4 }, // Eb | |||
| { 0.3333f, 4 }, // E | |||
| { 0.4167f, 140 }, // F | |||
| { 0.5000f, 4 }, // F# | |||
| { 0.5833f, 192 }, // G | |||
| { 0.6667f, 4 }, // G# | |||
| { 0.7500f, 96 }, // A | |||
| { 0.8333f, 4 }, // Bb | |||
| { 0.9167f, 4 }, // B | |||
| } | |||
| }, | |||
| // Pelog | |||
| { | |||
| 1.0f, | |||
| 7, | |||
| { | |||
| { 0.0000f, 255 }, // C | |||
| { 0.1275f, 128 }, // Db+ | |||
| { 0.2625f, 32 }, // Eb- | |||
| { 0.4600f, 8 }, // F#- | |||
| { 0.5883f, 192 }, // G | |||
| { 0.7067f, 64 }, // Ab | |||
| { 0.8817f, 16 }, // Bb+ | |||
| } | |||
| }, | |||
| // Raag Bhairav That | |||
| { | |||
| 1.0f, | |||
| 12, | |||
| { | |||
| { 0.0000f, 255 }, // ** Sa | |||
| { 0.0752f, 128 }, // ** Komal Re | |||
| { 0.1699f, 4 }, // Re | |||
| { 0.2630f, 4 }, // Komal Ga | |||
| { 0.3219f, 128 }, // ** Ga | |||
| { 0.4150f, 64 }, // ** Ma | |||
| { 0.4918f, 4 }, // Tivre Ma | |||
| { 0.5850f, 192 }, // ** Pa | |||
| { 0.6601f, 64 }, // ** Komal Dha | |||
| { 0.7549f, 4 }, // Dha | |||
| { 0.8479f, 4 }, // Komal Ni | |||
| { 0.9069f, 64 }, // ** Ni | |||
| } | |||
| }, | |||
| // Raag Shri | |||
| { | |||
| 1.0f, | |||
| 12, | |||
| { | |||
| { 0.0000f, 255 }, // ** Sa | |||
| { 0.0752f, 4 }, // Komal Re | |||
| { 0.1699f, 128 }, // ** Re | |||
| { 0.2630f, 64 }, // ** Komal Ga | |||
| { 0.3219f, 4 }, // Ga | |||
| { 0.4150f, 128 }, // ** Ma | |||
| { 0.4918f, 4 }, // Tivre Ma | |||
| { 0.5850f, 192 }, // ** Pa | |||
| { 0.6601f, 4 }, // Komal Dha | |||
| { 0.7549f, 64 }, // ** Dha | |||
| { 0.8479f, 128 }, // ** Komal Ni | |||
| { 0.9069f, 4 }, // Ni | |||
| } | |||
| }, | |||
| }; | |||
| #define FIX_OUTLIER(destination, expected_value) if (fabsf(destination / expected_value - 1.0f) > 0.1f) { destination = expected_value; } | |||
| void Settings::ResetScale(int i) { | |||
| persistent_data_.scale[i] = preset_scales[i]; | |||
| } | |||
| void Settings::Init() { | |||
| freshly_baked_ = false; | |||
| // Set default values for all calibration and state settings. | |||
| // This settings will be written to flash memory the first time the module | |||
| // is powered on, or if corrupted data is found in the flash sector, | |||
| // following a major firmware upgrade. | |||
| CalibrationData& c = persistent_data_.calibration_data; | |||
| fill(&c.adc_scale[0], &c.adc_scale[ADC_CHANNEL_LAST], -2.0f); | |||
| fill(&c.adc_offset[0], &c.adc_offset[ADC_CHANNEL_LAST], +1.0f); | |||
| fill(&c.dac_scale[0], &c.dac_scale[DAC_CHANNEL_LAST], -6212.8f); | |||
| fill(&c.dac_offset[0], &c.dac_offset[DAC_CHANNEL_LAST], 32768.0f); | |||
| c.adc_offset[ADC_CHANNEL_T_RATE] = 60.0f; | |||
| c.adc_scale[ADC_CHANNEL_T_RATE] = -120.0f; | |||
| for (size_t i = 0; i < kNumScales; ++i) { | |||
| ResetScale(i); | |||
| } | |||
| state_.t_deja_vu = 0; | |||
| state_.t_model = 0; | |||
| state_.t_range = 1; | |||
| state_.t_pulse_width_mean = 128; | |||
| state_.t_pulse_width_std = 0; | |||
| state_.x_deja_vu = 0; | |||
| state_.x_control_mode = 0; | |||
| state_.x_register_mode = 0; | |||
| state_.x_range = 2; | |||
| state_.x_scale = 0; | |||
| state_.y_spread = 128; | |||
| state_.y_bias = 128; | |||
| state_.y_steps = 0; | |||
| state_.y_divider = 128; | |||
| state_.y_range = 2; | |||
| state_.color_blind = 0; | |||
| freshly_baked_ = !chunk_storage_.Init(&persistent_data_, &state_); | |||
| if (!freshly_baked_) { | |||
| CONSTRAIN(state_.t_model, 0, 5); | |||
| CONSTRAIN(state_.t_range, 0, 2); | |||
| CONSTRAIN(state_.x_control_mode, 0, 2); | |||
| CONSTRAIN(state_.x_range, 0, 2); | |||
| CONSTRAIN(state_.x_scale, 0, 5); | |||
| CONSTRAIN(state_.y_range, 0, 2); | |||
| CalibrationData& c = persistent_data_.calibration_data; | |||
| for (size_t i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
| if (i == ADC_CHANNEL_T_RATE) { | |||
| FIX_OUTLIER(c.adc_scale[i], -120.0f); | |||
| FIX_OUTLIER(c.adc_offset[i], 60.0f); | |||
| } else { | |||
| FIX_OUTLIER(c.adc_scale[i], -2.0f); | |||
| FIX_OUTLIER(c.adc_offset[i], +1.0f); | |||
| } | |||
| } | |||
| for (size_t i = 0; i < DAC_CHANNEL_LAST; ++i) { | |||
| FIX_OUTLIER(c.dac_scale[i], -6212.8f); | |||
| FIX_OUTLIER(c.dac_offset[i], 32768.0f); | |||
| } | |||
| } | |||
| } | |||
| void Settings::SavePersistentData() { | |||
| chunk_storage_.SavePersistentData(); | |||
| } | |||
| void Settings::SaveState() { | |||
| chunk_storage_.SaveState(); | |||
| } | |||
| /* static */ | |||
| void Settings::ProgramOptionBytes() { | |||
| FLASH_Unlock(); | |||
| FLASH_OB_Unlock(); | |||
| FLASH_OB_BORConfig(OB_BOR_OFF); | |||
| FLASH_OB_Launch(); | |||
| FLASH_OB_Lock(); | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,152 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Settings storage. | |||
| #ifndef MARBLES_SETTINGS_H_ | |||
| #define MARBLES_SETTINGS_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/system/storage.h" | |||
| #include "marbles/drivers/adc.h" | |||
| #include "marbles/drivers/dac.h" | |||
| #include "marbles/random/quantizer.h" | |||
| namespace marbles { | |||
| struct CalibrationData { | |||
| float adc_offset[ADC_CHANNEL_LAST]; | |||
| float adc_scale[ADC_CHANNEL_LAST]; | |||
| float dac_offset[DAC_CHANNEL_LAST]; | |||
| float dac_scale[DAC_CHANNEL_LAST]; | |||
| }; | |||
| const int kNumScales = 6; | |||
| struct PersistentData { | |||
| CalibrationData calibration_data; | |||
| Scale scale[kNumScales]; | |||
| uint8_t padding[16]; | |||
| enum { tag = 0x494C4143 }; | |||
| }; | |||
| struct State { | |||
| uint8_t t_deja_vu; | |||
| uint8_t t_model; | |||
| uint8_t t_range; | |||
| uint8_t t_pulse_width_mean; | |||
| uint8_t t_pulse_width_std; | |||
| uint8_t x_deja_vu; | |||
| uint8_t x_control_mode; | |||
| uint8_t x_register_mode; | |||
| uint8_t x_range; | |||
| uint8_t x_scale; | |||
| uint8_t y_spread; | |||
| uint8_t y_bias; | |||
| uint8_t y_steps; | |||
| uint8_t y_divider; | |||
| uint8_t y_range; | |||
| uint8_t color_blind; | |||
| uint8_t padding[8]; | |||
| enum { tag = 0x54415453 }; | |||
| }; | |||
| class Settings { | |||
| public: | |||
| Settings() { } | |||
| ~Settings() { } | |||
| void Init(); | |||
| void SavePersistentData(); | |||
| void SaveState(); | |||
| void ResetScale(int i); | |||
| static void ProgramOptionBytes(); | |||
| inline const CalibrationData& calibration_data() { | |||
| return persistent_data_.calibration_data; | |||
| } | |||
| inline CalibrationData* mutable_calibration_data() { | |||
| return &persistent_data_.calibration_data; | |||
| } | |||
| inline const Scale& scale(int i) const { | |||
| return persistent_data_.scale[i]; | |||
| } | |||
| inline Scale* mutable_scale(int i) { | |||
| return &persistent_data_.scale[i]; | |||
| } | |||
| inline const State& state() const { | |||
| return state_; | |||
| } | |||
| inline State* mutable_state() { | |||
| return &state_; | |||
| } | |||
| inline const PersistentData& persistent_data() const { | |||
| return persistent_data_; | |||
| } | |||
| inline bool freshly_baked() const { | |||
| return freshly_baked_; | |||
| } | |||
| inline void set_dirty_scale_index(int i) { | |||
| dirty_scale_index_ = i; | |||
| } | |||
| inline int dirty_scale_index() const { | |||
| return dirty_scale_index_; | |||
| } | |||
| private: | |||
| bool freshly_baked_; | |||
| int dirty_scale_index_; | |||
| PersistentData persistent_data_; | |||
| State state_; | |||
| stmlib::ChunkStorage<1, PersistentData, State> chunk_storage_; | |||
| DISALLOW_COPY_AND_ASSIGN(Settings); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_SETTINGS_H_ | |||
| @@ -0,0 +1,227 @@ | |||
| // Copyright 2015 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/>. | |||
| #ifndef MARBLES_TEST_FIXTURES_H_ | |||
| #define MARBLES_TEST_FIXTURES_H_ | |||
| #include <cstdlib> | |||
| #include <vector> | |||
| #include "marbles/ramp/ramp_divider.h" | |||
| #include "marbles/ramp/ramp_extractor.h" | |||
| const size_t kSampleRate = 32000; | |||
| const size_t kAudioBlockSize = 8; | |||
| namespace marbles { | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| class PulseGenerator { | |||
| public: | |||
| PulseGenerator() { | |||
| counter_ = 0; | |||
| previous_state_ = 0; | |||
| } | |||
| ~PulseGenerator() { } | |||
| void AddPulses(int total_duration, int on_duration, int num_repetitions) { | |||
| Pulse p; | |||
| p.total_duration = total_duration; | |||
| p.on_duration = on_duration; | |||
| p.num_repetitions = num_repetitions; | |||
| pulses_.push_back(p); | |||
| } | |||
| void Render(GateFlags* clock, size_t size) { | |||
| while (size--) { | |||
| bool current_state = pulses_.size() && counter_ < pulses_[0].on_duration; | |||
| ++counter_; | |||
| if (pulses_.size() && counter_ >= pulses_[0].total_duration) { | |||
| counter_ = 0; | |||
| --pulses_[0].num_repetitions; | |||
| if (pulses_[0].num_repetitions == 0) { | |||
| pulses_.erase(pulses_.begin()); | |||
| } | |||
| } | |||
| previous_state_ = *clock++ = ExtractGateFlags(previous_state_, current_state); | |||
| } | |||
| } | |||
| private: | |||
| struct Pulse { | |||
| int total_duration; | |||
| int on_duration; | |||
| int num_repetitions; | |||
| }; | |||
| int counter_; | |||
| GateFlags previous_state_; | |||
| vector<Pulse> pulses_; | |||
| DISALLOW_COPY_AND_ASSIGN(PulseGenerator); | |||
| }; | |||
| enum PatternDifficulty { | |||
| FRIENDLY_PATTERNS, | |||
| FAST_PATTERNS, | |||
| TRICKY_PATTERNS, | |||
| PAUSE_PATTERNS, | |||
| }; | |||
| class ClockGeneratorPatterns { | |||
| public: | |||
| ClockGeneratorPatterns(PatternDifficulty difficulty) { | |||
| if (difficulty == FRIENDLY_PATTERNS) { | |||
| pulse_generator_.AddPulses(800, 400, 100); | |||
| pulse_generator_.AddPulses(400, 32, 100); | |||
| for (int i = 0; i < 15; ++i) { | |||
| for (int j = 0; j < 5; ++j) { | |||
| pulse_generator_.AddPulses(600 - j * 100, 3, 2); | |||
| } | |||
| } | |||
| for (int i = 0; i < 300; ++i) { | |||
| int t = 200 + (rand() % 400); | |||
| pulse_generator_.AddPulses(t, t / 4, 1); | |||
| } | |||
| // Completely random clock. | |||
| for (int i = 0; i < 400; ++i) { | |||
| int t = 200 + (rand() % 800); | |||
| int pw = t / 4 + (rand() % (t / 2)); | |||
| pulse_generator_.AddPulses(t, pw, 1); | |||
| } | |||
| return; | |||
| } else if (difficulty == FAST_PATTERNS) { | |||
| pulse_generator_.AddPulses(32, 16, 100); | |||
| pulse_generator_.AddPulses(16, 8, 100); | |||
| pulse_generator_.AddPulses(12, 6, 100); | |||
| pulse_generator_.AddPulses(8, 4, 100); | |||
| pulse_generator_.AddPulses(6, 3, 100); | |||
| pulse_generator_.AddPulses(4, 2, 100); | |||
| pulse_generator_.AddPulses(8, 4, 100); | |||
| pulse_generator_.AddPulses(12, 6, 100); | |||
| return; | |||
| } else if (difficulty == PAUSE_PATTERNS) { | |||
| pulse_generator_.AddPulses(800, 400, 100); | |||
| pulse_generator_.AddPulses(32000 * 5 + 10, 400, 1); | |||
| pulse_generator_.AddPulses(800, 400, 100); | |||
| } | |||
| // Steady clock | |||
| pulse_generator_.AddPulses(400, 200, 250); | |||
| pulse_generator_.AddPulses(4000 + (rand() % 1000), 2000, 10); | |||
| pulse_generator_.AddPulses(100, 10, 50); | |||
| pulse_generator_.AddPulses(16, 5, 100); | |||
| // Periodic clock with some jitter | |||
| for (int i = 0; i < 50; ++i) { | |||
| pulse_generator_.AddPulses(100, 10, 3); | |||
| pulse_generator_.AddPulses(400 + (i % 4), 10, 1); | |||
| pulse_generator_.AddPulses((i == 40) ? 40 : 300, 10, 1); | |||
| } | |||
| for (int i = 0; i < 50; ++i) { | |||
| pulse_generator_.AddPulses(100 + (i % 10), 10, 3); | |||
| pulse_generator_.AddPulses(200 + (i % 4), 10, 2); | |||
| pulse_generator_.AddPulses(300, 10, 1); | |||
| } | |||
| // Really long pattern that hashes well | |||
| for (int i = 0; i < 15; ++i) { | |||
| for (int j = 0; j < 10; ++j) { | |||
| pulse_generator_.AddPulses(100 + j * 30, 10, 1); | |||
| } | |||
| } | |||
| for (int i = 0; i < 15; ++i) { | |||
| for (int j = 0; j < 6; ++j) { | |||
| pulse_generator_.AddPulses(300 - j * 50, 3, 2); | |||
| } | |||
| } | |||
| // Random clock with reliable pulse width | |||
| for (int i = 0; i < 300; ++i) { | |||
| int t = 100 + (rand() % 400); | |||
| pulse_generator_.AddPulses(t, t / 4, 1); | |||
| } | |||
| // Completely random clock. | |||
| for (int i = 0; i < 400; ++i) { | |||
| int t = 100 + (rand() % 400); | |||
| int pw = t / 4 + (rand() % (t / 2)); | |||
| pulse_generator_.AddPulses(t, pw, 1); | |||
| } | |||
| } | |||
| ~ClockGeneratorPatterns() { } | |||
| void Render(size_t size) { | |||
| pulse_generator_.Render(buffer_, size); | |||
| } | |||
| GateFlags* clock() { return buffer_; } | |||
| private: | |||
| GateFlags buffer_[kAudioBlockSize]; | |||
| PulseGenerator pulse_generator_; | |||
| DISALLOW_COPY_AND_ASSIGN(ClockGeneratorPatterns); | |||
| }; | |||
| class MasterSlaveRampGenerator { | |||
| public: | |||
| MasterSlaveRampGenerator() { | |||
| ramp_extractor_.Init(4000.0f / kSampleRate); | |||
| ramp_divider_[0].Init(); | |||
| ramp_divider_[1].Init(); | |||
| } | |||
| ~MasterSlaveRampGenerator() { } | |||
| void Process(const GateFlags* clock, size_t size) { | |||
| Ratio r = { 1, 1 }; | |||
| ramp_extractor_.Process(r, true, clock, master_ramp_, size); | |||
| r.q = 2; | |||
| ramp_divider_[0].Process(r, master_ramp_, slave_ramp_1_, size); | |||
| r.p = 1; r.q = 2; | |||
| ramp_divider_[1].Process(r, master_ramp_, slave_ramp_2_, size); | |||
| } | |||
| Ramps ramps() { | |||
| Ramps r; | |||
| r.external = external_ramp_; | |||
| r.master = master_ramp_; | |||
| r.slave[0] = slave_ramp_1_; | |||
| r.slave[1] = slave_ramp_2_; | |||
| return r; | |||
| } | |||
| float* master_ramp() { return master_ramp_; } | |||
| float* slave_ramp_1() { return slave_ramp_1_; } | |||
| float* slave_ramp_2() { return slave_ramp_2_; } | |||
| private: | |||
| RampExtractor ramp_extractor_; | |||
| RampDivider ramp_divider_[2]; | |||
| float external_ramp_[kAudioBlockSize]; | |||
| float master_ramp_[kAudioBlockSize]; | |||
| float slave_ramp_1_[kAudioBlockSize]; | |||
| float slave_ramp_2_[kAudioBlockSize]; | |||
| DISALLOW_COPY_AND_ASSIGN(MasterSlaveRampGenerator); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_TEST_FIXTURES_H_ | |||
| @@ -0,0 +1,50 @@ | |||
| PACKAGES = marbles/test stmlib/utils marbles/ramp marbles/random marbles stmlib/dsp | |||
| VPATH = $(PACKAGES) | |||
| TARGET = marbles_test | |||
| BUILD_ROOT = build/ | |||
| BUILD_DIR = $(BUILD_ROOT)$(TARGET)/ | |||
| CC_FILES = marbles_test.cc \ | |||
| lag_processor.cc \ | |||
| output_channel.cc \ | |||
| quantizer.cc \ | |||
| discrete_distribution_quantizer.cc \ | |||
| ramp_extractor.cc \ | |||
| random.cc \ | |||
| resources.cc \ | |||
| units.cc \ | |||
| t_generator.cc \ | |||
| x_y_generator.cc | |||
| OBJ_FILES = $(CC_FILES:.cc=.o) | |||
| OBJS = $(patsubst %,$(BUILD_DIR)%,$(OBJ_FILES)) $(STARTUP_OBJ) | |||
| DEPS = $(OBJS:.o=.d) | |||
| DEP_FILE = $(BUILD_DIR)depends.mk | |||
| all: marbles_test | |||
| $(BUILD_DIR): | |||
| mkdir -p $(BUILD_DIR) | |||
| $(BUILD_DIR)%.o: %.cc | |||
| g++ -c -DTEST -g -Wall -Werror -msse2 -Wno-unused-variable -O2 -I. $< -o $@ | |||
| $(BUILD_DIR)%.d: %.cc | |||
| g++ -MM -DTEST -I. $< -MF $@ -MT $(@:.d=.o) | |||
| marbles_test: $(OBJS) | |||
| g++ -g -o $(TARGET) $(OBJS) -Wl,-no_pie -lm -lprofiler -L/opt/local/lib | |||
| depends: $(DEPS) | |||
| cat $(DEPS) > $(DEP_FILE) | |||
| $(DEP_FILE): $(BUILD_DIR) $(DEPS) | |||
| cat $(DEPS) > $(DEP_FILE) | |||
| profile: marbles_test | |||
| env CPUPROFILE_FREQUENCY=1000 CPUPROFILE=$(BUILD_DIR)/marbles.prof ./marbles_test && pprof --pdf ./marbles_test $(BUILD_DIR)/marbles.prof > profile.pdf && open profile.pdf | |||
| clean: | |||
| rm $(BUILD_DIR)*.* | |||
| include $(DEP_FILE) | |||
| @@ -0,0 +1,721 @@ | |||
| // Copyright 2015 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 "marbles/cv_reader_channel.h" | |||
| #include "marbles/note_filter.h" | |||
| #include "marbles/ramp/ramp_divider.h" | |||
| #include "marbles/ramp/ramp_extractor.h" | |||
| #include "marbles/random/distributions.h" | |||
| #include "marbles/random/output_channel.h" | |||
| #include "marbles/random/random_generator.h" | |||
| #include "marbles/random/random_sequence.h" | |||
| #include "marbles/random/random_stream.h" | |||
| #include "marbles/random/t_generator.h" | |||
| #include "marbles/random/x_y_generator.h" | |||
| #include "marbles/scale_recorder.h" | |||
| #include "marbles/test/fixtures.h" | |||
| #include "marbles/test/ramp_checker.h" | |||
| #include "stmlib/test/wav_writer.h" | |||
| #include "stmlib/utils/random.h" | |||
| using namespace marbles; | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| void TestBetaDistribution() { | |||
| // Plot result with: | |||
| // import numpy | |||
| // import pylab | |||
| // data = numpy.loadtxt('marbles_histograms.txt') | |||
| // n = 0 | |||
| // for i in xrange(9): | |||
| // for j in xrange(13): | |||
| // pylab.subplot(9, 13, n + 1) | |||
| // pylab.plot(data[(n * 101):((n + 1) * 101)]) | |||
| // pylab.gca().get_xaxis().set_visible(False) | |||
| // pylab.gca().get_yaxis().set_visible(False) | |||
| // n += 1 | |||
| // pylab.show() | |||
| FILE* fp = fopen("marbles_histograms.txt", "w"); | |||
| for (int i = 0; i < 9; ++i) { | |||
| for (int j = 0; j < 13; ++j) { | |||
| float bias = float(i) / 8.0f; | |||
| float range = float(j) / 12.0f; | |||
| vector<int> histogram(101); | |||
| for (int n = 0; n < 1000000; ++n) { | |||
| float value = BetaDistributionSample(Random::GetFloat(), range, bias); | |||
| histogram[int(value * 100.0f)]++; | |||
| } | |||
| for (int n = 0; n < 101; ++n) { | |||
| fprintf(fp, "%d\n", histogram[n]); | |||
| } | |||
| } | |||
| } | |||
| fclose(fp); | |||
| } | |||
| void TestQuantizer() { | |||
| // Plot result with: | |||
| // import numpy | |||
| // import pylab | |||
| // | |||
| // data = numpy.loadtxt('marbles_quantizer.txt') | |||
| // pylab.figure(figsize=(25,5)) | |||
| // for i in xrange(9): | |||
| // pylab.subplot(1, 9, i + 1) | |||
| // indices = numpy.where(data[:, 0] == i)[0] | |||
| // pylab.plot(data[indices, 1], data[indices, 2]) | |||
| // pylab.show() | |||
| FILE* fp = fopen("marbles_quantizer.txt", "w"); | |||
| Quantizer q; | |||
| Scale scale; | |||
| scale.InitMajor(); | |||
| q.Init(scale); | |||
| for (int i = 0; i <= 8; ++i) { | |||
| float amount = float(i) / 8.0f; | |||
| for (int j = 0; j <= 4000; ++j) { | |||
| float value = j / 1000.0f - 2.0f; | |||
| fprintf(fp, "%d %f %f\n", i, value, q.Process(value, amount, false)); | |||
| } | |||
| } | |||
| fclose(fp); | |||
| } | |||
| void TestQuantizerNoise() { | |||
| // Plot result with: | |||
| // import numpy | |||
| // import pylab | |||
| // | |||
| // data = numpy.loadtxt('marbles_quantizer_hysteresis.txt') | |||
| // pylab.plot(data) | |||
| // pylab.show() | |||
| FILE* fp = fopen("marbles_quantizer_hysteresis.txt", "w"); | |||
| Quantizer q; | |||
| Scale scale; | |||
| scale.InitTenth(); | |||
| q.Init(scale); | |||
| for (int j = 0; j <= 4000; ++j) { | |||
| float noise = (rand() % 500) / 250.0f - 1.0f; | |||
| float tri = j / 2000.0f; | |||
| if (tri >= 1.0f) tri = 2.0f - tri; | |||
| float value = 1.0f * tri + noise * 1.0f / 60.0f; | |||
| float result = q.Process(value, 0.18f, true); | |||
| fprintf(fp, "%f %f\n", value, result); | |||
| } | |||
| fclose(fp); | |||
| } | |||
| void TestRampExtractorClockBug() { | |||
| WavWriter wav_writer(2, ::kSampleRate, 20); | |||
| wav_writer.Open("marbles_ramp_extractor_clock_bug.wav"); | |||
| RandomGenerator random_generator; | |||
| RandomStream random_stream; | |||
| random_generator.Init(33); | |||
| random_stream.Init(&random_generator); | |||
| PulseGenerator pulse_generator; | |||
| for (size_t i = 0; i < 700; ++i) { | |||
| int t = (rand() % 8) + 796; | |||
| pulse_generator.AddPulses(t, t >> 1, 1); | |||
| } | |||
| TGenerator generator; | |||
| generator.Init(&random_stream, kSampleRate); | |||
| generator.set_model(T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI); | |||
| generator.set_rate(0.0f); | |||
| generator.set_pulse_width_mean(0.5f); | |||
| generator.set_pulse_width_std(0.0f); | |||
| generator.set_bias(0.5f); | |||
| generator.set_jitter(0.0f); | |||
| generator.set_deja_vu(0.0f); | |||
| generator.set_length(8); | |||
| generator.set_range(T_GENERATOR_RANGE_1X); | |||
| MasterSlaveRampGenerator ms_ramp_generator; | |||
| Ramps ramps = ms_ramp_generator.ramps(); | |||
| float phase = 0.0f; | |||
| for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
| phase += 0.0001f; | |||
| if (phase >= 1.0f) { | |||
| phase -= 1.0f; | |||
| } | |||
| float tri = phase < 0.5f ? 2.0f * phase : 2.0f - 2.0f * phase; | |||
| bool gate[kAudioBlockSize * 2]; | |||
| GateFlags clock[kAudioBlockSize]; | |||
| pulse_generator.Render(clock, kAudioBlockSize); | |||
| generator.set_rate(tri * 24.0f - 12.0f); | |||
| generator.Process( | |||
| true, | |||
| clock, | |||
| ramps, | |||
| gate, | |||
| kAudioBlockSize); | |||
| for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
| float s[6]; | |||
| s[0] = clock[j] ? 1.0f : 0.0f; | |||
| s[1] = ramps.external[j]; | |||
| wav_writer.Write(s, 2, 32767.0f); | |||
| } | |||
| } | |||
| } | |||
| void TestRampExtractorPause() { | |||
| WavWriter wav_writer(6, ::kSampleRate, 10); | |||
| wav_writer.Open("marbles_ramp_pause.wav"); | |||
| RandomGenerator random_generator; | |||
| RandomStream random_stream; | |||
| random_generator.Init(33); | |||
| random_stream.Init(&random_generator); | |||
| TGenerator generator; | |||
| generator.Init(&random_stream, kSampleRate); | |||
| generator.set_model(T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI); | |||
| generator.set_rate(0.0f); | |||
| generator.set_pulse_width_mean(0.0f); | |||
| generator.set_pulse_width_std(0.0f); | |||
| generator.set_bias(0.9f); | |||
| generator.set_jitter(0.0f); | |||
| generator.set_deja_vu(0.0f); | |||
| generator.set_length(8); | |||
| generator.set_range(T_GENERATOR_RANGE_0_25X); | |||
| ClockGeneratorPatterns patterns(PAUSE_PATTERNS); | |||
| MasterSlaveRampGenerator ms_ramp_generator; | |||
| Ramps ramps = ms_ramp_generator.ramps(); | |||
| for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
| bool gate[kAudioBlockSize * 2]; | |||
| patterns.Render(kAudioBlockSize); | |||
| generator.Process( | |||
| true, | |||
| patterns.clock(), | |||
| ramps, | |||
| gate, | |||
| kAudioBlockSize); | |||
| for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
| float s[6]; | |||
| s[5] = s[0] = patterns.clock()[j] & GATE_FLAG_HIGH ? 0.8f : 0.0f; | |||
| s[1] = ramps.external[j]; | |||
| s[2] = ramps.master[j]; | |||
| s[3] = ramps.slave[0][j]; | |||
| s[4] = ramps.slave[1][j]; | |||
| wav_writer.Write(s, 6, 32767.0f); | |||
| } | |||
| } | |||
| } | |||
| void TestRampDivider(PatternDifficulty difficulty, const char* file_name) { | |||
| WavWriter wav_writer(4, ::kSampleRate, 10); | |||
| wav_writer.Open(file_name); | |||
| ClockGeneratorPatterns patterns(difficulty); | |||
| RampExtractor ramp_extractor; | |||
| RampDivider ramp_divider; | |||
| RampDivider ramp_divider_double; | |||
| RampDivider ramp_divider_half; | |||
| ramp_extractor.Init(0.25f); | |||
| ramp_divider.Init(); | |||
| ramp_divider_double.Init(); | |||
| ramp_divider_half.Init(); | |||
| RampChecker ramp_checker[4]; | |||
| Ratio r8x; | |||
| r8x.p = 8; | |||
| r8x.q = 1; | |||
| Ratio r2x; | |||
| r2x.p = 2; | |||
| r2x.q = 1; | |||
| Ratio half; | |||
| half.p = 1; | |||
| half.q = 2; | |||
| Ratio one; | |||
| one.p = 1; | |||
| one.q = 1; | |||
| for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
| float ramp[kAudioBlockSize]; | |||
| float divided_ramp[kAudioBlockSize]; | |||
| float divided_ramp_double[kAudioBlockSize]; | |||
| float divided_ramp_half[kAudioBlockSize]; | |||
| // switch ((i / (kSampleRate / 4)) % 3) { | |||
| // case 0: | |||
| // ramp_divider.set_ratio(1, 2); | |||
| // break; | |||
| // case 1: | |||
| // ramp_divider.set_ratio(4, 1); | |||
| // break; | |||
| // case 2: | |||
| // ramp_divider.set_ratio(3, 2); | |||
| // break; | |||
| // } | |||
| patterns.Render(kAudioBlockSize); | |||
| ramp_extractor.Process(one, true, patterns.clock(), ramp, kAudioBlockSize); | |||
| ramp_divider.Process(r8x, ramp, divided_ramp, kAudioBlockSize); | |||
| ramp_divider_double.Process(r2x, divided_ramp, divided_ramp_double, kAudioBlockSize); | |||
| ramp_divider_half.Process(half, divided_ramp, divided_ramp_half, kAudioBlockSize); | |||
| for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
| float s[4]; | |||
| s[0] = ramp[j]; | |||
| s[1] = divided_ramp[j]; | |||
| s[2] = divided_ramp_double[j]; | |||
| s[3] = divided_ramp_half[j]; | |||
| wav_writer.Write(s, 4, 32767.0f); | |||
| } | |||
| ramp_checker[0].Check(ramp, kAudioBlockSize); | |||
| ramp_checker[1].Check(divided_ramp, kAudioBlockSize); | |||
| ramp_checker[2].Check(divided_ramp_double, kAudioBlockSize); | |||
| ramp_checker[3].Check(divided_ramp_half, kAudioBlockSize); | |||
| } | |||
| } | |||
| void TestOutputChannel() { | |||
| WavWriter wav_writer(2, ::kSampleRate, 3); | |||
| wav_writer.Open("marbles_random_voltage.wav"); | |||
| RampExtractor ramp_extractor; | |||
| RandomGenerator random_generator; | |||
| RandomStream random_stream; | |||
| RandomSequence random_sequence; | |||
| OutputChannel output_channel; | |||
| PulseGenerator pulse_generator; | |||
| ramp_extractor.Init(0.25f); | |||
| random_generator.Init(32); | |||
| random_stream.Init(&random_generator); | |||
| random_sequence.Init(&random_stream); | |||
| output_channel.Init(); | |||
| // Steady clock | |||
| pulse_generator.AddPulses(400, 10, 250); | |||
| pulse_generator.AddPulses(200, 10, 250); | |||
| output_channel.set_register_mode(false); | |||
| output_channel.set_steps(0.5f); | |||
| output_channel.set_bias(0.5f); | |||
| Ratio one; | |||
| one.p = 1; | |||
| one.q = 1; | |||
| for (size_t i = 0; i < ::kSampleRate * 3; i += kAudioBlockSize) { | |||
| GateFlags gate_flags[kAudioBlockSize]; | |||
| float ramp[kAudioBlockSize]; | |||
| float voltage[kAudioBlockSize]; | |||
| pulse_generator.Render(gate_flags, kAudioBlockSize); | |||
| ramp_extractor.Process(one, false, gate_flags, ramp, kAudioBlockSize); | |||
| output_channel.set_spread(wav_writer.triangle(3)); | |||
| output_channel.Process(&random_sequence, ramp, voltage, kAudioBlockSize, 1); | |||
| for (size_t j = 0; j < kAudioBlockSize; ++j) voltage[j] *= 0.1f; | |||
| wav_writer.Write(ramp, voltage, kAudioBlockSize); | |||
| } | |||
| } | |||
| void TestXYGenerator() { | |||
| WavWriter wav_writer(4, ::kSampleRate, 10); | |||
| wav_writer.Open("marbles_xy.wav"); | |||
| RandomGenerator random_generator; | |||
| RandomStream random_stream; | |||
| random_generator.Init(32); | |||
| random_stream.Init(&random_generator); | |||
| XYGenerator generator; | |||
| generator.Init(&random_stream, ::kSampleRate); | |||
| GroupSettings x_settings, y_settings; | |||
| x_settings.control_mode = CONTROL_MODE_IDENTICAL; | |||
| x_settings.voltage_range = VOLTAGE_RANGE_FULL; | |||
| x_settings.register_mode = false; | |||
| x_settings.register_value = 0.0f; | |||
| x_settings.spread = 0.8f; | |||
| x_settings.bias = 0.8f; | |||
| x_settings.steps = 0.3f; | |||
| x_settings.deja_vu = 0.0f; | |||
| x_settings.length = 8; | |||
| x_settings.ratio.p = 1; | |||
| x_settings.ratio.q = 1; | |||
| y_settings.control_mode = CONTROL_MODE_IDENTICAL; | |||
| y_settings.voltage_range = VOLTAGE_RANGE_FULL; | |||
| y_settings.register_mode = false; | |||
| y_settings.register_value = 0.0f; | |||
| y_settings.spread = 0.5f; | |||
| y_settings.bias = 0.5f; | |||
| y_settings.steps = 0.1f; | |||
| y_settings.deja_vu = 0.0f; | |||
| y_settings.length = 8; | |||
| y_settings.ratio.p = 1; | |||
| y_settings.ratio.q = 8; | |||
| ClockGeneratorPatterns patterns(TRICKY_PATTERNS); | |||
| MasterSlaveRampGenerator ms_ramp_generator; | |||
| for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
| patterns.Render(kAudioBlockSize); | |||
| ms_ramp_generator.Process(patterns.clock(), kAudioBlockSize); | |||
| float samples[kAudioBlockSize * 4]; | |||
| generator.Process( | |||
| CLOCK_SOURCE_INTERNAL_T1_T2_T3, | |||
| x_settings, | |||
| y_settings, | |||
| patterns.clock(), | |||
| ms_ramp_generator.ramps(), | |||
| samples, | |||
| kAudioBlockSize); | |||
| wav_writer.Write(samples, kAudioBlockSize * 4, 3276.7f); | |||
| } | |||
| } | |||
| void TestXYGeneratorASR() { | |||
| WavWriter wav_writer(4, ::kSampleRate, 10); | |||
| wav_writer.Open("marbles_xy_asr.wav"); | |||
| RandomGenerator random_generator; | |||
| RandomStream random_stream; | |||
| random_generator.Init(32); | |||
| random_stream.Init(&random_generator); | |||
| XYGenerator generator; | |||
| generator.Init(&random_stream, ::kSampleRate); | |||
| GroupSettings x_settings, y_settings; | |||
| x_settings.control_mode = CONTROL_MODE_IDENTICAL; | |||
| x_settings.voltage_range = VOLTAGE_RANGE_FULL; | |||
| x_settings.register_mode = false; | |||
| x_settings.register_value = 0.0f; | |||
| x_settings.spread = 0.2f; | |||
| x_settings.bias = 0.7f; | |||
| x_settings.steps = 0.5f; | |||
| x_settings.deja_vu = 0.0f; | |||
| x_settings.length = 8; | |||
| x_settings.ratio.p = 1; | |||
| x_settings.ratio.q = 1; | |||
| y_settings.control_mode = CONTROL_MODE_IDENTICAL; | |||
| y_settings.voltage_range = VOLTAGE_RANGE_FULL; | |||
| y_settings.register_mode = false; | |||
| y_settings.register_value = 0.0f; | |||
| y_settings.spread = 0.5f; | |||
| y_settings.bias = 0.5f; | |||
| y_settings.steps = 0.1f; | |||
| y_settings.deja_vu = 0.0f; | |||
| y_settings.length = 8; | |||
| y_settings.ratio.p = 1; | |||
| y_settings.ratio.q = 8; | |||
| ClockGeneratorPatterns patterns(TRICKY_PATTERNS); | |||
| MasterSlaveRampGenerator ms_ramp_generator; | |||
| for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
| patterns.Render(kAudioBlockSize); | |||
| ms_ramp_generator.Process(patterns.clock(), kAudioBlockSize); | |||
| float samples[kAudioBlockSize * 4]; | |||
| x_settings.register_mode = true; | |||
| x_settings.register_value = wav_writer.triangle(1); | |||
| generator.Process( | |||
| CLOCK_SOURCE_EXTERNAL, | |||
| x_settings, | |||
| y_settings, | |||
| patterns.clock(), | |||
| ms_ramp_generator.ramps(), | |||
| samples, | |||
| kAudioBlockSize); | |||
| wav_writer.Write(samples, kAudioBlockSize * 4, 3276.7f); | |||
| } | |||
| } | |||
| void TestTGenerator() { | |||
| WavWriter wav_writer(6, ::kSampleRate, 10); | |||
| wav_writer.Open("marbles_t.wav"); | |||
| RandomGenerator random_generator; | |||
| RandomStream random_stream; | |||
| random_generator.Init(33); | |||
| random_stream.Init(&random_generator); | |||
| TGenerator generator; | |||
| generator.Init(&random_stream, kSampleRate); | |||
| generator.set_model(T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI); | |||
| generator.set_rate(24.0f); | |||
| generator.set_pulse_width_mean(0.0f); | |||
| generator.set_pulse_width_std(0.0f); | |||
| generator.set_bias(0.9f); | |||
| generator.set_jitter(0.5f); | |||
| generator.set_deja_vu(0.0f); | |||
| generator.set_length(8); | |||
| generator.set_range(T_GENERATOR_RANGE_4X); | |||
| ClockGeneratorPatterns patterns(FRIENDLY_PATTERNS); | |||
| MasterSlaveRampGenerator ms_ramp_generator; | |||
| Ramps ramps = ms_ramp_generator.ramps(); | |||
| RampChecker ramp_checker[4]; | |||
| for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
| bool gate[kAudioBlockSize * 2]; | |||
| patterns.Render(kAudioBlockSize); | |||
| generator.Process( | |||
| true, | |||
| patterns.clock(), | |||
| ramps, | |||
| gate, | |||
| kAudioBlockSize); | |||
| if (i >= kSampleRate * 5) { | |||
| // generator.set_deja_vu(1.0f); | |||
| // generator.set_jitter(0.5f); | |||
| } | |||
| for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
| float s[6]; | |||
| s[0] = ramps.external[j]; | |||
| s[1] = ramps.master[j]; | |||
| s[2] = ramps.slave[0][j]; | |||
| s[3] = ramps.slave[1][j]; | |||
| s[4] = gate[j * 2] ? 1.0f : 0.0f; | |||
| s[5] = gate[j * 2 + 1] ? 1.0f : 0.0f; | |||
| wav_writer.Write(s, 6, 32767.0f); | |||
| } | |||
| ramp_checker[0].Check(ramps.external, kAudioBlockSize); | |||
| ramp_checker[1].Check(ramps.master, kAudioBlockSize); | |||
| ramp_checker[2].Check(ramps.slave[0], kAudioBlockSize); | |||
| ramp_checker[3].Check(ramps.slave[1], kAudioBlockSize); | |||
| } | |||
| } | |||
| void TestTGeneratorSuperFastClock() { | |||
| WavWriter wav_writer(6, ::kSampleRate, 10); | |||
| wav_writer.Open("marbles_t_super_fast.wav"); | |||
| RandomGenerator random_generator; | |||
| RandomStream random_stream; | |||
| random_generator.Init(33); | |||
| random_stream.Init(&random_generator); | |||
| TGenerator generator; | |||
| generator.Init(&random_stream, kSampleRate); | |||
| generator.set_model(T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI); | |||
| generator.set_rate(60.0f); | |||
| generator.set_pulse_width_mean(0.0f); | |||
| generator.set_pulse_width_std(0.0f); | |||
| generator.set_bias(0.5f); | |||
| generator.set_jitter(0.0f); | |||
| generator.set_deja_vu(0.0f); | |||
| generator.set_length(8); | |||
| generator.set_range(T_GENERATOR_RANGE_4X); | |||
| ClockGeneratorPatterns patterns(FAST_PATTERNS); | |||
| MasterSlaveRampGenerator ms_ramp_generator; | |||
| Ramps ramps = ms_ramp_generator.ramps(); | |||
| for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
| bool gate[kAudioBlockSize * 2]; | |||
| patterns.Render(kAudioBlockSize); | |||
| generator.Process( | |||
| true, | |||
| patterns.clock(), | |||
| ramps, | |||
| gate, | |||
| kAudioBlockSize); | |||
| for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
| float s[6]; | |||
| s[0] = ramps.external[j]; | |||
| s[1] = ramps.master[j]; | |||
| s[2] = ramps.slave[0][j]; | |||
| s[3] = ramps.slave[1][j]; | |||
| s[4] = gate[j * 2] ? 1.0f : 0.0f; | |||
| s[5] = gate[j * 2 + 1] ? 1.0f : 0.0f; | |||
| wav_writer.Write(s, 6, 32767.0f); | |||
| } | |||
| } | |||
| } | |||
| void TestTGeneratorRampIntegrity( | |||
| bool internal_clock, | |||
| float rate, | |||
| TGeneratorModel model, | |||
| float jitter) { | |||
| printf("Testing ramp integrity for intclock = %d\trate = %04.3f\tmodel = %d\tjitter = %04.3f\n", internal_clock, rate, model, jitter); | |||
| RandomGenerator random_generator; | |||
| RandomStream random_stream; | |||
| random_generator.Init(33); | |||
| random_stream.Init(&random_generator); | |||
| TGenerator generator; | |||
| generator.Init(&random_stream, kSampleRate); | |||
| generator.set_model(model); | |||
| generator.set_rate(rate + 36.0f); | |||
| generator.set_pulse_width_mean(0.0f); | |||
| generator.set_pulse_width_std(0.0f); | |||
| generator.set_bias(0.7f); | |||
| generator.set_jitter(jitter); | |||
| if (internal_clock) { | |||
| generator.set_range(T_GENERATOR_RANGE_4X); | |||
| } else { | |||
| generator.set_range(T_GENERATOR_RANGE_1X); | |||
| } | |||
| ClockGeneratorPatterns patterns(FRIENDLY_PATTERNS); | |||
| MasterSlaveRampGenerator ms_ramp_generator; | |||
| Ramps ramps = ms_ramp_generator.ramps(); | |||
| RampChecker ramp_checker[4]; | |||
| for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
| bool gate[kAudioBlockSize * 2]; | |||
| patterns.Render(kAudioBlockSize); | |||
| generator.Process( | |||
| !internal_clock, | |||
| patterns.clock(), | |||
| ramps, | |||
| gate, | |||
| kAudioBlockSize); | |||
| ramp_checker[0].Check(ramps.external, kAudioBlockSize); | |||
| ramp_checker[1].Check(ramps.master, kAudioBlockSize); | |||
| ramp_checker[2].Check(ramps.slave[0], kAudioBlockSize); | |||
| ramp_checker[3].Check(ramps.slave[1], kAudioBlockSize); | |||
| } | |||
| } | |||
| void TestTGeneratorRampIntegrity() { | |||
| for (size_t clock_source = 0; clock_source < 2; ++clock_source) { | |||
| for (size_t model = 0; model < 6; ++model) { | |||
| for (size_t jitter = 0; jitter < 5; ++jitter) { | |||
| for (size_t rate = 0; rate < 5; ++rate) { | |||
| TestTGeneratorRampIntegrity( | |||
| clock_source, | |||
| float(rate) * 12.0f - 24.0f, | |||
| TGeneratorModel(model), | |||
| float(jitter) * 0.2f); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void TestScaleRecorder() { | |||
| int prelude[] = { 0, 4, 7, 7, 12, 12, 16, 16, 7, 7, 12, 12, 16, 0, 0, 4, 16, | |||
| 4, 7, 7, 12, 12, 16, 16, 7, 7, 12, 12, 16, 0, 0, 4, 16, 2, 9, 9, 14, 14, | |||
| 17, 17, 9, 9, 14, 14, 17, 0, 0, 2, 17, 2, 9, 9, 14, 14, 17, 17, 9, 9, 14, | |||
| 14, 17, -1, 0, 2, 17, 2, 7, 7, 14, 14, 17, 17, 7, 7, 14, 14, 17, -1, -1, | |||
| 2, 17, 2, 7, 7, 14, 14, 17, 17, 7, 7, 14, 14, 17, -1, 0, 2, 17, 4, 7, 7, | |||
| 12, 12, 16, 16, 7, 7, 12, 12, 16, 0, 0, 4, 16, 4, 7, 7, 12, 12, 16, 16, 7, | |||
| 7, 12, 12, 16, 0, 0, 4, 16, 4, 9, 9, 16, 16, 21, 21, 9, 9, 16, 16, 21, 0, | |||
| 0, 4, 21, 4, 9, 9, 16, 16, 21, 21, 9, 9, 16, 16, 21, 0, 0, 4, 21, 2, 6, 6, | |||
| 9, 9, 14, 14, 6, 6, 9, 9, 14, 0, 0, 2, 14, 2, 6, 6, 9, 9, 14, 14, 6, 6, 9, | |||
| 9, 14, -1, 0, 2, 14, 2, 7, 7, 14, 14, 19, 19, 7, 7, 14, 14, 19, -1, -1, 2, | |||
| 19, 2, 7, 7, 14, 14, 19, 19, 7, 7, 14, 14, 19, -1, -1, 2, 19, 0, 4, 4, 7, | |||
| 7, 12, 12, 4, 4, 7, 7, 12, -1, -1, 0, 12, 0, 4, 4, 7, 7, 12, 12, 4, 4, 7, | |||
| 7, 12, -3, -1, 0, 12, 0, 4, 4, 7, 7, 12, 12, 4, 4, 7, 7, 12, -3, -3, 0, 12, | |||
| 0, 4, 4, 7, 7, 12, 12, 4, 4, 7, 7, 12, -10, -3, 0, 12, -3, 2, 2, 6, 6, 12, | |||
| 12, 2, 2, 6, 6, 12, -10, -10, -3, 12, -3, 2, 2, 6, 6, 12, 12, 2, 2, 6, 6, | |||
| 12, -10, -5, -3, 12, -1, 2, 2, 7, 7, 11, 11, 2, 2, 7, 7, 11, -5, -5, -1, | |||
| 11, -1, 2, 2, 7, 7, 11, 11, 2, 2, 7, 7, 11, -5, -5, -1, 11, -2, 4, 4, 7, 7, | |||
| 13, 13, 4, 4, 7, 7, 13, -5, -5, -2, 13, -2, 4, 4, 7, 7, 13, 13, 4, 4, 7, 7, | |||
| 13, -7, -5, -2, 13, -3, 2, 2, 9, 9, 14, 14, 2, 2, 9, 9, 14, -7, -7, -3, 14, | |||
| -3, 2, 2, 9, 9, 14, 14, 2, 2, 9, 9, 14, -7, -7, -3, 14, -4, 2, 2, 5, 5, 11, | |||
| 11, 2, 2, 5, 5, 11, -7, -7, -4, 11, -4, 2, 2, 5, 5, 11, 11, 2, 2, 5, 5, 11, | |||
| -8, -7, -4, 11, -5, 0, 0, 7, 7, 12, 12, 0, 0, 7, 7, 12, -8, -8, -5, 12, -5, | |||
| 0, 0, 7, 7, 12, 12, 0, 0, 7, 7, 12, -8, -8, -5, 12, -7, -3, -3, 0, 0, 5, 5, | |||
| -3, -3, 0, 0, 5, -8, -8, -7, 5, -7, -3, -3, 0, 0, 5, 5, -3, -3, 0, 0, 5, | |||
| -10, -8, -7, 5, -7, -3, -3, 0, 0, 5, 5, -3, -3, 0, 0, 5, -10, -10, -7, 5, | |||
| -7, -3, -3, 0, 0, 5, 5, -3, -3, 0, 0, 5, -17, -10, -7, 5, -10, -5, -5, -1, | |||
| -1, 5, 5, -5, -5, -1, -1, 5, -17, -17, -10, 5, -10, -5, -5, -1, -1, 5, 5, | |||
| -5, -5, -1, -1, 5, -17, -12, -10, 5, -8, -5, -5, 0, 0, 4, 4, -5, -5, 0, 0, | |||
| 4, -12, -12, -8, 4, -8, -5, -5, 0, 0, 4, 4, -5, -5, 0, 0, 4, -12, -12, -8, | |||
| 4, -5, -2, -2, 0, 0, 4, 4, -2, -2, 0, 0, 4, -12, -12, -5, 4, -5, -2, -2, 0, | |||
| 0, 4, 4, -2, -2, 0, 0, 4, -19, -12, -5, 4, -7, -3, -3, 0, 0, 4, 4, -3, -3, | |||
| 0, 0, 4, -19, -19, -7, 4, -7, -3, -3, 0, 0, 4, 4, -3, -3, 0, 0, 4, -19, | |||
| -18, -7, 4, -12, -3, -3, 0, 0, 3, 3, -3, -3, 0, 0, 3, -18, -18, -12, 3, | |||
| -12, -3, -3, 0, 0, 3, 3, -3, -3, 0, 0, 3, -18, -16, -12, 3, -7, -1, -1, 0, | |||
| 0, 2, 2, -1, -1, 0, 0, 2, -16, -16, -7, 2, -7, -1, -1, 0, 0, 2, 2, -1, -1, | |||
| 0, 0, 2, -17, -16, -7, 2, -7, -5, -5, -1, -1, 2, 2, -5, -5, -1, -1, 2, -17, | |||
| -17, -7, 2, -7, -5, -5, -1, -1, 2, 2, -5, -5, -1, -1, 2, -17, -17, -7, | |||
| 2, -8, -5, -5, 0, 0, 4, 4, -5, -5, 0, 0, 4, -17, -17, -8, 4, -8, -5, -5, 0, | |||
| 0, 4, 4, -5, -5, 0, 0, 4, -17, -17, -8, 4, -10, -5, -5, 0, 0, 5, 5, -5, -5, | |||
| 0, 0, 5, -17, -17, -10, 5, -10, -5, -5, 0, 0, 5, 5, -5, -5, 0, 0, 5, -17, | |||
| -17, -10, 5,-10, -5, -5, -1, -1, 5, 5, -5, -5, -1, -1, 5, -17, -17, -10, 5, | |||
| -10, -5, -5,-1, -1, 5, 5, -5, -5, -1, -1, 5, -17, -17, -10, 5, -9, -3, -3, | |||
| 0, 0, 6, 6, -3,-3, 0, 0, 6, -17, -17, -9, 6, -9, -3, -3, 0, 0, 6, 6, -3, | |||
| -3, 0, 0, 6, -17,-17, -9, 6, -8, -5, -5, 0, 0, 7, 7, -5, -5, 0, 0, 7, -17, | |||
| -17, -8, 7, -8, -5,-5, 0, 0, 7, 7, -5, -5, 0, 0, 7, -17, -17, -8, 7, -10, | |||
| -5, -5, 0, 0, 5, 5, -5,-5, 0, 0, 5, -17, -17, -10, 5, -10, -5, -5, 0, 0, 5, | |||
| 5, -5, -5, 0, 0, 5, -17,-17, -10, 5, -10, -5, -5, -1, -1, 5, 5, -5, -5, -1, | |||
| -1, 5, -17, -17, -10, 5,-10, -5, -5, -1, -1, 5, 5, -5, -5, -1, -1, 5, -24, | |||
| -17, -10, 5, -12, -5, -5,-2, -2, 4, 4, -5, -5, -2, -2, 4, -24, -24, -12, 4, | |||
| -12, -5, -5, -2, -2, 4, 4,-5, -5, -2, -2, 4, -24, -24, -12, 4, -12, -7, -7, | |||
| -3, -3, 0, 0, 5, 0, 5, -3, 0,-3, 0, -3, 0, -7, -3, -7, -3, -7, -3, -10, -7, | |||
| -10, -7, -10, -7, -12, -10, -24,-24, -10, 7, 7, 11, 11, 14, 14, 17, 14, 17, | |||
| 11, 14, 11, 14, 11, 14, 7, 11, 7,11, 2, 11, 2, 5, 4, 5, 2, 4, -10, 2, -24, | |||
| -24, -12, 4, 7, 12, -24, -12, 4, 7, 12 | |||
| }; | |||
| ScaleRecorder recorder; | |||
| recorder.Init(); | |||
| for (size_t i = 0; i < sizeof(prelude) / sizeof(int); ++i) { | |||
| float voltage = prelude[i] / 12.0f + Random::GetFloat() * 0.0001f; | |||
| recorder.NewNote(voltage); | |||
| recorder.UpdateVoltage(voltage); | |||
| recorder.AcceptNote(); | |||
| } | |||
| Scale s; | |||
| recorder.ExtractScale(&s); | |||
| for (int i = 0; i < s.num_degrees; ++i) { | |||
| printf("%f %d\n", s.degree[i].voltage, s.degree[i].weight); | |||
| } | |||
| } | |||
| int main(void) { | |||
| // Test distributions and value processors. | |||
| // TestBetaDistribution(); | |||
| // TestQuantizer(); | |||
| // TestQuantizerNoise(); | |||
| // Ramp tests. | |||
| // TestRampExtractor(FRIENDLY_PATTERNS, "marbles_ramp_extractor_friendly.wav"); | |||
| // TestRampExtractor(TRICKY_PATTERNS, "marbles_ramp_extractor_tricky.wav"); | |||
| // TestRampExtractor(FAST_PATTERNS, "marbles_ramp_extractor_fast.wav"); | |||
| // TestRampDivider(TRICKY_PATTERNS, "marbles_ramp_divider_tricky.wav"); | |||
| // TestRampExtractorClockBug(); | |||
| // TestRampExtractorPause(); | |||
| // TestOutputChannel(); | |||
| // TestXYGenerator(); | |||
| // TestXYGeneratorASR(); | |||
| // TestTGeneratorRampIntegrity(); | |||
| TestTGenerator(); | |||
| // TestScaleRecorder(); | |||
| } | |||
| @@ -0,0 +1,84 @@ | |||
| // Copyright 2015 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/>. | |||
| #ifndef MARBLES_TEST_RAMP_CHECKER_H_ | |||
| #define MARBLES_TEST_RAMP_CHECKER_H_ | |||
| #include <cstdio> | |||
| namespace marbles { | |||
| class RampChecker { | |||
| public: | |||
| RampChecker() { | |||
| previous_phase_ = 0.0f; | |||
| counter_ = 0; | |||
| average_frequency_ = 0.1f; | |||
| stall_counter_ = 0; | |||
| } | |||
| ~RampChecker() { } | |||
| void Error(const char* message) { | |||
| printf("RAMP ERROR: %.5f %s\n", float(counter_) / kSampleRate, message); | |||
| } | |||
| void Check(float* ramp, size_t size) { | |||
| while (size--) { | |||
| float phase = *ramp++; | |||
| float frequency = phase - previous_phase_; | |||
| if (phase < 0.0f || phase > 1.0f) { | |||
| Error("incorrect ramp value"); | |||
| } | |||
| if (frequency < 0.0f) { | |||
| if (average_frequency_ < 0.05f) { | |||
| if (previous_phase_ < 0.75f) { | |||
| Error("reset without having reached maximum value"); | |||
| } | |||
| if (phase > 0.25f) { | |||
| Error("does not reset to zero"); | |||
| } | |||
| } | |||
| } else { | |||
| average_frequency_ += 0.1f * (frequency - average_frequency_); | |||
| } | |||
| if (frequency == 0.0f && phase < 0.01f) { | |||
| ++stall_counter_; | |||
| if (stall_counter_ == 40) { | |||
| Error("Ramp stalls after reset"); | |||
| } | |||
| } else { | |||
| stall_counter_ = 0; | |||
| } | |||
| if (counter_ > 10 && frequency > 0.5f) { | |||
| Error("High slope"); | |||
| } | |||
| previous_phase_ = phase; | |||
| ++counter_; | |||
| } | |||
| } | |||
| private: | |||
| size_t stall_counter_; | |||
| float previous_phase_; | |||
| float average_frequency_; | |||
| size_t counter_; | |||
| DISALLOW_COPY_AND_ASSIGN(RampChecker); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_TEST_RAMP_CHECKER_H_ | |||
| @@ -0,0 +1,533 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // User interface. | |||
| #include "marbles/ui.h" | |||
| #include <algorithm> | |||
| #include "stmlib/system/system_clock.h" | |||
| #include "marbles/drivers/clock_inputs.h" | |||
| #include "marbles/cv_reader.h" | |||
| #include "marbles/scale_recorder.h" | |||
| #include "marbles/settings.h" | |||
| namespace marbles { | |||
| const int32_t kLongPressDuration = 2000; | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| /* static */ | |||
| const LedColor Ui::palette_[4] = { | |||
| LED_COLOR_GREEN, | |||
| LED_COLOR_YELLOW, | |||
| LED_COLOR_RED, | |||
| LED_COLOR_OFF | |||
| }; | |||
| /* static */ | |||
| AlternateKnobMapping Ui::alternate_knob_mappings_[ADC_CHANNEL_LAST]; | |||
| void Ui::Init( | |||
| Settings* settings, | |||
| CvReader* cv_reader, | |||
| ScaleRecorder* scale_recorder, | |||
| ClockInputs* clock_inputs) { | |||
| settings_ = settings; | |||
| cv_reader_ = cv_reader; | |||
| scale_recorder_ = scale_recorder; | |||
| clock_inputs_ = clock_inputs; | |||
| leds_.Init(); | |||
| switches_.Init(); | |||
| queue_.Init(); | |||
| // Initialize generator from settings_->state(); | |||
| fill(&pot_value_[0], &pot_value_[ADC_CHANNEL_LAST], 0.0f); | |||
| State* state = settings_->mutable_state(); | |||
| alternate_knob_mappings_[ADC_CHANNEL_T_BIAS].unlock_switch = SWITCH_T_MODEL; | |||
| alternate_knob_mappings_[ADC_CHANNEL_T_BIAS].destination = &state->t_pulse_width_mean; | |||
| alternate_knob_mappings_[ADC_CHANNEL_T_JITTER].unlock_switch = SWITCH_T_MODEL; | |||
| alternate_knob_mappings_[ADC_CHANNEL_T_JITTER].destination = &state->t_pulse_width_std; | |||
| alternate_knob_mappings_[ADC_CHANNEL_T_RATE].unlock_switch = SWITCH_X_MODE; | |||
| alternate_knob_mappings_[ADC_CHANNEL_T_RATE].destination = &state->y_divider; | |||
| alternate_knob_mappings_[ADC_CHANNEL_X_SPREAD].unlock_switch = SWITCH_X_MODE; | |||
| alternate_knob_mappings_[ADC_CHANNEL_X_SPREAD].destination = &state->y_spread; | |||
| alternate_knob_mappings_[ADC_CHANNEL_X_BIAS].unlock_switch = SWITCH_X_MODE; | |||
| alternate_knob_mappings_[ADC_CHANNEL_X_BIAS].destination = &state->y_bias; | |||
| alternate_knob_mappings_[ADC_CHANNEL_X_STEPS].unlock_switch = SWITCH_X_MODE; | |||
| alternate_knob_mappings_[ADC_CHANNEL_X_STEPS].destination = &state->y_steps; | |||
| setting_modification_flag_ = false; | |||
| output_test_mode_ = false; | |||
| if (switches_.pressed_immediate(SWITCH_X_MODE)) { | |||
| if (state->color_blind == 1) { | |||
| state->color_blind = 0; | |||
| } else { | |||
| state->color_blind = 1; | |||
| } | |||
| settings_->SaveState(); | |||
| } | |||
| deja_vu_lock_ = false; | |||
| } | |||
| void Ui::SaveState() { | |||
| settings_->SaveState(); | |||
| } | |||
| void Ui::Poll() { | |||
| // 1kHz. | |||
| system_clock.Tick(); | |||
| switches_.Debounce(); | |||
| for (int i = 0; i < SWITCH_LAST; ++i) { | |||
| if (switches_.just_pressed(Switch(i))) { | |||
| queue_.AddEvent(CONTROL_SWITCH, i, 0); | |||
| press_time_[i] = system_clock.milliseconds(); | |||
| ignore_release_[i] = false; | |||
| } | |||
| if (switches_.pressed(Switch(i)) && !ignore_release_[i]) { | |||
| int32_t pressed_time = system_clock.milliseconds() - press_time_[i]; | |||
| if (pressed_time > kLongPressDuration && !setting_modification_flag_) { | |||
| queue_.AddEvent(CONTROL_SWITCH, i, pressed_time); | |||
| ignore_release_[i] = true; | |||
| } | |||
| } | |||
| if (switches_.released(Switch(i)) && !ignore_release_[i]) { | |||
| queue_.AddEvent( | |||
| CONTROL_SWITCH, | |||
| i, | |||
| system_clock.milliseconds() - press_time_[i] + 1); | |||
| ignore_release_[i] = true; | |||
| } | |||
| } | |||
| UpdateLEDs(); | |||
| } | |||
| /* static */ | |||
| LedColor Ui::MakeColor(uint8_t value, bool color_blind) { | |||
| bool slow_blink = (system_clock.milliseconds() & 255) > 128; | |||
| uint8_t bank = value >= 3 ? 1 : 0; | |||
| value -= bank * 3; | |||
| LedColor color = palette_[value]; | |||
| if (color_blind) { | |||
| uint8_t pwm_counter = system_clock.milliseconds() & 15; | |||
| uint8_t triangle = (system_clock.milliseconds() >> 5) & 31; | |||
| triangle = triangle < 16 ? triangle : 31 - triangle; | |||
| if (value == 0) { | |||
| color = pwm_counter < (4 + (triangle >> 2)) | |||
| ? LED_COLOR_GREEN | |||
| : LED_COLOR_OFF; | |||
| } else if (value == 1) { | |||
| color = LED_COLOR_YELLOW; | |||
| } else { | |||
| color = pwm_counter == 0 ? LED_COLOR_RED : LED_COLOR_OFF; | |||
| } | |||
| } | |||
| return slow_blink || !bank ? color : LED_COLOR_OFF; | |||
| } | |||
| void Ui::UpdateLEDs() { | |||
| bool blink = (system_clock.milliseconds() & 127) > 64; | |||
| bool slow_blink = (system_clock.milliseconds() & 255) > 128; | |||
| bool fast_blink = (system_clock.milliseconds() & 63) > 32; | |||
| const State& state = settings_->state(); | |||
| bool cb = state.color_blind == 1; | |||
| LedColor scale_color = state.x_scale < 3 | |||
| ? (slow_blink ? palette_[state.x_scale] : LED_COLOR_OFF) | |||
| : (fast_blink ? palette_[state.x_scale - 3] : LED_COLOR_OFF); | |||
| if (cb) { | |||
| int poly_counter = (system_clock.milliseconds() >> 6) % 12; | |||
| if ((poly_counter >> 1) < (state.x_scale + 1) && (poly_counter & 1)) { | |||
| scale_color = LED_COLOR_YELLOW; | |||
| } else { | |||
| scale_color = LED_COLOR_OFF; | |||
| } | |||
| } | |||
| leds_.Clear(); | |||
| int slow_triangle = (system_clock.milliseconds() & 1023) >> 5; | |||
| slow_triangle = slow_triangle >= 16 ? 31 - slow_triangle : slow_triangle; | |||
| int pw = system_clock.milliseconds() & 15; | |||
| bool deja_vu_glow = !deja_vu_lock_ || (slow_triangle >= pw); | |||
| switch (mode_) { | |||
| case UI_MODE_NORMAL: | |||
| case UI_MODE_RECORD_SCALE: | |||
| { | |||
| leds_.set(LED_T_MODEL, MakeColor(state.t_model, cb)); | |||
| leds_.set(LED_T_RANGE, MakeColor(state.t_range, cb)); | |||
| leds_.set(LED_T_DEJA_VU, | |||
| state.t_deja_vu && deja_vu_glow ? | |||
| LED_COLOR_GREEN : LED_COLOR_OFF); | |||
| leds_.set(LED_X_CONTROL_MODE, MakeColor(state.x_control_mode, cb)); | |||
| leds_.set(LED_X_DEJA_VU, | |||
| state.x_deja_vu && deja_vu_glow ? | |||
| LED_COLOR_GREEN : LED_COLOR_OFF); | |||
| if (mode_ == UI_MODE_NORMAL) { | |||
| leds_.set(LED_X_RANGE, | |||
| state.x_register_mode | |||
| ? LED_COLOR_OFF | |||
| : MakeColor(state.x_range, cb)); | |||
| leds_.set(LED_X_EXT, | |||
| state.x_register_mode ? LED_COLOR_GREEN : LED_COLOR_OFF); | |||
| } else { | |||
| leds_.set(LED_X_RANGE, scale_color); | |||
| leds_.set(LED_X_EXT, LED_COLOR_GREEN); | |||
| } | |||
| } | |||
| break; | |||
| case UI_MODE_SELECT_SCALE: | |||
| leds_.set(LED_X_RANGE, scale_color); | |||
| break; | |||
| case UI_MODE_CALIBRATION_1: | |||
| leds_.set(LED_T_RANGE, blink ? MakeColor(0, cb) : LED_COLOR_OFF); | |||
| break; | |||
| case UI_MODE_CALIBRATION_2: | |||
| leds_.set(LED_T_RANGE, blink ? MakeColor(1, cb) : LED_COLOR_OFF); | |||
| break; | |||
| case UI_MODE_CALIBRATION_3: | |||
| leds_.set(LED_X_RANGE, blink ? MakeColor(0, cb) : LED_COLOR_OFF); | |||
| break; | |||
| case UI_MODE_CALIBRATION_4: | |||
| leds_.set(LED_X_RANGE, blink ? MakeColor(1, cb) : LED_COLOR_OFF); | |||
| break; | |||
| case UI_MODE_PANIC: | |||
| leds_.set(LED_T_MODEL, blink ? LED_COLOR_RED : LED_COLOR_OFF); | |||
| leds_.set(LED_T_RANGE, !blink ? LED_COLOR_RED : LED_COLOR_OFF); | |||
| leds_.set(LED_X_CONTROL_MODE, !blink ? LED_COLOR_RED : LED_COLOR_OFF); | |||
| leds_.set(LED_X_RANGE, blink ? LED_COLOR_RED : LED_COLOR_OFF); | |||
| break; | |||
| } | |||
| leds_.Write(); | |||
| } | |||
| void Ui::FlushEvents() { | |||
| queue_.Flush(); | |||
| } | |||
| void Ui::OnSwitchPressed(const Event& e) { | |||
| } | |||
| void Ui::OnSwitchReleased(const Event& e) { | |||
| if (setting_modification_flag_) { | |||
| for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
| cv_reader_->mutable_channel(i)->UnlockPot(); | |||
| } | |||
| setting_modification_flag_ = false; | |||
| return; | |||
| } | |||
| // Check if the other switch is still pressed. | |||
| if (e.control_id == SWITCH_T_RANGE && switches_.pressed(SWITCH_X_RANGE)) { | |||
| mode_ = UI_MODE_CALIBRATION_1; | |||
| ignore_release_[SWITCH_T_RANGE] = ignore_release_[SWITCH_X_RANGE] = true; | |||
| return; | |||
| } | |||
| State* state = settings_->mutable_state(); | |||
| switch (e.control_id) { | |||
| case SWITCH_T_DEJA_VU: | |||
| state->t_deja_vu = !state->t_deja_vu; | |||
| break; | |||
| case SWITCH_X_DEJA_VU: | |||
| state->x_deja_vu = !state->x_deja_vu; | |||
| break; | |||
| case SWITCH_T_MODEL: | |||
| { | |||
| uint8_t bank = state->t_model / 3; | |||
| if (e.data >= kLongPressDuration) { | |||
| if (!bank) { | |||
| state->t_model += 3; | |||
| } | |||
| } else { | |||
| if (bank) { | |||
| state->t_model -= 3; | |||
| } else { | |||
| state->t_model = (state->t_model + 1) % 3; | |||
| } | |||
| } | |||
| SaveState(); | |||
| } | |||
| break; | |||
| case SWITCH_T_RANGE: | |||
| { | |||
| if (mode_ >= UI_MODE_CALIBRATION_1 && mode_ <= UI_MODE_CALIBRATION_4) { | |||
| NextCalibrationStep(); | |||
| } else { | |||
| state->t_range = (state->t_range + 1) % 3; | |||
| } | |||
| SaveState(); | |||
| } | |||
| break; | |||
| case SWITCH_X_MODE: | |||
| state->x_control_mode = (state->x_control_mode + 1) % 3; | |||
| SaveState(); | |||
| break; | |||
| case SWITCH_X_EXT: | |||
| if (mode_ == UI_MODE_RECORD_SCALE) { | |||
| int scale_index = settings_->state().x_scale; | |||
| bool success = true; | |||
| if (e.data >= kLongPressDuration) { | |||
| settings_->ResetScale(scale_index); | |||
| } else { | |||
| success = scale_recorder_->ExtractScale( | |||
| settings_->mutable_scale(scale_index)); | |||
| } | |||
| if (success) { | |||
| settings_->SavePersistentData(); | |||
| settings_->set_dirty_scale_index(scale_index); | |||
| } | |||
| mode_ = UI_MODE_NORMAL; | |||
| } else if (e.data >= kLongPressDuration) { | |||
| mode_ = UI_MODE_RECORD_SCALE; | |||
| scale_recorder_->Clear(); | |||
| } else { | |||
| state->x_register_mode = !state->x_register_mode; | |||
| SaveState(); | |||
| } | |||
| break; | |||
| case SWITCH_X_RANGE: | |||
| if (mode_ >= UI_MODE_CALIBRATION_1 && mode_ <= UI_MODE_CALIBRATION_4) { | |||
| NextCalibrationStep(); | |||
| } else if (e.data >= kLongPressDuration) { | |||
| if (mode_ == UI_MODE_NORMAL) { | |||
| mode_ = UI_MODE_SELECT_SCALE; | |||
| } | |||
| } else if (mode_ == UI_MODE_SELECT_SCALE) { | |||
| state->x_scale = (state->x_scale + 1) % kNumScales; | |||
| } else { | |||
| if (!state->x_register_mode) { | |||
| state->x_range = (state->x_range + 1) % 3; | |||
| } | |||
| } | |||
| SaveState(); | |||
| break; | |||
| } | |||
| } | |||
| void Ui::TerminateScaleRecording() { | |||
| for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
| cv_reader_->mutable_channel(i)->UnlockPot(); | |||
| } | |||
| mode_ = UI_MODE_NORMAL; | |||
| } | |||
| void Ui::NextCalibrationStep() { | |||
| switch (mode_) { | |||
| case UI_MODE_CALIBRATION_1: | |||
| cv_reader_->CalibrateOffsets(); | |||
| cv_reader_->CalibrateRateC1(); | |||
| mode_ = UI_MODE_CALIBRATION_2; | |||
| break; | |||
| case UI_MODE_CALIBRATION_2: | |||
| cv_reader_->CalibrateRateC3(); | |||
| mode_ = UI_MODE_CALIBRATION_3; | |||
| break; | |||
| case UI_MODE_CALIBRATION_3: | |||
| cv_reader_->CalibrateSpreadC1(); | |||
| mode_ = UI_MODE_CALIBRATION_4; | |||
| break; | |||
| case UI_MODE_CALIBRATION_4: | |||
| if (cv_reader_->CalibrateSpreadC3()) { | |||
| settings_->SavePersistentData(); | |||
| mode_ = UI_MODE_NORMAL; | |||
| } else { | |||
| mode_ = UI_MODE_PANIC; | |||
| } | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| void Ui::UpdateHiddenParameters() { | |||
| // Check if some pots have been moved. | |||
| for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
| float new_value = cv_reader_->channel(i).unscaled_pot(); | |||
| float old_value = pot_value_[i]; | |||
| bool changed = fabs(new_value - old_value) >= 0.008f; | |||
| if (changed) { | |||
| pot_value_[i] = new_value; | |||
| AlternateKnobMapping mapping = alternate_knob_mappings_[i]; | |||
| if (switches_.pressed(mapping.unlock_switch)) { | |||
| if (mapping.unlock_switch == SWITCH_T_RANGE && new_value < 0.1f) { | |||
| new_value = 0.0f; | |||
| } | |||
| *mapping.destination = static_cast<uint8_t>(new_value * 255.0f); | |||
| cv_reader_->mutable_channel(i)->LockPot(); | |||
| // The next time a switch is released, we unlock the pots. | |||
| setting_modification_flag_ = true; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void Ui::DoEvents() { | |||
| while (queue_.available()) { | |||
| Event e = queue_.PullEvent(); | |||
| if (e.control_type == CONTROL_SWITCH) { | |||
| if (e.data == 0) { | |||
| OnSwitchPressed(e); | |||
| } else { | |||
| OnSwitchReleased(e); | |||
| } | |||
| } | |||
| } | |||
| UpdateHiddenParameters(); | |||
| if (queue_.idle_time() > 800 && mode_ == UI_MODE_PANIC) { | |||
| mode_ = UI_MODE_NORMAL; | |||
| } | |||
| if (mode_ == UI_MODE_SELECT_SCALE) { | |||
| if (queue_.idle_time() > 4000) { | |||
| mode_ = UI_MODE_NORMAL; | |||
| queue_.Touch(); | |||
| } | |||
| } else if (queue_.idle_time() > 1000) { | |||
| queue_.Touch(); | |||
| } | |||
| } | |||
| uint8_t Ui::HandleFactoryTestingRequest(uint8_t command) { | |||
| uint8_t argument = command & 0x1f; | |||
| command = command >> 5; | |||
| uint8_t reply = 0; | |||
| switch (command) { | |||
| case FACTORY_TESTING_READ_POT: | |||
| case FACTORY_TESTING_READ_CV: | |||
| reply = cv_reader_->adc_value(argument); | |||
| break; | |||
| case FACTORY_TESTING_READ_NORMALIZATION: | |||
| reply = clock_inputs_->is_normalized(ClockInput(argument)) ? 255 : 0; | |||
| break; | |||
| case FACTORY_TESTING_READ_GATE: | |||
| reply = argument >= SWITCH_LAST | |||
| ? clock_inputs_->value(ClockInput(argument - SWITCH_LAST)) | |||
| : switches_.pressed(Switch(argument)); | |||
| break; | |||
| case FACTORY_TESTING_GENERATE_TEST_SIGNALS: | |||
| output_test_mode_ = static_cast<bool>(argument); | |||
| fill( | |||
| &output_test_forced_dac_code_[0], | |||
| &output_test_forced_dac_code_[4], | |||
| 0); | |||
| break; | |||
| case FACTORY_TESTING_CALIBRATE: | |||
| if (argument == 0) { | |||
| // Revert all settings before getting into calibration mode. | |||
| settings_->mutable_state()->t_deja_vu = 0; | |||
| settings_->mutable_state()->x_deja_vu = 0; | |||
| settings_->mutable_state()->t_model = 0; | |||
| settings_->mutable_state()->t_range = 1; | |||
| settings_->mutable_state()->x_control_mode = 0; | |||
| settings_->mutable_state()->x_range = 2; | |||
| settings_->mutable_state()->x_register_mode = 0; | |||
| settings_->SavePersistentData(); | |||
| mode_ = UI_MODE_CALIBRATION_1; | |||
| } else { | |||
| NextCalibrationStep(); | |||
| } | |||
| { | |||
| const CalibrationData& cal = settings_->calibration_data(); | |||
| float voltage = (argument & 1) == 0 ? 1.0f : 3.0f; | |||
| for (int i = 0; i < 4; ++i) { | |||
| output_test_forced_dac_code_[i] = static_cast<uint16_t>( | |||
| voltage * cal.dac_scale[i] + cal.dac_offset[i]); | |||
| } | |||
| } | |||
| queue_.Touch(); | |||
| break; | |||
| case FACTORY_TESTING_FORCE_DAC_CODE: | |||
| { | |||
| int channel = argument >> 2; | |||
| int step = argument & 0x3; | |||
| if (step == 0) { | |||
| output_test_forced_dac_code_[channel] = 0xaf35; | |||
| } else if (step == 1) { | |||
| output_test_forced_dac_code_[channel] = 0x1d98; | |||
| } else { | |||
| CalibrationData* cal = settings_->mutable_calibration_data(); | |||
| cal->dac_offset[channel] = static_cast<float>( | |||
| calibration_data_ & 0xffff); | |||
| cal->dac_scale[channel] = static_cast<float>( | |||
| calibration_data_ >> 16) * -0.125f; | |||
| output_test_forced_dac_code_[channel] = static_cast<uint16_t>(cal->dac_scale[channel] + cal->dac_offset[channel]); | |||
| settings_->SavePersistentData(); | |||
| } | |||
| } | |||
| break; | |||
| case FACTORY_TESTING_WRITE_CALIBRATION_DATA_NIBBLE: | |||
| calibration_data_ <<= 4; | |||
| calibration_data_ |= argument & 0xf; | |||
| break; | |||
| } | |||
| return reply; | |||
| } | |||
| } // namespace marbles | |||
| @@ -0,0 +1,146 @@ | |||
| // Copyright 2015 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // User interface. | |||
| #ifndef MARBLES_UI_H_ | |||
| #define MARBLES_UI_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include "stmlib/ui/event_queue.h" | |||
| #include "marbles/drivers/adc.h" | |||
| #include "marbles/drivers/leds.h" | |||
| #include "marbles/drivers/switches.h" | |||
| namespace marbles { | |||
| class ClockInputs; | |||
| class CvReader; | |||
| class ScaleRecorder; | |||
| class Settings; | |||
| enum UiMode { | |||
| UI_MODE_NORMAL, | |||
| UI_MODE_SELECT_SCALE, | |||
| UI_MODE_RECORD_SCALE, | |||
| UI_MODE_CALIBRATION_1, | |||
| UI_MODE_CALIBRATION_2, | |||
| UI_MODE_CALIBRATION_3, | |||
| UI_MODE_CALIBRATION_4, | |||
| UI_MODE_PANIC, | |||
| }; | |||
| enum FactoryTestingCommand { | |||
| FACTORY_TESTING_READ_POT, | |||
| FACTORY_TESTING_READ_CV, | |||
| FACTORY_TESTING_READ_GATE, | |||
| FACTORY_TESTING_GENERATE_TEST_SIGNALS, | |||
| FACTORY_TESTING_CALIBRATE, | |||
| FACTORY_TESTING_READ_NORMALIZATION, | |||
| FACTORY_TESTING_FORCE_DAC_CODE, | |||
| FACTORY_TESTING_WRITE_CALIBRATION_DATA_NIBBLE, | |||
| }; | |||
| struct AlternateKnobMapping { | |||
| Switch unlock_switch; | |||
| uint8_t* destination; | |||
| }; | |||
| class Ui { | |||
| public: | |||
| Ui() { } | |||
| ~Ui() { } | |||
| void Init( | |||
| Settings* settings, | |||
| CvReader* cv_reader, | |||
| ScaleRecorder* scale_recorder, | |||
| ClockInputs* clock_inputs); | |||
| void Poll(); | |||
| void DoEvents(); | |||
| void FlushEvents(); | |||
| uint8_t HandleFactoryTestingRequest(uint8_t command); | |||
| bool recording_scale() const { | |||
| return mode_ == UI_MODE_RECORD_SCALE; | |||
| } | |||
| bool output_test_mode() const { | |||
| return output_test_mode_; | |||
| } | |||
| uint16_t output_test_forced_dac_code(int i) const { | |||
| return output_test_forced_dac_code_[i]; | |||
| } | |||
| void set_deja_vu_lock(bool deja_vu_lock) { | |||
| deja_vu_lock_ = deja_vu_lock; | |||
| } | |||
| private: | |||
| void UpdateLEDs(); | |||
| void OnSwitchPressed(const stmlib::Event& e); | |||
| void OnSwitchReleased(const stmlib::Event& e); | |||
| void NextCalibrationStep(); | |||
| void SaveState(); | |||
| void UpdateHiddenParameters(); | |||
| void TerminateScaleRecording(); | |||
| static LedColor MakeColor(uint8_t value, bool color_blind); | |||
| stmlib::EventQueue<16> queue_; | |||
| Leds leds_; | |||
| Switches switches_; | |||
| uint32_t press_time_[SWITCH_LAST]; | |||
| bool ignore_release_[SWITCH_LAST]; | |||
| UiMode mode_; | |||
| Settings* settings_; | |||
| CvReader* cv_reader_; | |||
| ScaleRecorder* scale_recorder_; | |||
| ClockInputs* clock_inputs_; | |||
| static const LedColor palette_[4]; | |||
| static AlternateKnobMapping alternate_knob_mappings_[ADC_CHANNEL_LAST]; | |||
| float pot_value_[ADC_CHANNEL_LAST]; | |||
| bool setting_modification_flag_; | |||
| bool deja_vu_lock_; | |||
| bool output_test_mode_; | |||
| uint16_t output_test_forced_dac_code_[4]; | |||
| uint32_t calibration_data_; | |||
| DISALLOW_COPY_AND_ASSIGN(Ui); | |||
| }; | |||
| } // namespace marbles | |||
| #endif // MARBLES_UI_H_ | |||
| @@ -0,0 +1,267 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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 <stm32f37x_conf.h> | |||
| #include <cstring> | |||
| #include "stmlib/system/bootloader_utils.h" | |||
| #include "stmlib/system/flash_programming.h" | |||
| #include "stmlib/system/system_clock.h" | |||
| #include "stm_audio_bootloader/qpsk/packet_decoder.h" | |||
| #include "stm_audio_bootloader/qpsk/demodulator.h" | |||
| #include "plaits/drivers/audio_dac.h" | |||
| #include "plaits/drivers/firmware_update_adc.h" | |||
| #include "plaits/drivers/leds.h" | |||
| #include "plaits/drivers/switches.h" | |||
| extern "C" { | |||
| void NMI_Handler() { } | |||
| void HardFault_Handler() { while (1); } | |||
| void MemManage_Handler() { while (1); } | |||
| void BusFault_Handler() { while (1); } | |||
| void UsageFault_Handler() { while (1); } | |||
| void SVC_Handler() { } | |||
| void DebugMon_Handler() { } | |||
| void PendSV_Handler() { } | |||
| } | |||
| using namespace plaits; | |||
| using namespace std; | |||
| using namespace stm_audio_bootloader; | |||
| using namespace stmlib; | |||
| const double kSampleRate = 48000.0; | |||
| const double kModulationRate = 6000.0; | |||
| const double kBitRate = 12000.0; | |||
| const uint32_t kStartAddress = 0x08008000; | |||
| const uint16_t kPacketsPerPage = PAGE_SIZE / kPacketSize; | |||
| enum UiState { | |||
| UI_STATE_WAITING, | |||
| UI_STATE_RECEIVING, | |||
| UI_STATE_ERROR, | |||
| UI_STATE_WRITING | |||
| }; | |||
| AudioDac audio_dac; | |||
| FirmwareUpdateAdc adc; | |||
| Leds leds; | |||
| Switches switches; | |||
| PacketDecoder decoder; | |||
| Demodulator demodulator; | |||
| int discard_samples = 8000; | |||
| int32_t peak = 0; | |||
| int32_t gain_pot = 0; | |||
| uint32_t current_address; | |||
| uint16_t packet_index; | |||
| uint8_t rx_buffer[PAGE_SIZE]; | |||
| volatile bool switch_released = false; | |||
| volatile UiState ui_state; | |||
| inline void UpdateLeds() { | |||
| leds.Clear(); | |||
| // Show bargraph on the upper 4 LEDs. | |||
| int32_t pwm = system_clock.milliseconds() & 15; | |||
| leds.set(3, (peak >> 9) > pwm ? LED_COLOR_GREEN : 0); | |||
| leds.set(2, ((peak - 8192) >> 9) >= pwm ? LED_COLOR_GREEN : 0); | |||
| leds.set(1, ((peak - 16384) >> 9) >= pwm ? LED_COLOR_YELLOW : 0); | |||
| leds.set(0, ((peak - 16384 - 8192) >> 9) >= pwm ? LED_COLOR_RED : 0); | |||
| // Show status info on the lower 4 LEDs. | |||
| switch (ui_state) { | |||
| case UI_STATE_WAITING: | |||
| { | |||
| bool on = system_clock.milliseconds() & 128; | |||
| for (int i = 4; i < 8; ++i) { | |||
| leds.set(i, on ? LED_COLOR_YELLOW : LED_COLOR_OFF); | |||
| } | |||
| } | |||
| break; | |||
| case UI_STATE_RECEIVING: | |||
| { | |||
| int stage = (system_clock.milliseconds() >> 7) & 3; | |||
| leds.set(stage + 4, LED_COLOR_GREEN); | |||
| } | |||
| break; | |||
| case UI_STATE_ERROR: | |||
| { | |||
| bool on = system_clock.milliseconds() & 256; | |||
| for (int i = 0; i < 8; ++i) { | |||
| leds.set(i, on ? LED_COLOR_RED : LED_COLOR_OFF); | |||
| } | |||
| } | |||
| break; | |||
| case UI_STATE_WRITING: | |||
| { | |||
| for (uint8_t i = 4; i < 8; ++i) { | |||
| leds.set(i, LED_COLOR_GREEN); | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| leds.Write(); | |||
| } | |||
| extern "C" { | |||
| void SysTick_Handler() { | |||
| system_clock.Tick(); | |||
| switches.Debounce(); | |||
| if (switches.released(Switch(0))) { | |||
| switch_released = true; | |||
| } | |||
| UpdateLeds(); | |||
| } | |||
| } | |||
| void ProgramPage(const uint8_t* data, size_t size) { | |||
| FLASH_Unlock(); | |||
| FLASH_ErasePage(current_address); | |||
| const uint32_t* words = static_cast<const uint32_t*>( | |||
| static_cast<const void*>(data)); | |||
| for (size_t written = 0; written < size; written += 4) { | |||
| FLASH_ProgramWord(current_address, *words++); | |||
| current_address += 4; | |||
| } | |||
| } | |||
| void FillBuffer(AudioDac::Frame* output, size_t size) { | |||
| gain_pot = (adc.gain_pot() + 4095 * gain_pot) >> 12; | |||
| int32_t sample = 32768 - static_cast<int32_t>(adc.sample()); | |||
| adc.Convert(); | |||
| int32_t gain = ((gain_pot >> 1) * gain_pot >> 21) + 128; | |||
| sample = sample * gain >> 8; | |||
| CONSTRAIN(sample, -32767, 32767) | |||
| int32_t rect = abs(sample); | |||
| peak = rect > peak ? rect : (rect + 32767 * peak) >> 15; | |||
| if (!discard_samples) { | |||
| demodulator.PushSample(2048 + (sample >> 4)); | |||
| } else { | |||
| --discard_samples; | |||
| } | |||
| output->l = -sample; | |||
| output->r = -sample; | |||
| } | |||
| void InitializeReception() { | |||
| decoder.Init(1000, true); | |||
| demodulator.Init( | |||
| kModulationRate / kSampleRate * 4294967296.0, | |||
| kSampleRate / kModulationRate, | |||
| 2.0 * kSampleRate / kBitRate); | |||
| demodulator.SyncCarrier(true); | |||
| decoder.Reset(); | |||
| current_address = kStartAddress; | |||
| packet_index = 0; | |||
| ui_state = UI_STATE_WAITING; | |||
| } | |||
| void Init() { | |||
| adc.Init(); | |||
| leds.Init(); | |||
| switches.Init(); | |||
| audio_dac.Init(48000, 1); | |||
| audio_dac.Start(&FillBuffer); | |||
| SysTick_Config(F_CPU / 1000); | |||
| } | |||
| int main(void) { | |||
| Init(); | |||
| InitializeReception(); | |||
| bool exit_updater = !switches.pressed_immediate(Switch(0)); | |||
| while (!exit_updater) { | |||
| bool error = false; | |||
| if (demodulator.state() == DEMODULATOR_STATE_OVERFLOW) { | |||
| error = true; | |||
| } else { | |||
| demodulator.ProcessAtLeast(32); | |||
| } | |||
| while (demodulator.available() && !error && !exit_updater) { | |||
| uint8_t symbol = demodulator.NextSymbol(); | |||
| PacketDecoderState state = decoder.ProcessSymbol(symbol); | |||
| switch (state) { | |||
| case PACKET_DECODER_STATE_OK: | |||
| { | |||
| ui_state = UI_STATE_RECEIVING; | |||
| memcpy( | |||
| rx_buffer + (packet_index % kPacketsPerPage) * kPacketSize, | |||
| decoder.packet_data(), | |||
| kPacketSize); | |||
| ++packet_index; | |||
| if ((packet_index % kPacketsPerPage) == 0) { | |||
| ui_state = UI_STATE_WRITING; | |||
| ProgramPage(rx_buffer, PAGE_SIZE); | |||
| decoder.Reset(); | |||
| demodulator.SyncCarrier(false); | |||
| ui_state = UI_STATE_RECEIVING; | |||
| } else { | |||
| decoder.Reset(); | |||
| demodulator.SyncDecision(); | |||
| } | |||
| } | |||
| break; | |||
| case PACKET_DECODER_STATE_ERROR_CRC: | |||
| case PACKET_DECODER_STATE_ERROR_SYNC: | |||
| error = true; | |||
| break; | |||
| break; | |||
| case PACKET_DECODER_STATE_END_OF_TRANSMISSION: | |||
| exit_updater = true; | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| if (error) { | |||
| ui_state = UI_STATE_ERROR; | |||
| switch_released = false; | |||
| while (!switch_released); // Polled in ISR | |||
| InitializeReception(); | |||
| } | |||
| } | |||
| adc.DeInit(); | |||
| audio_dac.Stop(); | |||
| Uninitialize(); | |||
| JumpTo(kStartAddress); | |||
| while (1) { } | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| # Copyright 2016 Olivier Gillet. | |||
| # | |||
| # Author: Olivier Gillet (ol.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. | |||
| # System specifications | |||
| F_CRYSTAL = 8000000L | |||
| F_CPU = 72000000L | |||
| SYSCLOCK = SYSCLK_FREQ_72MHz | |||
| FAMILY = f37x | |||
| # USB = enabled | |||
| # Preferred upload command | |||
| UPLOAD_COMMAND = upload_jtag_erase_first | |||
| # Packages to build | |||
| TARGET = plaits_bootloader | |||
| PACKAGES = plaits/bootloader \ | |||
| plaits/drivers \ | |||
| stm_audio_bootloader/qpsk \ | |||
| stmlib/dsp \ | |||
| stmlib/utils \ | |||
| stmlib/system | |||
| TOOLCHAIN_PATH ?= /usr/local/arm-4.8.3/ | |||
| include stmlib/makefile.inc | |||
| @@ -0,0 +1,132 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the PCM5100 DAC. | |||
| #include "plaits/drivers/audio_dac.h" | |||
| #include <stm32f37x_conf.h> | |||
| namespace plaits { | |||
| /* static */ | |||
| AudioDac* AudioDac::instance_; | |||
| void AudioDac::Init(int sample_rate, size_t block_size) { | |||
| instance_ = this; | |||
| block_size_ = block_size; | |||
| callback_ = NULL; | |||
| InitializeGPIO(); | |||
| InitializeAudioInterface(sample_rate); | |||
| InitializeDMA(block_size); | |||
| } | |||
| void AudioDac::InitializeGPIO() { | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_10 | GPIO_Pin_11; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_5); | |||
| GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_5); | |||
| GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_5); | |||
| } | |||
| void AudioDac::InitializeAudioInterface(int sample_rate) { | |||
| RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); | |||
| SPI_I2S_DeInit(SPI2); | |||
| I2S_InitTypeDef i2s_init; | |||
| i2s_init.I2S_Mode = I2S_Mode_MasterTx; | |||
| i2s_init.I2S_Standard = I2S_Standard_Phillips; | |||
| i2s_init.I2S_DataFormat = I2S_DataFormat_16b; | |||
| i2s_init.I2S_MCLKOutput = I2S_MCLKOutput_Disable; | |||
| i2s_init.I2S_AudioFreq = sample_rate; | |||
| i2s_init.I2S_CPOL = I2S_CPOL_Low; | |||
| I2S_Init(SPI2, &i2s_init); | |||
| I2S_Cmd(SPI2, ENABLE); | |||
| } | |||
| void AudioDac::InitializeDMA(size_t block_size) { | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); | |||
| DMA_Cmd(DMA1_Channel5, DISABLE); | |||
| DMA_DeInit(DMA1_Channel5); | |||
| DMA_InitTypeDef dma_init; | |||
| dma_init.DMA_PeripheralBaseAddr = (uint32_t)&(SPI2->DR); | |||
| dma_init.DMA_MemoryBaseAddr = (uint32_t)(&tx_dma_buffer_[0]); | |||
| dma_init.DMA_DIR = DMA_DIR_PeripheralDST; | |||
| dma_init.DMA_BufferSize = 2 * block_size * 2; // 2 channels, 2 half blocks. | |||
| dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
| dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
| dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
| dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
| dma_init.DMA_Mode = DMA_Mode_Circular; | |||
| dma_init.DMA_Priority = DMA_Priority_High; | |||
| dma_init.DMA_M2M = DMA_M2M_Disable; | |||
| DMA_Init(DMA1_Channel5, &dma_init); | |||
| // Enable the interrupts: half transfer and transfer complete. | |||
| DMA_ITConfig(DMA1_Channel5, DMA_IT_TC | DMA_IT_HT, ENABLE); | |||
| // Enable the IRQ. | |||
| NVIC_EnableIRQ(DMA1_Channel5_IRQn); | |||
| SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE); | |||
| } | |||
| void AudioDac::Start(FillBufferCallback callback) { | |||
| callback_ = callback; | |||
| DMA_Cmd(DMA1_Channel5, ENABLE); | |||
| } | |||
| void AudioDac::Stop() { | |||
| DMA_Cmd(DMA1_Channel5, DISABLE); | |||
| } | |||
| void AudioDac::Fill(size_t offset) { | |||
| (*callback_)(&tx_dma_buffer_[offset * block_size_], block_size_); | |||
| } | |||
| } // namespace plaits | |||
| extern "C" { | |||
| void DMA1_Channel5_IRQHandler(void) { | |||
| uint32_t flags = DMA1->ISR; | |||
| DMA1->IFCR = DMA1_FLAG_TC5 | DMA1_FLAG_HT5; | |||
| if (flags & DMA1_FLAG_TC5) { | |||
| plaits::AudioDac::GetInstance()->Fill(1); | |||
| } else if (flags & DMA1_FLAG_HT5) { | |||
| plaits::AudioDac::GetInstance()->Fill(0); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,73 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the PCM5100 DAC. | |||
| #ifndef PLAITS_DRIVERS_DAC_H_ | |||
| #define PLAITS_DRIVERS_DAC_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace plaits { | |||
| const size_t kMaxCodecBlockSize = 24; | |||
| class AudioDac { | |||
| public: | |||
| AudioDac() { } | |||
| ~AudioDac() { } | |||
| typedef struct { | |||
| short l; | |||
| short r; | |||
| } Frame; | |||
| typedef void (*FillBufferCallback)(Frame* tx, size_t size); | |||
| void Init(int sample_rate, size_t block_size); | |||
| void Start(FillBufferCallback callback); | |||
| void Stop(); | |||
| void Fill(size_t offset); | |||
| static AudioDac* GetInstance() { return instance_; } | |||
| private: | |||
| void InitializeGPIO(); | |||
| void InitializeAudioInterface(int sample_rate); | |||
| void InitializeDMA(size_t block_size); | |||
| static AudioDac* instance_; | |||
| size_t block_size_; | |||
| FillBufferCallback callback_; | |||
| Frame tx_dma_buffer_[kMaxCodecBlockSize * 2]; | |||
| DISALLOW_COPY_AND_ASSIGN(AudioDac); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DRIVERS_DAC_H_ | |||
| @@ -0,0 +1,193 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the 16-bit SDADC scanning the CVs. | |||
| #include "plaits/drivers/cv_adc.h" | |||
| #include <stm32f37x_conf.h> | |||
| namespace plaits { | |||
| struct ChannelConfiguration { | |||
| CvAdcChannel map_to; | |||
| uint32_t channel; | |||
| GPIO_TypeDef* gpio; | |||
| uint16_t pin; | |||
| }; | |||
| struct ConverterConfiguration { | |||
| SDADC_TypeDef* sdadc; | |||
| DMA_Channel_TypeDef* dma_channel; | |||
| int num_channels; | |||
| ChannelConfiguration channel[3]; | |||
| }; | |||
| const ConverterConfiguration converter_configuration[3] = { | |||
| { | |||
| SDADC1, DMA2_Channel3, 3, { | |||
| { CV_ADC_CHANNEL_TIMBRE, SDADC_Channel_4, GPIOB, GPIO_Pin_2 }, | |||
| { CV_ADC_CHANNEL_MODEL, SDADC_Channel_5, GPIOB, GPIO_Pin_1 }, | |||
| { CV_ADC_CHANNEL_TRIGGER, SDADC_Channel_6, GPIOB, GPIO_Pin_0 } | |||
| } | |||
| }, | |||
| { | |||
| SDADC2, DMA2_Channel4, 2, { | |||
| { CV_ADC_CHANNEL_FM, SDADC_Channel_7, GPIOE, GPIO_Pin_9 }, | |||
| { CV_ADC_CHANNEL_LEVEL, SDADC_Channel_8, GPIOE, GPIO_Pin_8 } | |||
| } | |||
| }, | |||
| { | |||
| SDADC3, DMA2_Channel5, 3, { | |||
| { CV_ADC_CHANNEL_HARMONICS, SDADC_Channel_6, GPIOD, GPIO_Pin_8 }, | |||
| { CV_ADC_CHANNEL_MORPH, SDADC_Channel_7, GPIOB, GPIO_Pin_15 }, | |||
| { CV_ADC_CHANNEL_V_OCT, SDADC_Channel_8, GPIOB, GPIO_Pin_14 } | |||
| } | |||
| }, | |||
| }; | |||
| void CvAdc::Init() { | |||
| // Power all the SDADCs. | |||
| RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); | |||
| PWR_SDADCAnalogCmd(PWR_SDADCAnalog_1, ENABLE); | |||
| PWR_SDADCAnalogCmd(PWR_SDADCAnalog_2, ENABLE); | |||
| PWR_SDADCAnalogCmd(PWR_SDADCAnalog_3, ENABLE); | |||
| // Enable SDADC clock. | |||
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDADC1, ENABLE); | |||
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDADC2, ENABLE); | |||
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDADC3, ENABLE); | |||
| RCC_SDADCCLKConfig(RCC_SDADCCLK_SYSCLK_Div12); | |||
| // Enable DMA2 clock. | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE); | |||
| // Enable GPIO clock. | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE); | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE, ENABLE); | |||
| // Init SDADC | |||
| SDADC_VREFSelect(SDADC_VREF_Ext); | |||
| DMA_InitTypeDef dma_init; | |||
| GPIO_InitTypeDef gpio_init; | |||
| SDADC_AINStructTypeDef sdadc_ain; | |||
| // Fill structures with the settings common to all channels/pins. | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
| sdadc_ain.SDADC_InputMode = SDADC_InputMode_SEZeroReference; | |||
| sdadc_ain.SDADC_Gain = SDADC_Gain_1; | |||
| sdadc_ain.SDADC_CommonMode = SDADC_CommonMode_VDDA_2; | |||
| sdadc_ain.SDADC_Offset = 0; | |||
| int current_channel = 0; | |||
| // Configure all SDADCs, all their input channels, and all the DMA channels. | |||
| for (int i = 0; i < 3; ++i) { | |||
| const ConverterConfiguration& config = converter_configuration[i]; | |||
| // Wait for SDADC to stabilize. | |||
| SDADC_Cmd(config.sdadc, ENABLE); | |||
| while (SDADC_GetFlagStatus(config.sdadc, SDADC_FLAG_STABIP) == SET); | |||
| // Configure GPIO pins. | |||
| for (int j = 0; j < config.num_channels; ++j) { | |||
| gpio_init.GPIO_Pin = config.channel[j].pin; | |||
| GPIO_Init(config.channel[j].gpio, &gpio_init); | |||
| } | |||
| // SDADC enters initialization mode. | |||
| SDADC_InitModeCmd(config.sdadc, ENABLE); | |||
| while (SDADC_GetFlagStatus(config.sdadc, SDADC_FLAG_INITRDY) == RESET); | |||
| // Configure DMA to read injected values into a slice of the | |||
| // values_ array. | |||
| dma_init.DMA_PeripheralBaseAddr = (uint32_t)&(config.sdadc->JDATAR); | |||
| dma_init.DMA_MemoryBaseAddr = (uint32_t)(&values_[current_channel]); | |||
| dma_init.DMA_DIR = DMA_DIR_PeripheralSRC; | |||
| dma_init.DMA_BufferSize = config.num_channels; | |||
| dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
| dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
| dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
| dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
| dma_init.DMA_Mode = DMA_Mode_Circular; | |||
| dma_init.DMA_Priority = DMA_Priority_High; | |||
| dma_init.DMA_M2M = DMA_M2M_Disable; | |||
| DMA_Init(config.dma_channel, &dma_init); | |||
| // Create a configuration and assign it to all channels used by this SDADC. | |||
| SDADC_AINInit(config.sdadc, SDADC_Conf_0, &sdadc_ain); | |||
| uint32_t channels = 0; | |||
| for (int j = 0; j < config.num_channels; ++j) { | |||
| channel_map_[config.channel[j].map_to] = current_channel++; | |||
| channels |= config.channel[j].channel; | |||
| SDADC_ChannelConfig( | |||
| config.sdadc, config.channel[j].channel, SDADC_Conf_0); | |||
| } | |||
| // Select injected channels. | |||
| SDADC_InjectedChannelSelect(config.sdadc, channels); | |||
| // Disable continuous mode - the conversions are restarted every time | |||
| // we render a block of samples. | |||
| SDADC_InjectedContinuousModeCmd(config.sdadc, DISABLE); | |||
| // Terminate initialization sequence. | |||
| SDADC_CalibrationSequenceConfig(config.sdadc, SDADC_CalibrationSequence_3); | |||
| SDADC_InitModeCmd(config.sdadc, DISABLE); | |||
| while (SDADC_GetFlagStatus(config.sdadc, SDADC_FLAG_INITRDY) == SET); | |||
| // Run calibration sequence. | |||
| SDADC_StartCalibration(config.sdadc); | |||
| while (SDADC_GetFlagStatus(config.sdadc, SDADC_FLAG_EOCAL) == RESET); | |||
| // Enable DMA. | |||
| DMA_Cmd(config.dma_channel, ENABLE); | |||
| SDADC_DMAConfig(config.sdadc, SDADC_DMATransfer_Injected, ENABLE); | |||
| } | |||
| Convert(); | |||
| } | |||
| void CvAdc::DeInit() { | |||
| for (int i = 0; i < 3; ++i) { | |||
| const ConverterConfiguration& config = converter_configuration[i]; | |||
| SDADC_Cmd(config.sdadc, DISABLE); | |||
| SDADC_DMAConfig(config.sdadc, SDADC_DMATransfer_Injected, DISABLE); | |||
| DMA_Cmd(config.dma_channel, DISABLE); | |||
| } | |||
| } | |||
| void CvAdc::Convert() { | |||
| SDADC_SoftwareStartInjectedConv(SDADC1); | |||
| SDADC_SoftwareStartInjectedConv(SDADC2); | |||
| SDADC_SoftwareStartInjectedConv(SDADC3); | |||
| } | |||
| } // namespace plaits | |||
| @@ -0,0 +1,74 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the 16-bit SDADC scanning the CV inputs. | |||
| #ifndef PLAITS_DRIVERS_CV_ADC_H_ | |||
| #define PLAITS_DRIVERS_CV_ADC_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace plaits { | |||
| enum CvAdcChannel { | |||
| CV_ADC_CHANNEL_MODEL, | |||
| CV_ADC_CHANNEL_V_OCT, | |||
| CV_ADC_CHANNEL_FM, | |||
| CV_ADC_CHANNEL_HARMONICS, | |||
| CV_ADC_CHANNEL_TIMBRE, | |||
| CV_ADC_CHANNEL_MORPH, | |||
| CV_ADC_CHANNEL_TRIGGER, | |||
| CV_ADC_CHANNEL_LEVEL, | |||
| CV_ADC_CHANNEL_LAST | |||
| }; | |||
| class CvAdc { | |||
| public: | |||
| CvAdc() { } | |||
| ~CvAdc() { } | |||
| void Init(); | |||
| void DeInit(); | |||
| void Convert(); | |||
| inline int16_t value(CvAdcChannel channel) const { | |||
| return values_[channel_map_[channel]]; | |||
| } | |||
| inline float float_value(CvAdcChannel channel) const { | |||
| return static_cast<float>(value(channel)) / 32768.0f; | |||
| } | |||
| private: | |||
| int channel_map_[CV_ADC_CHANNEL_LAST]; | |||
| int16_t values_[CV_ADC_CHANNEL_LAST]; | |||
| DISALLOW_COPY_AND_ASSIGN(CvAdc); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DRIVERS_CV_ADC_H_ | |||
| @@ -0,0 +1,72 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the debug (timing) pin. | |||
| #ifndef PLAITS_DRIVERS_DEBUG_PIN_H_ | |||
| #define PLAITS_DRIVERS_DEBUG_PIN_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <stm32f37x_conf.h> | |||
| namespace plaits { | |||
| class DebugPin { | |||
| public: | |||
| DebugPin() { } | |||
| ~DebugPin() { } | |||
| static void Init() { | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_8; | |||
| GPIO_Init(GPIOB, &gpio_init); | |||
| } | |||
| static inline void High() { | |||
| GPIOB->BSRR = GPIO_Pin_8; | |||
| } | |||
| static inline void Low() { | |||
| GPIOB->BRR = GPIO_Pin_8; | |||
| } | |||
| private: | |||
| DISALLOW_COPY_AND_ASSIGN(DebugPin); | |||
| }; | |||
| #define TIC DebugPin::High(); | |||
| #define TOC DebugPin::Low(); | |||
| } // namespace plaits | |||
| #endif // PLAITS_DRIVERS_DEBUG_PIN_H_ | |||
| @@ -0,0 +1,62 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // UART driver for conversing with the factory testing program. | |||
| #include "plaits/drivers/debug_port.h" | |||
| namespace plaits { | |||
| void DebugPort::Init() { | |||
| RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
| // Initialize TX and RX pins. | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| GPIO_Init(GPIOB, &gpio_init); | |||
| GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_7); | |||
| GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_7); | |||
| // Initialize USART. | |||
| USART_InitTypeDef usart_init; | |||
| usart_init.USART_BaudRate = 9600; | |||
| usart_init.USART_WordLength = USART_WordLength_8b; | |||
| usart_init.USART_StopBits = USART_StopBits_1; | |||
| usart_init.USART_Parity = USART_Parity_No; | |||
| usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None; | |||
| usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; | |||
| USART_Init(USART3, &usart_init); | |||
| USART_Cmd(USART3, ENABLE); | |||
| } | |||
| } // namespace plaits | |||
| @@ -0,0 +1,67 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // UART driver for conversing with the factory testing program. | |||
| #ifndef PLAITS_DRIVERS_DEBUG_PORT_H_ | |||
| #define PLAITS_DRIVERS_DEBUG_PORT_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <stm32f37x_conf.h> | |||
| namespace plaits { | |||
| class DebugPort { | |||
| public: | |||
| DebugPort() { } | |||
| ~DebugPort() { } | |||
| void Init(); | |||
| bool writable() { | |||
| return USART3->ISR & USART_FLAG_TXE; | |||
| } | |||
| bool readable() { | |||
| return USART3->ISR & USART_FLAG_RXNE; | |||
| } | |||
| void Write(uint8_t byte) { | |||
| USART3->TDR = byte; | |||
| } | |||
| uint8_t Read() { | |||
| return USART3->RDR; | |||
| } | |||
| private: | |||
| DISALLOW_COPY_AND_ASSIGN(DebugPort); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DRIVERS_DEBUG_PORT_H_ | |||
| @@ -0,0 +1,111 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the 12-bit ADC used for firmware updates through the MODEL CV in. | |||
| #include "plaits/drivers/firmware_update_adc.h" | |||
| #include <stm32f37x_conf.h> | |||
| namespace plaits { | |||
| void FirmwareUpdateAdc::Init() { | |||
| // Enable ADC clock. | |||
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); | |||
| RCC_ADCCLKConfig(RCC_PCLK2_Div8); | |||
| // Enable GPIO clock. | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
| // Enable DMA1 clock. | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); | |||
| DMA_InitTypeDef dma_init; | |||
| ADC_InitTypeDef adc_init; | |||
| GPIO_InitTypeDef gpio_init; | |||
| // Configure the two analog inputs. | |||
| gpio_init.GPIO_Pin = GPIO_Pin_1; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
| GPIO_Init(GPIOB, &gpio_init); | |||
| gpio_init.GPIO_Pin = GPIO_Pin_0; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| // Use DMA to automatically copy ADC data register to values_ buffer. | |||
| dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; | |||
| dma_init.DMA_MemoryBaseAddr = (uint32_t)&values_[0]; | |||
| dma_init.DMA_DIR = DMA_DIR_PeripheralSRC; | |||
| dma_init.DMA_BufferSize = 2; | |||
| dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
| dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
| dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
| dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
| dma_init.DMA_Mode = DMA_Mode_Circular; | |||
| dma_init.DMA_Priority = DMA_Priority_High; | |||
| dma_init.DMA_M2M = DMA_M2M_Disable; | |||
| DMA_Init(DMA1_Channel1, &dma_init); | |||
| DMA_Cmd(DMA1_Channel1, ENABLE); | |||
| // Init ADC1. | |||
| adc_init.ADC_ScanConvMode = ENABLE; | |||
| adc_init.ADC_ContinuousConvMode = DISABLE; | |||
| adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; | |||
| adc_init.ADC_DataAlign = ADC_DataAlign_Left; | |||
| adc_init.ADC_NbrOfChannel = 2; | |||
| ADC_Init(ADC1, &adc_init); | |||
| // Sample rate: 53.6 kHz | |||
| // 72000 / 6 / (12.5 * 2 + 71.5 * 2) | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_71Cycles5); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 2, ADC_SampleTime_71Cycles5); | |||
| // Enable and calibrate ADC1. | |||
| ADC_Cmd(ADC1, ENABLE); | |||
| ADC_ResetCalibration(ADC1); | |||
| while (ADC_GetResetCalibrationStatus(ADC1)); | |||
| ADC_StartCalibration(ADC1); | |||
| while (ADC_GetCalibrationStatus(ADC1)); | |||
| ADC_DMACmd(ADC1, ENABLE); | |||
| values_[0] = values_[1] = 32768; | |||
| Convert(); | |||
| } | |||
| void FirmwareUpdateAdc::DeInit() { | |||
| ADC_DMACmd(ADC1, DISABLE); | |||
| ADC_Cmd(ADC1, DISABLE); | |||
| DMA_Cmd(DMA1_Channel1, DISABLE); | |||
| } | |||
| void FirmwareUpdateAdc::Convert() { | |||
| ADC_SoftwareStartConv(ADC1); | |||
| } | |||
| } // namespace plaits | |||
| @@ -0,0 +1,60 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the 12-bit ADC used for firmware updates through the MODEL CV in. | |||
| #ifndef PLAITS_DRIVERS_FIRMWARE_UPDATE_ADC_H_ | |||
| #define PLAITS_DRIVERS_FIRMWARE_UPDATE_ADC_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace plaits { | |||
| class FirmwareUpdateAdc { | |||
| public: | |||
| FirmwareUpdateAdc() { } | |||
| ~FirmwareUpdateAdc() { } | |||
| void Init(); | |||
| void DeInit(); | |||
| void Convert(); | |||
| inline uint16_t gain_pot() const { | |||
| return values_[0]; | |||
| } | |||
| inline uint16_t sample() const { | |||
| return values_[1]; | |||
| } | |||
| private: | |||
| uint16_t values_[2]; | |||
| DISALLOW_COPY_AND_ASSIGN(FirmwareUpdateAdc); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DRIVERS_FIRMWARE_UPDATE_ADC_H_ | |||
| @@ -0,0 +1,114 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the column of LEDs. | |||
| #include "plaits/drivers/leds.h" | |||
| #include <algorithm> | |||
| #include <stm32f37x_conf.h> | |||
| namespace plaits { | |||
| using namespace std; | |||
| const uint16_t kPinEnable = GPIO_Pin_7; | |||
| void Leds::Init() { | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOF, ENABLE); | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); | |||
| GPIO_InitTypeDef gpio_init; | |||
| // SS | |||
| gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = kPinEnable; | |||
| GPIO_Init(GPIOF, &gpio_init); | |||
| // MOSI | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_6; | |||
| GPIO_Init(GPIOF, &gpio_init); | |||
| GPIO_PinAFConfig(GPIOF, GPIO_PinSource6, GPIO_AF_5); | |||
| // SCK | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_12; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| GPIO_PinAFConfig(GPIOA, GPIO_PinSource12, GPIO_AF_6); | |||
| // Initialize SPI | |||
| SPI_InitTypeDef spi_init; | |||
| spi_init.SPI_Direction = SPI_Direction_2Lines_FullDuplex; | |||
| spi_init.SPI_Mode = SPI_Mode_Master; | |||
| spi_init.SPI_DataSize = SPI_DataSize_16b; | |||
| spi_init.SPI_CPOL = SPI_CPOL_Low; | |||
| spi_init.SPI_CPHA = SPI_CPHA_1Edge; | |||
| spi_init.SPI_NSS = SPI_NSS_Soft; | |||
| spi_init.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; | |||
| spi_init.SPI_FirstBit = SPI_FirstBit_MSB; | |||
| spi_init.SPI_CRCPolynomial = 7; | |||
| SPI_Init(SPI1, &spi_init); | |||
| SPI_Cmd(SPI1, ENABLE); | |||
| Clear(); | |||
| } | |||
| void Leds::Clear() { | |||
| fill(&colors_[0], &colors_[kNumLEDs], LED_COLOR_OFF); | |||
| } | |||
| /* static */ | |||
| const int Leds::led_map_[8] = { | |||
| 0, 1, 2, 3, 7, 6, 5, 4 | |||
| }; | |||
| void Leds::Write() { | |||
| uint16_t leds_data = 0; | |||
| for (int i = 0; i < kNumLEDs; ++i) { | |||
| int j = led_map_[i]; | |||
| leds_data <<= 2; | |||
| leds_data |= (colors_[j] & LED_COLOR_RED) ? 1 : 0; | |||
| leds_data |= (colors_[j] & LED_COLOR_GREEN) ? 2 : 0; | |||
| } | |||
| GPIOF->BSRR = kPinEnable; | |||
| __asm__("nop"); | |||
| GPIOF->BRR = kPinEnable; | |||
| __asm__("nop"); | |||
| SPI1->DR = leds_data; | |||
| } | |||
| } // namespace plaits | |||
| @@ -0,0 +1,72 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the column of LEDs. | |||
| #ifndef PLAITS_DRIVERS_LEDS_H_ | |||
| #define PLAITS_DRIVERS_LEDS_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace plaits { | |||
| const int kNumLEDs = 8; | |||
| enum LedColor { | |||
| LED_COLOR_OFF = 0, | |||
| LED_COLOR_RED = 0xff0000, | |||
| LED_COLOR_GREEN = 0x00ff00, | |||
| LED_COLOR_YELLOW = 0xffff00, | |||
| }; | |||
| class Leds { | |||
| public: | |||
| Leds() { } | |||
| ~Leds() { } | |||
| void Init(); | |||
| void Write(); | |||
| void Clear(); | |||
| void set(int index, uint32_t color) { | |||
| colors_[index] = color; | |||
| } | |||
| void mask(int index, uint32_t color) { | |||
| colors_[index] |= color; | |||
| } | |||
| private: | |||
| uint32_t colors_[kNumLEDs]; | |||
| static const int led_map_[kNumLEDs]; | |||
| DISALLOW_COPY_AND_ASSIGN(Leds); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DRIVERS_LEDS_H_ | |||
| @@ -0,0 +1,87 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the normalization probe. | |||
| #ifndef PLAITS_DRIVERS_NORMALIZATION_PROBE_H_ | |||
| #define PLAITS_DRIVERS_NORMALIZATION_PROBE_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <stm32f37x_conf.h> | |||
| namespace plaits { | |||
| class NormalizationProbe { | |||
| public: | |||
| NormalizationProbe() { } | |||
| ~NormalizationProbe() { } | |||
| static inline void Init() { | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_9; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| } | |||
| void Disable() { | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_Pin = GPIO_Pin_9; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| } | |||
| static inline void High() { | |||
| GPIOA->BSRR = GPIO_Pin_9; | |||
| } | |||
| static inline void Low() { | |||
| GPIOA->BRR = GPIO_Pin_9; | |||
| } | |||
| static inline void Write(bool value) { | |||
| if (value) { | |||
| High(); | |||
| } else { | |||
| Low(); | |||
| } | |||
| } | |||
| private: | |||
| DISALLOW_COPY_AND_ASSIGN(NormalizationProbe); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DRIVERS_NORMALIZATION_PROBE_H_ | |||
| @@ -0,0 +1,111 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the 12-bit ADC scanning pots. | |||
| #include "plaits/drivers/pots_adc.h" | |||
| #include <algorithm> | |||
| #include <stm32f37x_conf.h> | |||
| namespace plaits { | |||
| using namespace std; | |||
| void PotsAdc::Init() { | |||
| // Enable ADC clock. | |||
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); | |||
| RCC_ADCCLKConfig(RCC_PCLK2_Div8); | |||
| // Enable GPIO clock. | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
| // Enable DMA1 clock. | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); | |||
| DMA_InitTypeDef dma_init; | |||
| ADC_InitTypeDef adc_init; | |||
| GPIO_InitTypeDef gpio_init; | |||
| // Configure the two analog inputs. | |||
| gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | \ | |||
| GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
| GPIO_Init(GPIOA, &gpio_init); | |||
| // Use DMA to automatically copy ADC data register to values_ buffer. | |||
| dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; | |||
| dma_init.DMA_MemoryBaseAddr = (uint32_t)&values_[0]; | |||
| dma_init.DMA_DIR = DMA_DIR_PeripheralSRC; | |||
| dma_init.DMA_BufferSize = POTS_ADC_CHANNEL_LAST; | |||
| dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
| dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
| dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
| dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
| dma_init.DMA_Mode = DMA_Mode_Circular; | |||
| dma_init.DMA_Priority = DMA_Priority_High; | |||
| dma_init.DMA_M2M = DMA_M2M_Disable; | |||
| DMA_Init(DMA1_Channel1, &dma_init); | |||
| DMA_Cmd(DMA1_Channel1, ENABLE); | |||
| // Init ADC1. | |||
| adc_init.ADC_ScanConvMode = ENABLE; | |||
| adc_init.ADC_ContinuousConvMode = DISABLE; | |||
| adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; | |||
| adc_init.ADC_DataAlign = ADC_DataAlign_Left; | |||
| adc_init.ADC_NbrOfChannel = POTS_ADC_CHANNEL_LAST; | |||
| ADC_Init(ADC1, &adc_init); | |||
| // Sample rate: 5.10 kHz > 4kHz | |||
| // 72000 / 8 / ((12.5 + 239.5) * 7) | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_239Cycles5); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 3, ADC_SampleTime_239Cycles5); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 4, ADC_SampleTime_239Cycles5); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 5, ADC_SampleTime_239Cycles5); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 6, ADC_SampleTime_239Cycles5); | |||
| ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 7, ADC_SampleTime_239Cycles5); | |||
| // Enable and calibrate ADC1. | |||
| ADC_Cmd(ADC1, ENABLE); | |||
| ADC_ResetCalibration(ADC1); | |||
| while (ADC_GetResetCalibrationStatus(ADC1)); | |||
| ADC_StartCalibration(ADC1); | |||
| while (ADC_GetCalibrationStatus(ADC1)); | |||
| ADC_DMACmd(ADC1, ENABLE); | |||
| fill(&values_[0], &values_[POTS_ADC_CHANNEL_LAST], 32768); | |||
| Convert(); | |||
| } | |||
| void PotsAdc::Convert() { | |||
| ADC_SoftwareStartConv(ADC1); | |||
| } | |||
| } // namespace plaits | |||
| @@ -0,0 +1,71 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Drivers for the 12-bit ADC scanning pots. | |||
| #ifndef PLAITS_DRIVERS_POTS_ADC_H_ | |||
| #define PLAITS_DRIVERS_POTS_ADC_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace plaits { | |||
| enum PotsAdcChannel { | |||
| POTS_ADC_CHANNEL_FREQ_POT, | |||
| POTS_ADC_CHANNEL_HARMONICS_POT, | |||
| POTS_ADC_CHANNEL_TIMBRE_POT, | |||
| POTS_ADC_CHANNEL_MORPH_POT, | |||
| POTS_ADC_CHANNEL_TIMBRE_ATTENUVERTER, | |||
| POTS_ADC_CHANNEL_FM_ATTENUVERTER, | |||
| POTS_ADC_CHANNEL_MORPH_ATTENUVERTER, | |||
| POTS_ADC_CHANNEL_LAST | |||
| }; | |||
| class PotsAdc { | |||
| public: | |||
| PotsAdc() { } | |||
| ~PotsAdc() { } | |||
| void Init(); | |||
| void Convert(); | |||
| inline int32_t value(PotsAdcChannel channel) const { | |||
| return static_cast<int32_t>(values_[channel]); | |||
| } | |||
| inline float float_value(PotsAdcChannel channel) const { | |||
| return static_cast<float>(value(channel)) / 65536.0f; | |||
| } | |||
| private: | |||
| uint16_t values_[POTS_ADC_CHANNEL_LAST]; | |||
| DISALLOW_COPY_AND_ASSIGN(PotsAdc); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DRIVERS_POTS_ADC_H_ | |||
| @@ -0,0 +1,72 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the 2 switches. | |||
| #include "plaits/drivers/switches.h" | |||
| #include <algorithm> | |||
| namespace plaits { | |||
| using namespace std; | |||
| struct SwitchDefinition { | |||
| GPIO_TypeDef* gpio; | |||
| uint16_t pin; | |||
| }; | |||
| const SwitchDefinition switch_definitions[] = { | |||
| { GPIOB, GPIO_Pin_7 }, | |||
| { GPIOB, GPIO_Pin_6 }, | |||
| }; | |||
| void Switches::Init() { | |||
| RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
| GPIO_InitTypeDef gpio_init; | |||
| gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||
| gpio_init.GPIO_OType = GPIO_OType_PP; | |||
| gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
| gpio_init.GPIO_PuPd = GPIO_PuPd_UP; | |||
| for (int i = 0; i < SWITCH_LAST; ++i) { | |||
| const SwitchDefinition& definition = switch_definitions[i]; | |||
| gpio_init.GPIO_Pin = definition.pin; | |||
| GPIO_Init(definition.gpio, &gpio_init); | |||
| } | |||
| fill(&switch_state_[0], &switch_state_[SWITCH_LAST], 0xff); | |||
| } | |||
| void Switches::Debounce() { | |||
| for (int i = 0; i < SWITCH_LAST; ++i) { | |||
| const SwitchDefinition& definition = switch_definitions[i]; | |||
| switch_state_[i] = (switch_state_[i] << 1) | \ | |||
| GPIO_ReadInputDataBit(definition.gpio, definition.pin); | |||
| } | |||
| } | |||
| } // namespace plaits | |||
| @@ -0,0 +1,82 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Driver for the 2 switches. | |||
| #ifndef PLAITS_DRIVERS_SWITCHES_H_ | |||
| #define PLAITS_DRIVERS_SWITCHES_H_ | |||
| #include "stmlib/stmlib.h" | |||
| #include <stm32f37x_conf.h> | |||
| namespace plaits { | |||
| enum Switch { | |||
| SWITCH_ROW_1, | |||
| SWITCH_ROW_2, | |||
| SWITCH_LAST | |||
| }; | |||
| class Switches { | |||
| public: | |||
| Switches() { } | |||
| ~Switches() { } | |||
| void Init(); | |||
| void Debounce(); | |||
| inline bool released(Switch s) const { | |||
| return switch_state_[s] == 0x7f; | |||
| } | |||
| inline bool just_pressed(Switch s) const { | |||
| return switch_state_[s] == 0x80; | |||
| } | |||
| inline bool pressed(Switch s) const { | |||
| return switch_state_[s] == 0x00; | |||
| } | |||
| inline bool pressed_immediate(Switch s) const { | |||
| if (s == SWITCH_ROW_1) { | |||
| return !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7); | |||
| } else if (s == SWITCH_ROW_2) { | |||
| return !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6); | |||
| } else { | |||
| return false; | |||
| } | |||
| } | |||
| private: | |||
| uint8_t switch_state_[SWITCH_LAST]; | |||
| DISALLOW_COPY_AND_ASSIGN(Switches); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DRIVERS_SWITCHES_H_ | |||
| @@ -0,0 +1,195 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // 808 bass drum model, revisited. | |||
| #ifndef PLAITS_DSP_DRUMS_ANALOG_BASS_DRUM_H_ | |||
| #define PLAITS_DSP_DRUMS_ANALOG_BASS_DRUM_H_ | |||
| #include <algorithm> | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/dsp/filter.h" | |||
| #include "stmlib/dsp/parameter_interpolator.h" | |||
| #include "stmlib/dsp/units.h" | |||
| #include "plaits/dsp/dsp.h" | |||
| #include "plaits/dsp/oscillator/sine_oscillator.h" | |||
| namespace plaits { | |||
| class AnalogBassDrum { | |||
| public: | |||
| AnalogBassDrum() { } | |||
| ~AnalogBassDrum() { } | |||
| void Init() { | |||
| pulse_remaining_samples_ = 0; | |||
| fm_pulse_remaining_samples_ = 0; | |||
| pulse_ = 0.0f; | |||
| pulse_height_ = 0.0f; | |||
| pulse_lp_ = 0.0f; | |||
| fm_pulse_lp_ = 0.0f; | |||
| retrig_pulse_ = 0.0f; | |||
| lp_out_ = 0.0f; | |||
| tone_lp_ = 0.0f; | |||
| sustain_gain_ = 0.0f; | |||
| resonator_.Init(); | |||
| oscillator_.Init(); | |||
| } | |||
| inline float Diode(float x) { | |||
| if (x >= 0.0f) { | |||
| return x; | |||
| } else { | |||
| x *= 2.0f; | |||
| return 0.7f * x / (1.0f + fabsf(x)); | |||
| } | |||
| } | |||
| void Render( | |||
| bool sustain, | |||
| bool trigger, | |||
| float accent, | |||
| float f0, | |||
| float tone, | |||
| float decay, | |||
| float attack_fm_amount, | |||
| float self_fm_amount, | |||
| float* out, | |||
| size_t size) { | |||
| const int kTriggerPulseDuration = 1.0e-3 * kSampleRate; | |||
| const int kFMPulseDuration = 6.0e-3 * kSampleRate; | |||
| const float kPulseDecayTime = 0.2e-3 * kSampleRate; | |||
| const float kPulseFilterTime = 0.1e-3 * kSampleRate; | |||
| const float kRetrigPulseDuration = 0.05f * kSampleRate; | |||
| const float scale = 0.001f / f0; | |||
| const float q = 1500.0f * stmlib::SemitonesToRatio(decay * 80.0f); | |||
| const float tone_f = std::min( | |||
| 4.0f * f0 * stmlib::SemitonesToRatio(tone * 108.0f), | |||
| 1.0f); | |||
| const float exciter_leak = 0.08f * (tone + 0.25f); | |||
| if (trigger) { | |||
| pulse_remaining_samples_ = kTriggerPulseDuration; | |||
| fm_pulse_remaining_samples_ = kFMPulseDuration; | |||
| pulse_height_ = 3.0f + 7.0f * accent; | |||
| lp_out_ = 0.0f; | |||
| } | |||
| stmlib::ParameterInterpolator sustain_gain( | |||
| &sustain_gain_, | |||
| accent * decay, | |||
| size); | |||
| while (size--) { | |||
| // Q39 / Q40 | |||
| float pulse = 0.0f; | |||
| if (pulse_remaining_samples_) { | |||
| --pulse_remaining_samples_; | |||
| pulse = pulse_remaining_samples_ ? pulse_height_ : pulse_height_ - 1.0f; | |||
| pulse_ = pulse; | |||
| } else { | |||
| pulse_ *= 1.0f - 1.0f / kPulseDecayTime; | |||
| pulse = pulse_; | |||
| } | |||
| if (sustain) { | |||
| pulse = 0.0f; | |||
| } | |||
| // C40 / R163 / R162 / D83 | |||
| ONE_POLE(pulse_lp_, pulse, 1.0f / kPulseFilterTime); | |||
| pulse = Diode((pulse - pulse_lp_) + pulse * 0.044f); | |||
| // Q41 / Q42 | |||
| float fm_pulse = 0.0f; | |||
| if (fm_pulse_remaining_samples_) { | |||
| --fm_pulse_remaining_samples_; | |||
| fm_pulse = 1.0f; | |||
| // C39 / C52 | |||
| retrig_pulse_ = fm_pulse_remaining_samples_ ? 0.0f : -0.8f; | |||
| } else { | |||
| // C39 / R161 | |||
| retrig_pulse_ *= 1.0f - 1.0f / kRetrigPulseDuration; | |||
| } | |||
| if (sustain) { | |||
| fm_pulse = 0.0f; | |||
| } | |||
| ONE_POLE(fm_pulse_lp_, fm_pulse, 1.0f / kPulseFilterTime); | |||
| // Q43 and R170 leakage | |||
| float punch = 0.7f + Diode(10.0f * lp_out_ - 1.0f); | |||
| // Q43 / R165 | |||
| float attack_fm = fm_pulse_lp_ * 1.7f * attack_fm_amount; | |||
| float self_fm = punch * 0.08f * self_fm_amount; | |||
| float f = f0 * (1.0f + attack_fm + self_fm); | |||
| CONSTRAIN(f, 0.0f, 0.4f); | |||
| float resonator_out; | |||
| if (sustain) { | |||
| oscillator_.Next(f, sustain_gain.Next(), &resonator_out, &lp_out_); | |||
| } else { | |||
| resonator_.set_f_q<stmlib::FREQUENCY_DIRTY>(f, 1.0f + q * f); | |||
| resonator_.Process<stmlib::FILTER_MODE_BAND_PASS, | |||
| stmlib::FILTER_MODE_LOW_PASS>( | |||
| (pulse - retrig_pulse_ * 0.2f) * scale, | |||
| &resonator_out, | |||
| &lp_out_); | |||
| } | |||
| ONE_POLE(tone_lp_, pulse * exciter_leak + resonator_out, tone_f); | |||
| *out++ = tone_lp_; | |||
| } | |||
| } | |||
| private: | |||
| int pulse_remaining_samples_; | |||
| int fm_pulse_remaining_samples_; | |||
| float pulse_; | |||
| float pulse_height_; | |||
| float pulse_lp_; | |||
| float fm_pulse_lp_; | |||
| float retrig_pulse_; | |||
| float lp_out_; | |||
| float tone_lp_; | |||
| float sustain_gain_; | |||
| stmlib::Svf resonator_; | |||
| // Replace the resonator in "free running" (sustain) mode. | |||
| SineOscillator oscillator_; | |||
| DISALLOW_COPY_AND_ASSIGN(AnalogBassDrum); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DSP_DRUMS_ANALOG_BASS_DRUM_H_ | |||
| @@ -0,0 +1,201 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // 808 snare drum model, revisited. | |||
| #ifndef PLAITS_DSP_DRUMS_ANALOG_SNARE_DRUM_H_ | |||
| #define PLAITS_DSP_DRUMS_ANALOG_SNARE_DRUM_H_ | |||
| #include <algorithm> | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/dsp/filter.h" | |||
| #include "stmlib/dsp/parameter_interpolator.h" | |||
| #include "stmlib/dsp/units.h" | |||
| #include "stmlib/utils/random.h" | |||
| #include "plaits/dsp/dsp.h" | |||
| #include "plaits/dsp/oscillator/sine_oscillator.h" | |||
| namespace plaits { | |||
| class AnalogSnareDrum { | |||
| public: | |||
| AnalogSnareDrum() { } | |||
| ~AnalogSnareDrum() { } | |||
| static const int kNumModes = 5; | |||
| void Init() { | |||
| pulse_remaining_samples_ = 0; | |||
| pulse_ = 0.0f; | |||
| pulse_height_ = 0.0f; | |||
| pulse_lp_ = 0.0f; | |||
| noise_envelope_ = 0.0f; | |||
| sustain_gain_ = 0.0f; | |||
| for (int i = 0; i < kNumModes; ++i) { | |||
| resonator_[i].Init(); | |||
| oscillator_[i].Init(); | |||
| } | |||
| noise_filter_.Init(); | |||
| } | |||
| void Render( | |||
| bool sustain, | |||
| bool trigger, | |||
| float accent, | |||
| float f0, | |||
| float tone, | |||
| float decay, | |||
| float snappy, | |||
| float* out, | |||
| size_t size) { | |||
| const float decay_xt = decay * (1.0f + decay * (decay - 1.0f)); | |||
| const int kTriggerPulseDuration = 1.0e-3 * kSampleRate; | |||
| const float kPulseDecayTime = 0.1e-3 * kSampleRate; | |||
| const float q = 2000.0f * stmlib::SemitonesToRatio(decay_xt * 84.0f); | |||
| const float noise_envelope_decay = 1.0f - 0.0017f * \ | |||
| stmlib::SemitonesToRatio(-decay * (50.0f + snappy * 10.0f)); | |||
| const float exciter_leak = snappy * (2.0f - snappy) * 0.1f; | |||
| snappy = snappy * 1.1f - 0.05f; | |||
| CONSTRAIN(snappy, 0.0f, 1.0f); | |||
| if (trigger) { | |||
| pulse_remaining_samples_ = kTriggerPulseDuration; | |||
| pulse_height_ = 3.0f + 7.0f * accent; | |||
| noise_envelope_ = 2.0f; | |||
| } | |||
| static const float kModeFrequencies[kNumModes] = { | |||
| 1.00f, | |||
| 2.00f, | |||
| 3.18f, | |||
| 4.16f, | |||
| 5.62f}; | |||
| float f[kNumModes]; | |||
| float gain[kNumModes]; | |||
| for (int i = 0; i < kNumModes; ++i) { | |||
| f[i] = std::min(f0 * kModeFrequencies[i], 0.499f); | |||
| resonator_[i].set_f_q<stmlib::FREQUENCY_FAST>( | |||
| f[i], | |||
| 1.0f + f[i] * (i == 0 ? q : q * 0.25f)); | |||
| } | |||
| if (tone < 0.666667f) { | |||
| // 808-style (2 modes) | |||
| tone *= 1.5f; | |||
| gain[0] = 1.5f + (1.0f - tone) * (1.0f - tone) * 4.5f; | |||
| gain[1] = 2.0f * tone + 0.15f; | |||
| std::fill(&gain[2], &gain[kNumModes], 0.0f); | |||
| } else { | |||
| // What the 808 could have been if there were extra modes! | |||
| tone = (tone - 0.666667f) * 3.0f; | |||
| gain[0] = 1.5f - tone * 0.5f; | |||
| gain[1] = 2.15f - tone * 0.7f; | |||
| for (int i = 2; i < kNumModes; ++i) { | |||
| gain[i] = tone; | |||
| tone *= tone; | |||
| } | |||
| } | |||
| float f_noise = f0 * 16.0f; | |||
| CONSTRAIN(f_noise, 0.0f, 0.499f); | |||
| noise_filter_.set_f_q<stmlib::FREQUENCY_FAST>( | |||
| f_noise, 1.0f + f_noise * 1.5f); | |||
| stmlib::ParameterInterpolator sustain_gain( | |||
| &sustain_gain_, | |||
| accent * decay, | |||
| size); | |||
| while (size--) { | |||
| // Q45 / Q46 | |||
| float pulse = 0.0f; | |||
| if (pulse_remaining_samples_) { | |||
| --pulse_remaining_samples_; | |||
| pulse = pulse_remaining_samples_ ? pulse_height_ : pulse_height_ - 1.0f; | |||
| pulse_ = pulse; | |||
| } else { | |||
| pulse_ *= 1.0f - 1.0f / kPulseDecayTime; | |||
| pulse = pulse_; | |||
| } | |||
| float sustain_gain_value = sustain_gain.Next(); | |||
| // R189 / C57 / R190 + C58 / C59 / R197 / R196 / IC14 | |||
| ONE_POLE(pulse_lp_, pulse, 0.75f); | |||
| float shell = 0.0f; | |||
| for (int i = 0; i < kNumModes; ++i) { | |||
| float excitation = i == 0 | |||
| ? (pulse - pulse_lp_) + 0.006f * pulse | |||
| : 0.026f * pulse; | |||
| shell += gain[i] * (sustain | |||
| ? oscillator_[i].Next(f[i]) * sustain_gain_value * 0.25f | |||
| : resonator_[i].Process<stmlib::FILTER_MODE_BAND_PASS>( | |||
| excitation) + excitation * exciter_leak); | |||
| } | |||
| shell = stmlib::SoftClip(shell); | |||
| // C56 / R194 / Q48 / C54 / R188 / D54 | |||
| float noise = 2.0f * stmlib::Random::GetFloat() - 1.0f; | |||
| if (noise < 0.0f) noise = 0.0f; | |||
| noise_envelope_ *= noise_envelope_decay; | |||
| noise *= (sustain ? sustain_gain_value : noise_envelope_) * snappy * 2.0f; | |||
| // C66 / R201 / C67 / R202 / R203 / Q49 | |||
| noise = noise_filter_.Process<stmlib::FILTER_MODE_BAND_PASS>(noise); | |||
| // IC13 | |||
| *out++ = noise + shell * (1.0f - snappy); | |||
| } | |||
| } | |||
| private: | |||
| int pulse_remaining_samples_; | |||
| float pulse_; | |||
| float pulse_height_; | |||
| float pulse_lp_; | |||
| float noise_envelope_; | |||
| float sustain_gain_; | |||
| stmlib::Svf resonator_[kNumModes]; | |||
| stmlib::Svf noise_filter_; | |||
| // Replace the resonators in "free running" (sustain) mode. | |||
| SineOscillator oscillator_[kNumModes]; | |||
| DISALLOW_COPY_AND_ASSIGN(AnalogSnareDrum); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DSP_DRUMS_ANALOG_SNARE_DRUM_H_ | |||
| @@ -0,0 +1,259 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // 808 HH, with a few extra parameters to push things to the CY territory... | |||
| // The template parameter MetallicNoiseSource allows another kind of "metallic | |||
| // noise" to be used, for results which are more similar to KR-55 or FM hi-hats. | |||
| #ifndef PLAITS_DSP_DRUMS_HI_HAT_H_ | |||
| #define PLAITS_DSP_DRUMS_HI_HAT_H_ | |||
| #include <algorithm> | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/dsp/filter.h" | |||
| #include "stmlib/dsp/parameter_interpolator.h" | |||
| #include "stmlib/dsp/units.h" | |||
| #include "stmlib/utils/random.h" | |||
| #include "plaits/dsp/dsp.h" | |||
| #include "plaits/dsp/oscillator/oscillator.h" | |||
| namespace plaits { | |||
| // 808 style "metallic noise" with 6 square oscillators. | |||
| class SquareNoise { | |||
| public: | |||
| SquareNoise() { } | |||
| ~SquareNoise() { } | |||
| void Init() { | |||
| std::fill(&phase_[0], &phase_[6], 0); | |||
| } | |||
| void Render(float f0, float* temp_1, float* temp_2, float* out, size_t size) { | |||
| const float ratios[6] = { | |||
| // Nominal f0: 414 Hz | |||
| 1.0f, 1.304f, 1.466f, 1.787f, 1.932f, 2.536f | |||
| }; | |||
| uint32_t increment[6]; | |||
| uint32_t phase[6]; | |||
| for (int i = 0; i < 6; ++i) { | |||
| float f = f0 * ratios[i]; | |||
| if (f >= 0.499f) f = 0.499f; | |||
| increment[i] = static_cast<uint32_t>(f * 4294967296.0f); | |||
| phase[i] = phase_[i]; | |||
| } | |||
| while (size--) { | |||
| phase[0] += increment[0]; | |||
| phase[1] += increment[1]; | |||
| phase[2] += increment[2]; | |||
| phase[3] += increment[3]; | |||
| phase[4] += increment[4]; | |||
| phase[5] += increment[5]; | |||
| uint32_t noise = 0; | |||
| noise += (phase[0] >> 31); | |||
| noise += (phase[1] >> 31); | |||
| noise += (phase[2] >> 31); | |||
| noise += (phase[3] >> 31); | |||
| noise += (phase[4] >> 31); | |||
| noise += (phase[5] >> 31); | |||
| *out++ = 0.33f * static_cast<float>(noise) - 1.0f; | |||
| } | |||
| for (int i = 0; i < 6; ++i) { | |||
| phase_[i] = phase[i]; | |||
| } | |||
| } | |||
| private: | |||
| uint32_t phase_[6]; | |||
| DISALLOW_COPY_AND_ASSIGN(SquareNoise); | |||
| }; | |||
| class RingModNoise { | |||
| public: | |||
| RingModNoise() { } | |||
| ~RingModNoise() { } | |||
| void Init() { | |||
| for (int i = 0; i < 6; ++i) { | |||
| oscillator_[i].Init(); | |||
| } | |||
| } | |||
| void Render(float f0, float* temp_1, float* temp_2, float* out, size_t size) { | |||
| const float ratio = f0 / (0.01f + f0); | |||
| const float f1a = 200.0f / kSampleRate * ratio; | |||
| const float f1b = 7530.0f / kSampleRate * ratio; | |||
| const float f2a = 510.0f / kSampleRate * ratio; | |||
| const float f2b = 8075.0f / kSampleRate * ratio; | |||
| const float f3a = 730.0f / kSampleRate * ratio; | |||
| const float f3b = 10500.0f / kSampleRate * ratio; | |||
| std::fill(&out[0], &out[size], 0.0f); | |||
| RenderPair(&oscillator_[0], f1a, f1b, temp_1, temp_2, out, size); | |||
| RenderPair(&oscillator_[2], f2a, f2b, temp_1, temp_2, out, size); | |||
| RenderPair(&oscillator_[4], f3a, f3b, temp_1, temp_2, out, size); | |||
| } | |||
| private: | |||
| void RenderPair( | |||
| Oscillator* osc, | |||
| float f1, | |||
| float f2, | |||
| float* temp_1, | |||
| float* temp_2, | |||
| float* out, | |||
| size_t size) { | |||
| osc[0].Render<OSCILLATOR_SHAPE_SQUARE>(f1, 0.5f, temp_1, size); | |||
| osc[1].Render<OSCILLATOR_SHAPE_SAW>(f2, 0.5f, temp_2, size); | |||
| while (size--) { | |||
| *out++ += *temp_1++ * *temp_2++; | |||
| } | |||
| } | |||
| Oscillator oscillator_[6]; | |||
| DISALLOW_COPY_AND_ASSIGN(RingModNoise); | |||
| }; | |||
| class SwingVCA { | |||
| public: | |||
| float operator()(float s, float gain) { | |||
| s *= s > 0.0f ? 10.0f : 0.1f; | |||
| s = s / (1.0f + fabsf(s)); | |||
| return (s + 1.0f) * gain; | |||
| } | |||
| }; | |||
| class LinearVCA { | |||
| public: | |||
| float operator()(float s, float gain) { | |||
| return s * gain; | |||
| } | |||
| }; | |||
| template<typename MetallicNoiseSource, typename VCA, bool resonance> | |||
| class HiHat { | |||
| public: | |||
| HiHat() { } | |||
| ~HiHat() { } | |||
| void Init() { | |||
| envelope_ = 0.0f; | |||
| noise_clock_ = 0.0f; | |||
| noise_sample_ = 0.0f; | |||
| sustain_gain_ = 0.0f; | |||
| metallic_noise_.Init(); | |||
| noise_coloration_svf_.Init(); | |||
| hpf_.Init(); | |||
| } | |||
| void Render( | |||
| bool sustain, | |||
| bool trigger, | |||
| float accent, | |||
| float f0, | |||
| float tone, | |||
| float decay, | |||
| float noisiness, | |||
| float* temp_1, | |||
| float* temp_2, | |||
| float* out, | |||
| size_t size) { | |||
| const float envelope_decay = 1.0f - 0.003f * stmlib::SemitonesToRatio( | |||
| -decay * 84.0f); | |||
| const float cut_decay = 1.0f - 0.0025f * stmlib::SemitonesToRatio( | |||
| -decay * 36.0f); | |||
| if (trigger) { | |||
| envelope_ = (1.5f + 0.5f * (1.0f - decay)) * (0.3f + 0.7f * accent); | |||
| } | |||
| // Render the metallic noise. | |||
| metallic_noise_.Render(2.0f * f0, temp_1, temp_2, out, size); | |||
| // Apply BPF on the metallic noise. | |||
| float cutoff = 150.0f / kSampleRate * stmlib::SemitonesToRatio( | |||
| tone * 72.0f); | |||
| CONSTRAIN(cutoff, 0.0f, 16000.0f / kSampleRate); | |||
| noise_coloration_svf_.set_f_q<stmlib::FREQUENCY_ACCURATE>( | |||
| cutoff, resonance ? 3.0f + 6.0f * tone : 1.0f); | |||
| noise_coloration_svf_.Process<stmlib::FILTER_MODE_BAND_PASS>( | |||
| out, out, size); | |||
| // This is not at all part of the 808 circuit! But to add more variety, we | |||
| // add a variable amount of clocked noise to the output of the 6 schmitt | |||
| // trigger oscillators. | |||
| noisiness *= noisiness; | |||
| float noise_f = f0 * (16.0f + 16.0f * (1.0f - noisiness)); | |||
| CONSTRAIN(noise_f, 0.0f, 0.5f); | |||
| for (size_t i = 0; i < size; ++i) { | |||
| noise_clock_ += noise_f; | |||
| if (noise_clock_ >= 1.0f) { | |||
| noise_clock_ -= 1.0f; | |||
| noise_sample_ = stmlib::Random::GetFloat() - 0.5f; | |||
| } | |||
| out[i] += noisiness * (noise_sample_ - out[i]); | |||
| } | |||
| // Apply VCA. | |||
| stmlib::ParameterInterpolator sustain_gain( | |||
| &sustain_gain_, | |||
| accent * decay, | |||
| size); | |||
| for (size_t i = 0; i < size; ++i) { | |||
| VCA vca; | |||
| envelope_ *= envelope_ > 0.5f ? envelope_decay : cut_decay; | |||
| out[i] = vca(out[i], sustain ? sustain_gain.Next() : envelope_); | |||
| } | |||
| hpf_.set_f_q<stmlib::FREQUENCY_ACCURATE>(cutoff, 0.5f); | |||
| hpf_.Process<stmlib::FILTER_MODE_HIGH_PASS>(out, out, size); | |||
| } | |||
| private: | |||
| float envelope_; | |||
| float noise_clock_; | |||
| float noise_sample_; | |||
| float sustain_gain_; | |||
| MetallicNoiseSource metallic_noise_; | |||
| stmlib::Svf noise_coloration_svf_; | |||
| stmlib::Svf hpf_; | |||
| DISALLOW_COPY_AND_ASSIGN(HiHat); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DSP_DRUMS_HI_HAT_H_ | |||
| @@ -0,0 +1,249 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Naive bass drum model (modulated oscillator with FM + envelope). | |||
| // Inadvertently 909-ish. | |||
| #ifndef PLAITS_DSP_DRUMS_SYNTHETIC_BASS_DRUM_H_ | |||
| #define PLAITS_DSP_DRUMS_SYNTHETIC_BASS_DRUM_H_ | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/dsp/units.h" | |||
| #include "stmlib/utils/random.h" | |||
| #include "plaits/dsp/dsp.h" | |||
| #include "plaits/resources.h" | |||
| namespace plaits { | |||
| class SyntheticBassDrumClick { | |||
| public: | |||
| SyntheticBassDrumClick() { } | |||
| ~SyntheticBassDrumClick() { } | |||
| void Init() { | |||
| lp_ = 0.0f; | |||
| hp_ = 0.0f; | |||
| filter_.Init(); | |||
| filter_.set_f_q<stmlib::FREQUENCY_FAST>(5000.0f / kSampleRate, 2.0f); | |||
| } | |||
| float Process(float in) { | |||
| SLOPE(lp_, in, 0.5f, 0.1f); | |||
| ONE_POLE(hp_, lp_, 0.04f); | |||
| return filter_.Process<stmlib::FILTER_MODE_LOW_PASS>(lp_ - hp_); | |||
| } | |||
| private: | |||
| float lp_; | |||
| float hp_; | |||
| stmlib::Svf filter_; | |||
| DISALLOW_COPY_AND_ASSIGN(SyntheticBassDrumClick); | |||
| }; | |||
| class SyntheticBassDrumAttackNoise { | |||
| public: | |||
| SyntheticBassDrumAttackNoise() { } | |||
| ~SyntheticBassDrumAttackNoise() { } | |||
| void Init() { | |||
| lp_ = 0.0f; | |||
| hp_ = 0.0f; | |||
| } | |||
| float Render() { | |||
| float sample = stmlib::Random::GetFloat(); | |||
| ONE_POLE(lp_, sample, 0.05f); | |||
| ONE_POLE(hp_, lp_, 0.005f); | |||
| return lp_ - hp_; | |||
| } | |||
| private: | |||
| float lp_; | |||
| float hp_; | |||
| DISALLOW_COPY_AND_ASSIGN(SyntheticBassDrumAttackNoise); | |||
| }; | |||
| class SyntheticBassDrum { | |||
| public: | |||
| SyntheticBassDrum() { } | |||
| ~SyntheticBassDrum() { } | |||
| void Init() { | |||
| phase_ = 0.0f; | |||
| phase_noise_ = 0.0f; | |||
| f0_ = 0.0f; | |||
| fm_ = 0.0f; | |||
| fm_lp_ = 0.0f; | |||
| body_env_lp_ = 0.0f; | |||
| body_env_ = 0.0f; | |||
| body_env_pulse_width_ = 0; | |||
| fm_pulse_width_ = 0; | |||
| tone_lp_ = 0.0f; | |||
| sustain_gain_ = 0.0f; | |||
| click_.Init(); | |||
| noise_.Init(); | |||
| } | |||
| inline float DistortedSine(float phase, float phase_noise, float dirtiness) { | |||
| phase += phase_noise * dirtiness; | |||
| MAKE_INTEGRAL_FRACTIONAL(phase); | |||
| phase = phase_fractional; | |||
| float triangle = (phase < 0.5f ? phase : 1.0f - phase) * 4.0f - 1.0f; | |||
| float sine = 2.0f * triangle / (1.0f + fabsf(triangle)); | |||
| float clean_sine = stmlib::InterpolateWrap( | |||
| lut_sine, phase + 0.75f, 1024.0f); | |||
| return sine + (1.0f - dirtiness) * (clean_sine - sine); | |||
| } | |||
| inline float TransistorVCA(float s, float gain) { | |||
| s = (s - 0.6f) * gain; | |||
| return 3.0f * s / (2.0f + fabsf(s)) + gain * 0.3f; | |||
| } | |||
| void Render( | |||
| bool sustain, | |||
| bool trigger, | |||
| float accent, | |||
| float f0, | |||
| float tone, | |||
| float decay, | |||
| float dirtiness, | |||
| float fm_envelope_amount, | |||
| float fm_envelope_decay, | |||
| float* out, | |||
| size_t size) { | |||
| decay *= decay; | |||
| fm_envelope_decay *= fm_envelope_decay; | |||
| stmlib::ParameterInterpolator f0_mod(&f0_, f0, size); | |||
| dirtiness *= std::max(1.0f - 8.0f * f0, 0.0f); | |||
| const float fm_decay = 1.0f - \ | |||
| 1.0f / (0.008f * (1.0f + fm_envelope_decay * 4.0f) * kSampleRate); | |||
| const float body_env_decay = 1.0f - 1.0f / (0.02f * kSampleRate) * \ | |||
| stmlib::SemitonesToRatio(-decay * 60.0f); | |||
| const float transient_env_decay = 1.0f - 1.0f / (0.005f * kSampleRate); | |||
| const float tone_f = std::min( | |||
| 4.0f * f0 * stmlib::SemitonesToRatio(tone * 108.0f), | |||
| 1.0f); | |||
| const float transient_level = tone; | |||
| if (trigger) { | |||
| fm_ = 1.0f; | |||
| body_env_ = transient_env_ = 0.3f + 0.7f * accent; | |||
| body_env_pulse_width_ = kSampleRate * 0.001f; | |||
| fm_pulse_width_ = kSampleRate * 0.0013f; | |||
| } | |||
| stmlib::ParameterInterpolator sustain_gain( | |||
| &sustain_gain_, | |||
| accent * decay, | |||
| size); | |||
| while (size--) { | |||
| ONE_POLE(phase_noise_, stmlib::Random::GetFloat() - 0.5f, 0.002f); | |||
| float mix = 0.0f; | |||
| if (sustain) { | |||
| phase_ += f0_mod.Next(); | |||
| if (phase_ >= 1.0f) { | |||
| phase_ -= 1.0f; | |||
| } | |||
| float body = DistortedSine(phase_, phase_noise_, dirtiness); | |||
| mix -= TransistorVCA(body, sustain_gain.Next()); | |||
| } else { | |||
| if (fm_pulse_width_) { | |||
| --fm_pulse_width_; | |||
| phase_ = 0.25f; | |||
| } else { | |||
| fm_ *= fm_decay; | |||
| float fm = 1.0f + fm_envelope_amount * 3.5f * fm_lp_; | |||
| phase_ += std::min(f0_mod.Next() * fm, 0.5f); | |||
| if (phase_ >= 1.0f) { | |||
| phase_ -= 1.0f; | |||
| } | |||
| } | |||
| if (body_env_pulse_width_) { | |||
| --body_env_pulse_width_; | |||
| } else { | |||
| body_env_ *= body_env_decay; | |||
| transient_env_ *= transient_env_decay; | |||
| } | |||
| const float envelope_lp_f = 0.1f; | |||
| ONE_POLE(body_env_lp_, body_env_, envelope_lp_f); | |||
| ONE_POLE(transient_env_lp_, transient_env_, envelope_lp_f); | |||
| ONE_POLE(fm_lp_, fm_, envelope_lp_f); | |||
| float body = DistortedSine(phase_, phase_noise_, dirtiness); | |||
| float transient = click_.Process( | |||
| body_env_pulse_width_ ? 0.0f : 1.0f) + noise_.Render(); | |||
| mix -= TransistorVCA(body, body_env_lp_); | |||
| mix -= transient * transient_env_lp_ * transient_level; | |||
| } | |||
| ONE_POLE(tone_lp_, mix, tone_f); | |||
| *out++ = tone_lp_; | |||
| } | |||
| } | |||
| private: | |||
| float f0_; | |||
| float phase_; | |||
| float phase_noise_; | |||
| float fm_; | |||
| float fm_lp_; | |||
| float body_env_; | |||
| float body_env_lp_; | |||
| float transient_env_; | |||
| float transient_env_lp_; | |||
| float sustain_gain_; | |||
| float tone_lp_; | |||
| SyntheticBassDrumClick click_; | |||
| SyntheticBassDrumAttackNoise noise_; | |||
| int body_env_pulse_width_; | |||
| int fm_pulse_width_; | |||
| DISALLOW_COPY_AND_ASSIGN(SyntheticBassDrum); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DSP_DRUMS_SYNTHETIC_BASS_DRUM_H_ | |||
| @@ -0,0 +1,198 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Naive snare drum model (two modulated oscillators + filtered noise). | |||
| // Uses a few magic numbers taken from the 909 schematics: | |||
| // - Ratio between the two modes of the drum set to 1.47. | |||
| // - Funky coupling between the two modes. | |||
| // - Noise coloration filters and envelope shapes for the snare. | |||
| #ifndef PLAITS_DSP_DRUMS_SYNTHETIC_SNARE_DRUM_H_ | |||
| #define PLAITS_DSP_DRUMS_SYNTHETIC_SNARE_DRUM_H_ | |||
| #include <algorithm> | |||
| #include "stmlib/dsp/dsp.h" | |||
| #include "stmlib/dsp/parameter_interpolator.h" | |||
| #include "stmlib/dsp/units.h" | |||
| #include "plaits/dsp/dsp.h" | |||
| namespace plaits { | |||
| class SyntheticSnareDrum { | |||
| public: | |||
| SyntheticSnareDrum() { } | |||
| ~SyntheticSnareDrum() { } | |||
| void Init() { | |||
| phase_[0] = 0.0f; | |||
| phase_[1] = 0.0f; | |||
| drum_amplitude_ = 0.0f; | |||
| snare_amplitude_ = 0.0f; | |||
| fm_ = 0.0f; | |||
| hold_counter_ = 0; | |||
| sustain_gain_ = 0.0f; | |||
| drum_lp_.Init(); | |||
| snare_hp_.Init(); | |||
| snare_lp_.Init(); | |||
| } | |||
| inline float DistortedSine(float phase) { | |||
| float triangle = (phase < 0.5f ? phase : 1.0f - phase) * 4.0f - 1.3f; | |||
| return 2.0f * triangle / (1.0f + fabsf(triangle)); | |||
| } | |||
| void Render( | |||
| bool sustain, | |||
| bool trigger, | |||
| float accent, | |||
| float f0, | |||
| float fm_amount, | |||
| float decay, | |||
| float snappy, | |||
| float* out, | |||
| size_t size) { | |||
| const float decay_xt = decay * (1.0f + decay * (decay - 1.0f)); | |||
| fm_amount *= fm_amount; | |||
| const float drum_decay = 1.0f - 1.0f / (0.015f * kSampleRate) * \ | |||
| stmlib::SemitonesToRatio( | |||
| -decay_xt * 72.0f - fm_amount * 12.0f + snappy * 7.0f); | |||
| const float snare_decay = 1.0f - 1.0f / (0.01f * kSampleRate) * \ | |||
| stmlib::SemitonesToRatio(-decay * 60.0f - snappy * 7.0f); | |||
| const float fm_decay = 1.0f - 1.0f / (0.007f * kSampleRate); | |||
| snappy = snappy * 1.1f - 0.05f; | |||
| CONSTRAIN(snappy, 0.0f, 1.0f); | |||
| const float drum_level = stmlib::Sqrt(1.0f - snappy); | |||
| const float snare_level = stmlib::Sqrt(snappy); | |||
| const float snare_f_min = std::min(10.0f * f0, 0.5f); | |||
| const float snare_f_max = std::min(35.0f * f0, 0.5f); | |||
| snare_hp_.set_f<stmlib::FREQUENCY_FAST>(snare_f_min); | |||
| snare_lp_.set_f_q<stmlib::FREQUENCY_FAST>(snare_f_max, | |||
| 0.5f + 2.0f * snappy); | |||
| drum_lp_.set_f<stmlib::FREQUENCY_FAST>(3.0f * f0); | |||
| if (trigger) { | |||
| snare_amplitude_ = drum_amplitude_ = 0.3f + 0.7f * accent; | |||
| fm_ = 1.0f; | |||
| phase_[0] = phase_[1] = 0.0f; | |||
| hold_counter_ = static_cast<int>((0.04f + decay * 0.03f) * kSampleRate); | |||
| } | |||
| stmlib::ParameterInterpolator sustain_gain( | |||
| &sustain_gain_, | |||
| accent * decay, | |||
| size); | |||
| while (size--) { | |||
| if (sustain) { | |||
| snare_amplitude_ = sustain_gain.Next(); | |||
| drum_amplitude_ = snare_amplitude_; | |||
| fm_ = 0.0f; | |||
| } else { | |||
| // Compute all D envelopes. | |||
| // The envelope for the drum has a very long tail. | |||
| // The envelope for the snare has a "hold" stage which lasts between | |||
| // 40 and 70 ms | |||
| drum_amplitude_ *= (drum_amplitude_ > 0.03f || !(size & 1)) | |||
| ? drum_decay | |||
| : 1.0f; | |||
| if (hold_counter_) { | |||
| --hold_counter_; | |||
| } else { | |||
| snare_amplitude_ *= snare_decay; | |||
| } | |||
| fm_ *= fm_decay; | |||
| } | |||
| // The 909 circuit has a funny kind of oscillator coupling - the signal | |||
| // leaving Q40's collector and resetting all oscillators allow some | |||
| // intermodulation. | |||
| float reset_noise = 0.0f; | |||
| float reset_noise_amount = (0.125f - f0) * 8.0f; | |||
| CONSTRAIN(reset_noise_amount, 0.0f, 1.0f); | |||
| reset_noise_amount *= reset_noise_amount; | |||
| reset_noise_amount *= fm_amount; | |||
| reset_noise += phase_[0] > 0.5f ? -1.0f : 1.0f; | |||
| reset_noise += phase_[1] > 0.5f ? -1.0f : 1.0f; | |||
| reset_noise *= reset_noise_amount * 0.025f; | |||
| float f = f0 * (1.0f + fm_amount * (4.0f * fm_)); | |||
| phase_[0] += f; | |||
| phase_[1] += f * 1.47f; | |||
| if (reset_noise_amount > 0.1f) { | |||
| if (phase_[0] >= 1.0f + reset_noise) { | |||
| phase_[0] = 1.0f - phase_[0]; | |||
| } | |||
| if (phase_[1] >= 1.0f + reset_noise) { | |||
| phase_[1] = 1.0f - phase_[1]; | |||
| } | |||
| } else { | |||
| if (phase_[0] >= 1.0f) { | |||
| phase_[0] -= 1.0f; | |||
| } | |||
| if (phase_[1] >= 1.0f) { | |||
| phase_[1] -= 1.0f; | |||
| } | |||
| } | |||
| float drum = -0.1f; | |||
| drum += DistortedSine(phase_[0]) * 0.60f; | |||
| drum += DistortedSine(phase_[1]) * 0.25f; | |||
| drum *= drum_amplitude_ * drum_level; | |||
| drum = drum_lp_.Process<stmlib::FILTER_MODE_LOW_PASS>(drum); | |||
| float noise = stmlib::Random::GetFloat(); | |||
| float snare = snare_lp_.Process<stmlib::FILTER_MODE_LOW_PASS>(noise); | |||
| snare = snare_hp_.Process<stmlib::FILTER_MODE_HIGH_PASS>(snare); | |||
| snare = (snare + 0.1f) * (snare_amplitude_ + fm_) * snare_level; | |||
| *out++ = snare + drum; // It's a snare, it's a drum, it's a snare drum. | |||
| } | |||
| } | |||
| private: | |||
| float phase_[2]; | |||
| float drum_amplitude_; | |||
| float snare_amplitude_; | |||
| float fm_; | |||
| float sustain_gain_; | |||
| int hold_counter_; | |||
| stmlib::OnePole drum_lp_; | |||
| stmlib::OnePole snare_hp_; | |||
| stmlib::Svf snare_lp_; | |||
| DISALLOW_COPY_AND_ASSIGN(SyntheticSnareDrum); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DSP_DRUMS_SYNTHETIC_SNARE_DRUM_H_ | |||
| @@ -0,0 +1,55 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Utility DSP routines. | |||
| #ifndef PLAITS_DSP_DSP_H_ | |||
| #define PLAITS_DSP_DSP_H_ | |||
| #include "stmlib/stmlib.h" | |||
| namespace plaits { | |||
| static const float kSampleRate = 48000.0f; | |||
| // There is no proper PLL for I2S, only a divider on the system clock to derive | |||
| // the bit clock. | |||
| // The division ratio is set to 47 (23 EVEN, 1 ODD) by the ST libraries. | |||
| // | |||
| // Bit clock = 72000000 / 47 = 1531.91 kHz | |||
| // Frame clock = Bit clock / 32 = 47872.34 Hz | |||
| // | |||
| // That's only 4.6 cts of error, but we care! | |||
| static const float kCorrectedSampleRate = 47872.34f; | |||
| const float a0 = (440.0f / 8.0f) / kCorrectedSampleRate; | |||
| const size_t kMaxBlockSize = 24; | |||
| const size_t kBlockSize = 12; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DSP_DSP_H_ | |||
| @@ -0,0 +1,152 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Additive synthesis with 32 partials. | |||
| #include "plaits/dsp/engine/additive_engine.h" | |||
| #include <algorithm> | |||
| #include "stmlib/dsp/cosine_oscillator.h" | |||
| #include "plaits/resources.h" | |||
| namespace plaits { | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| void AdditiveEngine::Init(BufferAllocator* allocator) { | |||
| fill( | |||
| &litudes_[0], | |||
| &litudes_[kNumHarmonics], | |||
| 0.0f); | |||
| for (int i = 0; i < kNumHarmonicOscillators; ++i) { | |||
| harmonic_oscillator_[i].Init(); | |||
| } | |||
| } | |||
| void AdditiveEngine::Reset() { | |||
| } | |||
| void AdditiveEngine::UpdateAmplitudes( | |||
| float centroid, | |||
| float slope, | |||
| float bumps, | |||
| float* amplitudes, | |||
| const int* harmonic_indices, | |||
| size_t num_harmonics) { | |||
| const float n = (static_cast<float>(num_harmonics) - 1.0f); | |||
| const float margin = (1.0f / slope - 1.0f) / (1.0f + bumps); | |||
| const float center = centroid * (n + margin) - 0.5f * margin; | |||
| float sum = 0.001f; | |||
| for (size_t i = 0; i < num_harmonics; ++i) { | |||
| float order = fabsf(static_cast<float>(i) - center) * slope; | |||
| float gain = 1.0f - order; | |||
| gain += fabsf(gain); | |||
| gain *= gain; | |||
| float b = 0.25f + order * bumps; | |||
| float bump_factor = 1.0f + InterpolateWrap(lut_sine, b, 1024.0f); | |||
| gain *= bump_factor; | |||
| gain *= gain; | |||
| gain *= gain; | |||
| int j = harmonic_indices[i]; | |||
| // Warning about the following line: this is not a proper LP filter because | |||
| // of the normalization. But in spite of its strange working, this line | |||
| // turns out ot be absolutely essential. | |||
| // | |||
| // I have tried both normalizing the LP-ed spectrum, and LP-ing the | |||
| // normalized spectrum, and both of them cause more annoyances than this | |||
| // "incorrect" solution. | |||
| ONE_POLE(amplitudes[j], gain, 0.001f); | |||
| sum += amplitudes[j]; | |||
| } | |||
| sum = 1.0f / sum; | |||
| for (size_t i = 0; i < num_harmonics; ++i) { | |||
| amplitudes[harmonic_indices[i]] *= sum; | |||
| } | |||
| } | |||
| inline float Bump(float x, float centroid, float slope) { | |||
| float d = fabsf(x - centroid); | |||
| float bump = 1.0f - d * slope; | |||
| return bump + fabsf(bump); | |||
| } | |||
| const int integer_harmonics[24] = { | |||
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | |||
| 16, 17, 18, 19, 20, 21, 22, 23 | |||
| }; | |||
| const int organ_harmonics[8] = { | |||
| 0, 1, 2, 3, 5, 7, 9, 11 | |||
| }; | |||
| void AdditiveEngine::Render( | |||
| const EngineParameters& parameters, | |||
| float* out, | |||
| float* aux, | |||
| size_t size, | |||
| bool* already_enveloped) { | |||
| const float f0 = NoteToFrequency(parameters.note); | |||
| const float centroid = parameters.timbre; | |||
| const float raw_bumps = parameters.harmonics; | |||
| const float raw_slope = (1.0f - 0.6f * raw_bumps) * parameters.morph; | |||
| const float slope = 0.01f + 1.99f * raw_slope * raw_slope * raw_slope; | |||
| const float bumps = 16.0f * raw_bumps * raw_bumps; | |||
| UpdateAmplitudes( | |||
| centroid, | |||
| slope, | |||
| bumps, | |||
| &litudes_[0], | |||
| integer_harmonics, | |||
| 24); | |||
| harmonic_oscillator_[0].Render<1>(f0, &litudes_[0], out, size); | |||
| harmonic_oscillator_[1].Render<13>(f0, &litudes_[12], out, size); | |||
| UpdateAmplitudes( | |||
| centroid, | |||
| slope, | |||
| bumps, | |||
| &litudes_[24], | |||
| organ_harmonics, | |||
| 8); | |||
| harmonic_oscillator_[2].Render<1>(f0, &litudes_[24], aux, size); | |||
| } | |||
| } // namespace plaits | |||
| @@ -0,0 +1,72 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // Additive synthesis with 24+8 partials. | |||
| #ifndef PLAITS_DSP_ENGINE_ADDITIVE_ENGINE_H_ | |||
| #define PLAITS_DSP_ENGINE_ADDITIVE_ENGINE_H_ | |||
| #include "plaits/dsp/engine/engine.h" | |||
| #include "plaits/dsp/oscillator/harmonic_oscillator.h" | |||
| namespace plaits { | |||
| const int kHarmonicBatchSize = 12; | |||
| const int kNumHarmonics = 36; | |||
| const int kNumHarmonicOscillators = kNumHarmonics / kHarmonicBatchSize; | |||
| class AdditiveEngine : public Engine { | |||
| public: | |||
| AdditiveEngine() { } | |||
| ~AdditiveEngine() { } | |||
| virtual void Init(stmlib::BufferAllocator* allocator); | |||
| virtual void Reset(); | |||
| virtual void Render(const EngineParameters& parameters, | |||
| float* out, | |||
| float* aux, | |||
| size_t size, | |||
| bool* already_enveloped); | |||
| private: | |||
| void UpdateAmplitudes( | |||
| float centroid, | |||
| float slope, | |||
| float bumps, | |||
| float* amplitudes, | |||
| const int* harmonic_indices, | |||
| size_t num_harmonics); | |||
| HarmonicOscillator<kHarmonicBatchSize> harmonic_oscillator_[kNumHarmonicOscillators]; | |||
| float amplitudes_[kNumHarmonics]; | |||
| DISALLOW_COPY_AND_ASSIGN(AdditiveEngine); | |||
| }; | |||
| } // namespace plaits | |||
| #endif // PLAITS_DSP_ENGINE_ADDITIVE_ENGINE_H_ | |||
| @@ -0,0 +1,96 @@ | |||
| // Copyright 2016 Olivier Gillet. | |||
| // | |||
| // Author: Olivier Gillet (ol.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. | |||
| // | |||
| // ----------------------------------------------------------------------------- | |||
| // | |||
| // 808 and synthetic bass drum generators. | |||
| #include "plaits/dsp/engine/bass_drum_engine.h" | |||
| #include <algorithm> | |||
| namespace plaits { | |||
| using namespace std; | |||
| using namespace stmlib; | |||
| void BassDrumEngine::Init(BufferAllocator* allocator) { | |||
| analog_bass_drum_.Init(); | |||
| synthetic_bass_drum_.Init(); | |||
| overdrive_.Init(); | |||
| } | |||
| void BassDrumEngine::Reset() { | |||
| } | |||
| void BassDrumEngine::Render( | |||
| const EngineParameters& parameters, | |||
| float* out, | |||
| float* aux, | |||
| size_t size, | |||
| bool* already_enveloped) { | |||
| const float f0 = NoteToFrequency(parameters.note); | |||
| const float attack_fm_amount = min(parameters.harmonics * 4.0f, 1.0f); | |||
| const float self_fm_amount = max(min(parameters.harmonics * 4.0f - 1.0f, 1.0f), 0.0f); | |||
| const float drive = max(parameters.harmonics * 2.0f - 1.0f, 0.0f) * \ | |||
| max(1.0f - 16.0f * f0, 0.0f); | |||
| const bool sustain = parameters.trigger & TRIGGER_UNPATCHED; | |||
| analog_bass_drum_.Render( | |||
| sustain, | |||
| parameters.trigger & TRIGGER_RISING_EDGE, | |||
| parameters.accent, | |||
| f0, | |||
| parameters.timbre, | |||
| parameters.morph, | |||
| attack_fm_amount, | |||
| self_fm_amount, | |||
| out, | |||
| size); | |||
| overdrive_.Process( | |||
| 0.5f + 0.5f * drive, | |||
| out, | |||
| size); | |||
| synthetic_bass_drum_.Render( | |||
| sustain, | |||
| parameters.trigger & TRIGGER_RISING_EDGE, | |||
| parameters.accent, | |||
| f0, | |||
| parameters.timbre, | |||
| parameters.morph, | |||
| sustain | |||
| ? parameters.harmonics | |||
| : 0.4f - 0.25f * parameters.morph * parameters.morph, | |||
| min(parameters.harmonics * 2.0f, 1.0f), | |||
| max(parameters.harmonics * 2.0f - 1.0f, 0.0f), | |||
| aux, | |||
| size); | |||
| } | |||
| } // namespace plaits | |||