added basic weather gen
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -18,6 +18,7 @@
|
|||||||
"@sveltejs/kit": "^2.16.0",
|
"@sveltejs/kit": "^2.16.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||||
"@vitest/browser": "^3.2.3",
|
"@vitest/browser": "^3.2.3",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
@@ -2163,6 +2164,19 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitejs/plugin-basic-ssl": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitest/browser": {
|
"node_modules/@vitest/browser": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.2.4.tgz",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"@sveltejs/kit": "^2.16.0",
|
"@sveltejs/kit": "^2.16.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||||
"@vitest/browser": "^3.2.3",
|
"@vitest/browser": "^3.2.3",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
|
|||||||
@@ -1,37 +1,237 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Synth, Loop, type SynthOptions, getTransport } from 'tone';
|
import * as Tone from 'tone';
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
|
||||||
let { currentWeather } = $props();
|
// Component props with default values
|
||||||
|
let {temperature2m = 20, relativeHumidity2m = 50, cloudCover = 30, volume = -10, windSpeed10m = 0, isDay} = $props();
|
||||||
|
|
||||||
const synthOptions = {
|
// Component state using runes
|
||||||
|
let isPlaying = $state(false);
|
||||||
|
let currentChordIndex = $state(0);
|
||||||
|
let isInitialized = $state(false);
|
||||||
|
|
||||||
|
// Audio components
|
||||||
|
let synth: Tone.PolySynth | null = null;
|
||||||
|
let reverb: Tone.Reverb | null = null;
|
||||||
|
let delay: Tone.FeedbackDelay | null = null;
|
||||||
|
let phaser: Tone.Phaser | null = null;
|
||||||
|
let sequence: Tone.Sequence | null = null;
|
||||||
|
let gain: Tone.Gain | null = null;
|
||||||
|
|
||||||
|
|
||||||
|
const chordProgressions = [
|
||||||
|
[
|
||||||
|
{ time: "0:0:0", notes: ['C4', 'E4', 'G4', 'B4'] },
|
||||||
|
{ time: "0:1:0", notes: ['A3', 'C4', 'E4', 'G4'] },
|
||||||
|
{ time: "0:2:0", notes: ['F3', 'A3', 'C4', 'E4'] },
|
||||||
|
{ time: "0:3:0", notes: ['G3', 'B3', 'D4', 'F4'] }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ time: "0:0:0", notes: ['D4', 'F4', 'A4', 'C5'] },
|
||||||
|
{ time: "0:1:0", notes: ['G3', 'B3', 'D4', 'F4'] },
|
||||||
|
{ time: "0:2:0", notes: ['C4', 'E4', 'G4', 'B4'] },
|
||||||
|
{ time: "0:3:0", notes: ['A3', 'C4', 'E4', 'G4'] }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ time: "0:0:0", notes: ['E4', 'G4', 'B4', 'D5'] },
|
||||||
|
{ time: "0:1:0", notes: ['A3', 'C4', 'E4', 'G4'] },
|
||||||
|
{ time: "0:2:0", notes: ['D4', 'F4', 'A4', 'C5'] },
|
||||||
|
{ time: "0:3:0", notes: ['G3', 'B3', 'D4', 'F4'] }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ time: "0:0:0", notes: ['F3', 'A3', 'C4', 'E4'] },
|
||||||
|
{ time: "0:1:0", notes: ['E4', 'G4', 'B4', 'D5'] },
|
||||||
|
{ time: "0:2:0", notes: ['D4', 'F4', 'A4', 'C5'] },
|
||||||
|
{ time: "0:3:0", notes: ['C4', 'E4', 'G4', 'B4'] }
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
let currentProgression = $state(chordProgressions[0]);
|
||||||
|
|
||||||
|
|
||||||
|
// Derived reactive values using runes
|
||||||
|
const bpm = $derived((temperature2m));
|
||||||
|
const reverbWet = $derived(relativeHumidity2m/100);
|
||||||
|
const delayWet = $derived(Math.round(windSpeed10m)/10);
|
||||||
|
const phaserBase = $derived((1 / cloudCover) * 100);
|
||||||
|
|
||||||
|
// Initialize audio components
|
||||||
|
const initializeAudio = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
// Create dreamy synth
|
||||||
|
synth = new Tone.PolySynth(Tone.Synth, {
|
||||||
oscillator: {
|
oscillator: {
|
||||||
type: 'triangle'
|
type: isDay? 'triangle' : 'sine',
|
||||||
},
|
},
|
||||||
envelope: {
|
envelope: {
|
||||||
attack: 0.005,
|
attack: 1.5,
|
||||||
decay: 0.3,
|
decay: 1,
|
||||||
sustain: 0.1,
|
sustain: 0.7,
|
||||||
release: 0.1
|
release: 1.0,
|
||||||
|
},
|
||||||
|
volume: -20,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create reverb with long, dreamy tail
|
||||||
|
reverb = new Tone.Reverb({
|
||||||
|
decay: 16,
|
||||||
|
wet: reverbWet,
|
||||||
|
preDelay: 0.5,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
delay = new Tone.FeedbackDelay({
|
||||||
|
delayTime: '0.5',
|
||||||
|
feedback: delayWet
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a phaser
|
||||||
|
phaser = new Tone.Phaser({
|
||||||
|
frequency : phaserBase,
|
||||||
|
octaves : 5,
|
||||||
|
baseFrequency : 350
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create gain node
|
||||||
|
gain = new Tone.Gain(Tone.dbToGain(volume));
|
||||||
|
|
||||||
|
// Connect audio chain
|
||||||
|
synth.connect(phaser).connect(delay).connect(reverb).connect(gain).toDestination();
|
||||||
|
|
||||||
|
// Generate reverb impulse
|
||||||
|
await reverb.generate();
|
||||||
|
|
||||||
|
isInitialized = true;
|
||||||
|
console.log('Audio initialized successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize audio:', error);
|
||||||
}
|
}
|
||||||
} as SynthOptions;
|
};
|
||||||
|
|
||||||
// getTransport().scheduleRepeat((time) => {
|
// Start the chord sequence
|
||||||
// // use the callback time to schedule events
|
const startSequence = async (): Promise<void> => {
|
||||||
// osc.start(time).stop(time + 0.1);
|
try {
|
||||||
// }, '8n');
|
if (!isInitialized) {
|
||||||
// // transport must be started before it starts invoking events
|
await initializeAudio();
|
||||||
// Tone.Transport.start();
|
|
||||||
|
|
||||||
function setupAndPlay() {
|
|
||||||
getTransport().bpm.value = 60;
|
|
||||||
const synth = new Synth(synthOptions).toDestination();
|
|
||||||
const loop = new Loop((time) => {
|
|
||||||
synth.triggerAttackRelease('C2', '8n');
|
|
||||||
}, '8n').start(0);
|
|
||||||
getTransport().stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAndPlay();
|
await Tone.start();
|
||||||
|
|
||||||
|
if (sequence) {
|
||||||
|
sequence.dispose();
|
||||||
|
sequence = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set transport BPM
|
||||||
|
Tone.getTransport().bpm.value = bpm;
|
||||||
|
|
||||||
|
let progressionChangeCounter = 0;
|
||||||
|
|
||||||
|
sequence = new Tone.Sequence((time: number, chord) => {
|
||||||
|
if (synth && chord) {
|
||||||
|
synth!.triggerAttackRelease(chord.notes, '4n', time);
|
||||||
|
}
|
||||||
|
|
||||||
|
progressionChangeCounter++;
|
||||||
|
|
||||||
|
//Change progression every full cycle (4 chords) for variation
|
||||||
|
if (progressionChangeCounter >= currentProgression.length) {
|
||||||
|
currentChordIndex = (currentChordIndex + 1) % chordProgressions.length;
|
||||||
|
currentProgression = chordProgressions[currentChordIndex];
|
||||||
|
sequence!.events = currentProgression;
|
||||||
|
progressionChangeCounter = 0;
|
||||||
|
}
|
||||||
|
}, currentProgression, "4n");
|
||||||
|
|
||||||
|
sequence.start(0);
|
||||||
|
Tone.getTransport().start();
|
||||||
|
isPlaying = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error starting sequence:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stop the sequence
|
||||||
|
const stopSequence = (): void => {
|
||||||
|
if (sequence) {
|
||||||
|
sequence.stop();
|
||||||
|
sequence.dispose();
|
||||||
|
sequence = null;
|
||||||
|
}
|
||||||
|
Tone.getTransport().stop();
|
||||||
|
Tone.getTransport().cancel();
|
||||||
|
isPlaying = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle playback
|
||||||
|
const togglePlayback = async (): Promise<void> => {
|
||||||
|
if (isPlaying) {
|
||||||
|
stopSequence();
|
||||||
|
} else {
|
||||||
|
await startSequence();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reactive updates for environmental parameters using effects
|
||||||
|
$effect(() => {
|
||||||
|
if (reverb && isInitialized) {
|
||||||
|
reverb.wet.rampTo(reverbWet, 0.5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (phaser && isInitialized) {
|
||||||
|
phaser.frequency.rampTo(phaserBase, 0.5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (gain && isInitialized) {
|
||||||
|
gain.gain.rampTo(Tone.dbToGain(volume), 0.1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (isPlaying && isInitialized) {
|
||||||
|
Tone.getTransport().bpm.rampTo(bpm, 1.0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
onMount(() => {
|
||||||
|
initializeAudio();
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (sequence) {
|
||||||
|
sequence.dispose();
|
||||||
|
}
|
||||||
|
if (synth) {
|
||||||
|
synth.dispose();
|
||||||
|
}
|
||||||
|
if (reverb) {
|
||||||
|
reverb.dispose();
|
||||||
|
}
|
||||||
|
if (phaser) {
|
||||||
|
phaser.dispose();
|
||||||
|
}
|
||||||
|
if (gain) {
|
||||||
|
gain.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex min-h-screen flex-col items-center justify-center">WEATHERGEN</div>
|
|
||||||
|
<!-- TODO: ADD VIZUALZ https://www.npmjs.com/package/p5-svelte -->
|
||||||
|
<div class="controls-container">
|
||||||
|
<!-- Playback Control -->
|
||||||
|
<div class="playback-section">
|
||||||
|
<button
|
||||||
|
class="play-button {isPlaying ? 'playing' : ''}"
|
||||||
|
onclick = {togglePlayback}
|
||||||
|
disabled={!isInitialized}
|
||||||
|
>
|
||||||
|
{isPlaying ? 'Stop' : 'Play'} ✨
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
let position: GeolocationPosition | undefined = $state(undefined);
|
let position: GeolocationPosition | undefined = $state(undefined);
|
||||||
let error: GeolocationError | undefined = $state(undefined);
|
let error: GeolocationError | undefined = $state(undefined);
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
timeout: 5000, // milliseconds
|
||||||
|
maximumAge: 60 * 60 * 1000, // milliseconds
|
||||||
|
};
|
||||||
|
|
||||||
|
$inspect(error);
|
||||||
|
|
||||||
function flipGetPosition(): void {
|
function flipGetPosition(): void {
|
||||||
getPosition = true;
|
getPosition = true;
|
||||||
}
|
}
|
||||||
@@ -36,4 +44,4 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Geolocation {getPosition} bind:position bind:loading bind:error />
|
<Geolocation {options} {getPosition} bind:position bind:loading bind:error />
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
|
|
||||||
const { data }: PageProps = $props();
|
const { data }: PageProps = $props();
|
||||||
const currentWeather = data.current;
|
const currentWeather = data.current;
|
||||||
|
|
||||||
|
$inspect(currentWeather)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<WeatherGen {currentWeather}></WeatherGen>
|
|
||||||
|
<div class="flex min-h-screen flex-col items-center justify-center">
|
||||||
|
<WeatherGen {...currentWeather}></WeatherGen>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import tailwindcss from '@tailwindcss/vite';
|
|||||||
import devtoolsJson from 'vite-plugin-devtools-json';
|
import devtoolsJson from 'vite-plugin-devtools-json';
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
import basicSsl from '@vitejs/plugin-basic-ssl'
|
||||||
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(), sveltekit(), devtoolsJson()],
|
plugins: [tailwindcss(), sveltekit(), devtoolsJson(), basicSsl()],
|
||||||
test: {
|
test: {
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user