iArt.ai
AI 모션 그래픽 소프트웨어, 누구나 사용할 수 있는 After Effects 대안, 텍스트 프롬프트로 몇 분 안에 애니메이션·설명 영상 제작
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A DAC Bridge
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
📖 Install guide & command builder: meltface-80.github.io/Mandarin-DAC-Bridge
Lets Audirvana and Mandarin share your USB DACs, bit-perfect. Written in C# (.NET 10) and
built ahead of time (Native AOT) into one small program. Nothing of .NET needs installing where
it runs.
On a Mac, Audirvana takes a USB DAC in exclusive (hog) mode to switch its sample rate for each
track. While it holds the DAC, nothing else can see it, so Mandarin finds no DAC. The bridge
fixes this by taking each DAC itself:README 전문 보기 ↗📖 Install guide & command builder: meltface-80.github.io/Mandarin-DAC-Bridge
Lets Audirvana and Mandarin share your USB DACs, bit-perfect. Written in C# (.NET 10) and built ahead of time (Native AOT) into one small program. Nothing of .NET needs installing where it runs.
On a Mac, Audirvana takes a USB DAC in exclusive (hog) mode to switch its sample rate for each track. While it holds the DAC, nothing else can see it, so Mandarin finds no DAC. The bridge fixes this by taking each DAC itself:
"<DAC name> (Bridge)", which both
Audirvana and Mandarin can play to./now shows what's playing on a
monitor beside it. Everything optional is off until you switch it on. Audirvana ───── UPnP ──────┐ ┌──▶ exclusive, rate-switched ──▶ USB DAC 1
Mandarin ────── UPnP ──────┤ │
Lyrion / Roon ─ Squeezebox ┤ │
Spotify app ─── Connect ───┼──▶ Mandarin DAC Bridge ─┤
Qobuz app ───── Connect ───┤ (port 55500) │
Plexamp ─────── Plex ──────┤ /now: now playing │
Music / Spotify on the Mac ┘ └──▶ exclusive, rate-switched ──▶ USB DAC 2
macOS 14 or newer (macOS 27 included), Apple silicon or Intel. No Docker needed.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/meltface-80/Mandarin-DAC-Bridge/main/tools/mac/install.sh)"
The installer needs no Homebrew and installs nothing else. It downloads the bridge from this
repository's latest Release into ~/Mandarin-DAC-Bridge, with its own small ffmpeg built with
SoX, the resampler the bridge needs (a few MB, made only of what the bridge uses), and checks them
against that release's checksums: a download that doesn't match is refused, and the bridge already
there is left as it was. If there is no Release yet, it builds the bridge and its ffmpeg on the Mac
instead, which takes a few minutes, once, and asks for Apple's command line tools if they aren't
there. On a Mac an earlier version set up, it then offers to tidy up: Homebrew's ffmpeg-full,
which earlier versions added, and the packages only it needed (it lists them and the space they
take; press Return to remove them). Homebrew itself, Mandarin's ffmpeg and anything you installed
yourself stay. If macOS asks to let mandarin-dac-bridge find
devices on your network, choose Allow. To update, paste the same line again.
Then point the apps at the bridge:
To remove it:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/meltface-80/Mandarin-DAC-Bridge/main/tools/mac/uninstall.sh)"
Windows 10 or 11, x64 or ARM64. Open Terminal (right-click Start) and paste:
powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/meltface-80/Mandarin-DAC-Bridge/main/tools/windows/install.ps1 | iex"
It installs the bridge and its own ffmpeg with SoX into %LOCALAPPDATA%\Mandarin-DAC-Bridge
(checked against the release's checksums, as on the Mac), starts it now and at every sign-in (a
hidden scheduled task), and opens http://localhost:55500. If there is no Release yet, it builds
the bridge here, and then installs ffmpeg with SoX with winget (Gyan.FFmpeg) if none is there. If Windows asks to let mandarin-dac-bridge use the
network, allow it on private networks. To update, paste the same line again.
The bridge holds each USB DAC in WASAPI exclusive mode, so Windows' mixer never touches the samples, and switches it to each track's own rate. In Sound settings the DACs show as in use: that's the hold. To remove it:
powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/meltface-80/Mandarin-DAC-Bridge/main/tools/windows/uninstall.ps1 | iex"
On GitHub, go to Releases → Draft a new release, create a tag v + the <Version> in
src/MandarinDacBridge/MandarinDacBridge.csproj on main (for example v1.2.0) and click
Publish. GitHub Actions (.github/workflows/release.yml) then tests the code, builds it for
Apple silicon and Intel Macs, Linux x64 and arm64, and Windows x64 and ARM64, and attaches
mandarin-dac-bridge-<platform>.tar.gz (.zip for Windows) to the release, with SHA256SUMS, the
checksums the installers check each build against. The installers always fetch the latest release.
If Docker isn't installed: dietpi-software install 162 (Docker) and 134 (Docker Compose) on
DietPi.
git clone https://github.com/meltface-80/Mandarin-DAC-Bridge.git
cd Mandarin-DAC-Bridge
docker compose up -d --build
Then open http://:55500. To update: git pull && docker compose up -d --build.
The Compose file says pull_policy: build: the bridge is always built, never pulled (there is no
published image), so Update in Dockge or Portainer works too, building the latest code. A
Compose file made before 1.1.7 needs that line added under dac-bridge:, or Update fails with
pull access denied for mandarin-dac-bridge.
The image builds the bridge with the .NET 10 SDK, then keeps only the native program, ffmpeg, and
what the optional connections need: PulseAudio (Spotify Soloist), Python (QobuzProxy) and bzip2
(Roon Bridge). Soloist, QobuzProxy, Caldera and Roon Bridge themselves are not in it: the page
downloads them from their publishers into the settings volume when you ask.
You can use docker run instead of Compose. This builds straight from GitHub, so no clone is
needed (it needs git on the host):
# Build the bridge from GitHub (first time, and to update)
docker build -t mandarin-dac-bridge https://github.com/meltface-80/Mandarin-DAC-Bridge.git#main
# Replace any existing container (the settings volume is kept)
docker stop mandarin-dac-bridge 2>/dev/null
docker rm mandarin-dac-bridge 2>/dev/null
docker run -d \
--name mandarin-dac-bridge \
--network host \
--restart unless-stopped \
--device-cgroup-rule 'c 116:* rmw' \
-v /dev/snd:/dev/snd \
--cap-add SYS_NICE \
-e TZ=Europe/London \
-v dac-bridge-data:/data \
mandarin-dac-bridge
--network host is required. UPnP discovery uses multicast, which doesn't cross Docker's
bridge network.-v /dev/snd:/dev/snd and --device-cgroup-rule 'c 116:* rmw' together let the container
open the DACs: the mount keeps the device list current, so a DAC plugged in later is found too,
and the rule lets the container open every sound device (ALSA's devices are character major 116),
and no other device on the host. Before 1.1.29 the command used --privileged for this, which
also gave the bridge, and the programs it downloads, every device and every capability. A
command or Compose file with --privileged still works; replacing it is recommended.--device /dev/snd, switch off its local output for
the DACs. Otherwise both containers try to open them.UPnP is always on. Everything here is optional. Squeezebox, Spotify, Qobuz and the Mac's apps go through the same exclusive, rate-switched path and the same rule: whatever is playing keeps the others out until it stops (and for 10 seconds after). Caldera (Plex) and Roon Bridge play to the DAC themselves: the bridge lets go of it for them while nothing plays through the bridge.
Switch on Squeezebox under Connections on the page (or set SQUEEZELITE=1). Each DAC
becomes a Squeezebox player, "<DAC> (Bridge)", for Lyrion Music Server and for Roon
with Settings → Setup → Squeezebox support on. The server is found by itself (UDP broadcast on
3483); type its address on the page, or set LMS_SERVER, if it isn't.
The bridge speaks the Squeezebox protocol (SlimProto) itself instead of running the squeezelite program, because squeezelite opens the DAC itself, and the bridge holds it. The server's stream goes through ffmpeg like a UPnP track. FLAC, PCM (WAV, AIFF, raw), MP3 and Ogg are taken as they come, and gapless: the bridge asks for the next track when it has read the current one. DSD is converted to PCM by the server. On a DAC with a volume of its own, LMS's volume sets the DAC's (see Volume); for a DAC feeding an amplifier, set the player's volume control in LMS to fixed at 100%. Title, artist and cover come from LMS's JSON-RPC (Roon's Squeezebox support has none, so its tracks show just the format).
Spotify Soloist is Spotify's own headless
Spotify Connect client for Linux. With it switched on, each DAC appears in the Spotify app's device
list as "<DAC> (Bridge)". It needs:
settings.json (readable by its owner only) and
never shown again. You can also set SOLOIST_API_KEY.DATA_DIR/soloist, the way RoPieee fetches Roon Bridge.
Each build lasts 90 days; the page shows until when, and an expired build that the bridge
downloaded is fetched again by itself.sudo apt install pulseaudio; it's in the Docker image). The bridge runs it
privately, with no sound card: it doesn't touch the system's sound.How it fits: Soloist only plays to PipeWire or PulseAudio, and the bridge holds the DAC. So each DAC's Soloist is started with a private PulseAudio server of its own that has no sound card, only a pipe sink: Soloist's sound comes out of a named pipe as 32-bit samples at 44.1 kHz, and the bridge plays them through the same exclusive, rate-switched path as everything else. The pipe waits for its reader, so the DAC's clock sets the pace: nothing is resampled and nothing drifts, and 16- and 24-bit audio arrive unchanged. Soloist's WebSocket (on 127.0.0.1) tells the bridge what's playing (title, artists, album, cover, position) and when Spotify plays, pauses or stops. If another app has the DAC when Spotify starts, the bridge tells Soloist to pause, so the Spotify app shows it didn't play. On a DAC with a volume of its own, the Spotify app's volume sets the DAC's, and a change made elsewhere moves Spotify's slider too. On a DAC without one, Spotify's volume stays at 100% (bit-perfect): a change in the Spotify app is put back.
Soloist takes the API key only on its command line, so other accounts on the same machine could read it from the process list, Docker or not (a container's programs show in the machine's process list too). On a machine only you log in to, that doesn't matter. On a Mac, the Spotify app itself plays through the bridge instead (below).
Switch on Qobuz Connect on the page (or set QOBUZ=1) and press Install QobuzProxy: it
fetches QobuzProxy (MIT) from GitHub into a private
Python environment in DATA_DIR/qobuz (Python 3.10 or later must be installed; the Docker image has
it). Only the release the bridge was made with is installed, checked against its files, and only
the versions of what it needs that were pinned with it, each checked against its hashes (Python 3.10
to 3.14). An install from before 1.1.27 is offered Install again. Then Sign in to Qobuz opens QobuzProxy's own page (port 8689). Each DAC then appears in the
Qobuz app's device list as "<DAC> (Bridge)".
QobuzProxy speaks Qobuz Connect and hands each track to the DAC's UPnP device, so Qobuz plays through the bridge like Audirvana does, under the same one-app-at-a-time rule. The bridge tells it the DAC's best rate and depth, so it streams hi-res up to what the DAC takes. The bridge starts QobuzProxy again when DACs come or go.
Switch on Plex · Caldera Headless on the page (or set CALDERA=1), press Download
Caldera (it fetches Caldera Headless for this machine's
processor), then Sign in to Plex: the page shows a code to enter at
plex.tv/link. Each DAC then appears as a player in Plexamp, Plex for iOS and
the Plex web app. The Plex token stays in settings.json (owner-only) and is never sent to the page;
Caldera keeps its own copy in its folder (DATA_DIR/caldera), its owner's only too. The bridge hands
the token to Caldera by running caldera-music --set for a moment, so while that runs it is on that
program's command line, where other accounts on the same machine could see it.
To show what Caldera plays, the bridge asks the Plex server it plays from; your sign-in is sent only to
your own servers (as plex.tv lists them), never to one another device named. The page says which Plex account is signed in; Sign out of Plex forgets it (here and in Caldera's
settings), so you can sign in with another. Plexamp lists the DACs for any account on the network,
but plays only for the one signed in.
Caldera plays to the DAC itself, bit-perfect, with its own player per DAC. The bridge shares the DAC
with it: while nothing plays through the bridge, it lets go of the DAC so Caldera can open it, and
it takes the DAC back when an app plays to the bridge and Caldera has let go (Caldera releases it
when idle). Whoever plays first has it. What Caldera plays shows on the DAC's page and on /now.
New. On a test Mac the DAC Bridge output gives back what it gets, sample for sample, at 44.1, 96 and 192 kHz. A click can come only when the Mac misses an audio deadline (then macOS moves the output's clock on); the bridge reads it with a large buffer so that it doesn't. Playing from the Music and Spotify apps themselves hasn't been tried on a real Mac yet.
Neither app can play to a DAC the bridge holds, or switch the DAC's rate per track. So, as Arco does for Roon, a small virtual output, DAC Bridge, becomes the Mac's output while this is on. The app plays to it, and the bridge plays the same samples to the DAC you choose:
Music / Spotify ──▶ "DAC Bridge" output ══ loopback ══▶ the bridge ──▶ exclusive, rate-switched ──▶ DAC
/now.Install the output once (it builds the driver with Apple's command line tools, and asks for your password; the Mac's sound stops for a few seconds):
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/meltface-80/Mandarin-DAC-Bridge/main/tools/mac/driver.sh)"
# remove it again: … driver.sh)" -- --remove
Then switch on This Mac: Apple Music & Spotify on the page (or set MAC_APPS=1) and pick the
DAC. The first time, macOS asks to allow Microphone access: that's how the bridge reads the DAC
Bridge output back (it never listens to a microphone). Switched off, the Mac's output goes back to
what it was. mandarin-dac-bridge --loopback-test checks that the output gives back exactly what
it's given, at 44.1, 96 and 192 kHz. The driver is adapted from Arco's (Apache-2.0; see
tools/mac/driver).
Roon plays through Roon Bridge, Roon's own player. It isn't shipped with the bridge: like
RoPieee, the bridge downloads Roon's own build from Roon Labs the first time it's wanted. Switch on
Roon Bridge under Connections (or set ROON_BRIDGE=1) and press Download Roon Bridge:
DATA_DIR/roon, checked with Roon's own check.sh, and run by the bridge as Roon's installer would
run it, with its data in DATA_DIR/roon/data. No root, no system service, and it runs inside the
Docker container. It updates itself. Unpacking needs bzip2 (sudo apt install bzip2; it's in the
Docker image).~/Applications, and the bridge opens it.Then in Roon, go to Settings → Audio and enable the DACs listed under this machine's name.
Roon's playback protocol (RAAT) is its own, so Roon Bridge plays to the DACs itself rather than through the bridge. While it's on, every DAC is shared: the bridge lets go of a DAC whenever nothing plays through it (no app playing, none in its 10-second grace), so Roon Bridge can open it. When Audirvana, Mandarin, LMS, Spotify or Qobuz play to the bridge, it takes the DAC back, once Roon has let go of it. While Roon is playing, they are refused with the DAC is held by another program. The tile says Shared · free for other players or Shared · RoonBridge is playing. Switched off, Roon Bridge stops and the DACs are held again.
A Roon Bridge installed separately is used as it is (the bridge doesn't start a second one). To install it system-wide on Linux yourself instead, or to share a DAC with any other player on the machine, switch on Share when idle on that DAC:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/meltface-80/Mandarin-DAC-Bridge/main/tools/linux/roon-bridge.sh)"
Or skip Roon Bridge: with the Squeezebox player on, Roon's Squeezebox support plays to the bridge directly (with Roon's limits for Squeezebox players, and not as Roon Ready).
Roon now playing. Roon's Squeezebox support sends no track details, and through Roon Bridge
the bridge isn't in the way at all, so the pages and the now-playing screen learn what Roon plays
from Roon itself, through its extension API (as RoPieee's display does). Switch on Roon now
playing under Connections (or set ROON_API=1), then in Roon go to Settings → Extensions and
enable Mandarin DAC Bridge (once: it stays enabled). The bridge finds the Roon Core on the
network by itself (set ROON_CORE=host:port if it can't, e.g. across subnets). Once enabled in a Core,
the bridge talks only to that Core, at the address where it was enabled. If the Core's address
changes, switch Roon now playing off and on. Each DAC shows its
Roon zone's title, artist, album, cover and position: the zone named like the DAC (its Squeezebox
name, or Roon Bridge's name for it), or the one chosen under Roon zone on the DAC's page.
http://<machine>:55500/now, full-screen in any browser: cover, title, artist, album, time and
format of whatever is playing (from any app), and a slowly drifting clock when nothing is. It
follows whichever DAC is playing; move the mouse to pick one (or add ?dac=<id>). On a Linux
machine with a monitor plugged in and no desktop (DietPi, Raspberry Pi OS Lite), this sets up
cage and Chromium to show it from boot:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/meltface-80/Mandarin-DAC-Bridge/main/tools/linux/display.sh)"
# a bridge on another machine: … -- --url http://192.168.1.20:55500/now remove: … -- --uninstall
| macOS | Windows | Linux / Docker | |
|---|---|---|---|
| Finding DACs | Core Audio (USB devices only) | the MMDevice API (USB endpoints) | /proc/asound (USB audio cards) |
| Exclusive hold | Core Audio hog mode, held all the time | WASAPI exclusive mode, event-driven | the ALSA hw: device, held open |
| Rate switching | the DAC's physical format, set per track (integer, widest depth) | the exclusive stream's format, per track (32, 24 or 16-bit) | hw params, set per track (S32_LE, S24_LE, S24_3LE or S16_LE) |
| Bit-perfect | up to 24-bit (Core Audio's float path carries 24 bits exactly) | up to 32-bit | up to 32-bit |
| DSD | DoP (off by default; switch it on per DAC) | DoP (off by default; switch it on per DAC) | DoP, on automatically when the DAC reports native DSD |
| Optional | Squeezebox, Qobuz, Roon Bridge, Roon now playing, the Music and Spotify apps | Squeezebox, Qobuz, Roon Bridge, Roon now playing | Squeezebox, Spotify Connect, Qobuz, Plex, Roon Bridge, Roon now playing |
The program calls Core Audio, WASAPI and ALSA directly from C#
(Native/CoreAudio.cs, Native/Wasapi.cs, Native/Alsa.cs). On a Mac, Core Audio's real-time thread reads the
samples from a buffer in native memory (Audio/CoreAudioSink.cs), so .NET's garbage collector
never touches that path.
ffmpeg/, built by
tools/ffmpeg/build.sh from ffmpeg's and libsoxr's pinned source, LGPL, with only what the bridge
uses; ffmpeg/NOTICE.md says what is in it), and the Docker image has Debian's, which has SoX.
Homebrew's plain ffmpeg and Chocolatey's ffmpeg have none (Homebrew's ffmpeg-full,
Chocolatey's ffmpeg-full and winget's Gyan.FFmpeg have it). The bridge uses its own first,
else one with SoX, and its log says which at start.SetNextAVTransportURI) and it has
the same rate, it plays straight on with no gap. A next track at a different rate waits for the
first to finish, then the DAC switches rate.mandarin-dac-bridge --list prints the DACs found and what each takes, and every other output it saw with why it was passed over. mandarin-dac-bridge --diagnose prints everything the machine reports about its sound devices; on a Mac that includes, for each output stream, every step of reading its formats from Core Audio. A Mac DAC whose formats Core Audio doesn't list is not played: its tile says what Core Audio answered, and the log has the details. The page's API is
GET /api/dacs, and GET/POST /api/services for the optional connections. Each DAC's UPnP description is at /upnp/<id>/description.xml.
Many USB DACs, headphone DACs above all (the AudioQuest DragonFly, for one), have no volume knob: the computer sets their volume, and the DAC applies it in its own chip, after the samples arrive. The stream stays bit-perfect. Where a DAC has such a volume, the bridge uses it:
A Squeezebox server and the DAC share one volume. When the bridge connects to Lyrion (LMS), it sets Lyrion's volume to the DAC's level, and moves Lyrion's slider whenever the DAC's volume changes elsewhere; moving Lyrion's slider sets the DAC to the number it shows. What Lyrion had stored before is never put on the DAC. Roon's Squeezebox support can't be told the DAC's level: there, only the changes made in Roon after connecting move the DAC, by as much as they move Roon's slider.
While another app is playing, a different app's volume change is refused, as for everything else. On the DAC's page, Volume: Fixed leaves the volume where it is, and the apps see 100: use it for a DAC feeding an amplifier with its own volume. A DAC without a volume of its own always plays at 100% (the bridge never changes the samples to make it quieter): use the amplifier's volume.
The DAC's volume is the ALSA mixer control alsamixer shows for the card (usually PCM) on Linux, Core Audio's device volume on a Mac, and the endpoint volume on Windows when the DAC applies it itself.
Anyone who can open the page can change the bridge, so keep port 55500 on your home network. From a
browser, only the bridge's own page can change anything: a request that another website's page sends
to the bridge (to switch a connection on, start a download or turn a DAC up) is refused. Programs
that aren't browsers (Audirvana, Mandarin, a Squeezebox server, curl) aren't affected. A browser
must also have reached the bridge by its address, localhost, or the machine's own name (with or
without .local), to change anything or to read it: another site can't point a name of its own at
the bridge to get round this. If you open the page by a name of your own (from your router's or
Pi-hole's DNS), add it to BRIDGE_HOSTNAMES, or the page is refused. An optional password is planned.
| Variable | Default | |
|---|---|---|
PORT | 55500 | the page and the UPnP devices |
BRIDGE_IP | — | the address to advertise, if the machine is on several networks |
BRIDGE_HOSTNAMES | — | other names you open the page by (your own DNS names), comma-separated |
LOCK_GRACE_S | 10 | how long an app keeps a DAC after it stops or pauses |
MANDARIN_PORT | 3500 | Mandarin's port, used to recognise its streams |
BRIDGE_ALL_OUTPUTS | — | 1 also offers non-USB outputs |
BRIDGE_NAME_SUFFIX | (Bridge) | added to each DAC's name on the network |
DATA_DIR | data/ beside the program | where settings.json lives |
FFMPEG | its own beside it (ffmpeg/bin), else one with SoX: Homebrew's ffmpeg-full, on the PATH, or Homebrew's or winget's | the decoder |
SQUEEZELITE | — | 1 starts with the Squeezebox player on (the page's switch, once used, wins) |
LMS_SERVER | found by itself | the Squeezebox server, host or host:port |
LMS_JSON_PORT | 9000 | that server's web port, if LMS_SERVER is set and Lyrion's isn't 9000 |
SPOTIFY | — | 1 starts with Spotify Connect on (Spotify Soloist, Linux) |
SOLOIST_API_KEY | — | your Soloist API key (or type it on the page) |
SOLOIST | downloaded by the page, or on the PATH | the soloist program |
QOBUZ | — | 1 starts with Qobuz Connect on (QobuzProxy) |
Each switch variable (SQUEEZELITE, SPOTIFY, QOBUZ, CALDERA, MAC_APPS, ROON_BRIDGE,
ROON_API) only sets how the bridge starts the first time, and so do LMS_SERVER and
SOLOIST_API_KEY: once that switch, address or key has been set on the page, the page's choice wins.
On the Mac these go in the EnvironmentVariables of
~/Library/LaunchAgents/app.mandarin.dacbridge.plist, then launchctl unload and launchctl load
that file. Running the installer again (to update) writes the file afresh, so add them again after
an update; what is set on the page is kept. On Windows, set them as user environment variables
(setx LOCK_GRACE_S 20) and sign in again; the installer expects port 55500, so leave PORT alone
there. In Docker, they go under environment: in docker-compose.yml, or as -e with docker run.
The settings volume. docker run and the site's Compose file keep the settings in a volume named
dac-bridge-data. The repository's docker-compose.yml doesn't name its volume, so Compose puts the
folder's name in front (mandarin-dac-bridge_dac-bridge-data for a clone in Mandarin-DAC-Bridge):
switching between the two starts with empty settings. docker volume ls shows which holds yours.
~/Mandarin-DAC-Bridge/data/bridge.log on a Mac,
%LOCALAPPDATA%\Mandarin-DAC-Bridge\data\bridge.log on Windows, docker logs mandarin-dac-bridge
in Docker.--network host.mandarin-dac-bridge --loopback-test checks the output itself..NET 10 SDK. ffmpeg with SoX is needed for the end-to-end tests; on Linux, libasound2 too (and
PulseAudio for the Soloist test). CI runs the tests and a native build on macOS, Windows and Linux,
with the bridge's own ffmpeg (tools/ffmpeg/build.sh <rid> <folder> builds it; put its bin first
on the PATH to test with it too).
tools/check.sh runs every gate and the whole suite (tools/check.sh gates the fast half).
Changes go on their own branch from dev, into dev by a pull request the owner merges once CI is
green; only the owner merges dev into main. The rules are in CLAUDE.md; the gates
(analyzers as errors, a file-length ratchet, tests on three systems) in
dev/quality.md; branches and releases in dev/release.md. Once
per clone: git config core.hooksPath .githooks.
dotnet test # unit and end-to-end tests
dotnet run --project src/MandarinDacBridge # run it
dotnet publish src/MandarinDacBridge -c Release -r osx-arm64 # one native program (osx-x64, linux-x64, linux-arm64, win-x64, win-arm64)
The end-to-end tests run the whole bridge in-process and drive it over real SOAP from two
pretend apps: play, lockout, gapless, seek, pause, rate changes, DSD→DoP, resampling and UPnP
events. A pretend Squeezebox server (gapless, stop, status) and a pretend Soloist (a script
that plays a tone through the bridge's private PulseAudio, and a WebSocket in the test) drive the other two ways in, and a shared DAC is let go and taken
back. They use a sink that plays in real time to a clock (Audio/ClockSink.cs), and on Linux
also the real ALSA path.
| File | What it does |
|---|---|
Program.cs | starts everything (BridgeHost); --list, --diagnose, --loopback-test (Mac), --version |
Config.cs, Settings.cs | the environment variables; settings.json (each DAC's settings, the connections, the secrets) |
OwnerOnlyFile.cs | files only their owner can read (settings and secrets), written whole or not at all |
Manager.cs | finds the DACs; one bridge (sink, renderer, arbiter) per DAC; the connections; the page's view |
Devices.cs, DevicesWindows.cs | what each DAC is and what it takes |
Renderer.cs | the transport, the position, the audio path |
Sources.cs | ffmpeg decoding, resampling only when needed, DSD as DoP |
Dsd.cs | DSF/DFF parsing and DoP packing |
Arbiter.cs | which app is in control |
Audio/ | the sinks: CoreAudioSink (hog mode, physical format, IOProc), WasapiSink (exclusive mode), AlsaSink (hw:), ClockSink (tests); each system's DAC volume |
Native/ | the C calls: Core Audio, WASAPI, ALSA |
Upnp/ | SSDP, the device description, SOAP control, GENA events |
Slim/ | the Squeezebox player (SlimProto), finding the server, its volume |
Spotify/ | Spotify Soloist: its download, the player, its private PulseAudio and pipe |
Qobuz/ | QobuzProxy: its install and its speakers |
Caldera/ | Caldera Headless: its download, the Plex sign-in, the player |
Roon/ | Roon Bridge beside the bridge, and Roon now playing (the extension API) |
Mac/ | the Music and Spotify apps through the DAC Bridge output; the loopback test |
QOBUZPROXY_SOURCE |
| the QobuzProxy release on GitHub |
| another place to install QobuzProxy from (a local copy, a fork), unchecked |
CALDERA | — | 1 starts with Plex through Caldera Headless on (Linux) |
MAC_APPS | — | 1 starts with the Music and Spotify apps on (macOS) |
ROON_BRIDGE | — | 1 starts with Roon Bridge on (downloaded from Roon Labs, run beside the bridge) |
ROON_API | — | 1 starts with Roon now playing on (Roon's extension API) |
ROON_CORE | found by itself | the Roon Core, host:port, if it can't be found on the network |
SOLOIST_VERBOSE | — | 1 runs Spotify Soloist with its detailed log (in DATA_DIR/soloist/<dac>/soloist.log) |
Web.cs | the HTTP server (Kestrel): the page, the API, UPnP |
Page/ | the page and the now-playing screen, built into the program |