-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAudioLink.h
More file actions
129 lines (110 loc) · 5.13 KB
/
AudioLink.h
File metadata and controls
129 lines (110 loc) · 5.13 KB
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
#pragma once
#include <stdint.h>
// ===========================================================================
// AudioLink - shared wireless protocol between the StickS3 (wireless mic) and
// the Core2 (loop deck). Transport is ESP-NOW; one packet carries one 20 ms
// audio frame as IMA-ADPCM (4:1). Header-only so both firmwares share it.
// ===========================================================================
static constexpr uint8_t LINK_MAGIC = 0x5A;
static constexpr uint8_t LINK_VER = 1;
static constexpr uint32_t SAMPLE_RATE_HZ = 16000; // must match Core2 SAMPLE_RATE
static constexpr uint16_t FRAME_SAMPLES = 320; // 20 ms @ 16 kHz mono
static constexpr uint16_t FRAME_BYTES = FRAME_SAMPLES / 2; // 4 bit/sample
static constexpr uint8_t LINK_CHANNEL = 1; // fixed Wi-Fi channel both sides
enum LinkType : uint8_t {
LT_AUDIO = 1, // StickS3 -> Core2
LT_STATUS = 2, // Core2 -> StickS3
};
enum LinkFlags : uint8_t {
LF_RECORDING = 1 << 0, // KEY1 down on the stick / remote-record active
};
// Audio frame. Each packet is self-contained: it stores the ADPCM start state
// so a lost packet never corrupts the ones that follow.
struct __attribute__((packed)) AudioPacket {
uint8_t magic; // LINK_MAGIC
uint8_t ver; // LINK_VER
uint8_t type; // LT_AUDIO
uint8_t flags; // LinkFlags
uint16_t seq; // sequence number for loss detection
int16_t predictor; // ADPCM predictor at frame start
uint8_t stepIndex; // ADPCM step index at frame start
uint8_t pad; // alignment
uint8_t data[FRAME_BYTES]; // 160 bytes
}; // = 168 bytes (< 250 B ESP-NOW limit)
// Status / heartbeat from the Core2 so the stick can mirror the deck state.
struct __attribute__((packed)) StatusPacket {
uint8_t magic; // LINK_MAGIC
uint8_t ver; // LINK_VER
uint8_t type; // LT_STATUS
uint8_t deckState; // 0=idle 1=rec 2=play
uint8_t selTrack; // 0..3
uint8_t flags; // LinkFlags (bit0 = remote-record active)
};
// ---------------------------------------------------------------------------
// IMA ADPCM (standard tables). Stateless per frame via stored start state.
// ---------------------------------------------------------------------------
static const int16_t kAdpcmStep[89] = {
7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21,
23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66,
73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209,
230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658,
724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
7132, 7845, 8630, 9493, 10442,11487,12635,13899,15289,16818,18500,20350,
22385,24623,27086,29794,32767};
static const int8_t kAdpcmIndex[16] = {
-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8};
static inline int16_t adpcm_clampSample(int32_t v) {
if (v > 32767) return 32767;
if (v < -32768) return -32768;
return (int16_t)v;
}
static inline int8_t adpcm_clampIndex(int v) {
if (v < 0) return 0;
if (v > 88) return 88;
return (int8_t)v;
}
// Encode FRAME_SAMPLES int16 -> FRAME_BYTES nibbles. predictor/index carry the
// running state in/out; caller stores the *entry* state into the packet first.
static inline void adpcm_encodeFrame(const int16_t* in, uint8_t* out,
int16_t& predictor, int8_t& index) {
int32_t pred = predictor;
int8_t idx = index;
for (uint16_t n = 0; n < FRAME_SAMPLES; ++n) {
int32_t step = kAdpcmStep[idx];
int32_t diff = (int32_t)in[n] - pred;
uint8_t code = 0;
if (diff < 0) { code = 8; diff = -diff; }
int32_t vpdiff = step >> 3;
if (diff >= step) { code |= 4; diff -= step; vpdiff += step; }
step >>= 1;
if (diff >= step) { code |= 2; diff -= step; vpdiff += step; }
step >>= 1;
if (diff >= step) { code |= 1; vpdiff += step; }
pred = (code & 8) ? pred - vpdiff : pred + vpdiff;
pred = adpcm_clampSample(pred);
idx = adpcm_clampIndex(idx + kAdpcmIndex[code]);
if (n & 1) out[n >> 1] |= (code << 4); // high nibble = odd sample
else out[n >> 1] = code; // low nibble = even sample
}
predictor = (int16_t)pred;
index = idx;
}
// Decode using the packet's stored start state (self-contained per frame).
static inline void adpcm_decodeFrame(const uint8_t* in, int16_t* out,
int16_t predictor, int8_t index) {
int32_t pred = predictor;
int8_t idx = adpcm_clampIndex(index);
for (uint16_t n = 0; n < FRAME_SAMPLES; ++n) {
uint8_t code = (n & 1) ? (in[n >> 1] >> 4) : (in[n >> 1] & 0x0F);
int32_t step = kAdpcmStep[idx];
int32_t vpdiff = step >> 3;
if (code & 4) vpdiff += step;
if (code & 2) vpdiff += step >> 1;
if (code & 1) vpdiff += step >> 2;
pred = (code & 8) ? pred - vpdiff : pred + vpdiff;
pred = adpcm_clampSample(pred);
idx = adpcm_clampIndex(idx + kAdpcmIndex[code]);
out[n] = (int16_t)pred;
}
}