Skip to content

Experimental - Add mic sync calibration#27

Draft
kahrendt wants to merge 2 commits intomainfrom
sync-calibration
Draft

Experimental - Add mic sync calibration#27
kahrendt wants to merge 2 commits intomainfrom
sync-calibration

Conversation

@kahrendt
Copy link

@kahrendt kahrendt commented Dec 30, 2025

This probably needs a lot more work/adaptation to fit into the codebase properly! I just want to share the current state I have before I start my vacation.

Prerequisites

  • Mute your capturing device before starting (to avoid interference)
  • Have a chirp signal ready to play in Music Assistant (you can generate one using the included helper script)
  • This tool cannot reliably calibrate the CLI client itself, as microphone AEC will interfere with measurements

Calibration Steps

  1. Start the calibrator:

    sendspin --calibrate-sync --mic-device 0
    
  2. Set up playback:

    • Sync your command-line player with another player
    • Mute the command-line player
  3. Position your microphone:

    • Hold your device's microphone as close as possible to the other device's speaker
    • Minimizing distance reduces interference from room echoes
  4. Capture the reference offset:

    • Start playback (preferably using a chirp signal)
    • Look for a single offset that stands out clearly in the histogram—this is your reference offset
  5. Measure additional devices:

    • Stop playback on the first device
    • Repeat steps 2–4 for each additional device you want to calibrate
    • The difference between two devices' histogram peaks is their relative offset—adjust accordingly

Known Limitations

  • Sample rate: 48 kHz works better for capturing on some microphones (this is now the default throughout the codebase)
  • Microphone drift: Some mics drift over time. Internal laptop mics tend to be stable, but external mics (e.g., webcams) may drift ~1 ms every 30 seconds. No reliable compensation method has been found yet.
  • Offset accuracy: The reported offset isn't absolute—ADC latency isn't fully accounted for, and your device's mic latency may vary.
  • Debug logging: There are still many debug logs printing that need to be removed before merging. You typically don't see them because of the calibration UI covering it.

@kahrendt kahrendt marked this pull request as draft December 30, 2025 17:31
@HarvsG
Copy link

HarvsG commented Jan 9, 2026

uvx --from git+https://github.com/Sendspin/sendspin-cli@sync-calibration sendspin --url ws://192.168.0.30:8927/sendspin --calibrate-sync --mic-device 0

george@G-Thinkpad:~$ uvx --from git+https://github.com/Sendspin/sendsp
in-cli@sync-calibration sendspin --url ws://192.168.0.30:8927/sendspin
    Updated https://github.com/Sendspin/sendspin-cli (1fe394be2c3ccd03
      Built sendspin @ git+https://github.com/Sendspin/sendspin-cli@1f
Installed 29 packages in 163ms
Using client ID: sendspin-cli-G-Thinkpad
Traceback (most recent call last):
  File "/home/george/.cache/uv/archive-v0/AkW1oSrZenvvfZPQgQITl/bin/sendspin", line 12, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/george/.cache/uv/archive-v0/AkW1oSrZenvvfZPQgQITl/lib/python3.12/site-packages/sendspin/cli.py", line 285, in main
    return asyncio.run(app.run())
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/george/.local/share/uv/python/cpython-3.12.12-linux-x86_64-gnu/lib/python3.12/asyncio/runners.py", line 195, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/home/george/.local/share/uv/python/cpython-3.12.12-linux-x86_64-gnu/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/george/.local/share/uv/python/cpython-3.12.12-linux-x86_64-gnu/lib/python3.12/asyncio/base_events.py", line 691, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/george/.cache/uv/archive-v0/AkW1oSrZenvvfZPQgQITl/lib/python3.12/site-packages/sendspin/app.py", line 576, in run
    audio_device = resolve_audio_device(config.audio_device)
                   ^^^^^^^^^^^^^^^^^^^^
NameError: name 'resolve_audio_device' is not defined

@HarvsG
Copy link

HarvsG commented Jan 12, 2026

I tried using this with music rather than a test tone with not very much success:

Thanks for the rebase. I am continually get +0ms delay, even on devices I know are out of sync. Here's what I did.

  1. I ran uvx --from git+https://github.com/Sendspin/sendspin-cli@sync-calibration sendspin --url ws://192.168.0.30:8927/sendspin --calibrate-sync --mic-device 0 on Windows Subsystem for Linux
  2. I started a sendspin session with music assistant and set music to a variety of devices but all muted
  3. I placed a VAPE against my laptop microphone and got a peak at +45ms on both histograms
  4. I closed the cli and then ran uvx --from git+https://github.com/Sendspin/sendspin-cli@sync-calibration sendspin --url ws://192.168.0.30:8927/sendspin --calibrate-sync --mic-device 0 --static-delay 45 and got a peak at 0ms, so far so good. My CLI is calibrated against my VAPE
  5. I then placed my laptop microphone against a variety of google home devices (making sure to mute other devices and my laptop) that I had tuned by ear and consistently got a peak at 0ms
  6. I then turned the offset on one of my chromecast audio devices from -330 to 0 making it audibly out of sync and tried again. Again a peak at 0ms.

Note between testing each device I closed and re-opened the cli to clear the histogram.
I tried again with --static-delay 0 rather than 45 I confirmed that with the VAPE I was getting -45. However with other devices I was getting 0 again. I am going to try with a test tone next

Edit:
OK I tried again with the "High Definition White Noise" sample (96-48 kHz) from here: https://www.audiocheck.net/testtones_highdefinitionaudio.php and got more reliable results.

A few notes.

  1. The + and - is confusing, which direction is it in, and as is it the error or the correction that needs to be made?
    • In the end I found it was the latter. If the current set delay was -330 and the histogram showed -5 the -335 would bring the histogram to 0
  2. Turn the speaker to the loudest volume you can bear and get the microphone as close to the speaker as possible
  3. The difference between the two histograms was unclear, and sometimes they differed by 1ms
  4. For stereo speakers - remove/mute one of them
  5. Let go of the laptop/microphone
  6. No need to reload the cli between devices, just give a bit of time with background noise to let the histograms fade into randomness
  7. It was quite common to get a histogram with a very strong peak at lets say +6ms that would then, without intervention, slowly trend towards 0 over about 30 seconds- at which point I would satisfy myself that it was perfectly calibrated. Now as I write this I wonder if I should have kept on waiting and might have found some drift

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants