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_mod/Mod_player.ml.html
Source file Mod_player.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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274(* 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 Mod_player.mli *) (*****************************************************************************) (* Types *) (*****************************************************************************) (* a channel: its Paula, the row's cell, the note's period (before the * vibrato and the arpeggio bend it), the volume, and the effects' * memories (a 300 or a 400 goes on with the last speeds) *) type channel = { paula : Paula.t; mutable cell : Mod.cell; mutable instrument : int; (* the last one given, 1-based; 0: none yet *) mutable finetune : int; mutable period : int; mutable volume : int; mutable target : int; mutable slide_speed : int; mutable vibrato_speed : int; mutable vibrato_depth : int; mutable vibrato_pos : int; } type t = { mutable song : Mod.song; mutable samples : Signal.t array; (* the instruments' samples, as numbers *) channels : channel array; loop : bool; mutable reading : Paula.reading; mutable separation : float; mutable position : int; mutable row : int; mutable tick : int; mutable speed : int; mutable tempo : int; mutable until_tick : float; (* samples left before the next tick *) (* a Bxx or a Dxy, done when the row ends: the position, the row *) mutable next : (int * int) option; mutable finished : bool; } let tick_samples (tempo : int) : float = float_of_int Signal.rate *. 2.5 /. float_of_int tempo (* the instruments' samples as numbers, read once *) let samples_of (song : Mod.song) : Signal.t array = Array.map (fun (i : Mod.instrument) -> Array.init (String.length i.data) (Mod.sample i)) song.instruments let create ?(loop = true) (song : Mod.song) : t = let channel () = { paula = Paula.create (); cell = Mod.empty_cell; instrument = 0; finetune = 0; period = 0; volume = 0; target = 0; slide_speed = 0; vibrato_speed = 0; vibrato_depth = 0; vibrato_pos = 0; } in { song; samples = samples_of song; channels = Array.init (Mod.channels song) (fun _ -> channel ()); loop; reading = Hold; separation = 1.; position = 0; row = 0; tick = 0; speed = 6; tempo = 125; until_tick = 0.; next = None; finished = Array.length song.positions = 0; } let set_reading (p : t) (r : Paula.reading) : unit = p.reading <- r let set_separation (p : t) (s : float) : unit = p.separation <- Float.max 0. (Float.min 1. s) let position (p : t) : int * int = (p.position, p.row) let song (p : t) : Mod.song = p.song let seek (p : t) ~(position : int) ~(row : int) : unit = p.position <- max 0 (min (Array.length p.song.positions - 1) position); p.row <- max 0 (min 63 row); p.tick <- 0; p.until_tick <- 0.; p.next <- None; p.finished <- Array.length p.song.positions = 0 let set_song (p : t) (song : Mod.song) : unit = if song.instruments != p.song.instruments then p.samples <- samples_of song; p.song <- song; (* a position gone (the order list shortened): back to its last *) if p.position >= Array.length song.positions then p.position <- max 0 (Array.length song.positions - 1) let finished (p : t) : bool = p.finished let channel_period (p : t) (c : int) : int = p.channels.(c).period let channel_volume (p : t) (c : int) : int = p.channels.(c).volume let speed (p : t) : int = p.speed let tempo (p : t) : int = p.tempo (*****************************************************************************) (* Periods *) (*****************************************************************************) (* the period a finetune (eighths of a semitone) and [semitones] up make * of [period] *) let bend (period : int) ~(finetune : int) ~(semitones : int) : int = int_of_float (Float.round (float_of_int period /. Float.pow 2. ((float_of_int semitones +. (float_of_int finetune /. 8.)) /. 12.))) let clamp_period (p : int) : int = max 113 (min 856 p) (* ProTracker's vibrato: a quarter sine, 0 to 255 in 16 steps *) let vibrato_table = [| 0; 24; 49; 74; 97; 120; 141; 161; 180; 197; 212; 224; 235; 244; 250; 253; 255; 253; 250; 244; 235; 224; 212; 197; 180; 161; 141; 120; 97; 74; 49; 24 |] (*****************************************************************************) (* A row's first tick: the cells *) (*****************************************************************************) let volume_slide (c : channel) (param : int) : unit = let up = param lsr 4 and down = param land 0x0F in c.volume <- max 0 (min 64 (if up > 0 then c.volume + up else c.volume - down)) let play_cell (p : t) (c : channel) (cell : Mod.cell) : unit = c.cell <- cell; if cell.instrument > 0 && cell.instrument <= Array.length p.song.instruments then ( let i = p.song.instruments.(cell.instrument - 1) in c.instrument <- cell.instrument; c.volume <- i.volume; c.finetune <- i.finetune); (* a new note restarts the channel's instrument, unless it's where a * slide goes *) if cell.period > 0 then ( let period = bend cell.period ~finetune:c.finetune ~semitones:0 in if cell.fx = 3 || cell.fx = 5 then c.target <- period else ( c.period <- period; c.vibrato_pos <- 0; if c.instrument > 0 then ( let k = c.instrument - 1 in let i = p.song.instruments.(k) in let offset = if cell.fx = 9 then cell.param * 256 else 0 in if Array.length p.samples.(k) > 0 then Paula.trigger c.paula p.samples.(k) ~loop_start:i.loop_start ~loop_length:i.loop_length ~offset))); (* the effects of the first tick *) let x = cell.param lsr 4 and y = cell.param land 0x0F in match cell.fx with | 3 -> if cell.param > 0 then c.slide_speed <- cell.param | 4 -> if x > 0 then c.vibrato_speed <- x; if y > 0 then c.vibrato_depth <- y | 0xB -> p.next <- Some (cell.param, 0) | 0xC -> c.volume <- min 64 cell.param | 0xD -> p.next <- Some ((match p.next with Some (pos, _) -> pos | None -> p.position + 1), min 63 ((x * 10) + y)) | 0xE -> ( match x with | 1 -> c.period <- clamp_period (c.period - y) | 2 -> c.period <- clamp_period (c.period + y) | 0xA -> c.volume <- min 64 (c.volume + y) | 0xB -> c.volume <- max 0 (c.volume - y) | 0xC -> if y = 0 then c.volume <- 0 | _ -> ()) | 0xF -> if cell.param = 0 then () else if cell.param < 32 then p.speed <- cell.param else p.tempo <- cell.param | _ -> () (*****************************************************************************) (* The other ticks: the effects going on *) (*****************************************************************************) let slide_to (c : channel) : unit = if c.target > 0 then if c.period < c.target then c.period <- min c.target (c.period + c.slide_speed) else c.period <- max c.target (c.period - c.slide_speed) let vibrato_delta (c : channel) : int = let d = vibrato_table.(c.vibrato_pos land 31) * c.vibrato_depth / 128 in if c.vibrato_pos land 32 <> 0 then -d else d (* the period Paula plays this tick: the note's, bent by the arpeggio * or the vibrato *) let heard_period (p : t) (c : channel) : int = let cell = c.cell in match cell.fx with | 0 when cell.param > 0 -> let s = match p.tick mod 3 with 0 -> 0 | 1 -> cell.param lsr 4 | _ -> cell.param land 0x0F in bend c.period ~finetune:0 ~semitones:s | 4 | 6 -> c.period + vibrato_delta c | _ -> c.period let effect_tick (p : t) (c : channel) : unit = let cell = c.cell in let x = cell.param lsr 4 and y = cell.param land 0x0F in match cell.fx with | 1 -> c.period <- clamp_period (c.period - cell.param) | 2 -> c.period <- clamp_period (c.period + cell.param) | 3 -> slide_to c | 4 -> c.vibrato_pos <- (c.vibrato_pos + c.vibrato_speed) land 63 | 5 -> slide_to c; volume_slide c cell.param | 6 -> c.vibrato_pos <- (c.vibrato_pos + c.vibrato_speed) land 63; volume_slide c cell.param | 0xA -> volume_slide c cell.param | 0xE when x = 0xC && p.tick = y -> c.volume <- 0 | _ -> () (*****************************************************************************) (* Ticks, rows, positions *) (*****************************************************************************) let tick (p : t) : unit = if not p.finished then ( if p.tick = 0 then ( let pattern = p.song.patterns.(p.song.positions.(p.position)) in Array.iteri (fun k c -> play_cell p c pattern.(p.row).(k)) p.channels) else Array.iter (effect_tick p) p.channels; Array.iter (fun c -> Paula.set_period c.paula (clamp_period (heard_period p c)); Paula.set_volume c.paula c.volume) p.channels; p.tick <- p.tick + 1; (* the row over: the next, or where a jump or a break says *) if p.tick >= p.speed then ( p.tick <- 0; let position, row = match p.next with Some (pos, r) -> (pos, r) | None -> if p.row = 63 then (p.position + 1, 0) else (p.position, p.row + 1) in p.next <- None; if position >= Array.length p.song.positions then if p.loop then ( p.position <- 0; p.row <- row) else ( p.finished <- true; Array.iter (fun c -> Paula.stop c.paula) p.channels) else ( p.position <- position; p.row <- row))) let fill (p : t) (out : Signal.stereo) : unit = let n = Array.length out.left in (* channels 1 and 4 left, 2 and 3 right: the Amiga's *) let near = (1. +. p.separation) /. 2. and far = (1. -. p.separation) /. 2. in for i = 0 to n - 1 do if p.until_tick <= 0. then ( tick p; p.until_tick <- p.until_tick +. tick_samples p.tempo); p.until_tick <- p.until_tick -. 1.; let l = ref 0. and r = ref 0. in Array.iteri (fun k c -> let x = Paula.next p.reading c.paula in let left = k mod 4 = 0 || k mod 4 = 3 in l := !l +. (x *. if left then near else far); r := !r +. (x *. if left then far else near)) p.channels; (* two channels a side at full volume: 2, halved *) out.left.(i) <- 0.5 *. !l; out.right.(i) <- 0.5 *. !r done
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>