-
Notifications
You must be signed in to change notification settings - Fork 1
/
midi_sender.ck
361 lines (315 loc) · 8.64 KB
/
midi_sender.ck
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
//
// Basic midi wrapper class. This will act as a wrapper for the MidiOut
// object to provide all the functionality I want to do more usefriendly
// MIDI handling in ChucK
//
// Bruce Murphy
// rattus.net, July 2006.
//
// Current version: alpha-31july2006
public class MIDIsender
{
MidiOut midiport;
MidiMsg midimessage;
// so we don't have to distinguish external from internal
// channels, we'll keep this in the people friendly 1-16 format
1 => int default_channel;
// this will be used for our notecounting. We'll turn notecounting on by
// default for now.
// Note that we have to do it per channel.
int notecount[16][128];
1=> int countingp;
// internally generated MIDI streams will have this spacing to avoid crashing
// older hardware.
5::ms => dur midigap;
// direct wrapper to the midiport's open function.
fun int open(int portid)
{
return midiport.open(portid);
}
// This is new. Try to open a MIDI port by finding one whose "name" matches
// the passed-in string.
//
// This is not efficient by a long stretch. Returns 0 on failure 1 on success.
fun int open(string portname)
{
int portret;
0 => int portnum;
while(1) {
midiport.open(portnum) => portret;
if (!portret) {
<<< "No matching port found" >>>;
return 0;
}
if (midiport.name() == portname) {
return 1;
}
portnum++;
}
}
// sets the default channel for polymorphic functions.
fun void set_channel(int channel)
{
if (channel < 1 || channel > 16) {
1 => default_channel;
} else {
channel => default_channel;
}
}
///////////////////
// Utility functions
//
// for notecounting etc.
fun void count_note(int notenum, int channel, int increment)
{
notenum & 0x7f => notenum;
(channel - 1) & 0xf => channel;
increment +=> notecount[channel][notenum];
}
// issues noteoffs for all the outstaning notes on a particular channel which
// this object has sent, but which haven't been called in yet.
//
// as a special case here, if we hand it the channel -1, it will stop the
// notes for all the channels. Once finished, it will clear the notecount array.
fun void stop_hanging_notes()
{
stop_hanging_notes(default_channel);
}
fun void stop_hanging_notes(int channel)
{
if (channel != -1) {
(channel - 1) & 0xf => channel;
for (0 => int ii; ii < 128; ii++) {
if (notecount[channel][ii] > 0) {
noteoff(ii, channel + 1);
midigap => now;
}
}
} else { // -1 behavior
for (0 => int cc; cc < 16; cc++) {
for (0 => int ii; ii < 128; ii++) {
if (notecount[cc][ii] > 0) {
noteoff(ii, cc + 1);
midigap => now;
}
}
}
}
clear_note_counts(channel);
}
// This just clears the hanging note count arrays. Has the same -1 channel behavior
// as clear_hanging_notes().
fun void clear_note_counts()
{
clear_note_counts(default_channel);
}
fun void clear_note_counts(int channel)
{
if (channel != -1) {
(channel - 1) & 0xf => channel;
for (0 => int ii; ii < 128; ii++) {
0 => notecount[channel][ii];
}
} else { // -1 behavior
for (0 => int cc; cc < 16; cc++) {
for (0 => int ii; ii < 128; ii++) {
0 => notecount[cc][ii];
}
}
}
}
///////////////////
// Interface functions.
//
// These functions map pretty simply onto the MIDI specification messages.
// polymorphic set of functions supplying default values for the MIDI
// noteon() function.
fun void noteon(int notenum)
{
noteon(notenum, 127, default_channel);
}
fun void noteon(int notenum, int velocity)
{
noteon(notenum, velocity, default_channel);
}
fun void noteon(int notenum, int velocity, int channel)
{
send_3bytes(0x9, channel, notenum, velocity);
if (countingp) {
velocity & 0x7f => velocity;
if (velocity != 0) {
count_note(notenum, channel, 1);
} else {
count_note(notenum, channel, -1);
}
}
}
// The basic MIDI noteoff uses noteons with zero velocity.
fun void noteoff(int notenum)
{
noteon(notenum, 0, default_channel);
}
fun void noteoff(int notenum, int channel)
{
noteon(notenum, 0, channel);
}
// Some rare synths make use of release velocity. For those we use a different
// MIDI message type.
fun void noteoff_velocity(int notenum, int velocity)
{
noteoff_velocity(notenum, velocity, default_channel);
}
fun void noteoff_velocity(int notenum, int velocity, int channel)
{
send_3bytes(0x8, channel, notenum, velocity);
if (countingp) {
count_note(notenum, channel, -1);
}
}
//
// Polyphonic aftertouch message. This controls aftertouch level for individual
// notes.
fun void aftertouch(int notenum, int level)
{
aftertouch(notenum, level, default_channel);
}
fun void aftertouch(int notenum, int level, int channel)
{
send_3bytes(0xa, channel, notenum, level);
}
fun void controller(int controllernum, int value)
{
controller(controllernum, value, default_channel);
}
fun void controller(int controllernum, int value, int channel)
{
send_3bytes(0xb, channel, controllernum, value);
}
// basic program change functionality. We need to have bank and subbank changes
// available as well, but they're convenience functions.
fun void program_change(int prognum)
{
program_change(prognum, default_channel);
}
fun void program_change(int program, int channel)
{
send_2bytes(0xc, channel, program);
}
// channel pressure is what most synths implement to do aftertouch. It isn't
// specific to any one note.
fun void channel_pressure(int pressure)
{
channel_pressure(pressure, default_channel);
}
fun void channel_pressure(int pressure, int channel)
{
send_2bytes(0xd, channel, pressure);
}
// pitchbending is actually complicated. We'll take a value of -64 to 63
// for now. This will only send coarse bends, to avoid overwhelming MIDI.
fun void pitchbend(int bend)
{
pitchbend(bend, default_channel);
}
fun void pitchbend(int bend, int channel)
{
bend + 64 => bend;
if (bend > 127) { 127 => bend; }
send_3bytes(0xe, channel, 0, bend);
}
// We'll use these to generate fine bends. Input is a float varying from -1
// to 1.
fun void pitchbend_fine(float bend)
{
pitchbend_fine(bend, default_channel);
}
fun void pitchbend_fine(float bend, int channel)
{
((bend + 1) * 8192) $ int => int bendval;
if (bendval < 0) {
0 => bendval;
}
if (bendval > 0x3fff) {
0x3fff => bendval;
}
send_3bytes(0xe, channel, bendval & 0x7f, (bendval >> 7) & 0x7f);
}
/////////////////////////////
// Convenience functions.
//
// These functions are just calls to the main interface functions with certain
// magic parameters. They are handy to have so you don't have to remember the
// magic parameters.
// This is the MIDI panic message that should turn all MIDI generated notes off
// on the device.
fun void all_notes_off()
{
all_notes_off(default_channel);
}
fun void all_notes_off(int channel)
{
controller(123, 0, channel);
}
// Another panic, this one is supposed to kill sound, not just emulate
// note offs and so ignore sustain and long envelopes.
// Less typically implemented.
fun void all_sound_off()
{
all_sound_off(default_channel);
}
fun void all_sound_off(int channel)
{
controller(120, 0, channel);
}
// Bank select is magic. Consult synth documentation to figure out which
// order of bank and program changes your device requires.
//
// It'll usually be Bank, Sub, Prog. or Sub, Bank, Prog, but some use only one
// of bank or sub :)
fun void select_bank(int bank)
{
select_bank(bank, default_channel);
}
fun void select_bank(int bank, int channel)
{
controller(0, bank, channel);
}
fun void select_subbank(int bank)
{
select_subbank(bank, default_channel);
}
fun void select_subbank(int bank, int channel)
{
controller(32, bank, channel);
}
// Uses only the coarse volume since few things both to implement fine.
fun void volume(int level)
{
volume(level, default_channel);
}
fun void volume(int level, int channel)
{
controller(7, level, channel);
}
////////////////////////////
// basic interface functions.
//
// These generate the low-level MIDI messages.
// Basically all the MIDI functions break down into sending one of these
// two things.
fun void send_3bytes(int command, int channel, int byte1, int byte2)
{
((command & 0xf) << 4) | ((channel - 1) & 0xf) => midimessage.data1;
command | channel => command;
byte1 & 0x7f => midimessage.data2;
byte2 & 0x7f => midimessage.data3;
midiport.send(midimessage);
}
// not sure what dance is required to make this only send two bytes.
fun void send_2bytes(int command, int channel, int byte1)
{
((command & 0x0f) << 4) | ((channel - 1) & 0xf) => midimessage.data1;
byte1 & 0x7f => midimessage.data2;
midiport.send(midimessage);
}
}