package tiny_libs
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0
doc/src/tiny_libs.audio_midi/Midi.ml.html
Source file Midi.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160(* Claude Code * * Copyright (C) 2026 Yoann Padioleau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * (LGPL) as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. *) (* See Midi.mli *) type note = { start : float; length : float; key : int; velocity : int; channel : int; program : int } type score = { notes : note list; duration : float } (*****************************************************************************) (* Variable-length quantities *) (*****************************************************************************) let vlq (n : int) : string = (* 7 bits at a time, the lowest last; every byte but the last with * its high bit set *) let rec groups n acc = if n < 128 then n :: acc else groups (n lsr 7) ((n land 127) :: acc) in let gs = groups n [] in let last = List.length gs - 1 in String.concat "" (List.mapi (fun i g -> String.make 1 (Char.chr (if i < last then g lor 128 else g))) gs) let read_vlq (s : string) (i : int) : int * int = let rec go i acc = let b = Char.code s.[i] in let acc = (acc lsl 7) lor (b land 127) in if b land 128 <> 0 then go (i + 1) acc else (acc, i + 1) in go i 0 (*****************************************************************************) (* Reading *) (*****************************************************************************) let u32 s i = (Char.code s.[i] lsl 24) lor (Char.code s.[i + 1] lsl 16) lor (Char.code s.[i + 2] lsl 8) lor Char.code s.[i + 3] let u16 s i = (Char.code s.[i] lsl 8) lor Char.code s.[i + 1] (* a track's events at their ticks: (tick, `Tempo us | `On (ch, key, vel) * | `Off (ch, key) | `Program (ch, p)) *) let track_events (s : string) (start : int) (stop : int) = let events = ref [] and tick = ref 0 and status = ref 0 in let i = ref start in (try while !i < stop do let (delta, j) = read_vlq s !i in tick := !tick + delta; i := j; let b = Char.code s.[!i] in (* running status: a data byte where a status was expected *) let st = if b land 0x80 <> 0 then (incr i; if b < 0xF0 then status := b; b) else !status in let data () = let d = Char.code s.[!i] in incr i; d in match st land 0xF0 with | 0x90 -> let k = data () in let v = data () in events := (!tick, if v = 0 then `Off (st land 15, k) else `On (st land 15, k, v)) :: !events | 0x80 -> let k = data () in ignore (data ()); events := (!tick, `Off (st land 15, k)) :: !events | 0xC0 -> events := (!tick, `Program (st land 15, data ())) :: !events | 0xD0 -> ignore (data ()) | 0xA0 | 0xB0 | 0xE0 -> ignore (data ()); ignore (data ()) | 0xF0 when st = 0xFF -> (* a meta event: its type, its length, its data *) let typ = data () in let (len, j) = read_vlq s !i in (* the tempo: 3 bytes, microseconds per quarter note *) if typ = 0x51 && len = 3 then events := (!tick, `Tempo ((Char.code s.[j] lsl 16) lor (Char.code s.[j + 1] lsl 8) lor Char.code s.[j + 2])) :: !events; i := j + len; if typ = 0x2F then i := stop | 0xF0 -> (* a system exclusive message: its length, skipped *) let (len, j) = read_vlq s !i in i := j + len | _ -> i := stop done with Invalid_argument _ -> ()); List.rev !events let parse (s : string) : (score, string) result = if String.length s < 14 || String.sub s 0 4 <> "MThd" then Error "not a MIDI file (no MThd)" else let tracks = u16 s 10 and division = u16 s 12 in if division land 0x8000 <> 0 then Error "SMPTE time division: not supported" else (* the tracks' chunks *) let rec chunks i n acc = if n = 0 || i + 8 > String.length s then List.rev acc else let len = u32 s (i + 4) in let acc = if String.sub s i 4 = "MTrk" then track_events s (i + 8) (min (String.length s) (i + 8 + len)) :: acc else acc in chunks (i + 8 + len) (n - 1) acc in let all = List.stable_sort (fun (a, _) (b, _) -> compare a b) (List.concat (chunks (8 + u32 s 4) tracks [])) in (* ticks to seconds, walking the tempo map *) let tempo = ref 500000 and last_tick = ref 0 and last_time = ref 0. in let seconds tick = !last_time +. (float_of_int (tick - !last_tick) *. float_of_int !tempo /. 1e6 /. float_of_int division) in let programs = Array.make 16 0 and on = Hashtbl.create 64 and notes = ref [] in List.iter (fun (tick, ev) -> let time = seconds tick in match ev with | `Tempo us -> last_time := time; last_tick := tick; tempo := us | `Program (ch, p) -> programs.(ch) <- p | `On (ch, key, velocity) -> Hashtbl.add on (ch, key) (time, velocity) | `Off (ch, key) -> ( match Hashtbl.find_opt on (ch, key) with | Some (start, velocity) -> Hashtbl.remove on (ch, key); notes := { start; length = time -. start; key; velocity; channel = ch; program = programs.(ch) } :: !notes | None -> ())) all; let notes = List.stable_sort (fun a b -> compare a.start b.start) !notes in let duration = List.fold_left (fun m n -> Float.max m (n.start +. n.length)) 0. notes in if notes = [] then Error "no notes" else Ok { notes; duration } (*****************************************************************************) (* Writing *) (*****************************************************************************) let be32 n = String.init 4 (fun i -> Char.chr ((n lsr (8 * (3 - i))) land 255)) let be16 n = String.init 2 (fun i -> Char.chr ((n lsr (8 * (1 - i))) land 255)) let chunk tag body = tag ^ be32 (String.length body) ^ body let bytes l = String.init (List.length l) (fun i -> Char.chr (List.nth l i)) let of_tune ?(program = 80) (tune : Abc.tune) : string = let division = 480 in (* 120 beats a minute: a second is two quarters, 960 ticks *) let ticks seconds = int_of_float (Float.round (seconds *. 960.)) in let track ch (events : Abc.event list) = (* note on at the start, off at the end, as (tick, bytes), in time * order, offs before ons at the same tick *) let msgs = List.concat_map (fun (e : Abc.event) -> List.concat_map (fun k -> [ (ticks e.start, 1, bytes [ 0x90 lor ch; k; 100 ]); (ticks (e.start +. e.length), 0, bytes [ 0x80 lor ch; k; 64 ]) ]) e.notes) events |> List.stable_sort compare in let (body, _) = List.fold_left (fun (acc, last) (tick, _, msg) -> (acc ^ vlq (tick - last) ^ msg, tick)) (vlq 0 ^ bytes [ 0xC0 lor ch; program ], 0) msgs in chunk "MTrk" (body ^ vlq 0 ^ bytes [ 0xFF; 0x2F; 0 ]) in let tempo = chunk "MTrk" (vlq 0 ^ bytes [ 0xFF; 0x51; 3; 0x07; 0xA1; 0x20 ] ^ vlq 0 ^ bytes [ 0xFF; 0x2F; 0 ]) in (* a percussion voice goes to channel 10 (9 from 0), General MIDI's drums *) let tracks = List.mapi (fun i events -> track (if List.nth_opt tune.drums i = Some true then 9 else i) events) tune.voices in chunk "MThd" (be16 1 ^ be16 (1 + List.length tracks) ^ be16 division) ^ String.concat "" (tempo :: tracks)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>