Skip to main content
Glama
sin5ddd
by sin5ddd
analyze.rs4.2 kB
use crate::model::{Analyzed, TrackStat}; use crate::utils::{sha256_hex, ticks_to_seconds}; use anyhow::{Result, anyhow}; use std::collections::{BTreeSet, HashMap}; pub fn analyze_basic(bytes: &[u8]) -> Result<Analyzed> { let smf = midly::Smf::parse(bytes).map_err(|e| anyhow!("midly parse error: {e}"))?; let (format, ppq) = match smf.header.timing { midly::Timing::Metrical(p) => (format!("{:?}", smf.header.format), p.as_int()), midly::Timing::Timecode(_, _) => (format!("{:?}", smf.header.format), 480), }; use midly::{MetaMessage as MM, MidiMessage as Msg, TrackEventKind as TEK}; let mut tempo_map: Vec<(u64, f64)> = Vec::new(); let mut time_sigs: Vec<(u64, u8, u8)> = Vec::new(); let mut key_sigs: Vec<(u64, i8, bool)> = Vec::new(); let mut tracks_info: Vec<TrackStat> = Vec::new(); for (ti, track) in smf.tracks.iter().enumerate() { let mut name: Option<String> = None; let mut channels = BTreeSet::new(); let mut programs: HashMap<u8, u8> = HashMap::new(); let mut note_count: usize = 0; let mut min_pitch: Option<u8> = None; let mut max_pitch: Option<u8> = None; let mut vel_sum: u64 = 0; let mut vel_n: u64 = 0; let mut has_drum = false; let mut tick_acc: u64 = 0; for ev in track { tick_acc += ev.delta.as_int() as u64; match &ev.kind { TEK::Meta(MM::Tempo(us)) => { let bpm = 60_000_000.0 / (us.as_int() as f64); tempo_map.push((tick_acc, bpm)); } TEK::Meta(MM::TimeSignature(n, d, _, _)) => { time_sigs.push((tick_acc, *n, 2u8.pow((*d).into()))); } TEK::Meta(MM::KeySignature(sf, mi)) => { key_sigs.push((tick_acc, *sf as i8, *mi)); } TEK::Meta(MM::TrackName(name_bytes)) => { name = Some(String::from_utf8_lossy(name_bytes).to_string()); } TEK::Midi { channel, message } => { let ch = channel.as_int(); channels.insert(ch); if ch == 9 { has_drum = true; } match message { Msg::ProgramChange { program } => { programs.insert(ch, program.as_int()); } Msg::NoteOn { key, vel } => { let p = key.as_int(); let v = vel.as_int(); if v > 0 { note_count += 1; vel_sum += v as u64; vel_n += 1; min_pitch = Some(min_pitch.map_or(p, |m| m.min(p))); max_pitch = Some(max_pitch.map_or(p, |m| m.max(p))); } } _ => {} } } _ => {} } } let velocity_avg = if vel_n > 0 { Some(vel_sum as f32 / vel_n as f32) } else { None }; let note_range = match (min_pitch, max_pitch) { (Some(a), Some(b)) => Some((a, b)), _ => None, }; tracks_info.push(TrackStat { index: ti, name, channels: channels.into_iter().collect(), programs, note_count, note_range, velocity_avg, has_drum, }); } let duration_ticks: u64 = smf .tracks .iter() .map(|t| t.iter().map(|e| e.delta.as_int() as u64).sum::<u64>()) .max() .unwrap_or(0); let duration_sec = ticks_to_seconds(duration_ticks, ppq as u64, &tempo_map); let file_id = sha256_hex(bytes); Ok(Analyzed { file_id, format, ppq, duration_ticks, duration_sec, tempo_map, time_sigs, key_sigs, tracks: tracks_info, }) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sin5ddd/midi-analyer-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server