Skip to content

Commit fabbde3

Browse files
committed
Rough support for midi over serial
1 parent 06deea4 commit fabbde3

14 files changed

+343
-39
lines changed

examples/dune

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,9 @@
8181
(modules midi_cat)
8282
(package llama)
8383
(libraries llama))
84+
85+
(executable
86+
(public_name midi_serial)
87+
(modules midi_serial)
88+
(package llama_interactive)
89+
(libraries llama_interactive))

examples/midi_interactive.ml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,15 @@ module Args = struct
120120
type t = {
121121
list_midi_ports : bool;
122122
echo_effect : bool;
123-
print_events : bool;
123+
print_messages : bool;
124124
midi_port : int;
125125
sample_paths : string list;
126126
}
127127

128128
let parse () =
129129
let list_midi_ports = ref false in
130130
let echo_effect = ref false in
131-
let print_events = ref false in
131+
let print_messages = ref false in
132132
let midi_port = ref 0 in
133133
let sample_paths = ref [] in
134134
Arg.parse
@@ -137,8 +137,8 @@ module Args = struct
137137
Arg.Set list_midi_ports,
138138
"List midi ports by index and exit" );
139139
("--echo-effect", Arg.Set echo_effect, "Use echo effect");
140-
( "--print-events",
141-
Arg.Set print_events,
140+
( "--print-messages",
141+
Arg.Set print_messages,
142142
"Print each midi event to stdout" );
143143
( "--midi-port",
144144
Arg.Set_int midi_port,
@@ -149,7 +149,7 @@ module Args = struct
149149
{
150150
list_midi_ports = !list_midi_ports;
151151
echo_effect = !echo_effect;
152-
print_events = !print_events;
152+
print_messages = !print_messages;
153153
midi_port = !midi_port;
154154
sample_paths = List.rev !sample_paths;
155155
}
@@ -159,7 +159,7 @@ let () =
159159
let {
160160
Args.list_midi_ports;
161161
echo_effect;
162-
print_events;
162+
print_messages;
163163
midi_port;
164164
sample_paths;
165165
} =
@@ -176,22 +176,22 @@ let () =
176176
~f:(fun i name -> Printf.printf "%d: %s\n" i name)
177177
midi_port_names
178178
else
179-
let midi_events =
180-
Llama.Midi.live_midi_signal midi_input midi_port |> Result.get_ok
179+
let midi_messages =
180+
Llama.Midi.live_midi_signal_messages midi_input midi_port |> Result.get_ok
181181
in
182-
let midi_events =
183-
if print_events then
184-
Signal.debug midi_events
182+
let midi_messages =
183+
if print_messages then
184+
Signal.debug midi_messages
185185
~f:
186-
(List.iter ~f:(fun event ->
187-
print_endline (Midi.Event.to_string event)))
188-
else midi_events
186+
(List.iter ~f:(fun message ->
187+
print_endline (Midi.Message.to_string message)))
188+
else midi_messages
189189
in
190190
let main_sequencer =
191-
Llama.Midi.Midi_sequencer.signal midi_events ~channel:0 ~polyphony:12
191+
Llama.Midi.Midi_sequencer.signal midi_messages ~channel:0 ~polyphony:12
192192
in
193193
let pad_gates =
194-
Llama.Midi.Midi_sequencer.key_gates ~channel:9 midi_events
194+
Llama.Midi.Midi_sequencer.key_gates ~channel:9 midi_messages
195195
in
196196
with_window ~background_rgba_01:(0.0, 0.05, 0.0, 1.0) (fun window ->
197197
let signal =

examples/midi_serial.ml

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
open StdLabels
2+
open Llama_interactive
3+
open Dsl
4+
5+
let make_voice frequency_hz gate =
6+
let detune_factor = const 1.005 in
7+
let oscillator_frequency_hz = frequency_hz in
8+
let waveform = const Saw in
9+
let osc =
10+
mean
11+
[
12+
oscillator waveform oscillator_frequency_hz;
13+
oscillator waveform (oscillator_frequency_hz *.. detune_factor);
14+
oscillator waveform (oscillator_frequency_hz /.. detune_factor);
15+
]
16+
in
17+
let attack_s = const 0.05 in
18+
let release_s = const 0.01 in
19+
let amp_env = ar_linear ~gate ~attack_s ~release_s in
20+
lazy_amplifier osc ~volume:amp_env
21+
22+
let signal ~main_sequencer ~secondary_sequencer =
23+
let {
24+
Midi.Midi_sequencer.voices;
25+
pitch_wheel_multiplier;
26+
controller_table = _;
27+
} =
28+
main_sequencer
29+
in
30+
let { Midi.Midi_sequencer.controller_table; _ } = secondary_sequencer in
31+
let voice =
32+
List.map voices
33+
~f:(fun { Midi.Midi_sequencer.frequency_hz; gate; velocity = _ } ->
34+
make_voice (frequency_hz *.. pitch_wheel_multiplier) gate)
35+
|> sum
36+
in
37+
let filter_cutoff =
38+
sum
39+
[
40+
const 100.0;
41+
Midi.Controller_table.get_raw controller_table 21 |> scale 2000.0;
42+
]
43+
in
44+
chebyshev_low_pass_filter voice ~cutoff_hz:filter_cutoff
45+
~resonance:(const 1.0)
46+
47+
module Args = struct
48+
type t = { list_midi_ports : bool; print_messages : bool; midi_port : int }
49+
50+
let parse () =
51+
let list_midi_ports = ref false in
52+
let print_messages = ref false in
53+
let midi_port = ref 0 in
54+
Arg.parse
55+
[
56+
( "--list-midi-ports",
57+
Arg.Set list_midi_ports,
58+
"List midi ports by index and exit" );
59+
( "--print-messages",
60+
Arg.Set print_messages,
61+
"Print each midi event to stdout" );
62+
( "--midi-port",
63+
Arg.Set_int midi_port,
64+
"Use the midi port with this index" );
65+
]
66+
(fun _ -> failwith "unexpected anonymous argument")
67+
"Play music with a midi keyboard";
68+
{
69+
list_midi_ports = !list_midi_ports;
70+
print_messages = !print_messages;
71+
midi_port = !midi_port;
72+
}
73+
end
74+
75+
let () =
76+
let { Args.list_midi_ports; print_messages; midi_port } = Args.parse () in
77+
let midi_input = Llama.Midi.Midi_input.create () in
78+
if list_midi_ports then
79+
let midi_port_names = Llama.Midi.Midi_input.port_names midi_input in
80+
List.iteri
81+
~f:(fun i name -> Printf.printf "%d: %s\n" i name)
82+
midi_port_names
83+
else
84+
let midi_messages =
85+
Llama.Midi.live_midi_signal_messages midi_input midi_port |> Result.get_ok
86+
in
87+
let midi_messages_control =
88+
Llama.Midi.live_midi_messages_serial ~port:"/dev/ttyUSB0" ~baud:115200
89+
in
90+
let midi_messages_control =
91+
if print_messages then
92+
Signal.debug midi_messages_control
93+
~f:
94+
(List.iter ~f:(fun message ->
95+
print_endline (Midi.Message.to_string message)))
96+
else midi_messages_control
97+
in
98+
let main_sequencer =
99+
Llama.Midi.Midi_sequencer.signal midi_messages ~channel:0 ~polyphony:12
100+
in
101+
let secondary_sequencer =
102+
Llama.Midi.Midi_sequencer.signal midi_messages_control ~channel:0
103+
~polyphony:1
104+
in
105+
with_window ~background_rgba_01:(0.0, 0.05, 0.0, 1.0) (fun window ->
106+
let signal = signal ~main_sequencer ~secondary_sequencer in
107+
let viz'd_signal =
108+
visualize ~stable:true ~stride:1 ~pixel_scale:1 ~sample_scale:0.4
109+
~sample_to_rgba_01:(Fun.const (0.0, 0.5, 0.0, 1.0))
110+
window signal
111+
in
112+
play_signal ~scale_output_volume:0.5 viz'd_signal)

src/core/midi.ml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@ module Controller_table = struct
55
type t = float Signal.t array
66

77
let num_controllers = 128
8-
9-
let create () =
10-
let refs = Array.init num_controllers ~f:(fun _ -> ref 0.0) in
11-
let t = Array.map refs ~f:Signal.of_ref in
12-
(t, refs)
13-
8+
let create_refs () = Array.init num_controllers ~f:(fun _ -> ref 0.0)
149
let get_raw = Array.get
1510
let modulation t = get_raw t 1
1611
let volume t = get_raw t 7
@@ -46,14 +41,14 @@ module Midi_sequencer = struct
4641

4742
type voice_state = { note : int; gate : bool; velocity : int }
4843

49-
let key_gates ~channel (track_signal : Event.t list Signal.t) =
44+
let key_gates ~channel (messages : Message.t list Signal.t) =
5045
let ref_array = Array.init num_notes ~f:(fun _ -> ref false) in
5146
let update_signal =
5247
Signal.of_raw (fun ctx ->
5348
let voice_messages =
54-
Signal.sample track_signal ctx
55-
|> List.filter_map ~f:(fun (event : Event.t) ->
56-
match event.message with
49+
Signal.sample messages ctx
50+
|> List.filter_map ~f:(fun (message : Message.t) ->
51+
match message with
5752
| Message.Channel_voice_message voice_message ->
5853
if voice_message.channel == channel then
5954
Some voice_message.message
@@ -71,7 +66,7 @@ module Midi_sequencer = struct
7166
Signal.map update_signal ~f:(fun _ -> !(Array.get ref_array i))
7267
|> Signal.gate)
7368

74-
let signal ~channel ~polyphony (track_signal : Event.t list Signal.t) =
69+
let signal ~channel ~polyphony (messages : Message.t list Signal.t) =
7570
let voices =
7671
Array.init polyphony
7772
~f:(Fun.const { note = 0; gate = false; velocity = 0 })
@@ -87,14 +82,14 @@ module Midi_sequencer = struct
8782
let currently_playing_voice_index_by_note =
8883
Array.init num_notes ~f:(Fun.const None)
8984
in
90-
let controller_table, controller_refs = Controller_table.create () in
85+
let controller_refs = Controller_table.create_refs () in
9186
let pitch_wheel_multiplier = ref 1.0 in
9287
let signal_to_update_state =
9388
Signal.of_raw (fun ctx ->
9489
let voice_messages =
95-
Signal.sample track_signal ctx
96-
|> List.filter_map ~f:(fun (event : Event.t) ->
97-
match event.message with
90+
Signal.sample messages ctx
91+
|> List.filter_map ~f:(fun (message : Message.t) ->
92+
match message with
9893
| Message.Channel_voice_message voice_message ->
9994
if voice_message.channel == channel then
10095
Some voice_message.message
@@ -172,6 +167,10 @@ module Midi_sequencer = struct
172167
{ frequency_hz; gate; velocity })
173168
in
174169
let pitch_wheel_multiplier = Signal.of_ref pitch_wheel_multiplier in
170+
let controller_table =
171+
Array.map controller_refs ~f:(fun controller_ref ->
172+
Signal.map signal_to_update_state ~f:(fun () -> !controller_ref))
173+
in
175174
{ voices; pitch_wheel_multiplier; controller_table }
176175
end
177176

@@ -201,3 +200,4 @@ let track_signal (track : Track.t) clock =
201200
List.rev (loop [ next_event ]))
202201
else [])
203202
else [])
203+
|> Signal.map ~f:(List.map ~f:(fun (event : Event.t) -> event.message))

src/core/midi.mli

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ module Midi_sequencer : sig
2929
controller_table : Controller_table.t;
3030
}
3131

32-
val key_gates : channel:int -> Event.t list Signal.t -> Gate_table.t
32+
val key_gates : channel:int -> Message.t list Signal.t -> Gate_table.t
3333
(** Create a table of gate signals that arrive on a given channel, ignoring
3434
other information. *)
3535

36-
val signal : channel:int -> polyphony:int -> Event.t list Signal.t -> output
36+
val signal : channel:int -> polyphony:int -> Message.t list Signal.t -> output
3737
end
3838

39-
val track_signal : Track.t -> Signal.Trigger.t -> Event.t list Signal.t
39+
val track_signal : Track.t -> Signal.Trigger.t -> Message.t list Signal.t

src/llama/llama.ml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
open StdLabels
12
include Llama_core
23

34
module Live = struct
@@ -42,7 +43,19 @@ module Midi = struct
4243
Midi.Event.parse_multi_from_char_array raw_data)))
4344
else Error `No_such_port
4445

45-
let live_midi_sequencer input ~port ~channel ~polyphony =
46+
let live_midi_signal_messages input port =
4647
live_midi_signal input port
48+
|> Result.map (fun event_signal ->
49+
Signal.map event_signal
50+
~f:(List.map ~f:(fun (event : Event.t) -> event.message)))
51+
52+
let live_midi_sequencer input ~port ~channel ~polyphony =
53+
live_midi_signal_messages input port
4754
|> Result.map (Midi_sequencer.signal ~channel ~polyphony)
55+
56+
let live_midi_messages_serial ~port ~baud =
57+
let midi_serial = Midi_serial.create ~port ~baud in
58+
Signal.of_raw (fun _ ->
59+
Midi_serial.consume_all_available_bytes midi_serial;
60+
Midi_serial.drain_messages midi_serial)
4861
end

src/llama/llama.mli

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ module Midi : sig
4545
Midi_input.t -> int -> (Event.t list Signal.t, [ `No_such_port ]) result
4646
(** Create a signal of lists of midi events that arrive on a given midi port in real time *)
4747

48+
val live_midi_signal_messages :
49+
Midi_input.t -> int -> (Message.t list Signal.t, [ `No_such_port ]) result
50+
(** Create a signal of lists of midi events that arrive on a given midi port in real time *)
51+
4852
val live_midi_sequencer :
4953
Midi_input.t ->
5054
port:int ->
@@ -54,4 +58,7 @@ module Midi : sig
5458
(** Create a midi sequencer that processes midi events on a single midi
5559
channel from a given port. Use this for simple cases where you're only
5660
interested in events on a single channel. *)
61+
62+
val live_midi_messages_serial :
63+
port:string -> baud:int -> Message.t list Signal.t
5764
end

0 commit comments

Comments
 (0)