278 files
This commit is contained in:
1 parent
d7a0a08372
commit
1b4f5f68c6
278 files changed
+36892
-7150
No files matched your search
@@ -216,353 +216,26 @@ class TaskMateChildCard extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the AudioContext (lazy initialization)
|
||||
* Must be called after user interaction due to browser autoplay policies
|
||||
*/
|
||||
_getAudioContext() {
|
||||
if (!this._audioContext) {
|
||||
this._audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
// Resume if suspended (required after user interaction)
|
||||
if (this._audioContext.state === 'suspended') {
|
||||
this._audioContext.resume();
|
||||
}
|
||||
return this._audioContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a completion sound using Web Audio API
|
||||
* Generates fun synthesized sounds - no external files needed!
|
||||
* @param {string} soundName - Name of the sound to play (coin, levelup, fanfare, chime, powerup)
|
||||
* Play a completion sound.
|
||||
*
|
||||
* Delegates to the shared engine in taskmate-sounds.js — this card used to
|
||||
* carry its own copy of every synthesised sound, which drifted out of sync
|
||||
* with the other copy in taskmate-config-sounds.js.
|
||||
*
|
||||
* @param {string} soundName - built-in name, or "custom:<file>" (#856)
|
||||
*/
|
||||
_playSound(soundName) {
|
||||
// Don't play if sound is "none" or not specified
|
||||
if (!soundName || soundName === 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const ctx = this._getAudioContext();
|
||||
const now = ctx.currentTime;
|
||||
|
||||
switch (soundName) {
|
||||
case 'coin':
|
||||
this._playCoinSound(ctx, now);
|
||||
break;
|
||||
case 'levelup':
|
||||
this._playLevelUpSound(ctx, now);
|
||||
break;
|
||||
case 'fanfare':
|
||||
this._playFanfareSound(ctx, now);
|
||||
break;
|
||||
case 'chime':
|
||||
this._playChimeSound(ctx, now);
|
||||
break;
|
||||
case 'powerup':
|
||||
this._playPowerUpSound(ctx, now);
|
||||
break;
|
||||
case 'undo':
|
||||
this._playUndoSound(ctx, now);
|
||||
break;
|
||||
case 'fart1':
|
||||
this._playAudioFile('fart1.mp3');
|
||||
break;
|
||||
case 'fart2':
|
||||
this._playAudioFile('fart2.mp3');
|
||||
break;
|
||||
case 'fart3':
|
||||
this._playAudioFile('fart3.mp3');
|
||||
break;
|
||||
case 'fart4':
|
||||
this._playAudioFile('fart4.mp3');
|
||||
break;
|
||||
case 'fart5':
|
||||
this._playAudioFile('fart5.mp3');
|
||||
break;
|
||||
case 'fart6':
|
||||
this._playAudioFile('fart6.mp3');
|
||||
break;
|
||||
case 'fart7':
|
||||
this._playAudioFile('fart7.mp3');
|
||||
break;
|
||||
case 'fart8':
|
||||
this._playAudioFile('fart8.mp3');
|
||||
break;
|
||||
case 'fart9':
|
||||
this._playAudioFile('fart9.mp3');
|
||||
break;
|
||||
case 'fart10':
|
||||
this._playAudioFile('fart10.mp3');
|
||||
break;
|
||||
case 'fart_random': {
|
||||
// Pick a random fart sound (1-10)
|
||||
const randomFartNum = Math.floor(Math.random() * 10) + 1;
|
||||
this._playAudioFile(`fart${randomFartNum}.mp3`);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this._playCoinSound(ctx, now);
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
const engine = window.__taskmate_sounds;
|
||||
if (!engine) return;
|
||||
engine.play(soundName, this._customSounds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Coin collect sound - classic video game coin pickup
|
||||
* Two quick ascending tones
|
||||
*/
|
||||
_playCoinSound(ctx, startTime) {
|
||||
const masterGain = ctx.createGain();
|
||||
masterGain.connect(ctx.destination);
|
||||
masterGain.gain.value = 0.3;
|
||||
|
||||
// First tone (E6)
|
||||
const osc1 = ctx.createOscillator();
|
||||
const gain1 = ctx.createGain();
|
||||
osc1.connect(gain1);
|
||||
gain1.connect(masterGain);
|
||||
osc1.frequency.value = 1318.5; // E6
|
||||
osc1.type = 'square';
|
||||
gain1.gain.setValueAtTime(0.5, startTime);
|
||||
gain1.gain.exponentialRampToValueAtTime(0.01, startTime + 0.1);
|
||||
osc1.start(startTime);
|
||||
osc1.stop(startTime + 0.1);
|
||||
|
||||
// Second tone (B6) - higher
|
||||
const osc2 = ctx.createOscillator();
|
||||
const gain2 = ctx.createGain();
|
||||
osc2.connect(gain2);
|
||||
gain2.connect(masterGain);
|
||||
osc2.frequency.value = 1975.5; // B6
|
||||
osc2.type = 'square';
|
||||
gain2.gain.setValueAtTime(0.5, startTime + 0.08);
|
||||
gain2.gain.exponentialRampToValueAtTime(0.01, startTime + 0.25);
|
||||
osc2.start(startTime + 0.08);
|
||||
osc2.stop(startTime + 0.25);
|
||||
}
|
||||
|
||||
/**
|
||||
* Level up sound - triumphant ascending arpeggio
|
||||
*/
|
||||
_playLevelUpSound(ctx, startTime) {
|
||||
const masterGain = ctx.createGain();
|
||||
masterGain.connect(ctx.destination);
|
||||
masterGain.gain.value = 0.25;
|
||||
|
||||
// C major arpeggio going up: C5, E5, G5, C6
|
||||
const notes = [523.25, 659.25, 783.99, 1046.5];
|
||||
const duration = 0.12;
|
||||
|
||||
notes.forEach((freq, i) => {
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = ctx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(masterGain);
|
||||
osc.frequency.value = freq;
|
||||
osc.type = 'square';
|
||||
|
||||
const noteStart = startTime + i * duration;
|
||||
gain.gain.setValueAtTime(0.6, noteStart);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, noteStart + duration + 0.1);
|
||||
osc.start(noteStart);
|
||||
osc.stop(noteStart + duration + 0.15);
|
||||
});
|
||||
|
||||
// Final sustained chord
|
||||
const chordNotes = [523.25, 659.25, 783.99]; // C major chord
|
||||
const chordStart = startTime + notes.length * duration;
|
||||
chordNotes.forEach((freq) => {
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = ctx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(masterGain);
|
||||
osc.frequency.value = freq;
|
||||
osc.type = 'triangle';
|
||||
gain.gain.setValueAtTime(0.3, chordStart);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, chordStart + 0.5);
|
||||
osc.start(chordStart);
|
||||
osc.stop(chordStart + 0.55);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fanfare sound - celebratory trumpet-like fanfare
|
||||
*/
|
||||
_playFanfareSound(ctx, startTime) {
|
||||
const masterGain = ctx.createGain();
|
||||
masterGain.connect(ctx.destination);
|
||||
masterGain.gain.value = 0.2;
|
||||
|
||||
// Fanfare pattern: G4, G4, G4, E4, G4, C5 (classic celebration pattern)
|
||||
const pattern = [
|
||||
{ freq: 392.00, duration: 0.1, delay: 0 }, // G4
|
||||
{ freq: 392.00, duration: 0.1, delay: 0.12 }, // G4
|
||||
{ freq: 392.00, duration: 0.15, delay: 0.24 }, // G4
|
||||
{ freq: 329.63, duration: 0.15, delay: 0.42 }, // E4
|
||||
{ freq: 392.00, duration: 0.15, delay: 0.6 }, // G4
|
||||
{ freq: 523.25, duration: 0.4, delay: 0.78 }, // C5 (long final note)
|
||||
];
|
||||
|
||||
pattern.forEach(({ freq, duration, delay }) => {
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = ctx.createGain();
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(masterGain);
|
||||
|
||||
osc.frequency.value = freq;
|
||||
osc.type = 'sawtooth';
|
||||
|
||||
const noteStart = startTime + delay;
|
||||
gain.gain.setValueAtTime(0.5, noteStart);
|
||||
gain.gain.setValueAtTime(0.5, noteStart + duration * 0.8);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, noteStart + duration);
|
||||
|
||||
osc.start(noteStart);
|
||||
osc.stop(noteStart + duration + 0.05);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Chime sound - simple pleasant bell chime
|
||||
*/
|
||||
_playChimeSound(ctx, startTime) {
|
||||
const masterGain = ctx.createGain();
|
||||
masterGain.connect(ctx.destination);
|
||||
masterGain.gain.value = 0.3;
|
||||
|
||||
// Bell-like sound using multiple harmonics
|
||||
const fundamental = 880; // A5
|
||||
const harmonics = [1, 2, 3, 4.2]; // Slight inharmonicity for bell-like quality
|
||||
|
||||
harmonics.forEach((harmonic, i) => {
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = ctx.createGain();
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(masterGain);
|
||||
|
||||
osc.frequency.value = fundamental * harmonic;
|
||||
osc.type = 'sine';
|
||||
|
||||
// Higher harmonics decay faster
|
||||
const amplitude = 0.5 / (i + 1);
|
||||
const decayTime = 0.8 / (i + 1);
|
||||
|
||||
gain.gain.setValueAtTime(amplitude, startTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, startTime + decayTime);
|
||||
|
||||
osc.start(startTime);
|
||||
osc.stop(startTime + decayTime + 0.1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Power up sound - ascending sweep with sparkle
|
||||
*/
|
||||
_playPowerUpSound(ctx, startTime) {
|
||||
const masterGain = ctx.createGain();
|
||||
masterGain.connect(ctx.destination);
|
||||
masterGain.gain.value = 0.25;
|
||||
|
||||
// Ascending sweep
|
||||
const osc1 = ctx.createOscillator();
|
||||
const gain1 = ctx.createGain();
|
||||
osc1.connect(gain1);
|
||||
gain1.connect(masterGain);
|
||||
osc1.type = 'sawtooth';
|
||||
osc1.frequency.setValueAtTime(200, startTime);
|
||||
osc1.frequency.exponentialRampToValueAtTime(1200, startTime + 0.3);
|
||||
gain1.gain.setValueAtTime(0.4, startTime);
|
||||
gain1.gain.exponentialRampToValueAtTime(0.01, startTime + 0.35);
|
||||
osc1.start(startTime);
|
||||
osc1.stop(startTime + 0.4);
|
||||
|
||||
// Sparkle notes at the end
|
||||
const sparkleNotes = [1318.5, 1567.98, 1975.5]; // E6, G6, B6
|
||||
sparkleNotes.forEach((freq, i) => {
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = ctx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(masterGain);
|
||||
osc.frequency.value = freq;
|
||||
osc.type = 'sine';
|
||||
|
||||
const noteStart = startTime + 0.25 + i * 0.05;
|
||||
gain.gain.setValueAtTime(0.3, noteStart);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, noteStart + 0.2);
|
||||
osc.start(noteStart);
|
||||
osc.stop(noteStart + 0.25);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo sound - sad descending "womp womp" style
|
||||
* Two descending tones that sound disappointed/sad
|
||||
*/
|
||||
_playUndoSound(ctx, startTime) {
|
||||
const masterGain = ctx.createGain();
|
||||
masterGain.connect(ctx.destination);
|
||||
masterGain.gain.value = 0.25;
|
||||
|
||||
// First "womp" - descending tone
|
||||
const osc1 = ctx.createOscillator();
|
||||
const gain1 = ctx.createGain();
|
||||
osc1.connect(gain1);
|
||||
gain1.connect(masterGain);
|
||||
osc1.type = 'triangle';
|
||||
osc1.frequency.setValueAtTime(311.13, startTime); // Eb4
|
||||
osc1.frequency.exponentialRampToValueAtTime(233.08, startTime + 0.25); // Bb3
|
||||
gain1.gain.setValueAtTime(0.6, startTime);
|
||||
gain1.gain.exponentialRampToValueAtTime(0.3, startTime + 0.2);
|
||||
gain1.gain.exponentialRampToValueAtTime(0.01, startTime + 0.3);
|
||||
osc1.start(startTime);
|
||||
osc1.stop(startTime + 0.35);
|
||||
|
||||
// Second "womp" - even lower descending tone (the sad part)
|
||||
const osc2 = ctx.createOscillator();
|
||||
const gain2 = ctx.createGain();
|
||||
osc2.connect(gain2);
|
||||
gain2.connect(masterGain);
|
||||
osc2.type = 'triangle';
|
||||
osc2.frequency.setValueAtTime(233.08, startTime + 0.3); // Bb3
|
||||
osc2.frequency.exponentialRampToValueAtTime(155.56, startTime + 0.7); // Eb3
|
||||
gain2.gain.setValueAtTime(0.5, startTime + 0.3);
|
||||
gain2.gain.exponentialRampToValueAtTime(0.25, startTime + 0.55);
|
||||
gain2.gain.exponentialRampToValueAtTime(0.01, startTime + 0.75);
|
||||
osc2.start(startTime + 0.3);
|
||||
osc2.stop(startTime + 0.8);
|
||||
|
||||
// Optional: add a subtle low vibrato for extra sadness
|
||||
const osc3 = ctx.createOscillator();
|
||||
const gain3 = ctx.createGain();
|
||||
osc3.connect(gain3);
|
||||
gain3.connect(masterGain);
|
||||
osc3.type = 'sine';
|
||||
osc3.frequency.setValueAtTime(116.54, startTime + 0.5); // Bb2 (sub bass)
|
||||
gain3.gain.setValueAtTime(0.15, startTime + 0.5);
|
||||
gain3.gain.exponentialRampToValueAtTime(0.01, startTime + 0.8);
|
||||
osc3.start(startTime + 0.5);
|
||||
osc3.stop(startTime + 0.85);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play an audio file from the www folder
|
||||
* Used for fart sounds (real audio files, not synthesized)
|
||||
* CC0 public domain from BigSoundBank.com
|
||||
* @param {string} filename - The audio file name (e.g., 'fart1.mp3')
|
||||
*/
|
||||
_playAudioFile(filename) {
|
||||
try {
|
||||
// Build the URL to the audio file in the www folder
|
||||
const audio = new Audio(`/local/taskmate/${filename}`);
|
||||
audio.volume = 1.0;
|
||||
audio.play().catch(e => {
|
||||
console.warn(`[TaskMate] Failed to play ${filename}:`, e);
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[TaskMate] Error preparing audio ${filename}:`, e);
|
||||
}
|
||||
/** Uploaded custom sounds (#856). Lives on the chores sensor; the attr
|
||||
* resolver merges it in, so the card reads it off the primary entity. */
|
||||
_customSounds() {
|
||||
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config?.entity))
|
||||
|| this.hass?.states?.[this.config?.entity]?.attributes || {};
|
||||
return attrs.custom_sounds || [];
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
|
||||
Reference in new issue
Block a user