Skip to content

Commit 3fe895d

Browse files
committed
Prep for Network MIDI 2.0
1 parent 3af4ae0 commit 3fe895d

5 files changed

Lines changed: 412 additions & 0 deletions

File tree

doc/NetworkMIDI2_UMP.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Network MIDI 2.0 - UMP Base (Phase 1)
2+
3+
Phase 1 adds **Universal MIDI Packet (UMP)** support for Network MIDI 2.0. The implementation is **parallel** to AppleMIDI – no dependency on the RTP-MIDI code.
4+
5+
## Enabling
6+
7+
Uncomment in `src/NetworkMIDI2_Config.h`:
8+
```c
9+
#define NETWORK_MIDI_2_SUPPORT
10+
```
11+
Or add `-DNETWORK_MIDI_2_SUPPORT` to build flags.
12+
13+
## Files
14+
15+
| File | Purpose |
16+
|------|---------|
17+
| `NetworkMIDI2_Config.h` | Enable/disable switch |
18+
| `ump_Defs.h` | UMP constants, Message Types, bit layouts |
19+
| `ump_Convert.h` | Byte↔UMP conversion API |
20+
| `ump_Convert.cpp` | Conversion implementation |
21+
22+
## Examples (standalone, no AppleMIDI)
23+
24+
- **UMP_Conversion** – Byte↔UMP conversion (Serial only)
25+
- **ESP8266_NetworkMIDI2_mDNS** – mDNS `_midi2` + UMP (no transport yet)
26+
27+
## API
28+
29+
```c
30+
// Byte stream -> UMP
31+
ump_bytestream_parser_t parser;
32+
ump_parser_init(&parser);
33+
ump_byte_to_ump(&parser, byte, &ump, group);
34+
35+
// UMP -> bytes
36+
ump_to_bytes(ump, bytes, maxlen);
37+
38+
// Direct build
39+
ump_build_midi1_channel_voice(status, data1, data2, group);
40+
```
41+
42+
## Supported
43+
44+
- **MT 2** (MIDI 1.0 Channel Voice): Note On/Off, CC, Program Change, etc.
45+
- **MT 1** (System Real-Time): Timing Clock, Start, Stop, etc.
46+
47+
## References
48+
49+
- M2-104-UM: UMP and MIDI 2.0 Protocol
50+
- M2-124-UM: Network MIDI 2.0 UDP Transport

src/NetworkMIDI2_Config.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @file NetworkMIDI2_Config.h
3+
* @brief Enable Network MIDI 2.0 / UMP support
4+
*
5+
* Implementation is parallel to AppleMIDI - no dependency on RTP-MIDI.
6+
*
7+
* To enable: uncomment the define below, or add -DNETWORK_MIDI_2_SUPPORT
8+
* to your compiler flags.
9+
*/
10+
11+
#ifndef NETWORK_MIDI_2_CONFIG_H
12+
#define NETWORK_MIDI_2_CONFIG_H
13+
14+
/* #define NETWORK_MIDI_2_SUPPORT */
15+
16+
#endif

src/ump_Convert.cpp

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/**
2+
* @file ump_Convert.cpp
3+
* @brief MIDI 1.0 <-> UMP conversion (standalone, no AppleMIDI dependency)
4+
*/
5+
6+
#include "NetworkMIDI2_Config.h"
7+
8+
#if defined(NETWORK_MIDI_2_SUPPORT)
9+
10+
#include "ump_Convert.h"
11+
12+
#define MIDI1_NOTE_OFF 0x80
13+
#define MIDI1_NOTE_ON 0x90
14+
#define MIDI1_POLY_PRESSURE 0xA0
15+
#define MIDI1_CONTROL_CHANGE 0xB0
16+
#define MIDI1_PROGRAM_CHANGE 0xC0
17+
#define MIDI1_CHANNEL_PRESSURE 0xD0
18+
#define MIDI1_PITCH_BEND 0xE0
19+
#define MIDI1_SYSEX_START 0xF0
20+
#define MIDI1_SYSEX_END 0xF7
21+
#define MIDI1_REALTIME_MIN 0xF8
22+
23+
#define STATE_IDLE 0
24+
#define STATE_HAVE_STATUS 1
25+
#define STATE_HAVE_DATA1 2
26+
27+
namespace appleMidi {
28+
namespace ump {
29+
30+
static uint8_t midi1_status_nibble(uint8_t status) {
31+
return (status >> 4) & 0x0F;
32+
}
33+
static uint8_t midi1_channel(uint8_t status) {
34+
return status & 0x0F;
35+
}
36+
static bool is_channel_voice(uint8_t status) {
37+
uint8_t n = midi1_status_nibble(status);
38+
return (n >= 0x8 && n <= 0xE);
39+
}
40+
static uint8_t channel_voice_data_bytes(uint8_t status_nibble) {
41+
switch (status_nibble) {
42+
case UMP_MIDI1_NOTE_OFF:
43+
case UMP_MIDI1_NOTE_ON:
44+
case UMP_MIDI1_POLY_PRESSURE:
45+
case UMP_MIDI1_CONTROL_CHANGE:
46+
case UMP_MIDI1_PITCH_BEND:
47+
return 2;
48+
case UMP_MIDI1_PROGRAM_CHANGE:
49+
case UMP_MIDI1_CHANNEL_PRESSURE:
50+
return 1;
51+
default:
52+
return 0;
53+
}
54+
}
55+
56+
void ump_parser_init(ump_bytestream_parser_t *p) {
57+
if (p) {
58+
p->state = STATE_IDLE;
59+
p->running_status = 0;
60+
p->data_count = 0;
61+
}
62+
}
63+
64+
ump_convert_result_t ump_byte_to_ump(ump_bytestream_parser_t *p, uint8_t byte,
65+
uint32_t *ump, uint8_t group) {
66+
if (!p || !ump) return UMP_CONVERT_ERROR;
67+
68+
if (byte >= MIDI1_REALTIME_MIN) {
69+
if (byte == 0xFE) return UMP_CONVERT_SKIP;
70+
*ump = (UMP_MT_SYSTEM_REALTIME << UMP_MT_SHIFT) | ((uint32_t)byte << 16);
71+
return UMP_CONVERT_OK;
72+
}
73+
74+
if (byte >= MIDI1_SYSEX_START) {
75+
p->running_status = 0;
76+
p->state = STATE_IDLE;
77+
return UMP_CONVERT_SKIP;
78+
}
79+
80+
if (byte & 0x80) {
81+
if (!is_channel_voice(byte)) {
82+
p->running_status = 0;
83+
p->state = STATE_IDLE;
84+
return UMP_CONVERT_SKIP;
85+
}
86+
p->running_status = byte;
87+
p->data_count = 0;
88+
p->state = STATE_HAVE_STATUS;
89+
return UMP_CONVERT_NEED_MORE;
90+
}
91+
92+
if (p->state == STATE_IDLE && p->running_status) {
93+
p->state = STATE_HAVE_STATUS;
94+
p->data_count = 0;
95+
}
96+
97+
if (p->state != STATE_HAVE_STATUS && p->state != STATE_HAVE_DATA1) {
98+
p->state = STATE_IDLE;
99+
return UMP_CONVERT_ERROR;
100+
}
101+
102+
uint8_t status_nibble = midi1_status_nibble(p->running_status);
103+
uint8_t n_data = channel_voice_data_bytes(status_nibble);
104+
105+
if (n_data == 0) {
106+
p->state = STATE_IDLE;
107+
return UMP_CONVERT_ERROR;
108+
}
109+
110+
p->data[p->data_count++] = byte;
111+
112+
if (p->data_count < n_data) {
113+
p->state = STATE_HAVE_DATA1;
114+
return UMP_CONVERT_NEED_MORE;
115+
}
116+
117+
*ump = (UMP_MT_MIDI1_CV << UMP_MT_SHIFT)
118+
| ((uint32_t)(group & 0x0F) << UMP_GROUP_SHIFT)
119+
| ((uint32_t)(status_nibble & 0x0F) << UMP_MT2_STATUS_SHIFT)
120+
| ((uint32_t)(midi1_channel(p->running_status) & 0x0F) << UMP_MT2_CHANNEL_SHIFT)
121+
| ((uint32_t)p->data[0] << UMP_MT2_DATA1_SHIFT);
122+
if (n_data == 2) *ump |= (p->data[1] & 0xFF);
123+
124+
p->state = STATE_IDLE;
125+
return UMP_CONVERT_OK;
126+
}
127+
128+
bool ump_has_second_word(const ump_bytestream_parser_t *p) {
129+
(void)p;
130+
return false;
131+
}
132+
133+
uint32_t ump_build_midi1_channel_voice(uint8_t status, uint8_t data1, uint8_t data2,
134+
uint8_t group) {
135+
if (!is_channel_voice(status)) return 0;
136+
uint8_t status_nibble = midi1_status_nibble(status);
137+
uint8_t channel = midi1_channel(status);
138+
uint8_t n_data = channel_voice_data_bytes(status_nibble);
139+
140+
uint32_t ump = (UMP_MT_MIDI1_CV << UMP_MT_SHIFT)
141+
| ((uint32_t)(group & 0x0F) << UMP_GROUP_SHIFT)
142+
| ((uint32_t)(status_nibble & 0x0F) << UMP_MT2_STATUS_SHIFT)
143+
| ((uint32_t)(channel & 0x0F) << UMP_MT2_CHANNEL_SHIFT)
144+
| ((uint32_t)data1 << UMP_MT2_DATA1_SHIFT);
145+
if (n_data >= 2) ump |= (data2 & 0xFF);
146+
return ump;
147+
}
148+
149+
int ump_to_bytes(uint32_t ump, uint8_t *bytes, size_t maxlen) {
150+
if (!bytes || maxlen < 1) return -1;
151+
152+
uint8_t mt = (ump >> UMP_MT_SHIFT) & 0x0F;
153+
154+
if (mt == UMP_MT_SYSTEM_REALTIME) {
155+
bytes[0] = (ump >> 16) & 0xFF;
156+
return 1;
157+
}
158+
159+
if (mt == UMP_MT_MIDI1_CV) {
160+
uint8_t status_nibble = (ump >> UMP_MT2_STATUS_SHIFT) & 0x0F;
161+
uint8_t channel = (ump >> UMP_MT2_CHANNEL_SHIFT) & 0x0F;
162+
uint8_t data1 = (ump >> UMP_MT2_DATA1_SHIFT) & 0xFF;
163+
uint8_t data2 = ump & 0xFF;
164+
165+
uint8_t status = (status_nibble << 4) | channel;
166+
uint8_t n_data = channel_voice_data_bytes(status_nibble);
167+
168+
if (maxlen < (size_t)(1 + n_data)) return -1;
169+
170+
bytes[0] = status;
171+
bytes[1] = data1;
172+
if (n_data == 2) {
173+
bytes[2] = data2;
174+
return 3;
175+
}
176+
return 2;
177+
}
178+
179+
return -1;
180+
}
181+
182+
bool ump_is_multi_word_start(uint32_t ump) {
183+
uint8_t mt = (ump >> UMP_MT_SHIFT) & 0x0F;
184+
return (mt == UMP_MT_DATA || mt == UMP_MT_SYSEX7 || mt == UMP_MT_SYSEX8_MDS);
185+
}
186+
187+
} /* namespace ump */
188+
} /* namespace appleMidi */
189+
190+
#endif /* NETWORK_MIDI_2_SUPPORT */

src/ump_Convert.h

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#pragma once
2+
3+
#if defined(NETWORK_MIDI_2_SUPPORT)
4+
5+
/**
6+
* @file ump_Convert.h
7+
* @brief MIDI 1.0 byte stream <-> Universal MIDI Packet (UMP) conversion
8+
*
9+
* Standalone - no dependency on AppleMIDI. For Network MIDI 2.0 transport.
10+
*
11+
* Supports:
12+
* - MT 2: MIDI 1.0 Channel Voice (Note On/Off, CC, PC, etc.)
13+
* - MT 1: System Real-Time (Timing Clock, Start, Stop, etc.)
14+
*/
15+
16+
#include "ump_Defs.h"
17+
#include <stddef.h>
18+
#include <stdint.h>
19+
#include <stdbool.h>
20+
21+
#ifdef __cplusplus
22+
namespace appleMidi {
23+
namespace ump {
24+
#endif
25+
26+
/* ============================================================================
27+
* Byte stream -> UMP conversion (stateful parser)
28+
* ============================================================================ */
29+
30+
typedef enum {
31+
UMP_CONVERT_NEED_MORE,
32+
UMP_CONVERT_OK,
33+
UMP_CONVERT_SKIP,
34+
UMP_CONVERT_ERROR
35+
} ump_convert_result_t;
36+
37+
typedef struct {
38+
uint8_t state;
39+
uint8_t running_status;
40+
uint8_t data_count;
41+
uint8_t reserved;
42+
uint8_t data[2];
43+
} ump_bytestream_parser_t;
44+
45+
void ump_parser_init(ump_bytestream_parser_t *p);
46+
47+
ump_convert_result_t ump_byte_to_ump(ump_bytestream_parser_t *p, uint8_t byte,
48+
uint32_t *ump, uint8_t group);
49+
50+
bool ump_has_second_word(const ump_bytestream_parser_t *p);
51+
52+
/**
53+
* Build UMP from complete MIDI 1.0 channel voice message.
54+
* @param status MIDI status byte (0x80-0xEF)
55+
* @param data1 First data byte
56+
* @param data2 Second data byte (0 for PC, Channel Pressure)
57+
* @param group UMP Group (0-15)
58+
*/
59+
uint32_t ump_build_midi1_channel_voice(uint8_t status, uint8_t data1, uint8_t data2,
60+
uint8_t group);
61+
62+
/* ============================================================================
63+
* UMP -> Byte stream conversion
64+
* ============================================================================ */
65+
66+
int ump_to_bytes(uint32_t ump, uint8_t *bytes, size_t maxlen);
67+
68+
bool ump_is_multi_word_start(uint32_t ump);
69+
70+
#ifdef __cplusplus
71+
} /* namespace ump */
72+
} /* namespace appleMidi */
73+
#endif
74+
75+
#endif /* NETWORK_MIDI_2_SUPPORT */

0 commit comments

Comments
 (0)