0
# Message Forwarding
1
2
Message forwarding provides functionality to route MIDI messages from input ports to output ports automatically. The Forwarder class enables flexible message routing with filtering and transformation capabilities.
3
4
## Capabilities
5
6
### Forwarder Construction
7
8
Create message forwarders with destination outputs and filtering options.
9
10
```javascript { .api }
11
class Forwarder {
12
/**
13
* Create a message forwarder
14
* @param destinations - Array of Output objects to forward messages to
15
* @param options - Forwarding options
16
* @param options.channels - Channel filter (number, array, or "all")
17
* @param options.types - Message types to forward (string or array)
18
* @param options.transform - Transform function for messages
19
*/
20
constructor(destinations?: Output[], options?: {
21
channels?: number | number[] | "all";
22
types?: string | string[];
23
transform?: (message: Message) => Message;
24
});
25
}
26
```
27
28
**Usage Examples:**
29
30
```javascript
31
import { WebMidi, Forwarder } from "webmidi";
32
33
await WebMidi.enable();
34
const input = WebMidi.inputs[0];
35
const output1 = WebMidi.outputs[0];
36
const output2 = WebMidi.outputs[1];
37
38
// Simple forwarder to single output
39
const simpleForwarder = new Forwarder([output1]);
40
41
// Multiple destinations
42
const multiForwarder = new Forwarder([output1, output2]);
43
44
// With channel filtering
45
const channelForwarder = new Forwarder([output1], {
46
channels: [1, 2, 3, 4] // Only forward channels 1-4
47
});
48
49
// With message type filtering
50
const noteForwarder = new Forwarder([output1], {
51
types: ["noteon", "noteoff"] // Only forward note messages
52
});
53
54
// Combined filtering
55
const filteredForwarder = new Forwarder([output1, output2], {
56
channels: [1, 10], // Channels 1 and 10
57
types: ["noteon", "noteoff", "controlchange"] // Notes and CC only
58
});
59
```
60
61
### Forwarder Properties
62
63
Access forwarder destinations and configuration.
64
65
```javascript { .api }
66
/**
67
* Array of destination Output objects
68
*/
69
readonly destinations: Output[];
70
71
/**
72
* Message types being forwarded (if filtered)
73
*/
74
readonly types: string[];
75
76
/**
77
* MIDI channels being forwarded (if filtered)
78
*/
79
readonly channels: number[];
80
81
/**
82
* Whether forwarding is suspended
83
*/
84
readonly suspended: boolean;
85
```
86
87
**Usage Examples:**
88
89
```javascript
90
const forwarder = new Forwarder([output1, output2]);
91
92
// Check destinations
93
console.log("Forwarding to", forwarder.destinations.length, "outputs");
94
forwarder.destinations.forEach((output, index) => {
95
console.log(`Destination ${index + 1}: ${output.name}`);
96
});
97
98
// Add more destinations (if implementation allows)
99
// Note: WebMidi.js may not support dynamic destination changes
100
```
101
102
### Message Forwarding
103
104
Forward messages to destination outputs.
105
106
```javascript { .api }
107
/**
108
* Forward a message to all destinations
109
* @param message - MIDI message to forward
110
*/
111
forward(message: Message): void;
112
```
113
114
**Usage Examples:**
115
116
```javascript
117
// Manual forwarding (usually handled automatically)
118
const forwarder = new Forwarder([output1]);
119
120
// Forward a specific message
121
const noteOnData = new Uint8Array([0x90, 60, 100]);
122
const message = new Message(noteOnData);
123
forwarder.forward(message);
124
125
// This is typically used internally when forwarder is attached to input
126
```
127
128
## Integration with Input Ports
129
130
### Adding Forwarders to Inputs
131
132
The primary way to use forwarders is to attach them to input ports.
133
134
```javascript
135
const input = WebMidi.inputs[0];
136
const output = WebMidi.outputs[0];
137
138
// Add forwarder to input
139
const forwarder = input.addForwarder(output);
140
141
// Add forwarder with options
142
const filteredForwarder = input.addForwarder(output, {
143
channels: [1, 2, 3],
144
types: ["noteon", "noteoff"]
145
});
146
147
// Remove forwarder
148
input.removeForwarder(forwarder);
149
150
// Check if forwarder exists
151
if (input.hasForwarder(forwarder)) {
152
console.log("Forwarder is active");
153
}
154
```
155
156
## Forwarding Patterns
157
158
### Simple MIDI Through
159
160
Forward all messages from input to output (MIDI through).
161
162
```javascript
163
async function setupMidiThrough(inputName, outputName) {
164
await WebMidi.enable();
165
166
const input = WebMidi.getInputByName(inputName);
167
const output = WebMidi.getOutputByName(outputName);
168
169
if (input && output) {
170
const forwarder = input.addForwarder(output);
171
console.log(`MIDI through: ${input.name} → ${output.name}`);
172
return forwarder;
173
} else {
174
console.error("Could not find specified input or output");
175
}
176
}
177
178
// Usage
179
const throughForwarder = await setupMidiThrough("My Keyboard", "My Synth");
180
```
181
182
### Channel Routing
183
184
Route specific channels to different outputs.
185
186
```javascript
187
async function setupChannelRouting() {
188
await WebMidi.enable();
189
190
const input = WebMidi.inputs[0];
191
const synthOutput = WebMidi.getOutputByName("Synthesizer");
192
const drumOutput = WebMidi.getOutputByName("Drum Machine");
193
194
// Route channels 1-8 to synthesizer
195
const synthForwarder = input.addForwarder(synthOutput, {
196
channels: [1, 2, 3, 4, 5, 6, 7, 8]
197
});
198
199
// Route channel 10 (drums) to drum machine
200
const drumForwarder = input.addForwarder(drumOutput, {
201
channels: [10]
202
});
203
204
console.log("Channel routing established");
205
return { synthForwarder, drumForwarder };
206
}
207
```
208
209
### Message Type Filtering
210
211
Forward only specific message types.
212
213
```javascript
214
async function setupMessageFiltering() {
215
await WebMidi.enable();
216
217
const input = WebMidi.inputs[0];
218
const noteOutput = WebMidi.outputs[0];
219
const controlOutput = WebMidi.outputs[1];
220
221
// Forward only note messages
222
const noteForwarder = input.addForwarder(noteOutput, {
223
types: ["noteon", "noteoff", "keyaftertouch"]
224
});
225
226
// Forward only control messages
227
const controlForwarder = input.addForwarder(controlOutput, {
228
types: ["controlchange", "programchange", "pitchbend"]
229
});
230
231
console.log("Message type filtering established");
232
return { noteForwarder, controlForwarder };
233
}
234
```
235
236
### Multi-Zone Keyboard
237
238
Split keyboard into zones forwarding to different outputs.
239
240
```javascript
241
async function setupKeyboardZones() {
242
await WebMidi.enable();
243
244
const input = WebMidi.getInputByName("88-Key Controller");
245
const bassOutput = WebMidi.getOutputByName("Bass Synth");
246
const leadOutput = WebMidi.getOutputByName("Lead Synth");
247
const padOutput = WebMidi.getOutputByName("Pad Synth");
248
249
// Lower zone: C0-B2 → Bass (Channel 1)
250
const bassForwarder = input.addForwarder(bassOutput, {
251
channels: [1]
252
// Note: WebMidi.js forwarder doesn't have built-in note range filtering
253
// You would need to implement this with custom message processing
254
});
255
256
// Middle zone: C3-C5 → Lead (Channel 2)
257
const leadForwarder = input.addForwarder(leadOutput, {
258
channels: [2]
259
});
260
261
// Upper zone: C#5-C8 → Pad (Channel 3)
262
const padForwarder = input.addForwarder(padOutput, {
263
channels: [3]
264
});
265
266
return { bassForwarder, leadForwarder, padForwarder };
267
}
268
269
// For note range filtering, you'd need custom processing:
270
function setupNoteRangeForwarding(input, output, minNote, maxNote, options = {}) {
271
return input.addListener("midimessage", (e) => {
272
const message = e.message;
273
274
// Check if it's a note message
275
if (message.type === "noteon" || message.type === "noteoff") {
276
const noteNumber = message.dataBytes[0];
277
278
// Forward if within range
279
if (noteNumber >= minNote && noteNumber <= maxNote) {
280
output.send(message.rawData, options);
281
}
282
}
283
});
284
}
285
```
286
287
### Velocity Scaling and Transformation
288
289
While the Forwarder class itself may not support message transformation, you can implement custom forwarding with transformation.
290
291
```javascript
292
function setupVelocityScaling(input, output, scaleFactor = 1.0, options = {}) {
293
return input.addListener("midimessage", (e) => {
294
const message = e.message;
295
let modifiedData = Array.from(message.rawData);
296
297
// Scale velocity for note messages
298
if ((message.type === "noteon" || message.type === "noteoff") && modifiedData.length >= 3) {
299
const originalVelocity = modifiedData[2];
300
const scaledVelocity = Math.round(Math.min(127, originalVelocity * scaleFactor));
301
modifiedData[2] = scaledVelocity;
302
}
303
304
// Forward modified message
305
output.send(new Uint8Array(modifiedData), options);
306
});
307
}
308
309
// Usage: Scale velocity down by 75%
310
const scalingListener = setupVelocityScaling(input, output, 0.75);
311
```
312
313
### Channel Remapping
314
315
Remap MIDI channels during forwarding.
316
317
```javascript
318
function setupChannelRemapping(input, output, channelMap) {
319
return input.addListener("midimessage", (e) => {
320
const message = e.message;
321
322
if (message.isChannelMessage) {
323
const originalChannel = message.channel;
324
const newChannel = channelMap[originalChannel];
325
326
if (newChannel !== undefined) {
327
let modifiedData = Array.from(message.rawData);
328
329
// Modify channel in status byte
330
const command = (modifiedData[0] & 0xF0); // Keep command, clear channel
331
const newChannelMidi = newChannel - 1; // Convert to MIDI channel (0-15)
332
modifiedData[0] = command | newChannelMidi;
333
334
// Forward remapped message
335
output.send(new Uint8Array(modifiedData));
336
}
337
} else {
338
// Forward system messages unchanged
339
output.send(message.rawData);
340
}
341
});
342
}
343
344
// Usage: Remap channels 1→2, 2→3, 3→1
345
const channelMap = { 1: 2, 2: 3, 3: 1 };
346
const remappingListener = setupChannelRemapping(input, output, channelMap);
347
```
348
349
## Advanced Forwarding Scenarios
350
351
### MIDI Router/Patchbay
352
353
Create a comprehensive MIDI routing system.
354
355
```javascript
356
class MidiRouter {
357
constructor() {
358
this.routes = new Map();
359
this.forwarders = [];
360
}
361
362
async initialize() {
363
await WebMidi.enable();
364
this.updateDeviceList();
365
}
366
367
updateDeviceList() {
368
this.inputs = WebMidi.inputs.map(input => ({ id: input.id, name: input.name }));
369
this.outputs = WebMidi.outputs.map(output => ({ id: output.id, name: output.name }));
370
}
371
372
addRoute(inputId, outputId, options = {}) {
373
const input = WebMidi.getInputById(inputId);
374
const output = WebMidi.getOutputById(outputId);
375
376
if (input && output) {
377
const forwarder = input.addForwarder(output, options);
378
const routeId = `${inputId}->${outputId}`;
379
380
this.routes.set(routeId, {
381
input: input,
382
output: output,
383
forwarder: forwarder,
384
options: options
385
});
386
387
return routeId;
388
}
389
390
return null;
391
}
392
393
removeRoute(routeId) {
394
const route = this.routes.get(routeId);
395
if (route) {
396
route.input.removeForwarder(route.forwarder);
397
this.routes.delete(routeId);
398
return true;
399
}
400
return false;
401
}
402
403
listRoutes() {
404
const routes = [];
405
for (const [routeId, route] of this.routes) {
406
routes.push({
407
id: routeId,
408
input: route.input.name,
409
output: route.output.name,
410
options: route.options
411
});
412
}
413
return routes;
414
}
415
416
clearAllRoutes() {
417
for (const routeId of this.routes.keys()) {
418
this.removeRoute(routeId);
419
}
420
}
421
}
422
423
// Usage
424
const router = new MidiRouter();
425
await router.initialize();
426
427
// Add routes
428
const route1 = router.addRoute(input1.id, output1.id, { channels: [1, 2] });
429
const route2 = router.addRoute(input1.id, output2.id, { channels: [10] });
430
431
// List active routes
432
console.log(router.listRoutes());
433
434
// Remove specific route
435
router.removeRoute(route1);
436
```
437
438
### Performance Monitoring
439
440
Monitor forwarding performance and message throughput.
441
442
```javascript
443
class ForwardingMonitor {
444
constructor(forwarder) {
445
this.forwarder = forwarder;
446
this.messageCount = 0;
447
this.startTime = Date.now();
448
this.lastResetTime = this.startTime;
449
}
450
451
// Wrap the forward method to count messages
452
wrapForwarder() {
453
const originalForward = this.forwarder.forward.bind(this.forwarder);
454
455
this.forwarder.forward = (message) => {
456
this.messageCount++;
457
return originalForward(message);
458
};
459
}
460
461
getStats() {
462
const now = Date.now();
463
const totalTime = now - this.startTime;
464
const resetTime = now - this.lastResetTime;
465
466
return {
467
messageCount: this.messageCount,
468
totalTime: totalTime,
469
messagesPerSecond: this.messageCount / (resetTime / 1000),
470
averageMessagesPerSecond: this.messageCount / (totalTime / 1000)
471
};
472
}
473
474
reset() {
475
this.messageCount = 0;
476
this.lastResetTime = Date.now();
477
}
478
}
479
480
// Usage
481
const forwarder = input.addForwarder(output);
482
const monitor = new ForwardingMonitor(forwarder);
483
monitor.wrapForwarder();
484
485
// Check stats periodically
486
setInterval(() => {
487
const stats = monitor.getStats();
488
console.log(`Messages/sec: ${stats.messagesPerSecond.toFixed(2)}`);
489
}, 1000);
490
```
491
492
## Types
493
494
```javascript { .api }
495
interface ForwarderOptions {
496
channels?: number | number[] | "all";
497
types?: string | string[];
498
transform?: (message: Message) => Message;
499
}
500
501
interface RouteInfo {
502
id: string;
503
input: string;
504
output: string;
505
options: ForwarderOptions;
506
}
507
508
type MessageType = "noteon" | "noteoff" | "keyaftertouch" | "controlchange" | "programchange" | "channelaftertouch" | "pitchbend" | "sysex";
509
```