@@ -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 | ### Macro Oscillator | ||||
Based on [Braids](https://mutable-instruments.net/modules/braids), [Manual](https://mutable-instruments.net/modules/braids/manual/) | Based on [Braids](https://mutable-instruments.net/modules/braids), [Manual](https://mutable-instruments.net/modules/braids/manual/) | ||||
 | |||||
- Sync input doesn't work | - Sync input doesn't work | ||||
- More settings could be supported | - More settings could be supported | ||||
### Modal Synthesizer | ### Modal Synthesizer | ||||
Based on [Elements](https://mutable-instruments.net/modules/elements), [Manual](https://mutable-instruments.net/modules/elements/manual/) | Based on [Elements](https://mutable-instruments.net/modules/elements), [Manual](https://mutable-instruments.net/modules/elements/manual/) | ||||
 | |||||
### Tidal Modulator | ### Tidal Modulator | ||||
Based on [Tides](https://mutable-instruments.net/modules/tides), [Manual](https://mutable-instruments.net/modules/tides/manual/) | Based on [Tides](https://mutable-instruments.net/modules/tides), [Manual](https://mutable-instruments.net/modules/tides/manual/) | ||||
 | |||||
### Wavetable Oscillator | ### Wavetable Oscillator | ||||
Based on [Sheep](https://mutable-instruments.net/modules/tides/firmware/) (Tides alternative firmware) | Based on [Sheep](https://mutable-instruments.net/modules/tides/firmware/) (Tides alternative firmware) | ||||
### Texture Synthesizer | ### Texture Synthesizer | ||||
Based on [Clouds](https://mutable-instruments.net/modules/clouds), [Manual](https://mutable-instruments.net/modules/clouds/manual/) | Based on [Clouds](https://mutable-instruments.net/modules/clouds), [Manual](https://mutable-instruments.net/modules/clouds/manual/) | ||||
 | |||||
- edit buttons and lights | - edit buttons and lights | ||||
- freeze button | - freeze button | ||||
- right-click context menus to replace menu diving | - 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 | ### Meta Modulator | ||||
Based on [Warps](https://mutable-instruments.net/modules/warps), [Manual](https://mutable-instruments.net/modules/warps/manual/) | Based on [Warps](https://mutable-instruments.net/modules/warps), [Manual](https://mutable-instruments.net/modules/warps/manual/) | ||||
 | |||||
### Resonator | ### Resonator | ||||
Based on [Rings](https://mutable-instruments.net/modules/rings), [Manual](https://mutable-instruments.net/modules/rings/manual/) | Based on [Rings](https://mutable-instruments.net/modules/rings), [Manual](https://mutable-instruments.net/modules/rings/manual/) | ||||
 | |||||
### Keyframer/Mixer | ### Keyframer/Mixer | ||||
Based on [Frames](https://mutable-instruments.net/modules/frames), [Manual](https://mutable-instruments.net/modules/frames/manual/) | Based on [Frames](https://mutable-instruments.net/modules/frames), [Manual](https://mutable-instruments.net/modules/frames/manual/) | ||||
### Multiples | ### Multiples | ||||
Based on [Links](https://mutable-instruments.net/modules/links), [Manual](https://mutable-instruments.net/modules/links/manual/) | Based on [Links](https://mutable-instruments.net/modules/links), [Manual](https://mutable-instruments.net/modules/links/manual/) | ||||
 | |||||
### Utilities | ### Utilities | ||||
Based on [Kinks](https://mutable-instruments.net/modules/kinks), [Manual](https://mutable-instruments.net/modules/kinks/manual/) | Based on [Kinks](https://mutable-instruments.net/modules/kinks), [Manual](https://mutable-instruments.net/modules/kinks/manual/) | ||||
 | |||||
### Mixer | ### Mixer | ||||
Based on [Shades](https://mutable-instruments.net/modules/shades), [Manual](https://mutable-instruments.net/modules/shades/manual/) | Based on [Shades](https://mutable-instruments.net/modules/shades), [Manual](https://mutable-instruments.net/modules/shades/manual/) | ||||
 | |||||
### Bernoulli Gate | ### Bernoulli Gate | ||||
Based on [Branches](https://mutable-instruments.net/modules/branches), [Manual](https://mutable-instruments.net/modules/branches/manual/) | Based on [Branches](https://mutable-instruments.net/modules/branches), [Manual](https://mutable-instruments.net/modules/branches/manual/) | ||||
 | |||||
### Quad VC-polarizer | ### Quad VC-polarizer | ||||
Based on [Blinds](https://mutable-instruments.net/modules/blinds), [Manual](https://mutable-instruments.net/modules/blinds/manual/) | Based on [Blinds](https://mutable-instruments.net/modules/blinds), [Manual](https://mutable-instruments.net/modules/blinds/manual/) | ||||
 | |||||
### Quad VCA | ### Quad VCA | ||||
Based on [Veils](https://mutable-instruments.net/modules/veils), [Manual](https://mutable-instruments.net/modules/veils/manual/) | Based on [Veils](https://mutable-instruments.net/modules/veils), [Manual](https://mutable-instruments.net/modules/veils/manual/) | ||||
 | |||||
## Not yet ported | ## 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) | ### [Edges](https://mutable-instruments.net/modules/edges) | ||||
[Manual](https://mutable-instruments.net/modules/edges/manual/) | [Manual](https://mutable-instruments.net/modules/edges/manual/) | ||||
- GPL, will not port | |||||
### [Grids](https://mutable-instruments.net/modules/grids) | ### [Grids](https://mutable-instruments.net/modules/grids) | ||||
[Manual](https://mutable-instruments.net/modules/grids/manual/) | [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 |