Skip to content

Add Left Only / Right Only channel modes#230

Open
W-Floyd wants to merge 11 commits into
CarlosDerSeher:developfrom
W-Floyd:feat/stereo-split
Open

Add Left Only / Right Only channel modes#230
W-Floyd wants to merge 11 commits into
CarlosDerSeher:developfrom
W-Floyd:feat/stereo-split

Conversation

@W-Floyd

@W-Floyd W-Floyd commented Jun 5, 2026

Copy link
Copy Markdown

Wanted a way to be able to use two independent speakers as a stereo pair.


Adds a runtime-settable channel routing mode (Stereo / Left Only / Right Only) that duplicates one input channel to both outputs before DSP processing. Persisted to NVS and exposed in the web UI as a dropdown.

@CarlosDerSeher CarlosDerSeher changed the base branch from master to develop June 5, 2026 21:46
@W-Floyd W-Floyd marked this pull request as draft June 5, 2026 23:18
@W-Floyd

W-Floyd commented Jun 5, 2026

Copy link
Copy Markdown
Author

Not confident this will pass either - I'll review and update. Edit: lol I missed the checks, nvm.

@W-Floyd W-Floyd marked this pull request as ready for review June 5, 2026 23:42
Adds a runtime-settable channel routing mode (Stereo / Left Only / Right
Only) that duplicates one input channel to both outputs before DSP
processing. Persisted to NVS and exposed in the web UI as a dropdown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@W-Floyd W-Floyd force-pushed the feat/stereo-split branch from 911a7ac to 54ed879 Compare June 6, 2026 01:28
@luar123

luar123 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

I think you can simply set i2s slot_mask to left/right to get the same result without any calculations in the dsp module.

Or remove every second sample (left or right) to generate a mono stream and set slot_mode to mono and keep slot_mask set to both. With this you could potentially save a lot of memory but you need to make sure that all the time calculations and sample stuffing don't break.

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/i2s.html#std-tx-mode

@CarlosDerSeher

Copy link
Copy Markdown
Owner

I think you can simply set i2s slot_mask to left/right to get the same result without any calculations in the dsp module

Probably the preferable solution.

@W-Floyd

W-Floyd commented Jun 7, 2026

Copy link
Copy Markdown
Author

For my case, my DAC is set to (L+R)/2, so wouldn't that make my output half volume?

@luar123

luar123 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

If I understand the linked i2s docu correctly it takes one date sample (even or odd depends on the selected channel) and repeats it so it is played on both channels. The other data sample is skipped.

So in the end it should be identical to your implementation.

@W-Floyd

W-Floyd commented Jun 7, 2026

Copy link
Copy Markdown
Author

Instead of duplicating L/R samples in software on every audio chunk,
use the I2S hardware's slot_mask to route the selected channel to both
outputs, eliminating the per-chunk sample manipulation loop. The inactive
slot duplicates the active one natively (confirmed via esp-idf HAL source).

Channel mode state moves from dsp_processor to player, where it can
be applied directly to the I2S peripheral via i2s_channel_reconfig_std_slot().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@W-Floyd

W-Floyd commented Jun 7, 2026

Copy link
Copy Markdown
Author

(I need to test again)

@CarlosDerSeher

Copy link
Copy Markdown
Owner

Do we really need to make that two components depend on one another? @luar123 what do you think about that? Couldn't we just implement all in player? This would mean dependency of lightsnapcast for ui http though? Not sure which is better. Is there a third option?

@luar123

luar123 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

This is a setting that does not need to change during operation, right?
Then I would simply add a parameter to init_player and retrieve settings either in the main task or in init_snapclient.

I think that is the easiest solution. Of course you could also add a function to set only this parameter. Then you could call it from http_get_task during restart (pausing the player would be the easiest way to apply new settings without full reset), but that might be over complicated.

@CarlosDerSeher

CarlosDerSeher commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Great thinking, this was my feeling too. Add API which let's us control this through http task main.

@CarlosDerSeher

CarlosDerSeher commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Then I would simply add a parameter to init_player and retrieve settings either in the main task or in init_snapclie

Settings are loaded in main(), aren't they? So apply this from main during boot (by passing a parameter to init_player). If settings change, simply press the reboot button in the UI. Esp32 is booting fast so this is OK

Channel mode is now passed as a parameter to init_player() rather than
being applied at runtime via player_set_channel_mode(). main() loads the
stored value from NVS before calling init_snapclient(), which threads it
through to init_player(). A reboot applies any UI change, which the
owner confirmed is acceptable.

Removes player_get/set_channel_mode() API and the lightsnapcast build
dependency from dsp_processor_settings. dsp_processor_settings retains
a local s_channel_mode cache for correct JSON serialisation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@W-Floyd

W-Floyd commented Jun 7, 2026

Copy link
Copy Markdown
Author

That sounds fine to me. Assuming we prefer not to make things runtime settable for the sake of simplicity?

Channel mode now takes effect on reboot; show a disabled restart button
that enables as soon as the user changes the dropdown, and add a brief
note explaining the reboot requirement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@CarlosDerSeher

Copy link
Copy Markdown
Owner

Since those things won't be changed very often rebooting should be OK don't you think?

W-Floyd and others added 4 commits June 7, 2026 21:11
Channel routing (slot_mask) is an I2S hardware concern, not a DSP
processing parameter. Remove it from dsp_processor_settings entirely:
- Drop dsp_channel_mode_t from dsp_types.h; define it in player.h
- Remove save/load/json/cache from dsp_processor_settings
- Add settings_get/set_channel_mode (int32_t) to settings_manager,
  persisted in the existing 'snapclient' NVS namespace
- ui_http_server routes channel_mode saves to settings_manager and
  injects the value into the DSP capabilities response for the UI
- main() reads channel_mode from settings_manager and passes it to
  init_player via init_snapclient

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Channel mode is a hardware/boot setting, not a DSP parameter. Move it
from the DSP settings page to the general settings page alongside other
boot-time settings. The dropdown saves immediately via setParameter and
enables a restart button to apply the change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SP JSON

channel_mode is already included in the general settings endpoint via
settings_manager, so there is no need to parse and reserialize the DSP
JSON to inject it. Removes the cJSON dependency from ui_http_server.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@W-Floyd

W-Floyd commented Jun 8, 2026

Copy link
Copy Markdown
Author

Ope, I missed this:

In mono mode [...]

So I would need to implement slot_mode anyway.

@luar123

luar123 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Did you try if your current implementation works? According to the documentation setting slot_mode=stereo and slot_mask=left/right should work.

There are more options, but this should be the easiest.

W-Floyd and others added 2 commits June 8, 2026 08:19
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
settings_get_channel_mode returns ESP_ERR_INVALID_STATE when the mutex
hasn't been created yet, leaving channel_mode silently at 0 (stereo).
Move settings_manager_init before init_snapclient so the NVS read works.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@W-Floyd

W-Floyd commented Jun 8, 2026

Copy link
Copy Markdown
Author

Ah, my settings weren't applying in the right order. Pushed my latest which uses slot_mask + slot_mode, need to do some cleanup...

- Remove spurious dsp_processor REQUIRES from lightsnapcast — nothing in
  player.c/player.h uses it, and it created a build-breaking cycle since
  dsp_processor already PRIV_REQUIRES lightsnapcast
- Reject out-of-range values in settings_set_channel_mode before writing
  to NVS, preventing a crafted HTTP request from permanently corrupting
  the stored mode
- Clamp and warn on invalid channel_mode_raw read from NVS before casting
  to dsp_channel_mode_t

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@W-Floyd

W-Floyd commented Jun 8, 2026

Copy link
Copy Markdown
Author

Ignoring the WiFi TX power section:
image

@W-Floyd

W-Floyd commented Jun 8, 2026

Copy link
Copy Markdown
Author

Much like #232, landing #225 might be best, then I should make the styling match existing sections.

@W-Floyd

W-Floyd commented Jun 8, 2026

Copy link
Copy Markdown
Author

Hm, it seems to be 1/2 volume when I use Left/Right mode vs Stereo.

@W-Floyd W-Floyd changed the title Add Left Only / Right Only channel mode to DSP settings Add Left Only / Right Only channel modes Jun 8, 2026
@W-Floyd

W-Floyd commented Jun 8, 2026

Copy link
Copy Markdown
Author

See #235 - it touches a lot, but seems to work fine.

@luar123

luar123 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Just tried it with only hard coding left channel: tx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;

Works as expected: used stereo dac and played a left/right stereo speaker test. Left channel was played to both sides, right channel not at all.

@W-Floyd

W-Floyd commented Jun 8, 2026

Copy link
Copy Markdown
Author

Do you find volume is cut in half?

@luar123

luar123 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

I would say no but hard to tell without measuring. My dac is not mixing anything. So from dac point of view it is just stereo.

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.

3 participants