SOURCE / PINNED RELEASE
Made of little things.
Soybert 3D
- Release
- 23b6785d0ac6…
- Author-recorded commit
- 4b065931154c…
- License
- LICENSE
- Author’s source reference
- nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-9021e1b6a96
Archive hash verified: af90956bb91e7a26…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import { media } from '@napplet/sdk';
import { runtimeHasDomain } from './domain-availability.js';
import { CoordinatedAudio } from '../docs/examples/coordinated-audio.js';
/** Short synthesized effects: no media files, downloads, or autoplay. */
export class Sound {
private coordinator = new CoordinatedAudio(runtimeHasDomain('media') ? media : undefined, 'Soybert 3D');
private soundEnabled = true;
get enabled() { return this.soundEnabled; }
set enabled(value: boolean) { this.soundEnabled = value; this.coordinator.setMuted(!value); }
private context?: AudioContext;
unlock() {
if (!this.enabled) return;
this.coordinator.activate(); this.context = this.coordinator.context;
}
play(kind: 'jump' | 'shoot' | 'land' | 'celebrate') {
if (!this.enabled || !this.context || !this.coordinator.audible) return;
const c = this.context, now = c.currentTime;
if (kind === 'shoot') {
const buffer = c.createBuffer(1, c.sampleRate * .19, c.sampleRate);
const channel = buffer.getChannelData(0);
for (let i = 0; i < channel.length; i++) channel[i] = (Math.random() * 2 - 1) * (1 - i / channel.length) ** 3;
const source = c.createBufferSource(); source.buffer = buffer;
const filter = c.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 1700;
const gain = c.createGain(); gain.gain.value = .27;
source.connect(filter).connect(gain).connect(this.coordinator.output!); source.start();
source.onended = () => { source.disconnect(); filter.disconnect(); gain.disconnect(); };
}
const notes = kind === 'celebrate' ? [523, 659, 784, 1047] : [kind === 'jump' ? 250 : kind === 'land' ? 90 : 70];
notes.forEach((frequency, i) => {
const oscillator = c.createOscillator(), gain = c.createGain(), start = now + i * .105;
oscillator.type = kind === 'shoot' ? 'triangle' : 'sine';
oscillator.frequency.setValueAtTime(frequency, start);
oscillator.frequency.exponentialRampToValueAtTime(kind === 'jump' ? 580 : frequency * .7, start + .17);
gain.gain.setValueAtTime(.001, start); gain.gain.linearRampToValueAtTime(.12, start + .012); gain.gain.exponentialRampToValueAtTime(.001, start + .2);
oscillator.connect(gain).connect(this.coordinator.output!); oscillator.start(start); oscillator.stop(start + .22);
oscillator.onended = () => { oscillator.disconnect(); gain.disconnect(); };
});
}
dispose() { this.coordinator.dispose(); }
}
