diff --git a/README.md b/README.md index 64c922c..35d9be8 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Inspired by [Friture: A real-time audio analyzer](https://friture.org/) and [Pra ## Features - **Real-time Audio Analysis**: Standard FFT-based frequency analysis + - **Multiple Visualization Modes**: - Linear, Logarithmic, and Mel frequency scales - Customizable frequency range (20Hz - 20kHz) @@ -30,6 +31,8 @@ Inspired by [Friture: A real-time audio analyzer](https://friture.org/) and [Pra - Adjustable scroll speed - Multiple interpolation modes +- **Mobile Friendly** + ## Usage diff --git a/index.html b/index.html index ae352c7..66d3d89 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,8 @@ - + + Paul1365972's Spectrogram diff --git a/package.json b/package.json index bf72e5f..77592f5 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "vite build", "preview": "vite preview --open", "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", - "format": "prettier --write 'src/**/*.{ts,svelte,css}' 'public/**/*.webmanifest' '*.{json,js,ts,html}'" + "format": "prettier --write 'src/**/*.{ts,svelte,css}' 'public/**/*.json' '*.{json,js,ts,html}'" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.3", diff --git a/public/site.webmanifest b/public/manifest.json similarity index 71% rename from public/site.webmanifest rename to public/manifest.json index 577641e..08a2915 100644 --- a/public/site.webmanifest +++ b/public/manifest.json @@ -1,6 +1,7 @@ { "name": "Paul1365972's Spectrogram", "short_name": "Spectrogram", + "description": "A spectrogram with pitch estimation and lots of configuration options", "icons": [ { "src": "/android-chrome-192x192.png", @@ -15,5 +16,6 @@ ], "theme_color": "#ffffff", "background_color": "#ffffff", - "display": "standalone" + "display": "standalone", + "start_url": "/" } diff --git a/src/components/DualSlider.svelte b/src/components/DualSlider.svelte index 7515a87..0b87397 100644 --- a/src/components/DualSlider.svelte +++ b/src/components/DualSlider.svelte @@ -12,14 +12,17 @@ let lowerPercentage = $derived(inverseLogScale($settings.lowerFrequency, MIN_FREQ, MAX_FREQ)) let upperPercentage = $derived(inverseLogScale($settings.upperFrequency, MIN_FREQ, MAX_FREQ)) - function handleDrag(event: MouseEvent, isLower: boolean) { + function handleStart(event: MouseEvent | TouchEvent, isLower: boolean) { + event.preventDefault() const slider = event.currentTarget as HTMLElement const rect = slider.parentElement!.getBoundingClientRect() - const startX = event.clientX + const startX = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX const startPos = isLower ? lowerPercentage : upperPercentage - function onMove(moveEvent: MouseEvent) { - const delta = (moveEvent.clientX - startX) / rect.width + function onMove(moveEvent: MouseEvent | TouchEvent) { + const currentX = + moveEvent instanceof MouseEvent ? moveEvent.clientX : moveEvent.touches[0].clientX + const delta = (currentX - startX) / rect.width let newPercentage = Math.max(0, Math.min(1, startPos + delta)) if (isLower) { @@ -31,16 +34,20 @@ } } - function onUp() { + function onEnd() { window.removeEventListener('mousemove', onMove) - window.removeEventListener('mouseup', onUp) + window.removeEventListener('mouseup', onEnd) + window.removeEventListener('touchmove', onMove) + window.removeEventListener('touchend', onEnd) } window.addEventListener('mousemove', onMove) - window.addEventListener('mouseup', onUp) + window.addEventListener('mouseup', onEnd) + window.addEventListener('touchmove', onMove) + window.addEventListener('touchend', onEnd) } - function handleDoubleClick(event: MouseEvent, lower: boolean, upper: boolean) { + function handleDoubleClick(event: MouseEvent | TouchEvent, lower: boolean, upper: boolean) { event.stopPropagation() const defaultSettings = getDefaultSettings() if (lower && defaultSettings.lowerFrequency < $settings.upperFrequency) { @@ -68,7 +75,8 @@ - +
+ + + {#if isOpen} + + {/if}
diff --git a/src/components/Spectrogram.svelte b/src/components/Spectrogram.svelte index c2f4bfb..ee729ad 100644 --- a/src/components/Spectrogram.svelte +++ b/src/components/Spectrogram.svelte @@ -42,11 +42,14 @@ }) async function init() { + window.removeEventListener('mousedown', init) + window.removeEventListener('touchstart', init) if (!initialized) { await audioManager.initialize() await estimatorManager.initialize() initialized = true requestAnimationFrame(render) + } } @@ -68,6 +71,48 @@ } } + function updateOscillatorFrequency() { + const percentage = 1.0 - mousePosition[1] / window.innerHeight + const freq = scale(percentage, settings.scala, settings.lowerFrequency, settings.upperFrequency) + audioManager?.setOscillatorFrequency(freq) + } + + function handleStart(event: MouseEvent | TouchEvent) { + if ((event instanceof MouseEvent && event.button === 0) || event instanceof TouchEvent) { + if (event instanceof MouseEvent) { + event.preventDefault() + mousePosition = [event.clientX, event.clientY] + } else if (event instanceof TouchEvent) { + const touch = event.touches[0] + mousePosition = [touch.clientX, touch.clientY] + } + toneEnabled = true + updateOscillatorFrequency() + audioManager?.setGain(settings.volume / 100) + } + } + + function handleMove(event: MouseEvent | TouchEvent) { + if (event instanceof MouseEvent) { + event.preventDefault() + mousePosition = [event.clientX, event.clientY] + } else if (event instanceof TouchEvent) { + const touch = event.touches[0] + mousePosition = [touch.clientX, touch.clientY] + } + if (toneEnabled) { + updateOscillatorFrequency() + } + } + + function handleEnd(event: MouseEvent | TouchEvent) { + if ((event instanceof MouseEvent && event.button === 0) || event instanceof TouchEvent) { + event.preventDefault() + toneEnabled = false + audioManager?.setGain(0) + } + } + function render() { settings = get(settingsStore) if (!paused) { @@ -106,26 +151,13 @@ { - mousePosition = [e.clientX, e.clientY] - const percentage = 1.0 - (1.0 * mousePosition[1]) / window.innerHeight - const freq = scale(percentage, settings.scala, settings.lowerFrequency, settings.upperFrequency) - audioManager?.setOscillatorFrequency(freq) - }} - onmousedown={(e) => { - if (e.button === 0) { - toneEnabled = true - audioManager?.setGain(settings.volume / 100) - e.preventDefault() - } - }} - onmouseup={(e) => { - if (e.button === 0) { - toneEnabled = false - audioManager?.setGain(0) - e.preventDefault() - } - }} + onmousemove={handleMove} + onmousedown={handleStart} + onmouseup={handleEnd} + ontouchstart={handleStart} + ontouchmove={handleMove} + ontouchend={handleEnd} + ontouchcancel={handleEnd} > @@ -134,5 +166,9 @@ width: 100%; height: 100%; aspect-ratio: unset; + touch-action: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; } diff --git a/src/lib/renderer.ts b/src/lib/renderer.ts index 8d3de17..8a4e9c4 100644 --- a/src/lib/renderer.ts +++ b/src/lib/renderer.ts @@ -162,7 +162,7 @@ export class Renderer { // Constants for the feedback box const boxWidth = 200 const boxHeight = 120 - const x = this.width - 60 - boxWidth + const x = this.width - 100 - boxWidth const y = 10 // Draw background diff --git a/src/lib/spectrogram_renderer.ts b/src/lib/spectrogram_renderer.ts index bc29c89..ea3821f 100644 --- a/src/lib/spectrogram_renderer.ts +++ b/src/lib/spectrogram_renderer.ts @@ -162,7 +162,7 @@ export class SpectrogramRenderer { ` const fragmentShaderSource = ` - precision mediump float; + precision highp float; varying vec2 texCoord; uniform sampler2D audioData; uniform sampler2D colorMapTexture;