QOL: Move source code under the src directory. (#1318)

This commit is contained in:
Angelos Bouklis
2023-10-15 15:52:48 +03:00
committed by GitHub
parent 30c8dcf730
commit 7625a3aa52
159 changed files with 102 additions and 71 deletions

View File

@ -0,0 +1,9 @@
import { BrowserWindow } from 'electron';
import emptyPlayerStyle from './empty-player.css';
import { injectCSS } from '../utils';
export default (win: BrowserWindow) => {
injectCSS(win.webContents, emptyPlayerStyle);
};

55
src/plugins/visualizer/butterchurn.d.ts vendored Normal file
View File

@ -0,0 +1,55 @@
declare module 'butterchurn' {
interface VisualizerOptions {
width?: number;
height?: number;
meshWidth?: number;
meshHeight?: number;
pixelRatio?: number;
textureRatio?: number;
outputFXAA?: boolean;
}
class Visualizer {
constructor(audioContext: AudioContext, canvas: HTMLCanvasElement, opts: ButterchurnOptions);
loseGLContext(): void;
connectAudio(audioNode: AudioNode): void;
disconnectAudio(audioNode: AudioNode): void;
static overrideDefaultVars(baseValsDefaults: unknown, baseVals: unknown): unknown;
createQVars(): Record<string, WebAssembly.Global>;
createTVars(): Record<string, WebAssembly.Global>;
createPerFramePool(baseVals: unknown): Record<string, WebAssembly.Global>;
createPerPixelPool(baseVals: unknown): Record<string, WebAssembly.Global>;
createCustomShapePerFramePool(baseVals: unknown): Record<string, WebAssembly.Global>;
createCustomWavePerFramePool(baseVals: unknown): Record<string, WebAssembly.Global>;
static makeShapeResetPool(pool: Record<string, WebAssembly.Global>, variables: string[], idx: number): Record<string, WebAssembly.Global>;
static base64ToArrayBuffer(base64: string): ArrayBuffer;
loadPreset(presetMap: unknown, blendTime?: number): Promise<void>;
async loadWASMPreset(preset: unknown, blendTime: number): Promise<void>;
loadJSPreset(preset: unknown, blendTime: number): void;
loadExtraImages(imageData: unknown): void;
setRendererSize(width: number, height: number, opts?: VisualizerOptions): void;
setInternalMeshSize(width: number, height: number): void;
setOutputAA(useAA: boolean): void;
setCanvas(canvas: HTMLCanvasElement): void;
render(opts?: VisualizerOptions): unknown;
launchSongTitleAnim(text: string): void;
toDataURL(): string;
warpBufferToDataURL(): string;
}
interface ButterchurnOptions {
width?: number;
height?: number;
onlyUseWASM?: boolean;
}
export default class Butterchurn {
static createVisualizer(audioContext: AudioContext, canvas: HTMLCanvasElement, options?: ButterchurnOptions): Visualizer;
}
}
declare module 'butterchurn-presets' {
const presets: Record<string, unknown>;
export default presets;
}

View File

@ -0,0 +1,5 @@
#visualizer {
position: absolute;
z-index: 1;
background-color: black;
}

View File

@ -0,0 +1,83 @@
import { ButterchurnVisualizer as butterchurn, WaveVisualizer as wave, VudioVisualizer as vudio } from './visualizers';
import { Visualizer } from './visualizers/visualizer';
import defaultConfig from '../../config/defaults';
import type { ConfigType } from '../../config/dynamic';
export default (options: ConfigType<'visualizer'>) => {
const optionsWithDefaults = {
...defaultConfig.plugins.visualizer,
...options,
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let visualizerType: { new(...args: any[]): Visualizer<unknown> } = vudio;
if (optionsWithDefaults.type === 'wave') {
visualizerType = wave;
} else if (optionsWithDefaults.type === 'butterchurn') {
visualizerType = butterchurn;
}
document.addEventListener(
'audioCanPlay',
(e) => {
const video = document.querySelector<HTMLVideoElement & { captureStream(): MediaStream; }>('video');
if (!video) {
return;
}
const visualizerContainer = document.querySelector<HTMLElement>('#player');
if (!visualizerContainer) {
return;
}
let canvas = document.querySelector<HTMLCanvasElement>('#visualizer');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.id = 'visualizer';
visualizerContainer?.prepend(canvas);
}
const resizeCanvas = () => {
if (canvas) {
canvas.width = visualizerContainer.clientWidth;
canvas.height = visualizerContainer.clientHeight;
}
};
resizeCanvas();
const gainNode = e.detail.audioContext.createGain();
gainNode.gain.value = 1.25;
e.detail.audioSource.connect(gainNode);
const visualizer = new visualizerType(
e.detail.audioContext,
e.detail.audioSource,
visualizerContainer,
canvas,
gainNode,
video.captureStream(),
optionsWithDefaults,
);
const resizeVisualizer = (width: number, height: number) => {
resizeCanvas();
visualizer.resize(width, height);
};
resizeVisualizer(canvas.width, canvas.height);
const visualizerContainerObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
resizeVisualizer(entry.contentRect.width, entry.contentRect.height);
}
});
visualizerContainerObserver.observe(visualizerContainer);
visualizer.render();
},
{ passive: true },
);
};

View File

@ -0,0 +1,23 @@
import { BrowserWindow } from 'electron';
import { MenuTemplate } from '../../menu';
import { setMenuOptions } from '../../config/plugins';
import type { ConfigType } from '../../config/dynamic';
const visualizerTypes = ['butterchurn', 'vudio', 'wave']; // For bundling
export default (win: BrowserWindow, options: ConfigType<'visualizer'>): MenuTemplate => [
{
label: 'Type',
submenu: visualizerTypes.map((visualizerType) => ({
label: visualizerType,
type: 'radio',
checked: options.type === visualizerType,
click() {
options.type = visualizerType;
setMenuOptions('visualizer', options);
},
})),
},
];

View File

@ -0,0 +1,63 @@
import Butterchurn from 'butterchurn';
import ButterchurnPresets from 'butterchurn-presets';
import { Visualizer } from './visualizer';
import { ConfigType } from '../../../config/dynamic';
class ButterchurnVisualizer extends Visualizer<Butterchurn> {
name = 'butterchurn';
visualizer: ReturnType<typeof Butterchurn.createVisualizer>;
private readonly renderingFrequencyInMs: number;
constructor(
audioContext: AudioContext,
audioSource: MediaElementAudioSourceNode,
visualizerContainer: HTMLElement,
canvas: HTMLCanvasElement,
audioNode: GainNode,
stream: MediaStream,
options: ConfigType<'visualizer'>,
) {
super(
audioContext,
audioSource,
visualizerContainer,
canvas,
audioNode,
stream,
options,
);
this.visualizer = Butterchurn.createVisualizer(
audioContext,
canvas,
{
width: canvas.width,
height: canvas.height,
}
);
const preset = ButterchurnPresets[options.butterchurn.preset];
this.visualizer.loadPreset(preset, options.butterchurn.blendTimeInSeconds);
this.visualizer.connectAudio(audioNode);
this.renderingFrequencyInMs = options.butterchurn.renderingFrequencyInMs;
}
resize(width: number, height: number) {
this.visualizer.setRendererSize(width, height);
}
render() {
const renderVisualizer = () => {
requestAnimationFrame(renderVisualizer);
this.visualizer.render();
};
setTimeout(renderVisualizer, this.renderingFrequencyInMs);
}
}
export default ButterchurnVisualizer;

View File

@ -0,0 +1,5 @@
import ButterchurnVisualizer from './butterchurn';
import VudioVisualizer from './vudio';
import WaveVisualizer from './wave';
export { ButterchurnVisualizer, VudioVisualizer, WaveVisualizer };

View File

@ -0,0 +1,22 @@
import type { ConfigType } from '../../../config/dynamic';
export abstract class Visualizer<T> {
/**
* The name must be the same as the file name.
*/
abstract name: string;
abstract visualizer: T;
protected constructor(
audioContext: AudioContext,
audioSource: MediaElementAudioSourceNode,
visualizerContainer: HTMLElement,
canvas: HTMLCanvasElement,
audioNode: GainNode,
stream: MediaStream,
options: ConfigType<'visualizer'>,
) {}
abstract resize(width: number, height: number): void;
abstract render(): void;
}

View File

@ -0,0 +1,52 @@
import Vudio from 'vudio/umd/vudio';
import { Visualizer } from './visualizer';
import type { ConfigType } from '../../../config/dynamic';
class VudioVisualizer extends Visualizer<Vudio> {
name = 'vudio';
visualizer: Vudio;
constructor(
audioContext: AudioContext,
audioSource: MediaElementAudioSourceNode,
visualizerContainer: HTMLElement,
canvas: HTMLCanvasElement,
audioNode: GainNode,
stream: MediaStream,
options: ConfigType<'visualizer'>,
) {
super(
audioContext,
audioSource,
visualizerContainer,
canvas,
audioNode,
stream,
options,
);
this.visualizer = new Vudio(stream, canvas, {
width: canvas.width,
height: canvas.height,
// Visualizer config
...options,
});
this.visualizer.dance();
}
resize(width: number, height: number) {
this.visualizer.setOption({
width,
height,
});
}
render() {
}
}
export default VudioVisualizer;

View File

@ -0,0 +1,51 @@
import { Wave } from '@foobar404/wave';
import { Visualizer } from './visualizer';
import type { ConfigType } from '../../../config/dynamic';
class WaveVisualizer extends Visualizer<Wave> {
name = 'wave';
visualizer: Wave;
constructor(
audioContext: AudioContext,
audioSource: MediaElementAudioSourceNode,
visualizerContainer: HTMLElement,
canvas: HTMLCanvasElement,
audioNode: GainNode,
stream: MediaStream,
options: ConfigType<'visualizer'>,
) {
super(
audioContext,
audioSource,
visualizerContainer,
canvas,
audioNode,
stream,
options,
);
this.visualizer = new Wave(
{ context: audioContext, source: audioSource },
canvas,
);
for (const animation of options.wave.animations) {
const TargetVisualizer = this.visualizer.animations[animation.type as keyof typeof this.visualizer.animations];
this.visualizer.addAnimation(
new TargetVisualizer(animation.config as never), // Magic of Typescript
);
}
}
resize(_: number, __: number) {
}
render() {
}
}
export default WaveVisualizer;

34
src/plugins/visualizer/vudio.d.ts vendored Normal file
View File

@ -0,0 +1,34 @@
declare module 'vudio/umd/vudio' {
interface NoneWaveformOptions {
maxHeight?: number;
minHeight?: number;
spacing?: number;
color?: string | string[];
shadowBlur?: number;
shadowColor?: string;
fadeSide?: boolean;
}
interface WaveformOptions extends NoneWaveformOptions{
horizontalAlign: 'left' | 'center' | 'right';
verticalAlign: 'top' | 'middle' | 'bottom';
}
interface VudioOptions {
effect?: 'waveform' | 'circlewave' | 'circlebar' | 'lighting';
accuracy?: number;
width?: number;
height?: number;
waveform?: WaveformOptions
}
class Vudio {
constructor(audio: HTMLAudioElement | MediaStream, canvas: HTMLCanvasElement, options: VudioOptions = {});
dance(): void;
pause(): void;
setOption(options: VudioOptions): void;
}
export default Vudio;
}