mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
fix(audio): smooth, monotonic volume curve (#84)
applyVolume() mapped 0-100 with a two-piece, discontinuous curve: gain = (vol/100)*0.2 for vol<100 (so the whole 0-99 range only spanned 0..0.198, making 80->99 feel flat) then a raw passthrough at vol===100 (a ~5x jump to full loudness). That produced the reported dead zone + sudden ear-blast at 100. Replace it with a single continuous, strictly-monotonic curve volumeToFactor(v) = 0.2*x + 0.8*x^8 (x = v/100): 0 at 0, exactly 1.0 at 100, no flat region and no discontinuity, so the slider feels proportional and full loudness is still reserved at 100. Extracted as an exported pure function and unit-tested (boundaries, strict monotonicity, dead-zone removal, no jump at 100). Note: per the maintainer's note on #84 the >80% suppression was intentional ear-protection; this change makes the upper range (above ~75%) audibly louder than before in exchange for a proportional slider — applied per maintainer decision. Fixes #84 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
bea2f92508
commit
3802c90d2d
2 files changed
+50
-3
No files matched your search
+18
-2
@@ -103,6 +103,21 @@ export function buildFfmpegArgs(url: string, seekSeconds: number): string[] {
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a 0-100 volume value to a linear PCM gain factor (#84).
|
||||
*
|
||||
* Continuous and strictly monotonic over [0,100]: 0 at vol 0 and exactly 1.0 at
|
||||
* vol 100. The previous mapping was a two-piece step — gain = (vol/100)*0.2 for
|
||||
* vol<100 (so the whole 0-99 range only spanned 0..0.198, making 80->99 feel
|
||||
* flat) then a raw passthrough at vol===100 (a ~5x jump). This single curve keeps
|
||||
* the low end gentle but ramps smoothly toward full loudness near the top, so the
|
||||
* slider feels proportional with no dead zone and no discontinuity at 100.
|
||||
*/
|
||||
export function volumeToFactor(volume: number): number {
|
||||
const x = Math.max(0, Math.min(100, volume)) / 100;
|
||||
return 0.2 * x + 0.8 * Math.pow(x, 8);
|
||||
}
|
||||
|
||||
export interface PlayerEvents {
|
||||
frame: (opusFrame: Buffer) => void;
|
||||
trackEnd: () => void;
|
||||
@@ -509,8 +524,9 @@ export class AudioPlayer extends EventEmitter {
|
||||
}
|
||||
|
||||
private applyVolume(pcm: Buffer): Buffer {
|
||||
if (this.volume === 100) return Buffer.from(pcm);
|
||||
const factor = (this.volume / 100) * 0.2;
|
||||
const factor = volumeToFactor(this.volume);
|
||||
// factor === 1 only at volume 100; skip the per-sample loop at full loudness.
|
||||
if (factor >= 1) return Buffer.from(pcm);
|
||||
const out = Buffer.alloc(pcm.length);
|
||||
for (let i = 0; i < pcm.length; i += 2) {
|
||||
let sample = Math.round(pcm.readInt16LE(i) * factor);
|
||||
|
||||
Reference in new issue
Block a user