moont 1.0.0

Roland CM-32L synthesizer emulator
Documentation
// Copyright (C) 2021-2026 Geoff Hill <geoff@geoffhill.org>
// Copyright (C) 2003-2026 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at
// your option) any later version. Read COPYING.LESSER.txt for details.

//! Provides the [`MidiQueue`] circular buffer of MIDI events.

use super::midi::Event;
use alloc::boxed::Box;

const MIDI_QUEUE_SIZE: usize = 4096;
const MIDI_QUEUE_MASK: usize = 0xfff;

#[derive(Debug)]
pub struct MidiQueue {
    items: Box<[Option<Event>; MIDI_QUEUE_SIZE]>,
    given: usize,
    taken: usize,
}

impl MidiQueue {
    pub fn new() -> MidiQueue {
        const INIT: Option<Event> = None;
        MidiQueue {
            items: Box::new([INIT; MIDI_QUEUE_SIZE]),
            given: 0,
            taken: 0,
        }
    }

    pub fn push(&mut self, value: Event) -> bool {
        let (given, taken) = (self.given, self.taken);

        if given.wrapping_sub(taken) >= MIDI_QUEUE_SIZE {
            return false;
        }

        let slot = given & MIDI_QUEUE_MASK;
        // assert_eq!(self.items[slot], None);
        self.items[slot] = Some(value);
        self.given = given.wrapping_add(1);
        true
    }

    pub fn peek(&self) -> Option<&Event> {
        let (given, taken) = (self.given, self.taken);

        if given == taken {
            return None;
        }

        let slot = taken & MIDI_QUEUE_MASK;
        self.items[slot].as_ref()
    }

    pub fn pop(&mut self) -> Option<Event> {
        let (given, taken) = (self.given, self.taken);

        if given == taken {
            return None;
        }

        let slot = taken & MIDI_QUEUE_MASK;
        let value = self.items[slot].take();
        self.taken = taken.wrapping_add(1);
        value
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::midi::Message;

    fn example_event() -> Event {
        Event {
            msg: Message::note_on(0x3c, 0x7f, 0x6).unwrap(),
            time: 10000,
        }
    }

    #[test]
    fn test_empty() {
        let mut queue = MidiQueue::new();

        assert!(queue.peek().is_none());
        assert!(queue.pop().is_none());
    }

    #[test]
    fn test_single() {
        let mut queue = MidiQueue::new();
        let event = example_event();

        assert_eq!(queue.push(event.clone()), true);

        assert_eq!(queue.peek().unwrap(), &event);
        assert_eq!(&queue.pop().unwrap(), &event);

        assert!(queue.peek().is_none());
        assert!(queue.pop().is_none());
    }

    #[test]
    fn test_mostly_empty() {
        let mut queue = MidiQueue::new();
        let event = example_event();

        assert_eq!(queue.push(event.clone()), true);
        assert_eq!(queue.push(event.clone()), true);
        assert_eq!(queue.push(event.clone()), true);

        assert_eq!(queue.peek().unwrap(), &event);
        assert_eq!(&queue.pop().unwrap(), &event);

        assert_eq!(queue.peek().unwrap(), &event);
        assert_eq!(&queue.pop().unwrap(), &event);

        assert_eq!(queue.peek().unwrap(), &event);
        assert_eq!(&queue.pop().unwrap(), &event);

        assert!(queue.peek().is_none());
        assert!(queue.pop().is_none());

        assert_eq!(queue.push(event.clone()), true);

        assert_eq!(queue.peek().unwrap(), &event);
        assert_eq!(&queue.pop().unwrap(), &event);

        assert!(queue.peek().is_none());
        assert!(queue.pop().is_none());
    }

    #[test]
    fn test_mostly_full() {
        let mut queue = MidiQueue::new();
        let event = example_event();

        for _ in 0..MIDI_QUEUE_SIZE - 3 {
            assert_eq!(queue.push(event.clone()), true);
        }
        assert_eq!(queue.push(event.clone()), true);
        assert_eq!(queue.push(event.clone()), true);
        assert_eq!(queue.push(event.clone()), true);
        assert_eq!(queue.push(event.clone()), false);

        assert_eq!(queue.peek().unwrap(), &event);
        assert_eq!(&queue.pop().unwrap(), &event);

        assert_eq!(queue.push(event.clone()), true);
        assert_eq!(queue.push(event.clone()), false);

        for _ in 0..MIDI_QUEUE_SIZE {
            assert_eq!(queue.peek().unwrap(), &event);
            assert_eq!(&queue.pop().unwrap(), &event);
        }

        assert!(queue.peek().is_none());
        assert!(queue.pop().is_none());
    }

    #[test]
    fn test_usize_overflow() {
        let mut queue = MidiQueue::new();
        let event = example_event();

        queue.given = usize::MAX - MIDI_QUEUE_SIZE;
        queue.taken = usize::MAX - MIDI_QUEUE_SIZE;

        const CHUNK: usize = 1000;
        assert!(CHUNK < MIDI_QUEUE_SIZE);

        for _ in 0..5 {
            for _ in 0..CHUNK {
                assert_eq!(queue.push(event.clone()), true);
            }

            for _ in 0..CHUNK {
                assert_eq!(queue.peek().unwrap(), &event);
                assert_eq!(&queue.pop().unwrap(), &event);
            }
        }

        assert!(queue.peek().is_none());
        assert!(queue.pop().is_none());
    }
}