2026-06-18 18:59:56 +09:00
|
|
|
"use strict";
|
|
|
|
|
const audio = {
|
|
|
|
|
enabled: false,
|
|
|
|
|
ctx: null,
|
2026-06-19 02:27:19 +09:00
|
|
|
samples: {},
|
|
|
|
|
sampleLast: {},
|
|
|
|
|
samplePaths: {
|
|
|
|
|
hau: "assets/sounds/voice_001_hau.wav",
|
|
|
|
|
flee: "assets/sounds/voice_002_flee.wav",
|
|
|
|
|
poop: "assets/sounds/voice_003_po.wav",
|
|
|
|
|
eat: "assets/sounds/voice_004_eat.wav",
|
|
|
|
|
stress: "assets/sounds/voice_005_stress.wav",
|
|
|
|
|
sleep: "assets/sounds/voice_006_sleep.wav",
|
|
|
|
|
explosion: "assets/sounds/firecracker_explosion.mp3",
|
|
|
|
|
},
|
2026-06-18 18:59:56 +09:00
|
|
|
ensure() {
|
|
|
|
|
const AudioClass = window.AudioContext || window.webkitAudioContext;
|
|
|
|
|
if (!AudioClass) return null;
|
|
|
|
|
if (!this.ctx) this.ctx = new AudioClass();
|
|
|
|
|
if (this.ctx.state === "suspended") this.ctx.resume();
|
2026-06-19 02:27:19 +09:00
|
|
|
this.initSamples();
|
2026-06-18 18:59:56 +09:00
|
|
|
return this.ctx;
|
|
|
|
|
},
|
2026-06-19 02:27:19 +09:00
|
|
|
initSamples() {
|
|
|
|
|
if (this._samplesReady) return;
|
|
|
|
|
this._samplesReady = true;
|
|
|
|
|
for (const [key, path] of Object.entries(this.samplePaths)) {
|
|
|
|
|
const a = new Audio(path);
|
|
|
|
|
a.preload = "auto";
|
|
|
|
|
a.volume = 0.72;
|
|
|
|
|
this.samples[key] = a;
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-06-18 18:59:56 +09:00
|
|
|
toggle() {
|
|
|
|
|
this.enabled = !this.enabled;
|
|
|
|
|
if (this.enabled) this.ensure();
|
|
|
|
|
return this.enabled;
|
|
|
|
|
},
|
2026-06-19 02:27:19 +09:00
|
|
|
playSample(key, minGap = 0.16) {
|
|
|
|
|
if (!this.enabled) return false;
|
|
|
|
|
this.ensure();
|
|
|
|
|
const base = this.samples[key];
|
|
|
|
|
if (!base) return false;
|
|
|
|
|
const t = performance.now() / 1000;
|
|
|
|
|
if (t - (this.sampleLast[key] || 0) < minGap) return true;
|
|
|
|
|
this.sampleLast[key] = t;
|
|
|
|
|
const node = base.cloneNode(true);
|
|
|
|
|
node.volume = base.volume;
|
|
|
|
|
node.play().catch(() => {});
|
|
|
|
|
return true;
|
|
|
|
|
},
|
2026-06-18 18:59:56 +09:00
|
|
|
tone(freq, dur = 0.08, type = "sine", gain = 0.035, slide = 0) {
|
|
|
|
|
if (!this.enabled) return;
|
|
|
|
|
const ctx = this.ensure();
|
|
|
|
|
if (!ctx) return;
|
|
|
|
|
const t0 = ctx.currentTime;
|
|
|
|
|
const osc = ctx.createOscillator();
|
|
|
|
|
const g = ctx.createGain();
|
|
|
|
|
osc.type = type;
|
|
|
|
|
osc.frequency.setValueAtTime(freq, t0);
|
|
|
|
|
if (slide) osc.frequency.linearRampToValueAtTime(Math.max(40, freq + slide), t0 + dur);
|
|
|
|
|
g.gain.setValueAtTime(0.0001, t0);
|
|
|
|
|
g.gain.exponentialRampToValueAtTime(gain, t0 + 0.01);
|
|
|
|
|
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
|
|
|
|
|
osc.connect(g).connect(ctx.destination);
|
|
|
|
|
osc.start(t0);
|
|
|
|
|
osc.stop(t0 + dur + 0.02);
|
|
|
|
|
},
|
2026-06-19 02:27:19 +09:00
|
|
|
bubble(text) {
|
|
|
|
|
const s = String(text || "");
|
|
|
|
|
if (s.includes("\u3082\u3050")) return this.playSample("eat", 0.20);
|
|
|
|
|
if (s.includes("\u3076\u308a")) return this.playSample("poop", 0.28);
|
|
|
|
|
if (s.includes("\u306f\u3063")) return this.playSample("stress", 0.80);
|
|
|
|
|
if (s.includes("Zzz")) return this.playSample("sleep", 1.25);
|
|
|
|
|
if (s.includes("\u306f\u3041") || s.includes("\u306f\u3042")) return this.playSample("flee", 0.70);
|
|
|
|
|
if (s.includes("\u306f\u3046") || s.includes("\u306f\u3045")) return this.playSample("hau", 0.45);
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
eat() { if (!this.playSample("eat", 0.20)) this.tone(660 + Math.random() * 140, 0.055, "triangle", 0.026, 120); },
|
2026-06-18 18:59:56 +09:00
|
|
|
poke() { this.tone(180, 0.09, "sawtooth", 0.022, -70); },
|
|
|
|
|
birth() { this.tone(520, 0.08, "sine", 0.028, 100); setTimeout(() => this.tone(760, 0.10, "triangle", 0.024, 80), 70); },
|
|
|
|
|
death() { this.tone(150, 0.22, "sine", 0.028, -70); },
|
|
|
|
|
fight() { this.tone(110, 0.055, "square", 0.018, 70); setTimeout(() => this.tone(90, 0.045, "sawtooth", 0.014, -40), 35); },
|
2026-06-19 02:27:19 +09:00
|
|
|
explode() { if (!this.playSample("explosion", 0.30)) { this.tone(90, 0.12, "sawtooth", 0.06, -30); setTimeout(() => this.tone(55, 0.20, "square", 0.04, -20), 40); } },
|
2026-06-18 18:59:56 +09:00
|
|
|
phase() { this.tone(440, 0.06, "sine", 0.018, 60); },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.TarinaiAudio = audio;
|