diff --git a/packages/app/public/assets/default_bg/dots.svg b/packages/app/public/assets/default_bg/dots.svg
new file mode 100644
index 00000000..0becad4e
--- /dev/null
+++ b/packages/app/public/assets/default_bg/dots.svg
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/packages/app/public/assets/default_bg/hideout.svg b/packages/app/public/assets/default_bg/hideout.svg
new file mode 100644
index 00000000..7b5eae54
--- /dev/null
+++ b/packages/app/public/assets/default_bg/hideout.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/packages/app/public/assets/default_bg/meteors.svg b/packages/app/public/assets/default_bg/meteors.svg
new file mode 100644
index 00000000..527a5c39
--- /dev/null
+++ b/packages/app/public/assets/default_bg/meteors.svg
@@ -0,0 +1,139 @@
+
\ No newline at end of file
diff --git a/packages/app/public/assets/default_bg/topography.svg b/packages/app/public/assets/default_bg/topography.svg
new file mode 100644
index 00000000..c76c13d7
--- /dev/null
+++ b/packages/app/public/assets/default_bg/topography.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/app/public/realtime-bpm-processor.js b/packages/app/public/realtime-bpm-processor.js
new file mode 100644
index 00000000..f4c246a8
--- /dev/null
+++ b/packages/app/public/realtime-bpm-processor.js
@@ -0,0 +1,324 @@
+"use strict";
+(() => {
+ var __async = (__this, __arguments, generator) => {
+ return new Promise((resolve, reject) => {
+ var fulfilled = (value) => {
+ try {
+ step(generator.next(value));
+ } catch (e) {
+ reject(e);
+ }
+ };
+ var rejected = (value) => {
+ try {
+ step(generator.throw(value));
+ } catch (e) {
+ reject(e);
+ }
+ };
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
+ step((generator = generator.apply(__this, __arguments)).next());
+ });
+ };
+
+ // src/consts.ts
+ var realtimeBpmProcessorName = "realtime-bpm-processor";
+ var startThreshold = 0.95;
+ var minValidThreshold = 0.3;
+ var minPeaks = 15;
+ var thresholdStep = 0.05;
+ var skipForwardIndexes = 1e4;
+
+ // src/utils.ts
+ function descendingOverThresholds(_0) {
+ return __async(this, arguments, function* (onThreshold, minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {
+ let threshold = startThreshold2;
+ do {
+ threshold -= thresholdStep2;
+ const shouldExit = yield onThreshold(threshold);
+ if (shouldExit) {
+ break;
+ }
+ } while (threshold > minValidThreshold2);
+ });
+ }
+ function generateValidPeaksModel(minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {
+ const object = {};
+ let threshold = startThreshold2;
+ do {
+ threshold -= thresholdStep2;
+ object[threshold.toString()] = [];
+ } while (threshold > minValidThreshold2);
+ return object;
+ }
+ function generateNextIndexPeaksModel(minValidThreshold2 = minValidThreshold, startThreshold2 = startThreshold, thresholdStep2 = thresholdStep) {
+ const object = {};
+ let threshold = startThreshold2;
+ do {
+ threshold -= thresholdStep2;
+ object[threshold.toString()] = 0;
+ } while (threshold > minValidThreshold2);
+ return object;
+ }
+ function chunckAggregator() {
+ const bufferSize = 4096;
+ let _bytesWritten = 0;
+ let buffer = new Float32Array(0);
+ function initBuffer() {
+ _bytesWritten = 0;
+ buffer = new Float32Array(0);
+ }
+ function isBufferFull() {
+ return _bytesWritten === bufferSize;
+ }
+ function flush() {
+ initBuffer();
+ }
+ return function (pcmData) {
+ if (isBufferFull()) {
+ flush();
+ }
+ const newBuffer = new Float32Array(buffer.length + pcmData.length);
+ newBuffer.set(buffer, 0);
+ newBuffer.set(pcmData, buffer.length);
+ buffer = newBuffer;
+ _bytesWritten += pcmData.length;
+ return {
+ isBufferFull: isBufferFull(),
+ buffer,
+ bufferSize
+ };
+ };
+ }
+
+ // src/analyzer.ts
+ function findPeaksAtThreshold(data, threshold, offset = 0, skipForwardIndexes2 = skipForwardIndexes) {
+ const peaks = [];
+ const { length } = data;
+ for (let i = offset; i < length; i += 1) {
+ if (data[i] > threshold) {
+ peaks.push(i);
+ i += skipForwardIndexes2;
+ }
+ }
+ return {
+ peaks,
+ threshold
+ };
+ }
+ function computeBpm(_0, _1) {
+ return __async(this, arguments, function* (data, audioSampleRate, minPeaks2 = minPeaks) {
+ let hasPeaks = false;
+ let foundThreshold = minValidThreshold;
+ yield descendingOverThresholds((threshold) => __async(this, null, function* () {
+ if (hasPeaks) {
+ return true;
+ }
+ if (data[threshold].length > minPeaks2) {
+ hasPeaks = true;
+ foundThreshold = threshold;
+ }
+ return false;
+ }));
+ if (hasPeaks && foundThreshold) {
+ const intervals = identifyIntervals(data[foundThreshold]);
+ const tempos = groupByTempo(audioSampleRate, intervals);
+ const candidates = getTopCandidates(tempos);
+ const bpmCandidates = {
+ bpm: candidates,
+ threshold: foundThreshold
+ };
+ return bpmCandidates;
+ }
+ return {
+ bpm: [],
+ threshold: foundThreshold
+ };
+ });
+ }
+ function getTopCandidates(candidates, length = 5) {
+ return candidates.sort((a, b) => b.count - a.count).splice(0, length);
+ }
+ function identifyIntervals(peaks) {
+ const intervals = [];
+ for (let n = 0; n < peaks.length; n++) {
+ for (let i = 0; i < 10; i++) {
+ const peak = peaks[n];
+ const peakIndex = n + i;
+ const interval = peaks[peakIndex] - peak;
+ const foundInterval = intervals.some((intervalCount) => {
+ if (intervalCount.interval === interval) {
+ intervalCount.count += 1;
+ return intervalCount.count;
+ }
+ return false;
+ });
+ if (!foundInterval) {
+ const item = {
+ interval,
+ count: 1
+ };
+ intervals.push(item);
+ }
+ }
+ }
+ return intervals;
+ }
+ function groupByTempo(audioSampleRate, intervalCounts) {
+ const tempoCounts = [];
+ for (const intervalCount of intervalCounts) {
+ if (intervalCount.interval === 0) {
+ continue;
+ }
+ intervalCount.interval = Math.abs(intervalCount.interval);
+ let theoreticalTempo = 60 / (intervalCount.interval / audioSampleRate);
+ while (theoreticalTempo < 90) {
+ theoreticalTempo *= 2;
+ }
+ while (theoreticalTempo > 180) {
+ theoreticalTempo /= 2;
+ }
+ theoreticalTempo = Math.round(theoreticalTempo);
+ const foundTempo = tempoCounts.some((tempoCount) => {
+ if (tempoCount.tempo === theoreticalTempo) {
+ tempoCount.count += intervalCount.count;
+ return tempoCount.count;
+ }
+ return false;
+ });
+ if (!foundTempo) {
+ const tempo = {
+ tempo: theoreticalTempo,
+ count: intervalCount.count,
+ confidence: 0
+ };
+ tempoCounts.push(tempo);
+ }
+ }
+ return tempoCounts;
+ }
+
+ // src/realtime-bpm-analyzer.ts
+ var initialValue = {
+ minValidThreshold: () => minValidThreshold,
+ timeoutStabilization: () => 0,
+ validPeaks: () => generateValidPeaksModel(),
+ nextIndexPeaks: () => generateNextIndexPeaksModel(),
+ skipIndexes: () => 1
+ };
+ var RealTimeBpmAnalyzer = class {
+ constructor(config = {}) {
+ this.options = {
+ continuousAnalysis: false,
+ computeBpmDelay: 1e4,
+ stabilizationTime: 2e4,
+ muteTimeInIndexes: 1e4
+ };
+ this.minValidThreshold = initialValue.minValidThreshold();
+ this.timeoutStabilization = initialValue.timeoutStabilization();
+ this.validPeaks = initialValue.validPeaks();
+ this.nextIndexPeaks = initialValue.nextIndexPeaks();
+ this.skipIndexes = initialValue.skipIndexes();
+ Object.assign(this.options, config);
+ }
+ setAsyncConfiguration(parameters) {
+ Object.assign(this.options, parameters);
+ }
+ reset() {
+ this.minValidThreshold = initialValue.minValidThreshold();
+ this.timeoutStabilization = initialValue.timeoutStabilization();
+ this.validPeaks = initialValue.validPeaks();
+ this.nextIndexPeaks = initialValue.nextIndexPeaks();
+ this.skipIndexes = initialValue.skipIndexes();
+ }
+ clearValidPeaks(minThreshold) {
+ return __async(this, null, function* () {
+ console.log(`[clearValidPeaks] function: under ${minThreshold}, this.minValidThreshold has been setted to that threshold.`);
+ this.minValidThreshold = Number.parseFloat(minThreshold.toFixed(2));
+ yield descendingOverThresholds((threshold) => __async(this, null, function* () {
+ if (threshold < minThreshold) {
+ delete this.validPeaks[threshold];
+ delete this.nextIndexPeaks[threshold];
+ }
+ return false;
+ }));
+ });
+ }
+ analyzeChunck(channelData, audioSampleRate, bufferSize, postMessage) {
+ return __async(this, null, function* () {
+ const currentMaxIndex = bufferSize * this.skipIndexes;
+ const currentMinIndex = currentMaxIndex - bufferSize;
+ yield this.findPeaks(channelData, bufferSize, currentMinIndex, currentMaxIndex);
+ this.skipIndexes++;
+ const result = yield computeBpm(this.validPeaks, audioSampleRate);
+ const { threshold } = result;
+ postMessage({ message: "BPM", result });
+ if (this.minValidThreshold < threshold) {
+ postMessage({ message: "BPM_STABLE", result });
+ yield this.clearValidPeaks(threshold);
+ }
+ if (this.options.continuousAnalysis) {
+ clearTimeout(this.timeoutStabilization);
+ this.timeoutStabilization = window.setTimeout(() => {
+ console.log("[timeoutStabilization] setTimeout: Fired !");
+ this.options.computeBpmDelay = 0;
+ this.reset();
+ }, this.options.stabilizationTime);
+ }
+ });
+ }
+ findPeaks(channelData, bufferSize, currentMinIndex, currentMaxIndex) {
+ return __async(this, null, function* () {
+ yield descendingOverThresholds((threshold) => __async(this, null, function* () {
+ if (this.nextIndexPeaks[threshold] >= currentMaxIndex) {
+ return false;
+ }
+ const offsetForNextPeak = this.nextIndexPeaks[threshold] % bufferSize;
+ const { peaks, threshold: atThreshold } = findPeaksAtThreshold(channelData, threshold, offsetForNextPeak);
+ if (peaks.length === 0) {
+ return false;
+ }
+ for (const relativeChunkPeak of peaks) {
+ this.nextIndexPeaks[atThreshold] = currentMinIndex + relativeChunkPeak + this.options.muteTimeInIndexes;
+ this.validPeaks[atThreshold].push(currentMinIndex + relativeChunkPeak);
+ }
+ return false;
+ }), this.minValidThreshold);
+ });
+ }
+ };
+
+ // processor/realtime-bpm-processor.ts
+ var RealTimeBpmProcessor = class extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ this.realTimeBpmAnalyzer = new RealTimeBpmAnalyzer();
+ this.aggregate = chunckAggregator();
+ this.port.addEventListener("message", this.onMessage.bind(this));
+ this.port.start();
+ }
+ onMessage(event) {
+ if (event.data.message === "ASYNC_CONFIGURATION") {
+ this.realTimeBpmAnalyzer.setAsyncConfiguration(event.data.parameters);
+ }
+ }
+ process(inputs, _outputs, _parameters) {
+ const currentChunk = inputs[0][0];
+ if (!currentChunk) {
+ return true;
+ }
+ const { isBufferFull, buffer, bufferSize } = this.aggregate(currentChunk);
+ if (isBufferFull) {
+ this.realTimeBpmAnalyzer.analyzeChunck(buffer, sampleRate, bufferSize, (event) => {
+ this.port.postMessage(event);
+ }).catch((error) => {
+ console.error(error);
+ });
+ }
+ return true;
+ }
+ };
+ registerProcessor(realtimeBpmProcessorName, RealTimeBpmProcessor);
+ var realtime_bpm_processor_default = {};
+})();
+//# sourceMappingURL=realtime-bpm-processor.js.map