package tiny_libs

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file Oscillator.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
(* 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 Oscillator.mli *)

type waveform = Sine | Square | Triangle | Sawtooth

let waveforms = [ Sine; Square; Triangle; Sawtooth ]
let name = function Sine -> "sine" | Square -> "square" | Triangle -> "triangle" | Sawtooth -> "sawtooth"

let wave (w : waveform) (phase : float) : float =
  match w with
  | Sine -> sin (2. *. Float.pi *. phase)
  | Square -> if phase < 0.5 then 1. else -1.
  (* 0 up to 1 at a quarter, down to -1 at three quarters, back to 0:
   * in step with the sine *)
  | Triangle -> if phase < 0.25 then 4. *. phase else if phase < 0.75 then 2. -. (4. *. phase) else (4. *. phase) -. 4.
  | Sawtooth -> (2. *. phase) -. 1.

let polyblep ~(dt : float) (t : float) : float =
  if t < dt then
    let u = t /. dt in
    (2. *. u) -. (u *. u) -. 1.
  else if t > 1. -. dt then
    let u = (t -. 1.) /. dt in
    (u *. u) +. (2. *. u) +. 1.
  else 0.

(* the integral of polyblep's residual for a unit step, (u+1)^3 / 6
 * before, (1-u)^3 / 6 after: 1/6 at the corner *)
let polyblamp ~(dt : float) (t : float) : float =
  if t < dt then
    let u = 1. -. (t /. dt) in
    u *. u *. u /. 6.
  else if t > 1. -. dt then
    let u = ((t -. 1.) /. dt) +. 1. in
    u *. u *. u /. 6.
  else 0.

let wrap (phase : float) : float = phase -. Float.floor phase

let wave_band_limited (w : waveform) ~(dt : float) (phase : float) : float =
  match w with
  | Sine -> wave w phase
  (* corners at 0.25 (the slope from +4 to -4 a period: -8 dt a sample)
   * and 0.75 (back up, +8 dt) *)
  | Triangle ->
      wave w phase
      -. (8. *. dt *. polyblamp ~dt (wrap (phase -. 0.25)))
      +. (8. *. dt *. polyblamp ~dt (wrap (phase -. 0.75)))
  (* up at 0, down at 0.5 *)
  | Square ->
      wave w phase +. polyblep ~dt phase -. polyblep ~dt (wrap (phase +. 0.5))
  (* down at 0 *)
  | Sawtooth -> wave w phase -. polyblep ~dt phase

let pulse ~(width : float) (phase : float) : float = (if phase < width then 1. else -1.) -. ((2. *. width) -. 1.)

(* up at 0, down at [width] *)
let pulse_band_limited ~(width : float) ~(dt : float) (phase : float) : float =
  pulse ~width phase +. polyblep ~dt phase -. polyblep ~dt (wrap (phase -. width))

type t = { waveform : waveform; frequency : float; phase : float }

let make (waveform : waveform) (frequency : float) : t = { waveform; frequency; phase = 0. }

let next ?(band_limited = false) (o : t) : float * t =
  let dt = o.frequency /. float_of_int Signal.rate in
  let phase = o.phase +. dt in
  let x = if band_limited then wave_band_limited o.waveform ~dt o.phase else wave o.waveform o.phase in
  (x, { o with phase = phase -. Float.floor phase })

let render ?band_limited (w : waveform) ~(frequency : float) (seconds : float) : Signal.t =
  let o = ref (make w frequency) in
  Array.init (Signal.samples seconds) (fun _ ->
      let (x, o') = next ?band_limited !o in
      o := o';
      x)