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 3 months
by
Olivier Gillet
part.cc// Copyright 2013 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. // // ----------------------------------------------------------------------------- // // Part. #include "yarns/part.h" #include <algorithm> #include "stmlib/midi/midi.h" #include "stmlib/utils/random.h" #include "yarns/just_intonation_processor.h" #include "yarns/midi_handler.h" #include "yarns/resources.h" #include "yarns/voice.h" namespace yarns { const uint8_t clock_divisions[] = { 96, 48, 32, 24, 16, 12, 8, 6, 4, 3, 2, 1 }; using namespace stmlib; using namespace stmlib_midi; using namespace std; void Part::Init() { pressed_keys_.Init(); mono_allocator_.Init(); poly_allocator_.Init(); generated_notes_.Init(); std::fill( &active_note_[0], &active_note_[kMaxNumVoices], VOICE_ALLOCATION_NOT_FOUND); num_voices_ = 0; polychained_ = false; ignore_note_off_messages_ = false; seq_recording_ = false; seq_running_ = false; release_latched_keys_on_next_note_on_ = false; } void Part::AllocateVoices(Voice* voice, uint8_t num_voices, bool polychain) { AllNotesOff(); num_voices_ = std::min(num_voices, kMaxNumVoices); polychained_ = polychain; for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i] = voice + i; } poly_allocator_.Clear(); poly_allocator_.set_size(num_voices_ * (polychain ? 2 : 1)); TouchVoices(); } bool Part::NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) { bool sent_from_step_editor = channel & 0x80; if (release_latched_keys_on_next_note_on_) { bool still_latched = ignore_note_off_messages_; // Releasing all latched key will generate "fake" NoteOff messages. We // should note ignore them. ignore_note_off_messages_ = false; ReleaseLatchedNotes(); release_latched_keys_on_next_note_on_ = still_latched; ignore_note_off_messages_ = still_latched; } pressed_keys_.NoteOn(note, velocity); if ((!(seq_.num_steps && seq_running_) && !seq_.arp_range) || sent_from_step_editor) { InternalNoteOn(note, velocity); } if (seq_recording_ && !sent_from_step_editor) { RecordStep(SequencerStep(note, velocity)); } return midi_.out_mode == MIDI_OUT_MODE_THRU && !polychained_; } bool Part::NoteOff(uint8_t channel, uint8_t note) { bool sent_from_step_editor = channel & 0x80; if (ignore_note_off_messages_) { for (uint8_t i = 1; i <= pressed_keys_.max_size(); ++i) { // Flag the note so that it is removed once the sustain pedal is released. NoteEntry* e = pressed_keys_.mutable_note(i); if (e->note == note && e->velocity) { e->velocity |= 0x80; } } } else { pressed_keys_.NoteOff(note); if ((!(seq_.num_steps && seq_running_) && !seq_.arp_range) || sent_from_step_editor) { InternalNoteOff(note); } } return midi_.out_mode == MIDI_OUT_MODE_THRU && !polychained_; } bool Part::ControlChange(uint8_t channel, uint8_t controller, uint8_t value) { switch (controller) { case kCCModulationWheelMsb: case kCCBreathController: case kCCFootPedalMsb: for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->ControlChange(controller, value); } break; case kCCOmniModeOff: midi_.channel = channel; break; case kCCOmniModeOn: midi_.channel = 0x10; break; case kCCMonoModeOn: voicing_.allocation_mode = VOICE_ALLOCATION_MODE_MONO; TouchVoiceAllocation(); break; case kCCPolyModeOn: voicing_.allocation_mode = VOICE_ALLOCATION_MODE_POLY; TouchVoiceAllocation(); break; case kCCHoldPedal: { if (value >= 64) { ignore_note_off_messages_ = true; } else { ignore_note_off_messages_ = false; ReleaseLatchedNotes(); } } break; case 0x70: if (seq_recording_) { RecordStep(SEQUENCER_STEP_TIE); } break; case 0x71: if (seq_recording_) { RecordStep(SEQUENCER_STEP_REST); } break; case 0x78: AllNotesOff(); break; case 0x79: ResetAllControllers(); break; case 0x7b: AllNotesOff(); break; } return midi_.out_mode != MIDI_OUT_MODE_OFF; } bool Part::PitchBend(uint8_t channel, uint16_t pitch_bend) { for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->PitchBend(pitch_bend); } if (seq_recording_ && (pitch_bend > 8192 + 2048 || pitch_bend < 8192 - 2048)) { seq_.step[seq_rec_step_].data[1] |= 0x80; } return midi_.out_mode != MIDI_OUT_MODE_OFF; } bool Part::Aftertouch(uint8_t channel, uint8_t note, uint8_t velocity) { if (voicing_.allocation_mode != VOICE_ALLOCATION_MODE_MONO) { uint8_t voice_index = \ voicing_.allocation_mode == VOICE_ALLOCATION_MODE_POLY ? \ poly_allocator_.Find(note) : \ FindVoiceForNote(note); if (voice_index < poly_allocator_.size()) { voice_[voice_index]->Aftertouch(velocity); } } else { Aftertouch(channel, velocity); } return midi_.out_mode != MIDI_OUT_MODE_OFF; } bool Part::Aftertouch(uint8_t channel, uint8_t velocity) { for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->Aftertouch(velocity); } return midi_.out_mode != MIDI_OUT_MODE_OFF; } void Part::Reset() { Stop(); for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->NoteOff(); voice_[i]->ResetAllControllers(); } } void Part::Clock() { if (!arp_seq_prescaler_) { if (seq_.num_steps && seq_running_) { ClockSequencer(); } else if (seq_.arp_range) { ClockArpeggiator(); } } if (!gate_length_counter_ && generated_notes_.size()) { StopSequencerArpeggiatorNotes(); } else { --gate_length_counter_; } ++arp_seq_prescaler_; if (arp_seq_prescaler_ >= clock_divisions[seq_.clock_division]) { arp_seq_prescaler_ = 0; } if (voicing_.modulation_rate >= 100) { uint32_t num_ticks = clock_divisions[voicing_.modulation_rate - 100]; uint32_t expected_phase = (lfo_counter_ % num_ticks) * 65536 / num_ticks; for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->TapLfo(expected_phase << 16); } } ++lfo_counter_; } void Part::Start(bool started_by_keyboard) { arp_seq_prescaler_ = 0; seq_step_ = 0; seq_running_ = !started_by_keyboard; release_latched_keys_on_next_note_on_ = false; ignore_note_off_messages_ = false; if (seq_.arp_direction == ARPEGGIATOR_DIRECTION_DOWN) { arp_note_ = pressed_keys_.size() - 1; arp_octave_ = seq_.arp_range - 1; arp_direction_ = -1; } else { arp_note_ = 0; arp_octave_ = 0; arp_direction_ = 1; } arp_step_ = 0; lfo_counter_ = 0; generated_notes_.Clear(); } void Part::Stop() { seq_running_ = false; StopSequencerArpeggiatorNotes(); AllNotesOff(); } void Part::StartRecording() { if (seq_recording_) { return; } seq_recording_ = true; seq_rec_step_ = 0; seq_overdubbing_ = seq_.num_steps && seq_running_; if (!seq_overdubbing_) { std::fill( &seq_.step[0], &seq_.step[kNumSteps], SequencerStep(SEQUENCER_STEP_REST, 0)); seq_.num_steps = 0; } } void Part::StopSequencerArpeggiatorNotes() { while (generated_notes_.size()) { uint8_t note = generated_notes_.sorted_note(0).note; generated_notes_.NoteOff(note); InternalNoteOff(note); } } void Part::ClockSequencer() { const SequencerStep& step = seq_.step[seq_step_]; if (step.has_note()) { int16_t note = step.note(); if (pressed_keys_.size() && !seq_recording_) { // When we play a monophonic sequence, we can make the guess that root // note = first note. // But this is not the case when we are playing several sequences at the // same time. In this case, we use root note = 60. int8_t root_note = !has_siblings_ ? seq_.first_note() : 60; note += pressed_keys_.most_recent_note().note - root_note; while (note > 127) { note -= 12; } while (note < 0) { note += 12; } } if (!step.is_slid()) { StopSequencerArpeggiatorNotes(); InternalNoteOn(note, step.velocity()); } else { InternalNoteOn(note, step.velocity()); StopSequencerArpeggiatorNotes(); } generated_notes_.NoteOn(note, step.velocity()); gate_length_counter_ = seq_.gate_length; } ++seq_step_; if (seq_step_ >= seq_.num_steps) { seq_step_ = 0; } if (seq_.step[seq_step_].is_tie() || seq_.step[seq_step_].is_slid()) { // The next step contains a "sustain" message; or a slid note. Extends // the duration of the current note. gate_length_counter_ += clock_divisions[seq_.clock_division]; } } void Part::ClockArpeggiator() { uint32_t pattern_mask = 1 << arp_step_; uint32_t pattern = lut_arpeggiator_patterns[seq_.arp_pattern]; uint32_t pattern_length = 16; if (seq_.euclidean_length != 0) { pattern_length = seq_.euclidean_length; pattern_mask = 1 << ((arp_step_ + seq_.euclidean_rotate) % pattern_length); // Read euclidean pattern from ROM. uint16_t offset = static_cast<uint16_t>(seq_.euclidean_length - 1) * 32; pattern = lut_euclidean[offset + seq_.euclidean_fill]; } uint8_t num_notes = pressed_keys_.size(); if ((pattern_mask & pattern) && num_notes) { // Update arepggiator note/octave counter. if (num_notes == 1 && seq_.arp_range == 1) { // This is a corner case for the Up/down pattern code. // Get it out of the way. arp_note_ = 0; arp_octave_ = 0; } else { if (seq_.arp_direction == ARPEGGIATOR_DIRECTION_RANDOM) { uint16_t random = Random::GetSample(); arp_octave_ = (random & 0xff) % seq_.arp_range; arp_note_ = (random >> 8) % num_notes; } else { bool wrapped = true; while (wrapped) { if (arp_note_ >= num_notes || arp_note_ < 0) { arp_octave_ += arp_direction_; arp_note_ = arp_direction_ > 0 ? 0 : num_notes - 1; } wrapped = false; if (arp_octave_ >= seq_.arp_range || arp_octave_ < 0) { arp_octave_ = arp_direction_ > 0 ? 0 : seq_.arp_range - 1; if (seq_.arp_direction == ARPEGGIATOR_DIRECTION_UP_DOWN) { arp_direction_ = -arp_direction_; arp_note_ = arp_direction_ > 0 ? 1 : num_notes - 2; arp_octave_ = arp_direction_ > 0 ? 0 : seq_.arp_range - 1; wrapped = true; } } } } } // Kill pending notes (if any). StopSequencerArpeggiatorNotes(); // Trigger arpeggiated note or chord. if (seq_.arp_direction != ARPEGGIATOR_DIRECTION_CHORD) { const NoteEntry* arpeggio_note = \ seq_.arp_direction == ARPEGGIATOR_DIRECTION_AS_PLAYED ? &pressed_keys_.played_note(arp_note_) : &pressed_keys_.sorted_note(arp_note_); uint8_t note = arpeggio_note->note; uint8_t velocity = arpeggio_note->velocity & 0x7f; note += 12 * arp_octave_; while (note > 127) { note -= 12; } generated_notes_.NoteOn(note, velocity); InternalNoteOn(note, velocity); } else { for (uint8_t i = 0; i < num_notes; ++i) { const NoteEntry& chord_note = pressed_keys_.played_note(i); generated_notes_.NoteOn(chord_note.note, chord_note.velocity & 0x7f); InternalNoteOn(chord_note.note, chord_note.velocity & 0x7f); } } arp_note_ += arp_direction_; gate_length_counter_ = seq_.gate_length; } ++arp_step_; if (arp_step_ >= pattern_length) { arp_step_ = 0; } } void Part::ResetAllControllers() { ignore_note_off_messages_ = false; for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->ResetAllControllers(); } } void Part::AllNotesOff() { poly_allocator_.ClearNotes(); mono_allocator_.Clear(); pressed_keys_.Clear(); for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->NoteOff(); } std::fill( &active_note_[0], &active_note_[kMaxNumVoices], VOICE_ALLOCATION_NOT_FOUND); release_latched_keys_on_next_note_on_ = false; ignore_note_off_messages_ = false; } void Part::ReleaseLatchedNotes() { for (uint8_t i = 1; i <= pressed_keys_.max_size(); ++i) { NoteEntry* e = pressed_keys_.mutable_note(i); if (e->velocity & 0x80) { NoteOff(tx_channel(), e->note); } } } void Part::DispatchSortedNotes(bool unison) { uint8_t n = mono_allocator_.size(); for (uint8_t i = 0; i < num_voices_; ++i) { uint8_t index = 0xff; if (unison) { index = n ? (i * n / num_voices_) : 0xff; } else { index = i < mono_allocator_.size() ? i : 0xff; } if (index != 0xff) { voice_[i]->NoteOn( Tune(mono_allocator_.sorted_note(index).note), mono_allocator_.sorted_note(index).velocity, voicing_.portamento, !voice_[i]->gate_on()); active_note_[i] = mono_allocator_.sorted_note(index).note; } else { voice_[i]->NoteOff(); active_note_[i] = VOICE_ALLOCATION_NOT_FOUND; } } } void Part::InternalNoteOn(uint8_t note, uint8_t velocity) { if (midi_.out_mode == MIDI_OUT_MODE_GENERATED_EVENTS && !polychained_) { midi_handler.OnInternalNoteOn(tx_channel(), note, velocity); } if (voicing_.allocation_mode == VOICE_ALLOCATION_MODE_MONO) { const NoteEntry& before = mono_allocator_.note_by_priority( static_cast<NoteStackFlags>(voicing_.allocation_priority)); mono_allocator_.NoteOn(note, velocity); const NoteEntry& after = mono_allocator_.note_by_priority( static_cast<NoteStackFlags>(voicing_.allocation_priority)); // Check if the note that has been played should be triggered according // to selected voice priority rules. if (before.note != after.note) { bool legato = mono_allocator_.size() > 1; for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->NoteOn( Tune(after.note), after.velocity, !voicing_.legato_mode || legato ? voicing_.portamento : 0, !voicing_.legato_mode || !legato); } } } else if (voicing_.allocation_mode == VOICE_ALLOCATION_MODE_POLY_SORTED || voicing_.allocation_mode == VOICE_ALLOCATION_MODE_POLY_UNISON_1 || voicing_.allocation_mode == VOICE_ALLOCATION_MODE_POLY_UNISON_2) { mono_allocator_.NoteOn(note, velocity); DispatchSortedNotes( voicing_.allocation_mode != VOICE_ALLOCATION_MODE_POLY_SORTED); } else { uint8_t voice_index = 0; switch (voicing_.allocation_mode) { case VOICE_ALLOCATION_MODE_POLY: voice_index = poly_allocator_.NoteOn(note); break; case VOICE_ALLOCATION_MODE_POLY_CYCLIC: if (cyclic_allocation_note_counter_ >= num_voices_) { cyclic_allocation_note_counter_ = 0; } voice_index = cyclic_allocation_note_counter_; ++cyclic_allocation_note_counter_; break; case VOICE_ALLOCATION_MODE_POLY_RANDOM: voice_index = (Random::GetWord() >> 24) % num_voices_; break; case VOICE_ALLOCATION_MODE_POLY_VELOCITY: voice_index = (static_cast<uint16_t>(velocity) * num_voices_) >> 7; break; default: break; } if (voice_index < num_voices_) { // Prevent the same note from being simultaneously played on two channels. KillAllInstancesOfNote(note); voice_[voice_index]->NoteOn( Tune(note), velocity, voicing_.portamento, true); active_note_[voice_index] = note; } else { // Polychaining forwarding. midi_handler.OnInternalNoteOn(tx_channel(), note, velocity); } } } void Part::KillAllInstancesOfNote(uint8_t note) { while (true) { uint8_t index = FindVoiceForNote(note); if (index != VOICE_ALLOCATION_NOT_FOUND) { voice_[index]->NoteOff(); active_note_[index] = VOICE_ALLOCATION_NOT_FOUND; } else { break; } } } void Part::InternalNoteOff(uint8_t note) { if (midi_.out_mode == MIDI_OUT_MODE_GENERATED_EVENTS && !polychained_) { midi_handler.OnInternalNoteOff(tx_channel(), note); } if (voicing_.tuning_system == TUNING_SYSTEM_JUST_INTONATION) { just_intonation_processor.NoteOff(note); } if (voicing_.allocation_mode == VOICE_ALLOCATION_MODE_MONO) { const NoteEntry& before = mono_allocator_.note_by_priority( static_cast<NoteStackFlags>(voicing_.allocation_priority)); mono_allocator_.NoteOff(note); const NoteEntry& after = mono_allocator_.note_by_priority( static_cast<NoteStackFlags>(voicing_.allocation_priority)); if (mono_allocator_.size() == 0) { // No key is pressed, we just close the gate. for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->NoteOff(); } } else if (before.note != after.note) { // Removing the note gives priority to another note that is still being // pressed. Slide to this note (or retrigger is legato mode is off). for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->NoteOn( Tune(after.note), after.velocity, voicing_.portamento, !voicing_.legato_mode); } } } else if (voicing_.allocation_mode == VOICE_ALLOCATION_MODE_POLY_SORTED || voicing_.allocation_mode == VOICE_ALLOCATION_MODE_POLY_UNISON_1 || voicing_.allocation_mode == VOICE_ALLOCATION_MODE_POLY_UNISON_2) { mono_allocator_.NoteOff(note); KillAllInstancesOfNote(note); if (voicing_.allocation_mode == VOICE_ALLOCATION_MODE_POLY_UNISON_1) { DispatchSortedNotes(true); } } else { uint8_t voice_index = \ voicing_.allocation_mode == VOICE_ALLOCATION_MODE_POLY ? \ poly_allocator_.NoteOff(note) : \ FindVoiceForNote(note); if (voice_index < num_voices_) { voice_[voice_index]->NoteOff(); active_note_[voice_index] = VOICE_ALLOCATION_NOT_FOUND; } else { midi_handler.OnInternalNoteOff(tx_channel(), note); } } } void Part::TouchVoiceAllocation() { AllNotesOff(); ResetAllControllers(); } void Part::TouchVoices() { CONSTRAIN(voicing_.aux_cv, 0, 7); CONSTRAIN(voicing_.aux_cv_2, 0, 7); for (uint8_t i = 0; i < num_voices_; ++i) { voice_[i]->set_pitch_bend_range(voicing_.pitch_bend_range); voice_[i]->set_modulation_rate(voicing_.modulation_rate); voice_[i]->set_vibrato_range(voicing_.vibrato_range); voice_[i]->set_trigger_duration(voicing_.trigger_duration); voice_[i]->set_trigger_scale(voicing_.trigger_scale); voice_[i]->set_trigger_shape(voicing_.trigger_shape); voice_[i]->set_aux_cv(voicing_.aux_cv); voice_[i]->set_aux_cv_2(voicing_.aux_cv_2); voice_[i]->set_audio_mode(voicing_.audio_mode); voice_[i]->set_tuning(voicing_.tuning_transpose, voicing_.tuning_fine); } } void Part::Set(uint8_t address, uint8_t value) { uint8_t* bytes; bytes = static_cast<uint8_t*>(static_cast<void*>(&midi_)); uint8_t previous_value = bytes[address]; bytes[address] = value; if (value != previous_value) { switch (address) { case PART_MIDI_CHANNEL: case PART_MIDI_MIN_NOTE: case PART_MIDI_MAX_NOTE: case PART_MIDI_MIN_VELOCITY: case PART_MIDI_MAX_VELOCITY: // Shut all channels off when a MIDI parameter is changed to prevent // stuck notes. AllNotesOff(); break; case PART_VOICING_ALLOCATION_MODE: TouchVoiceAllocation(); break; case PART_VOICING_PITCH_BEND_RANGE: case PART_VOICING_MODULATION_RATE: case PART_VOICING_VIBRATO_RANGE: case PART_VOICING_TRIGGER_DURATION: case PART_VOICING_TRIGGER_SHAPE: case PART_VOICING_TRIGGER_SCALE: case PART_VOICING_AUX_CV: case PART_VOICING_AUX_CV_2: case PART_VOICING_AUDIO_MODE: case PART_VOICING_TUNING_TRANSPOSE: case PART_VOICING_TUNING_FINE: TouchVoices(); break; case PART_SEQUENCER_ARP_DIRECTION: arp_direction_ = \ seq_.arp_direction == ARPEGGIATOR_DIRECTION_DOWN ? -1 : 1; break; } } } int16_t Part::Tune(int16_t midi_note) { int16_t note = midi_note; int16_t pitch = note << 7; uint8_t pitch_class = (note + 240) % 12; // Just intonation. if (voicing_.tuning_system == TUNING_SYSTEM_JUST_INTONATION) { pitch = just_intonation_processor.NoteOn(note); } else if (voicing_.tuning_system == TUNING_SYSTEM_CUSTOM) { pitch += custom_pitch_table_[pitch_class]; } else if (voicing_.tuning_system > TUNING_SYSTEM_JUST_INTONATION) { note -= voicing_.tuning_root; pitch_class = (note + 240) % 12; pitch += lookup_table_signed_table[LUT_SCALE_PYTHAGOREAN + \ voicing_.tuning_system - TUNING_SYSTEM_PYTHAGOREAN][pitch_class]; } return pitch; } } // namespace yarns