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_abc/Doremi.ml.html
Source file Doremi.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(* 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 Doremi.mli *) (* the syllables, and their semitones above do *) let syllables = [ ("do", 0); ("ut", 0); ("re", 2); ("ré", 2); ("mi", 4); ("fa", 5); ("sol", 7); ("la", 9); ("si", 11) ] (* "sol#5:1/2" -> the syllable, then the rest *) let split_syllable (word : string) : (int * string) option = List.find_map (fun (name, semitone) -> let n = String.length name in if String.length word >= n && String.lowercase_ascii (String.sub word 0 n) = name then Some (semitone, String.sub word n (String.length word - n)) else None) (* the longest names first: "sol" before "si"... and "ré" (3 * bytes) before "re" *) (List.sort (fun (a, _) (b, _) -> compare (String.length b) (String.length a)) syllables) let fraction (s : string) : float option = match String.split_on_char '/' s with | [ a ] -> float_of_string_opt a | [ a; b ] -> ( match (float_of_string_opt a, float_of_string_opt b) with Some a, Some b when b <> 0. -> Some (a /. b) | _ -> None) | _ -> None type voice = { mutable time : float; mutable events : Abc.event list } let parse (text : string) : (Abc.tune, string) result = let beat = ref 0.5 (* seconds, at tempo 120 *) and octave = ref 4 in let voices = ref [] and current = ref None in let voice () = match !current with | Some v -> v | None -> let v = { time = 0.; events = [] } in voices := !voices @ [ v ]; current := Some v; v in let emit notes beats = let v = voice () in let length = beats *. !beat in v.events <- { Abc.start = v.time; length; notes } :: v.events; v.time <- v.time +. length in (* "re:1/2" -> the length's beats, and the word without it *) let with_length (word : string) : (string * float, string) result = match String.index_opt word ':' with | None -> Ok (word, 1.) | Some i -> ( match fraction (String.sub word (i + 1) (String.length word - i - 1)) with | Some beats -> Ok (String.sub word 0 i, beats) | None -> Error ("not a length: " ^ word)) in let error = ref None in let rec words = function | [] -> () | ("voix" | "voice") :: rest -> current := None; words rest | "tempo" :: bpm :: rest -> (match float_of_string_opt bpm with Some n when n > 0. -> beat := 60. /. n | _ -> error := Some ("not a tempo: " ^ bpm)); words rest | "|" :: rest -> words rest | word :: rest -> (match with_length word with | Error e -> error := Some e | Ok ("-", beats) -> emit [] beats | Ok (w, beats) -> ( match split_syllable w with | None -> error := Some ("not a note: " ^ word) | Some (semitone, tail) -> (* then # or b, then an octave *) let (accidental, tail) = if tail <> "" && tail.[0] = '#' then (1, String.sub tail 1 (String.length tail - 1)) else if tail <> "" && tail.[0] = 'b' then (-1, String.sub tail 1 (String.length tail - 1)) else (0, tail) in if tail <> "" then ( match int_of_string_opt tail with | Some o when o >= -1 && o <= 9 -> octave := o | _ -> error := Some ("not an octave: " ^ word)); emit [ ((!octave + 1) * 12) + semitone + accidental ] beats)); if !error = None then words rest in String.split_on_char '\n' text |> List.iter (fun line -> let line = match String.index_opt line '%' with Some i -> String.sub line 0 i | None -> line in if !error = None then words (List.filter (( <> ) "") (String.split_on_char ' ' (String.map (fun c -> if c = '\t' then ' ' else c) line)))); match !error with | Some e -> Error e | None -> let voices = List.map (fun v -> List.rev v.events) !voices in if List.for_all (( = ) []) voices then Error "no notes" else Ok { Abc.title = ""; voices; drums = List.map (fun _ -> false) voices }
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>