moont 1.0.0

Roland CM-32L synthesizer emulator
Documentation
// Copyright (C) 2021-2026 Geoff Hill <geoff@geoffhill.org>
//
// 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.

//! General MIDI to CM-32L translation.
//!
//! The CM-32L has its own instrument map that differs entirely from General
//! MIDI. [`GmSynth`] wraps a [`crate::cm32l::Device`] and transparently translates GM
//! program changes, drum notes, and pan direction so that GM MIDI sources
//! are playable through the CM-32L.

use crate::rom::Rom;
use crate::{CM32L, ControlCommand, ControlError, Frame, Synth};
use alloc::{vec, vec::Vec};

/// GM program number (0-127) -> CM-32L program number (0-127).
const PROGRAM_MAP: [u8; 128] = [
    // Piano (GM 0-7).
    0,  // 0  Acoustic Grand Piano    -> AcouPiano1.
    1,  // 1  Bright Acoustic Piano   -> AcouPiano2.
    2,  // 2  Electric Grand Piano    -> AcouPiano3.
    3,  // 3  Honky-tonk Piano        -> ElecPiano1.
    4,  // 4  Electric Piano 1        -> ElecPiano2.
    5,  // 5  Electric Piano 2        -> ElecPiano3.
    7,  // 6  Harpsichord             -> Honkytonk.
    19, // 7  Clavinet                -> Clavi1.
    // Chromatic Percussion (GM 8-15).
    22,  // 8  Celesta                 -> Celesta1.
    101, // 9  Glockenspiel            -> Glock.
    99,  // 10 Music Box               -> SynMallet.
    97,  // 11 Vibraphone              -> Vibe1.
    104, // 12 Marimba                 -> Marimba.
    103, // 13 Xylophone               -> Xylophone.
    102, // 14 Tubular Bells           -> TubeBell.
    100, // 15 Dulcimer                -> Windbell.
    // Organ (GM 16-23).
    8,  // 16 Drawbar Organ           -> ElecOrg1.
    9,  // 17 Percussive Organ        -> ElecOrg2.
    10, // 18 Rock Organ              -> ElecOrg3.
    12, // 19 Church Organ            -> PipeOrg1.
    13, // 20 Reed Organ              -> PipeOrg2.
    15, // 21 Accordion               -> Accordion.
    87, // 22 Harmonica               -> Harmonica.
    11, // 23 Tango Accordion         -> ElecOrg4.
    // Guitar (GM 24-31).
    59, // 24 Acoustic Guitar (nylon) -> Guitar1.
    60, // 25 Acoustic Guitar (steel) -> Guitar2.
    61, // 26 Electric Guitar (jazz)  -> ElecGtr1.
    62, // 27 Electric Guitar (clean) -> ElecGtr2.
    61, // 28 Electric Guitar (muted) -> ElecGtr1.
    62, // 29 Overdriven Guitar       -> ElecGtr2.
    62, // 30 Distortion Guitar       -> ElecGtr2.
    62, // 31 Guitar Harmonics        -> ElecGtr2.
    // Bass (GM 32-39).
    64, // 32 Acoustic Bass           -> AcouBass1.
    66, // 33 Electric Bass (finger)  -> ElecBass1.
    67, // 34 Electric Bass (pick)    -> ElecBass2.
    70, // 35 Fretless Bass           -> Fretless1.
    68, // 36 Slap Bass 1             -> SlapBass1.
    69, // 37 Slap Bass 2             -> SlapBass2.
    28, // 38 Synth Bass 1            -> SynBass1.
    30, // 39 Synth Bass 2            -> SynBass3.
    // Strings (GM 40-47).
    52,  // 40 Violin                  -> Violin1.
    53,  // 41 Viola                   -> Violin2.
    54,  // 42 Cello                   -> Cello1.
    56,  // 43 Contrabass              -> Contrabass.
    48,  // 44 Tremolo Strings         -> StrSect1.
    51,  // 45 Pizzicato Strings       -> Pizzicato.
    57,  // 46 Orchestral Harp         -> Harp1.
    112, // 47 Timpani                 -> Timpani.
    // Ensemble (GM 48-55).
    48,  // 48 String Ensemble 1       -> StrSect1.
    49,  // 49 String Ensemble 2       -> StrSect2.
    50,  // 50 Synth Strings 1         -> StrSect3.
    50,  // 51 Synth Strings 2         -> StrSect3.
    34,  // 52 Choir Aahs              -> Chorale.
    39,  // 53 Voice Oohs              -> FunnyVox.
    34,  // 54 Synth Choir             -> Chorale.
    122, // 55 Orchestra Hit           -> OrcheHit.
    // Brass (GM 56-63).
    88, // 56 Trumpet                 -> Trumpet1.
    90, // 57 Trombone                -> Trombone1.
    94, // 58 Tuba                    -> Tuba.
    88, // 59 Muted Trumpet           -> Trumpet1.
    92, // 60 French Horn             -> FrHorn1.
    95, // 61 Brass Section           -> BrsSect1.
    24, // 62 Synth Brass 1           -> SynBrass1.
    26, // 63 Synth Brass 2           -> SynBrass3.
    // Reed (GM 64-71).
    78, // 64 Soprano Sax             -> Sax1.
    79, // 65 Alto Sax                -> Sax2.
    80, // 66 Tenor Sax               -> Sax3.
    81, // 67 Baritone Sax            -> Sax4.
    84, // 68 Oboe                    -> Oboe.
    85, // 69 English Horn            -> EnglHorn.
    86, // 70 Bassoon                 -> Bassoon.
    82, // 71 Clarinet                -> Clarinet1.
    // Pipe (GM 72-79).
    74,  // 72 Piccolo                 -> Piccolo1.
    72,  // 73 Flute                   -> Flute1.
    76,  // 74 Recorder                -> Recorder.
    77,  // 75 Pan Flute               -> PanPipes.
    110, // 76 Blown Bottle            -> Bottleblow.
    107, // 77 Shakuhachi              -> Shakuhachi.
    108, // 78 Whistle                 -> Whistle1.
    77,  // 79 Ocarina                 -> PanPipes.
    // Synth Lead (GM 80-87).
    47, // 80 Lead 1 (square)         -> SquareWave.
    47, // 81 Lead 2 (sawtooth)       -> SquareWave.
    42, // 82 Lead 3 (calliope)       -> Oboe2001.
    24, // 83 Lead 4 (chiff)          -> SynBrass1.
    26, // 84 Lead 5 (charang)        -> SynBrass3.
    34, // 85 Lead 6 (voice)          -> Chorale.
    32, // 86 Lead 7 (fifths)         -> Fantasy.
    24, // 87 Lead 8 (bass + lead)    -> SynBrass1.
    // Synth Pad (GM 88-95).
    36, // 88 Pad 1 (new age)         -> Soundtrack.
    38, // 89 Pad 2 (warm)            -> WarmBell.
    33, // 90 Pad 3 (polysynth)       -> HarmoPan.
    34, // 91 Pad 4 (choir)           -> Chorale.
    35, // 92 Pad 5 (bowed)           -> Glasses.
    37, // 93 Pad 6 (metallic)        -> Atmosphere.
    36, // 94 Pad 7 (halo)            -> Soundtrack.
    32, // 95 Pad 8 (sweep)           -> Fantasy.
    // Synth Effects (GM 96-103).
    41, // 96 FX 1 (rain)             -> IceRain.
    36, // 97 FX 2 (soundtrack)       -> Soundtrack.
    35, // 98 FX 3 (crystal)          -> Glasses.
    37, // 99 FX 4 (atmosphere)       -> Atmosphere.
    38, // 100 FX 5 (brightness)      -> WarmBell.
    32, // 101 FX 6 (goblins)         -> Fantasy.
    43, // 102 FX 7 (echoes)          -> EchoPan.
    36, // 103 FX 8 (sci-fi)          -> Soundtrack.
    // Ethnic (GM 104-111).
    63,  // 104 Sitar                  -> Sitar.
    59,  // 105 Banjo                  -> Guitar1.
    107, // 106 Shamisen               -> Shakuhachi.
    105, // 107 Koto                   -> Koto.
    112, // 108 Kalimba                -> Timpani.
    77,  // 109 Bag Pipe               -> PanPipes.
    52,  // 110 Fiddle                 -> Violin1.
    106, // 111 Shanai                 -> Sho.
    // Percussive (GM 112-119).
    112, // 112 Tinkle Bell            -> Timpani.
    117, // 113 Agogo                  -> Taiko.
    113, // 114 Steel Drums            -> MelodicTom.
    113, // 115 Woodblock              -> MelodicTom.
    117, // 116 Taiko Drum             -> Taiko.
    113, // 117 Melodic Tom            -> MelodicTom.
    114, // 118 Synth Drum             -> DeepSnare.
    119, // 119 Reverse Cymbal         -> Cymbal.
    // Sound Effects (GM 120-127).
    62,  // 120 Guitar Fret Noise      -> ElecGtr2.
    111, // 121 Breath Noise           -> Breathpipe.
    36,  // 122 Seashore               -> Soundtrack.
    124, // 123 Bird Tweet             -> BirdTweet.
    123, // 124 Telephone Ring         -> Telephone.
    122, // 125 Helicopter             -> OrcheHit.
    36,  // 126 Applause               -> Soundtrack.
    96,  // 127 Gunshot                -> BrsSect2.
];

/// GM drum note (0-127) -> CM-32L rhythm note (0-127).
const DRUM_MAP: [u8; 128] = {
    let mut m = [0u8; 128];
    let mut i = 0;
    while i < 128 {
        m[i] = i as u8;
        i += 1;
    }

    // GM 35-51 are identical to CM-32L 35-51 (bass drum through ride cymbal).

    // GM 52 Chinese Cymbal   -> 49 CrashCymbal.
    m[52] = 49;
    // GM 53 Ride Bell         -> 51 RideCymbal.
    m[53] = 51;
    // GM 54 Tambourine        -> 54 Tambourine (same).
    // GM 55 Splash Cymbal     -> 49 CrashCymbal.
    m[55] = 49;
    // GM 56 Cowbell            -> 56 Cowbell (same).
    // GM 57 Crash Cymbal 2    -> 49 CrashCymbal.
    m[57] = 49;
    // GM 58 Vibraslap         -> 73 Quijada.
    m[58] = 73;
    // GM 59 Ride Cymbal 2     -> 51 RideCymbal.
    m[59] = 51;
    // GM 60-64 are identical (bongos, congas).
    // GM 65-66 are identical (timbales).
    // GM 67-68 are identical (agogo).
    // GM 69 Cabasa            -> 69 Cabasa (same).
    // GM 70 Maracas           -> 70 Maracas (same).
    // GM 71-72 are identical (whistles).
    // GM 73 Short Guiro       -> 73 Quijada.
    // GM 74 Long Guiro        -> 73 Quijada.
    m[74] = 73;
    // GM 75 Claves            -> 75 Claves (same).
    // GM 76 Hi Wood Block     -> 75 Claves.
    m[76] = 75;
    // GM 77 Low Wood Block    -> 75 Claves.
    m[77] = 75;
    // GM 78 Mute Cuica        -> 78 Punch.
    // GM 79 Open Cuica        -> 78 Punch.
    m[79] = 78;
    // GM 80 Mute Triangle     -> 121 Triangle (CM-32L).
    m[80] = 121;
    // GM 81 Open Triangle     -> 121 Triangle (CM-32L).
    m[81] = 121;

    m
};

fn build_sysex(addr: &[u8; 3], data: &[u8]) -> Vec<u8> {
    let mut msg = vec![0xF0, 0x41, 0x10, 0x16, 0x12];
    msg.extend_from_slice(addr);
    msg.extend_from_slice(data);
    let mut sum = 0u8;
    for &b in &msg[5..] {
        sum = sum.wrapping_add(b);
    }
    let checksum = (128 - (sum & 0x7F)) & 0x7F;
    msg.push(checksum);
    msg.push(0xF7);
    msg
}

fn translate_msg(msg: u32) -> Option<u32> {
    let status = msg & 0xF0;
    let channel = msg & 0x0F;

    match status {
        // Program Change: remap program number.
        0xC0 => {
            let program = (msg >> 8) & 0x7F;
            let mapped = PROGRAM_MAP[program as usize];
            Some(0xC0 | channel | ((mapped as u32) << 8))
        }
        // Control Change: reverse pan.
        0xB0 => {
            let cc = (msg >> 8) & 0x7F;
            if cc == 10 {
                let value = (msg >> 16) & 0x7F;
                let reversed = 127 - value;
                Some((msg & 0xFF00FF) | (reversed << 16))
            } else {
                Some(msg)
            }
        }
        // Note On/Off on drum channel (9 = MIDI channel 10): remap note.
        0x90 | 0x80 if channel == 9 => {
            let note = (msg >> 8) & 0x7F;
            let mapped = DRUM_MAP[note as usize];
            let rest = msg & 0xFF0000FF;
            Some(rest | ((mapped as u32) << 8))
        }
        _ => Some(msg),
    }
}

fn is_device_reset(sysex: &[u8]) -> bool {
    if sysex.len() < 8 {
        return false;
    }
    if sysex[0] != 0xF0 || sysex[sysex.len() - 1] != 0xF7 {
        return false;
    }
    if sysex[1] != 0x41 || sysex[2] > 0x10 || sysex[3] != 0x16 {
        return false;
    }
    if sysex[4] != 0x12 || sysex[5] != 0x7F {
        return false;
    }
    let mut sum = 0u8;
    for i in 5..sysex.len() - 2 {
        sum = sum.wrapping_add(sysex[i]);
    }
    let expected = (128 - (sum & 0x7F)) & 0x7F;
    sysex[sysex.len() - 2] == expected
}

/// CM-32L synthesizer with General MIDI translation.
///
/// Wraps a [`crate::cm32l::Device`] and intercepts MIDI messages, translating GM program
/// changes, drum note numbers, and pan direction into their CM-32L
/// equivalents. On construction, sends SysEx to assign parts 1-8 to MIDI
/// channels 1-8 (with rhythm on channel 10) and set pitch bend range to 2
/// semitones on all parts.
///
/// GM defaults are automatically reapplied after a device reset (SysEx or
/// [`ControlCommand::Reset`]).
#[non_exhaustive]
#[derive(Debug)]
pub struct GmSynth {
    inner: CM32L,
}

impl GmSynth {
    /// Creates a new GM-translating synthesizer.
    pub fn new(rom: Rom) -> GmSynth {
        let mut s = GmSynth {
            inner: CM32L::new(rom),
        };
        s.apply_gm_defaults();
        s
    }

    fn apply_gm_defaults(&mut self) {
        // Assign parts 1-8 to MIDI channels 1-8, part 9 (rhythm) to channel 10.
        // System area chanAssign[0..9] at address 0x10000D.
        let sysex =
            build_sysex(&[0x10, 0x00, 0x0D], &[0, 1, 2, 3, 4, 5, 6, 7, 9]);
        self.inner.play_sysex(&sysex);

        // Set bender range to 2 on all 9 parts via patch temp.
        // Patch temp base = 0x030000, benderRange offset = 4, each entry = 16 bytes.
        for part in 0u8..9 {
            let base = 0x030000u32 + part as u32 * 16 + 4;
            let a0 = ((base >> 16) & 0x7F) as u8;
            let a1 = ((base >> 8) & 0x7F) as u8;
            let a2 = (base & 0x7F) as u8;
            let sysex = build_sysex(&[a0, a1, a2], &[2]);
            self.inner.play_sysex(&sysex);
        }
    }

    fn apply_gm_defaults_at(&mut self, time: u32) {
        let sysex =
            build_sysex(&[0x10, 0x00, 0x0D], &[0, 1, 2, 3, 4, 5, 6, 7, 9]);
        self.inner.play_sysex_at(&sysex, time);

        for part in 0u8..9 {
            let base = 0x030000u32 + part as u32 * 16 + 4;
            let a0 = ((base >> 16) & 0x7F) as u8;
            let a1 = ((base >> 8) & 0x7F) as u8;
            let a2 = (base & 0x7F) as u8;
            let sysex = build_sysex(&[a0, a1, a2], &[2]);
            self.inner.play_sysex_at(&sysex, time);
        }
    }
}

impl Synth for GmSynth {
    fn current_time(&self) -> u32 {
        self.inner.current_time()
    }

    fn play_msg_at(&mut self, msg: u32, time: u32) -> bool {
        match translate_msg(msg) {
            Some(m) => self.inner.play_msg_at(m, time),
            None => true,
        }
    }

    fn play_sysex_at(&mut self, sysex: &[u8], time: u32) -> bool {
        let ok = self.inner.play_sysex_at(sysex, time);
        if is_device_reset(sysex) {
            self.apply_gm_defaults_at(time);
        }
        ok
    }

    fn render(&mut self, out: &mut [Frame]) {
        self.inner.render(out);
    }

    fn apply_command(
        &mut self,
        command: ControlCommand,
    ) -> Result<(), ControlError> {
        let r = self.inner.apply_command(command);
        if matches!(command, ControlCommand::Reset) && r.is_ok() {
            self.apply_gm_defaults();
        }
        r
    }
}