diff --git a/src/lib/audio/effects/volume-effect.js b/src/lib/audio/effects/volume-effect.js
index 0cfcd64152c151ce156e9a1a594cdb11550b5ca5..a5d12a98709803008326227f4a61df0098b59763 100644
--- a/src/lib/audio/effects/volume-effect.js
+++ b/src/lib/audio/effects/volume-effect.js
@@ -14,8 +14,16 @@ class VolumeEffect {
         this.gain.gain.setValueAtTime(volume, endSeconds);
         this.gain.gain.exponentialRampToValueAtTime(1.0, endSeconds + this.rampLength);
 
+        // Use a waveshaper node to prevent sample values from exceeding -1 or 1.
+        // Without this, gain can cause samples to exceed this range, then they
+        // are clipped on save, and the sound is distorted on load.
+        this.waveShaper = this.audioContext.createWaveShaper();
+        this.waveShaper.curve = new Float32Array([-1, 1]);
+        this.waveShaper.oversample = 'none';
+
         this.input.connect(this.gain);
-        this.gain.connect(this.output);
+        this.gain.connect(this.waveShaper);
+        this.waveShaper.connect(this.output);
     }
 }