Skip to content

Commit 3591edf

Browse files
committed
FEAT: initial MIDI port scheme implementation (Windows only so far)
Here is example how to list available input/output MIDI devices: ``` >> query midi:// == make object! [ devices-in: ["Launch Control XL"] devices-out: ["Microsoft GS Wavetable Synth" "Launch Control XL"] ] ``` This will open MIDI port in write only mode and play a chord (Middle-C, E and G): ``` synth: open/write midi://1 write synth #{903C7F00 90407F00 90437F00} ``` The scheme is low-level so supports only binary for read/write. There can be made higher level scheme which could support a dialect for preparing the data. This is example how to get MIDI input using port's `awake`: ``` launch: open/read midi://device:1 launch/awake: function [event [event!]][ switch event/type [ read [ probe read event/port ] open [ print "MIDI port opened!" ] close [ print "MIDI port closed!" ] ] true ] ``` If the `awake` is defined like above and you are in some sort of `wait` loop, than whenever MIDI input is received, you would see the data from it printed in console. TODO: * support SYSEX messages * at least macOS support
1 parent f6b2bb0 commit 3591edf

File tree

8 files changed

+804
-3
lines changed

8 files changed

+804
-3
lines changed

src/boot/sysobj.r

+13-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,13 @@ standard: context [
252252
scheme: 'checksum
253253
method: none
254254
]
255-
255+
256+
port-spec-midi: make port-spec-head [
257+
scheme: 'midi
258+
device-in:
259+
device-out: none
260+
]
261+
256262
file-info: context [
257263
name:
258264
size:
@@ -284,6 +290,12 @@ standard: context [
284290
none
285291
]
286292

293+
midi-info: context [
294+
devices-in:
295+
devices-out:
296+
none
297+
]
298+
287299
extension: context [
288300
lib-base: ; handle to DLL
289301
lib-file: ; file name loaded

src/boot/words.r

+3
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,6 @@ buffer-cols
220220
buffer-rows
221221
window-cols
222222
window-rows
223+
224+
devices-in
225+
devices-out

src/core/p-midi.c

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/***********************************************************************
2+
**
3+
** REBOL [R3] Language Interpreter and Run-time Environment
4+
**
5+
** Copyright 2012 REBOL Technologies
6+
** Copyright 2012-2019 Rebol Open Source Developers
7+
** REBOL is a trademark of REBOL Technologies
8+
**
9+
** Licensed under the Apache License, Version 2.0 (the "License");
10+
** you may not use this file except in compliance with the License.
11+
** You may obtain a copy of the License at
12+
**
13+
** http://www.apache.org/licenses/LICENSE-2.0
14+
**
15+
** Unless required by applicable law or agreed to in writing, software
16+
** distributed under the License is distributed on an "AS IS" BASIS,
17+
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
** See the License for the specific language governing permissions and
19+
** limitations under the License.
20+
**
21+
************************************************************************
22+
**
23+
** Module: p-midi.c
24+
** Summary: MIDI port interface
25+
** Section: ports
26+
** Author: Oldes
27+
** Notes:
28+
**
29+
***********************************************************************/
30+
31+
#include "sys-core.h"
32+
33+
/***********************************************************************
34+
**
35+
*/ static REBOOL Query_MIDI_Field(REBSER *obj, REBCNT field, REBVAL *ret)
36+
/*
37+
** Set a value with MIDI related data according specified field
38+
** The `obj` must be system/standard/midi-info with filled info
39+
**
40+
***********************************************************************/
41+
{
42+
switch (field) {
43+
case SYM_DEVICES_IN:
44+
*ret = *Get_Field(obj, STD_MIDI_INFO_DEVICES_IN);
45+
return TRUE;
46+
case SYM_DEVICES_OUT:
47+
*ret = *Get_Field(obj, STD_MIDI_INFO_DEVICES_OUT);
48+
return TRUE;
49+
default:
50+
return FALSE;
51+
}
52+
}
53+
54+
/***********************************************************************
55+
**
56+
*/ static int MIDI_Actor(REBVAL *ds, REBSER *port, REBCNT action)
57+
/*
58+
***********************************************************************/
59+
{
60+
REBREQ *req;
61+
REBINT result;
62+
REBVAL *arg;
63+
REBCNT args = 0;
64+
REBINT len, n;
65+
REBSER *ser;
66+
REBVAL *spec;
67+
REBVAL *val1, *val2;
68+
69+
//printf("MIDI_Actor action: %i\n", action);
70+
71+
Validate_Port(port, action);
72+
73+
req = Use_Port_State(port, RDI_MIDI, sizeof(REBREQ));
74+
75+
switch (action) {
76+
77+
case A_READ:
78+
// This device is opened on the READ:
79+
if (!IS_OPEN(req)) Trap_Port(RE_NOT_OPEN, port, -12);
80+
81+
result = OS_DO_DEVICE(req, RDC_READ);
82+
if (result < 0) Trap_Port(RE_READ_ERROR, port, req->error);
83+
84+
// Copy and set the string result:
85+
arg = OFV(port, STD_PORT_DATA);
86+
//arg = D_ARG(1);
87+
SET_BINARY(arg, Copy_Bytes(req->data, req->actual));
88+
req->data = 0;
89+
*D_RET = *arg;
90+
return R_RET;
91+
92+
case A_WRITE:
93+
if (!IS_OPEN(req)) Trap_Port(RE_NOT_OPEN, port, -12);
94+
arg = D_ARG(2);
95+
if(IS_BINARY(arg)) {
96+
req->data = VAL_BIN(arg);
97+
req->length = VAL_LEN(arg);
98+
99+
result = OS_DO_DEVICE(req, RDC_WRITE);
100+
if (result < 0) Trap_Port(RE_WRITE_ERROR, port, req->error);
101+
}
102+
break;
103+
104+
case A_OPEN:
105+
spec = OFV(port, STD_PORT_SPEC);
106+
args = Find_Refines(ds, AM_OPEN_READ | AM_OPEN_WRITE);
107+
req->modes = args;
108+
if (IS_OBJECT(spec)) {
109+
val1 = Obj_Value(spec, STD_PORT_SPEC_MIDI_DEVICE_IN);
110+
if (IS_INTEGER(val1)) {
111+
req->midi.device_in = VAL_INT32(val1);
112+
//printf(";;;;;;;; requested device-in: %i\n", req->midi.device_in);
113+
}
114+
val2 = Obj_Value(spec, STD_PORT_SPEC_MIDI_DEVICE_OUT);
115+
if (IS_INTEGER(val2)) {
116+
req->midi.device_out = VAL_INT32(val2);
117+
//printf(";;;;;;;; requested device-out: %i\n", req->midi.device_out);
118+
} else if ((args & AM_OPEN_WRITE) && !(args & AM_OPEN_READ)) {
119+
// it was opened using: open/WRITE midi://device:NUM
120+
req->midi.device_out = req->midi.device_in;
121+
req->midi.device_in = 0;
122+
SET_NONE(val1);
123+
SET_INTEGER(val2, req->midi.device_out);
124+
}
125+
}
126+
if (OS_DO_DEVICE(req, RDC_OPEN)) Trap_Port(RE_CANNOT_OPEN, port, req->error);
127+
128+
break;
129+
130+
case A_CLOSE:
131+
if (!IS_OPEN(req)) Trap_Port(RE_NOT_OPEN, port, -12);
132+
OS_DO_DEVICE(req, RDC_CLOSE);
133+
break;
134+
135+
case A_QUERY:
136+
spec = Get_System(SYS_STANDARD, STD_MIDI_INFO);
137+
if (!IS_OBJECT(spec)) Trap_Arg(spec);
138+
if (D_REF(2) && IS_NONE(D_ARG(3))) {
139+
// query/mode midi:// none ;<-- lists possible fields to request
140+
Set_Block(D_RET, Get_Object_Words(spec));
141+
return R_RET;
142+
}
143+
144+
REBSER *obj = CLONE_OBJECT(VAL_OBJ_FRAME(spec));
145+
Set_Block(Get_Field(obj, STD_MIDI_INFO_DEVICES_IN), Make_Block(7));
146+
Set_Block(Get_Field(obj, STD_MIDI_INFO_DEVICES_OUT), Make_Block(7));
147+
req->data = (REBYTE*)obj;
148+
OS_DO_DEVICE(req, RDC_QUERY);
149+
150+
if (D_REF(2)) {
151+
// query/mode used
152+
REBVAL *field = D_ARG(3);
153+
if (IS_WORD(field)) {
154+
if (!Query_MIDI_Field(obj, VAL_WORD_SYM(field), D_RET))
155+
Trap_Reflect(VAL_TYPE(D_ARG(1)), field); // better error?
156+
}
157+
else if (IS_BLOCK(field)) {
158+
REBVAL *val;
159+
REBSER *values = Make_Block(2 * BLK_LEN(VAL_SERIES(field)));
160+
REBVAL *word = VAL_BLK_DATA(field);
161+
for (; NOT_END(word); word++) {
162+
if (ANY_WORD(word)) {
163+
if (IS_SET_WORD(word)) {
164+
// keep the set-word in result
165+
val = Append_Value(values);
166+
*val = *word;
167+
VAL_SET_LINE(val);
168+
}
169+
val = Append_Value(values);
170+
if (!Query_MIDI_Field(obj, VAL_WORD_SYM(word), val))
171+
Trap1(RE_INVALID_ARG, word);
172+
}
173+
else Trap1(RE_INVALID_ARG, word);
174+
}
175+
Set_Series(REB_BLOCK, D_RET, values);
176+
}
177+
return R_RET;
178+
}
179+
Set_Object(D_RET, obj);
180+
return R_RET;
181+
182+
case A_OPENQ:
183+
if (IS_OPEN(req)) return R_TRUE;
184+
return R_FALSE;
185+
186+
case A_UPDATE:
187+
// Update the port object after a READ or WRITE operation.
188+
// This is normally called by the WAKE-UP function.
189+
arg = OFV(port, STD_PORT_DATA);
190+
//printf("MIDI Update action, req->command = %d\n", req->command);
191+
if (req->command == RDC_READ) {
192+
if (ANY_BINSTR(arg)) VAL_TAIL(arg) += req->actual;
193+
}
194+
else if (req->command == RDC_WRITE) {
195+
SET_NONE(arg); // Write is done.
196+
}
197+
return R_NONE;
198+
199+
default:
200+
Trap_Action(REB_PORT, action);
201+
}
202+
203+
return R_ARG1; // port
204+
}
205+
206+
207+
/***********************************************************************
208+
**
209+
*/ void Init_MIDI_Scheme(void)
210+
/*
211+
***********************************************************************/
212+
{
213+
Register_Scheme(SYM_MIDI, 0, MIDI_Actor);
214+
}

src/core/t-event.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@
210210
*val = *Get_System(SYS_VIEW, VIEW_EVENT_PORT);
211211
}
212212
// Event holds a port:
213-
else if (IS_EVENT_MODEL(value, EVM_PORT)) {
213+
else if (IS_EVENT_MODEL(value, EVM_PORT) || IS_EVENT_MODEL(value, EVM_MIDI)) {
214214
SET_PORT(val, VAL_EVENT_SER(value));
215215
}
216216
// Event holds an object:
@@ -237,6 +237,9 @@
237237
break;
238238
}
239239
}
240+
else if (IS_EVENT_MODEL(value, EVM_MIDI))
241+
goto is_none;
242+
240243
return FALSE;
241244

242245
case SYM_OFFSET:

src/include/reb-device.h

+4
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ struct rebol_devreq {
187187
u32 window_rows;
188188
u32 window_cols;
189189
} console;
190+
struct {
191+
u32 device_in; // requested device ID (1-based; 0 = none)
192+
u32 device_out;
193+
} midi;
190194
};
191195
};
192196
#pragma pack()

src/include/reb-event.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
// Forward references:
3535
typedef struct rebol_device REBDEV;
3636
typedef struct rebol_devreq REBREQ;
37+
typedef struct Reb_Series REBSER;
3738

3839
#pragma pack(4)
3940
typedef struct rebol_event {
@@ -44,7 +45,8 @@ typedef struct rebol_event {
4445
u32 data; // an x/y position or keycode (raw/decoded)
4546
union {
4647
REBREQ *req; // request (for device events)
47-
void *ser; // port or object
48+
REBSER *port; // port
49+
void *ser; // object
4850
};
4951
} REBEVT;
5052
#pragma pack()
@@ -68,6 +70,7 @@ enum {
6870
EVM_OBJECT, // event holds object frame pointer
6971
EVM_GUI, // GUI event uses system/view/event/port
7072
EVM_CALLBACK, // Callback event uses system/ports/callback port
73+
EVM_MIDI, // event holds midi port pointer
7174
};
7275

7376
// Special messages

src/mezz/sys-ports.r

+24
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,30 @@ init-schemes: func [
320320
name: 'clipboard
321321
]
322322

323+
make-scheme [
324+
title: "MIDI"
325+
name: 'midi
326+
spec: system/standard/port-spec-midi
327+
init: func [port /local spec inp out] [
328+
spec: port/spec
329+
if url? spec/ref [
330+
parse spec/ref [
331+
thru #":" 0 2 slash
332+
opt "device:"
333+
copy inp *parse-url/digits
334+
opt [#"/" copy out *parse-url/digits]
335+
end
336+
]
337+
if inp [ spec/device-in: to integer! inp]
338+
if out [ spec/device-out: to integer! out]
339+
]
340+
; make port/spec to be only with midi related keys
341+
set port/spec: copy system/standard/port-spec-midi spec
342+
;protect/words port/spec ; protect spec object keys of modification
343+
true
344+
]
345+
]
346+
323347
system/ports/system: open [scheme: 'system]
324348
system/ports/input: open [scheme: 'console]
325349
system/ports/callback: open [scheme: 'callback]

0 commit comments

Comments
 (0)