Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not sure how to use the timing of emitted midi events #46

Closed
theor opened this issue Dec 12, 2022 · 4 comments
Closed

Not sure how to use the timing of emitted midi events #46

theor opened this issue Dec 12, 2022 · 4 comments

Comments

@theor
Copy link

theor commented Dec 12, 2022

First, great work ! I could get a vst3+clap plugin going in 5 minutes.

I'm trying to make a simple midi arpeggiator - one note in, multiple notes out. How would I emit a NoteOn midi event, let's say, one quarter noter later than the initial note received ? I'd love a quick sample.

Thanks !

@robbert-vdh
Copy link
Owner

Use context.pos_beats() and context.pos_samples() to figure out the current position in beats and samples, then determine the next note's start time in samples and count the number of samples until you arrive at the buffer where you should output the note on event (and then do a similar thing to determine when you should output the note off event). In practice it's of a course a bit more difficult than that if you want to take tempo changes into account (and also don't do the wrong thing when looping, which would be the case if you'd naively store the times as beats/ppq without also explicitly considering looping.

@theor
Copy link
Author

theor commented Dec 15, 2022

thanks, I'll try to figure it out.

@sourcebox
Copy link

I had a similar issue when writing a step sequencer. To solve it, I wrote a clock generator that can be used to generate regular events.

It can be used as iterator:

for (pulse_no, timing) in clock {
}

Implementation:

//! Clock generator.

use nih_plug::buffer::Buffer;
use nih_plug::context::process::Transport;

/// Clock generator.
#[derive(Debug)]
pub struct Clock {
    /// Position in the song in pulses.
    pos_pulses: Option<f64>,

    /// Duration of a pulse in samples.
    pulse_duration_samples: Option<f64>,

    /// Buffer length in samples.
    buffer_length: usize,

    /// Iteration count.
    count: usize,

    /// Flag if transport is playing.
    playing: bool,
}

impl Clock {
    /// Returns a new instance of `Clock`.
    /// - `buffer:` Reference to the buffer object.
    /// - `transport`: Reference to the transport object.
    /// - `ppq:` Pulses per quarter note.
    pub fn new(buffer: &Buffer, transport: &Transport, ppq: f64) -> Self {
        Self {
            pos_pulses: transport.pos_beats().map(|v| v * ppq),
            pulse_duration_samples: transport
                .tempo
                .map(|v| 60.0 / (v * ppq) * transport.sample_rate as f64),
            buffer_length: buffer.samples(),
            count: 0,
            playing: transport.playing,
        }
    }
}

impl Iterator for Clock {
    /// Tuple of (pulse number, timing).
    type Item = (i32, u32);

    /// Returns the next value.
    fn next(&mut self) -> Option<Self::Item> {
        if !self.playing {
            // Transport must be playing, otherwise clock generation makes no sense.
            return None;
        }

        if let (Some(pos_pulses), Some(pulse_duration_samples)) =
            (self.pos_pulses, self.pulse_duration_samples)
        {
            // Distance to the next pulse in samples.
            let next_pulse_delta = ((pos_pulses.ceil() - pos_pulses) * pulse_duration_samples
                + self.count as f64 * pulse_duration_samples)
                .round() as u32;

            if next_pulse_delta < self.buffer_length as u32 {
                let pulse = (pos_pulses.ceil() as i32, next_pulse_delta);

                // Prepare next pulse.
                self.pos_pulses = Some(pos_pulses + 1.0);
                self.count += 1;

                Some(pulse)
            } else {
                None
            }
        } else {
            None
        }
    }
}

@robbert-vdh If you find it useful, you could maybe include it as a helper in nih-glug.

@robbert-vdh
Copy link
Owner

If you have more questions about timing in the context of plugins, feel free to hop on the Rust Audio Discord and ask your question there!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants