Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ project(HDZGOGGLE VERSION 1.1)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(COMMON_COMPILER_FLAGS "-Wno-unused-function -Wno-unused-variable -Wno-deprecated-declarations -ffunction-sections -fdata-sections -Wl,-gc-sections -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64")
set(COMMON_COMPILER_FLAGS "-Wno-unused-function -Wno-unused-variable -Wno-deprecated-declarations -ffunction-sections -fdata-sections -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64")
if(NOT APPLE)
set(COMMON_COMPILER_FLAGS "${COMMON_COMPILER_FLAGS} -Wl,-gc-sections")
endif()

option(EMULATOR_BUILD "emulate using SDL on host" OFF)
if(NOT EMULATOR_BUILD)
Expand Down
59 changes: 59 additions & 0 deletions docs/synthetic-motor-audio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Synthetic Motor Audio (Goggle side)

Proof-of-concept: synthesize stereo propeller sound from mechanical RPM carried in the HDZero `0xFF` service packet. Pair with the matching VTX change (same packing). No UI, no hex/octo, no new RF opcodes.

## Packet (VRX view)

`rx_buf[0]` is the payload length. Legacy packets use `15`. Extended packets use `21`:

| Offset | Content |
|--------|---------|
| 0 | length (`21`) |
| 1..15 | legacy service fields (unchanged) |
| 16..21 | six packed 12-bit RPM bytes |

Unpack / dequantize (`q * 32`) lives in `src/core/motor_rpm_proto.*`. Decode runs in `parser_config()` when `length >= 21`; shorter packets leave audio silent.

Packing (must match VTX):

```
q = min((rpm + 16) >> 5, 4095) # 32 RPM steps, max 131040
b0 = q0[7:0]
b1 = q1[3:0] << 4 | q0[11:8]
b2 = q1[11:4]
b3 = q2[7:0]
b4 = q3[3:0] << 4 | q2[11:8]
b5 = q3[11:4]
```

## Audio engine

`src/core/motor_audio.*` — platform-independent stereo PCM at **22050 Hz** (enough for blade-pass + harmonics; half the CPU of 48 kHz).

- QUADX pan: fronts louder, left/right stereo
- Blade-pass pulse, shaft asymmetry, quiet air noise, soft LP
- Stale telemetry (>500 ms) or all-zero RPM fades to silence

## Menu / settings

**Record Option → Motor Audio** (`On` / `Off`), stored as `record.motor_audio` in `setting.ini` (default `Off`).

- Independent of **Record Audio** — this is a live headphone mix, not a DVR flag.
- Keeps the selected **Audio Source** (Mic / Line In / A/V In); when On, line-out also opens the DAC path (`audio_sel.sh out_dac_on`) so synthetic PCM can sit under the analog source.
- Emulator: toggles the SDL playback device. Hardware still needs a Softwinner AO PCM feed into that DAC path (not wired yet); RPM decode and mute gate are ready.

## Host / emulator tooling

Prefer the small host tool (full emulator needs Linux/ARM deps):

```bash
cmake -S utilities/motor_audio_poc -B tmp/motor_audio_poc_build
cmake --build tmp/motor_audio_poc_build
./tmp/motor_audio_poc_build/motor_audio_poc --motor-test
./tmp/motor_audio_poc_build/motor_audio_poc --motor-synthetic --motor-wav tmp/motor_synthetic.wav
./tmp/motor_audio_poc_build/motor_audio_poc --motor-replay path.csv --motor-poles 12 --motor-wav tmp/out.wav
```

Blackbox CSV needs `time` / `time (us)` and `eRPM[0..3]` or `eRPM(/100)[0..3]`. Mechanical RPM = `eRPM * 2 / poles` (with `×100` when the column is `/100`).

Emulator binary also accepts the same `--motor-*` flags when built with `EMULATOR_BUILD`.
6 changes: 5 additions & 1 deletion src/core/dvr.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ void dvr_enable_line_out(bool enable) {
system_exec(buf);
snprintf(buf, sizeof(buf), "%s out_linein_on", AUDIO_SEL_SH);
system_exec(buf);
snprintf(buf, sizeof(buf), "%s out_dac_off", AUDIO_SEL_SH);
/* Keep selected analog source; open DAC only when motor audio mix is on. */
if (g_setting.record.motor_audio)
snprintf(buf, sizeof(buf), "%s out_dac_on", AUDIO_SEL_SH);
else
snprintf(buf, sizeof(buf), "%s out_dac_off", AUDIO_SEL_SH);
system_exec(buf);
} else {
snprintf(buf, sizeof(buf), "%s out_off", AUDIO_SEL_SH);
Expand Down
13 changes: 13 additions & 0 deletions src/core/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

#ifdef EMULATOR_BUILD
#include "SDLaccess.h"
#include "emulator/motor_audio_dev.h"
#include "emulator/motor_audio_sdl.h"
SDL_mutex *global_sdl_mutex;
#endif

Expand All @@ -24,6 +26,7 @@ SDL_mutex *global_sdl_mutex;
#include "core/elrs.h"
#include "core/ht.h"
#include "core/input_device.h"
#include "core/motor_audio.h"
#include "core/osd.h"
#include "core/self_test.h"
#include "core/settings.h"
Expand Down Expand Up @@ -147,6 +150,11 @@ void lvgl_init() {
}

int main(int argc, char *argv[]) {
#ifdef EMULATOR_BUILD
if (motor_audio_dev_main(argc, argv))
return 0;
#endif

pthread_mutex_init(&lvgl_mutex, NULL);

#ifdef EMULATOR_BUILD
Expand Down Expand Up @@ -184,6 +192,11 @@ int main(int argc, char *argv[]) {
elrs_init();
ht_init();
beep_init();
motor_audio_set_enabled(g_setting.record.motor_audio ? 1 : 0);
#ifdef EMULATOR_BUILD
if (g_setting.record.motor_audio)
motor_audio_sdl_start(MOTOR_AUDIO_DEFAULT_RATE);
#endif

// 4. Initilize UI
lvgl_init();
Expand Down
Loading
Loading