• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mbarbin / fingerboard / 116

23 Dec 2025 04:57PM UTC coverage: 95.087% (-0.5%) from 95.592%
116

push

github

mbarbin
Use Code_error.raise

40 of 84 new or added lines in 8 files covered. (47.62%)

77 existing lines in 10 files now uncovered.

3716 of 3908 relevant lines covered (95.09%)

18681.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.19
/test/test__system.ml
1
(**********************************************************************************)
2
(*  Fingerboard - a microtonal geography of the cello fingerboard                 *)
3
(*  Copyright (C) 2022-2024 Mathieu Barbin <mathieu.barbin@gmail.com>             *)
4
(*                                                                                *)
5
(*  This file is part of Fingerboard.                                             *)
6
(*                                                                                *)
7
(*  Fingerboard is free software: you can redistribute it and/or modify it under  *)
8
(*  the terms of the GNU Affero General Public License as published by the Free   *)
9
(*  Software Foundation, either version 3 of the License, or any later version.   *)
10
(*                                                                                *)
11
(*  Fingerboard is distributed in the hope that it will be useful, but WITHOUT    *)
12
(*  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or         *)
13
(*  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License   *)
14
(*  for more details.                                                             *)
15
(*                                                                                *)
16
(*  You should have received a copy of the GNU Affero General Public License      *)
17
(*  along with Fingerboard. If not, see <https://www.gnu.org/licenses/>.          *)
18
(**********************************************************************************)
19

20
let test_pitch_exn ~system ~(intervals_going_down : Characterized_interval.t array) =
21
  (* We test that the pitch is the same for the open string and the position on
22
     the next vibrating string at the position of the interval between the two
23
     vibrating strings. *)
24
  for i = 1 to Array.length intervals_going_down do
5✔
25
    let high = Roman_numeral.of_int_exn i in
16✔
26
    let low = Roman_numeral.succ_exn high in
16✔
27
    let open_string =
16✔
28
      System.pitch
29
        system
30
        { fingerboard_position = Fingerboard_position.open_string; string_number = high }
31
    in
32
    let unison =
16✔
33
      System.pitch
34
        system
35
        { fingerboard_position =
36
            Fingerboard_position.create_exn
37
              ~name:"unison"
38
              ~acoustic_interval_to_the_open_string:
39
                intervals_going_down.(i - 1).acoustic_interval
40
        ; string_number = low
41
        }
42
    in
43
    if not (Frequency.equal open_string unison)
16✔
44
    then
45
      Code_error.raise
×
46
        "Unexpected pitch calculation."
UNCOV
47
        [ "high", high |> Roman_numeral.to_dyn
×
UNCOV
48
        ; "low", low |> Roman_numeral.to_dyn
×
UNCOV
49
        ; "open_string", open_string |> Frequency.to_dyn
×
UNCOV
50
        ; "unison", unison |> Frequency.to_dyn
×
51
        ]
52
  done
53
;;
54

55
let%expect_test "4-strings cello" =
56
  let a = { Note.letter_name = A; symbol = Natural; octave_designation = 3 } in
1✔
57
  let pitch = Frequency.a4_440 |> Acoustic_interval.shift_down Acoustic_interval.octave in
58
  let intervals_going_down =
1✔
59
    let fifth = { Interval.number = Fifth; quality = Perfect; additional_octaves = 0 } in
60
    Array.create
1✔
61
      ~len:3
62
      (Characterized_interval.create_exn
63
         ~interval:fifth
64
         ~acoustic_interval:(Acoustic_interval.pythagorean fifth))
1✔
65
  in
66
  let system = System.create ~high_vibrating_string:a ~pitch ~intervals_going_down in
67
  test_pitch_exn ~system ~intervals_going_down;
68
  print_dyn (system |> System.to_dyn);
1✔
69
  [%expect
1✔
70
    {|
71
    { vibrating_strings =
72
        [| { open_string =
73
               { letter_name = A; symbol = Natural; octave_designation = 3 }
74
           ; pitch = 220.
75
           ; roman_numeral = I
76
           }
77
        ;  { open_string =
78
               { letter_name = D; symbol = Natural; octave_designation = 3 }
79
           ; pitch = 146.666666667
80
           ; roman_numeral = II
81
           }
82
        ;  { open_string =
83
               { letter_name = G; symbol = Natural; octave_designation = 2 }
84
           ; pitch = 97.7777777778
85
           ; roman_numeral = III
86
           }
87
        ;  { open_string =
88
               { letter_name = C; symbol = Natural; octave_designation = 2 }
89
           ; pitch = 65.1851851852
90
           ; roman_numeral = IV
91
           }
92
        |]
93
    ; intervals_going_down =
94
        [| { interval =
95
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
96
           ; acoustic_interval =
97
               Reduced_natural_ratio
98
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
99
           }
100
        ;  { interval =
101
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
102
           ; acoustic_interval =
103
               Reduced_natural_ratio
104
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
105
           }
106
        ;  { interval =
107
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
108
           ; acoustic_interval =
109
               Reduced_natural_ratio
110
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
111
           }
112
        |]
113
    }
114
    |}];
1✔
115
  (* Creating a few positions and check the intervals between them. *)
116
  let fourth =
117
    Fingerboard_position.create_exn
118
      ~name:"4th"
119
      ~acoustic_interval_to_the_open_string:
120
        (Acoustic_interval.pythagorean
1✔
121
           { number = Fourth; quality = Perfect; additional_octaves = 0 })
122
  in
123
  let just_minor_ton =
124
    Fingerboard_position.create_exn
125
      ~name:"2MZ"
126
      ~acoustic_interval_to_the_open_string:Acoustic_interval.just_minor_ton
127
  in
128
  let pythagorean_minor_third =
129
    Fingerboard_position.create_exn
130
      ~name:"2mP"
131
      ~acoustic_interval_to_the_open_string:
132
        (Acoustic_interval.pythagorean
1✔
133
           { number = Third; quality = Minor; additional_octaves = 0 })
134
  in
135
  let i =
136
    System.acoustic_interval
137
      system
138
      ~from:{ fingerboard_position = fourth; string_number = III }
139
      ~to_:{ fingerboard_position = pythagorean_minor_third; string_number = I }
140
    |> Option.value_exn ~here:[%here]
1✔
141
  in
142
  assert (Acoustic_interval.equal i Acoustic_interval.octave);
1✔
143
  print_string (Acoustic_interval.to_string i);
1✔
144
  [%expect {| 2 |}];
1✔
145
  let i =
146
    System.acoustic_interval
147
      system
148
      ~from:{ fingerboard_position = fourth; string_number = III }
149
      ~to_:{ fingerboard_position = just_minor_ton; string_number = II }
150
    |> Option.value_exn ~here:[%here]
1✔
151
  in
152
  assert (Acoustic_interval.equal i Acoustic_interval.just_major_third);
1✔
153
  print_string (Acoustic_interval.to_string i);
1✔
154
  [%expect {| 5 / 2^2 |}];
1✔
155
  let i =
156
    System.acoustic_interval
157
      system
158
      ~from:{ fingerboard_position = fourth; string_number = III }
159
      ~to_:{ fingerboard_position = pythagorean_minor_third; string_number = II }
160
    |> Option.value_exn ~here:[%here]
1✔
161
  in
162
  assert (
1✔
163
    Acoustic_interval.equal
1✔
164
      i
165
      (Acoustic_interval.pythagorean
1✔
166
         { number = Fourth; quality = Perfect; additional_octaves = 0 }));
167
  print_string (Acoustic_interval.to_string i);
1✔
168
  [%expect {| 2^2 / 3 |}];
1✔
169
  let i =
170
    System.acoustic_interval
171
      system
172
      ~from:
173
        { fingerboard_position = Fingerboard_position.open_string; string_number = II }
174
      ~to_:{ fingerboard_position = just_minor_ton; string_number = I }
175
    |> Option.value_exn ~here:[%here]
1✔
176
  in
177
  assert (Acoustic_interval.equal i Acoustic_interval.just_major_sixth);
1✔
178
  print_string (Acoustic_interval.to_string i);
1✔
179
  [%expect {| 5 / 3 |}];
1✔
180
  let i =
181
    System.acoustic_interval
182
      system
183
      ~from:{ fingerboard_position = just_minor_ton; string_number = I }
184
      ~to_:{ fingerboard_position = pythagorean_minor_third; string_number = I }
185
    |> Option.value_exn ~here:[%here]
1✔
186
  in
187
  assert (Acoustic_interval.equal i Acoustic_interval.just_diatonic_semiton);
1✔
188
  print_string (Acoustic_interval.to_string i);
1✔
189
  [%expect {| 2^4 / (3 * 5) |}];
1✔
190
  ()
191
;;
192

193
let%expect_test "piccolo cello" =
194
  let e = { Note.letter_name = E; symbol = Natural; octave_designation = 4 } in
1✔
195
  let pitch =
196
    Frequency.a4_440
197
    |> Acoustic_interval.shift_down
198
         (Acoustic_interval.pythagorean
1✔
199
            { number = Fourth; quality = Perfect; additional_octaves = 0 })
200
  in
201
  let intervals_going_down =
1✔
202
    let fifth = { Interval.number = Fifth; quality = Perfect; additional_octaves = 0 } in
203
    Array.create
1✔
204
      ~len:4
205
      (Characterized_interval.create_exn
206
         ~interval:fifth
207
         ~acoustic_interval:(Acoustic_interval.pythagorean fifth))
1✔
208
  in
209
  let system = System.create ~high_vibrating_string:e ~pitch ~intervals_going_down in
210
  test_pitch_exn ~system ~intervals_going_down;
211
  print_dyn (system |> System.to_dyn);
1✔
212
  [%expect
1✔
213
    {|
214
    { vibrating_strings =
215
        [| { open_string =
216
               { letter_name = E; symbol = Natural; octave_designation = 4 }
217
           ; pitch = 330.
218
           ; roman_numeral = I
219
           }
220
        ;  { open_string =
221
               { letter_name = A; symbol = Natural; octave_designation = 3 }
222
           ; pitch = 220.
223
           ; roman_numeral = II
224
           }
225
        ;  { open_string =
226
               { letter_name = D; symbol = Natural; octave_designation = 3 }
227
           ; pitch = 146.666666667
228
           ; roman_numeral = III
229
           }
230
        ;  { open_string =
231
               { letter_name = G; symbol = Natural; octave_designation = 2 }
232
           ; pitch = 97.7777777778
233
           ; roman_numeral = IV
234
           }
235
        ;  { open_string =
236
               { letter_name = C; symbol = Natural; octave_designation = 2 }
237
           ; pitch = 65.1851851852
238
           ; roman_numeral = V
239
           }
240
        |]
241
    ; intervals_going_down =
242
        [| { interval =
243
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
244
           ; acoustic_interval =
245
               Reduced_natural_ratio
246
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
247
           }
248
        ;  { interval =
249
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
250
           ; acoustic_interval =
251
               Reduced_natural_ratio
252
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
253
           }
254
        ;  { interval =
255
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
256
           ; acoustic_interval =
257
               Reduced_natural_ratio
258
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
259
           }
260
        ;  { interval =
261
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
262
           ; acoustic_interval =
263
               Reduced_natural_ratio
264
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
265
           }
266
        |]
267
    }
268
    |}];
1✔
269
  ()
270
;;
271

272
let%expect_test "5th Bach's suite for cello" =
273
  let g = { Note.letter_name = G; symbol = Natural; octave_designation = 3 } in
1✔
274
  let pitch =
275
    Frequency.a4_440
276
    |> Acoustic_interval.shift_down
277
         (Acoustic_interval.pythagorean
1✔
278
            { number = Second; quality = Major; additional_octaves = 1 })
279
  in
280
  let intervals_going_down =
1✔
281
    let fourth =
282
      { Interval.number = Fourth; quality = Perfect; additional_octaves = 0 }
283
    in
284
    let fifth = { Interval.number = Fifth; quality = Perfect; additional_octaves = 0 } in
285
    Array.concat
1✔
286
      [ [| Characterized_interval.create_exn
287
             ~interval:fourth
288
             ~acoustic_interval:(Acoustic_interval.pythagorean fourth)
1✔
289
        |]
290
      ; Array.create
1✔
291
          ~len:2
292
          (Characterized_interval.create_exn
293
             ~interval:fifth
294
             ~acoustic_interval:(Acoustic_interval.pythagorean fifth))
1✔
295
      ]
296
  in
297
  let system = System.create ~high_vibrating_string:g ~pitch ~intervals_going_down in
298
  test_pitch_exn ~system ~intervals_going_down;
299
  print_dyn (system |> System.to_dyn);
1✔
300
  [%expect
1✔
301
    {|
302
    { vibrating_strings =
303
        [| { open_string =
304
               { letter_name = G; symbol = Natural; octave_designation = 3 }
305
           ; pitch = 195.555555556
306
           ; roman_numeral = I
307
           }
308
        ;  { open_string =
309
               { letter_name = D; symbol = Natural; octave_designation = 3 }
310
           ; pitch = 146.666666667
311
           ; roman_numeral = II
312
           }
313
        ;  { open_string =
314
               { letter_name = G; symbol = Natural; octave_designation = 2 }
315
           ; pitch = 97.7777777778
316
           ; roman_numeral = III
317
           }
318
        ;  { open_string =
319
               { letter_name = C; symbol = Natural; octave_designation = 2 }
320
           ; pitch = 65.1851851852
321
           ; roman_numeral = IV
322
           }
323
        |]
324
    ; intervals_going_down =
325
        [| { interval =
326
               { number = Fourth; quality = Perfect; additional_octaves = 0 }
327
           ; acoustic_interval =
328
               Reduced_natural_ratio
329
                 [ { prime = 2; exponent = 2 }; { prime = 3; exponent = -1 } ]
330
           }
331
        ;  { interval =
332
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
333
           ; acoustic_interval =
334
               Reduced_natural_ratio
335
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
336
           }
337
        ;  { interval =
338
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
339
           ; acoustic_interval =
340
               Reduced_natural_ratio
341
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
342
           }
343
        |]
344
    }
345
    |}];
1✔
346
  ()
347
;;
348

349
let%expect_test "Kodaly sonata for cello solo" =
350
  let a = { Note.letter_name = A; symbol = Natural; octave_designation = 3 } in
1✔
351
  let pitch = Frequency.a4_440 |> Acoustic_interval.shift_down Acoustic_interval.octave in
352
  let intervals_going_down =
1✔
353
    let fifth = { Interval.number = Fifth; quality = Perfect; additional_octaves = 0 } in
354
    [| Characterized_interval.create_exn
355
         ~interval:fifth
356
         ~acoustic_interval:(Acoustic_interval.pythagorean fifth)
1✔
357
     ; Characterized_interval.create_exn
358
         ~interval:{ Interval.number = Sixth; quality = Minor; additional_octaves = 0 }
359
         ~acoustic_interval:Acoustic_interval.just_minor_sixth
360
     ; Characterized_interval.create_exn
361
         ~interval:fifth
362
         ~acoustic_interval:(Acoustic_interval.pythagorean fifth)
1✔
363
    |]
364
  in
365
  let system = System.create ~high_vibrating_string:a ~pitch ~intervals_going_down in
366
  test_pitch_exn ~system ~intervals_going_down;
367
  print_dyn (system |> System.to_dyn);
1✔
368
  [%expect
1✔
369
    {|
370
    { vibrating_strings =
371
        [| { open_string =
372
               { letter_name = A; symbol = Natural; octave_designation = 3 }
373
           ; pitch = 220.
374
           ; roman_numeral = I
375
           }
376
        ;  { open_string =
377
               { letter_name = D; symbol = Natural; octave_designation = 3 }
378
           ; pitch = 146.666666667
379
           ; roman_numeral = II
380
           }
381
        ;  { open_string =
382
               { letter_name = F; symbol = Sharp; octave_designation = 2 }
383
           ; pitch = 91.6666666667
384
           ; roman_numeral = III
385
           }
386
        ;  { open_string =
387
               { letter_name = B; symbol = Natural; octave_designation = 1 }
388
           ; pitch = 61.1111111111
389
           ; roman_numeral = IV
390
           }
391
        |]
392
    ; intervals_going_down =
393
        [| { interval =
394
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
395
           ; acoustic_interval =
396
               Reduced_natural_ratio
397
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
398
           }
399
        ;  { interval =
400
               { number = Sixth; quality = Minor; additional_octaves = 0 }
401
           ; acoustic_interval =
402
               Reduced_natural_ratio
403
                 [ { prime = 2; exponent = 3 }; { prime = 5; exponent = -1 } ]
404
           }
405
        ;  { interval =
406
               { number = Fifth; quality = Perfect; additional_octaves = 0 }
407
           ; acoustic_interval =
408
               Reduced_natural_ratio
409
                 [ { prime = 2; exponent = -1 }; { prime = 3; exponent = 1 } ]
410
           }
411
        |]
412
    }
413
    |}];
1✔
414
  ()
415
;;
416

417
let%expect_test "reset-pitch" =
418
  let a = { Note.letter_name = A; symbol = Natural; octave_designation = 3 } in
1✔
419
  let pitch = Frequency.a4_440 |> Acoustic_interval.shift_down Acoustic_interval.octave in
420
  let intervals_going_down =
1✔
421
    let fifth = { Interval.number = Fifth; quality = Perfect; additional_octaves = 0 } in
422
    Array.create
1✔
423
      ~len:3
424
      (Characterized_interval.create_exn
425
         ~interval:fifth
426
         ~acoustic_interval:(Acoustic_interval.pythagorean fifth))
1✔
427
  in
428
  let system = System.create ~high_vibrating_string:a ~pitch ~intervals_going_down in
429
  test_pitch_exn ~system ~intervals_going_down;
430
  let system1 = Dyn.to_string (system |> System.to_dyn) in
1✔
431
  let change ~f =
1✔
432
    f ();
3✔
433
    let system2 = Dyn.to_string (system |> System.to_dyn) in
3✔
434
    Expect_test_patdiff.print_patdiff system1 system2 ~context:3
3✔
435
  in
436
  change ~f:(fun () -> System.reset_pitch system (Roman_numeral.of_int_exn 1) ~pitch);
1✔
437
  [%expect {| |}];
1✔
438
  change ~f:(fun () ->
439
    System.reset_pitch
1✔
440
      system
441
      (Roman_numeral.of_int_exn 1)
1✔
442
      ~pitch:
443
        (Frequency.of_float_exn 442.
444
         |> Acoustic_interval.shift_down Acoustic_interval.octave));
1✔
445
  [%expect
446
    {|
447
    -1,22 +1,22
448
      { vibrating_strings =
449
          [| { open_string =
450
                 { letter_name = A; symbol = Natural; octave_designation = 3 }
451
    -|       ; pitch = 220.
452
    +|       ; pitch = 221.
453
             ; roman_numeral = I
454
             }
455
          ;  { open_string =
456
                 { letter_name = D; symbol = Natural; octave_designation = 3 }
457
    -|       ; pitch = 146.666666667
458
    +|       ; pitch = 147.333333333
459
             ; roman_numeral = II
460
             }
461
          ;  { open_string =
462
                 { letter_name = G; symbol = Natural; octave_designation = 2 }
463
    -|       ; pitch = 97.7777777778
464
    +|       ; pitch = 98.2222222222
465
             ; roman_numeral = III
466
             }
467
          ;  { open_string =
468
                 { letter_name = C; symbol = Natural; octave_designation = 2 }
469
    -|       ; pitch = 65.1851851852
470
    +|       ; pitch = 65.4814814815
471
             ; roman_numeral = IV
472
             }
473
          |]
474
    |}];
1✔
475
  change ~f:(fun () ->
476
    System.reset_pitch
1✔
477
      system
478
      (Roman_numeral.of_int_exn 2)
1✔
479
      ~pitch:(Frequency.of_float_exn 147.));
1✔
480
  [%expect
481
    {|
482
    -1,22 +1,22
483
      { vibrating_strings =
484
          [| { open_string =
485
                 { letter_name = A; symbol = Natural; octave_designation = 3 }
486
    -|       ; pitch = 220.
487
    +|       ; pitch = 220.5
488
             ; roman_numeral = I
489
             }
490
          ;  { open_string =
491
                 { letter_name = D; symbol = Natural; octave_designation = 3 }
492
    -|       ; pitch = 146.666666667
493
    +|       ; pitch = 147.
494
             ; roman_numeral = II
495
             }
496
          ;  { open_string =
497
                 { letter_name = G; symbol = Natural; octave_designation = 2 }
498
    -|       ; pitch = 97.7777777778
499
    +|       ; pitch = 98.
500
             ; roman_numeral = III
501
             }
502
          ;  { open_string =
503
                 { letter_name = C; symbol = Natural; octave_designation = 2 }
504
    -|       ; pitch = 65.1851851852
505
    +|       ; pitch = 65.3333333333
506
             ; roman_numeral = IV
507
             }
508
          |]
509
    |}];
1✔
510
  ()
511
;;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc