diff --git a/components/PatternDisplay.tsx b/components/PatternDisplay.tsx index f164e4a..5fe7953 100644 --- a/components/PatternDisplay.tsx +++ b/components/PatternDisplay.tsx @@ -1266,7 +1266,7 @@ export const PatternDisplay: React.FC = ({ textureResourcesRef.current = null; bezelTextureResourcesRef.current = null; - const shaderBase = './'; + const shaderBase = import.meta.env.BASE_URL; const shaderSource = await fetch(`${shaderBase}shaders/${shaderFile}`).then(res => res.text()); if (cancelled) return; const module = device.createShaderModule({ code: shaderSource }); diff --git a/hooks/useLibOpenMPT.ts b/hooks/useLibOpenMPT.ts index 8c84129..0408259 100644 --- a/hooks/useLibOpenMPT.ts +++ b/hooks/useLibOpenMPT.ts @@ -14,8 +14,8 @@ interface SyncDebugInfo { // Constants const DEFAULT_ROWS = 64; const DEFAULT_CHANNELS = 4; -// Use './' so the URL resolves relative to the page, correct in any deployment path -const DEFAULT_MODULE_URL = './4-mat_madness.mod'; +// Use Vite BASE_URL for correct resolution under subdirectory deployment +const DEFAULT_MODULE_URL = `${import.meta.env.BASE_URL}4-mat_madness.mod`; // Runtime base URL detection for subdirectory deployment (e.g., /xm-player/) // Vite's BASE_URL may be '/' at build time, so we detect actual path from window.location diff --git a/public/shaders/patternv0.50.wgsl b/public/shaders/patternv0.50.wgsl index ecb532a..96e4a9d 100644 --- a/public/shaders/patternv0.50.wgsl +++ b/public/shaders/patternv0.50.wgsl @@ -1,8 +1,22 @@ // patternv0.50.wgsl -// Three-Emitter LED Indicator System with Unified Lens Cap -// Top: Blue Note-On | Middle: Steady Note Color | Bottom: Amber Control -// Based on v0.49 (circular layout with padTopChannel=true) -// Note: Requires padTopChannel=true in PatternDisplay to shift music channels 1-32. +// Frosted Glass Circular – Vibrant Note Colours + Blue LED Indicator + Full-Height Caps +// +// Hybrid composition combining v0.48 (vibrant note-data colours from neonPalette) +// and v0.49 (blue LED indicator ring, solid housing, frosted glass caps). +// +// Per-step layering: +// 1. HOUSING (BEHINDS) — Solid dark-metallic body lit with vibrant neonPalette +// colours driven by real note pitch (purple, teal, green, +// orange, red, cyan). Activity from v0.48's distance-based +// energy sweep + trail + noteAge + tickOffset sub-step. +// 2. CAP — Full-height frosted acrylic glass (0.88 × 0.88) in the same vibrant +// hue as the housing. LED-under-glass model with white bevel rim. +// 3. DEPRESSION — On playhead hit: cap scales 4 % smaller + top inner-shadow. +// +// Channel 0: Blue LED indicator ring shows playhead proximity (from v0.49). +// +// Background: bezel.wgsl (hardware photo with dark centre + white frame). +// Transparent gaps + centre circle allow bezel to show through. struct Uniforms { numRows: u32, @@ -37,11 +51,11 @@ struct ChannelState { volume: f32, pan: f32, freq: f32, trigger: u32, noteAge: f struct VertexOut { @builtin(position) position: vec4, - @location(0) @interpolate(flat) row: u32, - @location(1) @interpolate(flat) channel: u32, - @location(2) @interpolate(linear) uv: vec2, - @location(3) @interpolate(flat) packedA: u32, - @location(4) @interpolate(flat) packedB: u32, + @location(0) @interpolate(flat) row: u32, + @location(1) @interpolate(flat) channel: u32, + @location(2) @interpolate(linear) uv: vec2, + @location(3) @interpolate(flat) packedA: u32, + @location(4) @interpolate(flat) packedB: u32, }; @vertex @@ -52,38 +66,33 @@ fn vs(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instance ); let numChannels = uniforms.numChannels; - let row = instanceIndex / numChannels; - let channel = instanceIndex % numChannels; + let row = instanceIndex / numChannels; + let channel = instanceIndex % numChannels; let invertedChannel = numChannels - 1u - channel; - let ringIndex = select(invertedChannel, channel, (uniforms.invertChannels == 1u)); - - let center = vec2(uniforms.canvasW * 0.5, uniforms.canvasH * 0.5); - let minDim = min(uniforms.canvasW, uniforms.canvasH); + let ringIndex = select(invertedChannel, channel, uniforms.invertChannels == 1u); + let center = vec2(uniforms.canvasW * 0.5, uniforms.canvasH * 0.5); + let minDim = min(uniforms.canvasW, uniforms.canvasH); let maxRadius = minDim * 0.45; let minRadius = minDim * 0.15; let ringDepth = (maxRadius - minRadius) / f32(numChannels); + let radius = minRadius + f32(ringIndex) * ringDepth; - let radius = minRadius + f32(ringIndex) * ringDepth; - - let totalSteps = 64.0; + let totalSteps = 64.0; let anglePerStep = 6.2831853 / totalSteps; - let theta = -1.570796 + f32(row % 64u) * anglePerStep; + let theta = -1.570796 + f32(row % 64u) * anglePerStep; let circumference = 2.0 * 3.14159265 * radius; - let arcLength = circumference / totalSteps; - - let btnW = arcLength * 0.95; - let btnH = ringDepth * 0.95; + let arcLength = circumference / totalSteps; + let btnW = arcLength * 0.95; + let btnH = ringDepth * 0.95; - let lp = quad[vertexIndex]; + let lp = quad[vertexIndex]; let localPos = (lp - 0.5) * vec2(btnW, btnH); let rotAng = theta + 1.570796; - let cA = cos(rotAng); - let sA = sin(rotAng); - + let cA = cos(rotAng); let sA = sin(rotAng); let rotX = localPos.x * cA - localPos.y * sA; let rotY = localPos.x * sA + localPos.y * cA; @@ -94,25 +103,27 @@ fn vs(@builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instance let clipY = 1.0 - (worldY / uniforms.canvasH) * 2.0; let idx = instanceIndex * 2u; - let a = cells[idx]; - let b = cells[idx + 1u]; + let a = cells[idx]; + let b = cells[idx + 1u]; var out: VertexOut; out.position = vec4(clipX, clipY, 0.0, 1.0); - out.row = row; - out.channel = channel; - out.uv = lp; - out.packedA = a; - out.packedB = b; + out.row = row; + out.channel = channel; + out.uv = lp; + out.packedA = a; + out.packedB = b; return out; } +// ── Helpers ─────────────────────────────────────────────────────────────────── + fn neonPalette(t: f32) -> vec3 { let a = vec3(0.5, 0.5, 0.5); let b = vec3(0.5, 0.5, 0.5); let c = vec3(1.0, 1.0, 1.0); let d = vec3(0.0, 0.33, 0.67); - let beatDrift = uniforms.beatPhase * 0.1; + let beatDrift = uniforms.beatPhase * 0.08; return a + b * cos(6.28318 * (c * (t + beatDrift) + d)); } @@ -467,4 +478,4 @@ fn fs(in: VertexOut) -> @location(0) vec4 { if (housingMask < 0.5) { return vec4(fs.borderColor, 0.0); } return vec4(finalColor, 1.0); -} +} \ No newline at end of file diff --git a/utils/remoteMedia.ts b/utils/remoteMedia.ts index 0039723..2262c5e 100644 --- a/utils/remoteMedia.ts +++ b/utils/remoteMedia.ts @@ -1,7 +1,7 @@ import type { MediaItem } from '../types'; // Adjust this path to match where you drop your files on the FTP -const REMOTE_MEDIA_BASE_URL = './media/'; +const REMOTE_MEDIA_BASE_URL = `${import.meta.env.BASE_URL}media/`; export const fetchRemoteMedia = async (): Promise => { try {