Repository navigation
⚡ Hoist 'pow' and array lookups out of inner DSP loops #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1168,6 +1168,21 @@ public final class SynthVoice: @unchecked Sendable { | |
|
|
||
| var reachedReleaseEnd = false | ||
|
|
||
| // Precalculate unison and string increments outside the frame loop | ||
| let unisonDetunes: [Double] = [-0.35, -0.22, -0.10, 0.0, 0.10, 0.22, 0.35] | ||
| var unisonIncrements = [Double](repeating: 0.0, count: 7) | ||
| for vi in 0..<7 { | ||
| let df = frequency * (pow(2.0, unisonDetunes[vi] / 12.0) - 1.0) | ||
| unisonIncrements[vi] = min((frequency + df) / sampleRate, 0.49) | ||
| } | ||
|
|
||
| let stringDetunes: [Double] = [-0.08, -0.025, 0.025, 0.08] | ||
| var stringIncrements = [Double](repeating: 0.0, count: 4) | ||
| for vi in 0..<4 { | ||
| let df = frequency * (pow(2.0, stringDetunes[vi] / 12.0) - 1.0) | ||
| stringIncrements[vi] = min((frequency + df) / sampleRate, 0.49) | ||
| } | ||
|
|
||
|
Comment on lines
+1171
to
+1185
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (performance): The audio callback now constructs both increment buffers and executes all 11 Triggers: When an active voice uses any oscillator other than Suggested fix: Only calculate these buffers when the selected oscillator requires them, or store reusable buffers/constants outside the audio callback. |
||
| for frame in 0..<Int(frameCount) { | ||
| // Envelope | ||
| let envValue: Double | ||
|
|
@@ -1197,11 +1212,9 @@ public final class SynthVoice: @unchecked Sendable { | |
| let first: Double | ||
| switch snap.oscillator1 { | ||
| case .unison: | ||
| let unisonDetunes: [Double] = [-0.35, -0.22, -0.10, 0.0, 0.10, 0.22, 0.35] | ||
| var unisonMix = 0.0 | ||
| for vi in 0..<7 { | ||
| let df = frequency * (pow(2.0, unisonDetunes[vi] / 12.0) - 1.0) | ||
| let inc = min((frequency + df) / sampleRate, 0.49) | ||
| let inc = unisonIncrements[vi] | ||
| let s = (unisonPhases[vi] * 2.0 - 1.0) | ||
| - DSPMath.polyBLEP(phase: unisonPhases[vi], increment: inc) | ||
| unisonMix += s | ||
|
|
@@ -1210,11 +1223,9 @@ public final class SynthVoice: @unchecked Sendable { | |
| } | ||
| first = unisonMix * 0.18 | ||
| case .strings: | ||
| let stringDetunes: [Double] = [-0.08, -0.025, 0.025, 0.08] | ||
| var strMix = 0.0 | ||
| for vi in 0..<4 { | ||
| let df = frequency * (pow(2.0, stringDetunes[vi] / 12.0) - 1.0) | ||
| let inc = min((frequency + df) / sampleRate, 0.49) | ||
| let inc = stringIncrements[vi] | ||
| let s = (stringsPhases[vi] * 2.0 - 1.0) | ||
| - DSPMath.polyBLEP(phase: stringsPhases[vi], increment: inc) | ||
| strMix += s | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For every active voice using a normal oscillator such as
.sine,.saw, or.triangle—12 of the 14 shipped presets—this unconditionally constructs both detune/increment array pairs and executes all 11powcalls even though the subsequent switch reads neither bank;.unisonand.stringsalso calculate each other's unused bank. This introduces recurring CPU work and allocation jitter into the real-timeAVAudioSourceNodecallback for the common path, so select and compute only the bank required bysnap.oscillator1and reuse fixed storage/constants rather than creating arrays per callback.AGENTS.md reference: AGENTS.md:L7-L8
Useful? React with 👍 / 👎.