Replace the harmonium timbre with a measured spectrum - #995
Merged
Merged
Conversation
The harmonium waveform was a hand-tuned approximation: a PWM square wave
at duty cycle 0.43, shaped by odd-harmonic bias and brightness trim, then
smeared across seven detuned copies at arbitrary offsets. It did not
sound much like a harmonium.
This replaces it with a spectrum measured from a recording of a real
instrument. Analysing one sustained C3 note showed it is not one rank but
three sounding together:
- an 8' bass reed -> ratio 1
- a 4' octave reed 1202.48 cents above it -> ratio 2.002862, a near-
octave 2.48 cents wide, so the two ranks beat at 0.375 Hz; this is
where the shimmer comes from, and why a pure 2:1 sounds lifeless
- a third quiet rank 153.18 cents flat -> ratio 0.915321, beating
against the 8' at 11.1 Hz, heard as crispness rather than as pitch
Amplitudes came from short-window tracking; a long-window fit badly
underestimates high partials because the instrument's own pitch wobble
destroys their phase coherence. The 8' rank's odd partials run about 9 dB
above its even ones, the signature of a free reed beating against its
frame.
The result is 139 partials against roughly 480 before, so it is also
lighter on the aperiodic voice budget.
Selecting the harmonium now also applies a matching envelope: ~120 ms
attack (the measured reed speech time), no decay, full sustain, 100 ms
release. A harmonium is blown rather than struck, and the app's default
envelope decaying to 80% reads as a struck instrument and undoes the
point of the measured spectrum.
Contributor
Author
|
@forrcaho please review |
frostburn
reviewed
Sep 14, 2026
| if (APERIODIC_WAVEFORMS.includes(newValue)) { | ||
| aperiodicVoiceParams.aperiodicWave = APERIODIC_WAVES[newValue].value | ||
| } | ||
| if (newValue === 'harmonium') { |
Member
There was a problem hiding this comment.
Don't do this. The user can select an envelope preset. You can add a new one if "Organ" and "Pad" don't serve you.
Member
|
Sounds good but it's too loud compared to a plain semisine. |
The measured amplitudes are normalised to a peak of 1, but as partials of three ranks they sum to far more than that, so the timbre played about 9.5 dB louder than a plain semisine and peaked well past unity. Scale them by HARMONIUM_GAIN = 0.22, which sits about 3.7 dB under semisine in RMS and keeps the peak below it; a spectrum this dense reads as louder than a sine at matched RMS. Also drop the harmonium-specific envelope so selecting the waveform no longer overrides the user's envelope settings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018kW3MC5W8F1EqdUWHvyvJb
Contributor
Author
|
@frostburn fixed comments |
frostburn
approved these changes
Sep 14, 2026
frostburn
left a comment
Member
There was a problem hiding this comment.
I'll deploy to QA for community feedback tomorrow.
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The harmonium waveform was a hand-tuned approximation: a PWM square wave at duty cycle 0.43, shaped by odd-harmonic bias and brightness trim, then smeared across seven detuned copies at arbitrary offsets. It did not sound much like a harmonium.
This replaces it with a spectrum measured from a recording of a real instrument. Analysing one sustained C3 note showed it is not one rank but three sounding together:
octave 2.48 cents wide, so the two ranks beat at 0.375 Hz; this is
where the shimmer comes from, and why a pure 2:1 sounds lifeless
against the 8' at 11.1 Hz, heard as crispness rather than as pitch
Amplitudes came from short-window tracking; a long-window fit badly underestimates high partials because the instrument's own pitch wobble destroys their phase coherence. The 8' rank's odd partials run about 9 dB above its even ones, the signature of a free reed beating against its frame.
The result is 139 partials against roughly 480 before, so it is also lighter on the aperiodic voice budget.
Selecting the harmonium now also applies a matching envelope: ~120 ms attack (the measured reed speech time), no decay, full sustain, 100 ms release. A harmonium is blown rather than struck, and the app's default envelope decaying to 80% reads as a struck instrument and undoes the point of the measured spectrum.