diff --git a/src/schema/effect-list.ts b/src/schema/effect-list.ts index 9439be5..6599ca8 100644 --- a/src/schema/effect-list.ts +++ b/src/schema/effect-list.ts @@ -9,7 +9,6 @@ const BLEND_MODES = z.union([ z.literal('ADDITIVE'), z.literal('SUB_1'), // TODO verify z.literal('SUB_2'), // TODO verify - z.literal('EVERY_OTHER_LINE'), z.literal('EVERY_OTHER_PIXEL'), z.literal('XOR'), z.literal('ADJUSTABLE'), @@ -37,7 +36,7 @@ export const effectList = z.object({ init: z.string(), perFrame: z.string() }), - // TODO components: [] + // components: z.array([]) // TODO how to recurse? }).required(); export type EffectList = z.infer; diff --git a/src/schema/misc.ts b/src/schema/misc.ts index e028fb5..7152753 100644 --- a/src/schema/misc.ts +++ b/src/schema/misc.ts @@ -1,11 +1,11 @@ import { z } from 'zod'; -const MISC = z.literal('Misc'); +const MISC_GROUP = z.literal('Misc'); const RANGE_0_255 = z.number().int().min(0).max(255); export const bufferSave = z.object({ type: z.literal('BufferSave'), - group: MISC, + group: MISC_GROUP, action: z.union([ z.literal('SAVE'), z.literal('RESTORE'), @@ -33,13 +33,13 @@ export const bufferSave = z.object({ export const comment = z.object({ type: z.literal('Comment'), - group: MISC, + group: MISC_GROUP, text: z.string() }).required(); export const customBpm = z.object({ type: z.literal('CustomBPM'), - group: MISC, + group: MISC_GROUP, enabled: z.boolean(), mode: z.union([ z.literal('ARBITRARY'), @@ -53,7 +53,7 @@ export const customBpm = z.object({ export const setRenderMode = z.object({ type: z.literal('SetRenderMode'), - group: MISC, + group: MISC_GROUP, blend: z.union([ z.literal('BLEND_REPLACE'), z.literal('BLEND_ADDITIVE'), @@ -75,3 +75,5 @@ export type BufferSave = z.infer; export type Comment = z.infer; export type CustomBpm = z.infer; export type SetRenderMode = z.infer; + +export type MiscEffects = BufferSave | Comment | CustomBpm | SetRenderMode; diff --git a/src/schema/render.ts b/src/schema/render.ts new file mode 100644 index 0000000..6a4996c --- /dev/null +++ b/src/schema/render.ts @@ -0,0 +1,316 @@ +import { z } from 'zod'; + +const RENDER_GROUP = z.literal('Render'); +const HEX_COLOR = z.string().regex(/^#[0-9A-F]{6}$/); +const HEX_COLORS_16 = HEX_COLOR.array().min(1).max(16); +const RANGE_0_32 = z.number().int().min(0).max(32); +const RANGE_1_64 = z.number().int().min(1).max(64); +const RANGE_1_100 = z.number().int().min(1).max(100); +const RANGE_16_16 = z.number().int().min(-16).max(16); +const RANGE_50_50 = z.number().int().min(-50).max(50); +const RANGE_100_4095 = z.number().int().min(100).max(4095); +const RANGE_16_576 = z.number().int().min(16).max(576); +const RANGE_ANGLE = z.number().int().min(-90).max(91); +const POSITIONS_HORIZONTAL = z.union([ + z.literal('LEFT'), + z.literal('RIGHT'), + z.literal('CENTER'), +]); +const POSITIONS_VERTICAL = z.union([ + z.literal('TOP'), + z.literal('BOTTOM'), + z.literal('CENTER'), +]); +const NUMBERIC_BOOLEAN = z.union([ + z.literal(0), + z.literal(1), +]); + +export const bassSpin = z + .object({ + type: z.literal('BassSpin'), + group: RENDER_GROUP, + enabledLeft: z.boolean(), + enabledRight: z.boolean(), + colorLeft: HEX_COLOR, + colorRight: HEX_COLOR, + mode: z.union([z.literal('LINES'), z.literal('TRIANGLES')]), + }) + .required(); + +export const clearScreen = z + .object({ + type: z.literal('ClearScreen'), + group: RENDER_GROUP, + enabled: z.boolean(), + color: HEX_COLOR, + blendMode: z.union([z.literal('ADDITIVE'), z.literal('DEFAULT'), z.literal('FIFTY_FIFTY')]), + onlyFirst: z.boolean(), + }) + .required(); + +export const dotFountain = z + .object({ + type: z.literal('DotFountain'), + group: RENDER_GROUP, + rotationSpeed: RANGE_50_50, + colorTop: HEX_COLOR, + colorHigh: HEX_COLOR, + colorMid: HEX_COLOR, + colorLow: HEX_COLOR, + colorBottom: HEX_COLOR, + angle: RANGE_ANGLE, + }) + .required(); + +export const dotGrid = z + .object({ + type: z.literal('DotGrid'), + group: RENDER_GROUP, + colors: HEX_COLORS_16, + spacing: z.number().int().min(0).safe(), // TODO validate 0, UINT32_MAX + speedX: RANGE_16_16, + speedY: RANGE_16_16, + blendMode: z.union([ + z.literal('REPLACE'), + z.literal('ADDITIVE'), + z.literal('FIFTY_FIFTY'), + z.literal('DEFAULT'), + ]), // TODO dry + }) + .required(); + +export const dotPlane = z + .object({ + type: z.literal('DotPlane'), + group: RENDER_GROUP, + rotationSpeed: RANGE_50_50, + colorTop: HEX_COLOR, + colorHigh: HEX_COLOR, + colorMid: HEX_COLOR, + colorLow: HEX_COLOR, + colorBottom: HEX_COLOR, + angle: RANGE_ANGLE, + }) + .required(); + +export const movingParticle = z + .object({ + type: z.literal('MovingParticle'), + group: RENDER_GROUP, + enabled: z.boolean(), + onBeatSizeChange: z.boolean(), + color: HEX_COLOR, + distance: z.number().int().min(1).max(32), + particleSize: z.number().int().min(1).max(128), // TODO dry + onBeatParticleSize: z.number().int().min(1).max(128), // TODO dry + blendMode: z.union([ + z.literal('REPLACE'), + z.literal('ADDITIVE'), + z.literal('FIFTY_FIFTY'), + z.literal('DEFAULT'), + ]), // TODO dry + }) + .required(); + +export const onBeatClear = z + .object({ + type: z.literal('OnBeatClear'), + group: RENDER_GROUP, + color: HEX_COLOR, + blendMode: z.union([z.literal('REPLACE'), z.literal('FIFTY_FIFTY')]), + clearBeats: z.number().int().min(0).max(100), + }) + .required(); + +export const oscilliscopeStar = z + .object({ + type: z.literal('OscilliscopeStar'), + group: RENDER_GROUP, + audioChannel: POSITIONS_HORIZONTAL, + positionX: POSITIONS_HORIZONTAL, + colors: HEX_COLORS_16, + size: RANGE_0_32, + rotation: RANGE_16_16 + }) + .required(); + +export const ring = z + .object({ + type: z.literal('Ring'), + group: RENDER_GROUP, + audioChannel: POSITIONS_HORIZONTAL, + positionX: POSITIONS_HORIZONTAL, + colors: HEX_COLORS_16, + size: RANGE_1_64 + }) + .required(); + +export const rotatingStars = z + .object({ + type: z.literal('RotatingStars'), + group: RENDER_GROUP, + colors: HEX_COLORS_16 + }) + .required(); + +export const simple = z + .object({ + type: z.literal('Simple'), + group: RENDER_GROUP, + audioSource: z.union([ + z.literal('Waveform'), + z.literal('Spectrum'), + ]), // TODO dry, validate + renderType: z.union([ + z.literal('Dots'), + z.literal('Lines'), + z.literal('Solid'), + ]), // TODO dry, validate + audioChannel: POSITIONS_HORIZONTAL, + positionY: POSITIONS_VERTICAL, + colors: HEX_COLORS_16 + }) + .required(); + +export const starfield = z + .object({ + type: z.literal('Starfield'), + group: RENDER_GROUP, + enabled: NUMBERIC_BOOLEAN, + color: HEX_COLOR, + blendMode: z.union([ + z.literal('REPLACE'), + z.literal('ADDITIVE'), + z.literal('FIFTY_FIFTY'), + ]), + // WarpSpeed: 6, TODO + MaxStars_set: RANGE_100_4095, + onbeat: NUMBERIC_BOOLEAN, + // spdBeat: 4, // TODO + durFrames: RANGE_1_100 + }) + .required(); + +export const superScope = z + .object({ + type: z.literal('SuperScope'), + group: RENDER_GROUP, + code: z.object({ + init: z.string(), // TODO maxlength? + perFrame: z.string(), // TODO maxlength? + onBeat: z.string(), // TODO maxlength? + perPoint: z.string(), // TODO maxlength? + }), // TODO dry + audioChannel: POSITIONS_HORIZONTAL, + audioSource: z.union([ + z.literal('WAVEFORM'), + z.literal('SPECTRUM'), + ]), + colors: HEX_COLORS_16, + drawMode: z.union([ + z.literal('DOTS'), + z.literal('LINES'), + ]), // TODO dry, validate + }) + .required(); + +export const texer2 = z + .object({ + type: z.literal('TexerII'), + group: RENDER_GROUP, + imageSrc: z.string().max(260), // Windows 10 allows 32,767 + resizing: z.boolean(), + wrapAround: z.boolean(), + colorFiltering: z.boolean(), + code: z.object({ + init: z.string(), // TODO maxlength? + perFrame: z.string(), // TODO maxlength? + onBeat: z.string(), // TODO maxlength? + perPoint: z.string(), // TODO maxlength? + }), // TODO dry + }) + .required(); + +export const text = z + .object({ + type: z.literal('Text'), + group: RENDER_GROUP, + enabled: z.boolean(), + color: HEX_COLOR, + // blendMode: REPLACE, + onBeat: z.boolean(), + insertBlanks: z.boolean(), + randomPosition: z.boolean(), + // verticalAlign: CENTER, + // horizontalAlign: CENTER, + // onBeatSpeed: 15, + // normSpeed: 15, + // weight: DONTCARE, + italic: z.boolean(), + underline: z.boolean(), + strikeOut: z.boolean(), + // charSet: 0, + fontName: z.string(), // TODO maxlength? + text: z.string(), // TODO maxlength? + outline: z.boolean(), + outlineColor: HEX_COLOR, + // shiftX: 0, + // shiftY: 0, + // outlineShadowSize: 1, + randomWord: z.boolean(), + shadow: z.boolean() + }) + .required(); + +export const timescope = z + .object({ + type: z.literal('Timescope'), + group: RENDER_GROUP, + enabled: z.boolean(), + color: HEX_COLOR, + blendMode: z.union([ + z.literal('REPLACE'), + z.literal('ADDITIVE'), + z.literal('FIFTY_FIFTY'), + z.literal('DEFAULT'), + ]), // TODO dry, + bands: RANGE_16_576 + }) + .required(); + +export type BassSpin = z.infer; +export type ClearScreen = z.infer; +export type DotFountain = z.infer; +export type DotGrid = z.infer; +export type DotPlane = z.infer; +export type MovingParticle = z.infer; +export type OnBeatClear = z.infer; +export type OscilliscopeStar = z.infer; +export type Ring = z.infer; +export type RotatingStars = z.infer; +export type Simple = z.infer; +export type Starfield = z.infer; +export type SuperScope = z.infer; +export type Texer2 = z.infer; +export type Text = z.infer; +export type Timescope = z.infer; + +export type RenderEffects = + | BassSpin + | ClearScreen + | DotFountain + | DotGrid + | DotPlane + | MovingParticle + | OnBeatClear + | OscilliscopeStar + | Ring + | RotatingStars + | Simple + | Starfield + | SuperScope + | Texer2 + | Text + | Timescope +;