HUB MOON v1.2.0
v1.2.0A packaged build could not start a single system program. It can now.

Eight bands.
Straight to the silicon.

moondrop_control.py reads and writes the parametric EQ, pre-gain and DAC offset on a Moondrop USB DAC's own DSP, over USB HID. No browser, no account, no vendor app — and a native desktop front-end when you want a curve to drag.

The app
FIG 01 Editor · DAWN PRO2 · firmware 1.5 · live from the device
The Hub Moon editor: a control row with device slot, pre-gain and global offset; eight preset pills; a response graph with tinted frequency regions and eight numbered handles; and eight band cards below it
8
bands, on the DAC
11
devices listed
59,700
community curves
2
CLI dependencies
01

The command line is the tool.

Everything else here is built on one file. The desktop app imports it and calls its engine rather than reimplementing the protocol, so anything the window writes went through the same validation the shell does.

Discovery
$ python3 moondrop_control.py --list
Connected Moondrop Devices:
  - DAWN PRO2 [Vendor ID: 0x35D8, Product ID: 0x011D] (Path: /dev/hidraw2)

$ python3 moondrop_control.py --info
Device Name:       DAWN PRO2
Firmware Version:  1.5
Active EQ Profile: 9
Pre-Gain:          -4.80 dB
Global Gain:       0.00 dB
PEQ Bands:         8
One band · INDEX TYPE FREQ GAIN Q
$ python3 moondrop_control.py --set-peq 0 peaking 1000 -3.0 1.0

# audition it live, then decide to keep it
$ python3 moondrop_control.py --set-peq 0 peaking 1000 -3.0 1.0 --no-flash
$ python3 moondrop_control.py --save-flash

# it writes to flash. back the device up first.
$ python3 moondrop_control.py --export-json profile.json
FlagDoesOpens the DAC
--list · --infoConnected devices; firmware, profile and both gainsyes
--get-peqDump every PEQ slot as it currently reads backyes
--set-peqOne band: index, filter type, frequency, gain, Qyes
--set-pregainHeadroom, in dByes
--export-json · --import-jsonA full device snapshot, out and backyes
--import-rewAutoEQ / REW ParametricEQ.txt, preamp honouredyes
--jsonFull device state on stdout, for front-endsyes
--registryThis file's device table, as JSONno
--presets · --presetBrowse the community library; pull one curve downno
-iInteractive tuning panel, in the terminalyes
Why one column says no

Only one process can usefully hold the hidraw at a time — two readers pick up each other's replies. So anything passive (a tray icon, a status pill, a preset browser) must not be the second one. --registry, --presets and --preset touch no hardware at all: strace-verified, zero /dev/hidraw opens. Every flag and its output is in the manual.

02

One screen. Everything on it.

A native window, not a web view. Install it and open it like any other app — from your launcher, or the Start menu, or Applications. The command line is there if you want it, and it is the same program either way; you do not need it to use Hub Moon. Device slot, pre-gain and global offset; eight presets; the response graph; all eight bands; and the actions, with nothing behind a tab.

FIG 02 Band strip · one card per band · region, type, fader, steppers
The band strip: eight cards, each with its frequency region, a filter type button, a vertical gain fader and frequency and Q steppers

Drag the curve

Each band has a numbered handle on the plot. Drag it to move that band; scroll anywhere on the plot to widen or narrow the one you picked. The graph is the control — the card below it follows.

Live, then saved

Edits go straight to the DSP so you hear them as you tune. Only save to flash keeps them. Revert restores the last saved state, which a re-read cannot do: the DSP only ever reports what was written to it last.

Written on release

Every readout is live while you drag, but the hidraw is only touched when you let go — a HID write per mouse-move would queue hundreds behind the one that matters.

FIG 03Readout view
The readout view: one output curve on a dashed grid, normalised to a 60 dB reference
FIG 04Community preview
A community config open in a preview dialog, showing its response curve, band count and derived pre-gain before anything is applied

Two graph views: the editor draws the flat reference, the equalised curve and a dashed + pre-gain (output) curve — what actually leaves the DAC once headroom is paid. The readout drops the handles for the vendor chart's framing.

FIG 05Built-in tuning guide
The built-in tuning guide, explaining each frequency region and how to use pre-gain
FIG 06First run
The welcome screen: a crescent mark, the device that was found, and four cards explaining the interactions
Optional extra

The toolkit is Slint, lazy-imported and installed only if you ask for it — the plain CLI keeps its two-dependency footprint. No fonts ship and none have to be found: the icons are vector outlines generated into the interface. With no DAC present it opens on a demo curve, so the app can be looked at without hardware.

03

Two themes. Six accents.

Its own colours, not the vendor's near-black and blue. Dark is re-picked rather than inverted — every value chosen twice. The switch at the top of this page changes the page and every screenshot on it, so you are looking at the real thing in both modes.

FIG 07 Appearance · saved to ~/.config/hub-moon/settings.json
The appearance sheet: light and dark, six accent swatches, and the graph view toggle
The graph in the rose accent
Rose#e08aa6
The graph in the ember accent
Ember#ef8a72
The graph in the amber accent
Amber#dfb057
The graph in the moss accent
Moss#74c48d
The graph in the ocean accent
Ocean#6db6dc
The graph in the iris accent
Iris#b092e6

The hue drives the primary action, the active state and the equalised curve. The reference and output traces never move — those are learned colours, and nobody should have to relearn a graph because they liked the green.

04

Everything 1.1.0 and 1.2.0 added.

All of this spent time on the beta channel and is now on stable — you get it by downloading Hub Moon, with nothing to switch on. The changelog marks every release with the channel it was on.

stable

AutoEQ, in the app

8,827 corrections covering 6,015 headphones from 23 measurement sources, searchable, two clicks to apply. Every row names the rig it was measured on — six people have measured the HD 600 and they do not agree, so collapsing that to one "best" answer would be inventing a verdict this project has no business holding.

stable

A/B, held not toggled

Hold compare to hear the headphone with none of your tuning. Pre-gain goes to 0 for the duration, so both sides peak in the same place — leaving it in would compare your curve against a signal several decibels quieter, and louder always wins a blind comparison.

stable

Mute, solo and undo

Hover a band's slot number to mute it and hear what it was doing; solo asks the faster question, "what is this one doing". Undo is separate from revert — one step back rather than all the way to what flash holds — and a fader drag is one step, not the thirty frames it was made of.

stable

Saved profiles

Name the curve on screen and keep it, with no limit. The DAC's own slots live in its flash, are finite, and travel with the hardware instead of with you. A profile is byte-identical to an export, so one can be mailed to somebody and imported.

stable

Four palettes

Moon, Paper, Slate and Carbon — the surfaces and the ink, which is nearly the whole screen, and independent of the six accents. Each has a light and a dark half picked separately rather than inverted.

stable

A guide with the shapes in it

Eight topics, each opening on a diagram drawn by the same response engine the editor plots with — three Q values at one gain, a shelf against a peak, what pre-gain costs. And a walkthrough that rings the real controls instead of describing them.

stable

It updates itself

One small manifest a day. On Windows, macOS, the AppImage and the tarball it downloads, checks the SHA-256 and replaces itself. On a .deb, .rpm or Arch package it fetches and verifies the right one and asks your desktop for permission — the password goes to the system, never through the app.

stable

Keyboard editing

18 picks a band, arrows move gain and frequency, [ ] change Q, 0 flattens, m mutes, s solos, space held bypasses. For a tool you use while listening, most of the work was still on the mouse.

FIG 09 Headphone corrections · 8,827 for 6,015 headphones · 1.2.0, photographed from an installed build
The headphone corrections sheet in Hub Moon 1.2.0: a search box over 6,015 headphones and a list of models, each with its measurer and rig underneath

Beta and stable are built by the same three CI runners from the same commit, with the same signed-by-checksum assets. What differs is how long they have been in front of people.

05

2.0 is on the beta channel now.

2.0.0b1 is out, and it wants testing. It is built for every platform and on the beta channel now — not on stable, which stays on 1.2.0. Everything below was run against real hardware here; Windows and macOS are built by CI and tried by whoever volunteers. 2.0 also changes the settings format, so it is worth switching a machine you can afford to have opinions about.

2.0 is about discovery: finding a curve worth having, and knowing what you are listening to once you have it.

2.0.0b1 · beta

One library, four sources

Saved profiles, the ones you applied recently, the community index and the headphone corrections were four separate full-screen panels that were each the same screen: a searchable list of curves, a preview, apply. They become one Library with the source as a tab — yours first, then everybody else's.

2.0.0b1 · beta

Filter by what a curve does

Of the 9,243 configs a DAWN PRO2 query returns, 25 carry a tag. So the label comes from the curve instead: the bands are summed into a response and read as a tilt against the mids — Neutral, Bass boost, V-shaped, Warm, Treble-tamed, Bright, Bass-cut. A curve that fits no rule is "Other" rather than being forced into the nearest bucket.

2.0.0b1 · beta

Every card draws its own curve

A shelf of shapes can be read at a glance in a way a shelf of titles called "V2 final" cannot. It matters more than it sounds: most of that library is titled in Chinese, and a search over titles is no help to somebody who cannot type them. Searching now matches the shape too, so "bass" finds bass-boosted curves whose authors never wrote the word.

2.0.0b1 · beta

Only the presets for your DAC

The server pools by device family, so a DAWN PRO2 query answers with 9,243 rows of which 1,485 are DAWN PRO2 uploads and 5,518 belong to a FreeDSP Pro. Each row says which it was uploaded for, so telling them apart costs one comparison. On by default, whole family one click away, and the header says what is being hidden either way.

2.0.0b1 · beta

Curves remember where they came from

A chip under the presets says what is on the device — "Community · aira 咏叹调", "AutoEQ · HD 600" — and says "(edited)" the moment a band moves. Until now bands were anonymous: once written, a community config and a curve drawn by hand were identical and the app could not tell you which you were hearing.

2.0.0b1 · beta

Your DAC's own presets, told apart from yours

A DAWN PRO2 has five built-in profiles the volume buttons cycle, and one custom profile the app writes. Cycle to a built-in and Hub Moon used to read that and draw it as the curve you were editing. It now says which profile is playing, whether it is yours, and offers one button back.

2.0.0b1 · beta

Motion, with an off switch

Every animation runs off one scale, and Appearance gets Full · Reduced · Off for it. Off is genuinely off — a duration of zero animates nothing — and nothing the app does waits for an animation to finish. It has to be a setting because the toolkit cannot read the desktop's reduced-motion flag, and animation you cannot switch off is an accessibility problem rather than a taste one.

2.0.0b1 · beta

A library that scrolls properly

Four hundred cards were laid out on every frame when nine fit on screen, which cost more than half the frame rate: 54 fps sitting still, 49 while scrolling. Only the rows near the viewport are built now — 76 and 74. The little curve on each card is drawn on the thread that fetched it rather than on the one drawing the window.

FIG 10 The Library · four sources, one screen · a development build, not a release
The 2.0 Library on its Community tab: tabs for Saved, Recent, Community and Headphones with counts, a search box, This DAC and sort controls, curve-shape filter chips, and a list of configs each drawing its own response curve
FIG 11 The same headphone list, rebuilt · compare with FIG 09 above
The 2.0 Library on its Headphones tab: a chip per measurer with counts, and every row drawing its own correction curve beside the model name and the shape it works out to

b2 will be slow, on purpose. Everything 2.0 planned went into b1 at once, so what comes next is whatever testers actually hit, at the pace they hit it. Switch the app to Beta and it offers you each build as it lands — including the trip back to stable, if you would rather wait it out.

06

It refuses to lie about what the hardware can do.

The firmware stores biquad coefficients as Q2.30, which spans only [−2, 2). Some otherwise reasonable filters need coefficients outside that — and the official app does not check. Its JavaScript packs with bitwise operators that wrap modulo 2³², so past those limits it silently programs a filter unrelated to the curve it just drew you.

Refusal · real output
$ python3 moondrop_control.py --set-peq 3 high_shelf 8000 10 0.7
Error: high_shelf @ +10.0 dB overflows the firmware's Q2.30 coefficient
range (b0=2.514, limit +/-2.0). Max safe gain for high_shelf at
8000 Hz / Q=0.7 is about +4.7 dB. Refusing to write: the official
app wraps here and programs a filter that does not match the curve
it draws.
CoefficientWhen it happensExample
b1 < −2 A high_shelf above roughly +5 dB, or one with a corner below roughly 200 Hz at any gain high_shelf 150 6 0.7
b0 > 2 Any type at high gain, low Q and high frequency peaking 20000 12 0.3

A +6 dB shelf's b1 wraps from −2.303 to +1.697, flipping sign. What the firmware would do with that is untested — so this tool reports the largest gain that fits instead of finding out on your DAC. In the app, the band is clamped and its card tags itself limit.

07

Eleven listed. One tested.

Vendor ID 0x35D8. Names, IDs and columns are read out of the official web app's own registry, with a scrambled name-to-ID mapping corrected. Only the DAWN PRO2 has been tested against real hardware — every other row is plausible but unproven.

PIDDeviceTestedCustom-PEQ profilePre-gain
0x011BRays7not supported
0x011CMarigold7yes
0x011DDAWN PRO2tested · fw 1.57yes
0x011EAG Rays7yes
0x0120DHA157yes
0x012AINN Deco75-DH Audio7yes
0x012BDeco Audio System7yes
0x43DAMOONRIVER 37not supported
0x98D3FreeDSP Pro7not supported
0x98D4FreeDSP Mini7yes
0x98D5E.S. combo4yes
0x0122Old Fashionedrefusedregisters, not biquads — 5 bands, no pre-gain or global gain

Old Fashioned is detected, then refused

It does not drive PEQ with biquad coefficients the way every device above does. It writes through device registers (EQ_REG_BASE 38, WRITE_REG 87) using int8 gain ×10, uint16 frequency and int16 Q ×1000, and exposes 5 bands. None of these commands would mean anything to it.

"I changed the EQ and nothing happened"

On a DAWN PRO2 the equaliser is toggled on the hardware — press both volume buttons. PEQ writes only affect the sound in custom-EQ mode, and no readable register reports which mode the device is in. The long version.

08

59,700 curves, and no account needed.

Moondrop Hub carries a public library of user-made tunings from about 19,900 authors. Reading it needs no account, no key and no token — so the CLI browses it, and the app draws any curve's response before it goes anywhere near your DAC.

FIG 08 Community library · searched locally · nothing is written until you apply
The community library sheet: a search field over published configs with author, description, likes and download counts

Your whole device family

The server pools by the app's sharedConfigGroupId, so a DAWN PRO2 sees around 6,900 curves — its own 1,270 plus every other FreeDSP-family device.

Cached for a day, for a reason

The API has no pagination at all: page and limit return zero rows rather than being ignored. The smallest possible request is the entire ~3.6 MB index, so it is fetched once and searched locally.

Headroom, derived

Published curves carry no pre-gain of their own and most are boost-heavy, so a loud one clips. The app derives the headroom from the curve and applies it alongside the bands.

09

What has actually been run against a DAC.

The protocol was reverse engineered, so the honest thing is to say exactly what has been exercised on real hardware and what has not. This is that list.

AreaResultStatus
Discovery & state--list, --info, --get-peq, --json on a DAWN PRO2, firmware 1.5verified
Backup round-trip--export-json--import-json wrote 8 bands plus both gains to flash and compared byte-identical to the backupverified
Writes are audibleA low_pass at 800 Hz written live in custom-EQ mode audibly muffled playback; restoring the band returned it to normalverified
Flash persistenceThe flashed config survived a physical unplug/replug byte-identical — and --no-flash writes correctly did not surviveverified
REW import--import-rew against an exported ParametricEQ.txtverified
Stream status--stream-status idle and during playbackverified
Community library6,911 presets for the DAWN PRO2's family; both flags strace-confirmed to open zero /dev/hidraw handlesverified
Desktop appReads, live band writes with a verified read-back, drag-to-edit, presets, import/export, the community library and both graph views — plus a run under a fontconfig sandbox with Material Symbols removed, proving the icons need no fontverified
WindowsThe packaged build installs, launches and browses the community library on two machines — AndrewYii and NyChieng. No DAC was attached, so HID reads and writes there are still unproveninterface only
Interactive panel-i — written, but never run against a deviceuntested
macOSThe packaged .dmg installs and runs on an M4 MacBook Air (Apple Silicon)verified
Every other deviceNames, IDs and columns come from the app's registry onlyuntested
Before you experiment

It writes to your DAC's flash. Take a backup first — python3 moondrop_control.py --export-json profile.json — and keep it somewhere you will find it again.

10

Install it.

Installers for Windows, macOS and Linux are attached to every release — no Python needed. From source, the CLI wants Python and two libraries; the desktop app is one extra.

From source
$ pip install "hub-moon[gui] @ \
    git+https://github.com/MiyukiVigil/Hub_Moon"
$ hub-moon --list
# drop [gui] for the CLI alone: hidapi + certifi, nothing else

Linux packages install the udev rule for raw HID access · macOS is ad-hoc signed, so right-click → Open on first launch · Windows is unsigned, so SmartScreen will ask.