Files
-
blinds / hardware_design / pcb / blinds_v60.brd
-
blinds / hardware_design / pcb / blinds_v60.sch
-
braids / hardware_design / pcb / braids_v50.brd
-
braids / hardware_design / pcb / braids_v50.sch
-
branches / hardware_design / pcb / branches_v40.brd
-
branches / hardware_design / pcb / branches_v40.sch
-
clouds / hardware_design / pcb / clouds_v30.brd
-
clouds / hardware_design / pcb / clouds_v30.sch
-
ears / hardware_design / panel / ears_panel_v30.brd
-
ears / hardware_design / panel / ears_panel_v30.sch
-
ears / hardware_design / pcb / ears_v40.brd
-
ears / hardware_design / pcb / ears_v40.sch
-
edges / hardware_design / pcb / edges_expander_v01.brd
-
edges / hardware_design / pcb / edges_expander_v01.sch
-
edges / hardware_design / pcb / edges_v20.brd
-
edges / hardware_design / pcb / edges_v20.sch
-
elements / hardware_design / pcb / elements_v02.brd
-
elements / hardware_design / pcb / elements_v02.sch
-
frames / hardware_design / pcb / frames_v03.brd
-
frames / hardware_design / pcb / frames_v03.sch
-
grids / hardware_design / pcb / grids_v02.brd
-
grids / hardware_design / pcb / grids_v02.sch
-
kinks / hardware_design / pcb / kinks_v41.brd
-
kinks / hardware_design / pcb / kinks_v41.sch
-
links / hardware_design / pcb / links_v40.brd
-
links / hardware_design / pcb / links_v40.sch
-
marbles / hardware_design / pcb / marbles_v70.brd
-
marbles / hardware_design / pcb / marbles_v70.sch
-
peaks / hardware_design / pcb / peaks_v30.brd
-
peaks / hardware_design / pcb / peaks_v30.sch
-
plaits / hardware_design / pcb / plaits_v50.brd
-
plaits / hardware_design / pcb / plaits_v50.sch
-
rings / hardware_design / pcb / rings_v30.brd
-
rings / hardware_design / pcb / rings_v30.sch
-
ripples / hardware_design / pcb / ripples_v40.brd
-
ripples / hardware_design / pcb / ripples_v40.sch
-
shades / hardware_design / pcb / shades_v30.brd
-
shades / hardware_design / pcb / shades_v30.sch
-
shelves / hardware_design / pcb / shelves_expander_v10.brd
-
shelves / hardware_design / pcb / shelves_expander_v10.sch
-
shelves / hardware_design / pcb / shelves_v05.brd
-
shelves / hardware_design / pcb / shelves_v05.sch
-
stages / hardware_design / pcb / stages_v70.brd
-
stages / hardware_design / pcb / stages_v70.sch
-
streams / hardware_design / pcb / streams_v02_bargraph.brd
-
streams / hardware_design / pcb / streams_v02_bargraph.sch
-
streams / hardware_design / pcb / streams_v05.brd
-
streams / hardware_design / pcb / streams_v05.sch
-
tides / hardware_design / pcb / tides_v40.brd
-
tides / hardware_design / pcb / tides_v40.sch
-
veils / hardware_design / pcb / veils_v40.brd
-
veils / hardware_design / pcb / veils_v40.sch
-
volts / hardware_design / pcb / volts_v01.brd
-
volts / hardware_design / pcb / volts_v01.sch
-
warps / hardware_design / pcb / warps_v30.brd
-
warps / hardware_design / pcb / warps_v30.sch
-
yarns / hardware_design / pcb / yarns_v03.brd
-
yarns / hardware_design / pcb / yarns_v03.sch
Last update 6 years 1 month
by
Olivier Gillet
part.cc// 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. // // ----------------------------------------------------------------------------- // // Group of voices. #include "rings/dsp/part.h" #include "stmlib/dsp/units.h" #include "rings/resources.h" namespace rings { using namespace std; using namespace stmlib; void Part::Init(uint16_t* reverb_buffer) { active_voice_ = 0; fill(¬e_[0], ¬e_[kMaxPolyphony], 0.0f); bypass_ = false; polyphony_ = 1; model_ = RESONATOR_MODEL_MODAL; dirty_ = true; for (int32_t i = 0; i < kMaxPolyphony; ++i) { excitation_filter_[i].Init(); plucker_[i].Init(); dc_blocker_[i].Init(1.0f - 10.0f / kSampleRate); } reverb_.Init(reverb_buffer); limiter_.Init(); note_filter_.Init( kSampleRate / kMaxBlockSize, 0.001f, // Lag time with a sharp edge on the V/Oct input or trigger. 0.010f, // Lag time after the trigger has been received. 0.050f, // Time to transition from reactive to filtered. 0.004f); // Prevent a sharp edge to partly leak on the previous voice. } void Part::ConfigureResonators() { if (!dirty_) { return; } switch (model_) { case RESONATOR_MODEL_MODAL: { int32_t resolution = 64 / polyphony_ - 4; for (int32_t i = 0; i < polyphony_; ++i) { resonator_[i].Init(); resonator_[i].set_resolution(resolution); } } break; case RESONATOR_MODEL_SYMPATHETIC_STRING: case RESONATOR_MODEL_STRING: case RESONATOR_MODEL_SYMPATHETIC_STRING_QUANTIZED: case RESONATOR_MODEL_STRING_AND_REVERB: { float lfo_frequencies[kNumStrings] = { 0.5f, 0.4f, 0.35f, 0.23f, 0.211f, 0.2f, 0.171f }; for (int32_t i = 0; i < kNumStrings; ++i) { bool has_dispersion = model_ == RESONATOR_MODEL_STRING || \ model_ == RESONATOR_MODEL_STRING_AND_REVERB; string_[i].Init(has_dispersion); float f_lfo = float(kMaxBlockSize) / float(kSampleRate); f_lfo *= lfo_frequencies[i]; lfo_[i].Init<COSINE_OSCILLATOR_APPROXIMATE>(f_lfo); } for (int32_t i = 0; i < polyphony_; ++i) { plucker_[i].Init(); } } break; case RESONATOR_MODEL_FM_VOICE: { for (int32_t i = 0; i < polyphony_; ++i) { fm_voice_[i].Init(); } } break; default: break; } if (active_voice_ >= polyphony_) { active_voice_ = 0; } dirty_ = false; } #ifdef BRYAN_CHORDS // Chord table by Bryan Noll: float chords[kMaxPolyphony][11][8] = { { { -12.0f, -0.01f, 0.0f, 0.01f, 0.02f, 11.98f, 11.99f, 12.0f }, // OCT { -12.0f, -5.0f, 0.0f, 6.99f, 7.0f, 11.99f, 12.0f, 19.0f }, // 5 { -12.0f, -5.0f, 0.0f, 5.0f, 7.0f, 11.99f, 12.0f, 17.0f }, // sus4 { -12.0f, -5.0f, 0.0f, 3.0f, 7.0f, 3.01f, 12.0f, 19.0f }, // m { -12.0f, -5.0f, 0.0f, 3.0f, 7.0f, 3.01f, 10.0f, 19.0f }, // m7 { -12.0f, -5.0f, 0.0f, 3.0f, 14.0f, 3.01f, 10.0f, 19.0f }, // m9 { -12.0f, -5.0f, 0.0f, 3.0f, 7.0f, 3.01f, 10.0f, 17.0f }, // m11 { -12.0f, -5.0f, 0.0f, 2.0f, 7.0f, 9.0f, 16.0f, 19.0f }, // 69 { -12.0f, -5.0f, 0.0f, 4.0f, 7.0f, 11.0f, 14.0f, 19.0f }, // M9 { -12.0f, -5.0f, 0.0f, 4.0f, 7.0f, 11.0f, 10.99f, 19.0f }, // M7 { -12.0f, -5.0f, 0.0f, 4.0f, 7.0f, 11.99f, 12.0f, 19.0f } // M }, { { -12.0f, 0.0f, 0.01f, 12.0f }, // OCT { -12.0f, 6.99f, 7.0f, 12.0f }, // 5 { -12.0f, 5.0f, 7.0f, 12.0f }, // sus4 { -12.0f, 3.0f, 11.99f, 12.0f }, // m { -12.0f, 3.0f, 10.0f, 12.0f }, // m7 { -12.0f, 3.0f, 10.0f, 14.0f }, // m9 { -12.0f, 3.0f, 10.0f, 17.0f }, // m11 { -12.0f, 2.0f, 9.0f, 16.0f }, // 69 { -12.0f, 4.0f, 11.0f, 14.0f }, // M9 { -12.0f, 4.0f, 7.0f, 11.0f }, // M7 { -12.0f, 4.0f, 7.0f, 12.0f }, // M }, { { 0.0f, -12.0f }, { 0.0f, 2.0f }, { 0.0f, 3.0f }, { 0.0f, 4.0f }, { 0.0f, 5.0f }, { 0.0f, 7.0f }, { 0.0f, 9.0f }, { 0.0f, 10.0f }, { 0.0f, 11.0f }, { 0.0f, 12.0f }, { -12.0f, 12.0f } }, { { 0.0f, -12.0f }, { 0.0f, 2.0f }, { 0.0f, 3.0f }, { 0.0f, 4.0f }, { 0.0f, 5.0f }, { 0.0f, 7.0f }, { 0.0f, 9.0f }, { 0.0f, 10.0f }, { 0.0f, 11.0f }, { 0.0f, 12.0f }, { -12.0f, 12.0f } } }; #else // Original chord table float chords[kMaxPolyphony][11][8] = { { { -12.0f, 0.0f, 0.01f, 0.02f, 0.03f, 11.98f, 11.99f, 12.0f }, { -12.0f, 0.0f, 3.0f, 3.01f, 7.0f, 9.99f, 10.0f, 19.0f }, { -12.0f, 0.0f, 3.0f, 3.01f, 7.0f, 11.99f, 12.0f, 19.0f }, { -12.0f, 0.0f, 3.0f, 3.01f, 7.0f, 13.99f, 14.0f, 19.0f }, { -12.0f, 0.0f, 3.0f, 3.01f, 7.0f, 16.99f, 17.0f, 19.0f }, { -12.0f, 0.0f, 6.98f, 6.99f, 7.0f, 12.00f, 18.99f, 19.0f }, { -12.0f, 0.0f, 3.99f, 4.0f, 7.0f, 16.99f, 17.0f, 19.0f }, { -12.0f, 0.0f, 3.99f, 4.0f, 7.0f, 13.99f, 14.0f, 19.0f }, { -12.0f, 0.0f, 3.99f, 4.0f, 7.0f, 11.99f, 12.0f, 19.0f }, { -12.0f, 0.0f, 3.99f, 4.0f, 7.0f, 10.99f, 11.0f, 19.0f }, { -12.0f, 0.0f, 4.99f, 5.0f, 7.0f, 11.99f, 12.0f, 17.0f } }, { { -12.0f, 0.0f, 0.01f, 12.0f }, { -12.0f, 3.0f, 7.0f, 10.0f }, { -12.0f, 3.0f, 7.0f, 12.0f }, { -12.0f, 3.0f, 7.0f, 14.0f }, { -12.0f, 3.0f, 7.0f, 17.0f }, { -12.0f, 7.0f, 12.0f, 19.0f }, { -12.0f, 4.0f, 7.0f, 17.0f }, { -12.0f, 4.0f, 7.0f, 14.0f }, { -12.0f, 4.0f, 7.0f, 12.0f }, { -12.0f, 4.0f, 7.0f, 11.0f }, { -12.0f, 5.0f, 7.0f, 12.0f }, }, { { 0.0f, -12.0f }, { 0.0f, 0.01f }, { 0.0f, 2.0f }, { 0.0f, 3.0f }, { 0.0f, 4.0f }, { 0.0f, 5.0f }, { 0.0f, 7.0f }, { 0.0f, 10.0f }, { 0.0f, 11.0f }, { 0.0f, 12.0f }, { -12.0f, 12.0f } }, { { 0.0f, -12.0f }, { 0.0f, 0.01f }, { 0.0f, 2.0f }, { 0.0f, 3.0f }, { 0.0f, 4.0f }, { 0.0f, 5.0f }, { 0.0f, 7.0f }, { 0.0f, 10.0f }, { 0.0f, 11.0f }, { 0.0f, 12.0f }, { -12.0f, 12.0f } } }; #endif // BRYAN_CHORDS void Part::ComputeSympatheticStringsNotes( float tonic, float note, float parameter, float* destination, size_t num_strings) { float notes[9] = { tonic, note - 12.0f, note - 7.01955f, note, note + 7.01955f, note + 12.0f, note + 19.01955f, note + 24.0f, note + 24.0f }; const float detunings[4] = { 0.013f, 0.011f, 0.007f, 0.017f }; if (parameter >= 2.0f) { // Quantized chords int32_t chord_index = parameter - 2.0f; const float* chord = chords[polyphony_ - 1][chord_index]; for (size_t i = 0; i < num_strings; ++i) { destination[i] = chord[i] + note; } return; } size_t num_detuned_strings = (num_strings - 1) >> 1; size_t first_detuned_string = num_strings - num_detuned_strings; for (size_t i = 0; i < first_detuned_string; ++i) { float note = 3.0f; if (i != 0) { note = parameter * 7.0f; parameter += (1.0f - parameter) * 0.2f; } MAKE_INTEGRAL_FRACTIONAL(note); note_fractional = Squash(note_fractional); float a = notes[note_integral]; float b = notes[note_integral + 1]; note = a + (b - a) * note_fractional; destination[i] = note; if (i + first_detuned_string < num_strings) { destination[i + first_detuned_string] = destination[i] + detunings[i & 3]; } } } void Part::RenderModalVoice( int32_t voice, const PerformanceState& performance_state, const Patch& patch, float frequency, float filter_cutoff, size_t size) { // Internal exciter is a pulse, pre-filter. if (performance_state.internal_exciter && voice == active_voice_ && performance_state.strum) { resonator_input_[0] += 0.25f * SemitonesToRatio( filter_cutoff * filter_cutoff * 24.0f) / filter_cutoff; } // Process through filter. excitation_filter_[voice].Process<FILTER_MODE_LOW_PASS>( resonator_input_, resonator_input_, size); Resonator& r = resonator_[voice]; r.set_frequency(frequency); r.set_structure(patch.structure); r.set_brightness(patch.brightness * patch.brightness); r.set_position(patch.position); r.set_damping(patch.damping); r.Process(resonator_input_, out_buffer_, aux_buffer_, size); } void Part::RenderFMVoice( int32_t voice, const PerformanceState& performance_state, const Patch& patch, float frequency, float filter_cutoff, size_t size) { FMVoice& v = fm_voice_[voice]; if (performance_state.internal_exciter && voice == active_voice_ && performance_state.strum) { v.TriggerInternalEnvelope(); } v.set_frequency(frequency); v.set_ratio(patch.structure); v.set_brightness(patch.brightness); v.set_feedback_amount(patch.position); v.set_position(/*patch.position*/ 0.0f); v.set_damping(patch.damping); v.Process(resonator_input_, out_buffer_, aux_buffer_, size); } void Part::RenderStringVoice( int32_t voice, const PerformanceState& performance_state, const Patch& patch, float frequency, float filter_cutoff, size_t size) { // Compute number of strings and frequency. int32_t num_strings = 1; float frequencies[kNumStrings]; if (model_ == RESONATOR_MODEL_SYMPATHETIC_STRING || model_ == RESONATOR_MODEL_SYMPATHETIC_STRING_QUANTIZED) { num_strings = 2 * kMaxPolyphony / polyphony_; float parameter = model_ == RESONATOR_MODEL_SYMPATHETIC_STRING ? patch.structure : 2.0f + performance_state.chord; ComputeSympatheticStringsNotes( performance_state.tonic + performance_state.fm, performance_state.tonic + note_[voice] + performance_state.fm, parameter, frequencies, num_strings); for (int32_t i = 0; i < num_strings; ++i) { frequencies[i] = SemitonesToRatio(frequencies[i] - 69.0f) * a3; } } else { frequencies[0] = frequency; } if (voice == active_voice_) { const float gain = 1.0f / Sqrt(static_cast<float>(num_strings) * 2.0f); for (size_t i = 0; i < size; ++i) { resonator_input_[i] *= gain; } } // Process external input. excitation_filter_[voice].Process<FILTER_MODE_LOW_PASS>( resonator_input_, resonator_input_, size); // Add noise burst. if (performance_state.internal_exciter) { if (voice == active_voice_ && performance_state.strum) { plucker_[voice].Trigger(frequency, filter_cutoff * 8.0f, patch.position); } plucker_[voice].Process(noise_burst_buffer_, size); for (size_t i = 0; i < size; ++i) { resonator_input_[i] += noise_burst_buffer_[i]; } } dc_blocker_[voice].Process(resonator_input_, size); fill(&out_buffer_[0], &out_buffer_[size], 0.0f); fill(&aux_buffer_[0], &aux_buffer_[size], 0.0f); float structure = patch.structure; float dispersion = structure < 0.24f ? (structure - 0.24f) * 4.166f : (structure > 0.26f ? (structure - 0.26f) * 1.35135f : 0.0f); for (int32_t string = 0; string < num_strings; ++string) { int32_t i = voice + string * polyphony_; String& s = string_[i]; float lfo_value = lfo_[i].Next(); float brightness = patch.brightness; float damping = patch.damping; float position = patch.position; float glide = 1.0f; float string_index = static_cast<float>(string) / static_cast<float>(num_strings); const float* input = resonator_input_; if (model_ == RESONATOR_MODEL_STRING_AND_REVERB) { damping *= (2.0f - damping); } // When the internal exciter is used, string 0 is the main // source, the other strings are vibrating by sympathetic resonance. // When the internal exciter is not used, all strings are vibrating // by sympathetic resonance. if (string > 0 && performance_state.internal_exciter) { brightness *= (2.0f - brightness); brightness *= (2.0f - brightness); damping = 0.7f + patch.damping * 0.27f; float amount = (0.5f - fabs(0.5f - patch.position)) * 0.9f; position = patch.position + lfo_value * amount; glide = SemitonesToRatio((brightness - 1.0f) * 36.0f); input = sympathetic_resonator_input_; } s.set_dispersion(dispersion); s.set_frequency(frequencies[string], glide); s.set_brightness(brightness); s.set_position(position); s.set_damping(damping + string_index * (0.95f - damping)); s.Process(input, out_buffer_, aux_buffer_, size); if (string == 0) { // Was 0.1f, Ben Wilson -> 0.2f float gain = 0.2f / static_cast<float>(num_strings); for (size_t i = 0; i < size; ++i) { float sum = out_buffer_[i] - aux_buffer_[i]; sympathetic_resonator_input_[i] = gain * sum; } } } } const int32_t kPingPattern[] = { 1, 0, 2, 1, 0, 2, 1, 0 }; void Part::Process( const PerformanceState& performance_state, const Patch& patch, const float* in, float* out, float* aux, size_t size) { // Copy inputs to outputs when bypass mode is enabled. if (bypass_) { copy(&in[0], &in[size], &out[0]); copy(&in[0], &in[size], &aux[0]); return; } ConfigureResonators(); note_filter_.Process( performance_state.note, performance_state.strum); if (performance_state.strum) { note_[active_voice_] = note_filter_.stable_note(); if (polyphony_ > 1 && polyphony_ & 1) { active_voice_ = kPingPattern[step_counter_ % 8]; step_counter_ = (step_counter_ + 1) % 8; } else { active_voice_ = (active_voice_ + 1) % polyphony_; } } note_[active_voice_] = note_filter_.note(); fill(&out[0], &out[size], 0.0f); fill(&aux[0], &aux[size], 0.0f); for (int32_t voice = 0; voice < polyphony_; ++voice) { // Compute MIDI note value, frequency, and cutoff frequency for excitation // filter. float cutoff = patch.brightness * (2.0f - patch.brightness); float note = note_[voice] + performance_state.tonic + performance_state.fm; float frequency = SemitonesToRatio(note - 69.0f) * a3; float filter_cutoff_range = performance_state.internal_exciter ? frequency * SemitonesToRatio((cutoff - 0.5f) * 96.0f) : 0.4f * SemitonesToRatio((cutoff - 1.0f) * 108.0f); float filter_cutoff = min(voice == active_voice_ ? filter_cutoff_range : (10.0f / kSampleRate), 0.499f); float filter_q = performance_state.internal_exciter ? 1.5f : 0.8f; // Process input with excitation filter. Inactive voices receive silence. excitation_filter_[voice].set_f_q<FREQUENCY_DIRTY>(filter_cutoff, filter_q); if (voice == active_voice_) { copy(&in[0], &in[size], &resonator_input_[0]); } else { fill(&resonator_input_[0], &resonator_input_[size], 0.0f); } if (model_ == RESONATOR_MODEL_MODAL) { RenderModalVoice( voice, performance_state, patch, frequency, filter_cutoff, size); } else if (model_ == RESONATOR_MODEL_FM_VOICE) { RenderFMVoice( voice, performance_state, patch, frequency, filter_cutoff, size); } else { RenderStringVoice( voice, performance_state, patch, frequency, filter_cutoff, size); } if (polyphony_ == 1) { // Send the two sets of harmonics / pickups to individual outputs. for (size_t i = 0; i < size; ++i) { out[i] += out_buffer_[i]; aux[i] += aux_buffer_[i]; } } else { // Dispatch odd/even voices to individual outputs. float* destination = voice & 1 ? aux : out; for (size_t i = 0; i < size; ++i) { destination[i] += out_buffer_[i] - aux_buffer_[i]; } } } if (model_ == RESONATOR_MODEL_STRING_AND_REVERB) { for (size_t i = 0; i < size; ++i) { float l = out[i]; float r = aux[i]; out[i] = l * patch.position + (1.0f - patch.position) * r; aux[i] = r * patch.position + (1.0f - patch.position) * l; } reverb_.set_amount(0.1f + patch.damping * 0.5f); reverb_.set_diffusion(0.625f); reverb_.set_time(0.35f + 0.63f * patch.damping); reverb_.set_input_gain(0.2f); reverb_.set_lp(0.3f + patch.brightness * 0.6f); reverb_.Process(out, aux, size); for (size_t i = 0; i < size; ++i) { aux[i] = -aux[i]; } } // Apply limiter to string output. limiter_.Process(out, aux, size, model_gains_[model_]); } /* static */ float Part::model_gains_[] = { 1.4f, // RESONATOR_MODEL_MODAL 1.0f, // RESONATOR_MODEL_SYMPATHETIC_STRING 1.4f, // RESONATOR_MODEL_STRING 0.7f, // RESONATOR_MODEL_FM_VOICE, 1.0f, // RESONATOR_MODEL_SYMPATHETIC_STRING_QUANTIZED 1.4f, // RESONATOR_MODEL_STRING_AND_REVERB }; } // namespace rings