@@ -1,12 +0,0 @@ | |||
[submodule "ext/nanovg"] | |||
path = dep/nanovg | |||
url = https://github.com/memononen/nanovg.git | |||
[submodule "ext/nanosvg"] | |||
path = dep/nanosvg | |||
url = https://github.com/memononen/nanosvg.git | |||
[submodule "ext/osdialog"] | |||
path = dep/osdialog | |||
url = https://github.com/AndrewBelt/osdialog.git | |||
[submodule "ext/oui-blendish"] | |||
path = dep/oui-blendish | |||
url = https://github.com/AndrewBelt/oui-blendish.git |
@@ -0,0 +1,12 @@ | |||
### 0.6.3 (2018-10-10) | |||
- Added Segment Generator | |||
### 0.6.2 (2018-10-09) | |||
- Added Random Sampler from Audible Instruments Preview | |||
### 0.6.1 (2018-09-12) | |||
- Added Macro Oscillator 2 from Audible Instruments Preview |
@@ -16,28 +16,20 @@ After checking out AudibleInstruments in the `plugins/` directory, get external | |||
### Macro Oscillator | |||
Based on [Braids](https://mutable-instruments.net/modules/braids), [Manual](https://mutable-instruments.net/modules/braids/manual/) | |||
 | |||
- Sync input doesn't work | |||
- More settings could be supported | |||
### Modal Synthesizer | |||
Based on [Elements](https://mutable-instruments.net/modules/elements), [Manual](https://mutable-instruments.net/modules/elements/manual/) | |||
 | |||
### Tidal Modulator | |||
Based on [Tides](https://mutable-instruments.net/modules/tides), [Manual](https://mutable-instruments.net/modules/tides/manual/) | |||
 | |||
### Wavetable Oscillator | |||
Based on [Sheep](https://mutable-instruments.net/modules/tides/firmware/) (Tides alternative firmware) | |||
### Texture Synthesizer | |||
Based on [Clouds](https://mutable-instruments.net/modules/clouds), [Manual](https://mutable-instruments.net/modules/clouds/manual/) | |||
 | |||
- edit buttons and lights | |||
- freeze button | |||
- right-click context menus to replace menu diving | |||
@@ -45,46 +37,30 @@ Based on [Clouds](https://mutable-instruments.net/modules/clouds), [Manual](http | |||
### Meta Modulator | |||
Based on [Warps](https://mutable-instruments.net/modules/warps), [Manual](https://mutable-instruments.net/modules/warps/manual/) | |||
 | |||
### Resonator | |||
Based on [Rings](https://mutable-instruments.net/modules/rings), [Manual](https://mutable-instruments.net/modules/rings/manual/) | |||
 | |||
### Keyframer/Mixer | |||
Based on [Frames](https://mutable-instruments.net/modules/frames), [Manual](https://mutable-instruments.net/modules/frames/manual/) | |||
### Multiples | |||
Based on [Links](https://mutable-instruments.net/modules/links), [Manual](https://mutable-instruments.net/modules/links/manual/) | |||
 | |||
### Utilities | |||
Based on [Kinks](https://mutable-instruments.net/modules/kinks), [Manual](https://mutable-instruments.net/modules/kinks/manual/) | |||
 | |||
### Mixer | |||
Based on [Shades](https://mutable-instruments.net/modules/shades), [Manual](https://mutable-instruments.net/modules/shades/manual/) | |||
 | |||
### Bernoulli Gate | |||
Based on [Branches](https://mutable-instruments.net/modules/branches), [Manual](https://mutable-instruments.net/modules/branches/manual/) | |||
 | |||
### Quad VC-polarizer | |||
Based on [Blinds](https://mutable-instruments.net/modules/blinds), [Manual](https://mutable-instruments.net/modules/blinds/manual/) | |||
 | |||
### Quad VCA | |||
Based on [Veils](https://mutable-instruments.net/modules/veils), [Manual](https://mutable-instruments.net/modules/veils/manual/) | |||
 | |||
## Not yet ported | |||
@@ -113,9 +89,7 @@ Based on [Veils](https://mutable-instruments.net/modules/veils), [Manual](https: | |||
### [Edges](https://mutable-instruments.net/modules/edges) | |||
[Manual](https://mutable-instruments.net/modules/edges/manual/) | |||
- GPL, will not port | |||
### [Grids](https://mutable-instruments.net/modules/grids) | |||
[Manual](https://mutable-instruments.net/modules/grids/manual/) | |||
- GPL, will not port | |||
@@ -0,0 +1,317 @@ | |||
// Copyright 2014 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
#include "stmlib/system/bootloader_utils.h" | |||
#include "stmlib/system/system_clock.h" | |||
#include "marbles/drivers/adc.h" | |||
#include "marbles/drivers/dac.h" | |||
#include "marbles/drivers/leds.h" | |||
#include "marbles/drivers/switches.h" | |||
#include "marbles/drivers/system.h" | |||
#include "stm_audio_bootloader/qpsk/packet_decoder.h" | |||
#include "stm_audio_bootloader/qpsk/demodulator.h" | |||
#include <cstring> | |||
using namespace marbles; | |||
using namespace stmlib; | |||
using namespace stm_audio_bootloader; | |||
const double kSampleRate = 48000.0; | |||
const double kModulationRate = 6000.0; | |||
const double kBitRate = 12000.0; | |||
const uint32_t kStartAddress = 0x08008000; | |||
Adc adc; | |||
Dac dac; | |||
Leds leds; | |||
Switches switches; | |||
PacketDecoder decoder; | |||
Demodulator demodulator; | |||
int __errno; | |||
void UpdateLeds(); | |||
volatile bool switch_released = false; | |||
// Default interrupt handlers. | |||
extern "C" { | |||
void NMI_Handler() { } | |||
void HardFault_Handler() { while (1); } | |||
void MemManage_Handler() { while (1); } | |||
void BusFault_Handler() { while (1); } | |||
void UsageFault_Handler() { while (1); } | |||
void SVC_Handler() { } | |||
void DebugMon_Handler() { } | |||
void PendSV_Handler() { } | |||
void SysTick_Handler() { | |||
IWDG_ReloadCounter(); | |||
system_clock.Tick(); | |||
switches.Debounce(); | |||
if (switches.released(SWITCH_T_DEJA_VU)) { | |||
switch_released = true; | |||
} | |||
UpdateLeds(); | |||
} | |||
} | |||
enum UiState { | |||
UI_STATE_WAITING, | |||
UI_STATE_RECEIVING, | |||
UI_STATE_ERROR, | |||
UI_STATE_WRITING | |||
}; | |||
volatile UiState ui_state; | |||
volatile int32_t peak; | |||
void UpdateLeds() { | |||
leds.Clear(); | |||
switch (ui_state) { | |||
case UI_STATE_WAITING: | |||
leds.set( | |||
LED_T_DEJA_VU, | |||
system_clock.milliseconds() & 128 ? LED_COLOR_GREEN : 0); | |||
leds.set( | |||
LED_X_DEJA_VU, | |||
system_clock.milliseconds() & 128 ? 0 : LED_COLOR_GREEN); | |||
break; | |||
case UI_STATE_RECEIVING: | |||
leds.set( | |||
LED_T_DEJA_VU, | |||
system_clock.milliseconds() & 32 ? LED_COLOR_GREEN : 0); | |||
leds.set( | |||
LED_X_DEJA_VU, | |||
system_clock.milliseconds() & 32 ? 0 : LED_COLOR_GREEN); | |||
break; | |||
case UI_STATE_ERROR: | |||
{ | |||
bool on = system_clock.milliseconds() & 256; | |||
for (int i = 0; i < LED_LAST; ++i) { | |||
leds.set(Led(i), on ? LED_COLOR_RED : 0); | |||
} | |||
} | |||
break; | |||
case UI_STATE_WRITING: | |||
{ | |||
for (int i = 0; i < LED_LAST; ++i) { | |||
leds.set(Led(i), LED_COLOR_GREEN); | |||
} | |||
} | |||
break; | |||
} | |||
if (ui_state != UI_STATE_WRITING) { | |||
uint8_t pwm = system_clock.milliseconds() & 15; | |||
if (peak < 8192) { | |||
leds.set( | |||
LED_T_RANGE, | |||
(peak >> 9) > pwm ? LED_COLOR_GREEN : 0); | |||
} else if (peak < 16384) { | |||
leds.set( | |||
LED_T_RANGE, | |||
((peak - 8192) >> 9) >= pwm ? LED_COLOR_YELLOW : LED_COLOR_GREEN); | |||
} else if (peak < 16384 + 8192) { | |||
leds.set( | |||
LED_T_RANGE, | |||
((peak - 16384 - 8192) >> 9) >= pwm ? | |||
LED_COLOR_RED : LED_COLOR_YELLOW); | |||
} else { | |||
leds.set(LED_T_RANGE, LED_COLOR_RED); | |||
} | |||
} | |||
leds.Write(); | |||
} | |||
int32_t dc_offset = 0; | |||
int32_t gain_pot = 16; | |||
size_t discard_samples = 8000; | |||
IOBuffer::Block block; | |||
IOBuffer::Slice FillBuffer(size_t size) { | |||
adc.Convert(); | |||
if (!discard_samples) { | |||
// Scan gain pot. | |||
gain_pot = (adc.value(ADC_GROUP_POT) + 4095 * gain_pot) >> 12; | |||
int32_t gain = ((gain_pot >> 1) * gain_pot >> 21) + 128; | |||
// Extract sample. Note: there's a DC offset :/ | |||
int32_t sample = 32768 - static_cast<int32_t>(adc.value(ADC_GROUP_CV)); | |||
dc_offset += (sample - (dc_offset >> 15)); | |||
sample = (sample - (dc_offset >> 15)) * gain >> 8; // 0.5x to 4x | |||
CONSTRAIN(sample, -32768, 32767); | |||
// Update peak-meter | |||
int32_t rect = sample > 0 ? sample : -sample; | |||
peak = rect > peak ? rect : (rect + 32767 * peak) >> 15; | |||
// Write to DAC for monitoring | |||
block.cv_output[0][0] = 32767 - sample; | |||
block.cv_output[1][0] = 32767 - sample; | |||
block.cv_output[2][0] = 32767 - sample; | |||
block.cv_output[3][0] = 32767 - sample; | |||
demodulator.PushSample(2048 + (sample >> 4)); | |||
} else { | |||
--discard_samples; | |||
} | |||
IOBuffer::Slice s; | |||
s.block = █ | |||
s.frame_index = 0; | |||
return s; | |||
} | |||
static size_t current_address; | |||
static uint16_t packet_index; | |||
static uint32_t kSectorBaseAddress[] = { | |||
0x08000000, | |||
0x08004000, | |||
0x08008000, | |||
0x0800C000, | |||
0x08010000, | |||
0x08020000, | |||
0x08040000, | |||
0x08060000, | |||
0x08080000, | |||
0x080A0000, | |||
0x080C0000, | |||
0x080E0000 | |||
}; | |||
const uint32_t kBlockSize = 16384; | |||
const uint16_t kPacketsPerBlock = ::kBlockSize / kPacketSize; | |||
uint8_t rx_buffer[::kBlockSize]; | |||
void ProgramPage(const uint8_t* data, size_t size) { | |||
FLASH_Unlock(); | |||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | | |||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR); | |||
for (int32_t i = 0; i < 12; ++i) { | |||
if (current_address == kSectorBaseAddress[i]) { | |||
FLASH_EraseSector(i * 8, VoltageRange_3); | |||
} | |||
} | |||
const uint32_t* words = static_cast<const uint32_t*>( | |||
static_cast<const void*>(data)); | |||
for (size_t written = 0; written < size; written += 4) { | |||
FLASH_ProgramWord(current_address, *words++); | |||
current_address += 4; | |||
} | |||
} | |||
void InitializeReception() { | |||
decoder.Init(20000); | |||
demodulator.Init( | |||
kModulationRate / kSampleRate * 4294967296.0, | |||
kSampleRate / kModulationRate, | |||
2.0 * kSampleRate / kBitRate); | |||
demodulator.SyncCarrier(true); | |||
decoder.Reset(); | |||
current_address = kStartAddress; | |||
packet_index = 0; | |||
ui_state = UI_STATE_WAITING; | |||
} | |||
void Init() { | |||
System sys; | |||
switches.Init(); | |||
sys.Init(false); | |||
system_clock.Init(); | |||
adc.Init(true); | |||
dac.Init(48000, 1); | |||
leds.Init(); | |||
sys.StartTimers(); | |||
dac.Start(&FillBuffer); | |||
} | |||
int main(void) { | |||
Init(); | |||
InitializeReception(); | |||
bool exit_updater = !switches.pressed_immediate(SWITCH_T_DEJA_VU); | |||
while (!exit_updater) { | |||
bool error = false; | |||
if (demodulator.state() == DEMODULATOR_STATE_OVERFLOW) { | |||
error = true; | |||
} else { | |||
demodulator.ProcessAtLeast(32); | |||
} | |||
while (demodulator.available() && !error && !exit_updater) { | |||
uint8_t symbol = demodulator.NextSymbol(); | |||
PacketDecoderState state = decoder.ProcessSymbol(symbol); | |||
switch (state) { | |||
case PACKET_DECODER_STATE_OK: | |||
{ | |||
ui_state = UI_STATE_RECEIVING; | |||
memcpy( | |||
rx_buffer + (packet_index % kPacketsPerBlock) * kPacketSize, | |||
decoder.packet_data(), | |||
kPacketSize); | |||
++packet_index; | |||
if ((packet_index % kPacketsPerBlock) == 0) { | |||
ui_state = UI_STATE_WRITING; | |||
ProgramPage(rx_buffer, ::kBlockSize); | |||
decoder.Reset(); | |||
demodulator.SyncCarrier(false); | |||
} else { | |||
decoder.Reset(); | |||
demodulator.SyncDecision(); | |||
} | |||
} | |||
break; | |||
case PACKET_DECODER_STATE_ERROR_SYNC: | |||
case PACKET_DECODER_STATE_ERROR_CRC: | |||
error = true; | |||
break; | |||
case PACKET_DECODER_STATE_END_OF_TRANSMISSION: | |||
exit_updater = true; | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
if (error) { | |||
ui_state = UI_STATE_ERROR; | |||
switch_released = false; | |||
while (!switch_released); // Polled in ISR | |||
InitializeReception(); | |||
} | |||
} | |||
adc.DeInit(); | |||
Uninitialize(); | |||
JumpTo(kStartAddress); | |||
while (1) { } | |||
} |
@@ -0,0 +1,47 @@ | |||
# Copyright 2014 Olivier Gillet. | |||
# | |||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||
# | |||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||
# of this software and associated documentation files (the "Software"), to deal | |||
# in the Software without restriction, including without limitation the rights | |||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
# copies of the Software, and to permit persons to whom the Software is | |||
# furnished to do so, subject to the following conditions: | |||
# | |||
# The above copyright notice and this permission notice shall be included in | |||
# all copies or substantial portions of the Software. | |||
# | |||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
# THE SOFTWARE. | |||
# | |||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||
# System specifications | |||
F_CRYSTAL = 8000000L | |||
F_CPU = 168000000L | |||
SYSCLOCK = SYSCLK_FREQ_168MHz | |||
FAMILY = f4xx | |||
# USB = enabled | |||
# Preferred upload command | |||
UPLOAD_COMMAND = upload_jtag_erase_first | |||
# Packages to build | |||
TARGET = marbles_bootloader | |||
PACKAGES = marbles/bootloader \ | |||
marbles/drivers \ | |||
stm_audio_bootloader/qpsk \ | |||
stmlib/dsp \ | |||
stmlib/utils \ | |||
stmlib/system | |||
RESOURCES = marbles/resources | |||
TOOLCHAIN_PATH ?= /usr/local/arm-4.8.3/ | |||
include stmlib/makefile.inc |
@@ -0,0 +1,87 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Class for detecting if the t1 or t2 gate outputs are patched into the X | |||
// clock input. This is done by comparing the number of synchronous transitions | |||
// on the gate ouputs and the clock input. A small margin of error is allowed | |||
// because of acquisition delays. | |||
#ifndef MARBLES_CLOCK_SELF_PATCHING_DETECTOR_H_ | |||
#define MARBLES_CLOCK_SELF_PATCHING_DETECTOR_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/io_buffer.h" | |||
namespace marbles { | |||
class ClockSelfPatchingDetector { | |||
public: | |||
ClockSelfPatchingDetector() { } | |||
~ClockSelfPatchingDetector() { } | |||
void Init(size_t index) { | |||
index_ = index; | |||
error_streak_ = 0; | |||
match_length_ = 0; | |||
synchronous_transitions_ = 0; | |||
} | |||
size_t Process(IOBuffer::Block* block, size_t size) { | |||
for (size_t i = 0; i < size; ++i) { | |||
if (block->input[1][i] & stmlib::GATE_FLAG_RISING) { | |||
if (match_length_ >= 12) { | |||
++synchronous_transitions_; | |||
} | |||
error_streak_ = 0; | |||
match_length_ = 0; | |||
} | |||
bool output_gate = block->gate_output[index_][i]; | |||
bool input_gate = block->input[1][i] & stmlib::GATE_FLAG_HIGH; | |||
if (output_gate != input_gate) { | |||
++error_streak_; | |||
if (error_streak_ >= 6) { | |||
synchronous_transitions_ = 0; | |||
} | |||
} else { | |||
++match_length_; | |||
} | |||
} | |||
return synchronous_transitions_; | |||
} | |||
private: | |||
size_t index_; | |||
size_t error_streak_; | |||
size_t match_length_; | |||
size_t synchronous_transitions_; | |||
DISALLOW_COPY_AND_ASSIGN(ClockSelfPatchingDetector); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_CLOCK_SELF_PATCHING_DETECTOR_H_ |
@@ -0,0 +1,102 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// CV reader. | |||
#include "marbles/cv_reader.h" | |||
#include <algorithm> | |||
#include "stmlib/dsp/dsp.h" | |||
namespace marbles { | |||
using namespace std; | |||
using namespace stmlib; | |||
/* static */ | |||
const CvReaderChannel::Settings CvReader::channel_settings_[] = { | |||
// cv_lp | pot_scale | pot_offset | pot_lp | min | max | hysteresis | |||
// ADC_CHANNEL_DEJA_VU_AMOUNT, | |||
{ 0.05f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.00f }, | |||
// ADC_CHANNEL_X_SPREAD_2 / ADC_CHANNEL_DEJA_VU_LENGTH, | |||
{ 0.05f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.00f }, | |||
// ADC_CHANNEL_T_RATE, | |||
{ 0.2f, 120.0f, -60.0f, 0.01f, -120.0f, 120.0f, 0.001f }, | |||
// ADC_CHANNEL_T_BIAS, | |||
{ 0.05f, 1.05f, -0.025f, 0.01f, 0.0f, 1.0f, 0.00f }, | |||
// ADC_CHANNEL_T_JITTER, | |||
{ 0.05f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.00f }, | |||
// ADC_CHANNEL_X_SPREAD, | |||
{ 0.1f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.01f }, | |||
// ADC_CHANNEL_X_BIAS, | |||
{ 0.1f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.02f }, | |||
// ADC_CHANNEL_X_STEPS, | |||
{ 0.05f, 1.0f, 0.0f, 0.01f, 0.0f, 1.0f, 0.02f }, | |||
}; | |||
void CvReader::Init(CalibrationData* calibration_data) { | |||
calibration_data_ = calibration_data; | |||
adc_.Init(false); | |||
for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
channel_[i].Init( | |||
&calibration_data_->adc_scale[i], | |||
&calibration_data_->adc_offset[i], | |||
channel_settings_[i]); | |||
} | |||
fill(&attenuverter_[0], &attenuverter_[ADC_CHANNEL_LAST], 1.0f); | |||
// Set virtual attenuverter to 12 o'clock to ignore the non-existing | |||
// CV input for DEJA VU length. | |||
attenuverter_[ADC_CHANNEL_DEJA_VU_LENGTH] = 0.5f; | |||
// Set virtual attenuverter to a little more than 100% to | |||
// compensate for op-amp clipping and get full parameter swing. | |||
attenuverter_[ADC_CHANNEL_DEJA_VU_AMOUNT] = 1.01f; | |||
attenuverter_[ADC_CHANNEL_T_BIAS] = 1.01f; | |||
attenuverter_[ADC_CHANNEL_T_JITTER] = 1.01f; | |||
attenuverter_[ADC_CHANNEL_X_SPREAD] = 1.01f; | |||
attenuverter_[ADC_CHANNEL_X_BIAS] = 1.01f; | |||
attenuverter_[ADC_CHANNEL_X_STEPS] = 1.01f; | |||
} | |||
void CvReader::Copy(uint16_t* output) { | |||
const uint16_t* adc_values = adc_.values(); | |||
copy(&adc_values[0], &adc_values[ADC_CHANNEL_LAST * 2], output); | |||
adc_.Convert(); | |||
} | |||
void CvReader::Process(const uint16_t* raw_values, float* output) { | |||
for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
output[i] = channel_[i].Process( | |||
static_cast<float>(raw_values[ADC_GROUP_POT + i]) / 65536.0f, | |||
static_cast<float>(raw_values[ADC_GROUP_CV + i]) / 65536.0f, | |||
attenuverter_[i]); | |||
} | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,129 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// CV reader. | |||
#ifndef MARBLES_CV_READER_H_ | |||
#define MARBLES_CV_READER_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/drivers/adc.h" | |||
#include "marbles/cv_reader_channel.h" | |||
#include "marbles/settings.h" | |||
namespace marbles { | |||
class CvReader { | |||
public: | |||
CvReader() { } | |||
~CvReader() { } | |||
void Init(CalibrationData* calibration_data); | |||
inline bool ready_for_calibration() const { | |||
return true; | |||
} | |||
inline void CalibrateRateC1() { | |||
cv_c1_[0] = channel_[ADC_CHANNEL_T_RATE].unscaled_cv_lp(); | |||
} | |||
inline void CalibrateRateC3() { | |||
cv_c3_[0] = channel_[ADC_CHANNEL_T_RATE].unscaled_cv_lp(); | |||
} | |||
inline void CalibrateSpreadC1() { | |||
cv_c1_[1] = 0.5f * channel_[ADC_CHANNEL_X_SPREAD].unscaled_cv_lp() + \ | |||
0.5f * channel_[ADC_CHANNEL_X_SPREAD_2].unscaled_cv_lp(); | |||
} | |||
inline bool CalibrateSpreadC3() { | |||
cv_c3_[1] = 0.5f * channel_[ADC_CHANNEL_X_SPREAD].unscaled_cv_lp() + \ | |||
0.5f * channel_[ADC_CHANNEL_X_SPREAD_2].unscaled_cv_lp(); | |||
for (int i = 0; i < 2; ++i) { | |||
float c3 = cv_c3_[i]; // 0.2 | |||
float c1 = cv_c1_[i]; // 0.4 | |||
float delta = c3 - c1; | |||
float target_scale = i == 0 ? 24.0f : 0.4f; | |||
float target_offset = i == 0 ? 12.0f : 0.2f; | |||
if (delta > -0.3f && delta < -0.1f) { | |||
int channel = i == 0 ? ADC_CHANNEL_T_RATE : ADC_CHANNEL_X_SPREAD; | |||
calibration_data_->adc_scale[channel] = target_scale / (c3 - c1); | |||
calibration_data_->adc_offset[channel] = target_offset - \ | |||
calibration_data_->adc_scale[channel] * c1; | |||
} else { | |||
return false; | |||
} | |||
} | |||
calibration_data_->adc_scale[ADC_CHANNEL_X_SPREAD_2] = calibration_data_->adc_scale[ADC_CHANNEL_X_SPREAD]; | |||
calibration_data_->adc_offset[ADC_CHANNEL_X_SPREAD_2] = calibration_data_->adc_offset[ADC_CHANNEL_X_SPREAD]; | |||
return true; | |||
} | |||
inline void CalibrateOffsets() { | |||
for (size_t i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
if (i != ADC_CHANNEL_T_RATE && i != ADC_CHANNEL_X_SPREAD) { | |||
calibration_data_->adc_offset[i] = \ | |||
2.0f * channel_[i].unscaled_cv_lp(); | |||
} | |||
} | |||
} | |||
inline uint8_t adc_value(int index) const { | |||
return adc_.value(index) >> 8; | |||
} | |||
void Copy(uint16_t* output); | |||
void Process(const uint16_t* values, float* output); | |||
inline const CvReaderChannel& channel(size_t index) { | |||
return channel_[index]; | |||
} | |||
inline CvReaderChannel* mutable_channel(size_t index) { | |||
return &channel_[index]; | |||
} | |||
inline void set_attenuverter(int index, float value) { | |||
attenuverter_[index] = value; | |||
} | |||
private: | |||
Adc adc_; | |||
CalibrationData* calibration_data_; | |||
float cv_c1_[2]; | |||
float cv_c3_[2]; | |||
CvReaderChannel channel_[ADC_CHANNEL_LAST]; | |||
float attenuverter_[ADC_CHANNEL_LAST]; | |||
static const CvReaderChannel::Settings channel_settings_[ADC_CHANNEL_LAST]; | |||
DISALLOW_COPY_AND_ASSIGN(CvReader); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_CV_READER_H_ |
@@ -0,0 +1,213 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// CV reader channel. | |||
#ifndef MARBLES_CV_READER_CHANNEL_H_ | |||
#define MARBLES_CV_READER_CHANNEL_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "stmlib/dsp/dsp.h" | |||
namespace marbles { | |||
enum PotState { | |||
POT_STATE_TRACKING, | |||
POT_STATE_LOCKED, | |||
POT_STATE_CATCHING_UP | |||
}; | |||
class HysteresisFilter { | |||
public: | |||
HysteresisFilter() { } | |||
~HysteresisFilter() { } | |||
void Init(float threshold) { | |||
value_ = 0.0f; | |||
threshold_ = threshold; | |||
} | |||
inline float Process(float value) { | |||
float error = value - value_; | |||
if (error > threshold_) { | |||
value_ = value - threshold_; | |||
} else if (error < -threshold_) { | |||
value_ = value + threshold_; | |||
} | |||
return value_; | |||
} | |||
private: | |||
float value_; | |||
float threshold_; | |||
DISALLOW_COPY_AND_ASSIGN(HysteresisFilter); | |||
}; | |||
class CvReaderChannel { | |||
public: | |||
CvReaderChannel() { } | |||
~CvReaderChannel() { } | |||
// Because of the large number of initialization parameters, they are | |||
// passed in one single struct. | |||
struct Settings { | |||
float cv_lp; | |||
float pot_scale; | |||
float pot_offset; | |||
float pot_lp; | |||
float min; | |||
float max; | |||
float hysteresis; | |||
}; | |||
void Init(float* cv_scale, float* cv_offset, const Settings& settings) { | |||
cv_scale_ = cv_scale; | |||
cv_offset_ = cv_offset; | |||
cv_lp_ = settings.cv_lp; | |||
pot_scale_ = settings.pot_scale + 2.0f * settings.hysteresis; | |||
pot_offset_ = settings.pot_offset - settings.hysteresis; | |||
pot_lp_ = settings.pot_lp; | |||
min_ = settings.min; | |||
max_ = settings.max; | |||
raw_cv_value_ = 0.0f; | |||
cv_value_ = 0.0f; | |||
pot_value_ = 0.0f; | |||
stored_pot_value_ = 0.0f; | |||
attenuverter_value_ = 0.0f; | |||
previous_pot_value_ = 0.0f; | |||
pot_state_ = POT_STATE_TRACKING; | |||
hystereis_filter_.Init(settings.hysteresis); | |||
} | |||
inline float Process(float pot, float cv) { | |||
return Process(pot, cv, 1.0f); | |||
} | |||
inline float Process(float pot, float cv, float attenuverter) { | |||
cv *= *cv_scale_; | |||
cv += *cv_offset_; | |||
attenuverter -= 0.5f; | |||
attenuverter = attenuverter * attenuverter * attenuverter * 8.0f; | |||
ONE_POLE(attenuverter_value_, attenuverter, pot_lp_); | |||
raw_cv_value_ = cv; | |||
ONE_POLE(cv_value_, cv, cv_lp_); | |||
ONE_POLE(pot_value_, pot, pot_lp_); | |||
switch (pot_state_) { | |||
case POT_STATE_TRACKING: | |||
stored_pot_value_ = pot_value_; | |||
previous_pot_value_ = pot_value_; | |||
break; | |||
case POT_STATE_LOCKED: | |||
break; | |||
case POT_STATE_CATCHING_UP: | |||
{ | |||
if (fabs(pot_value_ - previous_pot_value_) > 0.01f) { | |||
float delta = pot_value_ - previous_pot_value_; | |||
float skew_ratio = delta > 0.0f | |||
? (1.001f - stored_pot_value_) / (1.001f - previous_pot_value_) | |||
: (0.001f + stored_pot_value_) / (0.001f + previous_pot_value_); | |||
CONSTRAIN(skew_ratio, 0.1f, 10.0f); | |||
stored_pot_value_ += skew_ratio * delta; | |||
CONSTRAIN(stored_pot_value_, 0.0f, 1.0f); | |||
if (fabs(stored_pot_value_ - pot_value_) < 0.01f) { | |||
pot_state_ = POT_STATE_TRACKING; | |||
} | |||
previous_pot_value_ = pot_value_; | |||
} | |||
} | |||
break; | |||
}; | |||
float value = hystereis_filter_.Process( | |||
cv_value_ * attenuverter_value_ + this->pot()); | |||
CONSTRAIN(value, min_, max_); | |||
return value; | |||
} | |||
inline float cv() const { return cv_value_; } | |||
inline float scaled_raw_cv() const { return raw_cv_value_; } | |||
inline float unscaled_cv_lp() const { | |||
return (cv_value_ - *cv_offset_) / (*cv_scale_); | |||
} | |||
inline float pot() const { | |||
return stored_pot_value_ * pot_scale_ + pot_offset_; | |||
} | |||
inline float unscaled_pot() const { return pot_value_; } | |||
inline void LockPot() { | |||
pot_state_ = POT_STATE_LOCKED; | |||
} | |||
inline void UnlockPot() { | |||
if (pot_state_ == POT_STATE_LOCKED) { | |||
previous_pot_value_ = pot_value_; | |||
pot_state_ = POT_STATE_CATCHING_UP; | |||
} | |||
} | |||
private: | |||
float* cv_scale_; | |||
float* cv_offset_; | |||
float raw_cv_value_; | |||
float cv_lp_; | |||
float pot_scale_; | |||
float pot_offset_; | |||
float pot_lp_; | |||
float attenuverter_lp_; | |||
float min_; | |||
float max_; | |||
PotState pot_state_; | |||
float cv_value_; | |||
float pot_value_; // Value after low-pass filtering. | |||
float previous_pot_value_; | |||
float stored_pot_value_; // The actual parameter value. | |||
float attenuverter_value_; | |||
HysteresisFilter hystereis_filter_; | |||
DISALLOW_COPY_AND_ASSIGN(CvReaderChannel); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_CV_READER_CHANNEL_H_ |
@@ -0,0 +1,162 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for ADC. ADC1 is used for the 8 pots ; ADC2 for the 8 CV inputs. | |||
#include "marbles/drivers/adc.h" | |||
#include <stm32f4xx_conf.h> | |||
namespace marbles { | |||
void Adc::Init(bool single_channel) { | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); | |||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE); | |||
DMA_InitTypeDef dma_init; | |||
ADC_CommonInitTypeDef adc_common_init; | |||
ADC_InitTypeDef adc_init; | |||
GPIO_InitTypeDef gpio_init; | |||
// Initialize A0..A7 (ADC0..ADC7) | |||
gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; | |||
gpio_init.GPIO_Pin |= GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
// Initialize B0..B1 (ADC8..ADC9) | |||
gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
GPIO_Init(GPIOB, &gpio_init); | |||
// Initialize C0..C5 (ADC10..ADC11) | |||
gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; | |||
gpio_init.GPIO_Pin |= GPIO_Pin_4 | GPIO_Pin_5; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
GPIO_Init(GPIOC, &gpio_init); | |||
// Use DMA to automatically copy ADC data register to values_ buffer. | |||
dma_init.DMA_Channel = DMA_Channel_0; | |||
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; | |||
dma_init.DMA_Memory0BaseAddr = (uint32_t)&values_[ADC_GROUP_POT]; | |||
dma_init.DMA_DIR = DMA_DIR_PeripheralToMemory; | |||
dma_init.DMA_BufferSize = single_channel ? 1 : ADC_CHANNEL_LAST; | |||
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
dma_init.DMA_Mode = DMA_Mode_Circular; | |||
dma_init.DMA_Priority = DMA_Priority_High; | |||
dma_init.DMA_FIFOMode = DMA_FIFOMode_Disable; | |||
dma_init.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; | |||
dma_init.DMA_MemoryBurst = DMA_MemoryBurst_Single; | |||
dma_init.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; | |||
DMA_Init(DMA2_Stream0, &dma_init); | |||
DMA_Cmd(DMA2_Stream0, ENABLE); | |||
dma_init.DMA_Channel = DMA_Channel_1; | |||
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC2->DR; | |||
dma_init.DMA_Memory0BaseAddr = (uint32_t)&values_[ADC_GROUP_CV]; | |||
DMA_Init(DMA2_Stream2, &dma_init); | |||
DMA_Cmd(DMA2_Stream2, ENABLE); | |||
adc_common_init.ADC_Mode = ADC_Mode_Independent; | |||
adc_common_init.ADC_Prescaler = ADC_Prescaler_Div8; | |||
adc_common_init.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; | |||
adc_common_init.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles; | |||
ADC_CommonInit(&adc_common_init); | |||
adc_init.ADC_Resolution = ADC_Resolution_12b; | |||
adc_init.ADC_ScanConvMode = ENABLE; | |||
adc_init.ADC_ContinuousConvMode = DISABLE; | |||
adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; | |||
adc_init.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; | |||
adc_init.ADC_DataAlign = ADC_DataAlign_Left; | |||
adc_init.ADC_NbrOfConversion = single_channel ? 1 : ADC_CHANNEL_LAST; | |||
ADC_Init(ADC1, &adc_init); | |||
ADC_Init(ADC2, &adc_init); | |||
// 168M / 2 / 8 / (8 x (144 + 20)) = 8.001kHz. | |||
if (single_channel) { | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 1, ADC_SampleTime_144Cycles); | |||
} else { | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 1, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 2, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_12,3, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_2,4, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_15,5, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_10,6, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 7, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 8, ADC_SampleTime_144Cycles); | |||
} | |||
if (single_channel) { | |||
ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 1, ADC_SampleTime_144Cycles); | |||
} else { | |||
ADC_RegularChannelConfig(ADC2, ADC_Channel_5, 1, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC2, ADC_Channel_0, 2, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 3, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC2, ADC_Channel_1, 4, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC2, ADC_Channel_4, 5, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC2, ADC_Channel_7, 6, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC2, ADC_Channel_14,7, ADC_SampleTime_144Cycles); | |||
ADC_RegularChannelConfig(ADC2, ADC_Channel_6, 8, ADC_SampleTime_144Cycles); | |||
} | |||
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE); | |||
ADC_DMARequestAfterLastTransferCmd(ADC2, ENABLE); | |||
ADC_Cmd(ADC1, ENABLE); | |||
ADC_Cmd(ADC2, ENABLE); | |||
ADC_DMACmd(ADC1, ENABLE); | |||
ADC_DMACmd(ADC2, ENABLE); | |||
Convert(); | |||
} | |||
void Adc::DeInit() { | |||
DMA_Cmd(DMA2_Stream0, DISABLE); | |||
DMA_Cmd(DMA2_Stream2, DISABLE); | |||
ADC_DMARequestAfterLastTransferCmd(ADC1, DISABLE); | |||
ADC_DMARequestAfterLastTransferCmd(ADC2, DISABLE); | |||
ADC_Cmd(ADC1, DISABLE); | |||
ADC_Cmd(ADC2, DISABLE); | |||
ADC_DMACmd(ADC1, DISABLE); | |||
ADC_DMACmd(ADC2, DISABLE); | |||
ADC_DeInit(); | |||
} | |||
void Adc::Convert() { | |||
ADC_SoftwareStartConv(ADC1); | |||
ADC_SoftwareStartConv(ADC2); | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,81 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for ADC. ADC1 is used for the 8 pots ; ADC2 for the 8 CV inputs. | |||
#ifndef MARBLES_DRIVERS_ADC_H_ | |||
#define MARBLES_DRIVERS_ADC_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace marbles { | |||
enum AdcParameter { | |||
ADC_CHANNEL_DEJA_VU_AMOUNT, | |||
ADC_CHANNEL_DEJA_VU_LENGTH, | |||
ADC_CHANNEL_X_SPREAD_2 = ADC_CHANNEL_DEJA_VU_LENGTH, | |||
ADC_CHANNEL_T_RATE, | |||
ADC_CHANNEL_T_BIAS, | |||
ADC_CHANNEL_T_JITTER, | |||
ADC_CHANNEL_X_SPREAD, | |||
ADC_CHANNEL_X_BIAS, | |||
ADC_CHANNEL_X_STEPS, | |||
ADC_CHANNEL_LAST | |||
}; | |||
enum AdcGroup { | |||
ADC_GROUP_POT = 0, | |||
ADC_GROUP_CV = ADC_CHANNEL_LAST | |||
}; | |||
class Adc { | |||
public: | |||
Adc() { } | |||
~Adc() { } | |||
void Init(bool single_channel); | |||
void DeInit(); | |||
void Convert(); | |||
inline float float_value(int channel) const { | |||
return static_cast<float>(values_[channel]) / 65536.0f; | |||
} | |||
inline uint16_t value(int channel) const { | |||
return values_[channel]; | |||
} | |||
inline const uint16_t* values() const { | |||
return &values_[0]; | |||
} | |||
private: | |||
uint16_t values_[ADC_CHANNEL_LAST * 2]; | |||
DISALLOW_COPY_AND_ASSIGN(Adc); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_ADC_H_ |
@@ -0,0 +1,122 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the two clock inputs and their normalization probe. | |||
#include "marbles/drivers/clock_inputs.h" | |||
#include <stm32f4xx_conf.h> | |||
namespace marbles { | |||
using namespace std; | |||
using namespace stmlib; | |||
struct ClockInputDefinition { | |||
GPIO_TypeDef* gpio; | |||
uint16_t pin; | |||
}; | |||
const ClockInputDefinition clock_input_definition[] = { | |||
{ GPIOC, GPIO_Pin_9 }, // CLOCK_INPUT_T, | |||
{ GPIOA, GPIO_Pin_8 }, // CLOCK_INPUT_X, | |||
}; | |||
void ClockInputs::Init() { | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
// Initialize probe. | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = GPIO_Pin_8; | |||
GPIO_Init(GPIOC, &gpio_init); | |||
// Initialize inputs. | |||
gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
gpio_init.GPIO_Pin = clock_input_definition[i].pin; | |||
GPIO_Init(clock_input_definition[i].gpio, &gpio_init); | |||
previous_flags_[i] = 0; | |||
normalization_mismatch_count_[i] = 0; | |||
normalized_[i] = false; | |||
} | |||
normalization_probe_state_ = 0; | |||
normalization_decision_count_ = 0; | |||
} | |||
void ClockInputs::ReadNormalization(IOBuffer::Block* block) { | |||
++normalization_decision_count_; | |||
if (normalization_decision_count_ >= kProbeSequenceDuration) { | |||
normalization_decision_count_ = 0; | |||
for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
normalized_[i] = \ | |||
normalization_mismatch_count_[i] < kProbeSequenceDuration / 8; | |||
normalization_mismatch_count_[i] = 0; | |||
} | |||
} | |||
int expected_value = normalization_probe_state_ >> 31; | |||
for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
int read_value = previous_flags_[i] & GATE_FLAG_HIGH; | |||
normalization_mismatch_count_[i] += read_value ^ expected_value; | |||
block->input_patched[i] = !normalized_[i]; | |||
} | |||
normalization_probe_state_ = 1103515245 * normalization_probe_state_ + 12345; | |||
if (normalization_probe_state_ >> 31) { | |||
GPIOC->BSRRL = GPIO_Pin_8; | |||
} else { | |||
GPIOC->BSRRH = GPIO_Pin_8; | |||
} | |||
} | |||
void ClockInputs::Read(const IOBuffer::Slice& slice, size_t size) { | |||
for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
previous_flags_[i] = ExtractGateFlags( | |||
previous_flags_[i], | |||
!(clock_input_definition[i].gpio->IDR & clock_input_definition[i].pin)); | |||
slice.block->input[i][slice.frame_index] = previous_flags_[i]; | |||
} | |||
// Extend gate input data to the next samples. | |||
for (size_t j = 1; j < size; ++j) { | |||
for (int i = 0; i < CLOCK_INPUT_LAST; ++i) { | |||
slice.block->input[i][slice.frame_index + j] = \ | |||
previous_flags_[i] & GATE_FLAG_HIGH; | |||
} | |||
} | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,75 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the two clock inputs and their normalization probe. | |||
#ifndef MARBLES_DRIVERS_CLOCK_INPUTS_H_ | |||
#define MARBLES_DRIVERS_CLOCK_INPUTS_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/io_buffer.h" | |||
namespace marbles { | |||
enum ClockInput { | |||
CLOCK_INPUT_T, | |||
CLOCK_INPUT_X, | |||
CLOCK_INPUT_LAST | |||
}; | |||
class ClockInputs { | |||
public: | |||
ClockInputs() { } | |||
~ClockInputs() { } | |||
void Init(); | |||
void Read(const IOBuffer::Slice& slice, size_t size); | |||
void ReadNormalization(IOBuffer::Block* block); | |||
bool is_normalized(ClockInput input) { | |||
return normalized_[input]; | |||
} | |||
bool value(ClockInput input) { | |||
return previous_flags_[input] & stmlib::GATE_FLAG_HIGH; | |||
} | |||
private: | |||
static const int kProbeSequenceDuration = 64; | |||
stmlib::GateFlags previous_flags_[CLOCK_INPUT_LAST]; | |||
uint32_t normalization_probe_state_; | |||
bool normalized_[CLOCK_INPUT_LAST]; | |||
int normalization_mismatch_count_[CLOCK_INPUT_LAST]; | |||
int normalization_decision_count_; | |||
DISALLOW_COPY_AND_ASSIGN(ClockInputs); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_CLOCK_INPUTS_H_ |
@@ -0,0 +1,160 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for DAC. | |||
#include "marbles/drivers/dac.h" | |||
#include <algorithm> | |||
namespace marbles { | |||
/* static */ | |||
Dac* Dac::instance_; | |||
void Dac::Init(int sample_rate, size_t block_size) { | |||
instance_ = this; | |||
block_size_ = block_size; | |||
callback_ = NULL; | |||
InitializeGPIO(); | |||
InitializeAudioInterface(sample_rate); | |||
InitializeDMA(block_size); | |||
} | |||
void Dac::InitializeGPIO() { | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
// Initialize SS pin. | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_25MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = GPIO_Pin_15; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
// Initialize MOSI and SCK pins. | |||
gpio_init.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_12; | |||
GPIO_Init(GPIOC, &gpio_init); | |||
GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SPI3); | |||
GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SPI3); | |||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_SPI3); | |||
} | |||
void Dac::InitializeAudioInterface(int sample_rate) { | |||
RCC_I2SCLKConfig(RCC_I2S2CLKSource_PLLI2S); | |||
RCC_PLLI2SCmd(DISABLE); | |||
// Best results for multiples of 32kHz. | |||
RCC_PLLI2SConfig(258, 3); | |||
RCC_PLLI2SCmd(ENABLE); | |||
while (RCC_GetFlagStatus(RCC_FLAG_PLLI2SRDY) == RESET); | |||
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE); | |||
SPI_I2S_DeInit(SPI3); | |||
I2S_InitTypeDef i2s_init; | |||
i2s_init.I2S_Mode = I2S_Mode_MasterTx; | |||
i2s_init.I2S_Standard = I2S_Standard_PCMShort; | |||
i2s_init.I2S_DataFormat = I2S_DataFormat_32b; | |||
i2s_init.I2S_MCLKOutput = I2S_MCLKOutput_Disable; | |||
i2s_init.I2S_AudioFreq = sample_rate * kNumDacChannels >> 1; | |||
i2s_init.I2S_CPOL = I2S_CPOL_Low; | |||
I2S_Init(SPI3, &i2s_init); | |||
I2S_Cmd(SPI3, ENABLE); | |||
} | |||
void Dac::InitializeDMA(size_t block_size) { | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); | |||
DMA_Cmd(DMA1_Stream5, DISABLE); | |||
DMA_DeInit(DMA1_Stream5); | |||
DMA_InitTypeDef dma_init; | |||
dma_init.DMA_Channel = DMA_Channel_0; | |||
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&(SPI3->DR); | |||
dma_init.DMA_Memory0BaseAddr = (uint32_t)(&tx_dma_buffer_[0]); | |||
dma_init.DMA_DIR = DMA_DIR_MemoryToPeripheral; | |||
dma_init.DMA_BufferSize = 2 * block_size * kNumDacChannels * 2; | |||
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
dma_init.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord; | |||
dma_init.DMA_Mode = DMA_Mode_Circular; | |||
dma_init.DMA_Priority = DMA_Priority_High; | |||
dma_init.DMA_FIFOMode = DMA_FIFOMode_Disable; | |||
dma_init.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; | |||
dma_init.DMA_MemoryBurst = DMA_MemoryBurst_Single; | |||
dma_init.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; | |||
DMA_Init(DMA1_Stream5, &dma_init); | |||
// Enable the interrupts. | |||
DMA_ITConfig(DMA1_Stream5, DMA_IT_TC | DMA_IT_HT, ENABLE); | |||
// Enable the IRQ. | |||
NVIC_EnableIRQ(DMA1_Stream5_IRQn); | |||
// Start DMA from/to codec. | |||
SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE); | |||
} | |||
void Dac::Start(FillBufferCallback callback) { | |||
callback_ = callback; | |||
DMA_Cmd(DMA1_Stream5, ENABLE); | |||
} | |||
void Dac::Stop() { | |||
DMA_Cmd(DMA1_Stream5, DISABLE); | |||
} | |||
void Dac::Fill(size_t offset) { | |||
// Fill the buffer. | |||
IOBuffer::Slice slice = (*callback_)(block_size_); | |||
uint16_t* p = &tx_dma_buffer_[offset * block_size_ * kNumDacChannels * 2]; | |||
for (size_t i = 0; i < block_size_; ++i) { | |||
for (size_t j = 0; j < kNumDacChannels; ++j) { | |||
uint16_t sample = slice.block->cv_output[j][slice.frame_index + i]; | |||
*p++ = 0x1000 | (j << 9) | (sample >> 8); | |||
*p++ = sample << 8; | |||
} | |||
} | |||
} | |||
} // namespace marbles | |||
extern "C" { | |||
void DMA1_Stream5_IRQHandler(void) { | |||
uint32_t flags = DMA1->HISR; | |||
DMA1->HIFCR = DMA_FLAG_TCIF5 | DMA_FLAG_HTIF5; | |||
if (flags & DMA_FLAG_TCIF5) { | |||
marbles::Dac::GetInstance()->Fill(1); | |||
} else if (flags & DMA_FLAG_HTIF5) { | |||
marbles::Dac::GetInstance()->Fill(0); | |||
} | |||
} | |||
} |
@@ -0,0 +1,82 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for quad SPI DAC. | |||
#ifndef MARBLES_DRIVERS_DAC_H_ | |||
#define MARBLES_DRIVERS_DAC_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <stm32f4xx_conf.h> | |||
#include "marbles/io_buffer.h" | |||
namespace marbles { | |||
enum DacChannel { | |||
DAC_CHANNEL_X_1, | |||
DAC_CHANNEL_X_2, | |||
DAC_CHANNEL_X_3, | |||
DAC_CHANNEL_Y, | |||
DAC_CHANNEL_LAST | |||
}; | |||
const size_t kMaxDacBlockSize = 8; | |||
const size_t kNumDacChannels = 4; | |||
class Dac { | |||
public: | |||
Dac() { } | |||
~Dac() { } | |||
typedef IOBuffer::Slice (*FillBufferCallback)(size_t size); | |||
void Init(int sample_rate, size_t block_size); | |||
void Start(FillBufferCallback callback); | |||
void Stop(); | |||
void Fill(size_t offset); | |||
static Dac* GetInstance() { return instance_; } | |||
private: | |||
void InitializeGPIO(); | |||
void InitializeAudioInterface(int sample_rate); | |||
void InitializeDMA(size_t block_size); | |||
static Dac* instance_; | |||
size_t block_size_; | |||
FillBufferCallback callback_; | |||
// There are 8 16-bit words per frame. | |||
uint16_t tx_dma_buffer_[2 * kMaxDacBlockSize * kNumDacChannels * 2]; | |||
DISALLOW_COPY_AND_ASSIGN(Dac); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_DAC_H_ |
@@ -0,0 +1,76 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the debug (timing) pin. | |||
#ifndef MARBLES_DRIVERS_DEBUG_PIN_H_ | |||
#define MARBLES_DRIVERS_DEBUG_PIN_H_ | |||
#include "stmlib/stmlib.h" | |||
#ifndef TEST | |||
#include <stm32f4xx_conf.h> | |||
#endif | |||
namespace marbles { | |||
class DebugPin { | |||
public: | |||
DebugPin() { } | |||
~DebugPin() { } | |||
#ifdef TEST | |||
static void Init() { } | |||
static void High() { } | |||
static void Low() { } | |||
#else | |||
static void Init() { | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Pin = GPIO_Pin_9; | |||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
} | |||
static inline void High() { | |||
GPIOA->BSRRL = GPIO_Pin_9; | |||
} | |||
static inline void Low() { | |||
GPIOA->BSRRH = GPIO_Pin_9; | |||
} | |||
#endif | |||
private: | |||
DISALLOW_COPY_AND_ASSIGN(DebugPin); | |||
}; | |||
#define TIC DebugPin::High(); | |||
#define TOC DebugPin::Low(); | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_DEBUG_PIN_H_ |
@@ -0,0 +1,94 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// UART driver for conversing with the factory testing program. | |||
#ifndef MARBLES_DRIVERS_DEBUG_PORT_H_ | |||
#define MARBLES_DRIVERS_DEBUG_PORT_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <stm32f4xx_conf.h> | |||
namespace marbles { | |||
class DebugPort { | |||
public: | |||
DebugPort() { } | |||
~DebugPort() { } | |||
void Init() { | |||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
// Initialize TX and RX pins. | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); | |||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); | |||
// Initialize USART. | |||
USART_InitTypeDef usart_init; | |||
usart_init.USART_BaudRate = 9600; | |||
usart_init.USART_WordLength = USART_WordLength_8b; | |||
usart_init.USART_StopBits = USART_StopBits_1; | |||
usart_init.USART_Parity = USART_Parity_No; | |||
usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None; | |||
usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; | |||
USART_Init(USART1, &usart_init); | |||
USART_Cmd(USART1, ENABLE); | |||
} | |||
bool writable() { | |||
return USART1->SR & USART_FLAG_TXE; | |||
} | |||
bool readable() { | |||
return USART1->SR & USART_FLAG_RXNE; | |||
} | |||
void Write(uint8_t byte) { | |||
USART1->DR = byte; | |||
} | |||
uint8_t Read() { | |||
return USART1->DR; | |||
} | |||
private: | |||
DISALLOW_COPY_AND_ASSIGN(DebugPort); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_DEBUG_PORT_H_ |
@@ -0,0 +1,86 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the two gate outputs. | |||
#ifndef MARBLES_DRIVERS_GATE_OUTPUTS_H_ | |||
#define MARBLES_DRIVERS_GATE_OUTPUTS_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <stm32f4xx_conf.h> | |||
#include "marbles/io_buffer.h" | |||
namespace marbles { | |||
class GateOutputs { | |||
public: | |||
GateOutputs() { } | |||
~GateOutputs() { } | |||
void Init() { | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
gpio_init.GPIO_Pin = GPIO_Pin_11; | |||
GPIO_Init(GPIOC, &gpio_init); | |||
} | |||
inline void Write(IOBuffer::Slice s) { | |||
if (s.block->gate_output[0][s.frame_index]) { | |||
GPIOC->BSRRL = GPIO_Pin_11; | |||
} else { | |||
GPIOC->BSRRH = GPIO_Pin_11; | |||
} | |||
if (s.block->gate_output[1][s.frame_index]) { | |||
GPIOA->BSRRL = GPIO_Pin_11; | |||
} else { | |||
GPIOA->BSRRH = GPIO_Pin_11; | |||
} | |||
if (s.block->gate_output[2][s.frame_index]) { | |||
GPIOA->BSRRL = GPIO_Pin_12; | |||
} else { | |||
GPIOA->BSRRH = GPIO_Pin_12; | |||
} | |||
} | |||
private: | |||
DISALLOW_COPY_AND_ASSIGN(GateOutputs); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_GATE_OUTPUTS_H_ |
@@ -0,0 +1,86 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for all the LEDs. | |||
#include "marbles/drivers/leds.h" | |||
#include <algorithm> | |||
#include <stm32f4xx_conf.h> | |||
namespace marbles { | |||
using namespace std; | |||
struct LedDefinition { | |||
GPIO_TypeDef* gpio; | |||
uint16_t pin[3]; // pins for R, G, B - assumed to be on the same GPIO | |||
}; | |||
const LedDefinition led_definition[] = { | |||
{ GPIOB, { 0, GPIO_Pin_10, 0 } }, // LED_T_DEJA_VU | |||
{ GPIOB, { GPIO_Pin_13, GPIO_Pin_12, 0 } }, // LED_T_MODEL | |||
{ GPIOC, { GPIO_Pin_7, GPIO_Pin_6, 0 } }, // LED_T_RANGE | |||
{ GPIOB, { 0, GPIO_Pin_9, 0 } }, // LED_X_DEJA_VU | |||
{ GPIOB, { GPIO_Pin_8, GPIO_Pin_7, 0 } }, // LED_X_CONTROL_MODE, | |||
{ GPIOB, { GPIO_Pin_4, GPIO_Pin_5, 0 } }, // LED_X_RANGE | |||
{ GPIOB, { 0, GPIO_Pin_3, 0 } } // LED_X_EXT | |||
}; | |||
void Leds::Init() { | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
for (int i = 0; i < LED_LAST; ++i) { | |||
LedDefinition d = led_definition[i]; | |||
gpio_init.GPIO_Pin = d.pin[0] | d.pin[1] | d.pin[2]; | |||
GPIO_Init(d.gpio, &gpio_init); | |||
} | |||
Clear(); | |||
} | |||
void Leds::Clear() { | |||
fill(&colors_[0], &colors_[LED_LAST], LED_COLOR_OFF); | |||
} | |||
void Leds::Write() { | |||
for (int i = 0; i < LED_LAST; ++i) { | |||
LedDefinition d = led_definition[i]; | |||
GPIO_WriteBit(d.gpio, d.pin[0], static_cast<BitAction>( | |||
(colors_[i] & 0x800000) >> 23)); | |||
GPIO_WriteBit(d.gpio, d.pin[1], static_cast<BitAction>( | |||
(colors_[i] & 0x008000) >> 15)); | |||
} | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,75 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for all the LEDs. | |||
#ifndef MARBLES_DRIVERS_LEDS_H_ | |||
#define MARBLES_DRIVERS_LEDS_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace marbles { | |||
enum Led { | |||
LED_T_DEJA_VU, | |||
LED_T_MODEL, | |||
LED_T_RANGE, | |||
LED_X_DEJA_VU, | |||
LED_X_CONTROL_MODE, | |||
LED_X_RANGE, | |||
LED_X_EXT, | |||
LED_LAST | |||
}; | |||
enum LedColor { | |||
LED_COLOR_OFF = 0, | |||
LED_COLOR_RED = 0xff0000, | |||
LED_COLOR_GREEN = 0x00ff00, | |||
LED_COLOR_YELLOW = 0xffff00, | |||
}; | |||
class Leds { | |||
public: | |||
Leds() { } | |||
~Leds() { } | |||
void Init(); | |||
void Write(); | |||
void Clear(); | |||
void set(Led led, uint32_t color) { | |||
colors_[led] = color; | |||
} | |||
private: | |||
uint32_t colors_[LED_LAST]; | |||
DISALLOW_COPY_AND_ASSIGN(Leds); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_LEDS_H_ |
@@ -0,0 +1,62 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for built-in random number generator. | |||
#ifndef MARBLES_DRIVERS_RNG_H_ | |||
#define MARBLES_DRIVERS_RNG_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <stm32f4xx_conf.h> | |||
namespace marbles { | |||
class Rng { | |||
public: | |||
Rng() { } | |||
~Rng() { } | |||
void Init() { | |||
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_RNG, ENABLE); | |||
RNG_Cmd(ENABLE); | |||
} | |||
inline bool readable() { | |||
return RNG->SR & RNG_FLAG_DRDY; | |||
} | |||
inline uint32_t data() { | |||
return RNG->DR; | |||
} | |||
private: | |||
DISALLOW_COPY_AND_ASSIGN(Rng); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_RNG_H_ |
@@ -0,0 +1,79 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the front panel switch. | |||
#include "marbles/drivers/switches.h" | |||
#include <algorithm> | |||
namespace marbles { | |||
using namespace std; | |||
struct SwitchDefinition { | |||
GPIO_TypeDef* gpio; | |||
uint16_t pin; | |||
}; | |||
const SwitchDefinition switch_definitions[] = { | |||
{ GPIOB, GPIO_Pin_11 }, // SWITCH_T_DEJA_VU, | |||
{ GPIOB, GPIO_Pin_14 }, // SWITCH_T_MODE | |||
{ GPIOB, GPIO_Pin_15 }, // SWITCH_T_RANGE | |||
{ GPIOC, GPIO_Pin_15 }, // SWITCH_X_DEJA_VU, | |||
{ GPIOC, GPIO_Pin_13 }, // SWITCH_X_MODE, | |||
{ GPIOB, GPIO_Pin_6 }, // SWITCH_X_RANGE | |||
{ GPIOD, GPIO_Pin_2 } // SWITCH_X_EXT | |||
}; | |||
void Switches::Init() { | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); | |||
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_UP; | |||
for (int i = 0; i < SWITCH_LAST; ++i) { | |||
SwitchDefinition definition = switch_definitions[i]; | |||
gpio_init.GPIO_Pin = definition.pin; | |||
GPIO_Init(definition.gpio, &gpio_init); | |||
} | |||
fill(&switch_state_[0], &switch_state_[SWITCH_LAST], 0xff); | |||
} | |||
void Switches::Debounce() { | |||
for (int i = 0; i < SWITCH_LAST; ++i) { | |||
SwitchDefinition definition = switch_definitions[i]; | |||
switch_state_[i] = (switch_state_[i] << 1) | \ | |||
GPIO_ReadInputDataBit(definition.gpio, definition.pin); | |||
} | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,87 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the 6 front panel switches. | |||
#ifndef MARBLES_DRIVERS_SWITCHES_H_ | |||
#define MARBLES_DRIVERS_SWITCHES_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <stm32f4xx_conf.h> | |||
namespace marbles { | |||
enum Switch { | |||
SWITCH_T_DEJA_VU, | |||
SWITCH_T_MODEL, | |||
SWITCH_T_RANGE, | |||
SWITCH_X_DEJA_VU, | |||
SWITCH_X_MODE, | |||
SWITCH_X_RANGE, | |||
SWITCH_X_EXT, | |||
SWITCH_LAST | |||
}; | |||
class Switches { | |||
public: | |||
Switches() { } | |||
~Switches() { } | |||
void Init(); | |||
void Debounce(); | |||
inline bool released(Switch s) const { | |||
return switch_state_[s] == 0x7f; | |||
} | |||
inline bool just_pressed(Switch s) const { | |||
return switch_state_[s] == 0x80; | |||
} | |||
inline bool pressed(Switch s) const { | |||
return switch_state_[s] == 0x00; | |||
} | |||
inline bool pressed_immediate(Switch s) const { | |||
if (s == SWITCH_T_DEJA_VU) { | |||
return !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); | |||
} else if (s == SWITCH_X_MODE) { | |||
return !GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13); | |||
} else { | |||
return false; | |||
} | |||
} | |||
private: | |||
uint8_t switch_state_[SWITCH_LAST]; | |||
DISALLOW_COPY_AND_ASSIGN(Switches); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_SWITCHES_H_ |
@@ -0,0 +1,48 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// System level initialization. | |||
#include "marbles/drivers/system.h" | |||
#include <stm32f4xx_conf.h> | |||
namespace marbles { | |||
void System::Init(bool application) { | |||
if (application) { | |||
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x8000); | |||
} | |||
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); | |||
IWDG_SetPrescaler(IWDG_Prescaler_32); | |||
} | |||
void System::StartTimers() { | |||
SysTick_Config(F_CPU / 1000); | |||
IWDG_Enable(); | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,50 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// System-level initialization. | |||
#ifndef MARBLES_DRIVERS_SYSTEM_H_ | |||
#define MARBLES_DRIVERS_SYSTEM_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace marbles { | |||
class System { | |||
public: | |||
System() { } | |||
~System() { } | |||
void Init(bool application); | |||
void StartTimers(); | |||
private: | |||
DISALLOW_COPY_AND_ASSIGN(System); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_DRIVERS_SYSTEM_H_ |
@@ -0,0 +1,109 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// I/O Buffering. | |||
#ifndef MARBLES_IO_BUFFER_H_ | |||
#define MARBLES_IO_BUFFER_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "stmlib/utils/gate_flags.h" | |||
#include <algorithm> | |||
namespace marbles { | |||
const size_t kNumBlocks = 2; | |||
const size_t kBlockSize = 5; | |||
const size_t kNumInputs = 2; | |||
const size_t kNumCvOutputs = 4; | |||
const size_t kNumGateOutputs = 3; | |||
const size_t kNumParameters = 8; | |||
class IOBuffer { | |||
public: | |||
struct Block { | |||
uint16_t adc_value[kNumParameters * 2]; | |||
bool input_patched[kNumInputs]; | |||
stmlib::GateFlags input[kNumInputs][kBlockSize]; | |||
uint16_t cv_output[kNumCvOutputs][kBlockSize]; | |||
bool gate_output[kNumGateOutputs][kBlockSize + 2]; | |||
}; | |||
struct Slice { | |||
Block* block; | |||
size_t frame_index; | |||
}; | |||
typedef void ProcessFn(Block* block, size_t size); | |||
IOBuffer() { } | |||
~IOBuffer() { } | |||
void Init() { | |||
io_block_ = 0; | |||
render_block_ = kNumBlocks / 2; | |||
io_frame_ = 0; | |||
} | |||
inline void Process(ProcessFn* fn) { | |||
while (render_block_ != io_block_) { | |||
(*fn)(&block_[render_block_], kBlockSize); | |||
render_block_ = (render_block_ + 1) % kNumBlocks; | |||
} | |||
} | |||
inline Slice NextSlice(size_t size) { | |||
Slice s; | |||
s.block = &block_[io_block_]; | |||
s.frame_index = io_frame_; | |||
io_frame_ += size; | |||
if (io_frame_ >= kBlockSize) { | |||
io_frame_ -= kBlockSize; | |||
io_block_ = (io_block_ + 1) % kNumBlocks; | |||
} | |||
return s; | |||
} | |||
inline bool new_block() const { | |||
return io_frame_ == 0; | |||
} | |||
private: | |||
Block block_[kNumBlocks]; | |||
size_t io_frame_; | |||
volatile size_t io_block_; | |||
volatile size_t render_block_; | |||
DISALLOW_COPY_AND_ASSIGN(IOBuffer); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_IO_BUFFER_H_ |
@@ -0,0 +1,57 @@ | |||
# Copyright 2015 Olivier Gillet. | |||
# | |||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||
# | |||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||
# of this software and associated documentation files (the "Software"), to deal | |||
# in the Software without restriction, including without limitation the rights | |||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
# copies of the Software, and to permit persons to whom the Software is | |||
# furnished to do so, subject to the following conditions: | |||
# | |||
# The above copyright notice and this permission notice shall be included in | |||
# all copies or substantial portions of the Software. | |||
# | |||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
# THE SOFTWARE. | |||
# | |||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||
# System specifications | |||
F_CRYSTAL = 8000000L | |||
F_CPU = 168000000L | |||
SYSCLOCK = SYSCLK_FREQ_168MHz | |||
FAMILY = f4xx | |||
# USB = enabled | |||
APPLICATION_LARGE = TRUE | |||
BOOTLOADER = marbles_bootloader | |||
# Prefered upload command | |||
UPLOAD_COMMAND = upload_combo_jtag_erase_first | |||
# Packages to build | |||
TARGET = marbles | |||
PACKAGES = marbles \ | |||
marbles/drivers \ | |||
marbles/ramp \ | |||
marbles/random \ | |||
stmlib/dsp \ | |||
stmlib/utils \ | |||
stmlib/system | |||
RESOURCES = marbles/resources | |||
TOOLCHAIN_PATH ?= /usr/local/arm-4.8.3/ | |||
include stmlib/makefile.inc | |||
# Rule for building the firmware update file | |||
wav: $(TARGET_BIN) | |||
python stm_audio_bootloader/qpsk/encoder.py \ | |||
-t stm32f4 -s 48000 -b 12000 -c 6000 -p 256 \ | |||
$(TARGET_BIN) |
@@ -0,0 +1,447 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
#include <stm32f4xx_conf.h> | |||
#include "marbles/drivers/clock_inputs.h" | |||
#include "marbles/drivers/dac.h" | |||
#include "marbles/drivers/debug_pin.h" | |||
#include "marbles/drivers/debug_port.h" | |||
#include "marbles/drivers/gate_outputs.h" | |||
#include "marbles/drivers/rng.h" | |||
#include "marbles/drivers/system.h" | |||
#include "marbles/ramp/ramp_extractor.h" | |||
#include "marbles/random/random_generator.h" | |||
#include "marbles/random/random_stream.h" | |||
#include "marbles/random/t_generator.h" | |||
#include "marbles/random/x_y_generator.h" | |||
#include "marbles/clock_self_patching_detector.h" | |||
#include "marbles/cv_reader.h" | |||
#include "marbles/io_buffer.h" | |||
#include "marbles/note_filter.h" | |||
#include "marbles/resources.h" | |||
#include "marbles/scale_recorder.h" | |||
#include "marbles/settings.h" | |||
#include "marbles/ui.h" | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/dsp/hysteresis_quantizer.h" | |||
#include "stmlib/dsp/units.h" | |||
#define PROFILE_INTERRUPT 0 | |||
#define PROFILE_RENDER 0 | |||
using namespace marbles; | |||
using namespace std; | |||
using namespace stmlib; | |||
const bool test_adc_noise = false; | |||
const int kSampleRate = 32000; | |||
const int kGateDelay = 2; | |||
ClockInputs clock_inputs; | |||
ClockSelfPatchingDetector self_patching_detector[kNumGateOutputs]; | |||
CvReader cv_reader; | |||
Dac dac; | |||
DebugPort debug_port; | |||
GateOutputs gate_outputs; | |||
HysteresisQuantizer deja_vu_length_quantizer; | |||
IOBuffer io_buffer; | |||
NoteFilter note_filter; | |||
Rng rng; | |||
ScaleRecorder scale_recorder; | |||
Settings settings; | |||
Ui ui; | |||
RandomGenerator random_generator; | |||
RandomStream random_stream; | |||
TGenerator t_generator; | |||
XYGenerator xy_generator; | |||
// Default interrupt handlers. | |||
extern "C" { | |||
int __errno; | |||
void NMI_Handler() { } | |||
void HardFault_Handler() { while (1); } | |||
void MemManage_Handler() { while (1); } | |||
void BusFault_Handler() { while (1); } | |||
void UsageFault_Handler() { while (1); } | |||
void SVC_Handler() { } | |||
void DebugMon_Handler() { } | |||
void PendSV_Handler() { } | |||
void SysTick_Handler() { | |||
IWDG_ReloadCounter(); | |||
ui.Poll(); | |||
if (settings.freshly_baked()) { | |||
if (debug_port.readable()) { | |||
uint8_t command = debug_port.Read(); | |||
uint8_t response = ui.HandleFactoryTestingRequest(command); | |||
debug_port.Write(response); | |||
} | |||
} | |||
} | |||
} | |||
IOBuffer::Slice FillBuffer(size_t size) { | |||
if (PROFILE_INTERRUPT) { | |||
TIC; | |||
} | |||
IOBuffer::Slice s = io_buffer.NextSlice(size); | |||
gate_outputs.Write(s); | |||
clock_inputs.Read(s, size); | |||
if (io_buffer.new_block()) { | |||
cv_reader.Copy(&s.block->adc_value[0]); | |||
clock_inputs.ReadNormalization(s.block); | |||
} | |||
if (rng.readable()) { | |||
random_stream.Write(rng.data()); | |||
} | |||
if (PROFILE_INTERRUPT) { | |||
TOC; | |||
} | |||
return s; | |||
} | |||
inline uint16_t DacCode(int index, float voltage) { | |||
CONSTRAIN(voltage, -5.0f, 5.0f); | |||
const float scale = settings.calibration_data().dac_scale[index]; | |||
const float offset = settings.calibration_data().dac_offset[index]; | |||
return ClipU16(static_cast<int32_t>(voltage * scale + offset)); | |||
} | |||
void ProcessTest(IOBuffer::Block* block, size_t size) { | |||
float parameters[kNumParameters]; | |||
static float phase; | |||
cv_reader.Process(&block->adc_value[0], parameters); | |||
for (size_t i = 0; i < size; ++i) { | |||
phase += 100.0f / static_cast<float>(kSampleRate); | |||
if (phase >= 1.0f) { | |||
phase -= 1.0f; | |||
} | |||
block->cv_output[0][i] = DacCode( | |||
0, 4.0 * Interpolate(lut_sine, phase, 256.0f)); | |||
block->cv_output[1][i] = DacCode( | |||
1, -8.0f * phase + 4.0f); | |||
block->cv_output[2][i] = DacCode( | |||
2, (phase < 0.5f ? phase : 1.0f - phase) * 16.0f - 4.0f); | |||
block->cv_output[3][i] = DacCode( | |||
3, phase < 0.5f ? -4.0f : 4.0f); | |||
for (int j = 0; j < 4; ++j) { | |||
uint16_t dac_code = ui.output_test_forced_dac_code(j); | |||
if (dac_code) { | |||
block->cv_output[j][i] = dac_code; | |||
} | |||
} | |||
block->gate_output[0][i] = block->input_patched[0] | |||
? block->input[0][i] | |||
: phase < 0.2f; | |||
block->gate_output[1][i] = phase < 0.5f; | |||
block->gate_output[2][i] = block->input_patched[1] | |||
? block->input[1][i] | |||
: phase < 0.8f; | |||
} | |||
} | |||
Ratio y_divider_ratios[] = { | |||
{ 1, 64 }, | |||
{ 1, 48 }, | |||
{ 1, 32 }, | |||
{ 1, 24 }, | |||
{ 1, 16 }, | |||
{ 1, 12 }, | |||
{ 1, 8 }, | |||
{ 1, 6 }, | |||
{ 1, 4 }, | |||
{ 1, 3 }, | |||
{ 1, 2 }, | |||
{ 1, 1 }, | |||
}; | |||
int loop_length[] = { | |||
1, | |||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, | |||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, | |||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, | |||
5, 5, 5, 5, | |||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, | |||
7, 7, | |||
8, 8, 8, 8, 8, 8, 8, 8, 8, | |||
10, 10, 10, | |||
12, 12, 12, 12, 12, 12, 12, | |||
14, | |||
16 | |||
}; | |||
float parameters[kNumParameters]; | |||
float ramp_buffer[kBlockSize * 4]; | |||
bool gates[kBlockSize * 2]; | |||
float voltages[kBlockSize * 4]; | |||
Ramps ramps; | |||
GroupSettings x, y; | |||
bool gate_delay_tail[kNumGateOutputs][kGateDelay]; | |||
float SineOscillator(float voltage) { | |||
static float phase = 0.0f; | |||
CONSTRAIN(voltage, -5.0f, 5.0f); | |||
float frequency = stmlib::SemitonesToRatio(voltage * 12.0f) * 220.0f / kSampleRate; | |||
phase += frequency; | |||
if (phase >= 1.0f) { | |||
phase -= 1.0f; | |||
} | |||
return 5.0f * Interpolate(lut_sine, phase, 256.0f); | |||
} | |||
void Process(IOBuffer::Block* block, size_t size) { | |||
if (PROFILE_RENDER) { | |||
TIC; | |||
} | |||
// Filter CV values (3.5%) | |||
cv_reader.Process(&block->adc_value[0], parameters); | |||
float deja_vu = parameters[ADC_CHANNEL_DEJA_VU_AMOUNT]; | |||
// Deadband near 12 o'clock for the deja vu parameter. | |||
const float d = fabsf(deja_vu - 0.5f); | |||
if (d > 0.03f) { | |||
ui.set_deja_vu_lock(false); | |||
} else if (d < 0.02f) { | |||
ui.set_deja_vu_lock(true); | |||
} | |||
if (deja_vu < 0.47f) { | |||
deja_vu *= 1.06382978723f; | |||
} else if (deja_vu > 0.53f) { | |||
deja_vu = 0.5f + (deja_vu - 0.53f) * 1.06382978723f; | |||
} else { | |||
deja_vu = 0.5f; | |||
} | |||
GateFlags* t_clock = block->input[0]; | |||
GateFlags* xy_clock = block->input[1]; | |||
// Determine the clock source for the XY section (2%) | |||
ClockSource xy_clock_source = CLOCK_SOURCE_INTERNAL_T1_T2_T3; | |||
if (block->input_patched[1]) { | |||
xy_clock_source = CLOCK_SOURCE_EXTERNAL; | |||
size_t best_score = 8; | |||
for (size_t i = 0; i < kNumGateOutputs; ++i) { | |||
size_t score = self_patching_detector[i].Process(block, size); | |||
if (score >= best_score) { | |||
xy_clock_source = ClockSource(CLOCK_SOURCE_INTERNAL_T1 + i); | |||
best_score = score; | |||
} | |||
} | |||
} | |||
// Generate gates for T-section (16%). | |||
ramps.master = &ramp_buffer[0]; | |||
ramps.external = &ramp_buffer[kBlockSize]; | |||
ramps.slave[0] = &ramp_buffer[kBlockSize * 2]; | |||
ramps.slave[1] = &ramp_buffer[kBlockSize * 3]; | |||
const State& state = settings.state(); | |||
int deja_vu_length = deja_vu_length_quantizer.Lookup( | |||
loop_length, | |||
parameters[ADC_CHANNEL_DEJA_VU_LENGTH], | |||
sizeof(loop_length) / sizeof(int)); | |||
t_generator.set_model(TGeneratorModel(state.t_model)); | |||
t_generator.set_range(TGeneratorRange(state.t_range)); | |||
t_generator.set_rate(parameters[ADC_CHANNEL_T_RATE]); | |||
t_generator.set_bias(parameters[ADC_CHANNEL_T_BIAS]); | |||
t_generator.set_jitter(parameters[ADC_CHANNEL_T_JITTER]); | |||
t_generator.set_deja_vu(state.t_deja_vu ? deja_vu : 0.0f); | |||
t_generator.set_length(deja_vu_length); | |||
t_generator.set_pulse_width_mean(float(state.t_pulse_width_mean) / 256.0f); | |||
t_generator.set_pulse_width_std(float(state.t_pulse_width_std) / 256.0f); | |||
t_generator.Process( | |||
block->input_patched[0], | |||
t_clock, | |||
ramps, | |||
gates, | |||
size); | |||
// Generate voltages for X-section (40%). | |||
float note_cv_1 = cv_reader.channel(ADC_CHANNEL_X_SPREAD).scaled_raw_cv(); | |||
float note_cv_2 = cv_reader.channel(ADC_CHANNEL_X_SPREAD_2).scaled_raw_cv(); | |||
float note_cv = 0.5f * (note_cv_1 + note_cv_2); | |||
float u = note_filter.Process(0.5f * (note_cv + 1.0f)); | |||
if (test_adc_noise) { | |||
static float note_lp = 0.0f; | |||
float note = note_cv_1; | |||
ONE_POLE(note_lp, note, 0.0001f); | |||
float cents = (note - note_lp) * 1200.0f * 5.0f; | |||
fill(&voltages[0], &voltages[4 * size], cents); | |||
} else if (ui.recording_scale()) { | |||
float voltage = (u - 0.5f) * 10.0f; | |||
for (size_t i = 0; i < size; ++i) { | |||
GateFlags gate = block->input_patched[1] | |||
? block->input[1][i] | |||
: GATE_FLAG_LOW; | |||
if (gate & GATE_FLAG_RISING) { | |||
scale_recorder.NewNote(voltage); | |||
} | |||
if (gate & GATE_FLAG_HIGH) { | |||
scale_recorder.UpdateVoltage(voltage); | |||
} | |||
if (gate & GATE_FLAG_FALLING) { | |||
scale_recorder.AcceptNote(); | |||
} | |||
} | |||
fill(&voltages[0], &voltages[4 * size], voltage); | |||
} else { | |||
x.control_mode = ControlMode(state.x_control_mode); | |||
x.voltage_range = VoltageRange(state.x_range % 3); | |||
x.register_mode = state.x_register_mode; | |||
x.register_value = u; | |||
cv_reader.set_attenuverter( | |||
ADC_CHANNEL_X_SPREAD, state.x_register_mode ? 0.5f : 1.0f); | |||
x.spread = parameters[ADC_CHANNEL_X_SPREAD]; | |||
x.bias = parameters[ADC_CHANNEL_X_BIAS]; | |||
x.steps = parameters[ADC_CHANNEL_X_STEPS]; | |||
x.deja_vu = state.x_deja_vu ? deja_vu : 0.0f; | |||
x.length = deja_vu_length; | |||
x.ratio.p = 1; | |||
x.ratio.q = 1; | |||
y.control_mode = CONTROL_MODE_IDENTICAL; | |||
y.voltage_range = VoltageRange(state.y_range); | |||
y.register_mode = false; | |||
y.register_value = 0.0f; | |||
y.spread = float(state.y_spread) / 256.0f; | |||
y.bias = float(state.y_bias) / 256.0f; | |||
y.steps = float(state.y_steps) / 256.0f; | |||
y.deja_vu = 0.0f; | |||
y.length = 1; | |||
y.ratio = y_divider_ratios[ | |||
static_cast<uint16_t>(state.y_divider) * 12 >> 8]; | |||
if (settings.dirty_scale_index() != -1) { | |||
int i = settings.dirty_scale_index(); | |||
xy_generator.LoadScale(i, settings.persistent_data().scale[i]); | |||
settings.set_dirty_scale_index(-1); | |||
} | |||
y.scale_index = x.scale_index = state.x_scale; | |||
xy_generator.Process( | |||
xy_clock_source, | |||
x, | |||
y, | |||
xy_clock, | |||
ramps, | |||
voltages, | |||
size); | |||
} | |||
const float* v = voltages; | |||
const bool* g = gates; | |||
for (size_t i = 0; i < size; ++i) { | |||
block->cv_output[1][i] = DacCode(1, *v++); | |||
block->cv_output[2][i] = DacCode(2, *v++); | |||
block->cv_output[3][i] = DacCode(3, *v++); | |||
block->cv_output[0][i] = DacCode(0, *v++); | |||
block->gate_output[0][i + kGateDelay] = *g++; | |||
block->gate_output[1][i + kGateDelay] = ramps.master[i] < 0.5f; | |||
block->gate_output[2][i + kGateDelay] = *g++; | |||
} | |||
for (size_t i = 0; i < kNumGateOutputs; ++i) { | |||
for (size_t j = 0; j < kGateDelay; ++j) { | |||
block->gate_output[i][j] = gate_delay_tail[i][j]; | |||
gate_delay_tail[i][j] = block->gate_output[i][size + j]; | |||
} | |||
} | |||
if (PROFILE_RENDER) { | |||
TOC; | |||
} | |||
} | |||
void Init() { | |||
System sys; | |||
sys.Init(true); | |||
settings.Init(); | |||
clock_inputs.Init(); | |||
dac.Init(kSampleRate, 1); | |||
rng.Init(); | |||
note_filter.Init(); | |||
gate_outputs.Init(); | |||
io_buffer.Init(); | |||
deja_vu_length_quantizer.Init(); | |||
cv_reader.Init(settings.mutable_calibration_data()); | |||
scale_recorder.Init(); | |||
ui.Init(&settings, &cv_reader, &scale_recorder, &clock_inputs); | |||
if (settings.freshly_baked()) { | |||
settings.ProgramOptionBytes(); | |||
if (PROFILE_INTERRUPT || PROFILE_RENDER) { | |||
DebugPin::Init(); | |||
} else { | |||
debug_port.Init(); | |||
} | |||
} | |||
random_generator.Init(1); | |||
random_stream.Init(&random_generator); | |||
t_generator.Init(&random_stream, static_cast<float>(kSampleRate)); | |||
xy_generator.Init(&random_stream, static_cast<float>(kSampleRate)); | |||
for (size_t i = 0; i < kNumScales; ++i) { | |||
xy_generator.LoadScale(i, settings.persistent_data().scale[i]); | |||
} | |||
for (size_t i = 0; i < kNumGateOutputs; ++i) { | |||
self_patching_detector[i].Init(i); | |||
} | |||
sys.StartTimers(); | |||
dac.Start(&FillBuffer); | |||
} | |||
int main(void) { | |||
Init(); | |||
while (1) { | |||
ui.DoEvents(); | |||
io_buffer.Process(ui.output_test_mode() ? &ProcessTest : &Process); | |||
} | |||
} |
@@ -0,0 +1,82 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Low pass filter for getting stable pitch data. | |||
#ifndef MARBLES_NOTE_FILTER_H_ | |||
#define MARBLES_NOTE_FILTER_H_ | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/dsp/delay_line.h" | |||
namespace marbles { | |||
// Note: this is a "slow" filter, since it takes about 10 samples to catch | |||
// up with an edge in the input signal. | |||
class NoteFilter { | |||
public: | |||
enum { | |||
N = 7 // Median filter order | |||
}; | |||
NoteFilter() { } | |||
~NoteFilter() { } | |||
void Init() { | |||
lp_1_ = 0.0f; | |||
lp_2_ = 0.0f; | |||
std::fill(&previous_values_[0], &previous_values_[N], 0.0f); | |||
} | |||
inline float Process(float value) { | |||
float sorted_values[N]; | |||
std::rotate( | |||
&previous_values_[0], | |||
&previous_values_[1], | |||
&previous_values_[N]); | |||
previous_values_[N - 1] = value; | |||
std::copy(&previous_values_[0], &previous_values_[N], &sorted_values[0]); | |||
std::sort(&sorted_values[0], &sorted_values[N]); | |||
value = sorted_values[(N - 1) / 2]; | |||
const float kLPCoefficient = 0.65f; | |||
ONE_POLE(lp_1_, value, kLPCoefficient); | |||
ONE_POLE(lp_2_, lp_1_, kLPCoefficient); | |||
return lp_2_; | |||
} | |||
private: | |||
float previous_values_[N]; | |||
float lp_1_; | |||
float lp_2_; | |||
DISALLOW_COPY_AND_ASSIGN(NoteFilter); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_NOTE_FILTER_H_ |
@@ -0,0 +1,40 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Timing information is represented as a ramp from 0.0 to kMaxRampValue | |||
#ifndef MARBLES_RAMP_RAMP_H_ | |||
#define MARBLES_RAMP_RAMP_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace marbles { | |||
const float kMaxRampValue = 0.9999f; | |||
} // namespace marbles | |||
#endif // MARBLES_RAMP_RAMP_H_ |
@@ -0,0 +1,107 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Generates a ramp whose frequency is p/q times the frequency of the input. | |||
// Phase is synchronized. | |||
#ifndef MARBLES_RAMP_RAMP_DIVIDER_H_ | |||
#define MARBLES_RAMP_RAMP_DIVIDER_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <algorithm> | |||
#include "marbles/ramp/ramp.h" | |||
namespace marbles { | |||
struct Ratio { | |||
float to_float() { return static_cast<float>(p) / static_cast<float>(q); } | |||
int p; | |||
int q; | |||
template<int n> void Simplify() { | |||
while ((p % n) == 0 && (q % n) == 0) { | |||
p /= n; | |||
q /= n; | |||
} | |||
} | |||
}; | |||
class RampDivider { | |||
public: | |||
RampDivider() { } | |||
~RampDivider() { } | |||
void Init() { | |||
phase_ = 0.0f; | |||
train_phase_ = 0.0f; | |||
max_train_phase_ = 1.0f; | |||
f_ratio_ = 0.99999f; | |||
reset_counter_ = 1; | |||
} | |||
void Process(Ratio ratio, const float* in, float* out, size_t size) { | |||
while (size--) { | |||
float new_phase = *in++; | |||
float frequency = new_phase - phase_; | |||
if (frequency < 0.0f) { | |||
frequency += 1.0f; | |||
--reset_counter_; | |||
if (!reset_counter_) { | |||
train_phase_ = new_phase; | |||
reset_counter_ = ratio.q; | |||
f_ratio_ = ratio.to_float() * kMaxRampValue; | |||
frequency = 0.0f; | |||
max_train_phase_ = static_cast<float>(ratio.q); | |||
} | |||
} | |||
train_phase_ += frequency; | |||
if (train_phase_ >= max_train_phase_) { | |||
train_phase_ = max_train_phase_; | |||
} | |||
float output_phase = train_phase_ * f_ratio_; | |||
output_phase -= static_cast<float>(static_cast<int>(output_phase)); | |||
*out++ = output_phase; | |||
phase_ = new_phase; | |||
} | |||
} | |||
private: | |||
float phase_; | |||
float train_phase_; | |||
float max_train_phase_; | |||
float f_ratio_; | |||
int reset_counter_; | |||
DISALLOW_COPY_AND_ASSIGN(RampDivider); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RAMP_RAMP_DIVIDER_H_ |
@@ -0,0 +1,312 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Recovers a ramp from a clock input by guessing at what time the next edge | |||
// will occur. Prediction strategies: | |||
// - Moving average of previous intervals. | |||
// - Trigram model on quantized intervals. | |||
// - Periodic rhythmic pattern. | |||
// - Assume that the pulse width is constant, deduct the period from the on time | |||
// and the pulse width. | |||
#include "marbles/ramp/ramp_extractor.h" | |||
#include <algorithm> | |||
#include "marbles/ramp/ramp.h" | |||
#include "stmlib/dsp/dsp.h" | |||
namespace marbles { | |||
using namespace std; | |||
using namespace stmlib; | |||
const float kLogOneFourth = 1.189207115f; | |||
const float kPulseWidthTolerance = 0.05f; | |||
inline bool IsWithinTolerance(float x, float y, float error) { | |||
return x >= y * (1.0f - error) && x <= y * (1.0f + error); | |||
} | |||
void RampExtractor::Init(float max_frequency) { | |||
max_frequency_ = max_frequency; | |||
audio_rate_period_ = 1.0f / (100.0f / 32000.0f); | |||
audio_rate_period_hysteresis_ = audio_rate_period_; | |||
Reset(); | |||
} | |||
void RampExtractor::Reset() { | |||
audio_rate_ = false; | |||
train_phase_ = 0.0f; | |||
target_frequency_ = frequency_ = 0.0001f; | |||
lp_coefficient_ = 0.5f; | |||
next_max_train_phase_ = max_train_phase_ = 0.999f; | |||
next_f_ratio_ = f_ratio_ = 1.0f; | |||
reset_counter_ = 1; | |||
reset_frequency_ = 0.0f; | |||
reset_interval_ = 32000 * 3; | |||
Pulse p; | |||
p.bucket = 1; | |||
p.on_duration = 2000; | |||
p.total_duration = 4000; | |||
p.pulse_width = 0.5f; | |||
fill(&history_[0], &history_[kHistorySize], p); | |||
current_pulse_ = 0; | |||
next_bucket_ = 48.0f; | |||
average_pulse_width_ = 0.0f; | |||
fill(&predicted_period_[0], &predicted_period_[PREDICTOR_LAST], 4000.0f); | |||
fill(&prediction_accuracy_[0], &prediction_accuracy_[PREDICTOR_LAST], 0.0f); | |||
fill( | |||
&prediction_hash_table_[0], | |||
&prediction_hash_table_[kHashTableSize], | |||
0.0f); | |||
} | |||
float RampExtractor::ComputeAveragePulseWidth(float tolerance) const { | |||
float sum = 0.0f; | |||
for (size_t i = 0; i < kHistorySize; ++i) { | |||
if (!IsWithinTolerance(history_[i].pulse_width, | |||
history_[current_pulse_].pulse_width, | |||
tolerance)) { | |||
return 0.0f; | |||
} | |||
sum += history_[i].pulse_width; | |||
} | |||
return sum / static_cast<float>(kHistorySize); | |||
} | |||
RampExtractor::Prediction RampExtractor::PredictNextPeriod() { | |||
float last_period = static_cast<float>(history_[current_pulse_].total_duration); | |||
Predictor best_predictor = PREDICTOR_FAST_MOVING_AVERAGE; | |||
for (int i = PREDICTOR_FAST_MOVING_AVERAGE; i < PREDICTOR_LAST; ++i) { | |||
float error = (predicted_period_[i] - last_period) / (last_period + 0.01f); | |||
// Scoring function: 10% error is half as good as 0% error. | |||
float accuracy = 1.0f / (1.0f + 100.0f * error * error); | |||
// Slowly trust good predictors, quickly demote predictors who make errors. | |||
SLOPE(prediction_accuracy_[i], accuracy, 0.1f, 0.5f); | |||
// (Ugly code but I don't want virtuals for these.) | |||
switch (i) { | |||
case PREDICTOR_SLOW_MOVING_AVERAGE: | |||
ONE_POLE(predicted_period_[i], last_period, 0.1f); | |||
break; | |||
case PREDICTOR_FAST_MOVING_AVERAGE: | |||
ONE_POLE(predicted_period_[i], last_period, 0.5f); | |||
break; | |||
case PREDICTOR_HASH: | |||
{ | |||
size_t t_2 = (current_pulse_ - 2 + kHistorySize) % kHistorySize; | |||
size_t t_1 = (current_pulse_ - 1 + kHistorySize) % kHistorySize; | |||
size_t t_0 = current_pulse_; | |||
size_t hash = history_[t_1].bucket + 17 * history_[t_2].bucket; | |||
ONE_POLE( | |||
prediction_hash_table_[hash % kHashTableSize], | |||
last_period, | |||
0.5f); | |||
hash = history_[t_0].bucket + 17 * history_[t_1].bucket; | |||
predicted_period_[i] = prediction_hash_table_[hash % kHashTableSize]; | |||
if (predicted_period_[i] == 0.0f) { | |||
predicted_period_[i] = last_period; | |||
} | |||
} | |||
break; | |||
default: | |||
{ | |||
// Periodicity detector. | |||
size_t candidate_period = i - PREDICTOR_PERIOD_1 + 1; | |||
size_t t = current_pulse_ + 1 + kHistorySize - candidate_period; | |||
predicted_period_[i] = history_[t % kHistorySize].total_duration; | |||
} | |||
break; | |||
} | |||
if (prediction_accuracy_[i] >= prediction_accuracy_[best_predictor]) { | |||
best_predictor = Predictor(i); | |||
} | |||
} | |||
Prediction p; | |||
p.period = predicted_period_[best_predictor]; | |||
p.accuracy = prediction_accuracy_[best_predictor]; | |||
return p; | |||
} | |||
void RampExtractor::Process( | |||
Ratio ratio, | |||
bool always_ramp_to_maximum, | |||
const GateFlags* gate_flags, | |||
float* ramp, | |||
size_t size) { | |||
while (size--) { | |||
GateFlags flags = *gate_flags++; | |||
// We are done with the previous pulse. | |||
if (flags & GATE_FLAG_RISING) { | |||
Pulse& p = history_[current_pulse_]; | |||
const bool record_pulse = p.total_duration < reset_interval_; | |||
if (!record_pulse) { | |||
// Quite a long pause - the clock has probably been stopped | |||
// and restarted. | |||
reset_frequency_ = 0.0f; | |||
train_phase_ = 0.0f; | |||
reset_counter_ = ratio.q; | |||
reset_interval_ = 4 * p.total_duration; | |||
} else { | |||
float period = float(p.total_duration); | |||
if (period <= audio_rate_period_hysteresis_) { | |||
audio_rate_ = true; | |||
audio_rate_period_hysteresis_ = audio_rate_period_ * 1.1f; | |||
average_pulse_width_ = 0.0f; | |||
bool no_glide = f_ratio_ != ratio.to_float(); | |||
f_ratio_ = ratio.to_float(); | |||
float frequency = 1.0f / period; | |||
target_frequency_ = std::min(f_ratio_ * frequency, max_frequency_); | |||
float up_tolerance = (1.02f + 2.0f * frequency) * frequency_; | |||
float down_tolerance = (0.98f - 2.0f * frequency) * frequency_; | |||
no_glide |= target_frequency_ > up_tolerance || | |||
target_frequency_ < down_tolerance; | |||
lp_coefficient_ = no_glide ? 1.0f : period * 0.00001f; | |||
} else { | |||
audio_rate_ = false; | |||
audio_rate_period_hysteresis_ = audio_rate_period_; | |||
// Compute the pulse width of the previous pulse, and check if the | |||
// PW has been consistent over the past pulses. | |||
p.pulse_width = static_cast<float>(p.on_duration) / period; | |||
average_pulse_width_ = ComputeAveragePulseWidth(kPulseWidthTolerance); | |||
if (p.on_duration < 32) { | |||
average_pulse_width_ = 0.0f; | |||
} | |||
// Try to predict the next interval between pulses. If the prediction | |||
// has been reliable over the past pulses, or if the PW is steady, | |||
// we'll be able to make reliable prediction about the time at which | |||
// the next pulse will occur | |||
Prediction prediction = PredictNextPeriod(); | |||
frequency_ = 1.0f / prediction.period; | |||
--reset_counter_; | |||
if (!reset_counter_) { | |||
next_f_ratio_ = ratio.to_float() * kMaxRampValue; | |||
next_max_train_phase_ = static_cast<float>(ratio.q); | |||
if (always_ramp_to_maximum && train_phase_ < max_train_phase_) { | |||
reset_frequency_ = \ | |||
(0.01f + max_train_phase_ - train_phase_) * 0.0625f; | |||
} else { | |||
reset_frequency_ = 0.0f; | |||
train_phase_ = 0.0f; | |||
f_ratio_ = next_f_ratio_; | |||
max_train_phase_ = next_max_train_phase_; | |||
} | |||
reset_counter_ = ratio.q; | |||
} else { | |||
float expected = max_train_phase_ - static_cast<float>(reset_counter_); | |||
float warp = expected - train_phase_ + 1.0f; | |||
frequency_ *= max(warp, 0.01f); | |||
} | |||
} | |||
reset_interval_ = static_cast<uint32_t>( | |||
std::max(4.0f / target_frequency_, 32000 * 3.0f)); | |||
current_pulse_ = (current_pulse_ + 1) % kHistorySize; | |||
} | |||
history_[current_pulse_].on_duration = 0; | |||
history_[current_pulse_].total_duration = 0; | |||
history_[current_pulse_].bucket = 0; | |||
next_bucket_ = 48.0f; | |||
} | |||
// Update history buffer with total duration and on duration. | |||
++history_[current_pulse_].total_duration; | |||
if (flags & GATE_FLAG_HIGH) { | |||
++history_[current_pulse_].on_duration; | |||
} | |||
if (float(history_[current_pulse_].total_duration) >= next_bucket_) { | |||
++history_[current_pulse_].bucket; | |||
next_bucket_ *= kLogOneFourth; | |||
} | |||
// If the pulse width is constant, and if a clock falling edge is | |||
// detected, estimate the period using the on time and the pulse width, | |||
// and correct the phase increment accordingly. | |||
if ((flags & GATE_FLAG_FALLING) && | |||
average_pulse_width_ > 0.0f) { | |||
float t_on = static_cast<float>(history_[current_pulse_].on_duration); | |||
float next = max_train_phase_ - static_cast<float>(reset_counter_) + 1.0f; | |||
float pw = average_pulse_width_; | |||
frequency_ = max((next - train_phase_), 0.0f) * pw / ((1.0f - pw) * t_on); | |||
} | |||
if (audio_rate_) { | |||
ONE_POLE(frequency_, target_frequency_, lp_coefficient_); | |||
train_phase_ += frequency_; | |||
if (train_phase_ >= 1.0f) { | |||
train_phase_ -= 1.0f; | |||
} | |||
*ramp++ = train_phase_; | |||
} else { | |||
if (reset_frequency_) { | |||
train_phase_ += reset_frequency_; | |||
if (train_phase_ >= max_train_phase_) { | |||
train_phase_ = 0.0f; | |||
reset_frequency_ = 0.0f; | |||
f_ratio_ = next_f_ratio_; | |||
max_train_phase_ = next_max_train_phase_; | |||
} | |||
} else { | |||
train_phase_ += frequency_; | |||
if (train_phase_ >= max_train_phase_) { | |||
if (frequency_ == max_frequency_) { | |||
train_phase_ -= max_train_phase_; | |||
} else { | |||
train_phase_ = max_train_phase_; | |||
} | |||
} | |||
} | |||
float output_phase = train_phase_ * f_ratio_; | |||
output_phase -= static_cast<float>(static_cast<int>(output_phase)); | |||
*ramp++ = output_phase; | |||
} | |||
} | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,132 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Recovers a ramp from a clock input by guessing at what time the next edge | |||
// will occur. Prediction strategies: | |||
// - Moving average of previous intervals. | |||
// - Trigram model on quantized intervals. | |||
// - Periodic rhythmic pattern. | |||
// - Assume that the pulse width is constant, deduct the period from the on time | |||
// and the pulse width. | |||
// | |||
// All prediction strategies are concurrently tested, and the output from the | |||
// best performing one is selected (Ă la early Scheirer/Goto beat trackers). | |||
#ifndef MARBLES_RAMP_RAMP_EXTRACTOR_H_ | |||
#define MARBLES_RAMP_RAMP_EXTRACTOR_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "stmlib/utils/gate_flags.h" | |||
#include "marbles/ramp/ramp_divider.h" | |||
namespace marbles { | |||
class RampExtractor { | |||
public: | |||
RampExtractor() { } | |||
~RampExtractor() { } | |||
void Init(float max_frequency); | |||
void Process( | |||
Ratio r, | |||
bool always_ramp_to_maximum, | |||
const stmlib::GateFlags* gate_flags, | |||
float* ramp, | |||
size_t size); | |||
void Reset(); | |||
private: | |||
struct Pulse { | |||
uint32_t on_duration; | |||
uint32_t total_duration; | |||
uint32_t bucket; // 4xlog2(total_duration). | |||
float pulse_width; | |||
}; | |||
struct Prediction { | |||
float period; | |||
float accuracy; | |||
}; | |||
enum Predictor { | |||
PREDICTOR_SLOW_MOVING_AVERAGE, | |||
PREDICTOR_FAST_MOVING_AVERAGE, | |||
PREDICTOR_HASH, | |||
PREDICTOR_PERIOD_1, | |||
PREDICTOR_PERIOD_2, | |||
PREDICTOR_PERIOD_3, | |||
PREDICTOR_PERIOD_4, | |||
PREDICTOR_PERIOD_5, | |||
PREDICTOR_PERIOD_6, | |||
PREDICTOR_PERIOD_7, | |||
PREDICTOR_PERIOD_8, | |||
PREDICTOR_PERIOD_9, | |||
PREDICTOR_PERIOD_10, | |||
PREDICTOR_LAST | |||
}; | |||
static const size_t kHistorySize = 16; | |||
static const size_t kHashTableSize = 256; | |||
float ComputeAveragePulseWidth(float tolerance) const; | |||
Prediction PredictNextPeriod(); | |||
size_t current_pulse_; | |||
Pulse history_[kHistorySize]; | |||
float next_bucket_; | |||
float prediction_hash_table_[kHashTableSize]; | |||
float predicted_period_[PREDICTOR_LAST]; | |||
float prediction_accuracy_[PREDICTOR_LAST]; | |||
float average_pulse_width_; | |||
float train_phase_; | |||
float frequency_; | |||
float max_output_phase_; | |||
float max_train_phase_; | |||
float reset_frequency_; | |||
float target_frequency_; | |||
float lp_coefficient_; | |||
float f_ratio_; | |||
float next_f_ratio_; | |||
float next_max_train_phase_; | |||
int reset_counter_; | |||
uint32_t reset_interval_; | |||
bool audio_rate_; | |||
float max_frequency_; | |||
float audio_rate_period_; | |||
float audio_rate_period_hysteresis_; | |||
DISALLOW_COPY_AND_ASSIGN(RampExtractor); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RAMP_RAMP_EXTRACTOR_H_ |
@@ -0,0 +1,63 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Simple ramp generator. | |||
#ifndef MARBLES_RAMP_RAMP_GENERATOR_H_ | |||
#define MARBLES_RAMP_RAMP_GENERATOR_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace marbles { | |||
class RampGenerator { | |||
public: | |||
RampGenerator() { } | |||
~RampGenerator() { } | |||
void Init() { | |||
phase_ = 0.0f; | |||
} | |||
void Render(float frequency, float* out, size_t size) { | |||
while (size--) { | |||
phase_ += frequency; | |||
if (phase_ >= 1.0f) { | |||
phase_ -= 1.0f; | |||
} | |||
*out++ = phase_; | |||
} | |||
} | |||
private: | |||
float phase_; | |||
DISALLOW_COPY_AND_ASSIGN(RampGenerator); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RAMP_RAMP_GENERATOR_H_ |
@@ -0,0 +1,132 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// A ramp that follows a mater ramp through division/multiplication. | |||
#ifndef MARBLES_RAMP_SLAVE_RAMP_H_ | |||
#define MARBLES_RAMP_SLAVE_RAMP_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/ramp/ramp.h" | |||
namespace marbles { | |||
class SlaveRamp { | |||
public: | |||
SlaveRamp() { } | |||
~SlaveRamp() { } | |||
inline void Init() { | |||
phase_ = 0.0f; | |||
max_phase_ = kMaxRampValue; | |||
ratio_ = 1.0f; | |||
pulse_width_ = 0.0f; | |||
target_ = 1.0f; | |||
pulse_length_ = 0; | |||
bernoulli_ = false; | |||
must_complete_ = false; | |||
} | |||
// Initialize with a multiplied/divided rate compared to the master. | |||
inline void Init(int pattern_length, Ratio ratio, float pulse_width) { | |||
bernoulli_ = false; | |||
phase_ = 0.0f; | |||
max_phase_ = static_cast<float>(pattern_length) * kMaxRampValue; | |||
ratio_ = ratio.to_float(); | |||
pulse_width_ = pulse_width; | |||
target_ = 1.0f; | |||
pulse_length_ = 0; | |||
} | |||
// Initialize with an adaptive slope: divide the frequency by 2 every time | |||
// we know we won't have to reach 1.0 at the next tick. | |||
inline void Init( | |||
bool must_complete, | |||
float pulse_width, | |||
float expected_value) { | |||
bernoulli_ = true; | |||
if (must_complete_) { | |||
phase_ = 0.0f; | |||
pulse_width_ = pulse_width; | |||
ratio_ = 1.0f; | |||
pulse_length_ = 0; | |||
} | |||
if (!must_complete) { | |||
ratio_ = (1.0f - phase_) * expected_value; | |||
} else { | |||
ratio_ = 1.0f - phase_; | |||
} | |||
must_complete_ = must_complete; | |||
} | |||
inline void Process(float frequency, float* phase, bool* gate) { | |||
float output_phase; | |||
if (bernoulli_) { | |||
phase_ += frequency * ratio_; | |||
output_phase = phase_; | |||
if (output_phase >= 1.0f) { | |||
output_phase = 1.0f; | |||
} | |||
} else { | |||
phase_ += frequency; | |||
if (phase_ >= max_phase_) { | |||
phase_ = max_phase_; | |||
} | |||
output_phase = phase_ * ratio_; | |||
if (output_phase > target_) { | |||
pulse_length_ = 0; | |||
target_ += 1.0f; | |||
} | |||
output_phase -= static_cast<float>(static_cast<int>(output_phase)); | |||
} | |||
*phase = output_phase; | |||
*gate = pulse_width_ == 0.0f | |||
? pulse_length_ < 32 && output_phase <= 0.5f | |||
: output_phase < pulse_width_; | |||
++pulse_length_; | |||
} | |||
private: | |||
float phase_; | |||
float max_phase_; | |||
float ratio_; | |||
float pulse_width_; | |||
float target_; | |||
int pulse_length_; | |||
bool bernoulli_; | |||
bool must_complete_; | |||
DISALLOW_COPY_AND_ASSIGN(SlaveRamp); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RAMP_SLAVE_RAMP_H_ |
@@ -0,0 +1,115 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Quantize voltages by sampling from a discrete distribution. | |||
#include "marbles/random/discrete_distribution_quantizer.h" | |||
#include "stmlib/dsp/dsp.h" | |||
#include <cmath> | |||
#include <algorithm> | |||
namespace marbles { | |||
using namespace stmlib; | |||
using namespace std; | |||
void DiscreteDistributionQuantizer::Init(const Scale& scale) { | |||
int n = scale.num_degrees; | |||
// We don't want garbage scale data here... | |||
if (!n || n > kMaxDegrees || scale.base_interval == 0.0f) { | |||
return; | |||
} | |||
base_interval_ = scale.base_interval; | |||
base_interval_reciprocal_ = 1.0f / scale.base_interval; | |||
num_cells_ = n + 1; | |||
for (int i = 0; i <= n; ++i) { | |||
float previous_voltage = scale.cell_voltage(i == 0 ? 0 : i - 1); | |||
float next_voltage = scale.cell_voltage(i == n ? n : i + 1); | |||
cells_[i].center = scale.cell_voltage(i); | |||
cells_[i].width = 0.5f * (next_voltage - previous_voltage); | |||
cells_[i].weight = static_cast<float>(scale.degree[i % n].weight) / 256.0f; | |||
} | |||
} | |||
float DiscreteDistributionQuantizer::Process(float value, float amount) { | |||
if (amount < 0.0f) { | |||
return value; | |||
} | |||
float raw_value = value; | |||
// Assuming 1V/Octave and a scale repeating every octave, note_integral | |||
// will store the octave number, and note_fractional the fractional | |||
// pitch class. | |||
const float note = value * base_interval_reciprocal_; | |||
MAKE_INTEGRAL_FRACTIONAL(note); | |||
if (value < 0.0f) { | |||
note_integral -= 1; | |||
note_fractional += 1.0f; | |||
} | |||
// For amount ranging between 0 and 0.25, do not remove notes from the scale | |||
// just crossfade from the unquantized output to the quantized output. | |||
const float scaled_amount = amount < 0.25f ? 0.0f : (amount - 0.25f) * 1.333f; | |||
distribution_.Init(); | |||
for (int i = 0; i < num_cells_ - 1; ++i) { | |||
distribution_.AddToken(i, cells_[i].scaled_width(scaled_amount)); | |||
} | |||
distribution_.NoMoreTokens(); | |||
Distribution::Result r = distribution_.Sample(note_fractional); | |||
float quantized_value = cells_[r.token_id].center; | |||
float offset = static_cast<float>(note_integral) * base_interval_; | |||
quantized_value += offset; | |||
r.start *= base_interval_; | |||
r.start += offset; | |||
if (amount < 0.25f) { | |||
amount *= 4.0f; | |||
float x; | |||
if (r.token_id == 0) { | |||
x = r.fraction - 1.0f; | |||
} else if (r.token_id == num_cells_ - 1) { | |||
x = -r.fraction; | |||
} else { | |||
x = 2.0f * (fabs(r.fraction - 0.5f) - 0.5f); | |||
} | |||
const float slope = amount / (1.01f - amount); | |||
const float y = max(x * slope + 1.0f, 0.0f); | |||
quantized_value -= y * (quantized_value - raw_value); | |||
} | |||
return quantized_value; | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,75 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Quantize voltages by sampling from a discrete distribution. | |||
#ifndef MARBLES_RANDOM_DISCRETE_DISTRIBUTION_QUANTIZER_H_ | |||
#define MARBLES_RANDOM_DISCRETE_DISTRIBUTION_QUANTIZER_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/random/distributions.h" | |||
#include "marbles/random/quantizer.h" | |||
namespace marbles { | |||
class DiscreteDistributionQuantizer { | |||
public: | |||
typedef DiscreteDistribution<kMaxDegrees> Distribution; | |||
struct Cell { | |||
float center; | |||
float width; | |||
float weight; | |||
inline float scaled_width(float amount) { | |||
float w = 8.0f * (weight - amount) + 0.5f; | |||
CONSTRAIN(w, 0.0f, 1.0f); | |||
return w * width; | |||
} | |||
}; | |||
DiscreteDistributionQuantizer() { } | |||
~DiscreteDistributionQuantizer() { } | |||
void Init(const Scale& scale); | |||
float Process(float value, float amount); | |||
private: | |||
float base_interval_; | |||
float base_interval_reciprocal_; | |||
int num_cells_; | |||
Cell cells_[kMaxDegrees + 1]; | |||
Distribution distribution_; | |||
DISALLOW_COPY_AND_ASSIGN(DiscreteDistributionQuantizer); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_DISCRETE_DISTRIBUTION_QUANTIZER_H_ |
@@ -0,0 +1,182 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Generates samples from various kinds of random distributions. | |||
#ifndef MARBLES_RANDOM_DISTRIBUTIONS_H_ | |||
#define MARBLES_RANDOM_DISTRIBUTIONS_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <algorithm> | |||
#include "stmlib/dsp/dsp.h" | |||
#include "marbles/resources.h" | |||
namespace marbles { | |||
const size_t kNumBiasValues = 5; | |||
const size_t kNumRangeValues = 9; | |||
const float kIcdfTableSize = 128.0f; | |||
// Generates samples from beta distribution, from uniformly distributed samples. | |||
// For higher throughput, uses pre-computed tables of inverse cdfs. | |||
inline float BetaDistributionSample(float uniform, float spread, float bias) { | |||
// Tables are pre-computed only for bias <= 0.5. For values above 0.5, | |||
// symmetry is used. | |||
bool flip_result = bias > 0.5f; | |||
if (flip_result) { | |||
uniform = 1.0f - uniform; | |||
bias = 1.0f - bias; | |||
} | |||
bias *= (static_cast<float>(kNumBiasValues) - 1.0f) * 2.0f; | |||
spread *= (static_cast<float>(kNumRangeValues) - 1.0f); | |||
MAKE_INTEGRAL_FRACTIONAL(bias); | |||
MAKE_INTEGRAL_FRACTIONAL(spread); | |||
size_t cell = bias_integral * (kNumRangeValues + 1) + spread_integral; | |||
// Lower 5% and 95% percentiles use a different table with higher resolution. | |||
size_t offset = 0; | |||
if (uniform <= 0.05f) { | |||
offset = kIcdfTableSize + 1; | |||
uniform *= 20.0f; | |||
} else if (uniform >= 0.95f) { | |||
offset = 2 * (kIcdfTableSize + 1); | |||
uniform = (uniform - 0.95f) * 20.0f; | |||
} | |||
float x1y1 = stmlib::Interpolate( | |||
distributions_table[cell] + offset, | |||
uniform, | |||
kIcdfTableSize); | |||
float x2y1 = stmlib::Interpolate( | |||
distributions_table[cell + 1] + offset, | |||
uniform, | |||
kIcdfTableSize); | |||
float x1y2 = stmlib::Interpolate( | |||
distributions_table[cell + kNumRangeValues + 1] + offset, | |||
uniform, | |||
kIcdfTableSize); | |||
float x2y2 = stmlib::Interpolate( | |||
distributions_table[cell + kNumRangeValues + 2] + offset, | |||
uniform, | |||
kIcdfTableSize); | |||
float y1 = x1y1 + (x2y1 - x1y1) * spread_fractional; | |||
float y2 = x1y2 + (x2y2 - x1y2) * spread_fractional; | |||
float y = y1 + (y2 - y1) * bias_fractional; | |||
if (flip_result) { | |||
y = 1.0f - y; | |||
} | |||
return y; | |||
} | |||
// Pre-computed beta(3, 3) with a fatter tail. | |||
inline float FastBetaDistributionSample(float uniform) { | |||
return stmlib::Interpolate(dist_icdf_4_3, uniform, kIcdfTableSize); | |||
} | |||
// Draws samples from a discrete distribution. Used for the quantizer. | |||
// Example: | |||
// * 1 with probability 0.2 | |||
// * 20 with probability 0.7 | |||
// * 666 with probability 0.1 | |||
// | |||
// DiscreteDistribution d; | |||
// d.Init(); | |||
// d.AddToken(1, 0.2); | |||
// d.AddToken(20, 0.7); | |||
// d.AddToken(666, 0.1); | |||
// d.NoMoreTokens(); | |||
// Result r = d.Sample(u); | |||
// cout << r.token_id; | |||
// | |||
// Weights do not have to add to 1.0f - the class handles normalization. | |||
// | |||
template<size_t size> | |||
class DiscreteDistribution { | |||
public: | |||
DiscreteDistribution() { } | |||
~DiscreteDistribution() { } | |||
void Init() { | |||
sum_ = 0.0f; | |||
num_tokens_ = 1; | |||
cdf_[0] = 0.0f; | |||
token_ids_[0] = 0; | |||
} | |||
void AddToken(int token_id, float weight) { | |||
if (weight <= 0.0f) { | |||
return; | |||
} | |||
sum_ += weight; | |||
token_ids_[num_tokens_] = token_id; | |||
cdf_[num_tokens_] = sum_; | |||
++num_tokens_; | |||
} | |||
void NoMoreTokens() { | |||
token_ids_[num_tokens_] = token_ids_[num_tokens_ - 1]; | |||
cdf_[num_tokens_] = sum_ + 1.0f; | |||
} | |||
struct Result { | |||
int token_id; | |||
float fraction; | |||
float start; | |||
float width; | |||
}; | |||
inline Result Sample(float u) const { | |||
Result r; | |||
u *= sum_; | |||
int n = std::upper_bound(&cdf_[1], &cdf_[num_tokens_ + 1], u) - &cdf_[0]; | |||
float norm = 1.0f / sum_; | |||
r.token_id = token_ids_[n]; | |||
r.width = (cdf_[n] - cdf_[n - 1]) * norm; | |||
r.start = (cdf_[n - 1]) * norm; | |||
r.fraction = (u - cdf_[n - 1]) / (cdf_[n] - cdf_[n - 1]); | |||
return r; | |||
} | |||
float sum_; | |||
float cdf_[size + 2]; | |||
int token_ids_[size + 2]; | |||
int num_tokens_; | |||
DISALLOW_COPY_AND_ASSIGN(DiscreteDistribution); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_DISTRIBUTIONS_H_ |
@@ -0,0 +1,88 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Lag processor for the STEPS control. | |||
#include "marbles/random/lag_processor.h" | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/dsp/units.h" | |||
#include "marbles/resources.h" | |||
namespace marbles { | |||
using namespace stmlib; | |||
void LagProcessor::Init() { | |||
ramp_start_ = 0.0f; | |||
ramp_value_ = 0.0f; | |||
lp_state_ = 0.0f; | |||
previous_phase_ = 0.0f; | |||
} | |||
float LagProcessor::Process(float value, float smoothness, float phase) { | |||
float frequency = phase - previous_phase_; | |||
if (frequency < 0.0f) { | |||
frequency += 1.0f; | |||
} | |||
previous_phase_ = phase; | |||
// The frequency of the portamento/glide LP filter follows an exponential | |||
// scale, with a minimum frequency corresponding to half the clock pulse | |||
// frequency (giving a roughly linear glide), and a maximum value 7 octaves | |||
// above. | |||
// | |||
// When smoothness approaches 0, the response curve is tweaked to give | |||
// immediate voltage changes, without any lag. | |||
frequency *= 0.25f; | |||
frequency *= SemitonesToRatio(84.0f * (1.0f - smoothness)); | |||
if (frequency >= 1.0f) { | |||
frequency = 1.0f; | |||
} | |||
if (smoothness <= 0.05f) { | |||
frequency += 20.f * (0.05f - smoothness) * (1.0f - frequency); | |||
} | |||
ONE_POLE(lp_state_, value, frequency); | |||
// The final output is a crossfade between a variable shape interpolation and | |||
// the low-pass glide/lag. | |||
float interp_amount = (smoothness - 0.6f) * 5.0f; | |||
CONSTRAIN(interp_amount, 0.0f, 1.0f); | |||
float interp_linearity = (1.0f - smoothness) * 5.0f; | |||
CONSTRAIN(interp_linearity, 0.0f, 1.0f); | |||
float warped_phase = Interpolate(lut_raised_cosine, phase, 256.0f); | |||
float interp_phase = Crossfade(warped_phase, phase, interp_linearity); | |||
float interp = Crossfade(ramp_start_, value, interp_phase); | |||
ramp_value_ = interp; | |||
return Crossfade(lp_state_, interp, interp_amount); | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,61 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Lag processor for the STEPS control. | |||
#ifndef MARBLES_RANDOM_LAG_PROCESSOR_H_ | |||
#define MARBLES_RANDOM_LAG_PROCESSOR_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <cstdio> | |||
namespace marbles { | |||
class LagProcessor { | |||
public: | |||
LagProcessor() { } | |||
~LagProcessor() { } | |||
void Init(); | |||
inline void ResetRamp() { | |||
ramp_start_ = ramp_value_; | |||
} | |||
float Process(float value, float smoothness, float phase); | |||
private: | |||
float ramp_start_; | |||
float ramp_value_; | |||
float lp_state_; | |||
float previous_phase_; | |||
DISALLOW_COPY_AND_ASSIGN(LagProcessor); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_LAG_PROCESSOR_H_ |
@@ -0,0 +1,145 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Random generation channel. | |||
#include "marbles/random/output_channel.h" | |||
#include "marbles/random/distributions.h" | |||
#include "marbles/random/random_sequence.h" | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/dsp/parameter_interpolator.h" | |||
#include "stmlib/utils/random.h" | |||
namespace marbles { | |||
using namespace stmlib; | |||
const size_t kNumReacquisitions = 20; // 6.4 samples per millisecond | |||
void OutputChannel::Init() { | |||
spread_ = 0.5f; | |||
bias_ = 0.5f; | |||
steps_ = 0.5f; | |||
scale_index_ = 0; | |||
register_mode_ = false; | |||
register_value_ = 0.0f; | |||
register_transposition_ = 0.0f; | |||
previous_steps_ = 0.0f; | |||
previous_phase_ = 0.0f; | |||
reacquisition_counter_ = 0; | |||
previous_voltage_ = 0.0f; | |||
voltage_ = 0.0f; | |||
quantized_voltage_ = 0.0f; | |||
scale_offset_ = ScaleOffset(10.0f, -5.0f); | |||
lag_processor_.Init(); | |||
Scale scale; | |||
scale.Init(); | |||
for (int i = 0; i < 6; ++i) { | |||
quantizer_[i].Init(scale); | |||
} | |||
} | |||
float OutputChannel::GenerateNewVoltage(RandomSequence* random_sequence) { | |||
float u = random_sequence->NextValue(register_mode_, register_value_); | |||
if (register_mode_) { | |||
return 10.0f * (u - 0.5f) + register_transposition_; | |||
} else { | |||
float degenerate_amount = 1.25f - spread_ * 25.0f; | |||
float bernoulli_amount = spread_ * 25.0f - 23.75f; | |||
CONSTRAIN(degenerate_amount, 0.0f, 1.0f); | |||
CONSTRAIN(bernoulli_amount, 0.0f, 1.0f); | |||
float value = BetaDistributionSample(u, spread_, bias_); | |||
float bernoulli_value = u >= (1.0f - bias_) ? 0.999999f : 0.0f; | |||
value += degenerate_amount * (bias_ - value); | |||
value += bernoulli_amount * (bernoulli_value - value); | |||
return scale_offset_(value); | |||
} | |||
} | |||
void OutputChannel::Process( | |||
RandomSequence* random_sequence, | |||
const float* phase, | |||
float* output, | |||
size_t size, | |||
size_t stride) { | |||
ParameterInterpolator steps_modulation( | |||
&previous_steps_, steps_, size); | |||
// This is a horrible hack that wouldn't be here if all the sequencers | |||
// and MIDI/CV interfaces in this world didn't have *horrible* slew on | |||
// their CV output (I'm looking at you KORG). | |||
// Without this hack, the shift register gets its value as soon as the | |||
// rising edge is observed on the GATE input. Problem: the CV input is | |||
// probably still slewing up, so we acquire the wrong value in the shift | |||
// register. What to do then? Over the next 2ms, we'll just track the CV | |||
// input until it reaches its final value - which means that Marbles | |||
// output will be slewed too. Another option would have been to wait 2ms | |||
// between the rising edge and the actual acquisition, but we don't want | |||
// to penalize people who use tighter sequencers. | |||
if (reacquisition_counter_) { | |||
--reacquisition_counter_; | |||
float u = random_sequence->RewriteValue(register_value_); | |||
voltage_ = 10.0f * (u - 0.5f) + register_transposition_; | |||
quantized_voltage_ = Quantize(voltage_, 2.0f * steps_ - 1.0f); | |||
} | |||
while (size--) { | |||
const float steps = steps_modulation.Next(); | |||
if (*phase < previous_phase_) { | |||
previous_voltage_ = voltage_; | |||
voltage_ = GenerateNewVoltage(random_sequence); | |||
lag_processor_.ResetRamp(); | |||
quantized_voltage_ = Quantize(voltage_, 2.0f * steps - 1.0f); | |||
if (register_mode_) { | |||
reacquisition_counter_ = kNumReacquisitions; | |||
} | |||
} | |||
if (steps >= 0.5f) { | |||
*output = quantized_voltage_; | |||
} else { | |||
const float smoothness = 1.0f - 2.0f * steps; | |||
*output = lag_processor_.Process(voltage_, smoothness, *phase); | |||
} | |||
output += stride; | |||
previous_phase_ = *phase++; | |||
} | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,139 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Random generation channel. | |||
#ifndef MARBLES_RANDOM_OUTPUT_CHANNEL_H_ | |||
#define MARBLES_RANDOM_OUTPUT_CHANNEL_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/random/lag_processor.h" | |||
#include "marbles/random/quantizer.h" | |||
namespace marbles { | |||
class RandomSequence; | |||
struct ScaleOffset { | |||
ScaleOffset(float s, float o) { | |||
scale = s; | |||
offset = o; | |||
} | |||
ScaleOffset() { scale = 1.0f; offset = 0.0f; } | |||
float scale; | |||
float offset; | |||
inline float operator()(float x) { return x * scale + offset; } | |||
}; | |||
class OutputChannel { | |||
public: | |||
OutputChannel() { } | |||
~OutputChannel() { } | |||
void Init(); | |||
void LoadScale(int i, const Scale& scale) { | |||
quantizer_[i].Init(scale); | |||
} | |||
void Process( | |||
RandomSequence* random_sequence, | |||
const float* phase, | |||
float* output, | |||
size_t size, | |||
size_t stride); | |||
inline void set_spread(float spread) { | |||
spread_ = spread; | |||
} | |||
inline void set_bias(float bias) { | |||
bias_ = bias; | |||
} | |||
inline void set_scale_index(int i) { | |||
scale_index_ = i; | |||
} | |||
inline void set_steps(float steps) { | |||
steps_ = steps; | |||
} | |||
inline void set_register_mode(bool register_mode) { | |||
register_mode_ = register_mode; | |||
} | |||
inline void set_register_value(float register_value) { | |||
register_value_ = register_value; | |||
} | |||
inline void set_register_transposition(float register_transposition) { | |||
register_transposition_ = register_transposition; | |||
} | |||
inline void set_scale_offset(const ScaleOffset& scale_offset) { | |||
scale_offset_ = scale_offset; | |||
} | |||
inline float Quantize(float voltage, float amount) { | |||
return quantizer_[scale_index_].Process(voltage, amount, false); | |||
} | |||
private: | |||
float GenerateNewVoltage(RandomSequence* random_sequence); | |||
float spread_; | |||
float bias_; | |||
float steps_; | |||
int scale_index_; | |||
bool register_mode_; | |||
float register_value_; | |||
float register_transposition_; | |||
float previous_steps_; | |||
float previous_phase_; | |||
uint32_t reacquisition_counter_; | |||
float previous_voltage_; | |||
float voltage_; | |||
float quantized_voltage_; | |||
ScaleOffset scale_offset_; | |||
LagProcessor lag_processor_; | |||
Quantizer quantizer_[6]; | |||
DISALLOW_COPY_AND_ASSIGN(OutputChannel); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_OUTPUT_CHANNEL_H_ |
@@ -0,0 +1,138 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Variable resolution quantizer. | |||
#include "marbles/random/quantizer.h" | |||
#include "stmlib/dsp/dsp.h" | |||
#include <cmath> | |||
#include <algorithm> | |||
namespace marbles { | |||
using namespace std; | |||
void Quantizer::Init(const Scale& scale) { | |||
int n = scale.num_degrees; | |||
// We don't want garbage scale data here... | |||
if (!n || n > kMaxDegrees || scale.base_interval == 0.0f) { | |||
return; | |||
} | |||
num_degrees_ = n; | |||
base_interval_ = scale.base_interval; | |||
base_interval_reciprocal_ = 1.0f / scale.base_interval; | |||
uint8_t second_largest_threshold = 0; | |||
for (int i = 0; i < n; ++i) { | |||
voltage_[i] = scale.degree[i].voltage; | |||
if (scale.degree[i].weight != 255 && \ | |||
scale.degree[i].weight >= second_largest_threshold) { | |||
second_largest_threshold = scale.degree[i].weight; | |||
} | |||
} | |||
uint8_t thresholds_[kNumThresholds] = { | |||
0, 16, 32, 64, 128, 192, 255 | |||
}; | |||
if (second_largest_threshold > 192) { | |||
// Be more selective to only include the notes at rank 1 and 2 at | |||
// the last but one position. | |||
thresholds_[kNumThresholds - 2] = second_largest_threshold; | |||
} | |||
for (int t = 0; t < kNumThresholds; ++t) { | |||
uint16_t bitmask = 0; | |||
uint8_t first = 0xff; | |||
uint8_t last = 0; | |||
for (int i = 0; i < n; ++i) { | |||
if (scale.degree[i].weight >= thresholds_[t]) { | |||
bitmask |= 1 << i; | |||
if (first == 0xff) first = i; | |||
last = i; | |||
} | |||
} | |||
level_[t].bitmask = bitmask; | |||
level_[t].first = first; | |||
level_[t].last = last; | |||
} | |||
level_quantizer_.Init(); | |||
fill(&feedback_[0], &feedback_[kNumThresholds], 0.0f); | |||
} | |||
float Quantizer::Process(float value, float amount, bool hysteresis) { | |||
int level = level_quantizer_.Process(amount, kNumThresholds + 1); | |||
float quantized_voltage = value; | |||
if (level > 0) { | |||
level -= 1; | |||
float raw_value = value; | |||
if (hysteresis) { | |||
value += feedback_[level]; | |||
} | |||
const float note = value * base_interval_reciprocal_; | |||
MAKE_INTEGRAL_FRACTIONAL(note); | |||
if (value < 0.0f) { | |||
note_integral -= 1; | |||
note_fractional += 1.0f; | |||
} | |||
note_fractional *= base_interval_; | |||
// Search for the tightest upper/lower bound in the set of available | |||
// voltages. stl::upper_bound / stl::lower_bound wouldn't work here | |||
// because some entries are masked. | |||
Level l = level_[level]; | |||
float a = voltage_[l.last] - base_interval_; | |||
float b = voltage_[l.first] + base_interval_; | |||
uint16_t bitmask = l.bitmask; | |||
for (int i = 0; i < num_degrees_; ++i) { | |||
if (bitmask & 1) { | |||
float v = voltage_[i]; | |||
if (note_fractional > v) { | |||
a = v; | |||
} else { | |||
b = v; | |||
break; | |||
} | |||
} | |||
bitmask >>= 1; | |||
} | |||
quantized_voltage = note_fractional < (a + b) * 0.5f ? a : b; | |||
quantized_voltage += static_cast<float>(note_integral) * base_interval_; | |||
feedback_[level] = (quantized_voltage - raw_value) * 0.25f; | |||
} | |||
return quantized_voltage; | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,122 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Variable resolution quantizer. | |||
#ifndef MARBLES_RANDOM_QUANTIZER_H_ | |||
#define MARBLES_RANDOM_QUANTIZER_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "stmlib/dsp/hysteresis_quantizer.h" | |||
#include "marbles/random/distributions.h" | |||
#include "marbles/random/quantizer.h" | |||
namespace marbles { | |||
const int kMaxDegrees = 16; | |||
const int kNumThresholds = 7; | |||
struct Degree { | |||
float voltage; | |||
uint8_t weight; | |||
}; | |||
struct Scale { | |||
float base_interval; | |||
int num_degrees; | |||
Degree degree[kMaxDegrees]; | |||
inline float cell_voltage(int i) const { | |||
float transposition = static_cast<float>(i / num_degrees) * base_interval; | |||
return degree[i % num_degrees].voltage + transposition; | |||
} | |||
void Init() { | |||
base_interval = 1.0f; | |||
num_degrees = 1; | |||
degree[0].voltage = 0.0f; | |||
degree[0].weight = 0.0f; | |||
} | |||
void InitMajor() { | |||
const uint8_t major_scale_weights[] = { | |||
255, 16, 128, 16, 192, 64, 8, 224, 16, 96, 32, 160, | |||
}; | |||
base_interval = 1.0f; | |||
num_degrees = 12; | |||
for (size_t i = 0; i < 12; ++i) { | |||
degree[i].voltage = static_cast<float>(i) * 0.0833333333f; | |||
degree[i].weight = major_scale_weights[i]; | |||
} | |||
} | |||
void InitTenth() { | |||
const uint8_t major_scale_weights[] = { | |||
255, 255, 255, 255, 255, 255, 255, 255, 255, 25 | |||
}; | |||
base_interval = 1.0f; | |||
num_degrees = 10; | |||
for (size_t i = 0; i < 10; ++i) { | |||
degree[i].voltage = static_cast<float>(i) * 0.1f; | |||
degree[i].weight = major_scale_weights[i]; | |||
} | |||
} | |||
}; | |||
class Quantizer { | |||
public: | |||
Quantizer() { } | |||
~Quantizer() { } | |||
void Init(const Scale& scale); | |||
float Process(float value, float amount, bool hysteresis); | |||
private: | |||
struct Level { | |||
uint16_t bitmask; // bitmask of active degrees. | |||
uint8_t first; // index of the first active degree. | |||
uint8_t last; // index of the last active degree. | |||
}; | |||
float voltage_[kMaxDegrees]; | |||
Level level_[kNumThresholds]; | |||
float feedback_[kNumThresholds]; | |||
float base_interval_; | |||
float base_interval_reciprocal_; | |||
int num_degrees_; | |||
stmlib::HysteresisQuantizer level_quantizer_; | |||
DISALLOW_COPY_AND_ASSIGN(Quantizer); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_QUANTIZER_H_ |
@@ -0,0 +1,65 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Pseudo-random generator used as a fallback when we need more random values | |||
// than available in the hardware RNG buffer. | |||
#ifndef MARBLES_RANDOM_RANDOM_GENERATOR_H_ | |||
#define MARBLES_RANDOM_RANDOM_GENERATOR_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "stmlib/utils/ring_buffer.h" | |||
namespace marbles { | |||
class RandomGenerator { | |||
public: | |||
RandomGenerator() { } | |||
~RandomGenerator() { } | |||
inline void Init(uint32_t seed) { | |||
state_ = seed; | |||
} | |||
inline void Mix(uint32_t word) { | |||
// state_ ^= word; | |||
} | |||
inline uint32_t GetWord() { | |||
state_ = state_ * 1664525L + 1013904223L; | |||
return state_; | |||
} | |||
private: | |||
uint32_t state_; | |||
DISALLOW_COPY_AND_ASSIGN(RandomGenerator); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_RANDOM_GENERATOR_H_ |
@@ -0,0 +1,228 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Sequence of random values. | |||
#ifndef MARBLES_RANDOM_RANDOM_SEQUENCE_H_ | |||
#define MARBLES_RANDOM_RANDOM_SEQUENCE_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/random/random_stream.h" | |||
#include <algorithm> | |||
namespace marbles { | |||
const int kDejaVuBufferSize = 16; | |||
const int kHistoryBufferSize = 16; | |||
const float kMaxUint32 = 4294967296.0f; | |||
class RandomSequence { | |||
public: | |||
RandomSequence() { } | |||
~RandomSequence() { } | |||
inline void Init(RandomStream* random_stream) { | |||
random_stream_ = random_stream; | |||
for (int i = 0; i < kDejaVuBufferSize; ++i) { | |||
loop_[i] = random_stream_->GetFloat(); | |||
} | |||
std::fill(&history_[0], &history_[kHistoryBufferSize], 0.0f); | |||
loop_write_head_ = 0; | |||
length_ = 8; | |||
step_ = 0; | |||
record_head_ = 0; | |||
replay_head_ = -1; | |||
replay_start_ = 0; | |||
deja_vu_ = 0.0f; | |||
replay_hash_ = replay_shift_ = 0; | |||
redo_read_ptr_ = &loop_[0]; | |||
redo_write_ptr_ = NULL; | |||
redo_write_history_ptr_ = NULL; | |||
} | |||
inline void Record() { | |||
replay_start_ = record_head_; | |||
replay_head_ = -1; | |||
} | |||
inline void ReplayPseudoRandom(uint32_t hash) { | |||
replay_head_ = replay_start_; | |||
replay_hash_ = hash; | |||
replay_shift_ = 0; | |||
} | |||
inline void ReplayShifted(uint32_t shift) { | |||
replay_head_ = replay_start_; | |||
replay_hash_ = 0; | |||
replay_shift_ = shift; | |||
} | |||
inline float GetReplayValue() const { | |||
uint32_t h = (replay_head_ - 1 - replay_shift_ + \ | |||
2 * kHistoryBufferSize) % kHistoryBufferSize; | |||
if (!replay_hash_) { | |||
return history_[h]; | |||
} else { | |||
uint32_t word = static_cast<float>(history_[h] * kMaxUint32); | |||
word = (word ^ replay_hash_) * 1664525L + 1013904223L; | |||
return static_cast<float>(word) / kMaxUint32; | |||
} | |||
} | |||
inline float RewriteValue(float value) { | |||
// RewriteValue(x) returns what the most recent call to NextValue would have | |||
// returned if its second argument were x instead. This is used to "rewrite | |||
// history" when the module acquires data from an external source (ASR, | |||
// randomizer or quantizer mode). | |||
if (replay_head_ >= 0) { | |||
return GetReplayValue(); | |||
} | |||
if (redo_write_ptr_) { | |||
*redo_write_ptr_ = 1.0f + value; | |||
} | |||
float result = *redo_read_ptr_; | |||
if (result >= 1.0f) { | |||
result -= 1.0f; | |||
} else { | |||
result = 0.5f; | |||
} | |||
if (redo_write_history_ptr_) { | |||
*redo_write_history_ptr_ = result; | |||
} | |||
return result; | |||
} | |||
inline float NextValue(bool deterministic, float value) { | |||
if (replay_head_ >= 0) { | |||
replay_head_ = (replay_head_ + 1) % kHistoryBufferSize; | |||
return GetReplayValue(); | |||
} | |||
const float p_sqrt = 2.0f * deja_vu_ - 1.0f; | |||
const float p = p_sqrt * p_sqrt; | |||
if (random_stream_->GetFloat() <= p && deja_vu_ <= 0.5f) { | |||
// Generate a new value and put it at the end of the loop. | |||
redo_write_ptr_ = &loop_[loop_write_head_]; | |||
*redo_write_ptr_ = deterministic | |||
? 1.0f + value | |||
: random_stream_->GetFloat(); | |||
loop_write_head_ = (loop_write_head_ + 1) % kDejaVuBufferSize; | |||
step_ = length_ - 1; | |||
} else { | |||
// Do not generate a new value, just replay the loop or jump randomly. | |||
// through it. | |||
redo_write_ptr_ = NULL; | |||
if (random_stream_->GetFloat() <= p) { | |||
step_ = static_cast<int>( | |||
random_stream_->GetFloat() * static_cast<float>(length_)); | |||
} else { | |||
step_ = step_ + 1; | |||
if (step_ >= length_) { | |||
step_ = 0; | |||
} | |||
} | |||
} | |||
uint32_t i = loop_write_head_ + kDejaVuBufferSize - length_ + step_; | |||
redo_read_ptr_ = &loop_[i % kDejaVuBufferSize]; | |||
float result = *redo_read_ptr_; | |||
if (result >= 1.0f) { | |||
result -= 1.0f; | |||
} else if (deterministic) { | |||
// We ask for a deterministic value (shift register), but the loop | |||
// contain random values. return 0.5f in this case! | |||
result = 0.5f; | |||
} | |||
redo_write_history_ptr_ = &history_[record_head_]; | |||
*redo_write_history_ptr_ = result; | |||
record_head_ = (record_head_ + 1) % kHistoryBufferSize; | |||
return result; | |||
} | |||
inline void NextVector(float* destination, size_t size) { | |||
float seed = NextValue(false, 0.0f); | |||
uint32_t word = static_cast<float>(seed * kMaxUint32); | |||
while (size--) { | |||
*destination++ = static_cast<float>(word) / kMaxUint32; | |||
word = word * 1664525L + 1013904223L; | |||
} | |||
} | |||
inline void set_deja_vu(float deja_vu) { | |||
deja_vu_ = deja_vu; | |||
} | |||
inline void set_length(int length) { | |||
if (length < 1 || length > kDejaVuBufferSize) { | |||
return; | |||
} | |||
length_ = length; | |||
step_ = step_ % length; | |||
} | |||
inline float deja_vu() const { | |||
return deja_vu_; | |||
} | |||
inline int length() const { | |||
return length_; | |||
} | |||
private: | |||
RandomStream* random_stream_; | |||
float loop_[kDejaVuBufferSize]; | |||
float history_[kHistoryBufferSize]; | |||
int loop_write_head_; | |||
int length_; | |||
int step_; | |||
// Allows to go back in the past and get the same results again from NextValue | |||
// calls. Allows the 3 X channels to be locked to the same random loop. | |||
int record_head_; | |||
int replay_head_; | |||
int replay_start_; | |||
uint32_t replay_hash_; | |||
uint32_t replay_shift_; | |||
float deja_vu_; | |||
float* redo_read_ptr_; | |||
float* redo_write_ptr_; | |||
float* redo_write_history_ptr_; | |||
DISALLOW_COPY_AND_ASSIGN(RandomSequence); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_RANDOM_SEQUENCE_H_ |
@@ -0,0 +1,82 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Stream of random values, filled from a hardware RNG, with a fallback | |||
// mechanism. | |||
#ifndef MARBLES_RANDOM_RANDOM_STREAM_H_ | |||
#define MARBLES_RANDOM_RANDOM_STREAM_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "stmlib/utils/ring_buffer.h" | |||
#include "marbles/random/random_generator.h" | |||
namespace marbles { | |||
class RandomStream { | |||
public: | |||
RandomStream() { } | |||
~RandomStream() { } | |||
inline void Init(RandomGenerator* fallback_generator) { | |||
fallback_generator_ = fallback_generator; | |||
buffer_.Init(); | |||
} | |||
inline void Write(uint32_t value) { | |||
// buffer_.Swallow(1); | |||
// buffer_.Overwrite(value); | |||
if (buffer_.writable()) { | |||
buffer_.Overwrite(value); | |||
} | |||
fallback_generator_->Mix(value); | |||
} | |||
inline uint32_t GetWord() { | |||
if (buffer_.readable()) { | |||
return buffer_.ImmediateRead(); | |||
} else { | |||
return fallback_generator_->GetWord(); | |||
} | |||
} | |||
inline float GetFloat() { | |||
uint32_t word = GetWord(); | |||
return static_cast<float>(word) / 4294967296.0f; | |||
} | |||
private: | |||
stmlib::RingBuffer<uint32_t, 128> buffer_; | |||
RandomGenerator* fallback_generator_; | |||
DISALLOW_COPY_AND_ASSIGN(RandomStream); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_RANDOM_STREAM_H_ |
@@ -0,0 +1,414 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Generator for the T outputs. | |||
#include "marbles/random/t_generator.h" | |||
#include <algorithm> | |||
#include "stmlib/dsp/units.h" | |||
#include "marbles/resources.h" | |||
namespace marbles { | |||
using namespace std; | |||
using namespace stmlib; | |||
/* static */ | |||
DividerPattern TGenerator::divider_patterns[kNumDividerPatterns] = { | |||
{ { { 1, 1 }, { 1, 1 } }, 1 }, | |||
{ { { 1, 1 }, { 2, 1 } }, 1 }, | |||
{ { { 1, 2 }, { 1, 1 } }, 2 }, | |||
{ { { 1, 1 }, { 4, 1 } }, 1 }, | |||
{ { { 1, 2 }, { 2, 1 } }, 2 }, | |||
{ { { 1, 1 }, { 3, 2 } }, 2 }, | |||
{ { { 1, 4 }, { 4, 1 } }, 4 }, | |||
{ { { 1, 4 }, { 2, 1 } }, 4 }, | |||
{ { { 1, 2 }, { 3, 2 } }, 2 }, | |||
{ { { 1, 1 }, { 8, 1 } }, 1 }, | |||
{ { { 1, 1 }, { 3, 1 } }, 1 }, | |||
{ { { 1, 3 }, { 1, 1 } }, 3 }, | |||
{ { { 1, 1 }, { 5, 4 } }, 4 }, | |||
{ { { 1, 2 }, { 5, 4 } }, 4 }, | |||
{ { { 1, 1 }, { 6, 1 } }, 1 }, | |||
{ { { 1, 3 }, { 2, 1 } }, 3 }, | |||
{ { { 1, 1 }, { 16, 1 } }, 1 }, | |||
}; | |||
/* static */ | |||
DividerPattern TGenerator::fixed_divider_patterns[kNumDividerPatterns] = { | |||
{ { { 8, 1 }, { 1, 8 } }, 8 }, | |||
{ { { 6, 1 }, { 1, 6 } }, 6 }, | |||
{ { { 4, 1 }, { 1, 4 } }, 4 }, | |||
{ { { 3, 1 }, { 1, 3 } }, 3 }, | |||
{ { { 2, 1 }, { 1, 2 } }, 2 }, | |||
{ { { 3, 2 }, { 2, 3 } }, 6 }, | |||
{ { { 4, 3 }, { 3, 4 } }, 12 }, | |||
{ { { 5, 4 }, { 4, 5 } }, 20 }, | |||
{ { { 1, 1 }, { 1, 1 } }, 1 }, | |||
{ { { 4, 5 }, { 5, 4 } }, 20 }, | |||
{ { { 3, 4 }, { 4, 3 } }, 12 }, | |||
{ { { 2, 2 }, { 3, 2 } }, 6 }, | |||
{ { { 1, 2 }, { 2, 1 } }, 2 }, | |||
{ { { 1, 3 }, { 3, 1 } }, 3 }, | |||
{ { { 1, 4 }, { 4, 1 } }, 4 }, | |||
{ { { 1, 6 }, { 6, 1 } }, 6 }, | |||
{ { { 1, 8 }, { 8, 1 } }, 8 }, | |||
}; | |||
/* static */ | |||
Ratio TGenerator::input_divider_ratios[kNumInputDividerRatios] = { | |||
{ 1, 4 }, | |||
{ 1, 3 }, | |||
{ 1, 2 }, | |||
{ 2, 3 }, | |||
{ 1, 1 }, | |||
{ 3, 2 }, | |||
{ 2, 1 }, | |||
{ 3, 1 }, | |||
{ 4, 1 }, | |||
}; | |||
/* static */ | |||
uint8_t TGenerator::drum_patterns[kNumDrumPatterns][kDrumPatternSize] = { | |||
{ 1, 0, 0, 0, 2, 0, 0, 0 }, | |||
{ 0, 0, 1, 0, 2, 0, 0, 0 }, | |||
{ 1, 0, 1, 0, 2, 0, 0, 0 }, | |||
{ 0, 0, 1, 0, 2, 0, 0, 2 }, | |||
{ 1, 0, 1, 0, 2, 0, 1, 0 }, | |||
{ 0, 2, 1, 0, 2, 0, 0, 2 }, | |||
{ 1, 0, 0, 0, 2, 0, 1, 0 }, | |||
{ 0, 2, 1, 0, 2, 0, 1, 2 }, | |||
{ 1, 0, 0, 1, 2, 0, 0, 0 }, | |||
{ 0, 2, 1, 1, 2, 0, 1, 2 }, | |||
{ 1, 0, 0, 1, 2, 0, 1, 0 }, | |||
{ 0, 2, 1, 1, 2, 2, 1, 2 }, | |||
{ 1, 0, 0, 1, 2, 0, 1, 2 }, | |||
{ 0, 2, 0, 1, 2, 0, 1, 2 }, | |||
{ 1, 0, 1, 1, 2, 0, 1, 2 }, | |||
{ 2, 0, 1, 2, 0, 1, 2, 0 }, | |||
{ 1, 2, 1, 1, 2, 0, 1, 2 }, | |||
{ 2, 0, 1, 2, 0, 1, 2, 2 } | |||
}; | |||
void TGenerator::Init(RandomStream* random_stream, float sr) { | |||
one_hertz_ = 1.0f / static_cast<float>(sr); | |||
model_ = T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI; | |||
range_ = T_GENERATOR_RANGE_1X; | |||
rate_ = 0.0f; | |||
bias_ = 0.5f; | |||
jitter_ = 0.0f; | |||
pulse_width_mean_ = 0.0f; | |||
pulse_width_std_ = 0.0f; | |||
master_phase_ = 0.0f; | |||
jitter_multiplier_ = 1.0f; | |||
phase_difference_ = 0.0f; | |||
previous_external_ramp_value_ = 0.0f; | |||
divider_pattern_length_ = 0; | |||
fill(&streak_counter_[0], &streak_counter_[kMarkovHistorySize], 0); | |||
fill(&markov_history_[0], &markov_history_[kMarkovHistorySize], 0); | |||
markov_history_ptr_ = 0; | |||
drum_pattern_step_ = 0; | |||
drum_pattern_index_ = 0; | |||
sequence_.Init(random_stream); | |||
ramp_divider_.Init(); | |||
ramp_extractor_.Init(1000.0f / sr); | |||
ramp_generator_.Init(); | |||
for (size_t i = 0; i < kNumTChannels; ++i) { | |||
slave_ramp_[i].Init(); | |||
} | |||
bias_quantizer_.Init(); | |||
rate_quantizer_.Init(); | |||
use_external_clock_ = false; | |||
} | |||
int TGenerator::GenerateComplementaryBernoulli(const RandomVector& x) { | |||
int bitmask = 0; | |||
for (size_t i = 0; i < kNumTChannels; ++i) { | |||
if ((x.variables.u[i >> 1] > bias_) ^ (i & 1)) { | |||
bitmask |= 1 << i; | |||
} | |||
} | |||
return bitmask; | |||
} | |||
int TGenerator::GenerateIndependentBernoulli(const RandomVector& x) { | |||
int bitmask = 0; | |||
for (size_t i = 0; i < kNumTChannels; ++i) { | |||
if ((x.variables.u[i] > bias_) ^ (i & 1)) { | |||
bitmask |= 1 << i; | |||
} | |||
} | |||
return bitmask; | |||
} | |||
int TGenerator::GenerateThreeStates(const RandomVector& x) { | |||
int bitmask = 0; | |||
float p_none = 0.75f - fabs(bias_ - 0.5f); | |||
float threshold = p_none + (1.0f - p_none) * (0.25f + (bias_ * 0.5f)); | |||
for (size_t i = 0; i < kNumTChannels; ++i) { | |||
float u = x.variables.u[i >> 1]; | |||
if (u > p_none && ((u > threshold) ^ (i & 1))) { | |||
bitmask |= 1 << i; | |||
} | |||
} | |||
return bitmask; | |||
} | |||
int TGenerator::GenerateDrums(const RandomVector& x) { | |||
++drum_pattern_step_; | |||
if (drum_pattern_step_ >= kDrumPatternSize) { | |||
drum_pattern_step_ = 0; | |||
float u = x.variables.u[0] * 2.0f * fabs(bias_ - 0.5f); | |||
drum_pattern_index_ = static_cast<int32_t>(kNumDrumPatterns * u); | |||
if (bias_ <= 0.5f) { | |||
drum_pattern_index_ -= drum_pattern_index_ % 2; | |||
} | |||
} | |||
return drum_patterns[drum_pattern_index_][drum_pattern_step_]; | |||
} | |||
int TGenerator::GenerateMarkov(const RandomVector& x) { | |||
int bitmask = 0; | |||
float b = 1.5f * bias_ - 0.5f; | |||
markov_history_[markov_history_ptr_] = 0; | |||
const int32_t p = markov_history_ptr_; | |||
for (size_t i = 0; i < kNumTChannels; ++i) { | |||
int32_t mask = 1 << i; | |||
// 4 rules: | |||
// * We favor repeating what we played 8 ticks ago. | |||
// * We do not favor pulses appearing on both channels. | |||
// * We favor sparse patterns (no consecutive hits). | |||
// * We favor patterns in which one channel "echoes" what the other | |||
// channel played 4 ticks before. | |||
bool periodic = markov_history_[(p + 8) % kMarkovHistorySize] & mask; | |||
bool simultaneous = markov_history_[(p + 8) % kMarkovHistorySize] & ~mask; | |||
bool dense = markov_history_[(p + 1) % kMarkovHistorySize] & mask; | |||
bool alternate = markov_history_[(p + 4) % kMarkovHistorySize] & ~mask; | |||
float logit = -1.5f; | |||
logit += streak_counter_[i] > 24 ? 10.0f : 0.0f; | |||
logit += 8.0f * fabs(b) * (periodic ? b : -b); | |||
logit -= 2.0f * (simultaneous ? b : -b); | |||
logit -= 1.0f * (dense ? b : 0.0f); | |||
logit += 1.0f * (alternate ? b : 0.0f); | |||
CONSTRAIN(logit, -10.0f, 10.0f); | |||
float probability = lut_logit[static_cast<int>(logit * 12.8f + 128.0f)]; | |||
bool state = x.variables.u[i] < probability; | |||
if (sequence_.deja_vu() >= x.variables.p) { | |||
state = markov_history_[(p + sequence_.length()) % kMarkovHistorySize] & mask; | |||
} | |||
if (state) { | |||
bitmask |= mask; | |||
streak_counter_[i] = 0; | |||
} else { | |||
++streak_counter_[i]; | |||
} | |||
} | |||
markov_history_[p] |= bitmask; | |||
markov_history_ptr_ = (p + kMarkovHistorySize - 1) % kMarkovHistorySize; | |||
return bitmask; | |||
} | |||
void TGenerator::ScheduleOutputPulses(const RandomVector& x, int bitmask) { | |||
for (size_t i = 0; i < kNumTChannels; ++i) { | |||
slave_ramp_[i].Init( | |||
bitmask & 1, | |||
RandomPulseWidth(i, x.variables.pulse_width[i]), | |||
0.5f); | |||
bitmask >>= 1; | |||
} | |||
} | |||
void TGenerator::ConfigureSlaveRamps(const RandomVector& x) { | |||
switch (model_) { | |||
// Generate a bitmask that will describe which outputs are active | |||
// at this clock tick. Use this bitmask to actually schedule pulses on the | |||
// outputs. | |||
case T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI: | |||
ScheduleOutputPulses(x, GenerateComplementaryBernoulli(x)); | |||
break; | |||
case T_GENERATOR_MODEL_INDEPENDENT_BERNOULLI: | |||
ScheduleOutputPulses(x, GenerateIndependentBernoulli(x)); | |||
break; | |||
case T_GENERATOR_MODEL_THREE_STATES: | |||
ScheduleOutputPulses(x, GenerateThreeStates(x)); | |||
break; | |||
case T_GENERATOR_MODEL_DRUMS: | |||
ScheduleOutputPulses(x, GenerateDrums(x)); | |||
break; | |||
case T_GENERATOR_MODEL_MARKOV: | |||
ScheduleOutputPulses(x, GenerateMarkov(x)); | |||
break; | |||
case T_GENERATOR_MODEL_CLUSTERS: | |||
case T_GENERATOR_MODEL_DIVIDER: | |||
--divider_pattern_length_; | |||
if (divider_pattern_length_ <= 0) { | |||
DividerPattern pattern; | |||
if (model_ == T_GENERATOR_MODEL_DIVIDER) { | |||
pattern = bias_quantizer_.Lookup( | |||
fixed_divider_patterns, | |||
bias_, | |||
kNumDividerPatterns); | |||
} else { | |||
float strength = fabs(bias_ - 0.5f) * 2.0f; | |||
float u = x.variables.u[0]; | |||
u *= (u + strength * strength * (1.0f - u)); | |||
u *= strength; | |||
pattern = divider_patterns[static_cast<size_t>( | |||
u * kNumDividerPatterns)]; | |||
if (bias_ < 0.5f) { | |||
for (size_t i = 0; i < kNumTChannels / 2; ++i) { | |||
swap(pattern.ratios[i], pattern.ratios[kNumTChannels - 1 - i]); | |||
} | |||
} | |||
} | |||
for (size_t i = 0; i < kNumTChannels; ++i) { | |||
slave_ramp_[i].Init( | |||
pattern.length, | |||
pattern.ratios[i], | |||
RandomPulseWidth(i, x.variables.pulse_width[i])); | |||
} | |||
divider_pattern_length_ = pattern.length; | |||
} | |||
break; | |||
} | |||
} | |||
void TGenerator::Process( | |||
bool use_external_clock, | |||
const GateFlags* external_clock, | |||
Ramps ramps, | |||
bool* gate, | |||
size_t size) { | |||
float internal_frequency; | |||
if (use_external_clock) { | |||
if (!use_external_clock_) { | |||
ramp_extractor_.Reset(); | |||
} | |||
Ratio ratio = rate_quantizer_.Lookup( | |||
input_divider_ratios, | |||
1.05f * rate_ / 96.0f + 0.5f, | |||
kNumInputDividerRatios); | |||
if (range_ == T_GENERATOR_RANGE_0_25X) { | |||
ratio.q *= 4; | |||
} else if (range_ == T_GENERATOR_RANGE_4X) { | |||
ratio.p *= 4; | |||
} | |||
ratio.Simplify<2>(); | |||
ramp_extractor_.Process(ratio, true, external_clock, ramps.external, size); | |||
internal_frequency = 0.0f; | |||
} else { | |||
float rate = 2.0f; | |||
if (range_ == T_GENERATOR_RANGE_4X) { | |||
rate = 8.0f; | |||
} else if (range_ == T_GENERATOR_RANGE_0_25X) { | |||
rate = 0.5f; | |||
} | |||
internal_frequency = rate * one_hertz_ * SemitonesToRatio(rate_); | |||
} | |||
use_external_clock_ = use_external_clock; | |||
while (size--) { | |||
float frequency = use_external_clock | |||
? *ramps.external - previous_external_ramp_value_ | |||
: internal_frequency; | |||
frequency += frequency < 0.0f ? 1.0f : 0.0f; | |||
float jittery_frequency = frequency * jitter_multiplier_; | |||
master_phase_ += jittery_frequency; | |||
phase_difference_ += frequency - jittery_frequency; | |||
if (master_phase_ > 1.0f) { | |||
master_phase_ -= 1.0f; | |||
RandomVector random_vector; | |||
sequence_.NextVector( | |||
random_vector.x, | |||
sizeof(random_vector.x) / sizeof(float)); | |||
float jitter_amount = jitter_ * jitter_ * jitter_ * jitter_ * 36.0f; | |||
float x = FastBetaDistributionSample(random_vector.variables.jitter); | |||
float multiplier = SemitonesToRatio((x * 2.0f - 1.0f) * jitter_amount); | |||
// This step is crucial in making sure that the jittered clock does not | |||
// deviate too much from the master clock. The larger the phase difference | |||
// difference between the two, the more likely the jittery clock will | |||
// speed up or down to catch up with the straight clock. | |||
multiplier *= phase_difference_ > 0.0f | |||
? 1.0f + phase_difference_ | |||
: 1.0f / (1.0f - phase_difference_); | |||
jitter_multiplier_ = multiplier; | |||
ConfigureSlaveRamps(random_vector); | |||
} | |||
if (internal_frequency) { | |||
*ramps.external = master_phase_; | |||
} | |||
previous_external_ramp_value_ = *ramps.external; | |||
ramps.external++; | |||
*ramps.master++ = master_phase_; | |||
for (size_t j = 0; j < kNumTChannels; ++j) { | |||
slave_ramp_[j].Process( | |||
frequency * jitter_multiplier_, | |||
ramps.slave[j], | |||
gate); | |||
ramps.slave[j]++; | |||
gate++; | |||
} | |||
} | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,206 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Generator for the T outputs. | |||
#ifndef MARBLES_RANDOM_T_GENERATOR_H_ | |||
#define MARBLES_RANDOM_T_GENERATOR_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/ramp/ramp_divider.h" | |||
#include "marbles/ramp/ramp_extractor.h" | |||
#include "marbles/ramp/ramp_generator.h" | |||
#include "marbles/ramp/slave_ramp.h" | |||
#include "marbles/random/distributions.h" | |||
#include "marbles/random/random_sequence.h" | |||
#include "stmlib/dsp/hysteresis_quantizer.h" | |||
namespace marbles { | |||
enum TGeneratorModel { | |||
T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI, | |||
T_GENERATOR_MODEL_CLUSTERS, | |||
T_GENERATOR_MODEL_DRUMS, | |||
T_GENERATOR_MODEL_INDEPENDENT_BERNOULLI, | |||
T_GENERATOR_MODEL_DIVIDER, | |||
T_GENERATOR_MODEL_THREE_STATES, | |||
T_GENERATOR_MODEL_MARKOV, | |||
}; | |||
enum TGeneratorRange { | |||
T_GENERATOR_RANGE_0_25X, | |||
T_GENERATOR_RANGE_1X, | |||
T_GENERATOR_RANGE_4X, | |||
}; | |||
const size_t kNumTChannels = 2; | |||
const size_t kMarkovHistorySize = 16; | |||
const size_t kNumDrumPatterns = 18; | |||
const size_t kDrumPatternSize = 8; | |||
struct DividerPattern { | |||
Ratio ratios[kNumTChannels]; | |||
int32_t length; | |||
}; | |||
struct Ramps { | |||
float* external; | |||
float* master; | |||
float* slave[kNumTChannels]; | |||
}; | |||
const size_t kNumDividerPatterns = 17; | |||
const size_t kNumInputDividerRatios = 9; | |||
class TGenerator { | |||
public: | |||
TGenerator() { } | |||
~TGenerator() { } | |||
void Init(RandomStream* random_stream, float sr); | |||
void Process( | |||
bool use_external_clock, | |||
const stmlib::GateFlags* external_clock, | |||
Ramps ramps, | |||
bool* gate, | |||
size_t size); | |||
inline void set_model(TGeneratorModel model) { | |||
model_ = model; | |||
} | |||
inline void set_range(TGeneratorRange range) { | |||
range_ = range; | |||
} | |||
inline void set_rate(float rate) { | |||
rate_ = rate; | |||
} | |||
inline void set_bias(float bias) { | |||
bias_ = bias; | |||
} | |||
inline void set_jitter(float jitter) { | |||
jitter_ = jitter; | |||
} | |||
inline void set_deja_vu(float deja_vu) { | |||
sequence_.set_deja_vu(deja_vu); | |||
} | |||
inline void set_length(int length) { | |||
sequence_.set_length(length); | |||
} | |||
inline void set_pulse_width_mean(float pulse_width_mean) { | |||
pulse_width_mean_ = pulse_width_mean; | |||
} | |||
inline void set_pulse_width_std(float pulse_width_std) { | |||
pulse_width_std_ = pulse_width_std; | |||
} | |||
private: | |||
union RandomVector { | |||
struct { | |||
float pulse_width[kNumTChannels]; | |||
float u[kNumTChannels]; | |||
float p; | |||
float jitter; | |||
} variables; | |||
float x[2 * kNumTChannels + 2]; | |||
}; | |||
void ConfigureSlaveRamps(const RandomVector& v); | |||
int GenerateComplementaryBernoulli(const RandomVector& v); | |||
int GenerateIndependentBernoulli(const RandomVector& v); | |||
int GenerateThreeStates(const RandomVector& v); | |||
int GenerateDrums(const RandomVector& v); | |||
int GenerateMarkov(const RandomVector& v); | |||
void ScheduleOutputPulses(const RandomVector& v, int bitmask); | |||
float RandomPulseWidth(int i, float u) { | |||
if (pulse_width_std_ == 0.0f) { | |||
return 0.05f + 0.9f * pulse_width_mean_; | |||
} else { | |||
return 0.05f + 0.9f * BetaDistributionSample( | |||
u, | |||
pulse_width_std_, | |||
pulse_width_mean_); // Jon Brooks | |||
// i & 1 ? 1.0f - pulse_width_mean_); | |||
} | |||
} | |||
float one_hertz_; | |||
TGeneratorModel model_; | |||
TGeneratorRange range_; | |||
float rate_; | |||
float bias_; | |||
float jitter_; | |||
float pulse_width_mean_; | |||
float pulse_width_std_; | |||
float master_phase_; | |||
float jitter_multiplier_; | |||
float phase_difference_; | |||
float previous_external_ramp_value_; | |||
bool use_external_clock_; | |||
int32_t divider_pattern_length_; | |||
int32_t streak_counter_[kMarkovHistorySize]; | |||
int32_t markov_history_[kMarkovHistorySize]; | |||
int32_t markov_history_ptr_; | |||
size_t drum_pattern_step_; | |||
size_t drum_pattern_index_; | |||
RandomSequence sequence_; | |||
RampDivider ramp_divider_; | |||
RampExtractor ramp_extractor_; | |||
RampGenerator ramp_generator_; | |||
SlaveRamp slave_ramp_[kNumTChannels]; | |||
stmlib::HysteresisQuantizer bias_quantizer_; | |||
stmlib::HysteresisQuantizer rate_quantizer_; | |||
static DividerPattern divider_patterns[kNumDividerPatterns]; | |||
static DividerPattern fixed_divider_patterns[kNumDividerPatterns]; | |||
static Ratio input_divider_ratios[kNumInputDividerRatios]; | |||
static uint8_t drum_patterns[kNumDrumPatterns][kDrumPatternSize]; | |||
DISALLOW_COPY_AND_ASSIGN(TGenerator); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_T_GENERATOR_H_ |
@@ -0,0 +1,184 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Generator for the X/Y outputs. | |||
#include "marbles/random/x_y_generator.h" | |||
#include <algorithm> | |||
#include "stmlib/dsp/dsp.h" | |||
namespace marbles { | |||
using namespace std; | |||
using namespace stmlib; | |||
void XYGenerator::Init(RandomStream* random_stream, float sr) { | |||
for (size_t i = 0; i < kNumChannels; ++i) { | |||
random_sequence_[i].Init(random_stream); | |||
output_channel_[i].Init(); | |||
} | |||
ramp_extractor_.Init(8000.0f / sr); | |||
ramp_divider_.Init(); | |||
external_clock_stabilization_counter_ = 16; | |||
} | |||
const uint32_t hashes[kNumXChannels] = { | |||
0, 0xbeca55e5, 0xf0cacc1a | |||
}; | |||
void XYGenerator::Process( | |||
ClockSource clock_source, | |||
const GroupSettings& x_settings, | |||
const GroupSettings& y_settings, | |||
const GateFlags* external_clock, | |||
const Ramps& ramps, | |||
float* output, | |||
size_t size) { | |||
float* channel_ramp[kNumChannels]; | |||
if (clock_source != CLOCK_SOURCE_EXTERNAL) { | |||
// For a couple of upcoming blocks, we'll still be receiving garbage from | |||
// the normalization pin that we need to ignore. | |||
external_clock_stabilization_counter_ = 16; | |||
} else { | |||
if (external_clock_stabilization_counter_) { | |||
--external_clock_stabilization_counter_; | |||
if (external_clock_stabilization_counter_ == 0) { | |||
ramp_extractor_.Reset(); | |||
} | |||
} | |||
} | |||
switch (clock_source) { | |||
case CLOCK_SOURCE_EXTERNAL: | |||
{ | |||
Ratio r = { 1, 1 }; | |||
ramp_extractor_.Process(r, false, external_clock, ramps.slave[0], size); | |||
if (external_clock_stabilization_counter_) { | |||
fill(&ramps.slave[0][0], &ramps.slave[0][size], 0.0f); | |||
} | |||
} | |||
channel_ramp[0] = ramps.slave[0]; | |||
channel_ramp[1] = ramps.slave[0]; | |||
channel_ramp[2] = ramps.slave[0]; | |||
break; | |||
case CLOCK_SOURCE_INTERNAL_T1: | |||
channel_ramp[0] = ramps.slave[0]; | |||
channel_ramp[1] = ramps.slave[0]; | |||
channel_ramp[2] = ramps.slave[0]; | |||
break; | |||
case CLOCK_SOURCE_INTERNAL_T2: | |||
channel_ramp[0] = ramps.master; | |||
channel_ramp[1] = ramps.master; | |||
channel_ramp[2] = ramps.master; | |||
break; | |||
case CLOCK_SOURCE_INTERNAL_T3: | |||
channel_ramp[0] = ramps.slave[1]; | |||
channel_ramp[1] = ramps.slave[1]; | |||
channel_ramp[2] = ramps.slave[1]; | |||
break; | |||
default: | |||
channel_ramp[0] = ramps.slave[0]; | |||
channel_ramp[1] = ramps.master; | |||
channel_ramp[2] = ramps.slave[1]; | |||
break; | |||
} | |||
ramp_divider_.Process(y_settings.ratio, channel_ramp[1], ramps.external, size); | |||
channel_ramp[kNumChannels - 1] = ramps.external; | |||
for (size_t i = 0; i < kNumChannels; ++i) { | |||
OutputChannel& channel = output_channel_[i]; | |||
const GroupSettings& settings = i < kNumXChannels ? x_settings : y_settings; | |||
switch (settings.voltage_range) { | |||
case VOLTAGE_RANGE_NARROW: | |||
channel.set_scale_offset(ScaleOffset(2.0f, 0.0f)); | |||
break; | |||
case VOLTAGE_RANGE_POSITIVE: | |||
channel.set_scale_offset(ScaleOffset(5.0f, 0.0f)); | |||
break; | |||
case VOLTAGE_RANGE_FULL: | |||
channel.set_scale_offset(ScaleOffset(10.0f, -5.0f)); | |||
break; | |||
default: | |||
break; | |||
} | |||
float amount = 1.0f; | |||
if (settings.control_mode == CONTROL_MODE_BUMP) { | |||
amount = i == kNumXChannels / 2 ? 1.0f : -1.0f; | |||
} else if (settings.control_mode == CONTROL_MODE_TILT) { | |||
amount = 2.0f * static_cast<float>(i) / float(kNumXChannels - 1) - 1.0f; | |||
} | |||
channel.set_spread(0.5f + (settings.spread - 0.5f) * amount); | |||
channel.set_bias(0.5f + (settings.bias - 0.5f) * amount); | |||
channel.set_steps(0.5f + (settings.steps - 0.5f) * \ | |||
(settings.register_mode ? 1.0f : amount)); | |||
channel.set_scale_index(settings.scale_index); | |||
channel.set_register_mode(settings.register_mode); | |||
channel.set_register_value(settings.register_value); | |||
channel.set_register_transposition( | |||
4.0f * settings.spread * (settings.bias - 0.5f) * amount); | |||
RandomSequence* sequence = &random_sequence_[i]; | |||
sequence->Record(); | |||
sequence->set_length(settings.length); | |||
sequence->set_deja_vu(settings.deja_vu); | |||
// When all channels follow the same clock, the deja-vu random looping will | |||
// follow the same pattern and the constant-mode input will be shifted! | |||
if (clock_source != CLOCK_SOURCE_INTERNAL_T1_T2_T3 | |||
&& i > 0 && i < kNumXChannels) { | |||
sequence = &random_sequence_[0]; | |||
if (settings.register_mode) { | |||
if (settings.control_mode == CONTROL_MODE_IDENTICAL) { | |||
sequence->ReplayShifted(i); | |||
} else if (settings.control_mode == CONTROL_MODE_BUMP) { | |||
sequence->ReplayShifted(i == 2 ? 1 : 0); | |||
} else { | |||
sequence->ReplayShifted(0); | |||
} | |||
} else { | |||
sequence->ReplayPseudoRandom(hashes[i]); | |||
} | |||
} | |||
channel.Process(sequence, channel_ramp[i], &output[i], size, kNumChannels); | |||
} | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,123 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Generator for the X/Y outputs. | |||
#ifndef MARBLES_RANDOM_X_Y_GENERATOR_H_ | |||
#define MARBLES_RANDOM_X_Y_GENERATOR_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/ramp/ramp_divider.h" | |||
#include "marbles/ramp/ramp_extractor.h" | |||
#include "marbles/random/output_channel.h" | |||
#include "marbles/random/random_sequence.h" | |||
#include "marbles/random/t_generator.h" | |||
namespace marbles { | |||
enum VoltageRange { | |||
VOLTAGE_RANGE_NARROW, // +2V | |||
VOLTAGE_RANGE_POSITIVE, // +5V | |||
VOLTAGE_RANGE_FULL // +/- 5V | |||
}; | |||
enum ClockSource { | |||
CLOCK_SOURCE_INTERNAL_T1_T2_T3, | |||
CLOCK_SOURCE_INTERNAL_T1, | |||
CLOCK_SOURCE_INTERNAL_T2, | |||
CLOCK_SOURCE_INTERNAL_T3, | |||
CLOCK_SOURCE_EXTERNAL | |||
}; | |||
enum ControlMode { | |||
CONTROL_MODE_IDENTICAL, | |||
CONTROL_MODE_BUMP, | |||
CONTROL_MODE_TILT | |||
}; | |||
enum OutputGroup { | |||
OUTPUT_GROUP_X, | |||
OUTPUT_GROUP_Y, | |||
OUTPUT_GROUP_LAST | |||
}; | |||
const size_t kNumXChannels = 3; | |||
const size_t kNumYChannels = 1; | |||
const size_t kNumChannels = kNumXChannels + kNumYChannels; | |||
struct GroupSettings { | |||
ControlMode control_mode; | |||
VoltageRange voltage_range; | |||
bool register_mode; | |||
float register_value; | |||
float spread; | |||
float bias; | |||
float steps; | |||
float deja_vu; | |||
int scale_index; | |||
int length; | |||
Ratio ratio; | |||
}; | |||
class XYGenerator { | |||
public: | |||
XYGenerator() { } | |||
~XYGenerator() { } | |||
void Init(RandomStream* random_stream, float sr); | |||
void Process( | |||
ClockSource clock_source, | |||
const GroupSettings& x_settings, | |||
const GroupSettings& y_settings, | |||
const stmlib::GateFlags* external_clock, | |||
const Ramps& ramps, | |||
float* output, | |||
size_t size); | |||
void LoadScale(int channel, int scale_index, const Scale& scale) { | |||
output_channel_[channel].LoadScale(scale_index, scale); | |||
} | |||
void LoadScale(int scale_index, const Scale& scale) { | |||
for (size_t i = 0; i < kNumXChannels; ++i) { | |||
output_channel_[i].LoadScale(scale_index, scale); | |||
} | |||
} | |||
private: | |||
RandomSequence random_sequence_[kNumChannels]; | |||
OutputChannel output_channel_[kNumChannels]; | |||
RampExtractor ramp_extractor_; | |||
RampDivider ramp_divider_; | |||
int external_clock_stabilization_counter_; | |||
DISALLOW_COPY_AND_ASSIGN(XYGenerator); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_RANDOM_X_Y_GENERATOR_H_ |
@@ -0,0 +1,226 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Resources definitions. | |||
// | |||
// Automatically generated with: | |||
// make resources | |||
#ifndef MARBLES_RESOURCES_H_ | |||
#define MARBLES_RESOURCES_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace marbles { | |||
typedef uint8_t ResourceId; | |||
extern const float* lookup_table_table[]; | |||
extern const float* distributions_table[]; | |||
extern const float lut_raised_cosine[]; | |||
extern const float lut_sine[]; | |||
extern const float lut_logit[]; | |||
extern const float dist_icdf_0_0[]; | |||
extern const float dist_icdf_0_1[]; | |||
extern const float dist_icdf_0_2[]; | |||
extern const float dist_icdf_0_3[]; | |||
extern const float dist_icdf_0_4[]; | |||
extern const float dist_icdf_0_5[]; | |||
extern const float dist_icdf_0_6[]; | |||
extern const float dist_icdf_0_7[]; | |||
extern const float dist_icdf_0_8[]; | |||
extern const float dist_icdf_1_0[]; | |||
extern const float dist_icdf_1_1[]; | |||
extern const float dist_icdf_1_2[]; | |||
extern const float dist_icdf_1_3[]; | |||
extern const float dist_icdf_1_4[]; | |||
extern const float dist_icdf_1_5[]; | |||
extern const float dist_icdf_1_6[]; | |||
extern const float dist_icdf_1_7[]; | |||
extern const float dist_icdf_1_8[]; | |||
extern const float dist_icdf_2_0[]; | |||
extern const float dist_icdf_2_1[]; | |||
extern const float dist_icdf_2_2[]; | |||
extern const float dist_icdf_2_3[]; | |||
extern const float dist_icdf_2_4[]; | |||
extern const float dist_icdf_2_5[]; | |||
extern const float dist_icdf_2_6[]; | |||
extern const float dist_icdf_2_7[]; | |||
extern const float dist_icdf_2_8[]; | |||
extern const float dist_icdf_3_0[]; | |||
extern const float dist_icdf_3_1[]; | |||
extern const float dist_icdf_3_2[]; | |||
extern const float dist_icdf_3_3[]; | |||
extern const float dist_icdf_3_4[]; | |||
extern const float dist_icdf_3_5[]; | |||
extern const float dist_icdf_3_6[]; | |||
extern const float dist_icdf_3_7[]; | |||
extern const float dist_icdf_3_8[]; | |||
extern const float dist_icdf_4_0[]; | |||
extern const float dist_icdf_4_1[]; | |||
extern const float dist_icdf_4_2[]; | |||
extern const float dist_icdf_4_3[]; | |||
extern const float dist_icdf_4_4[]; | |||
extern const float dist_icdf_4_5[]; | |||
extern const float dist_icdf_4_6[]; | |||
extern const float dist_icdf_4_7[]; | |||
extern const float dist_icdf_4_8[]; | |||
#define LUT_RAISED_COSINE 0 | |||
#define LUT_RAISED_COSINE_SIZE 257 | |||
#define LUT_SINE 1 | |||
#define LUT_SINE_SIZE 257 | |||
#define LUT_LOGIT 2 | |||
#define LUT_LOGIT_SIZE 257 | |||
#define DIST_ICDF_0_0 0 | |||
#define DIST_ICDF_0_0_SIZE 387 | |||
#define DIST_ICDF_0_1 1 | |||
#define DIST_ICDF_0_1_SIZE 387 | |||
#define DIST_ICDF_0_2 2 | |||
#define DIST_ICDF_0_2_SIZE 387 | |||
#define DIST_ICDF_0_3 3 | |||
#define DIST_ICDF_0_3_SIZE 387 | |||
#define DIST_ICDF_0_4 4 | |||
#define DIST_ICDF_0_4_SIZE 387 | |||
#define DIST_ICDF_0_5 5 | |||
#define DIST_ICDF_0_5_SIZE 387 | |||
#define DIST_ICDF_0_6 6 | |||
#define DIST_ICDF_0_6_SIZE 387 | |||
#define DIST_ICDF_0_7 7 | |||
#define DIST_ICDF_0_7_SIZE 387 | |||
#define DIST_ICDF_0_8 8 | |||
#define DIST_ICDF_0_8_SIZE 387 | |||
#define DIST_ICDF_0_8_GUARD 9 | |||
#define DIST_ICDF_0_8_GUARD_SIZE 387 | |||
#define DIST_ICDF_1_0 10 | |||
#define DIST_ICDF_1_0_SIZE 387 | |||
#define DIST_ICDF_1_1 11 | |||
#define DIST_ICDF_1_1_SIZE 387 | |||
#define DIST_ICDF_1_2 12 | |||
#define DIST_ICDF_1_2_SIZE 387 | |||
#define DIST_ICDF_1_3 13 | |||
#define DIST_ICDF_1_3_SIZE 387 | |||
#define DIST_ICDF_1_4 14 | |||
#define DIST_ICDF_1_4_SIZE 387 | |||
#define DIST_ICDF_1_5 15 | |||
#define DIST_ICDF_1_5_SIZE 387 | |||
#define DIST_ICDF_1_6 16 | |||
#define DIST_ICDF_1_6_SIZE 387 | |||
#define DIST_ICDF_1_7 17 | |||
#define DIST_ICDF_1_7_SIZE 387 | |||
#define DIST_ICDF_1_8 18 | |||
#define DIST_ICDF_1_8_SIZE 387 | |||
#define DIST_ICDF_1_8_GUARD 19 | |||
#define DIST_ICDF_1_8_GUARD_SIZE 387 | |||
#define DIST_ICDF_2_0 20 | |||
#define DIST_ICDF_2_0_SIZE 387 | |||
#define DIST_ICDF_2_1 21 | |||
#define DIST_ICDF_2_1_SIZE 387 | |||
#define DIST_ICDF_2_2 22 | |||
#define DIST_ICDF_2_2_SIZE 387 | |||
#define DIST_ICDF_2_3 23 | |||
#define DIST_ICDF_2_3_SIZE 387 | |||
#define DIST_ICDF_2_4 24 | |||
#define DIST_ICDF_2_4_SIZE 387 | |||
#define DIST_ICDF_2_5 25 | |||
#define DIST_ICDF_2_5_SIZE 387 | |||
#define DIST_ICDF_2_6 26 | |||
#define DIST_ICDF_2_6_SIZE 387 | |||
#define DIST_ICDF_2_7 27 | |||
#define DIST_ICDF_2_7_SIZE 387 | |||
#define DIST_ICDF_2_8 28 | |||
#define DIST_ICDF_2_8_SIZE 387 | |||
#define DIST_ICDF_2_8_GUARD 29 | |||
#define DIST_ICDF_2_8_GUARD_SIZE 387 | |||
#define DIST_ICDF_3_0 30 | |||
#define DIST_ICDF_3_0_SIZE 387 | |||
#define DIST_ICDF_3_1 31 | |||
#define DIST_ICDF_3_1_SIZE 387 | |||
#define DIST_ICDF_3_2 32 | |||
#define DIST_ICDF_3_2_SIZE 387 | |||
#define DIST_ICDF_3_3 33 | |||
#define DIST_ICDF_3_3_SIZE 387 | |||
#define DIST_ICDF_3_4 34 | |||
#define DIST_ICDF_3_4_SIZE 387 | |||
#define DIST_ICDF_3_5 35 | |||
#define DIST_ICDF_3_5_SIZE 387 | |||
#define DIST_ICDF_3_6 36 | |||
#define DIST_ICDF_3_6_SIZE 387 | |||
#define DIST_ICDF_3_7 37 | |||
#define DIST_ICDF_3_7_SIZE 387 | |||
#define DIST_ICDF_3_8 38 | |||
#define DIST_ICDF_3_8_SIZE 387 | |||
#define DIST_ICDF_3_8_GUARD 39 | |||
#define DIST_ICDF_3_8_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_0 40 | |||
#define DIST_ICDF_4_0_SIZE 387 | |||
#define DIST_ICDF_4_1 41 | |||
#define DIST_ICDF_4_1_SIZE 387 | |||
#define DIST_ICDF_4_2 42 | |||
#define DIST_ICDF_4_2_SIZE 387 | |||
#define DIST_ICDF_4_3 43 | |||
#define DIST_ICDF_4_3_SIZE 387 | |||
#define DIST_ICDF_4_4 44 | |||
#define DIST_ICDF_4_4_SIZE 387 | |||
#define DIST_ICDF_4_5 45 | |||
#define DIST_ICDF_4_5_SIZE 387 | |||
#define DIST_ICDF_4_6 46 | |||
#define DIST_ICDF_4_6_SIZE 387 | |||
#define DIST_ICDF_4_7 47 | |||
#define DIST_ICDF_4_7_SIZE 387 | |||
#define DIST_ICDF_4_8 48 | |||
#define DIST_ICDF_4_8_SIZE 387 | |||
#define DIST_ICDF_4_8_GUARD 49 | |||
#define DIST_ICDF_4_8_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_0_GUARD 50 | |||
#define DIST_ICDF_4_0_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_1_GUARD 51 | |||
#define DIST_ICDF_4_1_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_2_GUARD 52 | |||
#define DIST_ICDF_4_2_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_3_GUARD 53 | |||
#define DIST_ICDF_4_3_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_4_GUARD 54 | |||
#define DIST_ICDF_4_4_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_5_GUARD 55 | |||
#define DIST_ICDF_4_5_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_6_GUARD 56 | |||
#define DIST_ICDF_4_6_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_7_GUARD 57 | |||
#define DIST_ICDF_4_7_GUARD_SIZE 387 | |||
#define DIST__ICDF_4_8_GUARD 58 | |||
#define DIST__ICDF_4_8_GUARD_SIZE 387 | |||
#define DIST_ICDF_4_8_GUARD_GUARD 59 | |||
#define DIST_ICDF_4_8_GUARD_GUARD_SIZE 387 | |||
} // namespace marbles | |||
#endif // MARBLES_RESOURCES_H_ |
@@ -0,0 +1,109 @@ | |||
#!/usr/bin/python2.5 | |||
# | |||
# Copyright 2015 Olivier Gillet. | |||
# | |||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||
# | |||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||
# of this software and associated documentation files (the "Software"), to deal | |||
# in the Software without restriction, including without limitation the rights | |||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
# copies of the Software, and to permit persons to whom the Software is | |||
# furnished to do so, subject to the following conditions: | |||
# | |||
# The above copyright notice and this permission notice shall be included in | |||
# all copies or substantial portions of the Software. | |||
# | |||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
# THE SOFTWARE. | |||
# | |||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||
# | |||
# ----------------------------------------------------------------------------- | |||
# | |||
# Lookup table definitions. | |||
import numpy | |||
import scipy.stats | |||
lookup_tables = [] | |||
distributions = [] | |||
"""---------------------------------------------------------------------------- | |||
Raised cosine | |||
----------------------------------------------------------------------------""" | |||
x = numpy.arange(0, 257) / 256.0 | |||
c = 1.0 - (0.5 * numpy.cos(x * numpy.pi) + 0.5) | |||
lookup_tables += [('raised_cosine', c)] | |||
x = numpy.arange(0, 257) / 256.0 | |||
c = numpy.sin(x * numpy.pi * 2) | |||
lookup_tables += [('sine', c)] | |||
"""---------------------------------------------------------------------------- | |||
Logit table | |||
----------------------------------------------------------------------------""" | |||
x = numpy.arange(0, 257) / 256.0 | |||
log_odds = x * 20.0 - 10.0 | |||
odds = 2 ** log_odds | |||
p = odds / (1 + odds) | |||
lookup_tables += [('logit', p)] | |||
"""---------------------------------------------------------------------------- | |||
Inverse CDF of Beta distribution for various combinations of alpha/beta. | |||
Used as a LUT for inverse transform sampling. | |||
----------------------------------------------------------------------------""" | |||
N_nu = 9 | |||
N_mu = 5 | |||
def squash(x): | |||
return x / (1 + x ** 2) ** 0.5 | |||
nu_values = 2 ** numpy.array([9, 5, 3, 2.5, 2, 1.5, 1, 0.5, -1]) | |||
mu_values = numpy.linspace(0, 0.5, N_mu) | |||
mu_values[0] = 0.05 | |||
plot = False | |||
if plot: | |||
import pylab | |||
VOLTAGE_RANGE = 8 | |||
for i, mu in enumerate(mu_values): | |||
row = [] | |||
for j, nu in enumerate(nu_values): | |||
error = numpy.exp(-(numpy.log2(nu) - 1) ** 2 / 20.0) | |||
corrected_mu = 0.5 * (2 * mu) ** (1 / (1 + 3.0 * error)) | |||
alpha, beta = corrected_mu * nu, (1 - corrected_mu) * nu | |||
if plot: | |||
x = numpy.arange(-VOLTAGE_RANGE, VOLTAGE_RANGE, 0.1) | |||
p = scipy.stats.beta.pdf(0.5 * (x / VOLTAGE_RANGE + 1.0), alpha, beta) | |||
pylab.subplot(N_mu, N_nu, i * N_nu + j + 1) | |||
pylab.plot(x, p) | |||
body = numpy.arange(0, 129) / 128.0 | |||
head = body / 20.0 | |||
tail = body / 20.0 + 0.95 | |||
values = numpy.hstack((body, head, tail)) | |||
ppf = scipy.stats.beta.ppf(values, alpha, beta) | |||
row += [('icdf_%d_%d' % (i, j), ppf)] | |||
if j == N_nu - 1: | |||
row += [('icdf_%d_%d_guard' % (i, j), ppf)] | |||
distributions += row | |||
if i == N_mu - 1: | |||
distributions += [(name + '_guard', values) for (name, values) in row] | |||
if plot: | |||
pylab.show() |
@@ -0,0 +1,79 @@ | |||
#!/usr/bin/python2.5 | |||
# | |||
# Copyright 2015 Olivier Gillet. | |||
# | |||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||
# | |||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||
# of this software and associated documentation files (the "Software"), to deal | |||
# in the Software without restriction, including without limitation the rights | |||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
# copies of the Software, and to permit persons to whom the Software is | |||
# furnished to do so, subject to the following conditions: | |||
# | |||
# The above copyright notice and this permission notice shall be included in | |||
# all copies or substantial portions of the Software. | |||
# | |||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
# THE SOFTWARE. | |||
# | |||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||
# | |||
# ----------------------------------------------------------------------------- | |||
# | |||
# Master resources file. | |||
header = """// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Resources definitions. | |||
// | |||
// Automatically generated with: | |||
// make resources | |||
""" | |||
namespace = 'marbles' | |||
target = 'marbles' | |||
types = ['uint8_t', 'uint16_t'] | |||
includes = """ | |||
#include "stmlib/stmlib.h" | |||
""" | |||
import lookup_tables | |||
create_specialized_manager = True | |||
resources = [ | |||
(lookup_tables.lookup_tables, | |||
'lookup_table', 'LUT', 'float', float, False), | |||
(lookup_tables.distributions, | |||
'distributions', 'DIST', 'float', float, False) | |||
] |
@@ -0,0 +1,149 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Record a note CV distribution - to be used for the quantizer. | |||
#ifndef MARBLES_SCALE_RECORDER_H_ | |||
#define MARBLES_SCALE_RECORDER_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "marbles/random/quantizer.h" | |||
namespace marbles { | |||
class ScaleRecorder { | |||
public: | |||
ScaleRecorder() { } | |||
~ScaleRecorder() { } | |||
struct Degree { | |||
float average_voltage; | |||
float total_voltage; | |||
float count; | |||
bool operator< (const Degree& rhs) const { | |||
return average_voltage < rhs.average_voltage; | |||
} | |||
}; | |||
void Init() { | |||
Clear(); | |||
} | |||
void Clear() { | |||
num_degrees_ = 0; | |||
current_voltage_ = 0.0f; | |||
total_count_ = 0.0f; | |||
} | |||
void NewNote(float v) { | |||
current_voltage_ = v; | |||
} | |||
void UpdateVoltage(float v) { | |||
ONE_POLE(current_voltage_, v, 0.01f); | |||
} | |||
void AcceptNote() { | |||
const float base_interval = 1.0f; | |||
float v = current_voltage_; | |||
while (v < 0.0f) { | |||
v += base_interval; | |||
} | |||
float octave = static_cast<float>( | |||
static_cast<int>(v / base_interval)) * base_interval; | |||
v -= octave; | |||
int nearest_degree = -1; | |||
for (int i = 0; i < num_degrees_; ++i) { | |||
float av = degrees_[i].average_voltage; | |||
const float tolerance = 1.0f / 36.0f; | |||
if (fabsf(v - av) < tolerance) { | |||
nearest_degree = i; | |||
break; | |||
} | |||
if (fabsf((v - base_interval) - av) < tolerance) { | |||
v -= base_interval; | |||
nearest_degree = i; | |||
break; | |||
} | |||
} | |||
if (nearest_degree == -1 && num_degrees_ != kMaxDegrees) { | |||
nearest_degree = num_degrees_; | |||
Degree* d = °rees_[nearest_degree]; | |||
d->total_voltage = 0.0f; | |||
d->average_voltage = 0.0f; | |||
d->count = 0.0f; | |||
++num_degrees_; | |||
} | |||
if (nearest_degree != -1) { | |||
Degree* d = °rees_[nearest_degree]; | |||
d->total_voltage += v; | |||
d->count += 1.0f; | |||
d->average_voltage = d->total_voltage / d->count; | |||
total_count_ += 1.0f; | |||
} | |||
} | |||
bool ExtractScale(Scale* scale) { | |||
if (num_degrees_ < 2) { | |||
return false; | |||
} | |||
std::sort(°rees_[0], °rees_[num_degrees_]); | |||
float max_count = 0.0f; | |||
for (int i = 0; i < num_degrees_; ++i) { | |||
max_count = std::max(degrees_[i].count, max_count); | |||
} | |||
scale->base_interval = 1.0f; | |||
scale->num_degrees = num_degrees_; | |||
for (int i = 0; i < num_degrees_; ++i) { | |||
Degree* d = °rees_[i]; | |||
scale->degree[i].voltage = d->average_voltage; | |||
scale->degree[i].weight = static_cast<uint8_t>( | |||
255.0f * d->count / max_count); | |||
if (scale->degree[i].weight == 0) { | |||
++scale->degree[i].weight; | |||
} | |||
} | |||
return true; | |||
} | |||
private: | |||
int num_degrees_; | |||
float current_voltage_; | |||
float total_count_; | |||
Degree degrees_[kMaxDegrees]; | |||
DISALLOW_COPY_AND_ASSIGN(ScaleRecorder); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_SCALE_RECORDER_H_ |
@@ -0,0 +1,246 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Settings storage. | |||
#include "marbles/settings.h" | |||
#include <algorithm> | |||
#include "stmlib/system/storage.h" | |||
namespace marbles { | |||
using namespace std; | |||
const Scale preset_scales[6] = { | |||
// C major | |||
{ | |||
1.0f, | |||
12, | |||
{ | |||
{ 0.0000f, 255 }, // C | |||
{ 0.0833f, 16 }, // C# | |||
{ 0.1667f, 96 }, // D | |||
{ 0.2500f, 24 }, // D# | |||
{ 0.3333f, 128 }, // E | |||
{ 0.4167f, 64 }, // F | |||
{ 0.5000f, 8 }, // F# | |||
{ 0.5833f, 192 }, // G | |||
{ 0.6667f, 16 }, // G# | |||
{ 0.7500f, 96 }, // A | |||
{ 0.8333f, 24 }, // A# | |||
{ 0.9167f, 128 }, // B | |||
} | |||
}, | |||
// C minor | |||
{ | |||
1.0f, | |||
12, | |||
{ | |||
{ 0.0000f, 255 }, // C | |||
{ 0.0833f, 16 }, // C# | |||
{ 0.1667f, 96 }, // D | |||
{ 0.2500f, 128 }, // Eb | |||
{ 0.3333f, 8 }, // E | |||
{ 0.4167f, 64 }, // F | |||
{ 0.5000f, 4 }, // F# | |||
{ 0.5833f, 192 }, // G | |||
{ 0.6667f, 96 }, // G# | |||
{ 0.7500f, 16 }, // A | |||
{ 0.8333f, 128 }, // Bb | |||
{ 0.9167f, 16 }, // B | |||
} | |||
}, | |||
// Pentatonic | |||
{ | |||
1.0f, | |||
12, | |||
{ | |||
{ 0.0000f, 255 }, // C | |||
{ 0.0833f, 4 }, // C# | |||
{ 0.1667f, 96 }, // D | |||
{ 0.2500f, 4 }, // Eb | |||
{ 0.3333f, 4 }, // E | |||
{ 0.4167f, 140 }, // F | |||
{ 0.5000f, 4 }, // F# | |||
{ 0.5833f, 192 }, // G | |||
{ 0.6667f, 4 }, // G# | |||
{ 0.7500f, 96 }, // A | |||
{ 0.8333f, 4 }, // Bb | |||
{ 0.9167f, 4 }, // B | |||
} | |||
}, | |||
// Pelog | |||
{ | |||
1.0f, | |||
7, | |||
{ | |||
{ 0.0000f, 255 }, // C | |||
{ 0.1275f, 128 }, // Db+ | |||
{ 0.2625f, 32 }, // Eb- | |||
{ 0.4600f, 8 }, // F#- | |||
{ 0.5883f, 192 }, // G | |||
{ 0.7067f, 64 }, // Ab | |||
{ 0.8817f, 16 }, // Bb+ | |||
} | |||
}, | |||
// Raag Bhairav That | |||
{ | |||
1.0f, | |||
12, | |||
{ | |||
{ 0.0000f, 255 }, // ** Sa | |||
{ 0.0752f, 128 }, // ** Komal Re | |||
{ 0.1699f, 4 }, // Re | |||
{ 0.2630f, 4 }, // Komal Ga | |||
{ 0.3219f, 128 }, // ** Ga | |||
{ 0.4150f, 64 }, // ** Ma | |||
{ 0.4918f, 4 }, // Tivre Ma | |||
{ 0.5850f, 192 }, // ** Pa | |||
{ 0.6601f, 64 }, // ** Komal Dha | |||
{ 0.7549f, 4 }, // Dha | |||
{ 0.8479f, 4 }, // Komal Ni | |||
{ 0.9069f, 64 }, // ** Ni | |||
} | |||
}, | |||
// Raag Shri | |||
{ | |||
1.0f, | |||
12, | |||
{ | |||
{ 0.0000f, 255 }, // ** Sa | |||
{ 0.0752f, 4 }, // Komal Re | |||
{ 0.1699f, 128 }, // ** Re | |||
{ 0.2630f, 64 }, // ** Komal Ga | |||
{ 0.3219f, 4 }, // Ga | |||
{ 0.4150f, 128 }, // ** Ma | |||
{ 0.4918f, 4 }, // Tivre Ma | |||
{ 0.5850f, 192 }, // ** Pa | |||
{ 0.6601f, 4 }, // Komal Dha | |||
{ 0.7549f, 64 }, // ** Dha | |||
{ 0.8479f, 128 }, // ** Komal Ni | |||
{ 0.9069f, 4 }, // Ni | |||
} | |||
}, | |||
}; | |||
#define FIX_OUTLIER(destination, expected_value) if (fabsf(destination / expected_value - 1.0f) > 0.1f) { destination = expected_value; } | |||
void Settings::ResetScale(int i) { | |||
persistent_data_.scale[i] = preset_scales[i]; | |||
} | |||
void Settings::Init() { | |||
freshly_baked_ = false; | |||
// Set default values for all calibration and state settings. | |||
// This settings will be written to flash memory the first time the module | |||
// is powered on, or if corrupted data is found in the flash sector, | |||
// following a major firmware upgrade. | |||
CalibrationData& c = persistent_data_.calibration_data; | |||
fill(&c.adc_scale[0], &c.adc_scale[ADC_CHANNEL_LAST], -2.0f); | |||
fill(&c.adc_offset[0], &c.adc_offset[ADC_CHANNEL_LAST], +1.0f); | |||
fill(&c.dac_scale[0], &c.dac_scale[DAC_CHANNEL_LAST], -6212.8f); | |||
fill(&c.dac_offset[0], &c.dac_offset[DAC_CHANNEL_LAST], 32768.0f); | |||
c.adc_offset[ADC_CHANNEL_T_RATE] = 60.0f; | |||
c.adc_scale[ADC_CHANNEL_T_RATE] = -120.0f; | |||
for (size_t i = 0; i < kNumScales; ++i) { | |||
ResetScale(i); | |||
} | |||
state_.t_deja_vu = 0; | |||
state_.t_model = 0; | |||
state_.t_range = 1; | |||
state_.t_pulse_width_mean = 128; | |||
state_.t_pulse_width_std = 0; | |||
state_.x_deja_vu = 0; | |||
state_.x_control_mode = 0; | |||
state_.x_register_mode = 0; | |||
state_.x_range = 2; | |||
state_.x_scale = 0; | |||
state_.y_spread = 128; | |||
state_.y_bias = 128; | |||
state_.y_steps = 0; | |||
state_.y_divider = 128; | |||
state_.y_range = 2; | |||
state_.color_blind = 0; | |||
freshly_baked_ = !chunk_storage_.Init(&persistent_data_, &state_); | |||
if (!freshly_baked_) { | |||
CONSTRAIN(state_.t_model, 0, 5); | |||
CONSTRAIN(state_.t_range, 0, 2); | |||
CONSTRAIN(state_.x_control_mode, 0, 2); | |||
CONSTRAIN(state_.x_range, 0, 2); | |||
CONSTRAIN(state_.x_scale, 0, 5); | |||
CONSTRAIN(state_.y_range, 0, 2); | |||
CalibrationData& c = persistent_data_.calibration_data; | |||
for (size_t i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
if (i == ADC_CHANNEL_T_RATE) { | |||
FIX_OUTLIER(c.adc_scale[i], -120.0f); | |||
FIX_OUTLIER(c.adc_offset[i], 60.0f); | |||
} else { | |||
FIX_OUTLIER(c.adc_scale[i], -2.0f); | |||
FIX_OUTLIER(c.adc_offset[i], +1.0f); | |||
} | |||
} | |||
for (size_t i = 0; i < DAC_CHANNEL_LAST; ++i) { | |||
FIX_OUTLIER(c.dac_scale[i], -6212.8f); | |||
FIX_OUTLIER(c.dac_offset[i], 32768.0f); | |||
} | |||
} | |||
} | |||
void Settings::SavePersistentData() { | |||
chunk_storage_.SavePersistentData(); | |||
} | |||
void Settings::SaveState() { | |||
chunk_storage_.SaveState(); | |||
} | |||
/* static */ | |||
void Settings::ProgramOptionBytes() { | |||
FLASH_Unlock(); | |||
FLASH_OB_Unlock(); | |||
FLASH_OB_BORConfig(OB_BOR_OFF); | |||
FLASH_OB_Launch(); | |||
FLASH_OB_Lock(); | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,152 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Settings storage. | |||
#ifndef MARBLES_SETTINGS_H_ | |||
#define MARBLES_SETTINGS_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/system/storage.h" | |||
#include "marbles/drivers/adc.h" | |||
#include "marbles/drivers/dac.h" | |||
#include "marbles/random/quantizer.h" | |||
namespace marbles { | |||
struct CalibrationData { | |||
float adc_offset[ADC_CHANNEL_LAST]; | |||
float adc_scale[ADC_CHANNEL_LAST]; | |||
float dac_offset[DAC_CHANNEL_LAST]; | |||
float dac_scale[DAC_CHANNEL_LAST]; | |||
}; | |||
const int kNumScales = 6; | |||
struct PersistentData { | |||
CalibrationData calibration_data; | |||
Scale scale[kNumScales]; | |||
uint8_t padding[16]; | |||
enum { tag = 0x494C4143 }; | |||
}; | |||
struct State { | |||
uint8_t t_deja_vu; | |||
uint8_t t_model; | |||
uint8_t t_range; | |||
uint8_t t_pulse_width_mean; | |||
uint8_t t_pulse_width_std; | |||
uint8_t x_deja_vu; | |||
uint8_t x_control_mode; | |||
uint8_t x_register_mode; | |||
uint8_t x_range; | |||
uint8_t x_scale; | |||
uint8_t y_spread; | |||
uint8_t y_bias; | |||
uint8_t y_steps; | |||
uint8_t y_divider; | |||
uint8_t y_range; | |||
uint8_t color_blind; | |||
uint8_t padding[8]; | |||
enum { tag = 0x54415453 }; | |||
}; | |||
class Settings { | |||
public: | |||
Settings() { } | |||
~Settings() { } | |||
void Init(); | |||
void SavePersistentData(); | |||
void SaveState(); | |||
void ResetScale(int i); | |||
static void ProgramOptionBytes(); | |||
inline const CalibrationData& calibration_data() { | |||
return persistent_data_.calibration_data; | |||
} | |||
inline CalibrationData* mutable_calibration_data() { | |||
return &persistent_data_.calibration_data; | |||
} | |||
inline const Scale& scale(int i) const { | |||
return persistent_data_.scale[i]; | |||
} | |||
inline Scale* mutable_scale(int i) { | |||
return &persistent_data_.scale[i]; | |||
} | |||
inline const State& state() const { | |||
return state_; | |||
} | |||
inline State* mutable_state() { | |||
return &state_; | |||
} | |||
inline const PersistentData& persistent_data() const { | |||
return persistent_data_; | |||
} | |||
inline bool freshly_baked() const { | |||
return freshly_baked_; | |||
} | |||
inline void set_dirty_scale_index(int i) { | |||
dirty_scale_index_ = i; | |||
} | |||
inline int dirty_scale_index() const { | |||
return dirty_scale_index_; | |||
} | |||
private: | |||
bool freshly_baked_; | |||
int dirty_scale_index_; | |||
PersistentData persistent_data_; | |||
State state_; | |||
stmlib::ChunkStorage<1, PersistentData, State> chunk_storage_; | |||
DISALLOW_COPY_AND_ASSIGN(Settings); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_SETTINGS_H_ |
@@ -0,0 +1,227 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// This program is free software: you can redistribute it and/or modify | |||
// it under the terms of the GNU General Public License as published by | |||
// the Free Software Foundation, either version 3 of the License, or | |||
// (at your option) any later version. | |||
// This program is distributed in the hope that it will be useful, | |||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
// GNU General Public License for more details. | |||
// You should have received a copy of the GNU General Public License | |||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
#ifndef MARBLES_TEST_FIXTURES_H_ | |||
#define MARBLES_TEST_FIXTURES_H_ | |||
#include <cstdlib> | |||
#include <vector> | |||
#include "marbles/ramp/ramp_divider.h" | |||
#include "marbles/ramp/ramp_extractor.h" | |||
const size_t kSampleRate = 32000; | |||
const size_t kAudioBlockSize = 8; | |||
namespace marbles { | |||
using namespace std; | |||
using namespace stmlib; | |||
class PulseGenerator { | |||
public: | |||
PulseGenerator() { | |||
counter_ = 0; | |||
previous_state_ = 0; | |||
} | |||
~PulseGenerator() { } | |||
void AddPulses(int total_duration, int on_duration, int num_repetitions) { | |||
Pulse p; | |||
p.total_duration = total_duration; | |||
p.on_duration = on_duration; | |||
p.num_repetitions = num_repetitions; | |||
pulses_.push_back(p); | |||
} | |||
void Render(GateFlags* clock, size_t size) { | |||
while (size--) { | |||
bool current_state = pulses_.size() && counter_ < pulses_[0].on_duration; | |||
++counter_; | |||
if (pulses_.size() && counter_ >= pulses_[0].total_duration) { | |||
counter_ = 0; | |||
--pulses_[0].num_repetitions; | |||
if (pulses_[0].num_repetitions == 0) { | |||
pulses_.erase(pulses_.begin()); | |||
} | |||
} | |||
previous_state_ = *clock++ = ExtractGateFlags(previous_state_, current_state); | |||
} | |||
} | |||
private: | |||
struct Pulse { | |||
int total_duration; | |||
int on_duration; | |||
int num_repetitions; | |||
}; | |||
int counter_; | |||
GateFlags previous_state_; | |||
vector<Pulse> pulses_; | |||
DISALLOW_COPY_AND_ASSIGN(PulseGenerator); | |||
}; | |||
enum PatternDifficulty { | |||
FRIENDLY_PATTERNS, | |||
FAST_PATTERNS, | |||
TRICKY_PATTERNS, | |||
PAUSE_PATTERNS, | |||
}; | |||
class ClockGeneratorPatterns { | |||
public: | |||
ClockGeneratorPatterns(PatternDifficulty difficulty) { | |||
if (difficulty == FRIENDLY_PATTERNS) { | |||
pulse_generator_.AddPulses(800, 400, 100); | |||
pulse_generator_.AddPulses(400, 32, 100); | |||
for (int i = 0; i < 15; ++i) { | |||
for (int j = 0; j < 5; ++j) { | |||
pulse_generator_.AddPulses(600 - j * 100, 3, 2); | |||
} | |||
} | |||
for (int i = 0; i < 300; ++i) { | |||
int t = 200 + (rand() % 400); | |||
pulse_generator_.AddPulses(t, t / 4, 1); | |||
} | |||
// Completely random clock. | |||
for (int i = 0; i < 400; ++i) { | |||
int t = 200 + (rand() % 800); | |||
int pw = t / 4 + (rand() % (t / 2)); | |||
pulse_generator_.AddPulses(t, pw, 1); | |||
} | |||
return; | |||
} else if (difficulty == FAST_PATTERNS) { | |||
pulse_generator_.AddPulses(32, 16, 100); | |||
pulse_generator_.AddPulses(16, 8, 100); | |||
pulse_generator_.AddPulses(12, 6, 100); | |||
pulse_generator_.AddPulses(8, 4, 100); | |||
pulse_generator_.AddPulses(6, 3, 100); | |||
pulse_generator_.AddPulses(4, 2, 100); | |||
pulse_generator_.AddPulses(8, 4, 100); | |||
pulse_generator_.AddPulses(12, 6, 100); | |||
return; | |||
} else if (difficulty == PAUSE_PATTERNS) { | |||
pulse_generator_.AddPulses(800, 400, 100); | |||
pulse_generator_.AddPulses(32000 * 5 + 10, 400, 1); | |||
pulse_generator_.AddPulses(800, 400, 100); | |||
} | |||
// Steady clock | |||
pulse_generator_.AddPulses(400, 200, 250); | |||
pulse_generator_.AddPulses(4000 + (rand() % 1000), 2000, 10); | |||
pulse_generator_.AddPulses(100, 10, 50); | |||
pulse_generator_.AddPulses(16, 5, 100); | |||
// Periodic clock with some jitter | |||
for (int i = 0; i < 50; ++i) { | |||
pulse_generator_.AddPulses(100, 10, 3); | |||
pulse_generator_.AddPulses(400 + (i % 4), 10, 1); | |||
pulse_generator_.AddPulses((i == 40) ? 40 : 300, 10, 1); | |||
} | |||
for (int i = 0; i < 50; ++i) { | |||
pulse_generator_.AddPulses(100 + (i % 10), 10, 3); | |||
pulse_generator_.AddPulses(200 + (i % 4), 10, 2); | |||
pulse_generator_.AddPulses(300, 10, 1); | |||
} | |||
// Really long pattern that hashes well | |||
for (int i = 0; i < 15; ++i) { | |||
for (int j = 0; j < 10; ++j) { | |||
pulse_generator_.AddPulses(100 + j * 30, 10, 1); | |||
} | |||
} | |||
for (int i = 0; i < 15; ++i) { | |||
for (int j = 0; j < 6; ++j) { | |||
pulse_generator_.AddPulses(300 - j * 50, 3, 2); | |||
} | |||
} | |||
// Random clock with reliable pulse width | |||
for (int i = 0; i < 300; ++i) { | |||
int t = 100 + (rand() % 400); | |||
pulse_generator_.AddPulses(t, t / 4, 1); | |||
} | |||
// Completely random clock. | |||
for (int i = 0; i < 400; ++i) { | |||
int t = 100 + (rand() % 400); | |||
int pw = t / 4 + (rand() % (t / 2)); | |||
pulse_generator_.AddPulses(t, pw, 1); | |||
} | |||
} | |||
~ClockGeneratorPatterns() { } | |||
void Render(size_t size) { | |||
pulse_generator_.Render(buffer_, size); | |||
} | |||
GateFlags* clock() { return buffer_; } | |||
private: | |||
GateFlags buffer_[kAudioBlockSize]; | |||
PulseGenerator pulse_generator_; | |||
DISALLOW_COPY_AND_ASSIGN(ClockGeneratorPatterns); | |||
}; | |||
class MasterSlaveRampGenerator { | |||
public: | |||
MasterSlaveRampGenerator() { | |||
ramp_extractor_.Init(4000.0f / kSampleRate); | |||
ramp_divider_[0].Init(); | |||
ramp_divider_[1].Init(); | |||
} | |||
~MasterSlaveRampGenerator() { } | |||
void Process(const GateFlags* clock, size_t size) { | |||
Ratio r = { 1, 1 }; | |||
ramp_extractor_.Process(r, true, clock, master_ramp_, size); | |||
r.q = 2; | |||
ramp_divider_[0].Process(r, master_ramp_, slave_ramp_1_, size); | |||
r.p = 1; r.q = 2; | |||
ramp_divider_[1].Process(r, master_ramp_, slave_ramp_2_, size); | |||
} | |||
Ramps ramps() { | |||
Ramps r; | |||
r.external = external_ramp_; | |||
r.master = master_ramp_; | |||
r.slave[0] = slave_ramp_1_; | |||
r.slave[1] = slave_ramp_2_; | |||
return r; | |||
} | |||
float* master_ramp() { return master_ramp_; } | |||
float* slave_ramp_1() { return slave_ramp_1_; } | |||
float* slave_ramp_2() { return slave_ramp_2_; } | |||
private: | |||
RampExtractor ramp_extractor_; | |||
RampDivider ramp_divider_[2]; | |||
float external_ramp_[kAudioBlockSize]; | |||
float master_ramp_[kAudioBlockSize]; | |||
float slave_ramp_1_[kAudioBlockSize]; | |||
float slave_ramp_2_[kAudioBlockSize]; | |||
DISALLOW_COPY_AND_ASSIGN(MasterSlaveRampGenerator); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_TEST_FIXTURES_H_ |
@@ -0,0 +1,50 @@ | |||
PACKAGES = marbles/test stmlib/utils marbles/ramp marbles/random marbles stmlib/dsp | |||
VPATH = $(PACKAGES) | |||
TARGET = marbles_test | |||
BUILD_ROOT = build/ | |||
BUILD_DIR = $(BUILD_ROOT)$(TARGET)/ | |||
CC_FILES = marbles_test.cc \ | |||
lag_processor.cc \ | |||
output_channel.cc \ | |||
quantizer.cc \ | |||
discrete_distribution_quantizer.cc \ | |||
ramp_extractor.cc \ | |||
random.cc \ | |||
resources.cc \ | |||
units.cc \ | |||
t_generator.cc \ | |||
x_y_generator.cc | |||
OBJ_FILES = $(CC_FILES:.cc=.o) | |||
OBJS = $(patsubst %,$(BUILD_DIR)%,$(OBJ_FILES)) $(STARTUP_OBJ) | |||
DEPS = $(OBJS:.o=.d) | |||
DEP_FILE = $(BUILD_DIR)depends.mk | |||
all: marbles_test | |||
$(BUILD_DIR): | |||
mkdir -p $(BUILD_DIR) | |||
$(BUILD_DIR)%.o: %.cc | |||
g++ -c -DTEST -g -Wall -Werror -msse2 -Wno-unused-variable -O2 -I. $< -o $@ | |||
$(BUILD_DIR)%.d: %.cc | |||
g++ -MM -DTEST -I. $< -MF $@ -MT $(@:.d=.o) | |||
marbles_test: $(OBJS) | |||
g++ -g -o $(TARGET) $(OBJS) -Wl,-no_pie -lm -lprofiler -L/opt/local/lib | |||
depends: $(DEPS) | |||
cat $(DEPS) > $(DEP_FILE) | |||
$(DEP_FILE): $(BUILD_DIR) $(DEPS) | |||
cat $(DEPS) > $(DEP_FILE) | |||
profile: marbles_test | |||
env CPUPROFILE_FREQUENCY=1000 CPUPROFILE=$(BUILD_DIR)/marbles.prof ./marbles_test && pprof --pdf ./marbles_test $(BUILD_DIR)/marbles.prof > profile.pdf && open profile.pdf | |||
clean: | |||
rm $(BUILD_DIR)*.* | |||
include $(DEP_FILE) |
@@ -0,0 +1,721 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// This program is free software: you can redistribute it and/or modify | |||
// it under the terms of the GNU General Public License as published by | |||
// the Free Software Foundation, either version 3 of the License, or | |||
// (at your option) any later version. | |||
// This program is distributed in the hope that it will be useful, | |||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
// GNU General Public License for more details. | |||
// You should have received a copy of the GNU General Public License | |||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
#include "marbles/cv_reader_channel.h" | |||
#include "marbles/note_filter.h" | |||
#include "marbles/ramp/ramp_divider.h" | |||
#include "marbles/ramp/ramp_extractor.h" | |||
#include "marbles/random/distributions.h" | |||
#include "marbles/random/output_channel.h" | |||
#include "marbles/random/random_generator.h" | |||
#include "marbles/random/random_sequence.h" | |||
#include "marbles/random/random_stream.h" | |||
#include "marbles/random/t_generator.h" | |||
#include "marbles/random/x_y_generator.h" | |||
#include "marbles/scale_recorder.h" | |||
#include "marbles/test/fixtures.h" | |||
#include "marbles/test/ramp_checker.h" | |||
#include "stmlib/test/wav_writer.h" | |||
#include "stmlib/utils/random.h" | |||
using namespace marbles; | |||
using namespace std; | |||
using namespace stmlib; | |||
void TestBetaDistribution() { | |||
// Plot result with: | |||
// import numpy | |||
// import pylab | |||
// data = numpy.loadtxt('marbles_histograms.txt') | |||
// n = 0 | |||
// for i in xrange(9): | |||
// for j in xrange(13): | |||
// pylab.subplot(9, 13, n + 1) | |||
// pylab.plot(data[(n * 101):((n + 1) * 101)]) | |||
// pylab.gca().get_xaxis().set_visible(False) | |||
// pylab.gca().get_yaxis().set_visible(False) | |||
// n += 1 | |||
// pylab.show() | |||
FILE* fp = fopen("marbles_histograms.txt", "w"); | |||
for (int i = 0; i < 9; ++i) { | |||
for (int j = 0; j < 13; ++j) { | |||
float bias = float(i) / 8.0f; | |||
float range = float(j) / 12.0f; | |||
vector<int> histogram(101); | |||
for (int n = 0; n < 1000000; ++n) { | |||
float value = BetaDistributionSample(Random::GetFloat(), range, bias); | |||
histogram[int(value * 100.0f)]++; | |||
} | |||
for (int n = 0; n < 101; ++n) { | |||
fprintf(fp, "%d\n", histogram[n]); | |||
} | |||
} | |||
} | |||
fclose(fp); | |||
} | |||
void TestQuantizer() { | |||
// Plot result with: | |||
// import numpy | |||
// import pylab | |||
// | |||
// data = numpy.loadtxt('marbles_quantizer.txt') | |||
// pylab.figure(figsize=(25,5)) | |||
// for i in xrange(9): | |||
// pylab.subplot(1, 9, i + 1) | |||
// indices = numpy.where(data[:, 0] == i)[0] | |||
// pylab.plot(data[indices, 1], data[indices, 2]) | |||
// pylab.show() | |||
FILE* fp = fopen("marbles_quantizer.txt", "w"); | |||
Quantizer q; | |||
Scale scale; | |||
scale.InitMajor(); | |||
q.Init(scale); | |||
for (int i = 0; i <= 8; ++i) { | |||
float amount = float(i) / 8.0f; | |||
for (int j = 0; j <= 4000; ++j) { | |||
float value = j / 1000.0f - 2.0f; | |||
fprintf(fp, "%d %f %f\n", i, value, q.Process(value, amount, false)); | |||
} | |||
} | |||
fclose(fp); | |||
} | |||
void TestQuantizerNoise() { | |||
// Plot result with: | |||
// import numpy | |||
// import pylab | |||
// | |||
// data = numpy.loadtxt('marbles_quantizer_hysteresis.txt') | |||
// pylab.plot(data) | |||
// pylab.show() | |||
FILE* fp = fopen("marbles_quantizer_hysteresis.txt", "w"); | |||
Quantizer q; | |||
Scale scale; | |||
scale.InitTenth(); | |||
q.Init(scale); | |||
for (int j = 0; j <= 4000; ++j) { | |||
float noise = (rand() % 500) / 250.0f - 1.0f; | |||
float tri = j / 2000.0f; | |||
if (tri >= 1.0f) tri = 2.0f - tri; | |||
float value = 1.0f * tri + noise * 1.0f / 60.0f; | |||
float result = q.Process(value, 0.18f, true); | |||
fprintf(fp, "%f %f\n", value, result); | |||
} | |||
fclose(fp); | |||
} | |||
void TestRampExtractorClockBug() { | |||
WavWriter wav_writer(2, ::kSampleRate, 20); | |||
wav_writer.Open("marbles_ramp_extractor_clock_bug.wav"); | |||
RandomGenerator random_generator; | |||
RandomStream random_stream; | |||
random_generator.Init(33); | |||
random_stream.Init(&random_generator); | |||
PulseGenerator pulse_generator; | |||
for (size_t i = 0; i < 700; ++i) { | |||
int t = (rand() % 8) + 796; | |||
pulse_generator.AddPulses(t, t >> 1, 1); | |||
} | |||
TGenerator generator; | |||
generator.Init(&random_stream, kSampleRate); | |||
generator.set_model(T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI); | |||
generator.set_rate(0.0f); | |||
generator.set_pulse_width_mean(0.5f); | |||
generator.set_pulse_width_std(0.0f); | |||
generator.set_bias(0.5f); | |||
generator.set_jitter(0.0f); | |||
generator.set_deja_vu(0.0f); | |||
generator.set_length(8); | |||
generator.set_range(T_GENERATOR_RANGE_1X); | |||
MasterSlaveRampGenerator ms_ramp_generator; | |||
Ramps ramps = ms_ramp_generator.ramps(); | |||
float phase = 0.0f; | |||
for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
phase += 0.0001f; | |||
if (phase >= 1.0f) { | |||
phase -= 1.0f; | |||
} | |||
float tri = phase < 0.5f ? 2.0f * phase : 2.0f - 2.0f * phase; | |||
bool gate[kAudioBlockSize * 2]; | |||
GateFlags clock[kAudioBlockSize]; | |||
pulse_generator.Render(clock, kAudioBlockSize); | |||
generator.set_rate(tri * 24.0f - 12.0f); | |||
generator.Process( | |||
true, | |||
clock, | |||
ramps, | |||
gate, | |||
kAudioBlockSize); | |||
for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
float s[6]; | |||
s[0] = clock[j] ? 1.0f : 0.0f; | |||
s[1] = ramps.external[j]; | |||
wav_writer.Write(s, 2, 32767.0f); | |||
} | |||
} | |||
} | |||
void TestRampExtractorPause() { | |||
WavWriter wav_writer(6, ::kSampleRate, 10); | |||
wav_writer.Open("marbles_ramp_pause.wav"); | |||
RandomGenerator random_generator; | |||
RandomStream random_stream; | |||
random_generator.Init(33); | |||
random_stream.Init(&random_generator); | |||
TGenerator generator; | |||
generator.Init(&random_stream, kSampleRate); | |||
generator.set_model(T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI); | |||
generator.set_rate(0.0f); | |||
generator.set_pulse_width_mean(0.0f); | |||
generator.set_pulse_width_std(0.0f); | |||
generator.set_bias(0.9f); | |||
generator.set_jitter(0.0f); | |||
generator.set_deja_vu(0.0f); | |||
generator.set_length(8); | |||
generator.set_range(T_GENERATOR_RANGE_0_25X); | |||
ClockGeneratorPatterns patterns(PAUSE_PATTERNS); | |||
MasterSlaveRampGenerator ms_ramp_generator; | |||
Ramps ramps = ms_ramp_generator.ramps(); | |||
for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
bool gate[kAudioBlockSize * 2]; | |||
patterns.Render(kAudioBlockSize); | |||
generator.Process( | |||
true, | |||
patterns.clock(), | |||
ramps, | |||
gate, | |||
kAudioBlockSize); | |||
for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
float s[6]; | |||
s[5] = s[0] = patterns.clock()[j] & GATE_FLAG_HIGH ? 0.8f : 0.0f; | |||
s[1] = ramps.external[j]; | |||
s[2] = ramps.master[j]; | |||
s[3] = ramps.slave[0][j]; | |||
s[4] = ramps.slave[1][j]; | |||
wav_writer.Write(s, 6, 32767.0f); | |||
} | |||
} | |||
} | |||
void TestRampDivider(PatternDifficulty difficulty, const char* file_name) { | |||
WavWriter wav_writer(4, ::kSampleRate, 10); | |||
wav_writer.Open(file_name); | |||
ClockGeneratorPatterns patterns(difficulty); | |||
RampExtractor ramp_extractor; | |||
RampDivider ramp_divider; | |||
RampDivider ramp_divider_double; | |||
RampDivider ramp_divider_half; | |||
ramp_extractor.Init(0.25f); | |||
ramp_divider.Init(); | |||
ramp_divider_double.Init(); | |||
ramp_divider_half.Init(); | |||
RampChecker ramp_checker[4]; | |||
Ratio r8x; | |||
r8x.p = 8; | |||
r8x.q = 1; | |||
Ratio r2x; | |||
r2x.p = 2; | |||
r2x.q = 1; | |||
Ratio half; | |||
half.p = 1; | |||
half.q = 2; | |||
Ratio one; | |||
one.p = 1; | |||
one.q = 1; | |||
for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
float ramp[kAudioBlockSize]; | |||
float divided_ramp[kAudioBlockSize]; | |||
float divided_ramp_double[kAudioBlockSize]; | |||
float divided_ramp_half[kAudioBlockSize]; | |||
// switch ((i / (kSampleRate / 4)) % 3) { | |||
// case 0: | |||
// ramp_divider.set_ratio(1, 2); | |||
// break; | |||
// case 1: | |||
// ramp_divider.set_ratio(4, 1); | |||
// break; | |||
// case 2: | |||
// ramp_divider.set_ratio(3, 2); | |||
// break; | |||
// } | |||
patterns.Render(kAudioBlockSize); | |||
ramp_extractor.Process(one, true, patterns.clock(), ramp, kAudioBlockSize); | |||
ramp_divider.Process(r8x, ramp, divided_ramp, kAudioBlockSize); | |||
ramp_divider_double.Process(r2x, divided_ramp, divided_ramp_double, kAudioBlockSize); | |||
ramp_divider_half.Process(half, divided_ramp, divided_ramp_half, kAudioBlockSize); | |||
for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
float s[4]; | |||
s[0] = ramp[j]; | |||
s[1] = divided_ramp[j]; | |||
s[2] = divided_ramp_double[j]; | |||
s[3] = divided_ramp_half[j]; | |||
wav_writer.Write(s, 4, 32767.0f); | |||
} | |||
ramp_checker[0].Check(ramp, kAudioBlockSize); | |||
ramp_checker[1].Check(divided_ramp, kAudioBlockSize); | |||
ramp_checker[2].Check(divided_ramp_double, kAudioBlockSize); | |||
ramp_checker[3].Check(divided_ramp_half, kAudioBlockSize); | |||
} | |||
} | |||
void TestOutputChannel() { | |||
WavWriter wav_writer(2, ::kSampleRate, 3); | |||
wav_writer.Open("marbles_random_voltage.wav"); | |||
RampExtractor ramp_extractor; | |||
RandomGenerator random_generator; | |||
RandomStream random_stream; | |||
RandomSequence random_sequence; | |||
OutputChannel output_channel; | |||
PulseGenerator pulse_generator; | |||
ramp_extractor.Init(0.25f); | |||
random_generator.Init(32); | |||
random_stream.Init(&random_generator); | |||
random_sequence.Init(&random_stream); | |||
output_channel.Init(); | |||
// Steady clock | |||
pulse_generator.AddPulses(400, 10, 250); | |||
pulse_generator.AddPulses(200, 10, 250); | |||
output_channel.set_register_mode(false); | |||
output_channel.set_steps(0.5f); | |||
output_channel.set_bias(0.5f); | |||
Ratio one; | |||
one.p = 1; | |||
one.q = 1; | |||
for (size_t i = 0; i < ::kSampleRate * 3; i += kAudioBlockSize) { | |||
GateFlags gate_flags[kAudioBlockSize]; | |||
float ramp[kAudioBlockSize]; | |||
float voltage[kAudioBlockSize]; | |||
pulse_generator.Render(gate_flags, kAudioBlockSize); | |||
ramp_extractor.Process(one, false, gate_flags, ramp, kAudioBlockSize); | |||
output_channel.set_spread(wav_writer.triangle(3)); | |||
output_channel.Process(&random_sequence, ramp, voltage, kAudioBlockSize, 1); | |||
for (size_t j = 0; j < kAudioBlockSize; ++j) voltage[j] *= 0.1f; | |||
wav_writer.Write(ramp, voltage, kAudioBlockSize); | |||
} | |||
} | |||
void TestXYGenerator() { | |||
WavWriter wav_writer(4, ::kSampleRate, 10); | |||
wav_writer.Open("marbles_xy.wav"); | |||
RandomGenerator random_generator; | |||
RandomStream random_stream; | |||
random_generator.Init(32); | |||
random_stream.Init(&random_generator); | |||
XYGenerator generator; | |||
generator.Init(&random_stream, ::kSampleRate); | |||
GroupSettings x_settings, y_settings; | |||
x_settings.control_mode = CONTROL_MODE_IDENTICAL; | |||
x_settings.voltage_range = VOLTAGE_RANGE_FULL; | |||
x_settings.register_mode = false; | |||
x_settings.register_value = 0.0f; | |||
x_settings.spread = 0.8f; | |||
x_settings.bias = 0.8f; | |||
x_settings.steps = 0.3f; | |||
x_settings.deja_vu = 0.0f; | |||
x_settings.length = 8; | |||
x_settings.ratio.p = 1; | |||
x_settings.ratio.q = 1; | |||
y_settings.control_mode = CONTROL_MODE_IDENTICAL; | |||
y_settings.voltage_range = VOLTAGE_RANGE_FULL; | |||
y_settings.register_mode = false; | |||
y_settings.register_value = 0.0f; | |||
y_settings.spread = 0.5f; | |||
y_settings.bias = 0.5f; | |||
y_settings.steps = 0.1f; | |||
y_settings.deja_vu = 0.0f; | |||
y_settings.length = 8; | |||
y_settings.ratio.p = 1; | |||
y_settings.ratio.q = 8; | |||
ClockGeneratorPatterns patterns(TRICKY_PATTERNS); | |||
MasterSlaveRampGenerator ms_ramp_generator; | |||
for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
patterns.Render(kAudioBlockSize); | |||
ms_ramp_generator.Process(patterns.clock(), kAudioBlockSize); | |||
float samples[kAudioBlockSize * 4]; | |||
generator.Process( | |||
CLOCK_SOURCE_INTERNAL_T1_T2_T3, | |||
x_settings, | |||
y_settings, | |||
patterns.clock(), | |||
ms_ramp_generator.ramps(), | |||
samples, | |||
kAudioBlockSize); | |||
wav_writer.Write(samples, kAudioBlockSize * 4, 3276.7f); | |||
} | |||
} | |||
void TestXYGeneratorASR() { | |||
WavWriter wav_writer(4, ::kSampleRate, 10); | |||
wav_writer.Open("marbles_xy_asr.wav"); | |||
RandomGenerator random_generator; | |||
RandomStream random_stream; | |||
random_generator.Init(32); | |||
random_stream.Init(&random_generator); | |||
XYGenerator generator; | |||
generator.Init(&random_stream, ::kSampleRate); | |||
GroupSettings x_settings, y_settings; | |||
x_settings.control_mode = CONTROL_MODE_IDENTICAL; | |||
x_settings.voltage_range = VOLTAGE_RANGE_FULL; | |||
x_settings.register_mode = false; | |||
x_settings.register_value = 0.0f; | |||
x_settings.spread = 0.2f; | |||
x_settings.bias = 0.7f; | |||
x_settings.steps = 0.5f; | |||
x_settings.deja_vu = 0.0f; | |||
x_settings.length = 8; | |||
x_settings.ratio.p = 1; | |||
x_settings.ratio.q = 1; | |||
y_settings.control_mode = CONTROL_MODE_IDENTICAL; | |||
y_settings.voltage_range = VOLTAGE_RANGE_FULL; | |||
y_settings.register_mode = false; | |||
y_settings.register_value = 0.0f; | |||
y_settings.spread = 0.5f; | |||
y_settings.bias = 0.5f; | |||
y_settings.steps = 0.1f; | |||
y_settings.deja_vu = 0.0f; | |||
y_settings.length = 8; | |||
y_settings.ratio.p = 1; | |||
y_settings.ratio.q = 8; | |||
ClockGeneratorPatterns patterns(TRICKY_PATTERNS); | |||
MasterSlaveRampGenerator ms_ramp_generator; | |||
for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
patterns.Render(kAudioBlockSize); | |||
ms_ramp_generator.Process(patterns.clock(), kAudioBlockSize); | |||
float samples[kAudioBlockSize * 4]; | |||
x_settings.register_mode = true; | |||
x_settings.register_value = wav_writer.triangle(1); | |||
generator.Process( | |||
CLOCK_SOURCE_EXTERNAL, | |||
x_settings, | |||
y_settings, | |||
patterns.clock(), | |||
ms_ramp_generator.ramps(), | |||
samples, | |||
kAudioBlockSize); | |||
wav_writer.Write(samples, kAudioBlockSize * 4, 3276.7f); | |||
} | |||
} | |||
void TestTGenerator() { | |||
WavWriter wav_writer(6, ::kSampleRate, 10); | |||
wav_writer.Open("marbles_t.wav"); | |||
RandomGenerator random_generator; | |||
RandomStream random_stream; | |||
random_generator.Init(33); | |||
random_stream.Init(&random_generator); | |||
TGenerator generator; | |||
generator.Init(&random_stream, kSampleRate); | |||
generator.set_model(T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI); | |||
generator.set_rate(24.0f); | |||
generator.set_pulse_width_mean(0.0f); | |||
generator.set_pulse_width_std(0.0f); | |||
generator.set_bias(0.9f); | |||
generator.set_jitter(0.5f); | |||
generator.set_deja_vu(0.0f); | |||
generator.set_length(8); | |||
generator.set_range(T_GENERATOR_RANGE_4X); | |||
ClockGeneratorPatterns patterns(FRIENDLY_PATTERNS); | |||
MasterSlaveRampGenerator ms_ramp_generator; | |||
Ramps ramps = ms_ramp_generator.ramps(); | |||
RampChecker ramp_checker[4]; | |||
for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
bool gate[kAudioBlockSize * 2]; | |||
patterns.Render(kAudioBlockSize); | |||
generator.Process( | |||
true, | |||
patterns.clock(), | |||
ramps, | |||
gate, | |||
kAudioBlockSize); | |||
if (i >= kSampleRate * 5) { | |||
// generator.set_deja_vu(1.0f); | |||
// generator.set_jitter(0.5f); | |||
} | |||
for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
float s[6]; | |||
s[0] = ramps.external[j]; | |||
s[1] = ramps.master[j]; | |||
s[2] = ramps.slave[0][j]; | |||
s[3] = ramps.slave[1][j]; | |||
s[4] = gate[j * 2] ? 1.0f : 0.0f; | |||
s[5] = gate[j * 2 + 1] ? 1.0f : 0.0f; | |||
wav_writer.Write(s, 6, 32767.0f); | |||
} | |||
ramp_checker[0].Check(ramps.external, kAudioBlockSize); | |||
ramp_checker[1].Check(ramps.master, kAudioBlockSize); | |||
ramp_checker[2].Check(ramps.slave[0], kAudioBlockSize); | |||
ramp_checker[3].Check(ramps.slave[1], kAudioBlockSize); | |||
} | |||
} | |||
void TestTGeneratorSuperFastClock() { | |||
WavWriter wav_writer(6, ::kSampleRate, 10); | |||
wav_writer.Open("marbles_t_super_fast.wav"); | |||
RandomGenerator random_generator; | |||
RandomStream random_stream; | |||
random_generator.Init(33); | |||
random_stream.Init(&random_generator); | |||
TGenerator generator; | |||
generator.Init(&random_stream, kSampleRate); | |||
generator.set_model(T_GENERATOR_MODEL_COMPLEMENTARY_BERNOULLI); | |||
generator.set_rate(60.0f); | |||
generator.set_pulse_width_mean(0.0f); | |||
generator.set_pulse_width_std(0.0f); | |||
generator.set_bias(0.5f); | |||
generator.set_jitter(0.0f); | |||
generator.set_deja_vu(0.0f); | |||
generator.set_length(8); | |||
generator.set_range(T_GENERATOR_RANGE_4X); | |||
ClockGeneratorPatterns patterns(FAST_PATTERNS); | |||
MasterSlaveRampGenerator ms_ramp_generator; | |||
Ramps ramps = ms_ramp_generator.ramps(); | |||
for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
bool gate[kAudioBlockSize * 2]; | |||
patterns.Render(kAudioBlockSize); | |||
generator.Process( | |||
true, | |||
patterns.clock(), | |||
ramps, | |||
gate, | |||
kAudioBlockSize); | |||
for (size_t j = 0; j < kAudioBlockSize; ++j) { | |||
float s[6]; | |||
s[0] = ramps.external[j]; | |||
s[1] = ramps.master[j]; | |||
s[2] = ramps.slave[0][j]; | |||
s[3] = ramps.slave[1][j]; | |||
s[4] = gate[j * 2] ? 1.0f : 0.0f; | |||
s[5] = gate[j * 2 + 1] ? 1.0f : 0.0f; | |||
wav_writer.Write(s, 6, 32767.0f); | |||
} | |||
} | |||
} | |||
void TestTGeneratorRampIntegrity( | |||
bool internal_clock, | |||
float rate, | |||
TGeneratorModel model, | |||
float jitter) { | |||
printf("Testing ramp integrity for intclock = %d\trate = %04.3f\tmodel = %d\tjitter = %04.3f\n", internal_clock, rate, model, jitter); | |||
RandomGenerator random_generator; | |||
RandomStream random_stream; | |||
random_generator.Init(33); | |||
random_stream.Init(&random_generator); | |||
TGenerator generator; | |||
generator.Init(&random_stream, kSampleRate); | |||
generator.set_model(model); | |||
generator.set_rate(rate + 36.0f); | |||
generator.set_pulse_width_mean(0.0f); | |||
generator.set_pulse_width_std(0.0f); | |||
generator.set_bias(0.7f); | |||
generator.set_jitter(jitter); | |||
if (internal_clock) { | |||
generator.set_range(T_GENERATOR_RANGE_4X); | |||
} else { | |||
generator.set_range(T_GENERATOR_RANGE_1X); | |||
} | |||
ClockGeneratorPatterns patterns(FRIENDLY_PATTERNS); | |||
MasterSlaveRampGenerator ms_ramp_generator; | |||
Ramps ramps = ms_ramp_generator.ramps(); | |||
RampChecker ramp_checker[4]; | |||
for (size_t i = 0; i < ::kSampleRate * 10; i += kAudioBlockSize) { | |||
bool gate[kAudioBlockSize * 2]; | |||
patterns.Render(kAudioBlockSize); | |||
generator.Process( | |||
!internal_clock, | |||
patterns.clock(), | |||
ramps, | |||
gate, | |||
kAudioBlockSize); | |||
ramp_checker[0].Check(ramps.external, kAudioBlockSize); | |||
ramp_checker[1].Check(ramps.master, kAudioBlockSize); | |||
ramp_checker[2].Check(ramps.slave[0], kAudioBlockSize); | |||
ramp_checker[3].Check(ramps.slave[1], kAudioBlockSize); | |||
} | |||
} | |||
void TestTGeneratorRampIntegrity() { | |||
for (size_t clock_source = 0; clock_source < 2; ++clock_source) { | |||
for (size_t model = 0; model < 6; ++model) { | |||
for (size_t jitter = 0; jitter < 5; ++jitter) { | |||
for (size_t rate = 0; rate < 5; ++rate) { | |||
TestTGeneratorRampIntegrity( | |||
clock_source, | |||
float(rate) * 12.0f - 24.0f, | |||
TGeneratorModel(model), | |||
float(jitter) * 0.2f); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
void TestScaleRecorder() { | |||
int prelude[] = { 0, 4, 7, 7, 12, 12, 16, 16, 7, 7, 12, 12, 16, 0, 0, 4, 16, | |||
4, 7, 7, 12, 12, 16, 16, 7, 7, 12, 12, 16, 0, 0, 4, 16, 2, 9, 9, 14, 14, | |||
17, 17, 9, 9, 14, 14, 17, 0, 0, 2, 17, 2, 9, 9, 14, 14, 17, 17, 9, 9, 14, | |||
14, 17, -1, 0, 2, 17, 2, 7, 7, 14, 14, 17, 17, 7, 7, 14, 14, 17, -1, -1, | |||
2, 17, 2, 7, 7, 14, 14, 17, 17, 7, 7, 14, 14, 17, -1, 0, 2, 17, 4, 7, 7, | |||
12, 12, 16, 16, 7, 7, 12, 12, 16, 0, 0, 4, 16, 4, 7, 7, 12, 12, 16, 16, 7, | |||
7, 12, 12, 16, 0, 0, 4, 16, 4, 9, 9, 16, 16, 21, 21, 9, 9, 16, 16, 21, 0, | |||
0, 4, 21, 4, 9, 9, 16, 16, 21, 21, 9, 9, 16, 16, 21, 0, 0, 4, 21, 2, 6, 6, | |||
9, 9, 14, 14, 6, 6, 9, 9, 14, 0, 0, 2, 14, 2, 6, 6, 9, 9, 14, 14, 6, 6, 9, | |||
9, 14, -1, 0, 2, 14, 2, 7, 7, 14, 14, 19, 19, 7, 7, 14, 14, 19, -1, -1, 2, | |||
19, 2, 7, 7, 14, 14, 19, 19, 7, 7, 14, 14, 19, -1, -1, 2, 19, 0, 4, 4, 7, | |||
7, 12, 12, 4, 4, 7, 7, 12, -1, -1, 0, 12, 0, 4, 4, 7, 7, 12, 12, 4, 4, 7, | |||
7, 12, -3, -1, 0, 12, 0, 4, 4, 7, 7, 12, 12, 4, 4, 7, 7, 12, -3, -3, 0, 12, | |||
0, 4, 4, 7, 7, 12, 12, 4, 4, 7, 7, 12, -10, -3, 0, 12, -3, 2, 2, 6, 6, 12, | |||
12, 2, 2, 6, 6, 12, -10, -10, -3, 12, -3, 2, 2, 6, 6, 12, 12, 2, 2, 6, 6, | |||
12, -10, -5, -3, 12, -1, 2, 2, 7, 7, 11, 11, 2, 2, 7, 7, 11, -5, -5, -1, | |||
11, -1, 2, 2, 7, 7, 11, 11, 2, 2, 7, 7, 11, -5, -5, -1, 11, -2, 4, 4, 7, 7, | |||
13, 13, 4, 4, 7, 7, 13, -5, -5, -2, 13, -2, 4, 4, 7, 7, 13, 13, 4, 4, 7, 7, | |||
13, -7, -5, -2, 13, -3, 2, 2, 9, 9, 14, 14, 2, 2, 9, 9, 14, -7, -7, -3, 14, | |||
-3, 2, 2, 9, 9, 14, 14, 2, 2, 9, 9, 14, -7, -7, -3, 14, -4, 2, 2, 5, 5, 11, | |||
11, 2, 2, 5, 5, 11, -7, -7, -4, 11, -4, 2, 2, 5, 5, 11, 11, 2, 2, 5, 5, 11, | |||
-8, -7, -4, 11, -5, 0, 0, 7, 7, 12, 12, 0, 0, 7, 7, 12, -8, -8, -5, 12, -5, | |||
0, 0, 7, 7, 12, 12, 0, 0, 7, 7, 12, -8, -8, -5, 12, -7, -3, -3, 0, 0, 5, 5, | |||
-3, -3, 0, 0, 5, -8, -8, -7, 5, -7, -3, -3, 0, 0, 5, 5, -3, -3, 0, 0, 5, | |||
-10, -8, -7, 5, -7, -3, -3, 0, 0, 5, 5, -3, -3, 0, 0, 5, -10, -10, -7, 5, | |||
-7, -3, -3, 0, 0, 5, 5, -3, -3, 0, 0, 5, -17, -10, -7, 5, -10, -5, -5, -1, | |||
-1, 5, 5, -5, -5, -1, -1, 5, -17, -17, -10, 5, -10, -5, -5, -1, -1, 5, 5, | |||
-5, -5, -1, -1, 5, -17, -12, -10, 5, -8, -5, -5, 0, 0, 4, 4, -5, -5, 0, 0, | |||
4, -12, -12, -8, 4, -8, -5, -5, 0, 0, 4, 4, -5, -5, 0, 0, 4, -12, -12, -8, | |||
4, -5, -2, -2, 0, 0, 4, 4, -2, -2, 0, 0, 4, -12, -12, -5, 4, -5, -2, -2, 0, | |||
0, 4, 4, -2, -2, 0, 0, 4, -19, -12, -5, 4, -7, -3, -3, 0, 0, 4, 4, -3, -3, | |||
0, 0, 4, -19, -19, -7, 4, -7, -3, -3, 0, 0, 4, 4, -3, -3, 0, 0, 4, -19, | |||
-18, -7, 4, -12, -3, -3, 0, 0, 3, 3, -3, -3, 0, 0, 3, -18, -18, -12, 3, | |||
-12, -3, -3, 0, 0, 3, 3, -3, -3, 0, 0, 3, -18, -16, -12, 3, -7, -1, -1, 0, | |||
0, 2, 2, -1, -1, 0, 0, 2, -16, -16, -7, 2, -7, -1, -1, 0, 0, 2, 2, -1, -1, | |||
0, 0, 2, -17, -16, -7, 2, -7, -5, -5, -1, -1, 2, 2, -5, -5, -1, -1, 2, -17, | |||
-17, -7, 2, -7, -5, -5, -1, -1, 2, 2, -5, -5, -1, -1, 2, -17, -17, -7, | |||
2, -8, -5, -5, 0, 0, 4, 4, -5, -5, 0, 0, 4, -17, -17, -8, 4, -8, -5, -5, 0, | |||
0, 4, 4, -5, -5, 0, 0, 4, -17, -17, -8, 4, -10, -5, -5, 0, 0, 5, 5, -5, -5, | |||
0, 0, 5, -17, -17, -10, 5, -10, -5, -5, 0, 0, 5, 5, -5, -5, 0, 0, 5, -17, | |||
-17, -10, 5,-10, -5, -5, -1, -1, 5, 5, -5, -5, -1, -1, 5, -17, -17, -10, 5, | |||
-10, -5, -5,-1, -1, 5, 5, -5, -5, -1, -1, 5, -17, -17, -10, 5, -9, -3, -3, | |||
0, 0, 6, 6, -3,-3, 0, 0, 6, -17, -17, -9, 6, -9, -3, -3, 0, 0, 6, 6, -3, | |||
-3, 0, 0, 6, -17,-17, -9, 6, -8, -5, -5, 0, 0, 7, 7, -5, -5, 0, 0, 7, -17, | |||
-17, -8, 7, -8, -5,-5, 0, 0, 7, 7, -5, -5, 0, 0, 7, -17, -17, -8, 7, -10, | |||
-5, -5, 0, 0, 5, 5, -5,-5, 0, 0, 5, -17, -17, -10, 5, -10, -5, -5, 0, 0, 5, | |||
5, -5, -5, 0, 0, 5, -17,-17, -10, 5, -10, -5, -5, -1, -1, 5, 5, -5, -5, -1, | |||
-1, 5, -17, -17, -10, 5,-10, -5, -5, -1, -1, 5, 5, -5, -5, -1, -1, 5, -24, | |||
-17, -10, 5, -12, -5, -5,-2, -2, 4, 4, -5, -5, -2, -2, 4, -24, -24, -12, 4, | |||
-12, -5, -5, -2, -2, 4, 4,-5, -5, -2, -2, 4, -24, -24, -12, 4, -12, -7, -7, | |||
-3, -3, 0, 0, 5, 0, 5, -3, 0,-3, 0, -3, 0, -7, -3, -7, -3, -7, -3, -10, -7, | |||
-10, -7, -10, -7, -12, -10, -24,-24, -10, 7, 7, 11, 11, 14, 14, 17, 14, 17, | |||
11, 14, 11, 14, 11, 14, 7, 11, 7,11, 2, 11, 2, 5, 4, 5, 2, 4, -10, 2, -24, | |||
-24, -12, 4, 7, 12, -24, -12, 4, 7, 12 | |||
}; | |||
ScaleRecorder recorder; | |||
recorder.Init(); | |||
for (size_t i = 0; i < sizeof(prelude) / sizeof(int); ++i) { | |||
float voltage = prelude[i] / 12.0f + Random::GetFloat() * 0.0001f; | |||
recorder.NewNote(voltage); | |||
recorder.UpdateVoltage(voltage); | |||
recorder.AcceptNote(); | |||
} | |||
Scale s; | |||
recorder.ExtractScale(&s); | |||
for (int i = 0; i < s.num_degrees; ++i) { | |||
printf("%f %d\n", s.degree[i].voltage, s.degree[i].weight); | |||
} | |||
} | |||
int main(void) { | |||
// Test distributions and value processors. | |||
// TestBetaDistribution(); | |||
// TestQuantizer(); | |||
// TestQuantizerNoise(); | |||
// Ramp tests. | |||
// TestRampExtractor(FRIENDLY_PATTERNS, "marbles_ramp_extractor_friendly.wav"); | |||
// TestRampExtractor(TRICKY_PATTERNS, "marbles_ramp_extractor_tricky.wav"); | |||
// TestRampExtractor(FAST_PATTERNS, "marbles_ramp_extractor_fast.wav"); | |||
// TestRampDivider(TRICKY_PATTERNS, "marbles_ramp_divider_tricky.wav"); | |||
// TestRampExtractorClockBug(); | |||
// TestRampExtractorPause(); | |||
// TestOutputChannel(); | |||
// TestXYGenerator(); | |||
// TestXYGeneratorASR(); | |||
// TestTGeneratorRampIntegrity(); | |||
TestTGenerator(); | |||
// TestScaleRecorder(); | |||
} |
@@ -0,0 +1,84 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// This program is free software: you can redistribute it and/or modify | |||
// it under the terms of the GNU General Public License as published by | |||
// the Free Software Foundation, either version 3 of the License, or | |||
// (at your option) any later version. | |||
// This program is distributed in the hope that it will be useful, | |||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
// GNU General Public License for more details. | |||
// You should have received a copy of the GNU General Public License | |||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
#ifndef MARBLES_TEST_RAMP_CHECKER_H_ | |||
#define MARBLES_TEST_RAMP_CHECKER_H_ | |||
#include <cstdio> | |||
namespace marbles { | |||
class RampChecker { | |||
public: | |||
RampChecker() { | |||
previous_phase_ = 0.0f; | |||
counter_ = 0; | |||
average_frequency_ = 0.1f; | |||
stall_counter_ = 0; | |||
} | |||
~RampChecker() { } | |||
void Error(const char* message) { | |||
printf("RAMP ERROR: %.5f %s\n", float(counter_) / kSampleRate, message); | |||
} | |||
void Check(float* ramp, size_t size) { | |||
while (size--) { | |||
float phase = *ramp++; | |||
float frequency = phase - previous_phase_; | |||
if (phase < 0.0f || phase > 1.0f) { | |||
Error("incorrect ramp value"); | |||
} | |||
if (frequency < 0.0f) { | |||
if (average_frequency_ < 0.05f) { | |||
if (previous_phase_ < 0.75f) { | |||
Error("reset without having reached maximum value"); | |||
} | |||
if (phase > 0.25f) { | |||
Error("does not reset to zero"); | |||
} | |||
} | |||
} else { | |||
average_frequency_ += 0.1f * (frequency - average_frequency_); | |||
} | |||
if (frequency == 0.0f && phase < 0.01f) { | |||
++stall_counter_; | |||
if (stall_counter_ == 40) { | |||
Error("Ramp stalls after reset"); | |||
} | |||
} else { | |||
stall_counter_ = 0; | |||
} | |||
if (counter_ > 10 && frequency > 0.5f) { | |||
Error("High slope"); | |||
} | |||
previous_phase_ = phase; | |||
++counter_; | |||
} | |||
} | |||
private: | |||
size_t stall_counter_; | |||
float previous_phase_; | |||
float average_frequency_; | |||
size_t counter_; | |||
DISALLOW_COPY_AND_ASSIGN(RampChecker); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_TEST_RAMP_CHECKER_H_ |
@@ -0,0 +1,533 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// User interface. | |||
#include "marbles/ui.h" | |||
#include <algorithm> | |||
#include "stmlib/system/system_clock.h" | |||
#include "marbles/drivers/clock_inputs.h" | |||
#include "marbles/cv_reader.h" | |||
#include "marbles/scale_recorder.h" | |||
#include "marbles/settings.h" | |||
namespace marbles { | |||
const int32_t kLongPressDuration = 2000; | |||
using namespace std; | |||
using namespace stmlib; | |||
/* static */ | |||
const LedColor Ui::palette_[4] = { | |||
LED_COLOR_GREEN, | |||
LED_COLOR_YELLOW, | |||
LED_COLOR_RED, | |||
LED_COLOR_OFF | |||
}; | |||
/* static */ | |||
AlternateKnobMapping Ui::alternate_knob_mappings_[ADC_CHANNEL_LAST]; | |||
void Ui::Init( | |||
Settings* settings, | |||
CvReader* cv_reader, | |||
ScaleRecorder* scale_recorder, | |||
ClockInputs* clock_inputs) { | |||
settings_ = settings; | |||
cv_reader_ = cv_reader; | |||
scale_recorder_ = scale_recorder; | |||
clock_inputs_ = clock_inputs; | |||
leds_.Init(); | |||
switches_.Init(); | |||
queue_.Init(); | |||
// Initialize generator from settings_->state(); | |||
fill(&pot_value_[0], &pot_value_[ADC_CHANNEL_LAST], 0.0f); | |||
State* state = settings_->mutable_state(); | |||
alternate_knob_mappings_[ADC_CHANNEL_T_BIAS].unlock_switch = SWITCH_T_MODEL; | |||
alternate_knob_mappings_[ADC_CHANNEL_T_BIAS].destination = &state->t_pulse_width_mean; | |||
alternate_knob_mappings_[ADC_CHANNEL_T_JITTER].unlock_switch = SWITCH_T_MODEL; | |||
alternate_knob_mappings_[ADC_CHANNEL_T_JITTER].destination = &state->t_pulse_width_std; | |||
alternate_knob_mappings_[ADC_CHANNEL_T_RATE].unlock_switch = SWITCH_X_MODE; | |||
alternate_knob_mappings_[ADC_CHANNEL_T_RATE].destination = &state->y_divider; | |||
alternate_knob_mappings_[ADC_CHANNEL_X_SPREAD].unlock_switch = SWITCH_X_MODE; | |||
alternate_knob_mappings_[ADC_CHANNEL_X_SPREAD].destination = &state->y_spread; | |||
alternate_knob_mappings_[ADC_CHANNEL_X_BIAS].unlock_switch = SWITCH_X_MODE; | |||
alternate_knob_mappings_[ADC_CHANNEL_X_BIAS].destination = &state->y_bias; | |||
alternate_knob_mappings_[ADC_CHANNEL_X_STEPS].unlock_switch = SWITCH_X_MODE; | |||
alternate_knob_mappings_[ADC_CHANNEL_X_STEPS].destination = &state->y_steps; | |||
setting_modification_flag_ = false; | |||
output_test_mode_ = false; | |||
if (switches_.pressed_immediate(SWITCH_X_MODE)) { | |||
if (state->color_blind == 1) { | |||
state->color_blind = 0; | |||
} else { | |||
state->color_blind = 1; | |||
} | |||
settings_->SaveState(); | |||
} | |||
deja_vu_lock_ = false; | |||
} | |||
void Ui::SaveState() { | |||
settings_->SaveState(); | |||
} | |||
void Ui::Poll() { | |||
// 1kHz. | |||
system_clock.Tick(); | |||
switches_.Debounce(); | |||
for (int i = 0; i < SWITCH_LAST; ++i) { | |||
if (switches_.just_pressed(Switch(i))) { | |||
queue_.AddEvent(CONTROL_SWITCH, i, 0); | |||
press_time_[i] = system_clock.milliseconds(); | |||
ignore_release_[i] = false; | |||
} | |||
if (switches_.pressed(Switch(i)) && !ignore_release_[i]) { | |||
int32_t pressed_time = system_clock.milliseconds() - press_time_[i]; | |||
if (pressed_time > kLongPressDuration && !setting_modification_flag_) { | |||
queue_.AddEvent(CONTROL_SWITCH, i, pressed_time); | |||
ignore_release_[i] = true; | |||
} | |||
} | |||
if (switches_.released(Switch(i)) && !ignore_release_[i]) { | |||
queue_.AddEvent( | |||
CONTROL_SWITCH, | |||
i, | |||
system_clock.milliseconds() - press_time_[i] + 1); | |||
ignore_release_[i] = true; | |||
} | |||
} | |||
UpdateLEDs(); | |||
} | |||
/* static */ | |||
LedColor Ui::MakeColor(uint8_t value, bool color_blind) { | |||
bool slow_blink = (system_clock.milliseconds() & 255) > 128; | |||
uint8_t bank = value >= 3 ? 1 : 0; | |||
value -= bank * 3; | |||
LedColor color = palette_[value]; | |||
if (color_blind) { | |||
uint8_t pwm_counter = system_clock.milliseconds() & 15; | |||
uint8_t triangle = (system_clock.milliseconds() >> 5) & 31; | |||
triangle = triangle < 16 ? triangle : 31 - triangle; | |||
if (value == 0) { | |||
color = pwm_counter < (4 + (triangle >> 2)) | |||
? LED_COLOR_GREEN | |||
: LED_COLOR_OFF; | |||
} else if (value == 1) { | |||
color = LED_COLOR_YELLOW; | |||
} else { | |||
color = pwm_counter == 0 ? LED_COLOR_RED : LED_COLOR_OFF; | |||
} | |||
} | |||
return slow_blink || !bank ? color : LED_COLOR_OFF; | |||
} | |||
void Ui::UpdateLEDs() { | |||
bool blink = (system_clock.milliseconds() & 127) > 64; | |||
bool slow_blink = (system_clock.milliseconds() & 255) > 128; | |||
bool fast_blink = (system_clock.milliseconds() & 63) > 32; | |||
const State& state = settings_->state(); | |||
bool cb = state.color_blind == 1; | |||
LedColor scale_color = state.x_scale < 3 | |||
? (slow_blink ? palette_[state.x_scale] : LED_COLOR_OFF) | |||
: (fast_blink ? palette_[state.x_scale - 3] : LED_COLOR_OFF); | |||
if (cb) { | |||
int poly_counter = (system_clock.milliseconds() >> 6) % 12; | |||
if ((poly_counter >> 1) < (state.x_scale + 1) && (poly_counter & 1)) { | |||
scale_color = LED_COLOR_YELLOW; | |||
} else { | |||
scale_color = LED_COLOR_OFF; | |||
} | |||
} | |||
leds_.Clear(); | |||
int slow_triangle = (system_clock.milliseconds() & 1023) >> 5; | |||
slow_triangle = slow_triangle >= 16 ? 31 - slow_triangle : slow_triangle; | |||
int pw = system_clock.milliseconds() & 15; | |||
bool deja_vu_glow = !deja_vu_lock_ || (slow_triangle >= pw); | |||
switch (mode_) { | |||
case UI_MODE_NORMAL: | |||
case UI_MODE_RECORD_SCALE: | |||
{ | |||
leds_.set(LED_T_MODEL, MakeColor(state.t_model, cb)); | |||
leds_.set(LED_T_RANGE, MakeColor(state.t_range, cb)); | |||
leds_.set(LED_T_DEJA_VU, | |||
state.t_deja_vu && deja_vu_glow ? | |||
LED_COLOR_GREEN : LED_COLOR_OFF); | |||
leds_.set(LED_X_CONTROL_MODE, MakeColor(state.x_control_mode, cb)); | |||
leds_.set(LED_X_DEJA_VU, | |||
state.x_deja_vu && deja_vu_glow ? | |||
LED_COLOR_GREEN : LED_COLOR_OFF); | |||
if (mode_ == UI_MODE_NORMAL) { | |||
leds_.set(LED_X_RANGE, | |||
state.x_register_mode | |||
? LED_COLOR_OFF | |||
: MakeColor(state.x_range, cb)); | |||
leds_.set(LED_X_EXT, | |||
state.x_register_mode ? LED_COLOR_GREEN : LED_COLOR_OFF); | |||
} else { | |||
leds_.set(LED_X_RANGE, scale_color); | |||
leds_.set(LED_X_EXT, LED_COLOR_GREEN); | |||
} | |||
} | |||
break; | |||
case UI_MODE_SELECT_SCALE: | |||
leds_.set(LED_X_RANGE, scale_color); | |||
break; | |||
case UI_MODE_CALIBRATION_1: | |||
leds_.set(LED_T_RANGE, blink ? MakeColor(0, cb) : LED_COLOR_OFF); | |||
break; | |||
case UI_MODE_CALIBRATION_2: | |||
leds_.set(LED_T_RANGE, blink ? MakeColor(1, cb) : LED_COLOR_OFF); | |||
break; | |||
case UI_MODE_CALIBRATION_3: | |||
leds_.set(LED_X_RANGE, blink ? MakeColor(0, cb) : LED_COLOR_OFF); | |||
break; | |||
case UI_MODE_CALIBRATION_4: | |||
leds_.set(LED_X_RANGE, blink ? MakeColor(1, cb) : LED_COLOR_OFF); | |||
break; | |||
case UI_MODE_PANIC: | |||
leds_.set(LED_T_MODEL, blink ? LED_COLOR_RED : LED_COLOR_OFF); | |||
leds_.set(LED_T_RANGE, !blink ? LED_COLOR_RED : LED_COLOR_OFF); | |||
leds_.set(LED_X_CONTROL_MODE, !blink ? LED_COLOR_RED : LED_COLOR_OFF); | |||
leds_.set(LED_X_RANGE, blink ? LED_COLOR_RED : LED_COLOR_OFF); | |||
break; | |||
} | |||
leds_.Write(); | |||
} | |||
void Ui::FlushEvents() { | |||
queue_.Flush(); | |||
} | |||
void Ui::OnSwitchPressed(const Event& e) { | |||
} | |||
void Ui::OnSwitchReleased(const Event& e) { | |||
if (setting_modification_flag_) { | |||
for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
cv_reader_->mutable_channel(i)->UnlockPot(); | |||
} | |||
setting_modification_flag_ = false; | |||
return; | |||
} | |||
// Check if the other switch is still pressed. | |||
if (e.control_id == SWITCH_T_RANGE && switches_.pressed(SWITCH_X_RANGE)) { | |||
mode_ = UI_MODE_CALIBRATION_1; | |||
ignore_release_[SWITCH_T_RANGE] = ignore_release_[SWITCH_X_RANGE] = true; | |||
return; | |||
} | |||
State* state = settings_->mutable_state(); | |||
switch (e.control_id) { | |||
case SWITCH_T_DEJA_VU: | |||
state->t_deja_vu = !state->t_deja_vu; | |||
break; | |||
case SWITCH_X_DEJA_VU: | |||
state->x_deja_vu = !state->x_deja_vu; | |||
break; | |||
case SWITCH_T_MODEL: | |||
{ | |||
uint8_t bank = state->t_model / 3; | |||
if (e.data >= kLongPressDuration) { | |||
if (!bank) { | |||
state->t_model += 3; | |||
} | |||
} else { | |||
if (bank) { | |||
state->t_model -= 3; | |||
} else { | |||
state->t_model = (state->t_model + 1) % 3; | |||
} | |||
} | |||
SaveState(); | |||
} | |||
break; | |||
case SWITCH_T_RANGE: | |||
{ | |||
if (mode_ >= UI_MODE_CALIBRATION_1 && mode_ <= UI_MODE_CALIBRATION_4) { | |||
NextCalibrationStep(); | |||
} else { | |||
state->t_range = (state->t_range + 1) % 3; | |||
} | |||
SaveState(); | |||
} | |||
break; | |||
case SWITCH_X_MODE: | |||
state->x_control_mode = (state->x_control_mode + 1) % 3; | |||
SaveState(); | |||
break; | |||
case SWITCH_X_EXT: | |||
if (mode_ == UI_MODE_RECORD_SCALE) { | |||
int scale_index = settings_->state().x_scale; | |||
bool success = true; | |||
if (e.data >= kLongPressDuration) { | |||
settings_->ResetScale(scale_index); | |||
} else { | |||
success = scale_recorder_->ExtractScale( | |||
settings_->mutable_scale(scale_index)); | |||
} | |||
if (success) { | |||
settings_->SavePersistentData(); | |||
settings_->set_dirty_scale_index(scale_index); | |||
} | |||
mode_ = UI_MODE_NORMAL; | |||
} else if (e.data >= kLongPressDuration) { | |||
mode_ = UI_MODE_RECORD_SCALE; | |||
scale_recorder_->Clear(); | |||
} else { | |||
state->x_register_mode = !state->x_register_mode; | |||
SaveState(); | |||
} | |||
break; | |||
case SWITCH_X_RANGE: | |||
if (mode_ >= UI_MODE_CALIBRATION_1 && mode_ <= UI_MODE_CALIBRATION_4) { | |||
NextCalibrationStep(); | |||
} else if (e.data >= kLongPressDuration) { | |||
if (mode_ == UI_MODE_NORMAL) { | |||
mode_ = UI_MODE_SELECT_SCALE; | |||
} | |||
} else if (mode_ == UI_MODE_SELECT_SCALE) { | |||
state->x_scale = (state->x_scale + 1) % kNumScales; | |||
} else { | |||
if (!state->x_register_mode) { | |||
state->x_range = (state->x_range + 1) % 3; | |||
} | |||
} | |||
SaveState(); | |||
break; | |||
} | |||
} | |||
void Ui::TerminateScaleRecording() { | |||
for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
cv_reader_->mutable_channel(i)->UnlockPot(); | |||
} | |||
mode_ = UI_MODE_NORMAL; | |||
} | |||
void Ui::NextCalibrationStep() { | |||
switch (mode_) { | |||
case UI_MODE_CALIBRATION_1: | |||
cv_reader_->CalibrateOffsets(); | |||
cv_reader_->CalibrateRateC1(); | |||
mode_ = UI_MODE_CALIBRATION_2; | |||
break; | |||
case UI_MODE_CALIBRATION_2: | |||
cv_reader_->CalibrateRateC3(); | |||
mode_ = UI_MODE_CALIBRATION_3; | |||
break; | |||
case UI_MODE_CALIBRATION_3: | |||
cv_reader_->CalibrateSpreadC1(); | |||
mode_ = UI_MODE_CALIBRATION_4; | |||
break; | |||
case UI_MODE_CALIBRATION_4: | |||
if (cv_reader_->CalibrateSpreadC3()) { | |||
settings_->SavePersistentData(); | |||
mode_ = UI_MODE_NORMAL; | |||
} else { | |||
mode_ = UI_MODE_PANIC; | |||
} | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
void Ui::UpdateHiddenParameters() { | |||
// Check if some pots have been moved. | |||
for (int i = 0; i < ADC_CHANNEL_LAST; ++i) { | |||
float new_value = cv_reader_->channel(i).unscaled_pot(); | |||
float old_value = pot_value_[i]; | |||
bool changed = fabs(new_value - old_value) >= 0.008f; | |||
if (changed) { | |||
pot_value_[i] = new_value; | |||
AlternateKnobMapping mapping = alternate_knob_mappings_[i]; | |||
if (switches_.pressed(mapping.unlock_switch)) { | |||
if (mapping.unlock_switch == SWITCH_T_RANGE && new_value < 0.1f) { | |||
new_value = 0.0f; | |||
} | |||
*mapping.destination = static_cast<uint8_t>(new_value * 255.0f); | |||
cv_reader_->mutable_channel(i)->LockPot(); | |||
// The next time a switch is released, we unlock the pots. | |||
setting_modification_flag_ = true; | |||
} | |||
} | |||
} | |||
} | |||
void Ui::DoEvents() { | |||
while (queue_.available()) { | |||
Event e = queue_.PullEvent(); | |||
if (e.control_type == CONTROL_SWITCH) { | |||
if (e.data == 0) { | |||
OnSwitchPressed(e); | |||
} else { | |||
OnSwitchReleased(e); | |||
} | |||
} | |||
} | |||
UpdateHiddenParameters(); | |||
if (queue_.idle_time() > 800 && mode_ == UI_MODE_PANIC) { | |||
mode_ = UI_MODE_NORMAL; | |||
} | |||
if (mode_ == UI_MODE_SELECT_SCALE) { | |||
if (queue_.idle_time() > 4000) { | |||
mode_ = UI_MODE_NORMAL; | |||
queue_.Touch(); | |||
} | |||
} else if (queue_.idle_time() > 1000) { | |||
queue_.Touch(); | |||
} | |||
} | |||
uint8_t Ui::HandleFactoryTestingRequest(uint8_t command) { | |||
uint8_t argument = command & 0x1f; | |||
command = command >> 5; | |||
uint8_t reply = 0; | |||
switch (command) { | |||
case FACTORY_TESTING_READ_POT: | |||
case FACTORY_TESTING_READ_CV: | |||
reply = cv_reader_->adc_value(argument); | |||
break; | |||
case FACTORY_TESTING_READ_NORMALIZATION: | |||
reply = clock_inputs_->is_normalized(ClockInput(argument)) ? 255 : 0; | |||
break; | |||
case FACTORY_TESTING_READ_GATE: | |||
reply = argument >= SWITCH_LAST | |||
? clock_inputs_->value(ClockInput(argument - SWITCH_LAST)) | |||
: switches_.pressed(Switch(argument)); | |||
break; | |||
case FACTORY_TESTING_GENERATE_TEST_SIGNALS: | |||
output_test_mode_ = static_cast<bool>(argument); | |||
fill( | |||
&output_test_forced_dac_code_[0], | |||
&output_test_forced_dac_code_[4], | |||
0); | |||
break; | |||
case FACTORY_TESTING_CALIBRATE: | |||
if (argument == 0) { | |||
// Revert all settings before getting into calibration mode. | |||
settings_->mutable_state()->t_deja_vu = 0; | |||
settings_->mutable_state()->x_deja_vu = 0; | |||
settings_->mutable_state()->t_model = 0; | |||
settings_->mutable_state()->t_range = 1; | |||
settings_->mutable_state()->x_control_mode = 0; | |||
settings_->mutable_state()->x_range = 2; | |||
settings_->mutable_state()->x_register_mode = 0; | |||
settings_->SavePersistentData(); | |||
mode_ = UI_MODE_CALIBRATION_1; | |||
} else { | |||
NextCalibrationStep(); | |||
} | |||
{ | |||
const CalibrationData& cal = settings_->calibration_data(); | |||
float voltage = (argument & 1) == 0 ? 1.0f : 3.0f; | |||
for (int i = 0; i < 4; ++i) { | |||
output_test_forced_dac_code_[i] = static_cast<uint16_t>( | |||
voltage * cal.dac_scale[i] + cal.dac_offset[i]); | |||
} | |||
} | |||
queue_.Touch(); | |||
break; | |||
case FACTORY_TESTING_FORCE_DAC_CODE: | |||
{ | |||
int channel = argument >> 2; | |||
int step = argument & 0x3; | |||
if (step == 0) { | |||
output_test_forced_dac_code_[channel] = 0xaf35; | |||
} else if (step == 1) { | |||
output_test_forced_dac_code_[channel] = 0x1d98; | |||
} else { | |||
CalibrationData* cal = settings_->mutable_calibration_data(); | |||
cal->dac_offset[channel] = static_cast<float>( | |||
calibration_data_ & 0xffff); | |||
cal->dac_scale[channel] = static_cast<float>( | |||
calibration_data_ >> 16) * -0.125f; | |||
output_test_forced_dac_code_[channel] = static_cast<uint16_t>(cal->dac_scale[channel] + cal->dac_offset[channel]); | |||
settings_->SavePersistentData(); | |||
} | |||
} | |||
break; | |||
case FACTORY_TESTING_WRITE_CALIBRATION_DATA_NIBBLE: | |||
calibration_data_ <<= 4; | |||
calibration_data_ |= argument & 0xf; | |||
break; | |||
} | |||
return reply; | |||
} | |||
} // namespace marbles |
@@ -0,0 +1,146 @@ | |||
// Copyright 2015 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// User interface. | |||
#ifndef MARBLES_UI_H_ | |||
#define MARBLES_UI_H_ | |||
#include "stmlib/stmlib.h" | |||
#include "stmlib/ui/event_queue.h" | |||
#include "marbles/drivers/adc.h" | |||
#include "marbles/drivers/leds.h" | |||
#include "marbles/drivers/switches.h" | |||
namespace marbles { | |||
class ClockInputs; | |||
class CvReader; | |||
class ScaleRecorder; | |||
class Settings; | |||
enum UiMode { | |||
UI_MODE_NORMAL, | |||
UI_MODE_SELECT_SCALE, | |||
UI_MODE_RECORD_SCALE, | |||
UI_MODE_CALIBRATION_1, | |||
UI_MODE_CALIBRATION_2, | |||
UI_MODE_CALIBRATION_3, | |||
UI_MODE_CALIBRATION_4, | |||
UI_MODE_PANIC, | |||
}; | |||
enum FactoryTestingCommand { | |||
FACTORY_TESTING_READ_POT, | |||
FACTORY_TESTING_READ_CV, | |||
FACTORY_TESTING_READ_GATE, | |||
FACTORY_TESTING_GENERATE_TEST_SIGNALS, | |||
FACTORY_TESTING_CALIBRATE, | |||
FACTORY_TESTING_READ_NORMALIZATION, | |||
FACTORY_TESTING_FORCE_DAC_CODE, | |||
FACTORY_TESTING_WRITE_CALIBRATION_DATA_NIBBLE, | |||
}; | |||
struct AlternateKnobMapping { | |||
Switch unlock_switch; | |||
uint8_t* destination; | |||
}; | |||
class Ui { | |||
public: | |||
Ui() { } | |||
~Ui() { } | |||
void Init( | |||
Settings* settings, | |||
CvReader* cv_reader, | |||
ScaleRecorder* scale_recorder, | |||
ClockInputs* clock_inputs); | |||
void Poll(); | |||
void DoEvents(); | |||
void FlushEvents(); | |||
uint8_t HandleFactoryTestingRequest(uint8_t command); | |||
bool recording_scale() const { | |||
return mode_ == UI_MODE_RECORD_SCALE; | |||
} | |||
bool output_test_mode() const { | |||
return output_test_mode_; | |||
} | |||
uint16_t output_test_forced_dac_code(int i) const { | |||
return output_test_forced_dac_code_[i]; | |||
} | |||
void set_deja_vu_lock(bool deja_vu_lock) { | |||
deja_vu_lock_ = deja_vu_lock; | |||
} | |||
private: | |||
void UpdateLEDs(); | |||
void OnSwitchPressed(const stmlib::Event& e); | |||
void OnSwitchReleased(const stmlib::Event& e); | |||
void NextCalibrationStep(); | |||
void SaveState(); | |||
void UpdateHiddenParameters(); | |||
void TerminateScaleRecording(); | |||
static LedColor MakeColor(uint8_t value, bool color_blind); | |||
stmlib::EventQueue<16> queue_; | |||
Leds leds_; | |||
Switches switches_; | |||
uint32_t press_time_[SWITCH_LAST]; | |||
bool ignore_release_[SWITCH_LAST]; | |||
UiMode mode_; | |||
Settings* settings_; | |||
CvReader* cv_reader_; | |||
ScaleRecorder* scale_recorder_; | |||
ClockInputs* clock_inputs_; | |||
static const LedColor palette_[4]; | |||
static AlternateKnobMapping alternate_knob_mappings_[ADC_CHANNEL_LAST]; | |||
float pot_value_[ADC_CHANNEL_LAST]; | |||
bool setting_modification_flag_; | |||
bool deja_vu_lock_; | |||
bool output_test_mode_; | |||
uint16_t output_test_forced_dac_code_[4]; | |||
uint32_t calibration_data_; | |||
DISALLOW_COPY_AND_ASSIGN(Ui); | |||
}; | |||
} // namespace marbles | |||
#endif // MARBLES_UI_H_ |
@@ -0,0 +1,267 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
#include <stm32f37x_conf.h> | |||
#include <cstring> | |||
#include "stmlib/system/bootloader_utils.h" | |||
#include "stmlib/system/flash_programming.h" | |||
#include "stmlib/system/system_clock.h" | |||
#include "stm_audio_bootloader/qpsk/packet_decoder.h" | |||
#include "stm_audio_bootloader/qpsk/demodulator.h" | |||
#include "plaits/drivers/audio_dac.h" | |||
#include "plaits/drivers/firmware_update_adc.h" | |||
#include "plaits/drivers/leds.h" | |||
#include "plaits/drivers/switches.h" | |||
extern "C" { | |||
void NMI_Handler() { } | |||
void HardFault_Handler() { while (1); } | |||
void MemManage_Handler() { while (1); } | |||
void BusFault_Handler() { while (1); } | |||
void UsageFault_Handler() { while (1); } | |||
void SVC_Handler() { } | |||
void DebugMon_Handler() { } | |||
void PendSV_Handler() { } | |||
} | |||
using namespace plaits; | |||
using namespace std; | |||
using namespace stm_audio_bootloader; | |||
using namespace stmlib; | |||
const double kSampleRate = 48000.0; | |||
const double kModulationRate = 6000.0; | |||
const double kBitRate = 12000.0; | |||
const uint32_t kStartAddress = 0x08008000; | |||
const uint16_t kPacketsPerPage = PAGE_SIZE / kPacketSize; | |||
enum UiState { | |||
UI_STATE_WAITING, | |||
UI_STATE_RECEIVING, | |||
UI_STATE_ERROR, | |||
UI_STATE_WRITING | |||
}; | |||
AudioDac audio_dac; | |||
FirmwareUpdateAdc adc; | |||
Leds leds; | |||
Switches switches; | |||
PacketDecoder decoder; | |||
Demodulator demodulator; | |||
int discard_samples = 8000; | |||
int32_t peak = 0; | |||
int32_t gain_pot = 0; | |||
uint32_t current_address; | |||
uint16_t packet_index; | |||
uint8_t rx_buffer[PAGE_SIZE]; | |||
volatile bool switch_released = false; | |||
volatile UiState ui_state; | |||
inline void UpdateLeds() { | |||
leds.Clear(); | |||
// Show bargraph on the upper 4 LEDs. | |||
int32_t pwm = system_clock.milliseconds() & 15; | |||
leds.set(3, (peak >> 9) > pwm ? LED_COLOR_GREEN : 0); | |||
leds.set(2, ((peak - 8192) >> 9) >= pwm ? LED_COLOR_GREEN : 0); | |||
leds.set(1, ((peak - 16384) >> 9) >= pwm ? LED_COLOR_YELLOW : 0); | |||
leds.set(0, ((peak - 16384 - 8192) >> 9) >= pwm ? LED_COLOR_RED : 0); | |||
// Show status info on the lower 4 LEDs. | |||
switch (ui_state) { | |||
case UI_STATE_WAITING: | |||
{ | |||
bool on = system_clock.milliseconds() & 128; | |||
for (int i = 4; i < 8; ++i) { | |||
leds.set(i, on ? LED_COLOR_YELLOW : LED_COLOR_OFF); | |||
} | |||
} | |||
break; | |||
case UI_STATE_RECEIVING: | |||
{ | |||
int stage = (system_clock.milliseconds() >> 7) & 3; | |||
leds.set(stage + 4, LED_COLOR_GREEN); | |||
} | |||
break; | |||
case UI_STATE_ERROR: | |||
{ | |||
bool on = system_clock.milliseconds() & 256; | |||
for (int i = 0; i < 8; ++i) { | |||
leds.set(i, on ? LED_COLOR_RED : LED_COLOR_OFF); | |||
} | |||
} | |||
break; | |||
case UI_STATE_WRITING: | |||
{ | |||
for (uint8_t i = 4; i < 8; ++i) { | |||
leds.set(i, LED_COLOR_GREEN); | |||
} | |||
} | |||
break; | |||
} | |||
leds.Write(); | |||
} | |||
extern "C" { | |||
void SysTick_Handler() { | |||
system_clock.Tick(); | |||
switches.Debounce(); | |||
if (switches.released(Switch(0))) { | |||
switch_released = true; | |||
} | |||
UpdateLeds(); | |||
} | |||
} | |||
void ProgramPage(const uint8_t* data, size_t size) { | |||
FLASH_Unlock(); | |||
FLASH_ErasePage(current_address); | |||
const uint32_t* words = static_cast<const uint32_t*>( | |||
static_cast<const void*>(data)); | |||
for (size_t written = 0; written < size; written += 4) { | |||
FLASH_ProgramWord(current_address, *words++); | |||
current_address += 4; | |||
} | |||
} | |||
void FillBuffer(AudioDac::Frame* output, size_t size) { | |||
gain_pot = (adc.gain_pot() + 4095 * gain_pot) >> 12; | |||
int32_t sample = 32768 - static_cast<int32_t>(adc.sample()); | |||
adc.Convert(); | |||
int32_t gain = ((gain_pot >> 1) * gain_pot >> 21) + 128; | |||
sample = sample * gain >> 8; | |||
CONSTRAIN(sample, -32767, 32767) | |||
int32_t rect = abs(sample); | |||
peak = rect > peak ? rect : (rect + 32767 * peak) >> 15; | |||
if (!discard_samples) { | |||
demodulator.PushSample(2048 + (sample >> 4)); | |||
} else { | |||
--discard_samples; | |||
} | |||
output->l = -sample; | |||
output->r = -sample; | |||
} | |||
void InitializeReception() { | |||
decoder.Init(1000, true); | |||
demodulator.Init( | |||
kModulationRate / kSampleRate * 4294967296.0, | |||
kSampleRate / kModulationRate, | |||
2.0 * kSampleRate / kBitRate); | |||
demodulator.SyncCarrier(true); | |||
decoder.Reset(); | |||
current_address = kStartAddress; | |||
packet_index = 0; | |||
ui_state = UI_STATE_WAITING; | |||
} | |||
void Init() { | |||
adc.Init(); | |||
leds.Init(); | |||
switches.Init(); | |||
audio_dac.Init(48000, 1); | |||
audio_dac.Start(&FillBuffer); | |||
SysTick_Config(F_CPU / 1000); | |||
} | |||
int main(void) { | |||
Init(); | |||
InitializeReception(); | |||
bool exit_updater = !switches.pressed_immediate(Switch(0)); | |||
while (!exit_updater) { | |||
bool error = false; | |||
if (demodulator.state() == DEMODULATOR_STATE_OVERFLOW) { | |||
error = true; | |||
} else { | |||
demodulator.ProcessAtLeast(32); | |||
} | |||
while (demodulator.available() && !error && !exit_updater) { | |||
uint8_t symbol = demodulator.NextSymbol(); | |||
PacketDecoderState state = decoder.ProcessSymbol(symbol); | |||
switch (state) { | |||
case PACKET_DECODER_STATE_OK: | |||
{ | |||
ui_state = UI_STATE_RECEIVING; | |||
memcpy( | |||
rx_buffer + (packet_index % kPacketsPerPage) * kPacketSize, | |||
decoder.packet_data(), | |||
kPacketSize); | |||
++packet_index; | |||
if ((packet_index % kPacketsPerPage) == 0) { | |||
ui_state = UI_STATE_WRITING; | |||
ProgramPage(rx_buffer, PAGE_SIZE); | |||
decoder.Reset(); | |||
demodulator.SyncCarrier(false); | |||
ui_state = UI_STATE_RECEIVING; | |||
} else { | |||
decoder.Reset(); | |||
demodulator.SyncDecision(); | |||
} | |||
} | |||
break; | |||
case PACKET_DECODER_STATE_ERROR_CRC: | |||
case PACKET_DECODER_STATE_ERROR_SYNC: | |||
error = true; | |||
break; | |||
break; | |||
case PACKET_DECODER_STATE_END_OF_TRANSMISSION: | |||
exit_updater = true; | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
if (error) { | |||
ui_state = UI_STATE_ERROR; | |||
switch_released = false; | |||
while (!switch_released); // Polled in ISR | |||
InitializeReception(); | |||
} | |||
} | |||
adc.DeInit(); | |||
audio_dac.Stop(); | |||
Uninitialize(); | |||
JumpTo(kStartAddress); | |||
while (1) { } | |||
} |
@@ -0,0 +1,46 @@ | |||
# Copyright 2016 Olivier Gillet. | |||
# | |||
# Author: Olivier Gillet (ol.gillet@gmail.com) | |||
# | |||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||
# of this software and associated documentation files (the "Software"), to deal | |||
# in the Software without restriction, including without limitation the rights | |||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
# copies of the Software, and to permit persons to whom the Software is | |||
# furnished to do so, subject to the following conditions: | |||
# | |||
# The above copyright notice and this permission notice shall be included in | |||
# all copies or substantial portions of the Software. | |||
# | |||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
# THE SOFTWARE. | |||
# | |||
# See http://creativecommons.org/licenses/MIT/ for more information. | |||
# System specifications | |||
F_CRYSTAL = 8000000L | |||
F_CPU = 72000000L | |||
SYSCLOCK = SYSCLK_FREQ_72MHz | |||
FAMILY = f37x | |||
# USB = enabled | |||
# Preferred upload command | |||
UPLOAD_COMMAND = upload_jtag_erase_first | |||
# Packages to build | |||
TARGET = plaits_bootloader | |||
PACKAGES = plaits/bootloader \ | |||
plaits/drivers \ | |||
stm_audio_bootloader/qpsk \ | |||
stmlib/dsp \ | |||
stmlib/utils \ | |||
stmlib/system | |||
TOOLCHAIN_PATH ?= /usr/local/arm-4.8.3/ | |||
include stmlib/makefile.inc |
@@ -0,0 +1,132 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the PCM5100 DAC. | |||
#include "plaits/drivers/audio_dac.h" | |||
#include <stm32f37x_conf.h> | |||
namespace plaits { | |||
/* static */ | |||
AudioDac* AudioDac::instance_; | |||
void AudioDac::Init(int sample_rate, size_t block_size) { | |||
instance_ = this; | |||
block_size_ = block_size; | |||
callback_ = NULL; | |||
InitializeGPIO(); | |||
InitializeAudioInterface(sample_rate); | |||
InitializeDMA(block_size); | |||
} | |||
void AudioDac::InitializeGPIO() { | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_10 | GPIO_Pin_11; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_5); | |||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_5); | |||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_5); | |||
} | |||
void AudioDac::InitializeAudioInterface(int sample_rate) { | |||
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); | |||
SPI_I2S_DeInit(SPI2); | |||
I2S_InitTypeDef i2s_init; | |||
i2s_init.I2S_Mode = I2S_Mode_MasterTx; | |||
i2s_init.I2S_Standard = I2S_Standard_Phillips; | |||
i2s_init.I2S_DataFormat = I2S_DataFormat_16b; | |||
i2s_init.I2S_MCLKOutput = I2S_MCLKOutput_Disable; | |||
i2s_init.I2S_AudioFreq = sample_rate; | |||
i2s_init.I2S_CPOL = I2S_CPOL_Low; | |||
I2S_Init(SPI2, &i2s_init); | |||
I2S_Cmd(SPI2, ENABLE); | |||
} | |||
void AudioDac::InitializeDMA(size_t block_size) { | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); | |||
DMA_Cmd(DMA1_Channel5, DISABLE); | |||
DMA_DeInit(DMA1_Channel5); | |||
DMA_InitTypeDef dma_init; | |||
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&(SPI2->DR); | |||
dma_init.DMA_MemoryBaseAddr = (uint32_t)(&tx_dma_buffer_[0]); | |||
dma_init.DMA_DIR = DMA_DIR_PeripheralDST; | |||
dma_init.DMA_BufferSize = 2 * block_size * 2; // 2 channels, 2 half blocks. | |||
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
dma_init.DMA_Mode = DMA_Mode_Circular; | |||
dma_init.DMA_Priority = DMA_Priority_High; | |||
dma_init.DMA_M2M = DMA_M2M_Disable; | |||
DMA_Init(DMA1_Channel5, &dma_init); | |||
// Enable the interrupts: half transfer and transfer complete. | |||
DMA_ITConfig(DMA1_Channel5, DMA_IT_TC | DMA_IT_HT, ENABLE); | |||
// Enable the IRQ. | |||
NVIC_EnableIRQ(DMA1_Channel5_IRQn); | |||
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE); | |||
} | |||
void AudioDac::Start(FillBufferCallback callback) { | |||
callback_ = callback; | |||
DMA_Cmd(DMA1_Channel5, ENABLE); | |||
} | |||
void AudioDac::Stop() { | |||
DMA_Cmd(DMA1_Channel5, DISABLE); | |||
} | |||
void AudioDac::Fill(size_t offset) { | |||
(*callback_)(&tx_dma_buffer_[offset * block_size_], block_size_); | |||
} | |||
} // namespace plaits | |||
extern "C" { | |||
void DMA1_Channel5_IRQHandler(void) { | |||
uint32_t flags = DMA1->ISR; | |||
DMA1->IFCR = DMA1_FLAG_TC5 | DMA1_FLAG_HT5; | |||
if (flags & DMA1_FLAG_TC5) { | |||
plaits::AudioDac::GetInstance()->Fill(1); | |||
} else if (flags & DMA1_FLAG_HT5) { | |||
plaits::AudioDac::GetInstance()->Fill(0); | |||
} | |||
} | |||
} |
@@ -0,0 +1,73 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the PCM5100 DAC. | |||
#ifndef PLAITS_DRIVERS_DAC_H_ | |||
#define PLAITS_DRIVERS_DAC_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace plaits { | |||
const size_t kMaxCodecBlockSize = 24; | |||
class AudioDac { | |||
public: | |||
AudioDac() { } | |||
~AudioDac() { } | |||
typedef struct { | |||
short l; | |||
short r; | |||
} Frame; | |||
typedef void (*FillBufferCallback)(Frame* tx, size_t size); | |||
void Init(int sample_rate, size_t block_size); | |||
void Start(FillBufferCallback callback); | |||
void Stop(); | |||
void Fill(size_t offset); | |||
static AudioDac* GetInstance() { return instance_; } | |||
private: | |||
void InitializeGPIO(); | |||
void InitializeAudioInterface(int sample_rate); | |||
void InitializeDMA(size_t block_size); | |||
static AudioDac* instance_; | |||
size_t block_size_; | |||
FillBufferCallback callback_; | |||
Frame tx_dma_buffer_[kMaxCodecBlockSize * 2]; | |||
DISALLOW_COPY_AND_ASSIGN(AudioDac); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DRIVERS_DAC_H_ |
@@ -0,0 +1,193 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the 16-bit SDADC scanning the CVs. | |||
#include "plaits/drivers/cv_adc.h" | |||
#include <stm32f37x_conf.h> | |||
namespace plaits { | |||
struct ChannelConfiguration { | |||
CvAdcChannel map_to; | |||
uint32_t channel; | |||
GPIO_TypeDef* gpio; | |||
uint16_t pin; | |||
}; | |||
struct ConverterConfiguration { | |||
SDADC_TypeDef* sdadc; | |||
DMA_Channel_TypeDef* dma_channel; | |||
int num_channels; | |||
ChannelConfiguration channel[3]; | |||
}; | |||
const ConverterConfiguration converter_configuration[3] = { | |||
{ | |||
SDADC1, DMA2_Channel3, 3, { | |||
{ CV_ADC_CHANNEL_TIMBRE, SDADC_Channel_4, GPIOB, GPIO_Pin_2 }, | |||
{ CV_ADC_CHANNEL_MODEL, SDADC_Channel_5, GPIOB, GPIO_Pin_1 }, | |||
{ CV_ADC_CHANNEL_TRIGGER, SDADC_Channel_6, GPIOB, GPIO_Pin_0 } | |||
} | |||
}, | |||
{ | |||
SDADC2, DMA2_Channel4, 2, { | |||
{ CV_ADC_CHANNEL_FM, SDADC_Channel_7, GPIOE, GPIO_Pin_9 }, | |||
{ CV_ADC_CHANNEL_LEVEL, SDADC_Channel_8, GPIOE, GPIO_Pin_8 } | |||
} | |||
}, | |||
{ | |||
SDADC3, DMA2_Channel5, 3, { | |||
{ CV_ADC_CHANNEL_HARMONICS, SDADC_Channel_6, GPIOD, GPIO_Pin_8 }, | |||
{ CV_ADC_CHANNEL_MORPH, SDADC_Channel_7, GPIOB, GPIO_Pin_15 }, | |||
{ CV_ADC_CHANNEL_V_OCT, SDADC_Channel_8, GPIOB, GPIO_Pin_14 } | |||
} | |||
}, | |||
}; | |||
void CvAdc::Init() { | |||
// Power all the SDADCs. | |||
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); | |||
PWR_SDADCAnalogCmd(PWR_SDADCAnalog_1, ENABLE); | |||
PWR_SDADCAnalogCmd(PWR_SDADCAnalog_2, ENABLE); | |||
PWR_SDADCAnalogCmd(PWR_SDADCAnalog_3, ENABLE); | |||
// Enable SDADC clock. | |||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDADC1, ENABLE); | |||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDADC2, ENABLE); | |||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDADC3, ENABLE); | |||
RCC_SDADCCLKConfig(RCC_SDADCCLK_SYSCLK_Div12); | |||
// Enable DMA2 clock. | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE); | |||
// Enable GPIO clock. | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE); | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE, ENABLE); | |||
// Init SDADC | |||
SDADC_VREFSelect(SDADC_VREF_Ext); | |||
DMA_InitTypeDef dma_init; | |||
GPIO_InitTypeDef gpio_init; | |||
SDADC_AINStructTypeDef sdadc_ain; | |||
// Fill structures with the settings common to all channels/pins. | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
sdadc_ain.SDADC_InputMode = SDADC_InputMode_SEZeroReference; | |||
sdadc_ain.SDADC_Gain = SDADC_Gain_1; | |||
sdadc_ain.SDADC_CommonMode = SDADC_CommonMode_VDDA_2; | |||
sdadc_ain.SDADC_Offset = 0; | |||
int current_channel = 0; | |||
// Configure all SDADCs, all their input channels, and all the DMA channels. | |||
for (int i = 0; i < 3; ++i) { | |||
const ConverterConfiguration& config = converter_configuration[i]; | |||
// Wait for SDADC to stabilize. | |||
SDADC_Cmd(config.sdadc, ENABLE); | |||
while (SDADC_GetFlagStatus(config.sdadc, SDADC_FLAG_STABIP) == SET); | |||
// Configure GPIO pins. | |||
for (int j = 0; j < config.num_channels; ++j) { | |||
gpio_init.GPIO_Pin = config.channel[j].pin; | |||
GPIO_Init(config.channel[j].gpio, &gpio_init); | |||
} | |||
// SDADC enters initialization mode. | |||
SDADC_InitModeCmd(config.sdadc, ENABLE); | |||
while (SDADC_GetFlagStatus(config.sdadc, SDADC_FLAG_INITRDY) == RESET); | |||
// Configure DMA to read injected values into a slice of the | |||
// values_ array. | |||
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&(config.sdadc->JDATAR); | |||
dma_init.DMA_MemoryBaseAddr = (uint32_t)(&values_[current_channel]); | |||
dma_init.DMA_DIR = DMA_DIR_PeripheralSRC; | |||
dma_init.DMA_BufferSize = config.num_channels; | |||
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
dma_init.DMA_Mode = DMA_Mode_Circular; | |||
dma_init.DMA_Priority = DMA_Priority_High; | |||
dma_init.DMA_M2M = DMA_M2M_Disable; | |||
DMA_Init(config.dma_channel, &dma_init); | |||
// Create a configuration and assign it to all channels used by this SDADC. | |||
SDADC_AINInit(config.sdadc, SDADC_Conf_0, &sdadc_ain); | |||
uint32_t channels = 0; | |||
for (int j = 0; j < config.num_channels; ++j) { | |||
channel_map_[config.channel[j].map_to] = current_channel++; | |||
channels |= config.channel[j].channel; | |||
SDADC_ChannelConfig( | |||
config.sdadc, config.channel[j].channel, SDADC_Conf_0); | |||
} | |||
// Select injected channels. | |||
SDADC_InjectedChannelSelect(config.sdadc, channels); | |||
// Disable continuous mode - the conversions are restarted every time | |||
// we render a block of samples. | |||
SDADC_InjectedContinuousModeCmd(config.sdadc, DISABLE); | |||
// Terminate initialization sequence. | |||
SDADC_CalibrationSequenceConfig(config.sdadc, SDADC_CalibrationSequence_3); | |||
SDADC_InitModeCmd(config.sdadc, DISABLE); | |||
while (SDADC_GetFlagStatus(config.sdadc, SDADC_FLAG_INITRDY) == SET); | |||
// Run calibration sequence. | |||
SDADC_StartCalibration(config.sdadc); | |||
while (SDADC_GetFlagStatus(config.sdadc, SDADC_FLAG_EOCAL) == RESET); | |||
// Enable DMA. | |||
DMA_Cmd(config.dma_channel, ENABLE); | |||
SDADC_DMAConfig(config.sdadc, SDADC_DMATransfer_Injected, ENABLE); | |||
} | |||
Convert(); | |||
} | |||
void CvAdc::DeInit() { | |||
for (int i = 0; i < 3; ++i) { | |||
const ConverterConfiguration& config = converter_configuration[i]; | |||
SDADC_Cmd(config.sdadc, DISABLE); | |||
SDADC_DMAConfig(config.sdadc, SDADC_DMATransfer_Injected, DISABLE); | |||
DMA_Cmd(config.dma_channel, DISABLE); | |||
} | |||
} | |||
void CvAdc::Convert() { | |||
SDADC_SoftwareStartInjectedConv(SDADC1); | |||
SDADC_SoftwareStartInjectedConv(SDADC2); | |||
SDADC_SoftwareStartInjectedConv(SDADC3); | |||
} | |||
} // namespace plaits |
@@ -0,0 +1,74 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the 16-bit SDADC scanning the CV inputs. | |||
#ifndef PLAITS_DRIVERS_CV_ADC_H_ | |||
#define PLAITS_DRIVERS_CV_ADC_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace plaits { | |||
enum CvAdcChannel { | |||
CV_ADC_CHANNEL_MODEL, | |||
CV_ADC_CHANNEL_V_OCT, | |||
CV_ADC_CHANNEL_FM, | |||
CV_ADC_CHANNEL_HARMONICS, | |||
CV_ADC_CHANNEL_TIMBRE, | |||
CV_ADC_CHANNEL_MORPH, | |||
CV_ADC_CHANNEL_TRIGGER, | |||
CV_ADC_CHANNEL_LEVEL, | |||
CV_ADC_CHANNEL_LAST | |||
}; | |||
class CvAdc { | |||
public: | |||
CvAdc() { } | |||
~CvAdc() { } | |||
void Init(); | |||
void DeInit(); | |||
void Convert(); | |||
inline int16_t value(CvAdcChannel channel) const { | |||
return values_[channel_map_[channel]]; | |||
} | |||
inline float float_value(CvAdcChannel channel) const { | |||
return static_cast<float>(value(channel)) / 32768.0f; | |||
} | |||
private: | |||
int channel_map_[CV_ADC_CHANNEL_LAST]; | |||
int16_t values_[CV_ADC_CHANNEL_LAST]; | |||
DISALLOW_COPY_AND_ASSIGN(CvAdc); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DRIVERS_CV_ADC_H_ |
@@ -0,0 +1,72 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the debug (timing) pin. | |||
#ifndef PLAITS_DRIVERS_DEBUG_PIN_H_ | |||
#define PLAITS_DRIVERS_DEBUG_PIN_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <stm32f37x_conf.h> | |||
namespace plaits { | |||
class DebugPin { | |||
public: | |||
DebugPin() { } | |||
~DebugPin() { } | |||
static void Init() { | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = GPIO_Pin_8; | |||
GPIO_Init(GPIOB, &gpio_init); | |||
} | |||
static inline void High() { | |||
GPIOB->BSRR = GPIO_Pin_8; | |||
} | |||
static inline void Low() { | |||
GPIOB->BRR = GPIO_Pin_8; | |||
} | |||
private: | |||
DISALLOW_COPY_AND_ASSIGN(DebugPin); | |||
}; | |||
#define TIC DebugPin::High(); | |||
#define TOC DebugPin::Low(); | |||
} // namespace plaits | |||
#endif // PLAITS_DRIVERS_DEBUG_PIN_H_ |
@@ -0,0 +1,62 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// UART driver for conversing with the factory testing program. | |||
#include "plaits/drivers/debug_port.h" | |||
namespace plaits { | |||
void DebugPort::Init() { | |||
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
// Initialize TX and RX pins. | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
GPIO_Init(GPIOB, &gpio_init); | |||
GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_7); | |||
GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_7); | |||
// Initialize USART. | |||
USART_InitTypeDef usart_init; | |||
usart_init.USART_BaudRate = 9600; | |||
usart_init.USART_WordLength = USART_WordLength_8b; | |||
usart_init.USART_StopBits = USART_StopBits_1; | |||
usart_init.USART_Parity = USART_Parity_No; | |||
usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None; | |||
usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; | |||
USART_Init(USART3, &usart_init); | |||
USART_Cmd(USART3, ENABLE); | |||
} | |||
} // namespace plaits |
@@ -0,0 +1,67 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// UART driver for conversing with the factory testing program. | |||
#ifndef PLAITS_DRIVERS_DEBUG_PORT_H_ | |||
#define PLAITS_DRIVERS_DEBUG_PORT_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <stm32f37x_conf.h> | |||
namespace plaits { | |||
class DebugPort { | |||
public: | |||
DebugPort() { } | |||
~DebugPort() { } | |||
void Init(); | |||
bool writable() { | |||
return USART3->ISR & USART_FLAG_TXE; | |||
} | |||
bool readable() { | |||
return USART3->ISR & USART_FLAG_RXNE; | |||
} | |||
void Write(uint8_t byte) { | |||
USART3->TDR = byte; | |||
} | |||
uint8_t Read() { | |||
return USART3->RDR; | |||
} | |||
private: | |||
DISALLOW_COPY_AND_ASSIGN(DebugPort); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DRIVERS_DEBUG_PORT_H_ |
@@ -0,0 +1,111 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the 12-bit ADC used for firmware updates through the MODEL CV in. | |||
#include "plaits/drivers/firmware_update_adc.h" | |||
#include <stm32f37x_conf.h> | |||
namespace plaits { | |||
void FirmwareUpdateAdc::Init() { | |||
// Enable ADC clock. | |||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); | |||
RCC_ADCCLKConfig(RCC_PCLK2_Div8); | |||
// Enable GPIO clock. | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
// Enable DMA1 clock. | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); | |||
DMA_InitTypeDef dma_init; | |||
ADC_InitTypeDef adc_init; | |||
GPIO_InitTypeDef gpio_init; | |||
// Configure the two analog inputs. | |||
gpio_init.GPIO_Pin = GPIO_Pin_1; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
GPIO_Init(GPIOB, &gpio_init); | |||
gpio_init.GPIO_Pin = GPIO_Pin_0; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
// Use DMA to automatically copy ADC data register to values_ buffer. | |||
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; | |||
dma_init.DMA_MemoryBaseAddr = (uint32_t)&values_[0]; | |||
dma_init.DMA_DIR = DMA_DIR_PeripheralSRC; | |||
dma_init.DMA_BufferSize = 2; | |||
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
dma_init.DMA_Mode = DMA_Mode_Circular; | |||
dma_init.DMA_Priority = DMA_Priority_High; | |||
dma_init.DMA_M2M = DMA_M2M_Disable; | |||
DMA_Init(DMA1_Channel1, &dma_init); | |||
DMA_Cmd(DMA1_Channel1, ENABLE); | |||
// Init ADC1. | |||
adc_init.ADC_ScanConvMode = ENABLE; | |||
adc_init.ADC_ContinuousConvMode = DISABLE; | |||
adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; | |||
adc_init.ADC_DataAlign = ADC_DataAlign_Left; | |||
adc_init.ADC_NbrOfChannel = 2; | |||
ADC_Init(ADC1, &adc_init); | |||
// Sample rate: 53.6 kHz | |||
// 72000 / 6 / (12.5 * 2 + 71.5 * 2) | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_71Cycles5); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 2, ADC_SampleTime_71Cycles5); | |||
// Enable and calibrate ADC1. | |||
ADC_Cmd(ADC1, ENABLE); | |||
ADC_ResetCalibration(ADC1); | |||
while (ADC_GetResetCalibrationStatus(ADC1)); | |||
ADC_StartCalibration(ADC1); | |||
while (ADC_GetCalibrationStatus(ADC1)); | |||
ADC_DMACmd(ADC1, ENABLE); | |||
values_[0] = values_[1] = 32768; | |||
Convert(); | |||
} | |||
void FirmwareUpdateAdc::DeInit() { | |||
ADC_DMACmd(ADC1, DISABLE); | |||
ADC_Cmd(ADC1, DISABLE); | |||
DMA_Cmd(DMA1_Channel1, DISABLE); | |||
} | |||
void FirmwareUpdateAdc::Convert() { | |||
ADC_SoftwareStartConv(ADC1); | |||
} | |||
} // namespace plaits |
@@ -0,0 +1,60 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the 12-bit ADC used for firmware updates through the MODEL CV in. | |||
#ifndef PLAITS_DRIVERS_FIRMWARE_UPDATE_ADC_H_ | |||
#define PLAITS_DRIVERS_FIRMWARE_UPDATE_ADC_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace plaits { | |||
class FirmwareUpdateAdc { | |||
public: | |||
FirmwareUpdateAdc() { } | |||
~FirmwareUpdateAdc() { } | |||
void Init(); | |||
void DeInit(); | |||
void Convert(); | |||
inline uint16_t gain_pot() const { | |||
return values_[0]; | |||
} | |||
inline uint16_t sample() const { | |||
return values_[1]; | |||
} | |||
private: | |||
uint16_t values_[2]; | |||
DISALLOW_COPY_AND_ASSIGN(FirmwareUpdateAdc); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DRIVERS_FIRMWARE_UPDATE_ADC_H_ |
@@ -0,0 +1,114 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the column of LEDs. | |||
#include "plaits/drivers/leds.h" | |||
#include <algorithm> | |||
#include <stm32f37x_conf.h> | |||
namespace plaits { | |||
using namespace std; | |||
const uint16_t kPinEnable = GPIO_Pin_7; | |||
void Leds::Init() { | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOF, ENABLE); | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); | |||
GPIO_InitTypeDef gpio_init; | |||
// SS | |||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = kPinEnable; | |||
GPIO_Init(GPIOF, &gpio_init); | |||
// MOSI | |||
gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = GPIO_Pin_6; | |||
GPIO_Init(GPIOF, &gpio_init); | |||
GPIO_PinAFConfig(GPIOF, GPIO_PinSource6, GPIO_AF_5); | |||
// SCK | |||
gpio_init.GPIO_Mode = GPIO_Mode_AF; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = GPIO_Pin_12; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource12, GPIO_AF_6); | |||
// Initialize SPI | |||
SPI_InitTypeDef spi_init; | |||
spi_init.SPI_Direction = SPI_Direction_2Lines_FullDuplex; | |||
spi_init.SPI_Mode = SPI_Mode_Master; | |||
spi_init.SPI_DataSize = SPI_DataSize_16b; | |||
spi_init.SPI_CPOL = SPI_CPOL_Low; | |||
spi_init.SPI_CPHA = SPI_CPHA_1Edge; | |||
spi_init.SPI_NSS = SPI_NSS_Soft; | |||
spi_init.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; | |||
spi_init.SPI_FirstBit = SPI_FirstBit_MSB; | |||
spi_init.SPI_CRCPolynomial = 7; | |||
SPI_Init(SPI1, &spi_init); | |||
SPI_Cmd(SPI1, ENABLE); | |||
Clear(); | |||
} | |||
void Leds::Clear() { | |||
fill(&colors_[0], &colors_[kNumLEDs], LED_COLOR_OFF); | |||
} | |||
/* static */ | |||
const int Leds::led_map_[8] = { | |||
0, 1, 2, 3, 7, 6, 5, 4 | |||
}; | |||
void Leds::Write() { | |||
uint16_t leds_data = 0; | |||
for (int i = 0; i < kNumLEDs; ++i) { | |||
int j = led_map_[i]; | |||
leds_data <<= 2; | |||
leds_data |= (colors_[j] & LED_COLOR_RED) ? 1 : 0; | |||
leds_data |= (colors_[j] & LED_COLOR_GREEN) ? 2 : 0; | |||
} | |||
GPIOF->BSRR = kPinEnable; | |||
__asm__("nop"); | |||
GPIOF->BRR = kPinEnable; | |||
__asm__("nop"); | |||
SPI1->DR = leds_data; | |||
} | |||
} // namespace plaits |
@@ -0,0 +1,72 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the column of LEDs. | |||
#ifndef PLAITS_DRIVERS_LEDS_H_ | |||
#define PLAITS_DRIVERS_LEDS_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace plaits { | |||
const int kNumLEDs = 8; | |||
enum LedColor { | |||
LED_COLOR_OFF = 0, | |||
LED_COLOR_RED = 0xff0000, | |||
LED_COLOR_GREEN = 0x00ff00, | |||
LED_COLOR_YELLOW = 0xffff00, | |||
}; | |||
class Leds { | |||
public: | |||
Leds() { } | |||
~Leds() { } | |||
void Init(); | |||
void Write(); | |||
void Clear(); | |||
void set(int index, uint32_t color) { | |||
colors_[index] = color; | |||
} | |||
void mask(int index, uint32_t color) { | |||
colors_[index] |= color; | |||
} | |||
private: | |||
uint32_t colors_[kNumLEDs]; | |||
static const int led_map_[kNumLEDs]; | |||
DISALLOW_COPY_AND_ASSIGN(Leds); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DRIVERS_LEDS_H_ |
@@ -0,0 +1,87 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the normalization probe. | |||
#ifndef PLAITS_DRIVERS_NORMALIZATION_PROBE_H_ | |||
#define PLAITS_DRIVERS_NORMALIZATION_PROBE_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <stm32f37x_conf.h> | |||
namespace plaits { | |||
class NormalizationProbe { | |||
public: | |||
NormalizationProbe() { } | |||
~NormalizationProbe() { } | |||
static inline void Init() { | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_OUT; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = GPIO_Pin_9; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
} | |||
void Disable() { | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_Pin = GPIO_Pin_9; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
} | |||
static inline void High() { | |||
GPIOA->BSRR = GPIO_Pin_9; | |||
} | |||
static inline void Low() { | |||
GPIOA->BRR = GPIO_Pin_9; | |||
} | |||
static inline void Write(bool value) { | |||
if (value) { | |||
High(); | |||
} else { | |||
Low(); | |||
} | |||
} | |||
private: | |||
DISALLOW_COPY_AND_ASSIGN(NormalizationProbe); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DRIVERS_NORMALIZATION_PROBE_H_ |
@@ -0,0 +1,111 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the 12-bit ADC scanning pots. | |||
#include "plaits/drivers/pots_adc.h" | |||
#include <algorithm> | |||
#include <stm32f37x_conf.h> | |||
namespace plaits { | |||
using namespace std; | |||
void PotsAdc::Init() { | |||
// Enable ADC clock. | |||
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); | |||
RCC_ADCCLKConfig(RCC_PCLK2_Div8); | |||
// Enable GPIO clock. | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); | |||
// Enable DMA1 clock. | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); | |||
DMA_InitTypeDef dma_init; | |||
ADC_InitTypeDef adc_init; | |||
GPIO_InitTypeDef gpio_init; | |||
// Configure the two analog inputs. | |||
gpio_init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | \ | |||
GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_NOPULL; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Mode = GPIO_Mode_AN; | |||
GPIO_Init(GPIOA, &gpio_init); | |||
// Use DMA to automatically copy ADC data register to values_ buffer. | |||
dma_init.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; | |||
dma_init.DMA_MemoryBaseAddr = (uint32_t)&values_[0]; | |||
dma_init.DMA_DIR = DMA_DIR_PeripheralSRC; | |||
dma_init.DMA_BufferSize = POTS_ADC_CHANNEL_LAST; | |||
dma_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | |||
dma_init.DMA_MemoryInc = DMA_MemoryInc_Enable; | |||
dma_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; | |||
dma_init.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; | |||
dma_init.DMA_Mode = DMA_Mode_Circular; | |||
dma_init.DMA_Priority = DMA_Priority_High; | |||
dma_init.DMA_M2M = DMA_M2M_Disable; | |||
DMA_Init(DMA1_Channel1, &dma_init); | |||
DMA_Cmd(DMA1_Channel1, ENABLE); | |||
// Init ADC1. | |||
adc_init.ADC_ScanConvMode = ENABLE; | |||
adc_init.ADC_ContinuousConvMode = DISABLE; | |||
adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; | |||
adc_init.ADC_DataAlign = ADC_DataAlign_Left; | |||
adc_init.ADC_NbrOfChannel = POTS_ADC_CHANNEL_LAST; | |||
ADC_Init(ADC1, &adc_init); | |||
// Sample rate: 5.10 kHz > 4kHz | |||
// 72000 / 8 / ((12.5 + 239.5) * 7) | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_239Cycles5); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 3, ADC_SampleTime_239Cycles5); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 4, ADC_SampleTime_239Cycles5); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 5, ADC_SampleTime_239Cycles5); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 6, ADC_SampleTime_239Cycles5); | |||
ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 7, ADC_SampleTime_239Cycles5); | |||
// Enable and calibrate ADC1. | |||
ADC_Cmd(ADC1, ENABLE); | |||
ADC_ResetCalibration(ADC1); | |||
while (ADC_GetResetCalibrationStatus(ADC1)); | |||
ADC_StartCalibration(ADC1); | |||
while (ADC_GetCalibrationStatus(ADC1)); | |||
ADC_DMACmd(ADC1, ENABLE); | |||
fill(&values_[0], &values_[POTS_ADC_CHANNEL_LAST], 32768); | |||
Convert(); | |||
} | |||
void PotsAdc::Convert() { | |||
ADC_SoftwareStartConv(ADC1); | |||
} | |||
} // namespace plaits |
@@ -0,0 +1,71 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Drivers for the 12-bit ADC scanning pots. | |||
#ifndef PLAITS_DRIVERS_POTS_ADC_H_ | |||
#define PLAITS_DRIVERS_POTS_ADC_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace plaits { | |||
enum PotsAdcChannel { | |||
POTS_ADC_CHANNEL_FREQ_POT, | |||
POTS_ADC_CHANNEL_HARMONICS_POT, | |||
POTS_ADC_CHANNEL_TIMBRE_POT, | |||
POTS_ADC_CHANNEL_MORPH_POT, | |||
POTS_ADC_CHANNEL_TIMBRE_ATTENUVERTER, | |||
POTS_ADC_CHANNEL_FM_ATTENUVERTER, | |||
POTS_ADC_CHANNEL_MORPH_ATTENUVERTER, | |||
POTS_ADC_CHANNEL_LAST | |||
}; | |||
class PotsAdc { | |||
public: | |||
PotsAdc() { } | |||
~PotsAdc() { } | |||
void Init(); | |||
void Convert(); | |||
inline int32_t value(PotsAdcChannel channel) const { | |||
return static_cast<int32_t>(values_[channel]); | |||
} | |||
inline float float_value(PotsAdcChannel channel) const { | |||
return static_cast<float>(value(channel)) / 65536.0f; | |||
} | |||
private: | |||
uint16_t values_[POTS_ADC_CHANNEL_LAST]; | |||
DISALLOW_COPY_AND_ASSIGN(PotsAdc); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DRIVERS_POTS_ADC_H_ |
@@ -0,0 +1,72 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the 2 switches. | |||
#include "plaits/drivers/switches.h" | |||
#include <algorithm> | |||
namespace plaits { | |||
using namespace std; | |||
struct SwitchDefinition { | |||
GPIO_TypeDef* gpio; | |||
uint16_t pin; | |||
}; | |||
const SwitchDefinition switch_definitions[] = { | |||
{ GPIOB, GPIO_Pin_7 }, | |||
{ GPIOB, GPIO_Pin_6 }, | |||
}; | |||
void Switches::Init() { | |||
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); | |||
GPIO_InitTypeDef gpio_init; | |||
gpio_init.GPIO_Mode = GPIO_Mode_IN; | |||
gpio_init.GPIO_OType = GPIO_OType_PP; | |||
gpio_init.GPIO_Speed = GPIO_Speed_2MHz; | |||
gpio_init.GPIO_PuPd = GPIO_PuPd_UP; | |||
for (int i = 0; i < SWITCH_LAST; ++i) { | |||
const SwitchDefinition& definition = switch_definitions[i]; | |||
gpio_init.GPIO_Pin = definition.pin; | |||
GPIO_Init(definition.gpio, &gpio_init); | |||
} | |||
fill(&switch_state_[0], &switch_state_[SWITCH_LAST], 0xff); | |||
} | |||
void Switches::Debounce() { | |||
for (int i = 0; i < SWITCH_LAST; ++i) { | |||
const SwitchDefinition& definition = switch_definitions[i]; | |||
switch_state_[i] = (switch_state_[i] << 1) | \ | |||
GPIO_ReadInputDataBit(definition.gpio, definition.pin); | |||
} | |||
} | |||
} // namespace plaits |
@@ -0,0 +1,82 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Driver for the 2 switches. | |||
#ifndef PLAITS_DRIVERS_SWITCHES_H_ | |||
#define PLAITS_DRIVERS_SWITCHES_H_ | |||
#include "stmlib/stmlib.h" | |||
#include <stm32f37x_conf.h> | |||
namespace plaits { | |||
enum Switch { | |||
SWITCH_ROW_1, | |||
SWITCH_ROW_2, | |||
SWITCH_LAST | |||
}; | |||
class Switches { | |||
public: | |||
Switches() { } | |||
~Switches() { } | |||
void Init(); | |||
void Debounce(); | |||
inline bool released(Switch s) const { | |||
return switch_state_[s] == 0x7f; | |||
} | |||
inline bool just_pressed(Switch s) const { | |||
return switch_state_[s] == 0x80; | |||
} | |||
inline bool pressed(Switch s) const { | |||
return switch_state_[s] == 0x00; | |||
} | |||
inline bool pressed_immediate(Switch s) const { | |||
if (s == SWITCH_ROW_1) { | |||
return !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7); | |||
} else if (s == SWITCH_ROW_2) { | |||
return !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6); | |||
} else { | |||
return false; | |||
} | |||
} | |||
private: | |||
uint8_t switch_state_[SWITCH_LAST]; | |||
DISALLOW_COPY_AND_ASSIGN(Switches); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DRIVERS_SWITCHES_H_ |
@@ -0,0 +1,195 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// 808 bass drum model, revisited. | |||
#ifndef PLAITS_DSP_DRUMS_ANALOG_BASS_DRUM_H_ | |||
#define PLAITS_DSP_DRUMS_ANALOG_BASS_DRUM_H_ | |||
#include <algorithm> | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/dsp/filter.h" | |||
#include "stmlib/dsp/parameter_interpolator.h" | |||
#include "stmlib/dsp/units.h" | |||
#include "plaits/dsp/dsp.h" | |||
#include "plaits/dsp/oscillator/sine_oscillator.h" | |||
namespace plaits { | |||
class AnalogBassDrum { | |||
public: | |||
AnalogBassDrum() { } | |||
~AnalogBassDrum() { } | |||
void Init() { | |||
pulse_remaining_samples_ = 0; | |||
fm_pulse_remaining_samples_ = 0; | |||
pulse_ = 0.0f; | |||
pulse_height_ = 0.0f; | |||
pulse_lp_ = 0.0f; | |||
fm_pulse_lp_ = 0.0f; | |||
retrig_pulse_ = 0.0f; | |||
lp_out_ = 0.0f; | |||
tone_lp_ = 0.0f; | |||
sustain_gain_ = 0.0f; | |||
resonator_.Init(); | |||
oscillator_.Init(); | |||
} | |||
inline float Diode(float x) { | |||
if (x >= 0.0f) { | |||
return x; | |||
} else { | |||
x *= 2.0f; | |||
return 0.7f * x / (1.0f + fabsf(x)); | |||
} | |||
} | |||
void Render( | |||
bool sustain, | |||
bool trigger, | |||
float accent, | |||
float f0, | |||
float tone, | |||
float decay, | |||
float attack_fm_amount, | |||
float self_fm_amount, | |||
float* out, | |||
size_t size) { | |||
const int kTriggerPulseDuration = 1.0e-3 * kSampleRate; | |||
const int kFMPulseDuration = 6.0e-3 * kSampleRate; | |||
const float kPulseDecayTime = 0.2e-3 * kSampleRate; | |||
const float kPulseFilterTime = 0.1e-3 * kSampleRate; | |||
const float kRetrigPulseDuration = 0.05f * kSampleRate; | |||
const float scale = 0.001f / f0; | |||
const float q = 1500.0f * stmlib::SemitonesToRatio(decay * 80.0f); | |||
const float tone_f = std::min( | |||
4.0f * f0 * stmlib::SemitonesToRatio(tone * 108.0f), | |||
1.0f); | |||
const float exciter_leak = 0.08f * (tone + 0.25f); | |||
if (trigger) { | |||
pulse_remaining_samples_ = kTriggerPulseDuration; | |||
fm_pulse_remaining_samples_ = kFMPulseDuration; | |||
pulse_height_ = 3.0f + 7.0f * accent; | |||
lp_out_ = 0.0f; | |||
} | |||
stmlib::ParameterInterpolator sustain_gain( | |||
&sustain_gain_, | |||
accent * decay, | |||
size); | |||
while (size--) { | |||
// Q39 / Q40 | |||
float pulse = 0.0f; | |||
if (pulse_remaining_samples_) { | |||
--pulse_remaining_samples_; | |||
pulse = pulse_remaining_samples_ ? pulse_height_ : pulse_height_ - 1.0f; | |||
pulse_ = pulse; | |||
} else { | |||
pulse_ *= 1.0f - 1.0f / kPulseDecayTime; | |||
pulse = pulse_; | |||
} | |||
if (sustain) { | |||
pulse = 0.0f; | |||
} | |||
// C40 / R163 / R162 / D83 | |||
ONE_POLE(pulse_lp_, pulse, 1.0f / kPulseFilterTime); | |||
pulse = Diode((pulse - pulse_lp_) + pulse * 0.044f); | |||
// Q41 / Q42 | |||
float fm_pulse = 0.0f; | |||
if (fm_pulse_remaining_samples_) { | |||
--fm_pulse_remaining_samples_; | |||
fm_pulse = 1.0f; | |||
// C39 / C52 | |||
retrig_pulse_ = fm_pulse_remaining_samples_ ? 0.0f : -0.8f; | |||
} else { | |||
// C39 / R161 | |||
retrig_pulse_ *= 1.0f - 1.0f / kRetrigPulseDuration; | |||
} | |||
if (sustain) { | |||
fm_pulse = 0.0f; | |||
} | |||
ONE_POLE(fm_pulse_lp_, fm_pulse, 1.0f / kPulseFilterTime); | |||
// Q43 and R170 leakage | |||
float punch = 0.7f + Diode(10.0f * lp_out_ - 1.0f); | |||
// Q43 / R165 | |||
float attack_fm = fm_pulse_lp_ * 1.7f * attack_fm_amount; | |||
float self_fm = punch * 0.08f * self_fm_amount; | |||
float f = f0 * (1.0f + attack_fm + self_fm); | |||
CONSTRAIN(f, 0.0f, 0.4f); | |||
float resonator_out; | |||
if (sustain) { | |||
oscillator_.Next(f, sustain_gain.Next(), &resonator_out, &lp_out_); | |||
} else { | |||
resonator_.set_f_q<stmlib::FREQUENCY_DIRTY>(f, 1.0f + q * f); | |||
resonator_.Process<stmlib::FILTER_MODE_BAND_PASS, | |||
stmlib::FILTER_MODE_LOW_PASS>( | |||
(pulse - retrig_pulse_ * 0.2f) * scale, | |||
&resonator_out, | |||
&lp_out_); | |||
} | |||
ONE_POLE(tone_lp_, pulse * exciter_leak + resonator_out, tone_f); | |||
*out++ = tone_lp_; | |||
} | |||
} | |||
private: | |||
int pulse_remaining_samples_; | |||
int fm_pulse_remaining_samples_; | |||
float pulse_; | |||
float pulse_height_; | |||
float pulse_lp_; | |||
float fm_pulse_lp_; | |||
float retrig_pulse_; | |||
float lp_out_; | |||
float tone_lp_; | |||
float sustain_gain_; | |||
stmlib::Svf resonator_; | |||
// Replace the resonator in "free running" (sustain) mode. | |||
SineOscillator oscillator_; | |||
DISALLOW_COPY_AND_ASSIGN(AnalogBassDrum); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DSP_DRUMS_ANALOG_BASS_DRUM_H_ |
@@ -0,0 +1,201 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// 808 snare drum model, revisited. | |||
#ifndef PLAITS_DSP_DRUMS_ANALOG_SNARE_DRUM_H_ | |||
#define PLAITS_DSP_DRUMS_ANALOG_SNARE_DRUM_H_ | |||
#include <algorithm> | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/dsp/filter.h" | |||
#include "stmlib/dsp/parameter_interpolator.h" | |||
#include "stmlib/dsp/units.h" | |||
#include "stmlib/utils/random.h" | |||
#include "plaits/dsp/dsp.h" | |||
#include "plaits/dsp/oscillator/sine_oscillator.h" | |||
namespace plaits { | |||
class AnalogSnareDrum { | |||
public: | |||
AnalogSnareDrum() { } | |||
~AnalogSnareDrum() { } | |||
static const int kNumModes = 5; | |||
void Init() { | |||
pulse_remaining_samples_ = 0; | |||
pulse_ = 0.0f; | |||
pulse_height_ = 0.0f; | |||
pulse_lp_ = 0.0f; | |||
noise_envelope_ = 0.0f; | |||
sustain_gain_ = 0.0f; | |||
for (int i = 0; i < kNumModes; ++i) { | |||
resonator_[i].Init(); | |||
oscillator_[i].Init(); | |||
} | |||
noise_filter_.Init(); | |||
} | |||
void Render( | |||
bool sustain, | |||
bool trigger, | |||
float accent, | |||
float f0, | |||
float tone, | |||
float decay, | |||
float snappy, | |||
float* out, | |||
size_t size) { | |||
const float decay_xt = decay * (1.0f + decay * (decay - 1.0f)); | |||
const int kTriggerPulseDuration = 1.0e-3 * kSampleRate; | |||
const float kPulseDecayTime = 0.1e-3 * kSampleRate; | |||
const float q = 2000.0f * stmlib::SemitonesToRatio(decay_xt * 84.0f); | |||
const float noise_envelope_decay = 1.0f - 0.0017f * \ | |||
stmlib::SemitonesToRatio(-decay * (50.0f + snappy * 10.0f)); | |||
const float exciter_leak = snappy * (2.0f - snappy) * 0.1f; | |||
snappy = snappy * 1.1f - 0.05f; | |||
CONSTRAIN(snappy, 0.0f, 1.0f); | |||
if (trigger) { | |||
pulse_remaining_samples_ = kTriggerPulseDuration; | |||
pulse_height_ = 3.0f + 7.0f * accent; | |||
noise_envelope_ = 2.0f; | |||
} | |||
static const float kModeFrequencies[kNumModes] = { | |||
1.00f, | |||
2.00f, | |||
3.18f, | |||
4.16f, | |||
5.62f}; | |||
float f[kNumModes]; | |||
float gain[kNumModes]; | |||
for (int i = 0; i < kNumModes; ++i) { | |||
f[i] = std::min(f0 * kModeFrequencies[i], 0.499f); | |||
resonator_[i].set_f_q<stmlib::FREQUENCY_FAST>( | |||
f[i], | |||
1.0f + f[i] * (i == 0 ? q : q * 0.25f)); | |||
} | |||
if (tone < 0.666667f) { | |||
// 808-style (2 modes) | |||
tone *= 1.5f; | |||
gain[0] = 1.5f + (1.0f - tone) * (1.0f - tone) * 4.5f; | |||
gain[1] = 2.0f * tone + 0.15f; | |||
std::fill(&gain[2], &gain[kNumModes], 0.0f); | |||
} else { | |||
// What the 808 could have been if there were extra modes! | |||
tone = (tone - 0.666667f) * 3.0f; | |||
gain[0] = 1.5f - tone * 0.5f; | |||
gain[1] = 2.15f - tone * 0.7f; | |||
for (int i = 2; i < kNumModes; ++i) { | |||
gain[i] = tone; | |||
tone *= tone; | |||
} | |||
} | |||
float f_noise = f0 * 16.0f; | |||
CONSTRAIN(f_noise, 0.0f, 0.499f); | |||
noise_filter_.set_f_q<stmlib::FREQUENCY_FAST>( | |||
f_noise, 1.0f + f_noise * 1.5f); | |||
stmlib::ParameterInterpolator sustain_gain( | |||
&sustain_gain_, | |||
accent * decay, | |||
size); | |||
while (size--) { | |||
// Q45 / Q46 | |||
float pulse = 0.0f; | |||
if (pulse_remaining_samples_) { | |||
--pulse_remaining_samples_; | |||
pulse = pulse_remaining_samples_ ? pulse_height_ : pulse_height_ - 1.0f; | |||
pulse_ = pulse; | |||
} else { | |||
pulse_ *= 1.0f - 1.0f / kPulseDecayTime; | |||
pulse = pulse_; | |||
} | |||
float sustain_gain_value = sustain_gain.Next(); | |||
// R189 / C57 / R190 + C58 / C59 / R197 / R196 / IC14 | |||
ONE_POLE(pulse_lp_, pulse, 0.75f); | |||
float shell = 0.0f; | |||
for (int i = 0; i < kNumModes; ++i) { | |||
float excitation = i == 0 | |||
? (pulse - pulse_lp_) + 0.006f * pulse | |||
: 0.026f * pulse; | |||
shell += gain[i] * (sustain | |||
? oscillator_[i].Next(f[i]) * sustain_gain_value * 0.25f | |||
: resonator_[i].Process<stmlib::FILTER_MODE_BAND_PASS>( | |||
excitation) + excitation * exciter_leak); | |||
} | |||
shell = stmlib::SoftClip(shell); | |||
// C56 / R194 / Q48 / C54 / R188 / D54 | |||
float noise = 2.0f * stmlib::Random::GetFloat() - 1.0f; | |||
if (noise < 0.0f) noise = 0.0f; | |||
noise_envelope_ *= noise_envelope_decay; | |||
noise *= (sustain ? sustain_gain_value : noise_envelope_) * snappy * 2.0f; | |||
// C66 / R201 / C67 / R202 / R203 / Q49 | |||
noise = noise_filter_.Process<stmlib::FILTER_MODE_BAND_PASS>(noise); | |||
// IC13 | |||
*out++ = noise + shell * (1.0f - snappy); | |||
} | |||
} | |||
private: | |||
int pulse_remaining_samples_; | |||
float pulse_; | |||
float pulse_height_; | |||
float pulse_lp_; | |||
float noise_envelope_; | |||
float sustain_gain_; | |||
stmlib::Svf resonator_[kNumModes]; | |||
stmlib::Svf noise_filter_; | |||
// Replace the resonators in "free running" (sustain) mode. | |||
SineOscillator oscillator_[kNumModes]; | |||
DISALLOW_COPY_AND_ASSIGN(AnalogSnareDrum); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DSP_DRUMS_ANALOG_SNARE_DRUM_H_ |
@@ -0,0 +1,259 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// 808 HH, with a few extra parameters to push things to the CY territory... | |||
// The template parameter MetallicNoiseSource allows another kind of "metallic | |||
// noise" to be used, for results which are more similar to KR-55 or FM hi-hats. | |||
#ifndef PLAITS_DSP_DRUMS_HI_HAT_H_ | |||
#define PLAITS_DSP_DRUMS_HI_HAT_H_ | |||
#include <algorithm> | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/dsp/filter.h" | |||
#include "stmlib/dsp/parameter_interpolator.h" | |||
#include "stmlib/dsp/units.h" | |||
#include "stmlib/utils/random.h" | |||
#include "plaits/dsp/dsp.h" | |||
#include "plaits/dsp/oscillator/oscillator.h" | |||
namespace plaits { | |||
// 808 style "metallic noise" with 6 square oscillators. | |||
class SquareNoise { | |||
public: | |||
SquareNoise() { } | |||
~SquareNoise() { } | |||
void Init() { | |||
std::fill(&phase_[0], &phase_[6], 0); | |||
} | |||
void Render(float f0, float* temp_1, float* temp_2, float* out, size_t size) { | |||
const float ratios[6] = { | |||
// Nominal f0: 414 Hz | |||
1.0f, 1.304f, 1.466f, 1.787f, 1.932f, 2.536f | |||
}; | |||
uint32_t increment[6]; | |||
uint32_t phase[6]; | |||
for (int i = 0; i < 6; ++i) { | |||
float f = f0 * ratios[i]; | |||
if (f >= 0.499f) f = 0.499f; | |||
increment[i] = static_cast<uint32_t>(f * 4294967296.0f); | |||
phase[i] = phase_[i]; | |||
} | |||
while (size--) { | |||
phase[0] += increment[0]; | |||
phase[1] += increment[1]; | |||
phase[2] += increment[2]; | |||
phase[3] += increment[3]; | |||
phase[4] += increment[4]; | |||
phase[5] += increment[5]; | |||
uint32_t noise = 0; | |||
noise += (phase[0] >> 31); | |||
noise += (phase[1] >> 31); | |||
noise += (phase[2] >> 31); | |||
noise += (phase[3] >> 31); | |||
noise += (phase[4] >> 31); | |||
noise += (phase[5] >> 31); | |||
*out++ = 0.33f * static_cast<float>(noise) - 1.0f; | |||
} | |||
for (int i = 0; i < 6; ++i) { | |||
phase_[i] = phase[i]; | |||
} | |||
} | |||
private: | |||
uint32_t phase_[6]; | |||
DISALLOW_COPY_AND_ASSIGN(SquareNoise); | |||
}; | |||
class RingModNoise { | |||
public: | |||
RingModNoise() { } | |||
~RingModNoise() { } | |||
void Init() { | |||
for (int i = 0; i < 6; ++i) { | |||
oscillator_[i].Init(); | |||
} | |||
} | |||
void Render(float f0, float* temp_1, float* temp_2, float* out, size_t size) { | |||
const float ratio = f0 / (0.01f + f0); | |||
const float f1a = 200.0f / kSampleRate * ratio; | |||
const float f1b = 7530.0f / kSampleRate * ratio; | |||
const float f2a = 510.0f / kSampleRate * ratio; | |||
const float f2b = 8075.0f / kSampleRate * ratio; | |||
const float f3a = 730.0f / kSampleRate * ratio; | |||
const float f3b = 10500.0f / kSampleRate * ratio; | |||
std::fill(&out[0], &out[size], 0.0f); | |||
RenderPair(&oscillator_[0], f1a, f1b, temp_1, temp_2, out, size); | |||
RenderPair(&oscillator_[2], f2a, f2b, temp_1, temp_2, out, size); | |||
RenderPair(&oscillator_[4], f3a, f3b, temp_1, temp_2, out, size); | |||
} | |||
private: | |||
void RenderPair( | |||
Oscillator* osc, | |||
float f1, | |||
float f2, | |||
float* temp_1, | |||
float* temp_2, | |||
float* out, | |||
size_t size) { | |||
osc[0].Render<OSCILLATOR_SHAPE_SQUARE>(f1, 0.5f, temp_1, size); | |||
osc[1].Render<OSCILLATOR_SHAPE_SAW>(f2, 0.5f, temp_2, size); | |||
while (size--) { | |||
*out++ += *temp_1++ * *temp_2++; | |||
} | |||
} | |||
Oscillator oscillator_[6]; | |||
DISALLOW_COPY_AND_ASSIGN(RingModNoise); | |||
}; | |||
class SwingVCA { | |||
public: | |||
float operator()(float s, float gain) { | |||
s *= s > 0.0f ? 10.0f : 0.1f; | |||
s = s / (1.0f + fabsf(s)); | |||
return (s + 1.0f) * gain; | |||
} | |||
}; | |||
class LinearVCA { | |||
public: | |||
float operator()(float s, float gain) { | |||
return s * gain; | |||
} | |||
}; | |||
template<typename MetallicNoiseSource, typename VCA, bool resonance> | |||
class HiHat { | |||
public: | |||
HiHat() { } | |||
~HiHat() { } | |||
void Init() { | |||
envelope_ = 0.0f; | |||
noise_clock_ = 0.0f; | |||
noise_sample_ = 0.0f; | |||
sustain_gain_ = 0.0f; | |||
metallic_noise_.Init(); | |||
noise_coloration_svf_.Init(); | |||
hpf_.Init(); | |||
} | |||
void Render( | |||
bool sustain, | |||
bool trigger, | |||
float accent, | |||
float f0, | |||
float tone, | |||
float decay, | |||
float noisiness, | |||
float* temp_1, | |||
float* temp_2, | |||
float* out, | |||
size_t size) { | |||
const float envelope_decay = 1.0f - 0.003f * stmlib::SemitonesToRatio( | |||
-decay * 84.0f); | |||
const float cut_decay = 1.0f - 0.0025f * stmlib::SemitonesToRatio( | |||
-decay * 36.0f); | |||
if (trigger) { | |||
envelope_ = (1.5f + 0.5f * (1.0f - decay)) * (0.3f + 0.7f * accent); | |||
} | |||
// Render the metallic noise. | |||
metallic_noise_.Render(2.0f * f0, temp_1, temp_2, out, size); | |||
// Apply BPF on the metallic noise. | |||
float cutoff = 150.0f / kSampleRate * stmlib::SemitonesToRatio( | |||
tone * 72.0f); | |||
CONSTRAIN(cutoff, 0.0f, 16000.0f / kSampleRate); | |||
noise_coloration_svf_.set_f_q<stmlib::FREQUENCY_ACCURATE>( | |||
cutoff, resonance ? 3.0f + 6.0f * tone : 1.0f); | |||
noise_coloration_svf_.Process<stmlib::FILTER_MODE_BAND_PASS>( | |||
out, out, size); | |||
// This is not at all part of the 808 circuit! But to add more variety, we | |||
// add a variable amount of clocked noise to the output of the 6 schmitt | |||
// trigger oscillators. | |||
noisiness *= noisiness; | |||
float noise_f = f0 * (16.0f + 16.0f * (1.0f - noisiness)); | |||
CONSTRAIN(noise_f, 0.0f, 0.5f); | |||
for (size_t i = 0; i < size; ++i) { | |||
noise_clock_ += noise_f; | |||
if (noise_clock_ >= 1.0f) { | |||
noise_clock_ -= 1.0f; | |||
noise_sample_ = stmlib::Random::GetFloat() - 0.5f; | |||
} | |||
out[i] += noisiness * (noise_sample_ - out[i]); | |||
} | |||
// Apply VCA. | |||
stmlib::ParameterInterpolator sustain_gain( | |||
&sustain_gain_, | |||
accent * decay, | |||
size); | |||
for (size_t i = 0; i < size; ++i) { | |||
VCA vca; | |||
envelope_ *= envelope_ > 0.5f ? envelope_decay : cut_decay; | |||
out[i] = vca(out[i], sustain ? sustain_gain.Next() : envelope_); | |||
} | |||
hpf_.set_f_q<stmlib::FREQUENCY_ACCURATE>(cutoff, 0.5f); | |||
hpf_.Process<stmlib::FILTER_MODE_HIGH_PASS>(out, out, size); | |||
} | |||
private: | |||
float envelope_; | |||
float noise_clock_; | |||
float noise_sample_; | |||
float sustain_gain_; | |||
MetallicNoiseSource metallic_noise_; | |||
stmlib::Svf noise_coloration_svf_; | |||
stmlib::Svf hpf_; | |||
DISALLOW_COPY_AND_ASSIGN(HiHat); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DSP_DRUMS_HI_HAT_H_ |
@@ -0,0 +1,249 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Naive bass drum model (modulated oscillator with FM + envelope). | |||
// Inadvertently 909-ish. | |||
#ifndef PLAITS_DSP_DRUMS_SYNTHETIC_BASS_DRUM_H_ | |||
#define PLAITS_DSP_DRUMS_SYNTHETIC_BASS_DRUM_H_ | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/dsp/units.h" | |||
#include "stmlib/utils/random.h" | |||
#include "plaits/dsp/dsp.h" | |||
#include "plaits/resources.h" | |||
namespace plaits { | |||
class SyntheticBassDrumClick { | |||
public: | |||
SyntheticBassDrumClick() { } | |||
~SyntheticBassDrumClick() { } | |||
void Init() { | |||
lp_ = 0.0f; | |||
hp_ = 0.0f; | |||
filter_.Init(); | |||
filter_.set_f_q<stmlib::FREQUENCY_FAST>(5000.0f / kSampleRate, 2.0f); | |||
} | |||
float Process(float in) { | |||
SLOPE(lp_, in, 0.5f, 0.1f); | |||
ONE_POLE(hp_, lp_, 0.04f); | |||
return filter_.Process<stmlib::FILTER_MODE_LOW_PASS>(lp_ - hp_); | |||
} | |||
private: | |||
float lp_; | |||
float hp_; | |||
stmlib::Svf filter_; | |||
DISALLOW_COPY_AND_ASSIGN(SyntheticBassDrumClick); | |||
}; | |||
class SyntheticBassDrumAttackNoise { | |||
public: | |||
SyntheticBassDrumAttackNoise() { } | |||
~SyntheticBassDrumAttackNoise() { } | |||
void Init() { | |||
lp_ = 0.0f; | |||
hp_ = 0.0f; | |||
} | |||
float Render() { | |||
float sample = stmlib::Random::GetFloat(); | |||
ONE_POLE(lp_, sample, 0.05f); | |||
ONE_POLE(hp_, lp_, 0.005f); | |||
return lp_ - hp_; | |||
} | |||
private: | |||
float lp_; | |||
float hp_; | |||
DISALLOW_COPY_AND_ASSIGN(SyntheticBassDrumAttackNoise); | |||
}; | |||
class SyntheticBassDrum { | |||
public: | |||
SyntheticBassDrum() { } | |||
~SyntheticBassDrum() { } | |||
void Init() { | |||
phase_ = 0.0f; | |||
phase_noise_ = 0.0f; | |||
f0_ = 0.0f; | |||
fm_ = 0.0f; | |||
fm_lp_ = 0.0f; | |||
body_env_lp_ = 0.0f; | |||
body_env_ = 0.0f; | |||
body_env_pulse_width_ = 0; | |||
fm_pulse_width_ = 0; | |||
tone_lp_ = 0.0f; | |||
sustain_gain_ = 0.0f; | |||
click_.Init(); | |||
noise_.Init(); | |||
} | |||
inline float DistortedSine(float phase, float phase_noise, float dirtiness) { | |||
phase += phase_noise * dirtiness; | |||
MAKE_INTEGRAL_FRACTIONAL(phase); | |||
phase = phase_fractional; | |||
float triangle = (phase < 0.5f ? phase : 1.0f - phase) * 4.0f - 1.0f; | |||
float sine = 2.0f * triangle / (1.0f + fabsf(triangle)); | |||
float clean_sine = stmlib::InterpolateWrap( | |||
lut_sine, phase + 0.75f, 1024.0f); | |||
return sine + (1.0f - dirtiness) * (clean_sine - sine); | |||
} | |||
inline float TransistorVCA(float s, float gain) { | |||
s = (s - 0.6f) * gain; | |||
return 3.0f * s / (2.0f + fabsf(s)) + gain * 0.3f; | |||
} | |||
void Render( | |||
bool sustain, | |||
bool trigger, | |||
float accent, | |||
float f0, | |||
float tone, | |||
float decay, | |||
float dirtiness, | |||
float fm_envelope_amount, | |||
float fm_envelope_decay, | |||
float* out, | |||
size_t size) { | |||
decay *= decay; | |||
fm_envelope_decay *= fm_envelope_decay; | |||
stmlib::ParameterInterpolator f0_mod(&f0_, f0, size); | |||
dirtiness *= std::max(1.0f - 8.0f * f0, 0.0f); | |||
const float fm_decay = 1.0f - \ | |||
1.0f / (0.008f * (1.0f + fm_envelope_decay * 4.0f) * kSampleRate); | |||
const float body_env_decay = 1.0f - 1.0f / (0.02f * kSampleRate) * \ | |||
stmlib::SemitonesToRatio(-decay * 60.0f); | |||
const float transient_env_decay = 1.0f - 1.0f / (0.005f * kSampleRate); | |||
const float tone_f = std::min( | |||
4.0f * f0 * stmlib::SemitonesToRatio(tone * 108.0f), | |||
1.0f); | |||
const float transient_level = tone; | |||
if (trigger) { | |||
fm_ = 1.0f; | |||
body_env_ = transient_env_ = 0.3f + 0.7f * accent; | |||
body_env_pulse_width_ = kSampleRate * 0.001f; | |||
fm_pulse_width_ = kSampleRate * 0.0013f; | |||
} | |||
stmlib::ParameterInterpolator sustain_gain( | |||
&sustain_gain_, | |||
accent * decay, | |||
size); | |||
while (size--) { | |||
ONE_POLE(phase_noise_, stmlib::Random::GetFloat() - 0.5f, 0.002f); | |||
float mix = 0.0f; | |||
if (sustain) { | |||
phase_ += f0_mod.Next(); | |||
if (phase_ >= 1.0f) { | |||
phase_ -= 1.0f; | |||
} | |||
float body = DistortedSine(phase_, phase_noise_, dirtiness); | |||
mix -= TransistorVCA(body, sustain_gain.Next()); | |||
} else { | |||
if (fm_pulse_width_) { | |||
--fm_pulse_width_; | |||
phase_ = 0.25f; | |||
} else { | |||
fm_ *= fm_decay; | |||
float fm = 1.0f + fm_envelope_amount * 3.5f * fm_lp_; | |||
phase_ += std::min(f0_mod.Next() * fm, 0.5f); | |||
if (phase_ >= 1.0f) { | |||
phase_ -= 1.0f; | |||
} | |||
} | |||
if (body_env_pulse_width_) { | |||
--body_env_pulse_width_; | |||
} else { | |||
body_env_ *= body_env_decay; | |||
transient_env_ *= transient_env_decay; | |||
} | |||
const float envelope_lp_f = 0.1f; | |||
ONE_POLE(body_env_lp_, body_env_, envelope_lp_f); | |||
ONE_POLE(transient_env_lp_, transient_env_, envelope_lp_f); | |||
ONE_POLE(fm_lp_, fm_, envelope_lp_f); | |||
float body = DistortedSine(phase_, phase_noise_, dirtiness); | |||
float transient = click_.Process( | |||
body_env_pulse_width_ ? 0.0f : 1.0f) + noise_.Render(); | |||
mix -= TransistorVCA(body, body_env_lp_); | |||
mix -= transient * transient_env_lp_ * transient_level; | |||
} | |||
ONE_POLE(tone_lp_, mix, tone_f); | |||
*out++ = tone_lp_; | |||
} | |||
} | |||
private: | |||
float f0_; | |||
float phase_; | |||
float phase_noise_; | |||
float fm_; | |||
float fm_lp_; | |||
float body_env_; | |||
float body_env_lp_; | |||
float transient_env_; | |||
float transient_env_lp_; | |||
float sustain_gain_; | |||
float tone_lp_; | |||
SyntheticBassDrumClick click_; | |||
SyntheticBassDrumAttackNoise noise_; | |||
int body_env_pulse_width_; | |||
int fm_pulse_width_; | |||
DISALLOW_COPY_AND_ASSIGN(SyntheticBassDrum); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DSP_DRUMS_SYNTHETIC_BASS_DRUM_H_ |
@@ -0,0 +1,198 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Naive snare drum model (two modulated oscillators + filtered noise). | |||
// Uses a few magic numbers taken from the 909 schematics: | |||
// - Ratio between the two modes of the drum set to 1.47. | |||
// - Funky coupling between the two modes. | |||
// - Noise coloration filters and envelope shapes for the snare. | |||
#ifndef PLAITS_DSP_DRUMS_SYNTHETIC_SNARE_DRUM_H_ | |||
#define PLAITS_DSP_DRUMS_SYNTHETIC_SNARE_DRUM_H_ | |||
#include <algorithm> | |||
#include "stmlib/dsp/dsp.h" | |||
#include "stmlib/dsp/parameter_interpolator.h" | |||
#include "stmlib/dsp/units.h" | |||
#include "plaits/dsp/dsp.h" | |||
namespace plaits { | |||
class SyntheticSnareDrum { | |||
public: | |||
SyntheticSnareDrum() { } | |||
~SyntheticSnareDrum() { } | |||
void Init() { | |||
phase_[0] = 0.0f; | |||
phase_[1] = 0.0f; | |||
drum_amplitude_ = 0.0f; | |||
snare_amplitude_ = 0.0f; | |||
fm_ = 0.0f; | |||
hold_counter_ = 0; | |||
sustain_gain_ = 0.0f; | |||
drum_lp_.Init(); | |||
snare_hp_.Init(); | |||
snare_lp_.Init(); | |||
} | |||
inline float DistortedSine(float phase) { | |||
float triangle = (phase < 0.5f ? phase : 1.0f - phase) * 4.0f - 1.3f; | |||
return 2.0f * triangle / (1.0f + fabsf(triangle)); | |||
} | |||
void Render( | |||
bool sustain, | |||
bool trigger, | |||
float accent, | |||
float f0, | |||
float fm_amount, | |||
float decay, | |||
float snappy, | |||
float* out, | |||
size_t size) { | |||
const float decay_xt = decay * (1.0f + decay * (decay - 1.0f)); | |||
fm_amount *= fm_amount; | |||
const float drum_decay = 1.0f - 1.0f / (0.015f * kSampleRate) * \ | |||
stmlib::SemitonesToRatio( | |||
-decay_xt * 72.0f - fm_amount * 12.0f + snappy * 7.0f); | |||
const float snare_decay = 1.0f - 1.0f / (0.01f * kSampleRate) * \ | |||
stmlib::SemitonesToRatio(-decay * 60.0f - snappy * 7.0f); | |||
const float fm_decay = 1.0f - 1.0f / (0.007f * kSampleRate); | |||
snappy = snappy * 1.1f - 0.05f; | |||
CONSTRAIN(snappy, 0.0f, 1.0f); | |||
const float drum_level = stmlib::Sqrt(1.0f - snappy); | |||
const float snare_level = stmlib::Sqrt(snappy); | |||
const float snare_f_min = std::min(10.0f * f0, 0.5f); | |||
const float snare_f_max = std::min(35.0f * f0, 0.5f); | |||
snare_hp_.set_f<stmlib::FREQUENCY_FAST>(snare_f_min); | |||
snare_lp_.set_f_q<stmlib::FREQUENCY_FAST>(snare_f_max, | |||
0.5f + 2.0f * snappy); | |||
drum_lp_.set_f<stmlib::FREQUENCY_FAST>(3.0f * f0); | |||
if (trigger) { | |||
snare_amplitude_ = drum_amplitude_ = 0.3f + 0.7f * accent; | |||
fm_ = 1.0f; | |||
phase_[0] = phase_[1] = 0.0f; | |||
hold_counter_ = static_cast<int>((0.04f + decay * 0.03f) * kSampleRate); | |||
} | |||
stmlib::ParameterInterpolator sustain_gain( | |||
&sustain_gain_, | |||
accent * decay, | |||
size); | |||
while (size--) { | |||
if (sustain) { | |||
snare_amplitude_ = sustain_gain.Next(); | |||
drum_amplitude_ = snare_amplitude_; | |||
fm_ = 0.0f; | |||
} else { | |||
// Compute all D envelopes. | |||
// The envelope for the drum has a very long tail. | |||
// The envelope for the snare has a "hold" stage which lasts between | |||
// 40 and 70 ms | |||
drum_amplitude_ *= (drum_amplitude_ > 0.03f || !(size & 1)) | |||
? drum_decay | |||
: 1.0f; | |||
if (hold_counter_) { | |||
--hold_counter_; | |||
} else { | |||
snare_amplitude_ *= snare_decay; | |||
} | |||
fm_ *= fm_decay; | |||
} | |||
// The 909 circuit has a funny kind of oscillator coupling - the signal | |||
// leaving Q40's collector and resetting all oscillators allow some | |||
// intermodulation. | |||
float reset_noise = 0.0f; | |||
float reset_noise_amount = (0.125f - f0) * 8.0f; | |||
CONSTRAIN(reset_noise_amount, 0.0f, 1.0f); | |||
reset_noise_amount *= reset_noise_amount; | |||
reset_noise_amount *= fm_amount; | |||
reset_noise += phase_[0] > 0.5f ? -1.0f : 1.0f; | |||
reset_noise += phase_[1] > 0.5f ? -1.0f : 1.0f; | |||
reset_noise *= reset_noise_amount * 0.025f; | |||
float f = f0 * (1.0f + fm_amount * (4.0f * fm_)); | |||
phase_[0] += f; | |||
phase_[1] += f * 1.47f; | |||
if (reset_noise_amount > 0.1f) { | |||
if (phase_[0] >= 1.0f + reset_noise) { | |||
phase_[0] = 1.0f - phase_[0]; | |||
} | |||
if (phase_[1] >= 1.0f + reset_noise) { | |||
phase_[1] = 1.0f - phase_[1]; | |||
} | |||
} else { | |||
if (phase_[0] >= 1.0f) { | |||
phase_[0] -= 1.0f; | |||
} | |||
if (phase_[1] >= 1.0f) { | |||
phase_[1] -= 1.0f; | |||
} | |||
} | |||
float drum = -0.1f; | |||
drum += DistortedSine(phase_[0]) * 0.60f; | |||
drum += DistortedSine(phase_[1]) * 0.25f; | |||
drum *= drum_amplitude_ * drum_level; | |||
drum = drum_lp_.Process<stmlib::FILTER_MODE_LOW_PASS>(drum); | |||
float noise = stmlib::Random::GetFloat(); | |||
float snare = snare_lp_.Process<stmlib::FILTER_MODE_LOW_PASS>(noise); | |||
snare = snare_hp_.Process<stmlib::FILTER_MODE_HIGH_PASS>(snare); | |||
snare = (snare + 0.1f) * (snare_amplitude_ + fm_) * snare_level; | |||
*out++ = snare + drum; // It's a snare, it's a drum, it's a snare drum. | |||
} | |||
} | |||
private: | |||
float phase_[2]; | |||
float drum_amplitude_; | |||
float snare_amplitude_; | |||
float fm_; | |||
float sustain_gain_; | |||
int hold_counter_; | |||
stmlib::OnePole drum_lp_; | |||
stmlib::OnePole snare_hp_; | |||
stmlib::Svf snare_lp_; | |||
DISALLOW_COPY_AND_ASSIGN(SyntheticSnareDrum); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DSP_DRUMS_SYNTHETIC_SNARE_DRUM_H_ |
@@ -0,0 +1,55 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Utility DSP routines. | |||
#ifndef PLAITS_DSP_DSP_H_ | |||
#define PLAITS_DSP_DSP_H_ | |||
#include "stmlib/stmlib.h" | |||
namespace plaits { | |||
static const float kSampleRate = 48000.0f; | |||
// There is no proper PLL for I2S, only a divider on the system clock to derive | |||
// the bit clock. | |||
// The division ratio is set to 47 (23 EVEN, 1 ODD) by the ST libraries. | |||
// | |||
// Bit clock = 72000000 / 47 = 1531.91 kHz | |||
// Frame clock = Bit clock / 32 = 47872.34 Hz | |||
// | |||
// That's only 4.6 cts of error, but we care! | |||
static const float kCorrectedSampleRate = 47872.34f; | |||
const float a0 = (440.0f / 8.0f) / kCorrectedSampleRate; | |||
const size_t kMaxBlockSize = 24; | |||
const size_t kBlockSize = 12; | |||
} // namespace plaits | |||
#endif // PLAITS_DSP_DSP_H_ |
@@ -0,0 +1,152 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Additive synthesis with 32 partials. | |||
#include "plaits/dsp/engine/additive_engine.h" | |||
#include <algorithm> | |||
#include "stmlib/dsp/cosine_oscillator.h" | |||
#include "plaits/resources.h" | |||
namespace plaits { | |||
using namespace std; | |||
using namespace stmlib; | |||
void AdditiveEngine::Init(BufferAllocator* allocator) { | |||
fill( | |||
&litudes_[0], | |||
&litudes_[kNumHarmonics], | |||
0.0f); | |||
for (int i = 0; i < kNumHarmonicOscillators; ++i) { | |||
harmonic_oscillator_[i].Init(); | |||
} | |||
} | |||
void AdditiveEngine::Reset() { | |||
} | |||
void AdditiveEngine::UpdateAmplitudes( | |||
float centroid, | |||
float slope, | |||
float bumps, | |||
float* amplitudes, | |||
const int* harmonic_indices, | |||
size_t num_harmonics) { | |||
const float n = (static_cast<float>(num_harmonics) - 1.0f); | |||
const float margin = (1.0f / slope - 1.0f) / (1.0f + bumps); | |||
const float center = centroid * (n + margin) - 0.5f * margin; | |||
float sum = 0.001f; | |||
for (size_t i = 0; i < num_harmonics; ++i) { | |||
float order = fabsf(static_cast<float>(i) - center) * slope; | |||
float gain = 1.0f - order; | |||
gain += fabsf(gain); | |||
gain *= gain; | |||
float b = 0.25f + order * bumps; | |||
float bump_factor = 1.0f + InterpolateWrap(lut_sine, b, 1024.0f); | |||
gain *= bump_factor; | |||
gain *= gain; | |||
gain *= gain; | |||
int j = harmonic_indices[i]; | |||
// Warning about the following line: this is not a proper LP filter because | |||
// of the normalization. But in spite of its strange working, this line | |||
// turns out ot be absolutely essential. | |||
// | |||
// I have tried both normalizing the LP-ed spectrum, and LP-ing the | |||
// normalized spectrum, and both of them cause more annoyances than this | |||
// "incorrect" solution. | |||
ONE_POLE(amplitudes[j], gain, 0.001f); | |||
sum += amplitudes[j]; | |||
} | |||
sum = 1.0f / sum; | |||
for (size_t i = 0; i < num_harmonics; ++i) { | |||
amplitudes[harmonic_indices[i]] *= sum; | |||
} | |||
} | |||
inline float Bump(float x, float centroid, float slope) { | |||
float d = fabsf(x - centroid); | |||
float bump = 1.0f - d * slope; | |||
return bump + fabsf(bump); | |||
} | |||
const int integer_harmonics[24] = { | |||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | |||
16, 17, 18, 19, 20, 21, 22, 23 | |||
}; | |||
const int organ_harmonics[8] = { | |||
0, 1, 2, 3, 5, 7, 9, 11 | |||
}; | |||
void AdditiveEngine::Render( | |||
const EngineParameters& parameters, | |||
float* out, | |||
float* aux, | |||
size_t size, | |||
bool* already_enveloped) { | |||
const float f0 = NoteToFrequency(parameters.note); | |||
const float centroid = parameters.timbre; | |||
const float raw_bumps = parameters.harmonics; | |||
const float raw_slope = (1.0f - 0.6f * raw_bumps) * parameters.morph; | |||
const float slope = 0.01f + 1.99f * raw_slope * raw_slope * raw_slope; | |||
const float bumps = 16.0f * raw_bumps * raw_bumps; | |||
UpdateAmplitudes( | |||
centroid, | |||
slope, | |||
bumps, | |||
&litudes_[0], | |||
integer_harmonics, | |||
24); | |||
harmonic_oscillator_[0].Render<1>(f0, &litudes_[0], out, size); | |||
harmonic_oscillator_[1].Render<13>(f0, &litudes_[12], out, size); | |||
UpdateAmplitudes( | |||
centroid, | |||
slope, | |||
bumps, | |||
&litudes_[24], | |||
organ_harmonics, | |||
8); | |||
harmonic_oscillator_[2].Render<1>(f0, &litudes_[24], aux, size); | |||
} | |||
} // namespace plaits |
@@ -0,0 +1,72 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// Additive synthesis with 24+8 partials. | |||
#ifndef PLAITS_DSP_ENGINE_ADDITIVE_ENGINE_H_ | |||
#define PLAITS_DSP_ENGINE_ADDITIVE_ENGINE_H_ | |||
#include "plaits/dsp/engine/engine.h" | |||
#include "plaits/dsp/oscillator/harmonic_oscillator.h" | |||
namespace plaits { | |||
const int kHarmonicBatchSize = 12; | |||
const int kNumHarmonics = 36; | |||
const int kNumHarmonicOscillators = kNumHarmonics / kHarmonicBatchSize; | |||
class AdditiveEngine : public Engine { | |||
public: | |||
AdditiveEngine() { } | |||
~AdditiveEngine() { } | |||
virtual void Init(stmlib::BufferAllocator* allocator); | |||
virtual void Reset(); | |||
virtual void Render(const EngineParameters& parameters, | |||
float* out, | |||
float* aux, | |||
size_t size, | |||
bool* already_enveloped); | |||
private: | |||
void UpdateAmplitudes( | |||
float centroid, | |||
float slope, | |||
float bumps, | |||
float* amplitudes, | |||
const int* harmonic_indices, | |||
size_t num_harmonics); | |||
HarmonicOscillator<kHarmonicBatchSize> harmonic_oscillator_[kNumHarmonicOscillators]; | |||
float amplitudes_[kNumHarmonics]; | |||
DISALLOW_COPY_AND_ASSIGN(AdditiveEngine); | |||
}; | |||
} // namespace plaits | |||
#endif // PLAITS_DSP_ENGINE_ADDITIVE_ENGINE_H_ |
@@ -0,0 +1,96 @@ | |||
// Copyright 2016 Olivier Gillet. | |||
// | |||
// Author: Olivier Gillet (ol.gillet@gmail.com) | |||
// | |||
// Permission is hereby granted, free of charge, to any person obtaining a copy | |||
// of this software and associated documentation files (the "Software"), to deal | |||
// in the Software without restriction, including without limitation the rights | |||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
// copies of the Software, and to permit persons to whom the Software is | |||
// furnished to do so, subject to the following conditions: | |||
// | |||
// The above copyright notice and this permission notice shall be included in | |||
// all copies or substantial portions of the Software. | |||
// | |||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
// THE SOFTWARE. | |||
// | |||
// See http://creativecommons.org/licenses/MIT/ for more information. | |||
// | |||
// ----------------------------------------------------------------------------- | |||
// | |||
// 808 and synthetic bass drum generators. | |||
#include "plaits/dsp/engine/bass_drum_engine.h" | |||
#include <algorithm> | |||
namespace plaits { | |||
using namespace std; | |||
using namespace stmlib; | |||
void BassDrumEngine::Init(BufferAllocator* allocator) { | |||
analog_bass_drum_.Init(); | |||
synthetic_bass_drum_.Init(); | |||
overdrive_.Init(); | |||
} | |||
void BassDrumEngine::Reset() { | |||
} | |||
void BassDrumEngine::Render( | |||
const EngineParameters& parameters, | |||
float* out, | |||
float* aux, | |||
size_t size, | |||
bool* already_enveloped) { | |||
const float f0 = NoteToFrequency(parameters.note); | |||
const float attack_fm_amount = min(parameters.harmonics * 4.0f, 1.0f); | |||
const float self_fm_amount = max(min(parameters.harmonics * 4.0f - 1.0f, 1.0f), 0.0f); | |||
const float drive = max(parameters.harmonics * 2.0f - 1.0f, 0.0f) * \ | |||
max(1.0f - 16.0f * f0, 0.0f); | |||
const bool sustain = parameters.trigger & TRIGGER_UNPATCHED; | |||
analog_bass_drum_.Render( | |||
sustain, | |||
parameters.trigger & TRIGGER_RISING_EDGE, | |||
parameters.accent, | |||
f0, | |||
parameters.timbre, | |||
parameters.morph, | |||
attack_fm_amount, | |||
self_fm_amount, | |||
out, | |||
size); | |||
overdrive_.Process( | |||
0.5f + 0.5f * drive, | |||
out, | |||
size); | |||
synthetic_bass_drum_.Render( | |||
sustain, | |||
parameters.trigger & TRIGGER_RISING_EDGE, | |||
parameters.accent, | |||
f0, | |||
parameters.timbre, | |||
parameters.morph, | |||
sustain | |||
? parameters.harmonics | |||
: 0.4f - 0.25f * parameters.morph * parameters.morph, | |||
min(parameters.harmonics * 2.0f, 1.0f), | |||
max(parameters.harmonics * 2.0f - 1.0f, 0.0f), | |||
aux, | |||
size); | |||
} | |||
} // namespace plaits |