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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
open Bigarray
open B_utils
open Tsdl
module Time = B_time
module Theme = B_theme
type audio_spec = Sdl.audio_spec
type sound = (int, Bigarray.int16_signed_elt) Sdl.bigarray
type repeat = Repeat of int | Forever
type sound_effect = sound -> unit
type track = {
mutable soundpos : int;
soundlen : int;
mutable repeat : repeat;
sound : sound;
mutable volume : float;
effects : sound_effect list;
}
type t = {
mutable dev_id : Sdl.audio_device_id option;
devname : string option;
mutable callback : Sdl.audio_callback option;
mutable have: audio_spec;
tracks : (track option) array
}
let init () =
match Sdl.(init_sub_system Init.audio) with
| Error (`Msg e) ->
printd (debug_error + debug_io) "Cannot initialize audio. SDL error! %s" e;
None
| Ok () ->
begin
match Sdl.get_current_audio_driver () with
| None -> printd (debug_error + debug_io)
"mixer.ml: cannot find audio driver."
| Some s -> printd debug_io "Using audio driver: %s." s
end;
match Sdl.get_num_audio_drivers () with
| Error (`Msg e) ->
printd (debug_error + debug_io) "Cannot get number of audio drivers. SDL error! %s" e;
None
| Ok i -> if i < 1
then begin
printd (debug_error + debug_io) "Don't see any specific audio devices!";
None
end else Some "default"
let print_spec spec =
let open Sdl in
printd debug_io "as_freq=%d, as_format=%d, as_channels=%d, as_silence=%d \
as_samples=%d as_size = %ld"
spec.as_freq
spec.as_format
spec.as_channels
spec.as_silence
spec.as_samples
spec.as_size
let bytes_to_value_s16le b1 b2 =
let value = b1 lor (b2 lsl 8) in
if value land 32768 <> 0
then -1 - value lxor 65535
else value
let value_to_bytes_s16le value =
let value = if value < 0
then (-value) lxor 65535 + 1
else value in
value land 255, value lsr 8
let blit_or_sum first last volume chunk output =
if first && abs_float (1. -. volume) < 0.0001
then Array1.blit chunk output
else if Array1.dim chunk <> Array1.dim output
then failwith "chunk and output arrays must have same dim."
else
let clipping = ref 0 in
let d = Array1.dim chunk in
if first
then for i = 0 to d-1 do
let value = round (volume *. (float (Array1.unsafe_get chunk i))) in
Array1.unsafe_set output i value
done
else begin
for i = 0 to d-1 do
let value1 = Array1.unsafe_get chunk i in
let value2 = Array1.unsafe_get output i in
let value' = (round (volume *. (float value1)) + value2) in
let value' = if last then
if value' > 32767
then (incr clipping; 32767)
else if value' < -32768
then (incr clipping; -32768)
else value'
else value' in
Array1.unsafe_set output i value';
done;
if !clipping > 0
then printd (debug_io + debug_warning) "Sound had to be clipped %u times for saturation" !clipping
end
let callback mixer =
let no_sound = ref true in
fun output ->
let chunk_length = Array1.dim output in
let first = ref true in
let last = let rec loop i = if i=0 || mixer.tracks.(i) <> None then i
else loop (i-1) in
loop (Array.length mixer.tracks - 1) in
let filled = ref 0 in
for i = 0 to Array.length mixer.tracks - 1 do
do_option mixer.tracks.(i) (fun track ->
if track.soundpos = track.soundlen
then printd (debug_io + debug_error)
"Trying to play a finished sound. This should not happen"
else begin
printd debug_io "Track #%d playing %u/%u."
i track.soundpos track.soundlen;
let waveleft = track.soundlen - track.soundpos in
let cpy = min chunk_length waveleft in
let chunk = Array1.sub track.sound track.soundpos cpy in
List.iter (fun f -> f chunk) track.effects;
if cpy = chunk_length
then blit_or_sum !first (last = i) track.volume chunk output
else blit_or_sum !first (last = i) track.volume chunk
(Array1.sub output 0 cpy);
track.soundpos <- track.soundpos + cpy;
filled := imax !filled cpy;
first := false;
if track.soundpos = track.soundlen
then (match track.repeat with
| Repeat n ->
track.repeat <- Repeat (n-1);
if n <= 1 then begin
printd debug_io "Track #%u is available for playing." i;
mixer.tracks.(i) <- None
end
else begin
track.soundpos <- 0;
printd debug_io
"%u repeat%s remaining for sample in track #%u."
(n-1) (if n>2 then "s" else "") i
end
| Forever ->
printd debug_io "Repeat sample forever in track #%u." i)
end)
done;
if !filled <> chunk_length
then Array1.fill
(Array1.sub output !filled (chunk_length - !filled))
0 ;
if !first
then begin
if not !no_sound
then printd debug_io "No sound to play. Disabling further messages.";
no_sound := true;
end
else no_sound := false
let change_volume factor sound =
blit_or_sum true true factor sound sound
let create_mixer ?(tracks=8) ?(freq=44100) devname =
let format = Sdl.Audio.s16_lsb in
let tmp_spec = {
Sdl.as_freq = freq;
as_format = format;
as_channels = 2;
as_silence = 0;
as_callback = None;
as_samples = 4096;
as_size = 8192l;
} in
let mixer = {
dev_id = None;
have = tmp_spec;
devname;
callback = None;
tracks = Array.make tracks None
} in
match devname with
| None ->
printd (debug_io + debug_warning)
"Creating a dummy mixer: no audio available.";
mixer
| Some devname ->
let devname = if devname = "default" then None else Some devname in
let callback = Sdl.audio_callback int16_signed (callback mixer) in
mixer.callback <- Some callback;
let spec = { tmp_spec with Sdl.as_callback = Some callback } in
let () = match Sdl.open_audio_device devname false spec 0 with
| Error (`Msg e) ->
printd (debug_io + debug_error)
"Cannot open audio device. SDL error: %s" e
| Ok (dev_id, spec') ->
print_spec spec';
if spec'.Sdl.as_format <> format
then printd (debug_io + debug_error)
"Audio device doesn't support s16le format. Prepare to hear weird \
sounds.";
mixer.dev_id <- Some dev_id;
mixer.have <- spec';
in
mixer
let convert_from_32le _ b2 =
b2
let convert_from_32be b1 _ =
b1
let convert_from_s16be b =
(b lsr 8) lor ((b land 255) lsl 8)
let convert mixer spec sound =
let t = Time.now() in
let target_format = mixer.have.Sdl.as_format in
if target_format <> Sdl.Audio.s16_lsb
then
(printd debug_error "Convert sound to format %u not implemented." target_format;
sound)
else
let ba_bytes_size = 2 in
let target_bps = 2 in
let sound_format = spec.Sdl.as_format in
let target_channels = mixer.have.Sdl.as_channels in
let sound_channels = spec.Sdl.as_channels in
if sound_format = target_format && target_channels = sound_channels then sound
else begin
if target_channels <> 2
then printd (debug_error + debug_io) "Only 2 audio channels are implemented.";
let bitsize = sound_format land 255 in
let sound_bps = bitsize lsr 3 in
if sound_bps = 0 then failwith "invalid sound format";
let soundlen = Array1.dim sound in
let targetlen = (target_bps / ba_bytes_size) * target_channels * soundlen / (sound_bps / ba_bytes_size) / sound_channels in
printd debug_io "Converting sound with length %u ==> %u."
soundlen targetlen;
let target = Array1.create int16_signed c_layout targetlen in
let () =
if sound_bps = 1
then
let () = printd debug_io "Converting (u8,%u) to (s16le,%u)."
sound_channels target_channels in
for j = 0 to soundlen - 1 do
let i = 2 * target_channels * j / sound_channels in
let v = Array1.unsafe_get sound j in
Array1.unsafe_set target i (v land 255);
Array1.unsafe_set target (i+1) (v lsr 8);
done
else if sound_bps = 2
then if sound_format = Sdl.Audio.s16_msb ||
(sound_format = Sdl.Audio.s16_sys && Sys.big_endian)
then
let () = printd debug_io "Converting (s16be,%u) to (s16le,%u)."
sound_channels target_channels in
for i = 0 to targetlen - 1 do
let j = sound_channels * i / target_channels in
Array1.unsafe_set target i (convert_from_s16be
(Array1.unsafe_get sound j))
done
else
let () = printd debug_io "Converting (s16le,%u) to (s16le,%u)."
sound_channels target_channels in
for j = 0 to soundlen - 1 do
let x = Array1.unsafe_get sound j in
Array1.unsafe_set target (2*j) x;
Array1.unsafe_set target (2*j+1) x
done
else if sound_bps = 4
then let convert_sample = if sound_format land (1 lsl 12) <> 0
then convert_from_32be
else convert_from_32le in
for i = 0 to targetlen - 1 do
let j = 2 * sound_channels * i / target_channels in
let v = convert_sample
(Array1.unsafe_get sound j)
(Array1.unsafe_get sound (j+1)) in
Array1.unsafe_set target i v;
done
else printd (debug_io + debug_error)
"Conversion from format %u not supported." sound_format in
printd debug_io "Sound converted in %u msec." (Time.now() - t);
target
end
let rec gcd a b =
if b = 0 then a else gcd b (a mod b)
let interpolate e1 e2 data1 data2 =
let ch = 2 in
if Array1.dim data1 < ch
then failwith "invalid format"
else begin
let x = Array.init e2 (fun i -> (float ((e1 * i) mod e2) /. float e2)) in
let value1left = ref (Array1.unsafe_get data1 0) in
let value2left = ref 0 in
let value1right = ref (Array1.unsafe_get data1 1) in
let value2right = ref 0 in
let ii = ref 0 in
for i = 0 to (Array1.dim data2)/ch - 1 do
let pos = (e1 * i) / e2 in
let rest = Array.unsafe_get x !ii in
let j = (pos + 1) * ch and jj = i * ch in
value2left := Array1.unsafe_get data1 j;
let value = if rest = 0.
then !value1left
else round (float !value1left +. rest *. (float (!value2left - !value1left))) in
Array1.unsafe_set data2 jj value;
value1left := !value2left;
value2right := Array1.unsafe_get data1 (j + 1);
let value = if rest = 0.
then !value1right
else round (float !value1right +. rest *. (float (!value2right - !value1right))) in
Array1.unsafe_set data2 (jj+1) value;
value1right := !value2right;
incr ii; if !ii = e2 then ii:=0;
done
end;;
let stretch f1 f2 sound =
let t = Time.now() in
let ch = 2 in
let f1, f2 = if f1 < f2 then f1, f2 else f2, f1 in
let d = gcd f1 f2 in
let e1, e2 = f1/d, f2/d in
let l1 = Array1.dim sound / ch in
let l2 = if l1 mod e1 = 0 then l1 * e2 / e1 else e2 * (l1 / e1 + 1) in
printd debug_io "Stretching %u => %u (%u => %u), size=%u" f1 f2 e1 e2 (ch * l2);
let output = Array1.create int16_signed c_layout (ch * l2) in
Array1.fill output 0;
printd debug_io "Interpolate e1=%u e2=%u size1=%u size2=%u."
e1 e2 (ch * (e1+1)) (ch * e2);
interpolate e1 e2
(Array1.sub sound 0 ((l1/e1 - 2) * ch * e1 + ch))
(Array1.sub output 0 ((l1/e1 - 2) * ch * e2));
let finalpos = ch * (l1/e1 - 1) * e1 in
interpolate e1 e2
(Array1.sub sound finalpos (imin (ch * e1) (Array1.dim sound - finalpos)))
(Array1.sub output (ch * (l1/e1 - 1) * e2) (Array1.dim sound - finalpos - ch));
printd debug_io "Sound was stretched in %u msec." (Time.now() -t);
output
let load_chunk mixer filename =
let open Sdl in
let filename = Theme.get_path filename in
let file = go (rw_from_file filename "rb") in
let audio_spec, sound =
let rec loop i =
if i = 0
then (printd (debug_io + debug_error) "Cannot load WAV file.";
let data = Array1.create int16_signed c_layout 1024 in
Array1.fill data 0;
mixer.have, data)
else
let spec, data = go (load_wav_rw file mixer.have int16_signed) in
if spec.as_channels = 0
then begin
printd (debug_io + debug_error) "WAV file corrupt. We try again.";
Time.delay 100;
loop (i-1)
end else spec,data in
loop 3 in
go (rw_close file);
printd debug_io "Loading WAV file %s." filename;
print_spec audio_spec;
let chunk =
if audio_spec.as_format <> mixer.have.as_format
|| audio_spec.as_channels <> mixer.have.as_channels
then begin
printd (debug_io + debug_warning)
"WAV chunk has different format (%u,%u) than the mixer (%u,%u). Will try \
to convert." audio_spec.as_format audio_spec.as_channels
mixer.have.as_format mixer.have.as_channels;
convert mixer audio_spec sound
end
else sound in
let chunk =
if audio_spec.as_freq <> mixer.have.as_freq
then begin
printd (debug_io + debug_warning)
"WAV chunk has different freq (%u) than the mixer (%u). We try to \
interpolate." audio_spec.as_freq mixer.have.as_freq;
stretch audio_spec.as_freq mixer.have.as_freq chunk
end
else chunk in
chunk
let play_chunk ?track ?(effects=[]) ?(volume=1.) ?(repeat = Repeat 1)
mixer sound =
let tracks = Array.length mixer.tracks in
let track = match track with
| None -> let rec loop i =
if i = tracks then None
else if mixer.tracks.(i) = None then Some i
else loop (i+1) in
loop 0
| Some i -> if 0 <= i && i < tracks && mixer.tracks.(i) = None
then Some i else None
in
let () = match track with
| None ->
printd (debug_io + debug_error) "No available track for playing chunk.";
| Some i -> begin
let soundlen = Array1.dim sound in
printd debug_io "Playing sound of length %u on track #%u." soundlen i;
let track = {
soundpos = 0;
soundlen;
repeat;
sound;
volume;
effects
} in
mixer.tracks.(i) <- Some track;
end in
track
let stop_track mixer i =
mixer.tracks.(i) <- None
let pause mixer =
do_option mixer.dev_id (fun d ->
printd debug_io "Pause mixer";
Sdl.pause_audio_device d true)
let unpause mixer =
do_option mixer.dev_id (fun d ->
printd debug_io "Unpause mixer";
Sdl.pause_audio_device d false)
let close mixer =
printd debug_io "Closing mixer";
do_option mixer.dev_id (fun id ->
Time.delay 50;
Sdl.close_audio_device id);
mixer.dev_id <- None;
mixer.callback <- None;
for i = 0 to Array.length mixer.tracks - 1 do
mixer.tracks.(i) <- None
done
let test () =
print_endline "MIXER TEST";
Gc.compact ();
let devname = init () in
let mixer = create_mixer devname in
let chunk2 = load_chunk mixer "%assets/audio/sia.wav" in
let chunk3 = load_chunk mixer "%assets/audio/sample.wav" in
let chunk1 = load_chunk mixer "%assets/audio/chunk.wav" in
Gc.compact ();
unpause mixer;
print_endline "Playing music...";
let sia = play_chunk ~volume:0.8 mixer chunk2 in
Sdl.delay 1000l;
print_endline "Adding repeated sound on top...";
let _ = play_chunk ~repeat:(Repeat 5) mixer chunk1 in
Sdl.delay 5000l;
print_endline "Switching to another tune...";
do_option sia (stop_track mixer);
let _ = play_chunk ~volume:0.5 mixer chunk3 in
Sdl.delay 1000l;
print_endline "Pausing...";
pause mixer;
Sdl.delay 1000l;
print_endline "Resuming...";
unpause mixer;
Sdl.delay 3000l;
close mixer;
print_endline "Done!";
Sdl.quit()