diff --git a/src/Audio/Audio.ts b/src/Audio/Audio.ts index 2b138f86..e9f63880 100644 --- a/src/Audio/Audio.ts +++ b/src/Audio/Audio.ts @@ -22,7 +22,6 @@ export class Audio { if (!this.source){ throw new Error("Source not created yet!"); } - this.source.connect(node); this.nodes.push(node); } @@ -36,7 +35,7 @@ export class Audio { } } - public ConnectToContext(audioContext: AudioContext) { + public ConnectToContext(audioContext: AudioContext, howToConnectFunction?: (nodes: AudioNode[], source: AudioBufferSourceNode) => void) { if (!this.source){ throw new Error("Source not created yet!"); } @@ -45,12 +44,17 @@ export class Audio { } this._connectedToContext = true; if (this.nodes.length > 0){ - this.nodes.forEach((node, index) => { - this.source!.connect(node); - if (!(node instanceof AnalyserNode)) { - node.connect(audioContext.destination); - } - }); + if (howToConnectFunction){ + howToConnectFunction(this.nodes, this.source); + } + else { + this.nodes.forEach((node, index) => { + this.source!.connect(node); + if (!(node instanceof AnalyserNode)) { + node.connect(audioContext.destination); + } + }); + } } else { this.source.connect(audioContext.destination); diff --git a/src/Audio/AudioEngine.ts b/src/Audio/AudioEngine.ts index 9a9d100e..4659d958 100644 --- a/src/Audio/AudioEngine.ts +++ b/src/Audio/AudioEngine.ts @@ -65,10 +65,32 @@ export class AudioEngine { let gain = this.audioContext.createGain(); gain.gain.value = 0; let analyzer = this.audioContext.createAnalyser(); - analyzer.fftSize = 256; + analyzer.fftSize = 1024; + let lowpassFilter = this.audioContext.createBiquadFilter(); + lowpassFilter.type = 'lowpass'; + lowpassFilter.frequency.value = 75; // Set cutoff frequency to 200 Hz + lowpassFilter.Q.value = 1; audio.AddAudioNode(analyzer); audio.AddAudioNode(gain); - audio.ConnectToContext(this.audioContext); + audio.AddAudioNode(lowpassFilter); + audio.ConnectToContext(this.audioContext, (nodes, source) => { + let analyzerNode: AnalyserNode; + nodes.forEach((node) => { + if (node instanceof GainNode) { + source.connect(node); + node.connect(this.audioContext.destination); + } + if (node instanceof AnalyserNode) { + analyzerNode = node; + } + }); + nodes.forEach((node) => { + if (node instanceof BiquadFilterNode) { + source.connect(node); + node.connect(analyzerNode); + } + }); + }); audio.Play(); if (audio.playingCallback){ audio.playingCallback(); diff --git a/src/Elements/AudioVisualizers/LogoVisualizer.ts b/src/Elements/AudioVisualizers/LogoVisualizer.ts index 9211490b..6f9734da 100644 --- a/src/Elements/AudioVisualizers/LogoVisualizer.ts +++ b/src/Elements/AudioVisualizers/LogoVisualizer.ts @@ -3,6 +3,7 @@ import {MapAudio} from "../../Audio/Audio"; import * as PIXI from "pixi.js"; import {data} from "browserslist"; import {MathUtil} from "../../Util/MathUtil"; +import {UniformData} from "pixi.js"; export class LogoVisualizer extends PIXI.Container { @@ -12,7 +13,7 @@ export class LogoVisualizer extends PIXI.Container { private readonly index_change = 5; // The maximum length of each bar in the visualiser. Will be reduced when kiai is not activated. - private readonly bar_length = 2; + private readonly bar_length = 5; // The number of bars in one rotation of the visualiser. private bars_per_visualiser = 200; @@ -29,36 +30,74 @@ export class LogoVisualizer extends PIXI.Container { // The minimum amplitude to show a bar. private readonly amplitude_dead_zone = 1 / this.bar_length; + public alphaMultiplier = 1; + protected audio!: MapAudio; protected analyzer!: AnalyserNode; protected bufferLength!: number; - protected audioData!: Uint8Array + protected amplitudes!: Uint8Array + + protected temporalAmplitudes: Uint8Array = new Uint8Array(256); + protected frequencyAmplitudes: Uint8Array = new Uint8Array(256); + public audioData: Uint8Array = new Uint8Array(256); protected graphics: PIXI.Graphics = new PIXI.Graphics(); + private indexOffset = 0; + public start() { Main.AudioEngine.addMusicChangeEventListener(() => this.initVisualizer()); + this.graphics.blendMode = "add"; this.addChild(this.graphics); + this.graphics.eventMode = "none"; + this.eventMode = "none"; + setInterval(() => {this.updateAmplitudes()}, this.time_between_updates); } - public initVisualizer() { + private updateAmplitudes() { + let half = this.amplitudes.length/2; + this.amplitudes.slice(100, half+100); + for (let i = 3; i < this.amplitudes.length; i++) { + this.temporalAmplitudes[i] = this.amplitudes[i]; + } + for (let i = 0; i < this.bars_per_visualiser; i++) { + let targetAmplitude = (this.temporalAmplitudes[(i + this.indexOffset) % this.bars_per_visualiser]) * 0.5; + if (targetAmplitude > this.frequencyAmplitudes[i]) { + this.frequencyAmplitudes[i] = targetAmplitude; + } + } + this.indexOffset = (this.indexOffset + this.index_change) % this.bars_per_visualiser; + } + + private initVisualizer() { this.audio = Main.AudioEngine.GetCurrentPlayingMusic(); const analyzerNodes = this.audio.GetNode(AnalyserNode); if (analyzerNodes == null) { throw new Error("Couldn't find any AnalyzerNode on Audio Object!"); } this.analyzer = analyzerNodes[0]; - this.bufferLength = this.analyzer.frequencyBinCount; + this.bufferLength = 256; this.bars_per_visualiser = this.bufferLength; - this.audioData = new Uint8Array(this.bufferLength); + this.amplitudes = new Uint8Array(this.bufferLength); + } public draw(ticker: PIXI.Ticker) { this.graphics.clear(); - this.analyzer.getByteFrequencyData(this.audioData); + this.analyzer.getByteFrequencyData(this.amplitudes); + let decayFactor = ticker.deltaMS * this.decay_per_millisecond; + for (let i = 0; i < this.bars_per_visualiser; i++) { + //3% of extra bar length to make it a little faster when bar is almost at it's minimum + this.frequencyAmplitudes[i] -= decayFactor * (this.frequencyAmplitudes[i] + 0.03); + if (this.frequencyAmplitudes[i] < 0) { + this.frequencyAmplitudes[i] = 0; + } + } + this.audioData = new Uint8Array(this.frequencyAmplitudes); + for (let j = 0; j < this.visualiser_rounds; j++){ for (let i = 0; i < this.bars_per_visualiser; i++){ if (this.audioData[i] < this.amplitude_dead_zone){ @@ -75,14 +114,12 @@ export class LogoVisualizer extends PIXI.Container { x: LogoVisualizer.size * Math.sqrt(2 * (1 - Math.cos(MathUtil.DegreesToRadians(360 / this.bars_per_visualiser)))) / 2, y: this.bar_length * this.audioData[i] }; - // The distance between the position and the sides of the bar. - let bottomOffset = {x: -rotationSin * barSize.x / 2, y: rotationCos * barSize.x / 2}; // The distance between the bottom side of the bar and the top side. let amplitudeOffset = {x: rotationCos * barSize.y, y: rotationSin * barSize.y}; this.graphics.moveTo(barPosition.x, barPosition.y); this.graphics.lineTo(barPosition.x + amplitudeOffset.x, barPosition.y + amplitudeOffset.y); - this.graphics.stroke({color: "rgba(255, 255, 255, 0.2)", width: 10}); + this.graphics.stroke({color: "rgba(255, 255, 255, "+0.2 * this.alphaMultiplier+")", width: 10}); } } diff --git a/src/Elements/MainMenu/OsuCircle/OsuCircle.ts b/src/Elements/MainMenu/OsuCircle/OsuCircle.ts index 0ebd7d95..c9669c92 100644 --- a/src/Elements/MainMenu/OsuCircle/OsuCircle.ts +++ b/src/Elements/MainMenu/OsuCircle/OsuCircle.ts @@ -18,6 +18,7 @@ export class OsuCircle extends PIXI.Container { private readonly parallaxContainer: PIXI.Container = new PIXI.Container(); private readonly menu: Menu = new Menu(); private isBeingHovered = false; + private readonly defaultVisualizerAlpha = 0.5; public constructor() { super(); @@ -30,6 +31,7 @@ export class OsuCircle extends PIXI.Container { let scale = 0.6; this.visualizer.position.set(-LogoVisualizer.size/3.35, -LogoVisualizer.size/3.35); this.visualizer.scale.set(scale); + this.visualizer.alphaMultiplier = this.defaultVisualizerAlpha; this.beatContainer.addChild(this.visualizer); let mask = PIXI.Sprite.from("mainMenu.logoMask"); @@ -137,9 +139,37 @@ export class OsuCircle extends PIXI.Container { } }); + setInterval(() => {this.onNewBeat()}, 375) + } + private onNewBeat() { + let beatLength = 375; + let maxAmplitude = 0; + this.visualizer.audioData.forEach((num) => { + if (maxAmplitude < num) { + maxAmplitude = num; + } + }); + let amplitudeAdjust = Math.min(1, 0.4 + maxAmplitude); + ease.add(this.beatContainer, {scale: 1 - 0.02 * amplitudeAdjust}, {ease: "linear", duration: 60}).once("complete", + () => { + ease.add(this.beatContainer, {scale: 1}, {ease: "easeOutQuint", duration: beatLength*2}); + }); + ease.add(this.triangles.flash, {alpha: 0.2*amplitudeAdjust}, {duration: 60, ease:"linear"}).once("complete", + () => { + ease.add(this.triangles.flash, {alpha: 0}, {duration: beatLength}) + }); + let dummy = new PIXI.Container(); + let visualizerEase = ease.add(dummy, {alpha: this.defaultVisualizerAlpha * 1.8 * amplitudeAdjust}, + {duration: 60, ease: "linear"}).on("each", () => {this.visualizer.alphaMultiplier = dummy.alpha}); + visualizerEase.once("complete", () => { + ease.add(dummy, {alpha: this.defaultVisualizerAlpha}, {duration: beatLength, ease: "linear"}).on("each", () => { + this.visualizer.alphaMultiplier = dummy.alpha + }); + }); } + public onResize() { this.menu.onResize(); } diff --git a/src/Elements/MainMenu/OsuCircle/Triangles.ts b/src/Elements/MainMenu/OsuCircle/Triangles.ts index 88488a01..1e996a4e 100644 --- a/src/Elements/MainMenu/OsuCircle/Triangles.ts +++ b/src/Elements/MainMenu/OsuCircle/Triangles.ts @@ -7,9 +7,7 @@ export class Triangles extends PIXI.Container{ private triangles: Triangle[] = []; private triangleGenInterval: NodeJS.Timeout; private graphics: PIXI.Graphics = new PIXI.Graphics(); - private pulseAnimation: EaseOutSine; - private pulseAnimationFlash: EaseOutSine; - private flash: PIXI.Sprite; + public flash: PIXI.Sprite; public constructor() { super(); @@ -53,14 +51,6 @@ export class Triangles extends PIXI.Container{ this.flash.blendMode = "add"; this.addChild(this.flash); - this.pulseAnimation = new EaseOutSine(0, true, 0); - this.pulseAnimationFlash = new EaseOutSine(0, true, 0); - let playingAudio = Main.AudioEngine.GetCurrentPlayingMusic(); - if (playingAudio != null) { - this.pulseAnimation = new EaseOutSine(375, true, playingAudio.timeStarted); - this.pulseAnimationFlash = new EaseOutSine(375, true, playingAudio.timeStarted); - } - } public destroy(options?: PIXI.DestroyOptions) { @@ -68,10 +58,7 @@ export class Triangles extends PIXI.Container{ } public draw(ticker: PIXI.Ticker) { - this.pulseAnimation.update(); - this.pulseAnimationFlash.update(); if (!this.destroyed){ - this.flash.alpha = this.pulseAnimationFlash.getValue()/7; if (document.hasFocus()){ this.graphics.clear(); this.graphics.rect(0, 0, 1024, 1024);