mirror of
https://github.com/danbulant/osuVisualizer
synced 2026-05-19 12:28:59 +00:00
Compare commits
No commits in common. "master" and "v0.1-beta" have entirely different histories.
15 changed files with 5696 additions and 2517 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -9,14 +9,9 @@ lerna-debug.log*
|
|||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
.envrc
|
||||
|
||||
# build
|
||||
public/build
|
||||
public/build/*
|
||||
token.txt
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
|
|
|
|||
5191
package-lock.json
generated
Normal file
5191
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
22
package.json
22
package.json
|
|
@ -1,10 +1,9 @@
|
|||
{
|
||||
"name": "osu",
|
||||
"productName": "osu",
|
||||
"version": "0.2.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Osu! visualizer",
|
||||
"main": "src/index.js",
|
||||
"repository": "github:danbulant/osuVisualizer",
|
||||
"scripts": {
|
||||
"start": "concurrently \"npm:svelte-dev\" \"electron-forge start\"",
|
||||
"package": "electron-forge package",
|
||||
|
|
@ -49,30 +48,27 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"concurrently": "^5.3.0",
|
||||
"discord-rpc": "^3.1.4",
|
||||
"electron-is-dev": "^1.2.0",
|
||||
"electron-reload": "^1.5.0",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-store": "^6.0.0",
|
||||
"osu-db-parser": "^1.0.35",
|
||||
"osu-parser": "^0.3.3",
|
||||
"sirv-cli": "^1.0.0",
|
||||
"update-electron-app": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^14.0.0",
|
||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-svelte": "^6.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
"svelte": "^3.0.0",
|
||||
"@electron-forge/cli": "^6.0.0-beta.53",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.53",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.53",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.53",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.53",
|
||||
"@electron-forge/publisher-github": "^6.0.0-beta.52",
|
||||
"@rollup/plugin-commonjs": "^14.0.0",
|
||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||
"electron": "10.1.2",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-svelte": "^6.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
"svelte": "^3.0.0"
|
||||
"electron": "10.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
main.svelte-5atqf{position:relative;width:100vw;height:100vh}.background.svelte-5atqf{position:fixed;z-index:0;left:0;right:0;width:100vw;height:100vh}.menu.svelte-5atqf{position:absolute;z-index:1;left:0;right:0;width:100vw;height:100vh}
|
||||
.info.svelte-1qt5obi.svelte-1qt5obi{opacity:1;position:relative;top:0;left:0;width:100vw;height:80px;transition:opacity 0.6s;z-index:2}.volume.svelte-1qt5obi.svelte-1qt5obi{opacity:1;position:fixed;z-index:5;right:0;bottom:0;border-radius:50%;font-size:30px;color:white;background-color:black;width:100px;height:100px}.volume.svelte-1qt5obi .slider.svelte-1qt5obi{position:relative;top:0;left:0;width:100%;height:100%}.percent.svelte-1qt5obi.svelte-1qt5obi{position:absolute;top:25px;left:0;width:100%;height:100%;text-align:center}.progress-ring.svelte-1qt5obi.svelte-1qt5obi{position:absolute;top:0;left:0;width:100%;height:100%}.progress-ring.svelte-1qt5obi circle.svelte-1qt5obi{transition:stroke-dashoffset 0.32s;transform:rotate(-90deg);transform-origin:50% 50%;position:absolute;top:1px;left:1px;width:100%;height:100%}.hidden.svelte-1qt5obi.svelte-1qt5obi{opacity:0;transition:opacity 1s}.info.svelte-1qt5obi .song.svelte-1qt5obi{color:white;position:absolute;padding:5px 5px 5px 25px;top:0;right:0;text-align:right;background:black;background:linear-gradient(90deg, transparent 0%, rgba(0,0,0,0.5) 15%, rgba(0,0,0,0.5) 100%)}.info.svelte-1qt5obi .song h2.svelte-1qt5obi{margin:0}.info.svelte-1qt5obi .controls.svelte-1qt5obi{height:50px;display:flex}.info.svelte-1qt5obi .controls div.svelte-1qt5obi{height:100%}.info.svelte-1qt5obi .controls img.svelte-1qt5obi{height:100%;filter:invert(100%)}.info.svelte-1qt5obi .controls .settings img.svelte-1qt5obi{height:65%;padding-top:25%}
|
||||
.main.svelte-18bmol8.svelte-18bmol8{width:100%;height:100%;background-size:cover;background-repeat:no-repeat}@keyframes svelte-18bmol8-bpm{from{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}to{width:525px;height:525px;top:calc(50vh - 262.5px);left:calc(50vw - 262.5px)}}@keyframes svelte-18bmol8-bpmShadow{0%{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}70%{width:510px;height:510px;top:calc(50vh - 255px);left:calc(50vw - 255px)}100%{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}}video.svelte-18bmol8.svelte-18bmol8{position:fixed;z-index:0;top:0;left:0;width:100vw;height:100vh}.main.svelte-18bmol8 img.svelte-18bmol8{position:fixed;width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px);z-index:1}.main.svelte-18bmol8 .logo.svelte-18bmol8{animation-name:svelte-18bmol8-bpm;animation-direction:alternate}.main.svelte-18bmol8 .shadow.svelte-18bmol8{opacity:0.2;animation-name:svelte-18bmol8-bpmShadow;animation-delay:50ms}.main.svelte-18bmol8 .repeat.svelte-18bmol8{animation-iteration-count:infinite}
|
||||
.bg.svelte-1h7gt2z{position:fixed;display:none;width:100vw;height:100vh;top:0;left:0;z-index:3}.bg.visible.svelte-1h7gt2z{display:block}nav.svelte-1h7gt2z{position:fixed;height:100vh;width:400px;top:0;left:-400px;opacity:0;background:rgba(0,0,0,0.4);color:white;z-index:4;padding-left:1rem;transition:opacity 0.3s, left 0.3s}nav.visible.svelte-1h7gt2z{left:0;opacity:1}
|
||||
.info.svelte-1j9fr45.svelte-1j9fr45{opacity:1;position:relative;top:0;left:0;width:100vw;height:80px;transition:opacity 0.6s;z-index:1}.volume.svelte-1j9fr45.svelte-1j9fr45{opacity:1;position:fixed;z-index:2;right:0;bottom:0;border-radius:50%;color:black;font-size:30px}.hidden.svelte-1j9fr45.svelte-1j9fr45{opacity:0;transition:opacity 1s}.info.svelte-1j9fr45 .song.svelte-1j9fr45{color:white;position:absolute;padding:5px 5px 5px 25px;top:0;right:0;text-align:right;background:black;background:linear-gradient(90deg, transparent 0%, rgba(0,0,0,0.5) 15%, rgba(0,0,0,0.5) 100%)}.info.svelte-1j9fr45 .song h2.svelte-1j9fr45{margin:0}.info.svelte-1j9fr45 .controls.svelte-1j9fr45{height:50px;display:flex}.info.svelte-1j9fr45 .controls div.svelte-1j9fr45{height:100%}.info.svelte-1j9fr45 .controls img.svelte-1j9fr45{height:100%;filter:invert(100%)}
|
||||
.main.svelte-yh70k3.svelte-yh70k3{width:100%;height:100%;background-size:cover;background-repeat:no-repeat}@keyframes svelte-yh70k3-bpm{from{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}to{width:525px;height:525px;top:calc(50vh - 262.5px);left:calc(50vw - 262.5px)}}@keyframes svelte-yh70k3-bpmShadow{0%{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}70%{width:510px;height:510px;top:calc(50vh - 255px);left:calc(50vw - 255px)}100%{width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}}.main.svelte-yh70k3 img.svelte-yh70k3{position:fixed;width:500px;height:500px;top:calc(50vh - 250px);left:calc(50vw - 250px)}.main.svelte-yh70k3 .logo.svelte-yh70k3{animation-name:svelte-yh70k3-bpm;animation-iteration-count:infinite;animation-direction:alternate}.main.svelte-yh70k3 .shadow.svelte-yh70k3{opacity:0.2;animation-name:svelte-yh70k3-bpmShadow;animation-iteration-count:infinite;animation-delay:50ms}
|
||||
|
||||
/*# sourceMappingURL=bundle.css.map */
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 125 KiB |
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="25px" height="25.001px" viewBox="0 0 25 25.001" style="enable-background:new 0 0 25 25.001;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M24.38,10.175l-2.231-0.268c-0.228-0.851-0.562-1.655-0.992-2.401l1.387-1.763c0.212-0.271,0.188-0.69-0.057-0.934
|
||||
l-2.299-2.3c-0.242-0.243-0.662-0.269-0.934-0.057l-1.766,1.389c-0.743-0.43-1.547-0.764-2.396-0.99L14.825,0.62
|
||||
C14.784,0.279,14.469,0,14.125,0h-3.252c-0.344,0-0.659,0.279-0.699,0.62L9.906,2.851c-0.85,0.227-1.655,0.562-2.398,0.991
|
||||
L5.743,2.455c-0.27-0.212-0.69-0.187-0.933,0.056L2.51,4.812C2.268,5.054,2.243,5.474,2.456,5.746L3.842,7.51
|
||||
c-0.43,0.744-0.764,1.549-0.991,2.4l-2.23,0.267C0.28,10.217,0,10.532,0,10.877v3.252c0,0.344,0.279,0.657,0.621,0.699l2.231,0.268
|
||||
c0.228,0.848,0.561,1.652,0.991,2.396l-1.386,1.766c-0.211,0.271-0.187,0.69,0.057,0.934l2.296,2.301
|
||||
c0.243,0.242,0.663,0.269,0.933,0.057l1.766-1.39c0.744,0.43,1.548,0.765,2.398,0.991l0.268,2.23
|
||||
c0.041,0.342,0.355,0.62,0.699,0.62h3.252c0.345,0,0.659-0.278,0.699-0.62l0.268-2.23c0.851-0.228,1.655-0.562,2.398-0.991
|
||||
l1.766,1.387c0.271,0.212,0.69,0.187,0.933-0.056l2.299-2.301c0.244-0.242,0.269-0.662,0.056-0.935l-1.388-1.764
|
||||
c0.431-0.744,0.764-1.548,0.992-2.397l2.23-0.268C24.721,14.785,25,14.473,25,14.127v-3.252
|
||||
C25.001,10.529,24.723,10.216,24.38,10.175z M12.501,18.75c-3.452,0-6.25-2.798-6.25-6.25s2.798-6.25,6.25-6.25
|
||||
s6.25,2.798,6.25,6.25S15.954,18.75,12.501,18.75z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
|
@ -1,515 +0,0 @@
|
|||
var fs = require('fs');
|
||||
var slidercalc = require('osu-parser/lib/slidercalc.js');
|
||||
|
||||
function beatmapParser() {
|
||||
var beatmap = {
|
||||
nbCircles: 0,
|
||||
nbSliders: 0,
|
||||
nbSpinners: 0,
|
||||
timingPoints: [],
|
||||
breakTimes: [],
|
||||
hitObjects: []
|
||||
};
|
||||
|
||||
var osuSection;
|
||||
var bpmMin;
|
||||
var bpmMax;
|
||||
var members;
|
||||
|
||||
var timingLines = [];
|
||||
var objectLines = [];
|
||||
var eventsLines = [];
|
||||
var sectionReg = /^\[([a-zA-Z0-9]+)\]$/;
|
||||
var keyValReg = /^([a-zA-Z0-9]+)[ ]*:[ ]*(.+)$/;
|
||||
var curveTypes = {
|
||||
C: "catmull",
|
||||
B: "bezier",
|
||||
L: "linear",
|
||||
P: "pass-through"
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the timing point affecting a specific offset
|
||||
* @param {Integer} offset
|
||||
* @return {Object} timingPoint
|
||||
*/
|
||||
var getTimingPoint = function (offset) {
|
||||
for (var i = beatmap.timingPoints.length - 1; i >= 0; i--) {
|
||||
if (beatmap.timingPoints[i].offset <= offset) { return beatmap.timingPoints[i]; }
|
||||
}
|
||||
return beatmap.timingPoints[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse additions member
|
||||
* @param {String} str additions member (sample:add:customSampleIndex:Volume:hitsound)
|
||||
* @return {Object} additions a list of additions
|
||||
*/
|
||||
var parseAdditions = function (str) {
|
||||
if (!str) return {};
|
||||
|
||||
var additions = {};
|
||||
var adds = str.split(':');
|
||||
|
||||
if (adds[0] && adds[0] !== '0') {
|
||||
var sample;
|
||||
switch (adds[0]) {
|
||||
case '1':
|
||||
sample = 'normal';
|
||||
break;
|
||||
case '2':
|
||||
sample = 'soft';
|
||||
break;
|
||||
case '3':
|
||||
sample = 'drum';
|
||||
break;
|
||||
}
|
||||
additions.sample = sample;
|
||||
}
|
||||
|
||||
if (adds[1] && adds[1] !== '0') {
|
||||
var addSample;
|
||||
switch (adds[1]) {
|
||||
case '1':
|
||||
addSample = 'normal';
|
||||
break;
|
||||
case '2':
|
||||
addSample = 'soft';
|
||||
break;
|
||||
case '3':
|
||||
addSample = 'drum';
|
||||
break;
|
||||
}
|
||||
additions.additionalSample = addSample;
|
||||
}
|
||||
|
||||
if (adds[2] && adds[2] !== '0') { additions.customSampleIndex = parseInt(adds[2]); }
|
||||
if (adds[3] && adds[3] !== '0') { additions.hitsoundVolume = parseInt(adds[3]); }
|
||||
if (adds[4]) { additions.hitsound = adds[4]; }
|
||||
|
||||
return additions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a timing line
|
||||
* @param {String} line
|
||||
*/
|
||||
var parseTimingPoint = function (line) {
|
||||
members = line.split(',');
|
||||
|
||||
var timingPoint = {
|
||||
offset: parseInt(members[0]),
|
||||
beatLength: parseFloat(members[1]),
|
||||
velocity: 1,
|
||||
timingSignature: parseInt(members[2]),
|
||||
sampleSetId: parseInt(members[3]),
|
||||
customSampleIndex: parseInt(members[4]),
|
||||
sampleVolume: parseInt(members[5]),
|
||||
timingChange: (members[6] == 1),
|
||||
kiaiTimeActive: (members[7] == 1)
|
||||
};
|
||||
|
||||
if (!isNaN(timingPoint.beatLength) && timingPoint.beatLength !== 0) {
|
||||
if (timingPoint.beatLength > 0) {
|
||||
// If positive, beatLength is the length of a beat in milliseconds
|
||||
var bpm = Math.round(60000 / timingPoint.beatLength);
|
||||
beatmap.bpmMin = beatmap.bpmMin ? Math.min(beatmap.bpmMin, bpm) : bpm;
|
||||
beatmap.bpmMax = beatmap.bpmMax ? Math.max(beatmap.bpmMax, bpm) : bpm;
|
||||
timingPoint.bpm = bpm;
|
||||
} else {
|
||||
// If negative, beatLength is a velocity factor
|
||||
timingPoint.velocity = Math.abs(100 / timingPoint.beatLength);
|
||||
}
|
||||
}
|
||||
|
||||
beatmap.timingPoints.push(timingPoint);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse an object line
|
||||
* @param {String} line
|
||||
*/
|
||||
var parseHitObject = function (line) {
|
||||
members = line.split(',');
|
||||
|
||||
var soundType = members[4];
|
||||
var objectType = members[3];
|
||||
|
||||
var hitObject = {
|
||||
startTime: parseInt(members[2]),
|
||||
newCombo: ((objectType & 4) == 4),
|
||||
soundTypes: [],
|
||||
position: [
|
||||
parseInt(members[0]),
|
||||
parseInt(members[1])
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* sound type is a bitwise flag enum
|
||||
* 0 : normal
|
||||
* 2 : whistle
|
||||
* 4 : finish
|
||||
* 8 : clap
|
||||
*/
|
||||
if ((soundType & 2) == 2) { hitObject.soundTypes.push('whistle'); }
|
||||
if ((soundType & 4) == 4) { hitObject.soundTypes.push('finish'); }
|
||||
if ((soundType & 8) == 8) { hitObject.soundTypes.push('clap'); }
|
||||
if (hitObject.soundTypes.length === 0) { hitObject.soundTypes.push('normal'); }
|
||||
|
||||
/**
|
||||
* object type is a bitwise flag enum
|
||||
* 1: circle
|
||||
* 2: slider
|
||||
* 8: spinner
|
||||
*/
|
||||
if ((objectType & 1) == 1) {
|
||||
// Circle
|
||||
beatmap.nbCircles++;
|
||||
hitObject.objectName = 'circle';
|
||||
hitObject.additions = parseAdditions(members[5]);
|
||||
} else if ((objectType & 8) == 8) {
|
||||
// Spinner
|
||||
beatmap.nbSpinners++;
|
||||
hitObject.objectName = 'spinner';
|
||||
hitObject.endTime = parseInt(members[5]);
|
||||
hitObject.additions = parseAdditions(members[6]);
|
||||
} else if ((objectType & 2) == 2) {
|
||||
// Slider
|
||||
beatmap.nbSliders++;
|
||||
hitObject.objectName = 'slider';
|
||||
hitObject.repeatCount = parseInt(members[6]);
|
||||
hitObject.pixelLength = parseInt(members[7]);
|
||||
hitObject.additions = parseAdditions(members[10]);
|
||||
hitObject.edges = [];
|
||||
hitObject.points = [
|
||||
[hitObject.position[0], hitObject.position[1]]
|
||||
];
|
||||
|
||||
/**
|
||||
* Calculate slider duration
|
||||
*/
|
||||
var timing = getTimingPoint(hitObject.startTime);
|
||||
|
||||
if (timing) {
|
||||
var pxPerBeat = beatmap.SliderMultiplier * 100 * timing.velocity;
|
||||
var beatsNumber = (hitObject.pixelLength * hitObject.repeatCount) / pxPerBeat;
|
||||
hitObject.duration = Math.ceil(beatsNumber * timing.beatLength);
|
||||
hitObject.endTime = hitObject.startTime + hitObject.duration;
|
||||
}
|
||||
/**
|
||||
* Parse slider points
|
||||
*/
|
||||
var points = (members[5] || '').split('|');
|
||||
if (points.length) {
|
||||
hitObject.curveType = curveTypes[points[0]] || 'unknown';
|
||||
|
||||
for (var i = 1, l = points.length; i < l; i++) {
|
||||
var coordinates = points[i].split(':');
|
||||
hitObject.points.push([
|
||||
parseInt(coordinates[0]),
|
||||
parseInt(coordinates[1])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
var edgeSounds = [];
|
||||
var edgeAdditions = [];
|
||||
if (members[8]) { edgeSounds = members[8].split('|'); }
|
||||
if (members[9]) { edgeAdditions = members[9].split('|'); }
|
||||
|
||||
/**
|
||||
* Get soundTypes and additions for each slider edge
|
||||
*/
|
||||
for (var j = 0, lgt = hitObject.repeatCount + 1; j < lgt; j++) {
|
||||
var edge = {
|
||||
soundTypes: [],
|
||||
additions: parseAdditions(edgeAdditions[j])
|
||||
};
|
||||
|
||||
if (edgeSounds[j]) {
|
||||
var sound = edgeSounds[j];
|
||||
if ((sound & 2) == 2) { edge.soundTypes.push('whistle'); }
|
||||
if ((sound & 4) == 4) { edge.soundTypes.push('finish'); }
|
||||
if ((sound & 8) == 8) { edge.soundTypes.push('clap'); }
|
||||
if (edge.soundTypes.length === 0) { edge.soundTypes.push('normal'); }
|
||||
} else {
|
||||
edge.soundTypes.push('normal');
|
||||
}
|
||||
|
||||
hitObject.edges.push(edge);
|
||||
}
|
||||
|
||||
// get coordinates of the slider endpoint
|
||||
var endPoint = slidercalc.getEndPoint(hitObject.curveType, hitObject.pixelLength, hitObject.points);
|
||||
if (endPoint && endPoint[0] && endPoint[1]) {
|
||||
hitObject.endPosition = [
|
||||
Math.round(endPoint[0]),
|
||||
Math.round(endPoint[1])
|
||||
];
|
||||
} else {
|
||||
// If endPosition could not be calculated, approximate it by setting it to the last point
|
||||
hitObject.endPosition = hitObject.points[hitObject.points.length - 1];
|
||||
}
|
||||
} else {
|
||||
// Unknown
|
||||
hitObject.objectName = 'unknown';
|
||||
}
|
||||
|
||||
beatmap.hitObjects.push(hitObject);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse an event line
|
||||
* @param {String} line
|
||||
*/
|
||||
var parseEvent = function (line) {
|
||||
/**
|
||||
* Background line : 0,0,"bg.jpg"
|
||||
* TODO: confirm that the second member is always zero
|
||||
*
|
||||
* Breaktimes lines : 2,1000,2000
|
||||
* second integer is start offset
|
||||
* third integer is end offset
|
||||
*/
|
||||
members = line.split(',');
|
||||
|
||||
if (members[0] == '0' && members[1] == '0' && members[2]) {
|
||||
var bgName = members[2].trim();
|
||||
|
||||
if (bgName.charAt(0) == '"' && bgName.charAt(bgName.length - 1) == '"') {
|
||||
beatmap.bgFilename = bgName.substring(1, bgName.length - 1);
|
||||
} else {
|
||||
beatmap.bgFilename = bgName;
|
||||
}
|
||||
} else if (members[0] == 'Video' && members[2]) {
|
||||
var bgName = members[2].trim();
|
||||
|
||||
if (bgName.charAt(0) == '"' && bgName.charAt(bgName.length - 1) == '"') {
|
||||
beatmap.video = bgName.substring(1, bgName.length - 1);
|
||||
} else {
|
||||
beatmap.video = bgName;
|
||||
}
|
||||
} else if (members[0] == '2' && /^[0-9]+$/.test(members[1]) && /^[0-9]+$/.test(members[2])) {
|
||||
beatmap.breakTimes.push({
|
||||
startTime: parseInt(members[1]),
|
||||
endTime: parseInt(members[2])
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the total time and the draining time of the beatmap
|
||||
*/
|
||||
var computeDuration = function () {
|
||||
var firstObject = beatmap.hitObjects[0];
|
||||
var lastObject = beatmap.hitObjects[beatmap.hitObjects.length - 1];
|
||||
|
||||
var totalBreakTime = 0;
|
||||
|
||||
beatmap.breakTimes.forEach(function (breakTime) {
|
||||
totalBreakTime += (breakTime.endTime - breakTime.startTime);
|
||||
});
|
||||
|
||||
if (firstObject && lastObject) {
|
||||
beatmap.totalTime = Math.floor(lastObject.startTime / 1000);
|
||||
beatmap.drainingTime = Math.floor((lastObject.startTime - firstObject.startTime - totalBreakTime) / 1000);
|
||||
} else {
|
||||
beatmap.totalTime = 0;
|
||||
beatmap.drainingTime = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Browse objects and compute max combo
|
||||
*/
|
||||
var computeMaxCombo = function () {
|
||||
if (beatmap.timingPoints.length === 0) { return; }
|
||||
|
||||
var maxCombo = 0;
|
||||
var sliderMultiplier = parseFloat(beatmap.SliderMultiplier);
|
||||
var sliderTickRate = parseInt(beatmap.SliderTickRate, 10);
|
||||
|
||||
var timingPoints = beatmap.timingPoints;
|
||||
var currentTiming = timingPoints[0];
|
||||
var nextOffset = timingPoints[1] ? timingPoints[1].offset : Infinity;
|
||||
var i = 1;
|
||||
|
||||
beatmap.hitObjects.forEach(function (hitObject) {
|
||||
if (hitObject.startTime >= nextOffset) {
|
||||
currentTiming = timingPoints[i++];
|
||||
nextOffset = timingPoints[i] ? timingPoints[i].offset : Infinity;
|
||||
}
|
||||
|
||||
var osupxPerBeat = sliderMultiplier * 100 * currentTiming.velocity;
|
||||
var tickLength = osupxPerBeat / sliderTickRate;
|
||||
|
||||
switch (hitObject.objectName) {
|
||||
case 'spinner':
|
||||
case 'circle':
|
||||
maxCombo++;
|
||||
break;
|
||||
case 'slider':
|
||||
var tickPerSide = Math.ceil((Math.floor(hitObject.pixelLength / tickLength * 100) / 100) - 1);
|
||||
maxCombo += (hitObject.edges.length - 1) * (tickPerSide + 1) + 1; // 1 combo for each tick and endpoint
|
||||
}
|
||||
});
|
||||
|
||||
beatmap.maxCombo = maxCombo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read a single line, parse when key/value, store when further parsing needed
|
||||
* @param {String|Buffer} line
|
||||
*/
|
||||
var readLine = function (line) {
|
||||
line = line.toString().trim();
|
||||
if (!line) { return; }
|
||||
|
||||
var match = sectionReg.exec(line);
|
||||
if (match) {
|
||||
osuSection = match[1].toLowerCase();
|
||||
return;
|
||||
}
|
||||
if(line.startsWith("//")) return;
|
||||
|
||||
switch (osuSection) {
|
||||
case 'timingpoints':
|
||||
timingLines.push(line);
|
||||
break;
|
||||
case 'hitobjects':
|
||||
objectLines.push(line);
|
||||
break;
|
||||
case 'events':
|
||||
eventsLines.push(line);
|
||||
break;
|
||||
default:
|
||||
if (!osuSection) {
|
||||
match = /^osu file format (v[0-9]+)$/.exec(line);
|
||||
if (match) {
|
||||
beatmap.fileFormat = match[1];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apart from events, timingpoints and hitobjects sections, lines are "key: value"
|
||||
*/
|
||||
match = keyValReg.exec(line);
|
||||
if (match) { beatmap[match[1]] = match[2]; }
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute everything that require the file to be completely parsed and return the beatmap
|
||||
* @return {Object} beatmap
|
||||
*/
|
||||
var buildBeatmap = function () {
|
||||
if (beatmap.Tags) {
|
||||
beatmap.tagsArray = beatmap.Tags.split(' ');
|
||||
}
|
||||
|
||||
eventsLines.forEach(parseEvent);
|
||||
beatmap.breakTimes.sort(function (a, b) { return (a.startTime > b.startTime ? 1 : -1); });
|
||||
|
||||
timingLines.forEach(parseTimingPoint);
|
||||
beatmap.timingPoints.sort(function (a, b) { return (a.offset > b.offset ? 1 : -1); });
|
||||
|
||||
var timingPoints = beatmap.timingPoints;
|
||||
|
||||
for (var i = 1, l = timingPoints.length; i < l; i++) {
|
||||
if (!timingPoints[i].hasOwnProperty('bpm')) {
|
||||
timingPoints[i].beatLength = timingPoints[i - 1].beatLength;
|
||||
timingPoints[i].bpm = timingPoints[i - 1].bpm;
|
||||
}
|
||||
}
|
||||
|
||||
objectLines.forEach(parseHitObject);
|
||||
beatmap.hitObjects.sort(function (a, b) { return (a.startTime > b.startTime ? 1 : -1); });
|
||||
|
||||
computeMaxCombo();
|
||||
computeDuration();
|
||||
|
||||
return beatmap;
|
||||
};
|
||||
|
||||
return {
|
||||
readLine: readLine,
|
||||
buildBeatmap: buildBeatmap
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parse a .osu file
|
||||
* @param {String} file path to the file
|
||||
* @param {Function} callback(err, beatmap)
|
||||
*/
|
||||
exports.parseFile = function (file, callback) {
|
||||
if (!fs.existsSync(file)) {
|
||||
callback(new Error('File does not exist'));
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = beatmapParser();
|
||||
var stream = fs.createReadStream(file);
|
||||
var buffer = '';
|
||||
|
||||
|
||||
stream.on('data', function (chunk) {
|
||||
buffer += chunk;
|
||||
var lines = buffer.split(/\r?\n/);
|
||||
buffer = lines.pop() || '';
|
||||
lines.forEach(parser.readLine);
|
||||
});
|
||||
|
||||
stream.on('error', function (err) {
|
||||
callback(err);
|
||||
});
|
||||
|
||||
stream.on('end', function () {
|
||||
buffer.split(/\r?\n/).forEach(parser.readLine);
|
||||
callback(null, parser.buildBeatmap());
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a stream containing .osu content
|
||||
* @param {Stream} stream
|
||||
* @param {Function} callback(err, beatmap)
|
||||
*/
|
||||
exports.parseStream = function (stream, callback) {
|
||||
var parser = beatmapParser();
|
||||
var buffer = '';
|
||||
|
||||
stream.on('data', function (chunk) {
|
||||
buffer += chunk.toString();
|
||||
var lines = buffer.split(/\r?\n/);
|
||||
buffer = lines.pop() || '';
|
||||
lines.forEach(parser.readLine);
|
||||
});
|
||||
|
||||
stream.on('error', function (err) {
|
||||
callback(err);
|
||||
});
|
||||
|
||||
stream.on('end', function () {
|
||||
buffer.split(/\r?\n/).forEach(parser.readLine);
|
||||
callback(null, parser.buildBeatmap());
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the content of a .osu
|
||||
* @param {String|Buffer} content
|
||||
* @return {Object} beatmap
|
||||
*/
|
||||
exports.parseContent = function (content) {
|
||||
var parser = beatmapParser();
|
||||
content.toString().split(/[\n\r]+/).forEach(function (line) {
|
||||
parser.readLine(line);
|
||||
});
|
||||
|
||||
return parser.buildBeatmap();
|
||||
};
|
||||
|
|
@ -1,60 +1,18 @@
|
|||
<script>
|
||||
import Menu from "./Menu.svelte";
|
||||
import Visualizer from "./Visualizer.svelte";
|
||||
const Store = require('electron-store');
|
||||
|
||||
const store = new Store();
|
||||
|
||||
var songData = {};
|
||||
var config = store.get("config");
|
||||
|
||||
var osuData = {};
|
||||
const osuFolder = process.env.OSU_FOLDER || process.env.USERPROFILE + "/AppData/Local/osu!";
|
||||
|
||||
(() => {
|
||||
const configTemplate = {
|
||||
parallax: {
|
||||
enabled: true,
|
||||
treshold: 10
|
||||
},
|
||||
rpc: true,
|
||||
backgrounds: 0,
|
||||
mediaSession: true,
|
||||
videoBackground: true,
|
||||
autohide: {
|
||||
info: 2000,
|
||||
volume: 2000
|
||||
}
|
||||
};
|
||||
|
||||
function checkSettings(value, template) {
|
||||
if(value === undefined) return template;
|
||||
if(typeof value !== "object") return value;
|
||||
|
||||
var out = {};
|
||||
|
||||
for(var key in template) {
|
||||
if(value[key] === undefined || typeof value[key] === "undefined") {
|
||||
out[key] = template[key];
|
||||
continue;
|
||||
}
|
||||
if(typeof value[key] === "object") out[key] = checkSettings(value[key], template[key]);
|
||||
if(typeof value[key] !== "object") out[key] = value[key];
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
config = checkSettings(config, configTemplate);
|
||||
})();
|
||||
|
||||
$: store.set("config", config);
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div class="background">
|
||||
<Visualizer bind:songData bind:osuData {config} {osuFolder} />
|
||||
<Visualizer bind:songData bind:osuData/>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<Menu bind:song={songData} bind:osuData bind:config {osuFolder} />
|
||||
<Menu bind:song={songData} bind:osuData/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
|
|
|||
204
src/Menu.svelte
204
src/Menu.svelte
|
|
@ -1,19 +1,17 @@
|
|||
<script>
|
||||
import Options from "./components/options.svelte";
|
||||
|
||||
export var osuData;
|
||||
export var song;
|
||||
export var config;
|
||||
export var osuFolder;
|
||||
|
||||
var last = Date.now();
|
||||
var lastVolumeUpdate = Date.now() - 5000;
|
||||
var settingsOpen = false;
|
||||
var dialogActive = false;
|
||||
var now = Date.now();
|
||||
|
||||
setInterval(() => {
|
||||
now = Date.now();
|
||||
}, 800);
|
||||
}, 500);
|
||||
|
||||
var playing = true;
|
||||
|
||||
function resetPool() {
|
||||
if(!osuData.songs) return false;
|
||||
|
|
@ -23,11 +21,6 @@
|
|||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[a[i], a[j]] = [a[j], a[i]];
|
||||
}
|
||||
osuData.songPool.forEach(v => {
|
||||
delete v.audio;
|
||||
delete v.video;
|
||||
v.playing = true;
|
||||
})
|
||||
song = osuData.songPool.shift();
|
||||
}
|
||||
|
||||
|
|
@ -47,143 +40,85 @@
|
|||
$: console.log(song);
|
||||
|
||||
$: {
|
||||
(() => {
|
||||
if(song && song.folder && !song.audio) {
|
||||
// var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
// song.context = audioCtx;
|
||||
// song.analyser = audioCtx.createAnalyser();
|
||||
song.audio = new Audio(`${osuFolder}/Songs/${song.folder}/${song.audioFile}`);
|
||||
// song.source = audioCtx.createMediaElementSource(song.audio);
|
||||
// song.source.connect(song.analyser);
|
||||
// song.analyser.connect(audioCtx.destination);
|
||||
song.audio.play();
|
||||
if(song && song.folder && !song.audio) {
|
||||
song.audio = new Audio(process.env.USERPROFILE + "/AppData/Local/osu!/Songs/" + song.folder + "/" + song.audioFile);
|
||||
song.audio.play();
|
||||
|
||||
song.audio.onended = () => {
|
||||
playNext();
|
||||
}
|
||||
song.audio.onpause = () => {
|
||||
song.playing = false;
|
||||
if(song.video) song.video.pause();
|
||||
}
|
||||
song.audio.onplay = () => {
|
||||
song.playing = true;
|
||||
if(song.video) song.video.play();
|
||||
}
|
||||
if ('mediaSession' in navigator && config.mediaSession) {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: song.song,
|
||||
artist: song.artist,
|
||||
album: "Osu! visualizer",
|
||||
artwork: [
|
||||
{ src: `${osuFolder}/Data/bt/${song.id}.jpg`, type: 'image/jpeg' },
|
||||
]
|
||||
});
|
||||
|
||||
navigator.mediaSession.setActionHandler('play', function() { song.playing = true; song.audio.play(); });
|
||||
navigator.mediaSession.setActionHandler('pause', function() { song.playing = false; song.audio.pause(); });
|
||||
navigator.mediaSession.setActionHandler('nexttrack', function() { playNext()});
|
||||
}
|
||||
song.audio.onended = () => {
|
||||
playNext();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
$: if(song && song.audio && config.rpc) {
|
||||
if(song.playing) {
|
||||
window.songActivity = {
|
||||
state: "Listening to osu! beatmaps",
|
||||
details: `${song.artist} - ${song.song}`,
|
||||
startTimestamp: Date.now(),
|
||||
endTimestamp: Date.now() + song.audio.duration * 1000,
|
||||
instance: false,
|
||||
largeImageKey: "logo",
|
||||
largeImageText: "Osu!visualizer"
|
||||
song.audio.onpause = () => {
|
||||
playing = false;
|
||||
}
|
||||
} else {
|
||||
window.songActivity = {
|
||||
state: "Paused",
|
||||
details: `${song.artist} - ${song.song}`,
|
||||
instance: false,
|
||||
largeImageKey: "logo",
|
||||
largeImageText: "Osu!visualizer"
|
||||
song.audio.onplay = () => {
|
||||
playing = true;
|
||||
}
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: song.song,
|
||||
artist: song.artist,
|
||||
album: "Osu! visualizer",
|
||||
artwork: [
|
||||
{ src: process.env.USERPROFILE + "/AppData/Local/osu!/Data/bt/" + song.id + ".jpg", type: 'image/jpeg' },
|
||||
]
|
||||
});
|
||||
|
||||
navigator.mediaSession.setActionHandler('play', function() { playing = true; song.audio.play(); });
|
||||
navigator.mediaSession.setActionHandler('pause', function() { playing = false; song.audio.pause(); });
|
||||
navigator.mediaSession.setActionHandler('nexttrack', function() { playNext()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function togglePlay() {
|
||||
song.playing = !song.playing;
|
||||
if(!song.audio) return;
|
||||
if(song.playing) {
|
||||
playing = !playing;
|
||||
if(playing) {
|
||||
song.audio.play();
|
||||
} else {
|
||||
song.audio.pause();
|
||||
}
|
||||
}
|
||||
|
||||
var volume = 1;
|
||||
|
||||
function updateVolume(e) {
|
||||
if(!song || !song.audio || !e.altKey) return;
|
||||
lastVolumeUpdate = Date.now();
|
||||
var volume = song.audio.volume;
|
||||
volume += e.deltaY * -0.0005;
|
||||
volume = Math.min(1, Math.max(volume, 0));
|
||||
song.audio.volume = Math.min(1, Math.max(volume, 0));
|
||||
}
|
||||
|
||||
$: if(song.audio) song.audio.volume = volume;
|
||||
|
||||
setTimeout(() => {
|
||||
playNext();
|
||||
song = song;
|
||||
}, 200);
|
||||
|
||||
const volumeWidth = 100;
|
||||
const volumeStroke = 4;
|
||||
const volumeRadius = 50;
|
||||
</script>
|
||||
|
||||
<svelte:window on:mousemove={() => last = Date.now()} on:wheel={e => updateVolume(e)} />
|
||||
|
||||
<div class="menu">
|
||||
{#if now - last < config.autohide.info + 1000}
|
||||
<div class="info" class:hidden={now - last > config.autohide.info}>
|
||||
{#if song}
|
||||
<div class="song">
|
||||
<h2>{song.artist} - {song.song}</h2>
|
||||
<div class="controls">
|
||||
<div class="play" on:click={togglePlay}>
|
||||
<img src="images/music_{song.playing ? "pause" : "play"}.svg" alt="{song.playing ? "Pause" : "Play"} music" title="{song.playing ? "Pause" : "Play"} music">
|
||||
</div>
|
||||
<div class="forward" on:click={playNext}>
|
||||
<img src="images/music_forward.svg" alt="Skip the song" title="Skip the song">
|
||||
</div>
|
||||
<div class="settings" on:click={() => settingsOpen = !settingsOpen}>
|
||||
<img src="images/settings.svg" alt="Settings" title="Open settings">
|
||||
</div>
|
||||
<div class="info" class:hidden={now - last > 2000}>
|
||||
{#if song}
|
||||
<div class="song">
|
||||
<h2>{song.artist} - {song.song}</h2>
|
||||
<div class="controls">
|
||||
<div class="play" on:click={togglePlay}>
|
||||
<img src="images/music_{playing ? "pause" : "play"}.svg" alt="{playing ? "Pause" : "Play"} music" title="{playing ? "Pause" : "Play"} music">
|
||||
</div>
|
||||
<div class="forward" on:click={playNext}>
|
||||
<img src="images/music_forward.svg" alt="Skip the song" title="Skip the song">
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if now - lastVolumeUpdate < config.autohide.volume + 1000 && song && song.audio}
|
||||
<div class="volume" class:hidden={now - lastVolumeUpdate > config.autohide.volume}>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if now - lastVolumeUpdate < 4000 && song && song.audio}
|
||||
<div class="volume" class:hidden={now - lastVolumeUpdate > 2000}>
|
||||
<div class="slider">
|
||||
<div class="percent">
|
||||
{Math.round(song.audio.volume * 100)}%
|
||||
</div>
|
||||
<svg class="progress-ring" width={volumeWidth} height={volumeWidth}>
|
||||
<circle
|
||||
stroke-width={volumeStroke}
|
||||
fill="transparent"
|
||||
stroke="blue"
|
||||
stroke-dasharray={volumeRadius * 2 * Math.PI + " " + volumeRadius * 2 * Math.PI}
|
||||
stroke-dashoffset={volumeRadius * 2 * Math.PI - song.audio.volume * volumeRadius * 2 * Math.PI}
|
||||
r={volumeRadius - 1}
|
||||
cx={volumeRadius - 1}
|
||||
cy={volumeRadius + 1}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<Options bind:config={config} bind:visible={settingsOpen} {osuFolder} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
@ -195,53 +130,18 @@
|
|||
width: 100vw;
|
||||
height: 80px;
|
||||
transition: opacity 0.6s;
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.volume {
|
||||
opacity: 1;
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
z-index: 2;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 50%;
|
||||
color: black;
|
||||
font-size: 30px;
|
||||
color: white;
|
||||
background-color: black;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.volume .slider {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.percent {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.progress-ring {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.progress-ring circle {
|
||||
transition: stroke-dashoffset 0.32s;
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: 50% 50%;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
|
@ -276,8 +176,4 @@
|
|||
height: 100%;
|
||||
filter: invert(100%);
|
||||
}
|
||||
.info .controls .settings img {
|
||||
height: 65%;
|
||||
padding-top: 25%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,22 +1,21 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const fs = require("fs");
|
||||
const OsuDBParser = require("osu-db-parser");
|
||||
const osuParser = require("./lib/osu-parser.js");
|
||||
const osuParser = require("osu-parser");
|
||||
export var osuData;
|
||||
export var songData;
|
||||
export var config;
|
||||
export var osuFolder;
|
||||
|
||||
var wallpapers = [];
|
||||
try {
|
||||
wallpapers = fs.readdirSync(`${osuFolder}/Data/bg`);
|
||||
wallpapers = fs.readdirSync(process.env.USERPROFILE + "/AppData/Local/osu!/Data/bg");
|
||||
} catch(e) {
|
||||
console.error("Osu backgrounds weren't found. You must have osu installed and started at least once.", e);
|
||||
alert("Osu backgrounds not found!");
|
||||
}
|
||||
|
||||
try {
|
||||
osuData = (new OsuDBParser(Buffer.from(fs.readFileSync(`${osuFolder}/osu!.db`)))).getOsuDBData();
|
||||
osuData = (new OsuDBParser(Buffer.from(fs.readFileSync(process.env.USERPROFILE + "/AppData/Local/osu!/osu!.db")))).getOsuDBData();
|
||||
console.log(osuData);
|
||||
osuData.songs = osuData.beatmaps.map(v => ({
|
||||
artist: v.artist_name,
|
||||
|
|
@ -26,8 +25,7 @@
|
|||
song: v.song_title,
|
||||
song_u: v.song_title_unicode,
|
||||
id: v.beatmapset_id,
|
||||
dataFile: `${v.artist_name} - ${v.song_title} (${v.creator_name}) [${v.difficulty}].osu`.replace(/\/|\*|"|:|\?/g, ""),
|
||||
playing: true
|
||||
dataFile: `${v.artist_name} - ${v.song_title} (${v.creator_name}) [${v.difficulty}].osu`
|
||||
})).filter((v, i, a) => a.findIndex(x => x.id === v.id) === i);
|
||||
} catch(e) {
|
||||
console.error("Osu DB weren't found. You must have osu installed and started at least once.", e);
|
||||
|
|
@ -36,41 +34,20 @@
|
|||
|
||||
var wallpaper;
|
||||
function shuffleWallpapers() {
|
||||
switch(config.backgrounds) {
|
||||
case 1:
|
||||
if(songData.beatmap) {
|
||||
wallpaper = `${osuFolder}/Songs/${songData.folder}/${songData.beatmap.bgFilename}`;
|
||||
} else {
|
||||
wallpaper = `${osuFolder}/Data/bg/${wallpapers[Math.floor(Math.random() * wallpapers.length)]}`;
|
||||
}
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
wallpaper = `${osuFolder}/Data/bg/${wallpapers[Math.floor(Math.random() * wallpapers.length)]}`;
|
||||
}
|
||||
wallpaper = wallpapers[Math.floor(Math.random() * wallpapers.length)];
|
||||
}
|
||||
shuffleWallpapers();
|
||||
|
||||
var lastSong = null;
|
||||
var lastBackgroundOption = null;
|
||||
|
||||
$: {
|
||||
if(songData !== lastSong) {
|
||||
lastSong = songData;
|
||||
shuffleWallpapers();
|
||||
}
|
||||
if(config.backgrounds !== lastBackgroundOption) {
|
||||
lastBackgroundOption = config.backgrounds;
|
||||
shuffleWallpapers();
|
||||
}
|
||||
}
|
||||
function fetchBeatmap() {
|
||||
let file = fs.readFileSync(`${osuFolder}/Songs/${songData.folder}/${songData.dataFile}`);
|
||||
let file = fs.readFileSync(process.env.USERPROFILE + "/AppData/Local/osu!/Songs/" + songData.folder + "/" + songData.dataFile);
|
||||
songData.beatmap = osuParser.parseContent(file);
|
||||
|
||||
if(config.backgrounds === 1) {
|
||||
wallpaper = `${osuFolder}/Songs/${songData.folder}/${songData.beatmap.bgFilename}`;
|
||||
}
|
||||
}
|
||||
$: if(songData && songData.dataFile && !songData.beatmap) fetchBeatmap();
|
||||
|
||||
|
|
@ -79,11 +56,9 @@
|
|||
y: 0.5
|
||||
};
|
||||
|
||||
var parallaxTreshold;
|
||||
$: parallaxTreshold = config.parallax.treshold;
|
||||
const parallaxTreshold = 10;
|
||||
|
||||
function updateMouse(e) {
|
||||
if(!config.parallax.enabled) return;
|
||||
mouse = {
|
||||
x: -(e.clientX / window.innerWidth) * parallaxTreshold - parallaxTreshold/2,
|
||||
y: -(e.clientY / window.innerHeight) * parallaxTreshold - parallaxTreshold/2
|
||||
|
|
@ -103,7 +78,7 @@
|
|||
setInterval(() => {
|
||||
if(!songData) return;
|
||||
if(!songData.beatmap && songData.dataFile) fetchBeatmap();
|
||||
if(!songData.beatmap || !songData.audio) return;
|
||||
if(!songData.beatmap) return;
|
||||
|
||||
var tp = null;
|
||||
for(var t of songData.beatmap.timingPoints) {
|
||||
|
|
@ -119,25 +94,6 @@
|
|||
animDuration = tp.beatLength/2;
|
||||
kiaiTime = tp.kiaiTimeActive;
|
||||
}, 50);
|
||||
|
||||
$: {
|
||||
if(!songData || !songData.beatmap || !songData.beatmap.video || !config.videoBackground) window.backgroundVideo = null;
|
||||
}
|
||||
|
||||
$: console.log("Wallpaper", wallpaper);
|
||||
|
||||
$: console.log("Beatmap", songData.beatmap);
|
||||
|
||||
var backgroundVideo;
|
||||
|
||||
$: {
|
||||
if(backgroundVideo) {
|
||||
songData.video = backgroundVideo;
|
||||
if(songData && songData.audio && songData.video) {
|
||||
songData.video.currentTime = songData.audio.currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:mousemove={updateMouse} on:resize={resize} />
|
||||
|
|
@ -145,24 +101,13 @@
|
|||
<div
|
||||
class="main"
|
||||
style="
|
||||
background-image: url('{wallpaper}');
|
||||
background-image: url('{process.env.USERPROFILE.replace(/\\/g, "/")}/AppData/Local/osu!/Data/bg/{wallpaper}');
|
||||
background-size: {!isWidthSmaller ? `calc(100% + ${parallaxTreshold * 1.5}px) auto` : `auto calc(100% + ${parallaxTreshold * 1.5}px)`};
|
||||
background-position: {mouse.x}px {mouse.y}px;
|
||||
"
|
||||
>
|
||||
{#if songData && songData.beatmap && songData.beatmap.video && config.videoBackground}
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<video bind:this={backgroundVideo} style="
|
||||
width: {isWidthSmaller ? "auto" : `calc(100% + ${parallaxTreshold * 1.5}px)`};
|
||||
height: {!isWidthSmaller ? "auto" : `calc(100% + ${parallaxTreshold * 1.5}px)`};
|
||||
top: {mouse.y}px;
|
||||
left: {mouse.x}px;
|
||||
">
|
||||
<source src="file://{osuFolder}/Songs/{songData.folder}/{songData.beatmap.video}">
|
||||
</video>
|
||||
{/if}
|
||||
<img src="images/logo.svg" alt="logo" class="logo" style="animation-duration: {animDuration}ms;" class:repeat={songData.playing}>
|
||||
<img src="images/logo.svg" alt="" class="shadow" style="animation-duration: {animDuration * 2}ms;" class:repeat={songData.playing}>
|
||||
<img src="images/logo.svg" alt="logo" class="logo" style="animation-duration: {animDuration}ms;">
|
||||
<img src="images/logo.svg" alt="" class="shadow" style="animation-duration: {animDuration * 2}ms;">
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
@ -209,36 +154,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
video {
|
||||
position: fixed;
|
||||
z-index: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.main img {
|
||||
position: fixed;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
top: calc(50vh - 250px);
|
||||
left: calc(50vw - 250px);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.main .logo {
|
||||
animation-name: bpm;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
|
||||
.main .shadow {
|
||||
opacity: 0.2;
|
||||
animation-name: bpmShadow;
|
||||
animation-iteration-count: infinite;
|
||||
animation-delay: 50ms;
|
||||
}
|
||||
|
||||
.main .repeat {
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
<script>
|
||||
export var config;
|
||||
export var visible;
|
||||
export var osuFolder;
|
||||
|
||||
$: console.log("Config", config);
|
||||
</script>
|
||||
|
||||
<div class="options">
|
||||
<div class="bg" class:visible={visible} on:click={() => visible = false}></div>
|
||||
<nav class:visible={visible}>
|
||||
<h2>Options</h2>
|
||||
|
||||
<div class="group">
|
||||
<h3>Parallax</h3>
|
||||
<div class="row">
|
||||
<span>Enable parallax</span>
|
||||
<input type="checkbox" bind:checked={config.parallax.enabled}>
|
||||
</div>
|
||||
<div class="row" class:enabled={config.parallax.enabled}>
|
||||
<span>Parallax treshold</span>
|
||||
<input type="range" min="1" max="30" bind:value={config.parallax.treshold}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Integrations</h3>
|
||||
<div class="row">
|
||||
<span>Discord Rich Presence</span>
|
||||
<input type="checkbox" bind:checked={config.rpc}>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>MediaSession (system-wide controls)</span>
|
||||
<input type="checkbox" bind:checked={config.mediaSession}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Backgrounds</h3>
|
||||
<select bind:value={config.backgrounds}>
|
||||
<option value={0}>Osu!wallpapers</option>
|
||||
<option value={1}>Beatmap wallpapers</option>
|
||||
</select>
|
||||
<div class="row">
|
||||
<span>Video backgrounds</span>
|
||||
<input type="checkbox" bind:checked={config.videoBackground}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>UI</h3>
|
||||
<div class="row">
|
||||
<span>Song info hide timeout</span>
|
||||
<input type="range" min="1000" max="15000" step="500" bind:value={config.autohide.info}>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>Volume hide timeout</span>
|
||||
<input type="range" min="1000" max="15000" step="500" bind:value={config.autohide.volume}>
|
||||
</div>
|
||||
</div>
|
||||
<span>Osu folder used: <code>{osuFolder}</code></span>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.bg {
|
||||
position: fixed;
|
||||
display: none;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
.bg.visible {
|
||||
display: block;
|
||||
}
|
||||
nav {
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
width: 400px;
|
||||
top: 0;
|
||||
left: -400px;
|
||||
opacity: 0;
|
||||
background: rgba(0,0,0,0.4);
|
||||
color: white;
|
||||
z-index: 4;
|
||||
padding-left: 1rem;
|
||||
transition: opacity 0.3s, left 0.3s;
|
||||
}
|
||||
nav.visible {
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
83
src/index.js
83
src/index.js
|
|
@ -1,48 +1,38 @@
|
|||
const { app, BrowserWindow } = require('electron');
|
||||
const isDev = require('electron-is-dev');
|
||||
const RPC = require("discord-rpc");
|
||||
const rpc = new RPC.Client({ transport: "ipc" });
|
||||
const path = require('path');
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
|
||||
app.quit();
|
||||
app.quit();
|
||||
}
|
||||
|
||||
if (!isDev) require('update-electron-app')()
|
||||
|
||||
console.log("isDev?", isDev);
|
||||
if(!isDev) require('update-electron-app')()
|
||||
|
||||
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');
|
||||
|
||||
var mainWindow;
|
||||
|
||||
const createWindow = () => {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true
|
||||
},
|
||||
autoHideMenuBar: true
|
||||
});
|
||||
mainWindow.setMenu(null);
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
},
|
||||
autoHideMenuBar: true
|
||||
});
|
||||
mainWindow.setMenu(null);
|
||||
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadFile(path.join(__dirname, '../public/index.html'));
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadFile(path.join(__dirname, '../public/index.html'));
|
||||
|
||||
// Open the DevTools.
|
||||
if(isDev) mainWindow.webContents.openDevTools({
|
||||
activate: true,
|
||||
mode: 'detach'
|
||||
});
|
||||
// Open the DevTools.
|
||||
mainWindow.webContents.openDevTools();
|
||||
};
|
||||
|
||||
require('electron-reload')(__dirname, {
|
||||
electron: path.join(__dirname, '../node_modules', '.bin', 'electron'),
|
||||
awaitWriteFinish: true,
|
||||
electron: path.join(__dirname, '../node_modules', '.bin', 'electron'),
|
||||
awaitWriteFinish: true,
|
||||
});
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
|
|
@ -54,39 +44,18 @@ app.on('ready', createWindow);
|
|||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and import them here.
|
||||
|
||||
async function setActivity() {
|
||||
if (!rpc || !mainWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activity = await mainWindow.webContents.executeJavaScript('window.songActivity');
|
||||
|
||||
rpc.setActivity(activity).catch((e) => { console.error(e); });
|
||||
}
|
||||
|
||||
rpc.on('ready', () => {
|
||||
setActivity();
|
||||
|
||||
// activity can only be set every 15 seconds
|
||||
setInterval(() => {
|
||||
setActivity();
|
||||
}, 15e3);
|
||||
});
|
||||
|
||||
rpc.login({ clientId: "756806736106618951" });
|
||||
Loading…
Reference in a new issue