Skip to content

Commit

Permalink
Merge pull request #7 from valsteen/note_fanout
Browse files Browse the repository at this point in the history
distribute channels
  • Loading branch information
valsteen authored Dec 21, 2020
2 parents 9d361d9 + 86273b5 commit 19e7a22
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 40 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ This plugin lets in one note out of N steps, allowing you to build a fan-out dev

![](docs/fanout.gif)

Using channel distribution each note will get a different channel. In order to not interfere with MPE, it starts at channel 2.

![](docs/fanoutchannel.gif)

Using Bitwig's _note counter_ modulator on a selector FX should just do that, except that the modulation signal is not straightforward to troubleshoot.

## Note Generator
Expand Down
Binary file added docs/fanoutchannel.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 79 additions & 29 deletions note_fan_out/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ use vst::event::{Event, MidiEvent};
use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin};
use std::sync::Arc;
use util::parameters::ParameterConversion;
use util::messages::{MidiMessageType, RawMessage, ChannelMessage, NoteMessage};
use util::messages::{MidiMessageType, RawMessage, ChannelMessage, NoteMessage, GenericChannelMessage, NoteOn, NoteOff};
use parameters::{NoteFanoutParameters, Parameter};
use std::collections::HashSet;
use std::hash::{Hasher, Hash};
use parameters::ChannelDistribution;


plugin_main!(NoteFanOut);
Expand All @@ -24,7 +25,8 @@ pub struct NoteFanOut {
current_playing_notes: HashSet<PlayingNote>,
send_buffer: SendEventBuffer,
parameters: Arc<NoteFanoutParameters>,
current_step: u8
current_step: u8,
notes_counter: usize
}


Expand All @@ -38,10 +40,11 @@ impl NoteFanOut {
}
}

#[derive(Eq)]
#[derive(Eq,Clone)]
struct PlayingNote {
channel: u8,
pitch: u8
pitch: u8,
mapped_channel: u8
}

impl PartialEq for PlayingNote {
Expand All @@ -63,7 +66,7 @@ impl Plugin for NoteFanOut {
name: "Note fan-out".to_string(),
vendor: "DJ Crontab".to_string(),
unique_id: 123458,
parameters: 2,
parameters: 3,
category: Category::Effect,
initial_delay: 0,
version: 5,
Expand Down Expand Up @@ -113,37 +116,84 @@ impl Plugin for NoteFanOut {

for e in events.events() {
if let Event::Midi(e) = e {
if steps > 0 {
let raw_message = RawMessage::from(e.data);
let midi_message = MidiMessageType::from(raw_message);

match midi_message {
MidiMessageType::NoteOnMessage(midi_message) => {
if selection == self.current_step {
self.events.push(e);
self.current_playing_notes.insert(PlayingNote {
channel: midi_message.get_channel(),
pitch: midi_message.get_pitch()
});
let raw_message = RawMessage::from(e.data);
let midi_message = MidiMessageType::from(raw_message);
let channel_message = GenericChannelMessage::from(raw_message);

match midi_message {
MidiMessageType::NoteOnMessage(midi_message) => {
let target_channel = match self.parameters.get_channel_distribution(Parameter::ChannelDistribute) {
ChannelDistribution::Channels(distribution) => {
let target_channel = (self.notes_counter % (distribution as usize)) as u8 + 1;
self.notes_counter += 1;
target_channel
}
self.current_step = (self.current_step + 1) % steps ;
}
MidiMessageType::NoteOffMessage(midi_message) => {
let lookup = PlayingNote {
ChannelDistribution::Off => {
channel_message.get_channel()
}
};

if steps == 0 || selection == self.current_step {
let raw_message : RawMessage = NoteOn {
channel: target_channel,
pitch: midi_message.pitch,
velocity: midi_message.velocity
}.into();

self.events.push(MidiEvent {
data: raw_message.into(),
delta_frames: e.delta_frames,
live: e.live,
note_length: e.note_length,
note_offset: e.note_offset,
detune: e.detune,
note_off_velocity: e.note_off_velocity
});

self.current_playing_notes.insert(PlayingNote {
channel: midi_message.get_channel(),
pitch: midi_message.get_pitch(),
};
if self.current_playing_notes.contains(&lookup) {
self.current_playing_notes.remove(&lookup);
mapped_channel: target_channel
});
}

if steps > 0 {
self.current_step = (self.current_step + 1) % steps ;
}
}
MidiMessageType::NoteOffMessage(midi_message) => {
let lookup = PlayingNote {
channel: midi_message.get_channel(),
pitch: midi_message.get_pitch(),
mapped_channel: midi_message.get_channel()
};

match self.current_playing_notes.take(&lookup) {
Some(note) => {
let raw_message : RawMessage = NoteOff {
channel: note.mapped_channel,
pitch: midi_message.pitch,
velocity: midi_message.velocity
}.into();

self.events.push(MidiEvent {
data: raw_message.into(),
delta_frames: e.delta_frames,
live: e.live,
note_length: e.note_length,
note_offset: e.note_offset,
detune: e.detune,
note_off_velocity: e.note_off_velocity
});
}
None => {
self.events.push(e);
}
}
_ => {
self.events.push(e);
}
}
} else {
self.events.push(e);
_ => {
self.events.push(e);
}
}
}
}
Expand Down
59 changes: 54 additions & 5 deletions note_fan_out/src/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ pub struct NoteFanoutParameters {
#[derive(Copy)]
pub enum Parameter {
Steps = 0,
Selection
Selection,
ChannelDistribute,
}


Expand All @@ -35,6 +36,7 @@ impl From<i32> for Parameter {
match i {
0 => Parameter::Steps,
1 => Parameter::Selection,
2 => Parameter::ChannelDistribute,
_ => panic!(format!("No such Parameter {}", i)),
}
}
Expand All @@ -46,21 +48,57 @@ impl Into<i32> for Parameter {
}
}

impl NoteFanoutParameters {
pub fn get_channel_distribution(&self, parameter: Parameter) -> ChannelDistribution {
return ChannelDistribution::from(self.transfer.get_parameter(parameter as usize))
}
}

impl ParameterConversion<Parameter> for NoteFanoutParameters {
fn get_parameter_transfer(&self) -> &ParameterTransfer {
&self.transfer
}

fn get_parameter_count() -> usize {
2
3
}
}

impl NoteFanoutParameters {
pub fn new(host: HostCallback) -> Self {
NoteFanoutParameters {
host: Mutex::new(HostCallbackLock { host }),
transfer: ParameterTransfer::new(2),
transfer: ParameterTransfer::new(3),
}
}
}

pub enum ChannelDistribution {
Channels(u8),
Off
}

impl From<f32> for ChannelDistribution {
fn from(i: f32) -> Self {
let channels_value: u8 = ((i - 1. / 15.) / 14. * 15. * 13. + 2.0) as u8;

if channels_value < 2 {
ChannelDistribution::Off
} else {
ChannelDistribution::Channels(channels_value) // channel 0 ( displayed as 1 ) is reserved for MPE
}
}
}


impl Into<f32> for ChannelDistribution {
fn into(self) -> f32 {
match self {
ChannelDistribution::Channels(value) => {
// normalize over 15 values, first range is off
((value as f32 - 2.) / 13.) * 14. / 15. + 1./15.
}
ChannelDistribution::Off => 0.0
}
}
}
Expand All @@ -79,13 +117,20 @@ impl PluginParameters for NoteFanoutParameters {
Parameter::Selection => {
format!("{}", self.get_byte_parameter(Parameter::Selection) / 8)
}
Parameter::ChannelDistribute => {
match self.get_channel_distribution(Parameter::ChannelDistribute) {
ChannelDistribution::Channels(c) => format!("{}", c),
ChannelDistribution::Off => "Off".to_string()
}
}
}
}

fn get_parameter_name(&self, index: i32) -> String {
match Parameter::from(index as i32) {
Parameter::Steps => "Steps",
Parameter::Selection => "Selection"
Parameter::Selection => "Selection",
Parameter::ChannelDistribute => "Channel distribution"
}
.to_string()
}
Expand All @@ -105,6 +150,9 @@ impl PluginParameters for NoteFanoutParameters {
self.transfer.set_parameter(index as usize, value)
}
}
Parameter::ChannelDistribute => {
self.transfer.set_parameter(index as usize, value);
}
}
}

Expand All @@ -129,10 +177,11 @@ impl Default for NoteFanoutParameters {
fn default() -> Self {
let parameters = NoteFanoutParameters {
host: Default::default(),
transfer: ParameterTransfer::new(2),
transfer: ParameterTransfer::new(3),
};
parameters.set_byte_parameter(Parameter::Steps, 0);
parameters.set_byte_parameter(Parameter::Selection, 0);
parameters.set_byte_parameter(Parameter::ChannelDistribute, 0);
parameters
}
}
24 changes: 18 additions & 6 deletions util/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ impl From<[u8;3]> for RawMessage {
}
}

impl Into<[u8;3]> for RawMessage {
fn into(self) -> [u8; 3] {
self.0
}
}

impl Index<usize> for RawMessage {
type Output = u8;

Expand Down Expand Up @@ -152,9 +158,9 @@ pub trait ChannelMessage {
}

pub struct NoteOn {
channel: u8,
pitch: u8,
velocity: u8
pub channel: u8,
pub pitch: u8,
pub velocity: u8
}

impl Into<RawMessage> for NoteOn {
Expand Down Expand Up @@ -207,9 +213,9 @@ pub trait NoteMessage where Self: ChannelMessage {
}

pub struct NoteOff {
channel: u8,
pitch: u8,
velocity: u8
pub channel: u8,
pub pitch: u8,
pub velocity: u8
}

impl From<NoteOn> for NoteOff {
Expand Down Expand Up @@ -339,6 +345,12 @@ impl ChannelMessage for CC {

pub struct GenericChannelMessage(RawMessage);

impl ChannelMessage for GenericChannelMessage {
fn get_channel(&self) -> u8 {
self.0[0] & 0x0F
}
}

impl From<RawMessage> for GenericChannelMessage {
fn from(data: RawMessage) -> Self {
GenericChannelMessage(data)
Expand Down

0 comments on commit 19e7a22

Please sign in to comment.