Add OSD with song name
This commit is contained in:
@@ -19,6 +19,90 @@ function applyResponse(value, response) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard uniform dictionary every fragment layer is compiled against.
|
||||
* Shared by ShaderLayer and the OSD plate so a text layer honours exactly the
|
||||
* same contract — palette, audio features and personality — as a scene.
|
||||
*/
|
||||
export function buildShaderUniforms(module, baseParams, seed) {
|
||||
const uniforms = {
|
||||
u_resolution: { value: new THREE.Vector2(1, 1) },
|
||||
u_aspect: { value: 1 },
|
||||
u_pixelScale: { value: 1 },
|
||||
u_time: { value: 0 },
|
||||
u_frame: { value: 0 },
|
||||
u_progress: { value: 0 },
|
||||
u_seed: { value: (seed >>> 0) % 100000 / 1000 },
|
||||
u_opacity: { value: 1 },
|
||||
u_colors: { value: Array.from({ length: 8 }, () => new THREE.Vector3(1, 1, 1)) },
|
||||
u_colorCount: { value: 1 },
|
||||
u_prev: { value: null },
|
||||
u_hasPrev: { value: 0 },
|
||||
};
|
||||
for (const name of AUDIO_UNIFORMS) uniforms[name] = { value: 0 };
|
||||
for (const [name, type] of Object.entries(SIGNATURE_UNIFORMS)) {
|
||||
const v = NEUTRAL_UNIFORMS[name];
|
||||
uniforms[name] = { value: type === 'vec2' ? new THREE.Vector2(v[0], v[1]) : v };
|
||||
}
|
||||
|
||||
for (const [name, def] of Object.entries(module.params || {})) {
|
||||
if (!def.uniform || def.type === 'palette') continue;
|
||||
const v = baseParams[name];
|
||||
uniforms[def.uniform] = {
|
||||
value: def.type === 'vec2'
|
||||
? new THREE.Vector2(v ? v[0] : 0, v ? v[1] : 0)
|
||||
: def.type === 'bool' ? (v ? 1 : 0) : (v || 0),
|
||||
};
|
||||
}
|
||||
return uniforms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the per-frame values into a fragment layer's uniforms: the frame, the
|
||||
* audio features, the palette and the personality. Shared by scene layers and
|
||||
* the OSD plate so they all see the same inputs — the same render path, so
|
||||
* what a scene honours a plate honours too.
|
||||
*/
|
||||
export function setFrameUniforms(layer, renderer, target, ctx) {
|
||||
const u = layer.uniforms;
|
||||
const { timeline, features, prevTexture } = ctx;
|
||||
const w = target ? target.width : renderer.width;
|
||||
const h = target ? target.height : renderer.height;
|
||||
|
||||
u.u_resolution.value.set(w, h);
|
||||
u.u_aspect.value = w / h;
|
||||
u.u_pixelScale.value = h / 1080; // reference height; keeps 720p ≡ 4K
|
||||
u.u_time.value = timeline.time;
|
||||
u.u_frame.value = timeline.frame;
|
||||
u.u_progress.value = timeline.progress;
|
||||
u.u_opacity.value = layer.opacity;
|
||||
|
||||
if (features) {
|
||||
for (const name of AUDIO_UNIFORMS) {
|
||||
const key = name.slice(2); // u_bandLow -> bandLow
|
||||
const v = features[key];
|
||||
u[name].value = v === undefined ? 0 : v;
|
||||
}
|
||||
}
|
||||
|
||||
const colors = layer.palette || [];
|
||||
u.u_colorCount.value = Math.max(1, Math.min(8, colors.length));
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const c = colors[i % Math.max(1, colors.length)];
|
||||
if (c) u.u_colors.value[i].set(c[0], c[1], c[2]);
|
||||
}
|
||||
|
||||
const signature = signatureUniforms(layer.personality);
|
||||
for (const [name, type] of Object.entries(SIGNATURE_UNIFORMS)) {
|
||||
const v = signature[name];
|
||||
if (type === 'vec2') u[name].value.set(v[0], v[1]);
|
||||
else u[name].value = v;
|
||||
}
|
||||
|
||||
u.u_prev.value = prevTexture || null;
|
||||
u.u_hasPrev.value = prevTexture ? 1 : 0;
|
||||
}
|
||||
|
||||
/** Common surface for shader layers and 3D layers, so Compositor holds one type. */
|
||||
export class Layer {
|
||||
constructor({ module, params = {}, seed = 1, opacity = 1, blend = 'normal' }) {
|
||||
@@ -99,89 +183,24 @@ export class ShaderLayer extends Layer {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
const uniforms = {
|
||||
u_resolution: { value: new THREE.Vector2(1, 1) },
|
||||
u_aspect: { value: 1 },
|
||||
u_pixelScale: { value: 1 },
|
||||
u_time: { value: 0 },
|
||||
u_frame: { value: 0 },
|
||||
u_progress: { value: 0 },
|
||||
u_seed: { value: (this.seed % 100000) / 1000 },
|
||||
u_opacity: { value: 1 },
|
||||
u_colors: { value: Array.from({ length: 8 }, () => new THREE.Vector3(1, 1, 1)) },
|
||||
u_colorCount: { value: 1 },
|
||||
u_prev: { value: null },
|
||||
u_hasPrev: { value: 0 },
|
||||
};
|
||||
for (const name of AUDIO_UNIFORMS) uniforms[name] = { value: 0 };
|
||||
for (const [name, type] of Object.entries(SIGNATURE_UNIFORMS)) {
|
||||
const v = NEUTRAL_UNIFORMS[name];
|
||||
uniforms[name] = { value: type === 'vec2' ? new THREE.Vector2(v[0], v[1]) : v };
|
||||
}
|
||||
|
||||
for (const [name, def] of Object.entries(this.module.params || {})) {
|
||||
if (!def.uniform || def.type === 'palette') continue;
|
||||
const v = this.baseParams[name];
|
||||
uniforms[def.uniform] = {
|
||||
value: def.type === 'vec2'
|
||||
? new THREE.Vector2(v ? v[0] : 0, v ? v[1] : 0)
|
||||
: def.type === 'bool' ? (v ? 1 : 0) : (v || 0),
|
||||
};
|
||||
}
|
||||
|
||||
this.uniforms = uniforms;
|
||||
this.uniforms = buildShaderUniforms(this.module, this.baseParams, this.seed);
|
||||
this.material = new THREE.ShaderMaterial({
|
||||
vertexShader: VERTEX_SHADER,
|
||||
fragmentShader: buildFragmentShader(this.module),
|
||||
uniforms,
|
||||
uniforms: this.uniforms,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
});
|
||||
}
|
||||
|
||||
render(renderer, target, ctx) {
|
||||
const { timeline, features, prevTexture } = ctx;
|
||||
const u = this.uniforms;
|
||||
const w = target ? target.width : renderer.width;
|
||||
const h = target ? target.height : renderer.height;
|
||||
|
||||
u.u_resolution.value.set(w, h);
|
||||
u.u_aspect.value = w / h;
|
||||
u.u_pixelScale.value = h / 1080; // reference height; keeps 720p ≡ 4K
|
||||
u.u_time.value = timeline.time;
|
||||
u.u_frame.value = timeline.frame;
|
||||
u.u_progress.value = timeline.progress;
|
||||
u.u_opacity.value = this.opacity;
|
||||
|
||||
if (features) {
|
||||
for (const name of AUDIO_UNIFORMS) {
|
||||
const key = name.slice(2); // u_bandLow -> bandLow
|
||||
const v = features[key];
|
||||
u[name].value = v === undefined ? 0 : v;
|
||||
}
|
||||
}
|
||||
|
||||
const colors = this.palette || [];
|
||||
u.u_colorCount.value = Math.max(1, Math.min(8, colors.length));
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const c = colors[i % Math.max(1, colors.length)];
|
||||
if (c) u.u_colors.value[i].set(c[0], c[1], c[2]);
|
||||
}
|
||||
|
||||
const signature = signatureUniforms(this.personality);
|
||||
for (const [name, type] of Object.entries(SIGNATURE_UNIFORMS)) {
|
||||
const v = signature[name];
|
||||
if (type === 'vec2') u[name].value.set(v[0], v[1]);
|
||||
else u[name].value = v;
|
||||
}
|
||||
|
||||
u.u_prev.value = prevTexture || null;
|
||||
u.u_hasPrev.value = prevTexture ? 1 : 0;
|
||||
const { features } = ctx;
|
||||
setFrameUniforms(this, renderer, target, ctx);
|
||||
|
||||
const resolved = this.resolveParams(features);
|
||||
for (const [name, def] of Object.entries(this.module.params || {})) {
|
||||
if (!def.uniform || def.type === 'palette') continue;
|
||||
const target_u = u[def.uniform];
|
||||
const target_u = this.uniforms[def.uniform];
|
||||
const v = resolved[name];
|
||||
if (v === undefined) continue;
|
||||
if (def.type === 'vec2') target_u.value.set(v[0], v[1]);
|
||||
|
||||
Reference in New Issue
Block a user