Skip to content

Noiseless parameter change#955

Closed
soyersoyer wants to merge 6 commits intoprobonopd:mainfrom
soyersoyer:noiseless
Closed

Noiseless parameter change#955
soyersoyer wants to merge 6 commits intoprobonopd:mainfrom
soyersoyer:noiseless

Conversation

@soyersoyer
Copy link
Contributor

@soyersoyer soyersoyer commented Jul 11, 2025

It would be nice to be able to change any parameter while playing without any pops and clicks.

In principle, it is possible if the parameter change occurs at zero crossing.

I wrote ramping and zero cross detection for panning, TG Volume / Expression change and Master Volume.

While playing, changing Master Volume from 100% -> 0% takes about 1 second (depending on ZCs).

If anyone has any ideas on how to do it better or faster, please don't keep it to yourself :)

Summary by Sourcery

Implement zero-crossing detection and ramped scaling for panning, per-channel gain, and master volume to enable pop-free parameter changes, integrate new DSP routines into the mixer and processing pipeline, and update build scripts to include the new source files.

New Features:

  • Add zero-crossing ramped scaling (arm_scale_zc_ramp_f32) and vector zip (arm_zip_f32) routines for smooth audio parameter changes

Enhancements:

  • Integrate ramped scaling into AudioStereoMixer for noiseless pan and gain adjustments
  • Apply zero-crossing volume ramping in CMiniDexed to eliminate pops when changing master volume
  • Optimize arm_scale_zip_f32 NEON implementation to process four samples per iteration and update build configuration to include new DSP sources

@github-actions
Copy link

Build for testing:
MiniDexed_1214_2025-07-11-9661a17_32bit
MiniDexed_1214_2025-07-11-9661a17_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1215_2025-07-11-c58eb81_32bit
MiniDexed_1215_2025-07-11-c58eb81_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1227_2025-07-22-4d749d2_32bit
MiniDexed_1227_2025-07-22-4d749d2_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1228_2025-07-22-7db1845_32bit
MiniDexed_1228_2025-07-22-7db1845_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1235_2025-07-23-4dd516c_32bit
MiniDexed_1235_2025-07-23-4dd516c_64bit
Use at your own risk.

@Banana71
Copy link

Good job, everything works great, I just couldn't test the HDMI output.

@soyersoyer
Copy link
Contributor Author

soyersoyer commented Jul 24, 2025

The clicking noise due to the DC component is still annoying.
I'll try it out to see what happens if the parameter doesn't change immediately, but continuously.

Why can't you test the HDMI output?

@Banana71
Copy link

Why can't you test the HDMI output?

Because my Raspberry Pi's are built in and the HDMI interface is difficult to access.

@soyersoyer
Copy link
Contributor Author

Because my Raspberry Pi's are built in and the HDMI interface is difficult to access.

Ah okay, then it's not because of the code :)

@soyersoyer
Copy link
Contributor Author

I also added ramping, so even performances with DC components don't click when changing volume or pan. The parameter is adjusted a little slower, but it's not that bad.

@github-actions
Copy link

Build for testing:
MiniDexed_1236_2025-07-24-37098f9_32bit
MiniDexed_1236_2025-07-24-37098f9_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1239_2025-07-24-ebf515d_64bit
Use at your own risk.

@soyersoyer soyersoyer force-pushed the noiseless branch 2 times, most recently from 8da3810 to 62bce01 Compare July 24, 2025 21:59
@github-actions
Copy link

Build for testing:
MiniDexed_1241_2025-07-24-428cbf2_32bit
MiniDexed_1241_2025-07-24-428cbf2_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1242_2025-07-25-7fdfc6a_32bit
MiniDexed_1242_2025-07-25-7fdfc6a_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1243_2025-07-25-5392b77_32bit
MiniDexed_1243_2025-07-25-5392b77_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1244_2025-07-25-f004056_32bit
MiniDexed_1244_2025-07-25-f004056_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1247_2025-07-25-8302d74_32bit
MiniDexed_1247_2025-07-25-8302d74_64bit
Use at your own risk.

@soyersoyer
Copy link
Contributor Author

soyersoyer commented Jul 26, 2025

Now with NEON zero cross detection.

While playing, changing Master Volume from 100% -> 0% takes about 1 second (depending on ZCs).
If anyone has any ideas on how to do it better or faster, please don't keep it to yourself :)

Synth_Dexed PR:
https://codeberg.org/dcoredump/Synth_Dexed/pulls/28

@github-actions
Copy link

Build for testing:
MiniDexed_1248_2025-07-26-61bfcee_32bit
MiniDexed_1248_2025-07-26-61bfcee_64bit
Use at your own risk.

@github-actions
Copy link

Build for testing:
MiniDexed_1249_2025-07-26-5a73ca9_32bit
MiniDexed_1249_2025-07-26-5a73ca9_64bit
Use at your own risk.

@probonopd
Copy link
Owner

Wondering if this could be done on a Syth_Dexed level for all parameters...

@soyersoyer
Copy link
Contributor Author

I tried doing it for cutoff and resonance, but it didn't work this way. (or I overlooked something)

0.0 full left
1.0 full right
@github-actions
Copy link

Build for testing:
MiniDexed_1280_2025-07-30-6ff7514_32bit
MiniDexed_1280_2025-07-30-6ff7514_64bit
Use at your own risk.

@soyersoyer soyersoyer changed the title [WIP] Noiseless parameter change Noiseless parameter change Aug 6, 2025
@soyersoyer soyersoyer marked this pull request as ready for review August 6, 2025 10:30
@sourcery-ai
Copy link

sourcery-ai bot commented Aug 6, 2025

Reviewer's Guide

This PR implements zero-crossing aware ramping for parameter changes (volume, panning, master gain) by introducing ARM NEON-optimized scaling and zip utilities and integrating them into the stereo mixer and MiniDexed audio pipeline to eliminate pops and clicks during live adjustments.

Sequence diagram for zero-crossing aware parameter change during audio mixing

sequenceDiagram
    participant User as actor User
    participant Mixer as AudioStereoMixer
    participant DSP as arm_scale_zc_ramp_f32
    User->>Mixer: Change parameter (e.g., volume, pan)
    Mixer->>Mixer: Update mp_w (target multiplier)
    Mixer->>DSP: Call arm_scale_zc_ramp_f32 with current and target multiplier
    DSP-->>Mixer: Scaled audio with smooth ramp at zero crossing
    Mixer->>Output: Output noiseless audio
Loading

Sequence diagram for master volume change in CMiniDexed

sequenceDiagram
    participant User as actor User
    participant MiniDexed as CMiniDexed
    participant DSP as arm_scale_zc_ramp_f32
    User->>MiniDexed: setMasterVolume(newVol)
    MiniDexed->>MiniDexed: Update m_fMasterVolumeW (target)
    MiniDexed->>DSP: Call arm_scale_zc_ramp_f32 for each channel if needed
    DSP-->>MiniDexed: Smoothly ramp master volume at zero crossing
    MiniDexed->>Output: Output noiseless audio
Loading

Class diagram for updated AudioStereoMixer and new DSP utilities

classDiagram
    class AudioStereoMixer {
        +void gain(uint8_t channel, float32_t gain)
        +void gain(float32_t gain)
        +void pan(uint8_t channel, float32_t pan)
        +void doAddMix(uint8_t channel, float32_t* in)
        float32_t panorama[NN][2]
        float32_t mp[NN][2]
        float32_t mp_w[NN][2]
    }
    class arm_scale_zc_ramp_f32 {
        +void arm_scale_zc_ramp_f32(const float32_t* pSrc, float32_t* pScale, float32_t dScale, float32_t* pDst, uint32_t blockSize)
    }
    class arm_zip_f32 {
        +void arm_zip_f32(const float32_t* pSrc1, const float32_t* pSrc2, float32_t* pDst, uint32_t blockSize)
    }
    class arm_scale_zip_f32 {
        +void arm_scale_zip_f32(const float32_t* pSrc1, const float32_t* pSrc2, float32_t scale, float32_t* pDst, uint32_t blockSize)
    }
    AudioStereoMixer --> arm_scale_zc_ramp_f32 : uses
    AudioStereoMixer --> arm_scale_zip_f32 : uses
    AudioStereoMixer --> arm_zip_f32 : uses
Loading

Class diagram for updated CMiniDexed master volume handling

classDiagram
    class CMiniDexed {
        float32_t m_fMasterVolume[8]
        float32_t m_fMasterVolumeW
        +void setMasterVolume(float32_t vol)
        +int GetMasterVolume127() const
    }
Loading

File-Level Changes

Change Details Files
Add ARM NEON zero-crossing ramp and zip utilities
  • Implement arm_scale_zc_ramp_f32.c/.h with NEON and scalar fallback for ZC-aware scaling
  • Create arm_zip_f32.c/.h to interleave two channels after scaling
  • Refactor arm_scale_zip_f32.c to use 4-sample NEON unrolling and handle remainders
  • Update Makefile to compile/link new utilities
  • Adjust submod.sh to reference updated Synth_Dexed commit
src/arm_scale_zc_ramp_f32.c
src/arm_scale_zc_ramp_f32.h
src/arm_zip_f32.c
src/arm_zip_f32.h
src/arm_scale_zip_f32.c
Makefile
submod.sh
Extend AudioStereoMixer for noiseless gain/pan ramps
  • Include arm_scale_zc_ramp_f32 header
  • Add mp and mp_w arrays to track current/target multipliers
  • Enhance gain() overloads to clamp input and update target mp_w
  • Update pan() to compute new stereo gains and refresh mp_w
  • Modify doAddMix() to choose between arm_scale_f32 and arm_scale_zc_ramp_f32 based on mp vs. mp_w
src/effect_mixer.hpp
Integrate ZC-aware master volume control in MiniDexed
  • Introduce m_fMasterVolume array and m_fMasterVolumeW, remove legacy nMasterVolume
  • Include arm_scale_zc_ramp_f32.h and arm_zip_f32.h in minidexed.cpp
  • Update ProcessSound() to ramp master volume via ZC functions or fall back
  • Refactor setMasterVolume() and GetMasterVolume127() to use new members
src/minidexed.cpp
src/minidexed.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @soyersoyer - I've reviewed your changes - here's some feedback:

  • In the gain(float32_t) overload you’re clamping and calling powf(gain,4) inside the loop—which both repeats expensive work and mutates gain for subsequent channels—so consider moving the clamp and multiplier calculation outside the loop and applying the precomputed multiplier to each channel.
  • The hardcoded ramp constants (RAMP_DT = 1/254 and RAMP_EPS = 1/127) could use a brief comment or better yet be made configurable so it’s clear how they relate to your desired ramp duration and precision.
  • The NEON and non-NEON paths in arm_scale_zc_ramp_f32 duplicate a lot of logic; it might be worth factoring out the shared zero-cross/ramp detection into a single helper to reduce duplication and ease future maintenance.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In the gain(float32_t) overload you’re clamping and calling powf(gain,4) inside the loop—which both repeats expensive work and mutates `gain` for subsequent channels—so consider moving the clamp and multiplier calculation outside the loop and applying the precomputed multiplier to each channel.
- The hardcoded ramp constants (RAMP_DT = 1/254 and RAMP_EPS = 1/127) could use a brief comment or better yet be made configurable so it’s clear how they relate to your desired ramp duration and precision.
- The NEON and non-NEON paths in arm_scale_zc_ramp_f32 duplicate a lot of logic; it might be worth factoring out the shared zero-cross/ramp detection into a single helper to reduce duplication and ease future maintenance.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@github-actions
Copy link

github-actions bot commented Aug 6, 2025

Build for testing:
MiniDexed_1295_2025-08-06-2fdad89_32bit
MiniDexed_1295_2025-08-06-2fdad89_64bit
Use at your own risk.

Ramp the scale parameter and change it only at zero crossings
to minimize pops and clicks.
@github-actions
Copy link

github-actions bot commented Aug 6, 2025

Build for testing:
MiniDexed_1296_2025-08-06-09adf04_32bit
MiniDexed_1296_2025-08-06-09adf04_64bit
Use at your own risk.

@soyersoyer soyersoyer closed this Aug 12, 2025
@soyersoyer soyersoyer deleted the noiseless branch December 3, 2025 22:25
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