SoundFont local initialAttenuation does not override global attenuation, making affected presets too quiet
Maintainer thường phản hồi trong vòng 1 ngày
@Danielku15 đang làm issue này rồi.
Từ ngày 27/10/2026.
Đánh giá
Issue này chưa được đánh giá.
Mô tả
Is there an existing issue for this?
- I have searched the existing issues
I have read the contribution rules
- I have read CONTRIBUTING.md and, if any AI agent helped draft this issue, AGENTS.md.
AI authorship
AI-assisted (disclosure block included as described in AGENTS.md)
Current Behavior
[!NOTE]
AI-authored disclosure (alphatab-ai-authored-v1)Portions of this content were authored by an AI agent. The agent has read
AGENTS.md and the human submitter accepts responsibility for
compliance with the rules in that document.
With the official npm package @coderline/[email protected], two SoundFonts that should be equivalent produce different PCM levels when one contains a global initialAttenuation value that is overridden locally.
For example, a preset with global attenuation = 200 cB and local attenuation = 120 cB renders at 32 dB attenuation, instead of 12 dB. Removing only the overridden global value makes the output 20 dB louder. The sample, MIDI note, velocity, channel controls, and export settings are identical. The same behavior occurs at the instrument level, including an explicit local value of zero.
This was initially noticed as unusually quiet overdriven guitar playback. The reproduction below uses program 0 and an original generated sine-wave sample, so it does not depend on a particular commercial SoundFont, guitar preset, application, or private score.
Measured attenuation relative to the identical unattenuated fixture:
| Case | Expected dB | Actual dB in 1.8.4 |
|---|---|---|
| Preset global 200 cB, local 120 cB | 12 | 32 |
| Instrument global 127 cB, local 50 cB | 5 | 17.7 |
| Both of the above | 17 | 49.7 |
| Missing locals: preset global 200 cB + instrument global 50 cB | 25 | 25 |
| Preset global 200 cB, local 0; instrument local 50 cB | 5 | 25 |
| Preset local 120 cB; instrument global 127 cB, local 0 | 12 | 24.7 |
| Preset local 120 cB + instrument local 50 cB | 17 | 17 |
| Preset local -60 cB + instrument local 100 cB | 4 | 4 |
| No attenuation generators | 0 | 0 |
Expected Behavior
A local initialAttenuation generator should replace the corresponding global generator at the same level. An omitted local value should inherit the global value; an explicit zero should override it. After resolving each level, preset attenuation should still be added to instrument attenuation.
The two equivalent preset fixtures (global 200 cB/local 120 cB versus local 120 cB alone) should therefore have identical output.
Normative basis: SoundFont 2.04 specification, section 9.4, PDF pages 50–51. It specifies local-over-global replacement separately at the instrument and preset levels, and addition between the preset and instrument levels. Section 9.3 defines one centibel as one tenth of a decibel.
Steps To Reproduce
- Create an empty directory and install the pinned official package:
npm init -y
npm install --ignore-scripts --save-exact @coderline/[email protected]
- Save the following as
repro.cjs. - Run
node repro.cjs(tested with Node v24.11.1). - It prints the table shown above and exits with an assertion failure: five override cases fail, while the four control cases pass. All SF2 data and the MusicXML score are generated in memory; no external assets are required.
Self-contained reproduction: repro.cjs
const assert = require('node:assert/strict');
const alphaTab = require('@coderline/alphatab');
// Build an original, looping sine-wave SF2 in memory. Generator 48 uses centibels.
function sf2({ pg, pl, ig, il } = {}) {
const words = (size, values) => {
const b = Buffer.alloc(size * values.length);
values.forEach((v, i) => size === 2 ? b.writeUInt16LE(v & 65535, i * size) : b.writeUInt32LE(v, i * size));
return b;
};
const u16 = (...v) => words(2, v), u32 = (...v) => words(4, v);
const name = s => { const b = Buffer.alloc(20); b.write(s); return b; };
const chunk = (id, ...parts) => {
const body = Buffer.concat(parts);
return Buffer.concat([Buffer.from(id), u32(body.length), body, Buffer.alloc(body.length % 2)]);
};
function zones(global, local, terminal) {
const gens = [], bags = [u16(0, 0)];
if (global !== undefined) { gens.push(u16(48, global)); bags.push(u16(gens.length, 0)); }
if (local !== undefined) gens.push(u16(48, local));
gens.push(...terminal);
bags.push(u16(gens.length, 0));
gens.push(u16(0, 0));
return { bags: Buffer.concat(bags), gens: Buffer.concat(gens), count: bags.length - 1 };
}
const p = zones(pg, pl, [u16(41, 0)]);
const i = zones(ig, il, [u16(54, 1), u16(53, 0)]);
const pcm = Buffer.alloc((512 + 46) * 2);
for (let n = 0; n < 512; n++) pcm.writeInt16LE(Math.round(8000 * Math.sin(2 * Math.PI * n / 64)), n * 2);
return chunk('RIFF', Buffer.from('sfbk'),
chunk('LIST', Buffer.from('INFO'), chunk('ifil', u16(2, 1))),
chunk('LIST', Buffer.from('sdta'), chunk('smpl', pcm)),
chunk('LIST', Buffer.from('pdta'),
chunk('phdr', name('Test'), u16(0, 0, 0), u32(0, 0, 0), name('EOP'), u16(0, 0, p.count), u32(0, 0, 0)),
chunk('pbag', p.bags), chunk('pmod', Buffer.alloc(10)), chunk('pgen', p.gens),
chunk('inst', name('Test'), u16(0), name('EOI'), u16(i.count)),
chunk('ibag', i.bags), chunk('imod', Buffer.alloc(10)), chunk('igen', i.gens),
chunk('shdr', name('Sine'), u32(0, 512, 64, 448, 22050), Buffer.from([60, 0]), u16(0, 1),
name('EOS'), u32(512, 512, 512, 512, 0), Buffer.from([0, 0]), u16(0, 1))));
}
const xml = `<score-partwise version="4.0"><part-list><score-part id="P">
<part-name>Test</part-name><score-instrument id="I"><instrument-name>Test</instrument-name></score-instrument>
<midi-instrument id="I"><midi-channel>1</midi-channel><midi-program>1</midi-program></midi-instrument>
</score-part></part-list><part id="P"><measure number="1"><attributes><divisions>1</divisions>
<time><beats>4</beats><beat-type>4</beat-type></time><clef><sign>G</sign><line>2</line></clef></attributes>
<direction><sound tempo="96"/></direction><note><pitch><step>C</step><octave>4</octave></pitch>
<duration>4</duration><type>whole</type></note></measure></part></score-partwise>`;
const score = alphaTab.importer.ScoreLoader.loadScoreFromBytes(new TextEncoder().encode(xml));
const midi = new alphaTab.midi.MidiFile();
const generator = new alphaTab.midi.MidiFileGenerator(score, new alphaTab.Settings(), new alphaTab.midi.AlphaSynthMidiFileHandler(midi));
generator.generate();
class Event { on(f) { this.f = f; } trigger() { this.f?.(); } }
function render(parameters) {
// Offline output adapter: no browser, audio device, network, or external SF2 needed.
const output = {
sampleRate: 44100, ready: new Event(), sampleRequest: new Event(), samplesPlayed: new Event(),
open() { this.ready.trigger(); }, destroy() {}, pause() {}, play() {}, resetSamples() {}, addSamples() {}
};
const synth = new alphaTab.synth.AlphaSynth(output, 200);
const options = new alphaTab.synth.AudioExportOptions();
options.soundFonts = [sf2(parameters)];
const exporter = synth.exportAudio(options, midi, generator.syncPoints, generator.transpositionPitches);
return exporter.render(400).samples;
}
const rms = samples => Math.sqrt(samples.reduce((sum, v) => sum + v * v, 0) / samples.length);
const reference = rms(render({}));
assert.ok(reference > 0.0001, 'Reference must be audible');
const cases = [
['preset override', { pg: 200, pl: 120 }, 12],
['instrument override', { ig: 127, il: 50 }, 5],
['both overrides', { pg: 200, pl: 120, ig: 127, il: 50 }, 17],
['inherit missing locals', { pg: 200, ig: 50 }, 25],
['preset zero override', { pg: 200, pl: 0, il: 50 }, 5],
['instrument zero override', { pl: 120, ig: 127, il: 0 }, 12],
['cross-level addition', { pl: 120, il: 50 }, 17],
['negative preset offset', { pl: -60, il: 100 }, 4],
['default', {}, 0]
];
const results = cases.map(([name, parameters, expectedDb]) => {
const actualDb = -20 * Math.log10(rms(render(parameters)) / reference);
return { name, expectedDb, actualDb: Number(actualDb.toFixed(4)), pass: Math.abs(actualDb - expectedDb) < 0.00001 };
});
console.table(results);
assert.ok(results.every(r => r.pass), 'Local attenuation must replace global attenuation at the same level');
Link to jsFiddle, CodePen, Project
No response
Version and Environment
[Actual output from alphaTab.Environment.printEnvironmentInfo()]
[AlphaTab][VersionInfo] alphaTab 1.8.4
[AlphaTab][VersionInfo] commit: 022a45c8e42370f9e12e68949d11eada370da83d
[AlphaTab][VersionInfo] build date: 2026-07-05T14:46:18.224Z
[AlphaTab][VersionInfo] High DPI: 1
[AlphaTab][VersionInfo] Platform: NodeJs
[AlphaTab][VersionInfo] WebPack: false
[AlphaTab][VersionInfo] Vite: false
Node v24.11.1; process.platform = darwin; process.arch = arm64.
Official npm 1.8.4 tarball verified against its registry SHA512 integrity value.
Offline PCM export at 44100 Hz; no browser or audio device is required.
Platform
Node.js
Anything else?
The reproduction measures PCM from the public AlphaSynth export API, rather than inferring the behavior from internal fields. It also includes passing controls for inheritance, cross-level addition, signed preset offsets, and the default gain.
The generated sine wave, MusicXML, and SF2 are contained entirely in the script; no external score, sample library, or hosted reproduction is required. No files are uploaded separately.
- Ngôn ngữ chính
- TypeScript
- Star
- 1.9k
- Fork
- 297
- Merge trung bình
- 9 giờ 41 phút
- Pull request đã merge (30 ngày)
- 41
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của CoderLine/alphaTab
-
ai-authored
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 69/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Fade out: track silent until the endCó thể đã có người làm @Danielku15 đã nhận 2 ngày trước. Đang mở
CoderLine/alphaTab#2978 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Rendering issue: Unable to switch to Numbered Notation (jianpu) for specific tracks in v1.8.4Có thể đã có người làm @Danielku15 đã nhận 3 ngày trước. Đang mở
CoderLine/alphaTab#2976 · 3 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ai-authored
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 66/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Respect systems layout of MusicXMLCó thể đã có người làm @Danielku15 đã nhận 24 ngày trước. Đang mở
CoderLine/alphaTab#2909 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của CoderLine/alphaTab
Issue tương tự
-
Parallel reticle_verify opens per-flow leases without the source lease's ignoreHTTPSErrorsCó thể đã có người làm @as992949791 đã nhận hôm nay. Đang mởgood first issue help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
reticlehq/reticle#2033 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
area:quality p3-medium ready-for-agent type:docs
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 83/100
fderuiter/deruiter.dev#3589 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
manfredsteyer/angular-oauth2-oidc#1514 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
marmelab/react-admin#11431 ·
Maintainer thường phản hồi trong vòng 1 ngày