Skip to content

Commit e1664a1

Browse files
Create MIDI mapping documentation, initial commit
1 parent ab5bea9 commit e1664a1

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed

extras/midi_mapping.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# MIDI Mapping
2+
3+
## Introduction
4+
5+
Most of my audio projects use a static MIDI map. Each project supports a wide range of features, and to control them, you need to assign MIDI controllers to specific functions. The defined structure for mapping is usually found in the `z_config.ino` file of the respective project.
6+
7+
A typical mapping might look like this:
8+
9+
```c
10+
struct midiControllerMapping edirolMapping[] = {
11+
/* MIDI defaults */
12+
{ ALL_ORGAN_CHANNELS, 7, "Volume", NULL, App_MainVolume, 0 },
13+
{ 0x0, 91, "Reverb", NULL, Reverb_SetLevelInt, 8 },
14+
{ 0x0, 93, "Chorus", NULL, Organ_SetLeslieSpeedNorm, 0 },
15+
};
16+
```
17+
18+
## Details of MIDI Mapping
19+
20+
To understand the mapping function, let’s take a closer look at the `struct midiControllerMapping`. This structure contains the following elements:
21+
22+
```c
23+
struct midiControllerMapping {
24+
uint8_t channel;
25+
uint8_t data1;
26+
const char *desc;
27+
void (*callback_mid)(uint8_t ch, uint8_t data1, uint8_t data2);
28+
#ifdef MIDI_FMT_INT
29+
void (*callback_val)(uint8_t userdata, uint8_t value);
30+
#else
31+
void (*callback_val)(uint8_t userdata, float value);
32+
#endif
33+
uint8_t user_data;
34+
};
35+
```
36+
37+
### MIDI channel number `channel`
38+
39+
This field selects the channel on which the message is received. It can be a number from 0 to 15 (for channels 1 to 16) or a predefined mask (with current limitations).
40+
41+
To link a function to all four channels, you can use a define like this:
42+
43+
```c
44+
#define ALL_ORGAN_CHANNELS (MIDI_CHANNEL_MASK | MIDI_CHANNEL_0 | MIDI_CHANNEL_1 | MIDI_CHANNEL_2)
45+
```
46+
47+
### Controller number (`data1`)
48+
49+
The `data1` field refers to the controller number. Each knob or slider on your MIDI controller is assigned a specific controller number, which this field uses to identify the corresponding control.
50+
51+
Standard MIDI controller numbers include:
52+
- 1: Modulation
53+
- 4: Foot Pedal
54+
- 7: Volume
55+
- 64: Sustain Pedal
56+
- 91: Reverb
57+
- 93: Chorus
58+
59+
However, not all projects support the full General MIDI (GM) or General Standard (GS) specifications.
60+
61+
### Description `desc`
62+
63+
This field is for a text description to make identification easier. For example, you can include the names of the controllers. Currently, this is not actively used but serves as a reference.
64+
65+
### Callback Functions
66+
67+
Callback functions are invoked when the channel and controller number of a received message match the mapping. There are two types of callbacks:
68+
69+
#### Short callback `callback_mid`
70+
71+
This function receives the complete MIDI short message and is called when the channel and controller number match the received MIDI message.
72+
73+
#### Value callback `callback_val`
74+
75+
This function provides `user_data` (an identifier) and the value of the received control change message.
76+
77+
### Additional data `user_data`
78+
79+
The `user_data` field allows you to pass a custom identifier to the callback function. This is useful when using the same callback for different control change messages, as it helps differentiate between them. It also simplifies remapping because the `user_data` remains unchanged if you modify the `controller_number`.
80+
81+
## Examples
82+
83+
### Example 1
84+
85+
A mapping with a single entry:
86+
87+
```c
88+
struct midiControllerMapping edirolMapping[] = {
89+
{ 0x0, 0x40, "sustain", NULL, Organ_SetLeslieSpeedNorm, 0 },
90+
};
91+
```
92+
93+
When receiving a short message `0xC0 0x40 <value>`, the function `Organ_SetLeslieSpeedNorm` will be called as follows:
94+
95+
```c
96+
Organ_SetLeslieSpeedNorm(0, <value>);
97+
```
98+
99+
Values received on channel 0 for control change number `0x40` (64) will trigger this function.
100+
101+
### Example 2
102+
103+
Using a defined mask:
104+
105+
```c
106+
#define ALL_ORGAN_CHANNELS (MIDI_CHANNEL_MASK | MIDI_CHANNEL_0 | MIDI_CHANNEL_1 | MIDI_CHANNEL_2)
107+
108+
struct midiControllerMapping edirolMapping[] = {
109+
{ ALL_ORGAN_CHANNELS, 0x01, "modulation", NULL, App_Modulation, 0 },
110+
};
111+
```
112+
113+
In this case, control number 1 received on channels 0 to 2 will call `App_Modulation`.
114+
115+
### Example 3
116+
117+
A mapping with multiple entries:
118+
119+
```c
120+
struct midiControllerMapping edirolMapping[] = {
121+
{ 0x00, 0x20, "C1", NULL, App_Param, 0 },
122+
{ 0x00, 0x21, "C2", NULL, App_Param, 1 },
123+
{ 0x00, 0x22, "C3", NULL, App_Param, 2 },
124+
{ 0x00, 0x23, "C4", NULL, App_Param, 3 },
125+
};
126+
```
127+
128+
Here, `App_Param` will be called for control change numbers `0x20` to `0x23` on channel 0. The `user_data` distinguishes the messages:
129+
130+
- `user_data = 0` for `0x20`
131+
- `user_data = 1` for `0x21`
132+
-
133+
134+
The `App_Param` function should be defined as:
135+
136+
```c
137+
void App_Param(uint8_t userdata, float value);
138+
```
139+
140+
## MIDI Float or Integer
141+
142+
The system can be configured to use either integer or float values via a define:
143+
144+
```c
145+
#define MIDI_FMT_INT
146+
```
147+
148+
If `MIDI_FMT_INT` is defined, the value is passed as a `uint8_t`, representing the raw MIDI value (0–127). If not defined, the value is converted to a float internally and passed as a normalized value (0.0 to 1.0).

0 commit comments

Comments
 (0)