Skip to content

Commit 1654d33

Browse files
committed
Add Tunes tab.
1 parent a923fdf commit 1654d33

9 files changed

Lines changed: 805 additions & 466 deletions

File tree

SerialPortConnector.pro

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ QT += serialport
44

55
CONFIG += c++11
66

7+
CONFIG += release
8+
9+
CONFIG(release, debug|release) {
10+
CONFIG += static
11+
}
12+
713
# The following define makes your compiler emit warnings if you use
814
# any Qt feature that has been marked deprecated (the exact warnings
915
# depend on your compiler). Please consult the documentation of the
@@ -20,13 +26,15 @@ SOURCES += \
2026
bluejaymelody.cpp \
2127
fourwayif.cpp \
2228
main.cpp \
29+
music.cpp \
2330
widget.cpp
2431

2532
HEADERS += \
2633
BF_ROOTLOADER.h \
2734
bluejaymelody.h \
2835
defaults.h \
2936
fourwayif.h \
37+
music.h \
3038
widget.h
3139

3240
FORMS += \

SerialPortConnector.pro.user

Lines changed: 56 additions & 94 deletions
Large diffs are not rendered by default.

defaults.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ uint8_t air_starteeprom[48] ={
5050
0x66, // current protection level (value x 2) above 100 disables, default 102 (204 degrees) (byte 44)
5151
0x06, // sine mode strength 1-10 default 6 byte 45
5252
//eeprom version 2 or later
53-
0x01, // input type selector 1)Auto 2)Dshot only 3)Servo only 4)PWM 5)Serial 6)BetaFlight Safe Arming
53+
0x00, // input type selector 1)Auto 2)Dshot only 3)Servo only 4)PWM 5)Serial 6)BetaFlight Safe Arming
5454
0x00, // auto_timing
5555
};
5656

fourwayif.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ void FourWayIF::set_Ack_req(char ackreq){
184184
}
185185

186186
bool FourWayIF::ACK_received(){
187-
return ack_received;
187+
188188
}
189189

190190

music.cpp

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
#include "music.h"
2+
#include <string.h>
3+
#include <stdlib.h>
4+
#include <stdio.h>
5+
#include <math.h>
6+
#include <ctype.h>
7+
8+
/* ═══════════════════════════════════════════════════════════════════════
9+
* Frequency table
10+
* Matches Python: freq = round(440 * 2^((midi-69)/12), 1)
11+
* where midi = (octave+1)*12 + semitone
12+
* This is the actual frequency used for BOTH T3 encoding AND pulse counting.
13+
* ═══════════════════════════════════════════════════════════════════════ */
14+
15+
static double note_actual_freq(int semitone, int octave)
16+
{
17+
int midi = (octave + 1) * 12 + semitone;
18+
double freq = 440.0 * pow(2.0, (midi - 69) / 12.0);
19+
/* Round to 1 decimal place to match Python's round(..., 1) */
20+
return round(freq * 10.0) / 10.0;
21+
}
22+
23+
/* ═══════════════════════════════════════════════════════════════════════
24+
* MIDI → T3 lookup table
25+
* Covers MIDI notes 60-107 (C4–B7).
26+
* midi = (octave+1)*12 + semitone, so:
27+
* octave 4: midi 60-71
28+
* octave 5: midi 72-83 ← verified hard-coded values
29+
* octave 6: midi 84-95 ← verified hard-coded values
30+
* octave 7: midi 96-107
31+
* ═══════════════════════════════════════════════════════════════════════ */
32+
33+
#define MIDI_T3_MIN 60
34+
#define MIDI_T3_MAX 107
35+
36+
static uint8_t midi_t3_table[MIDI_T3_MAX - MIDI_T3_MIN + 1];
37+
static int table_built = 0;
38+
39+
40+
static void limit_bluejay_pitch(int *semitone, int *octave)
41+
{
42+
/* Bluejay max frequency ≈ 2290 Hz (T3 = 1) */
43+
const double MAX_FREQ = 2200.0;
44+
45+
double freq = note_actual_freq(*semitone, *octave);
46+
47+
if (freq > MAX_FREQ)
48+
{
49+
/* Drop one octave */
50+
(*octave)--;
51+
52+
/* Safety clamp so we never go below valid range */
53+
if (*octave < 4)
54+
*octave = 4;
55+
}
56+
}
57+
58+
59+
60+
static uint8_t midi_to_t3_precise(int midi)
61+
{
62+
/* exact frequency from MIDI note */
63+
double freq = 440.0 * pow(2.0, (midi - 69) / 12.0);
64+
65+
/* Bluejay timing formula */
66+
double t3f = 41000.0 / freq - 16.9;
67+
68+
int t3 = (int)lround(t3f);
69+
70+
if (t3 < 1) t3 = 1;
71+
if (t3 > 255) t3 = 255;
72+
73+
return (uint8_t)t3;
74+
}
75+
76+
77+
static void build_midi_t3_table(void)
78+
{
79+
if (table_built) return;
80+
81+
static const uint8_t oct5[12] = { 61,57,53,49,45,42,39,35,32,30,27,25 };
82+
static const uint8_t oct6[12] = { 23,20,18,16,15,12,11,10,8,7,5,4 };
83+
84+
for (int midi = MIDI_T3_MIN; midi <= MIDI_T3_MAX; midi++)
85+
{
86+
int semitone = midi % 12;
87+
88+
if (midi >= 72 && midi <= 83)
89+
midi_t3_table[midi - MIDI_T3_MIN] = oct5[semitone];
90+
91+
else if (midi >= 84 && midi <= 95)
92+
midi_t3_table[midi - MIDI_T3_MIN] = oct6[semitone];
93+
94+
else
95+
midi_t3_table[midi - MIDI_T3_MIN] = midi_to_t3_precise(midi);
96+
}
97+
98+
table_built = 1;
99+
}
100+
101+
/* semitone 0=C..11=B, octave 4-7 → T3 byte */
102+
static uint8_t note_to_t3(int semitone, int octave)
103+
{
104+
build_midi_t3_table();
105+
int midi = (octave + 1) * 12 + semitone;
106+
if (midi >= MIDI_T3_MIN && midi <= MIDI_T3_MAX)
107+
return midi_t3_table[midi - MIDI_T3_MIN];
108+
/* Fallback for out-of-range octaves */
109+
return midi_to_t3_precise(note_actual_freq(semitone, octave));
110+
}
111+
112+
/* ═══════════════════════════════════════════════════════════════════════
113+
* Note name parsing
114+
* ═══════════════════════════════════════════════════════════════════════ */
115+
116+
static const char *NOTE_NAMES[12] = {
117+
"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"
118+
};
119+
120+
static const struct { const char *flat; const char *sharp; } FLAT_MAP[] = {
121+
{"DB","C#"}, {"EB","D#"}, {"FB","E"}, {"GB","F#"},
122+
{"AB","G#"}, {"BB","A#"}, {"CB","B"}, {NULL, NULL}
123+
};
124+
125+
/* Advances *p past the note name. Returns semitone 0-11, or -1 on failure. */
126+
static int parse_note_name(const char **p)
127+
{
128+
char name[4] = {0};
129+
int len = 0;
130+
131+
if (!isalpha((unsigned char)**p)) return -1;
132+
name[len++] = (char)toupper((unsigned char)**p);
133+
(*p)++;
134+
135+
if (**p == '#' || **p == 'b') {
136+
name[len++] = (char)toupper((unsigned char)**p);
137+
(*p)++;
138+
}
139+
140+
for (int i = 0; FLAT_MAP[i].flat; i++)
141+
if (strcmp(name, FLAT_MAP[i].flat) == 0)
142+
{ strcpy(name, FLAT_MAP[i].sharp); break; }
143+
144+
for (int i = 0; i < 12; i++)
145+
if (strcmp(name, NOTE_NAMES[i]) == 0) return i;
146+
147+
return -1;
148+
}
149+
150+
/* ═══════════════════════════════════════════════════════════════════════
151+
* Duration string → divisor integer
152+
* Accepts "8", "4", "1/8", "1/4" etc.
153+
* ═══════════════════════════════════════════════════════════════════════ */
154+
static int parse_duration_str(const char *s)
155+
{
156+
const char *slash = strchr(s, '/');
157+
if (slash)
158+
return atoi(slash + 1); /* "1/8" → 8 */
159+
return atoi(s); /* "8" → 8 */
160+
}
161+
162+
/* ═══════════════════════════════════════════════════════════════════════
163+
* Internal encoder — operates on const char*
164+
* ═══════════════════════════════════════════════════════════════════════ */
165+
166+
static int encode_notation(const char *notation, int bpm, uint8_t out[BLUEJAY_ARRAY_SIZE])
167+
{
168+
memset(out, 0, BLUEJAY_ARRAY_SIZE);
169+
170+
/* Tokenise — work on a mutable copy */
171+
char buf[2048];
172+
strncpy(buf, notation, sizeof(buf) - 1);
173+
buf[sizeof(buf) - 1] = '\0';
174+
for (char *c = buf; *c; c++) if (*c == ',') *c = ' ';
175+
176+
char *raw_toks[512];
177+
int ntok = 0;
178+
char *tok = strtok(buf, " \t\r\n");
179+
while (tok && ntok < (int)(sizeof(raw_toks)/sizeof(raw_toks[0]))) {
180+
raw_toks[ntok++] = tok;
181+
tok = strtok(NULL, " \t\r\n");
182+
}
183+
184+
/* Parse tokens into events */
185+
typedef struct { int semitone; int octave; int dur_div; } Event;
186+
Event events[512];
187+
int nevents = 0;
188+
189+
int dur_counts[33] = {0};
190+
int oct_counts[9] = {0};
191+
192+
int i = 0;
193+
while (i < ntok) {
194+
const char *t = raw_toks[i];
195+
196+
if (t[0] == 'P' || t[0] == 'p') {
197+
int d = parse_duration_str(t + 1);
198+
if (d <= 0) return -1;
199+
events[nevents].semitone = -1;
200+
events[nevents].octave = 0;
201+
events[nevents].dur_div = d;
202+
nevents++;
203+
if (d <= 32) dur_counts[d]++;
204+
i++;
205+
} else {
206+
const char *p = t;
207+
int semi = parse_note_name(&p);
208+
if (semi < 0) return -1;
209+
210+
int octave = 5;
211+
if (*p >= '4' && *p <= '7') { octave = *p - '0'; p++; }
212+
213+
if (i + 1 >= ntok) return -1;
214+
int d = parse_duration_str(raw_toks[i + 1]);
215+
if (d <= 0) return -1;
216+
217+
events[nevents].semitone = semi;
218+
events[nevents].octave = octave;
219+
events[nevents].dur_div = d;
220+
nevents++;
221+
if (d <= 32) dur_counts[d]++;
222+
if (octave >= 0 && octave <= 8) oct_counts[octave]++;
223+
i += 2;
224+
}
225+
}
226+
227+
if (nevents == 0) return -1;
228+
229+
/* Determine default duration and octave (most common) */
230+
int default_dur = 4, best_dur_cnt = 0;
231+
for (int d = 1; d <= 32; d++)
232+
if (dur_counts[d] > best_dur_cnt) { best_dur_cnt = dur_counts[d]; default_dur = d; }
233+
234+
int default_oct = 5, best_oct_cnt = 0;
235+
for (int o = 4; o <= 7; o++)
236+
if (oct_counts[o] > best_oct_cnt) { best_oct_cnt = oct_counts[o]; default_oct = o; }
237+
238+
/* Build (T4, T3) pairs */
239+
double ms_per_beat = 60000.0 / bpm;
240+
uint8_t pairs[BLUEJAY_MAX_PAIRS * 2];
241+
int npairs = 0;
242+
243+
for (int e = 0; e < nevents && npairs < BLUEJAY_MAX_PAIRS; e++) {
244+
double dur_ms = ms_per_beat * 4.0 / events[e].dur_div;
245+
246+
if (events[e].semitone < 0) {
247+
/* Pause: T3=0, T4=ms clamped 1-255 */
248+
int t4 = (int)round(dur_ms);
249+
if (t4 < 1) t4 = 1;
250+
if (t4 > 255) t4 = 255;
251+
pairs[npairs * 2] = (uint8_t)t4;
252+
pairs[npairs * 2 + 1] = 0;
253+
npairs++;
254+
} else {
255+
int semi = events[e].semitone;
256+
int oct = events[e].octave;
257+
258+
/* Automatically lower notes above C#7 */
259+
limit_bluejay_pitch(&semi, &oct);
260+
261+
uint8_t t3 = note_to_t3(semi, oct);
262+
263+
/*
264+
* FIX: use the actual note frequency for pulse counting,
265+
* NOT the inverse of the T3 byte (41000/(t3+16.9)).
266+
* The T3-inverse introduces rounding error that produces
267+
* wrong pair counts, especially for higher-octave notes.
268+
* This matches the Python reference tool exactly.
269+
*/
270+
double actual_freq = note_actual_freq(semi, oct);
271+
// double actual_freq = note_actual_freq(events[e].semitone, events[e].octave);
272+
double pulses_remaining = dur_ms / 1000.0 * actual_freq;
273+
274+
while (pulses_remaining > 0.5 && npairs < BLUEJAY_MAX_PAIRS) {
275+
int chunk = (pulses_remaining >= 255.0)
276+
? 255
277+
: (int)round(pulses_remaining);
278+
if (chunk < 1) chunk = 1;
279+
pairs[npairs * 2] = (uint8_t)chunk;
280+
pairs[npairs * 2 + 1] = t3;
281+
npairs++;
282+
pulses_remaining -= chunk;
283+
}
284+
}
285+
}
286+
287+
/* Write 128-byte header + pairs */
288+
out[0] = 0;
289+
out[1] = (uint8_t)(bpm > 255 ? 255 : bpm);
290+
out[2] = (uint8_t)default_oct;
291+
out[3] = (uint8_t)default_dur;
292+
for (int p = 0; p < npairs; p++) {
293+
out[4 + p * 2] = pairs[p * 2];
294+
out[4 + p * 2 + 1] = pairs[p * 2 + 1];
295+
}
296+
297+
return npairs;
298+
}
299+
300+
301+
int blheli32_to_bluejay_array(const QString &notation, int bpm, uint8_t out[BLUEJAY_ARRAY_SIZE])
302+
{
303+
QByteArray utf8 = notation.toUtf8();
304+
return encode_notation(utf8.constData(), bpm, out);
305+
}
306+
307+
void bluejay_array_to_c_literal(const uint8_t arr[BLUEJAY_ARRAY_SIZE],
308+
const char *var_name)
309+
{
310+
printf("uint8_t %s[%d] = {\n", var_name, BLUEJAY_ARRAY_SIZE);
311+
printf(" /* [0] reserved */ 0x%02X,\n", arr[0]);
312+
printf(" /* [1] BPM */ 0x%02X, // %d BPM\n", arr[1], arr[1]);
313+
printf(" /* [2] def oct */ 0x%02X, // octave %d\n", arr[2], arr[2]);
314+
printf(" /* [3] def dur */ 0x%02X, // 1/%d\n", arr[3], arr[3]);
315+
printf("\n /* [4..127] (T4, T3) note pairs */\n");
316+
for (int i = 4; i < BLUEJAY_ARRAY_SIZE; i += 2) {
317+
uint8_t t4 = arr[i], t3 = arr[i + 1];
318+
if (t4 == 0 && t3 == 0)
319+
printf(" 0x00, 0x00, // (unused)\n");
320+
else if (t3 == 0)
321+
printf(" 0x%02X, 0x00, // pause %d ms\n", t4, t4);
322+
else
323+
printf(" 0x%02X, 0x%02X, // T3=0x%02X (~%.0f Hz), %d pulses\n",
324+
t4, t3, t3, 41000.0 / (t3 + 16.9), t4);
325+
}
326+
printf("};\n");
327+
}
328+

0 commit comments

Comments
 (0)