Compare commits

..

No commits in common. "rustify" and "master" have entirely different histories.

135 changed files with 12480 additions and 856 deletions

13
.editorconfig Normal file
View file

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View file

@ -1,2 +1 @@
.git
node_modules

56
.eslintrc.json Normal file
View file

@ -0,0 +1,56 @@
{
"root": true,
"ignorePatterns": [
"**/*"
],
"plugins": [
"@nx"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": [
"*"
]
}
]
}
]
}
},
{
"files": [
"*.ts",
"*.tsx"
],
"extends": [
"plugin:@nx/typescript"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"extends": [
"plugin:@nx/javascript"
],
"rules": {}
}
]
}

6
.gitignore vendored
View file

@ -50,8 +50,4 @@ Thumbs.db
*.png
*.jpeg
*.jpg
*.exr
# Added by cargo
/target
*.exr

3
.prettierignore Normal file
View file

@ -0,0 +1,3 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage

13
.prettierrc Normal file
View file

@ -0,0 +1,13 @@
{
"parser": "typescript",
"trailingComma": "none",
"useTabs": true,
"tabWidth": 4,
"semi": false,
"singleQuote": false,
"endOfLine": "lf",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"printWidth": 150
}

402
Cargo.lock generated
View file

@ -1,402 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "euterpe"
version = "0.1.0"
dependencies = [
"thiserror",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "euterpe_db"
version = "0.1.0"
dependencies = [
"thiserror",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "euterpe_dj"
version = "0.1.0"
dependencies = [
"thiserror",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "euterpe_player"
version = "0.1.0"
dependencies = [
"futures-signals",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "euterpe_preprocessor"
version = "0.1.0"
dependencies = [
"thiserror",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "euterpe_visualizer"
version = "0.1.0"
dependencies = [
"thiserror",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "futures-channel"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-macro"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-signals"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b175f2f6600dd81d92d20cf10872b03ea9df6b2513ca7f672341260dacb1ab2"
dependencies = [
"discard",
"futures-channel",
"futures-core",
"futures-util",
"gensym",
"log",
"pin-project",
"serde",
]
[[package]]
name = "futures-task"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gensym"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82"
dependencies = [
"proc-macro2",
"quote",
"syn",
"uuid",
]
[[package]]
name = "getrandom"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "pin-project"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "syn"
version = "2.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "uuid"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "web-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
]

View file

@ -1,19 +0,0 @@
[workspace]
members = [
"libs/dj",
"libs/euterpe",
"libs/db",
"libs/player",
"libs/preprocessor",
"libs/visualizer",
]
resolver = "2"
[workspace.dependencies]
thiserror = "1.0.58"
wasm-bindgen = "0.2.92"
futures-signals = "0.3.33"
[workspace.dependencies.web-sys]
version = "0.3.69"
features = ["AudioContext"]

5
babel.config.json Normal file
View file

@ -0,0 +1,5 @@
{
"babelrcRoots": [
"*"
]
}

View file

@ -1,19 +0,0 @@
[package]
name = "euterpe_db"
version = "0.1.0"
edition = "2021"
authors = ["Djkáťo <djkatovfx@gmail.com>"]
description = "Fully featured web player"
homepage = "https://github.com/euterpe/euterpe-source"
repository = "https://github.com/euterpe/euterpe-source"
documentation = "https://github.com/euterpe/euterpe-source"
# keywords = ["player", "web"]
# categories = ["web-programming::http-server"]
[dependencies]
thiserror.workspace = true
wasm-bindgen.workspace = true
[dependencies.web-sys]
workspace = true
features = []

View file

@ -1,19 +0,0 @@
[package]
name = "euterpe_dj"
version = "0.1.0"
edition = "2021"
authors = ["Djkáťo <djkatovfx@gmail.com>"]
description = "Fully featured web player"
homepage = "https://github.com/euterpe/euterpe-source"
repository = "https://github.com/euterpe/euterpe-source"
documentation = "https://github.com/euterpe/euterpe-source"
# keywords = ["player", "web"]
# categories = ["web-programming::http-server"]
[dependencies]
thiserror.workspace = true
wasm-bindgen.workspace = true
[dependencies.web-sys]
workspace = true
features = []

View file

View file

@ -1,19 +0,0 @@
[package]
name = "euterpe"
version = "0.1.0"
edition = "2021"
authors = ["Djkáťo <djkatovfx@gmail.com>"]
description = "Fully featured web player"
homepage = "https://github.com/euterpe/euterpe-source"
repository = "https://github.com/euterpe/euterpe-source"
documentation = "https://github.com/euterpe/euterpe-source"
# keywords = ["player", "web"]
# categories = ["web-programming::http-server"]
[dependencies]
thiserror.workspace = true
wasm-bindgen.workspace = true
[dependencies.web-sys]
workspace = true
features = []

View file

@ -1,16 +0,0 @@
# Euterpe
Fully featured AudioContext music player for the web, written in Rust.
## Euterpe in production:
- Hypertrance ( [site](https://hypertrance.eu/), [repository](https://github.com/nuphory/hypertrance.eu) )
Features:
- "Local" library/database for songs, collections, artists, waveforms, artist links and much more!
- Queue and history
- Easy way to create Vector based audio visuals
- Library automatization based on folder/file structure, preprocessing and encoding media files for all platforms
- Safe. Provides wrappers for all functions that are either unsafe or don't give a success return. (very Rust inspired, yes.)
- Frontend library agnostic

View file

@ -1,2 +0,0 @@
[dependencies]
euterpe_player = { path = "player", version = "*" }

View file

@ -1,36 +0,0 @@
[package]
name = "euterpe_player"
version = "0.1.0"
edition = "2021"
authors = ["Djkáťo <djkatovfx@gmail.com>"]
description = "Fully featured web player"
homepage = "https://github.com/euterpe/euterpe-source"
repository = "https://github.com/euterpe/euterpe-source"
documentation = "https://github.com/euterpe/euterpe-source"
# keywords = ["player", "web"]
# categories = ["web-programming::http-server"]
[dependencies]
thiserror.workspace = true
wasm-bindgen.workspace = true
futures-signals = "0.3.33"
wasm-bindgen-futures = "0.4.42"
[lib]
crate-type = ["cdylib"]
[dependencies.web-sys]
features = [
"AudioContext",
"AbortController",
"Event",
"MediaElementAudioSourceNode",
"AnalyserNode",
"AudioDestinationNode",
"AudioContextState",
"HtmlAudioElement",
"BaseAudioContext",
"GainNode",
"AudioParam",
]
workspace = true

View file

@ -1,277 +0,0 @@
use futures_signals::signal::Mutable;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
use web_sys::{
AudioContext, AudioContextState, AudioNode, GainNode, HtmlAudioElement,
MediaElementAudioSourceNode,
};
pub struct MusicPlayerBuilder {
audio_context: AudioContext,
audio_element: HtmlAudioElement,
gain: GainNode,
track: MediaElementAudioSourceNode,
volume: f32,
prev_node: Option<AudioNode>,
is_gain_connected: bool,
}
impl MusicPlayerBuilder {
pub fn new(audio_element: HtmlAudioElement) -> Result<MusicPlayerBuilder, JsValue> {
if audio_element.is_undefined() {
return Err("Audio Element is undefined".into());
}
let audio_context = AudioContext::new()?;
let track = audio_context.create_media_element_source(&audio_element)?;
let gain = audio_context.create_gain()?;
Ok(MusicPlayerBuilder {
audio_context,
audio_element,
gain,
track,
volume: 1.,
prev_node: None,
is_gain_connected: false,
})
}
pub fn add_analyzer(mut self) -> Result<MusicPlayerBuilder, JsValue> {
let analyzer = self.audio_context.create_analyser()?;
if let Some(node) = &self.prev_node {
node.connect_with_audio_node(&analyzer)?;
} else {
self.track.connect_with_audio_node(&analyzer)?;
}
self.prev_node = Some(analyzer.into());
Ok(self)
}
pub fn add_custom_node(mut self, node: AudioNode) -> Result<MusicPlayerBuilder, JsValue> {
match &self.prev_node {
Some(n) => n.connect_with_audio_node(&node)?,
None => self.track.connect_with_audio_node(&node)?,
};
self.prev_node = Some(node);
Ok(self)
}
pub fn build(self) -> Result<MusicPlayer, JsValue> {
if !self.is_gain_connected {
match &self.prev_node {
None => {
self.track.connect_with_audio_node(self.gain.as_ref())?;
}
Some(node) => {
node.connect_with_audio_node(self.gain.as_ref())?;
}
};
}
if let Some(node) = &self.prev_node {
node.connect_with_audio_node(self.audio_context.destination().as_ref())?;
}
Ok(MusicPlayer {
audio_context: self.audio_context,
gain: self.gain,
volume: Mutable::new(self.volume),
audio_element: self.audio_element,
volume_cache: 0.,
current_song_duration: Mutable::new(0.),
is_playing: Mutable::new(false),
time: Mutable::new(0.),
current_song_path: None,
})
}
}
pub struct MusicPlayer {
pub current_song_duration: Mutable<f64>,
pub is_playing: Mutable<bool>,
pub time: Mutable<f32>,
pub audio_context: AudioContext,
pub audio_element: HtmlAudioElement,
pub gain: GainNode,
pub volume: Mutable<f32>,
current_song_path: Option<String>,
volume_cache: f32,
}
impl MusicPlayer {
pub fn mute_toggle(&mut self) {
if self.gain.gain().value() == 0. {
self.unmute();
} else {
self.mute()
}
}
pub fn mute(&mut self) {
self.volume_cache = self.gain.gain().value();
/* Gentler mute, doesn't pop
gain.gain.linearRampToValueAtTime(
0,
audio_context.currentTime + 0.1
);*/
self.volume.set(0.);
self.gain.gain().set_value(0.);
}
pub fn unmute(&mut self) {
self.volume.set(self.volume_cache);
self.gain.gain().set_value(self.volume_cache);
}
pub fn change_volume(&mut self, volume: f32) {
self.volume.set(volume);
self.gain.gain().set_value(volume);
}
pub async fn seek(&mut self, new_time: f64) -> Result<(), ()> {
if self.audio_context.state() != AudioContextState::Running {
self.is_playing.set(false);
}
self.audio_element.set_current_time(new_time);
Ok(())
}
pub async fn play_toggle(&mut self) -> Result<(), ()> {
if self.audio_context.state() != AudioContextState::Running {
JsFuture::from(self.audio_context.resume().unwrap())
.await
.unwrap();
}
if self.audio_element.paused() {
// try {
JsFuture::from(self.audio_element.play().unwrap())
.await
.unwrap();
self.is_playing.set(true);
// } catch (e) {
self.is_playing.set(false);
// throw e
// }
} else {
self.audio_element.pause().unwrap();
self.is_playing.set(false);
}
Ok(())
}
pub async fn play(&mut self) -> Result<&mut Self, JsValue> {
if self.is_playing.get() {
return Ok(self);
}
if self.audio_context.state() != AudioContextState::Running {
JsFuture::from(self.audio_context.resume()?).await?;
}
if self.audio_element.paused() {
// try {
JsFuture::from(self.audio_element.play()?).await?;
self.is_playing.set(true);
// } catch (e) {
self.is_playing.set(false);
// throw e
// }
}
Ok(self)
}
pub fn pause(&mut self) -> Result<&mut Self, JsValue> {
self.audio_element.pause()?;
self.is_playing.set(false);
Ok(self)
}
pub async fn new_song(&mut self, path: String) -> Result<&mut Self, JsValue> {
if self.audio_context.state() != AudioContextState::Running {
JsFuture::from(self.audio_context.resume()?).await?;
}
self.audio_element.set_src(&path);
Ok(self)
/*
let good_abort_controller = AbortController::new()?;
let bad_abort_controller = AbortController::new()?;
let can_play_through: Box<dyn FnMut(_)> = Box::new(move |_: web_sys::Event| {
good_abort_controller.abort();
});
let can_play_through_cb = Closure::wrap(can_play_through);
let bad_event: Box<dyn FnMut(_)> = Box::new(move |_: web_sys::Event| {
bad_abort_controller.abort();
});
let bad_event_cb = Closure::wrap(bad_event);
self.audio_element.add_event_listener_with_callback(
"canplaythrough",
can_play_through_cb.as_ref().unchecked_ref(),
)?;
self.audio_element
.add_event_listener_with_callback("error", abort_cb.as_ref().unchecked_ref())?;
self.audio_element.remove_event_listener_with_callback(
"canplaythrough",
abort_cb.as_ref().unchecked_ref(),
)?;
self.audio_element
.remove_event_listener_with_callback("error", abort_cb.as_ref().unchecked_ref())?;
self.audio_element
.remove_event_listener_with_callback("stalled", abort_cb.as_ref().unchecked_ref())?;
*/
}
// /**
// * Will parse the duration of the song to make it easy to display in UI
// * If somethings undefined it returns "0:00"
// */
// pub fn get_formatted_duration() {
// let dur = self.audio_element.duration;
// self.current_song_duration = self.audio_element.duration;
//
// if (dur == 0 || !dur) return "0:00";
//
// // ~ is Bitwise NOT, equivalent to Math.floor()
// let hrs = ~~(dur / 3600)
// let mins = ~~((dur % 3600) / 60)
// let secs = ~~dur % 60
//
// let ret = ""
// if (hrs > 0) {
// ret += "" + hrs + ":" + (mins < 10 ? "0" : "")
// }
//
// ret += "" + mins + ":" + (secs < 10 ? "0" : "")
// ret += "" + secs
// return ret
// }
// /**
// * Will parse the current time of the song to make it easy to display in UI
// * If somethings undefined it returns "0:00"
// */
fn get_formatted_current_time(time: f64) -> String {
if time == 0. {
return "0:00".to_owned();
}
let hrs = f64::floor(time / 3600.);
let mins = f64::floor((time % 3600.) / 60.);
let secs = time / 60.;
let mut res = "".to_owned();
if hrs > 0. {
res = hrs.to_string()
+ ":"
+ match mins < 10. {
true => "0",
false => "",
};
}
res = res
+ &mins.to_string()
+ ":"
+ match secs < 10. {
true => "0",
false => "",
};
res = res + &secs.to_string();
res
}
}

View file

@ -1,19 +0,0 @@
[package]
name = "euterpe_preprocessor"
version = "0.1.0"
edition = "2021"
authors = ["Djkáťo <djkatovfx@gmail.com>"]
description = "Fully featured web player"
homepage = "https://github.com/euterpe/euterpe-source"
repository = "https://github.com/euterpe/euterpe-source"
documentation = "https://github.com/euterpe/euterpe-source"
# keywords = ["player", "web"]
# categories = ["web-programming::http-server"]
[dependencies]
thiserror.workspace = true
wasm-bindgen.workspace = true
[dependencies.web-sys]
workspace = true
features = []

View file

@ -1,3 +0,0 @@
fn main() {
todo!();
}

View file

@ -1,19 +0,0 @@
[package]
name = "euterpe_visualizer"
version = "0.1.0"
edition = "2021"
authors = ["Djkáťo <djkatovfx@gmail.com>"]
description = "Fully featured web player"
homepage = "https://github.com/euterpe/euterpe-source"
repository = "https://github.com/euterpe/euterpe-source"
documentation = "https://github.com/euterpe/euterpe-source"
# keywords = ["player", "web"]
# categories = ["web-programming::http-server"]
[dependencies]
thiserror.workspace = true
wasm-bindgen.workspace = true
[dependencies.web-sys]
workspace = true
features = []

76
nx.json Normal file
View file

@ -0,0 +1,76 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"affected": {
"defaultBase": "master"
},
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"lint",
"test",
"e2e"
]
}
}
},
"targetDefaults": {
"build": {
"dependsOn": [
"^build"
],
"inputs": [
"production",
"^production"
]
},
"lint": {
"inputs": [
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore"
]
},
"e2e": {
"inputs": [
"default",
"^production"
]
},
"test": {
"inputs": [
"default",
"^production"
]
}
},
"namedInputs": {
"default": [
"{projectRoot}/**/*",
"sharedGlobals"
],
"production": [
"default",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json"
],
"sharedGlobals": [
"{workspaceRoot}/babel.config.json"
]
},
"workspaceLayout": {
"appsDir": "packages",
"libsDir": "packages"
},
"generators": {
"@nx/web:application": {
"style": "css",
"linter": "eslint",
"unitTestRunner": "vitest",
"e2eTestRunner": "cypress"
}
}
}

10994
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

45
package.json Normal file
View file

@ -0,0 +1,45 @@
{
"name": "@euterpe.js/source",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"format": "prettier --write '**/*.{js,ts,css,html,json,mjs}'",
"publish-player": "nx build player && cd dist/packages/player && npm publish --access=public",
"publish-visualizer": "nx build visualizer && cd dist/packages/visualizer && npm publish --access=public",
"publish-library": "nx build music-library && cd dist/packages/music-library && npm publish --access=public",
"publish-euterpe": "nx build euterpe && cd dist/packages/euterpe && npm publish --access=public",
"publish-all": "npm run publish-player && npm run publish-library && npm run publish-visualizer && npm run publish-euterpe"
},
"private": false,
"devDependencies": {
"@nx/cypress": "16.2.1",
"@nx/eslint-plugin": "16.2.1",
"@nx/js": "16.2.1",
"@nx/linter": "16.2.1",
"@nx/vite": "^16.2.1",
"@nx/web": "^16.2.1",
"@nx/workspace": "16.2.1",
"@swc/core": "~1.3.51",
"@types/node": "^20.2.1",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"@vitest/coverage-c8": "^0.31.0",
"@vitest/ui": "^0.31.0",
"cypress": "^12.11.0",
"eslint": "~8.15.0",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-cypress": "^2.10.3",
"jsdom": "~20.0.3",
"nx": "16.2.1",
"prettier": "^2.6.2",
"swc-loader": "0.1.15",
"typescript": "~5.0.2",
"vite": "^4.3.4",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.0.2",
"vitest": "^0.31.0"
},
"dependencies": {
"tslib": "^2.3.0"
}
}

5
packages/dj/package.json Normal file
View file

@ -0,0 +1,5 @@
{
"name": "@euterpe.js/dj",
"version": "0.0.1",
"type": "module"
}

40
packages/dj/project.json Normal file
View file

@ -0,0 +1,40 @@
{
"name": "dj",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/dj/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/dj",
"main": "packages/dj/src/index.ts",
"tsConfig": "packages/dj/tsconfig.lib.json",
"assets": [
"packages/dj/*.md"
]
}
},
"publish": {
"command": "node tools/scripts/publish.mjs dj {args.ver} {args.tag}",
"dependsOn": [
"build"
]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"packages/dj/**/*.ts"
]
}
}
},
"tags": []
}

19
packages/dj/tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View file

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

176
packages/euterpe/README.md Normal file
View file

@ -0,0 +1,176 @@
# Euterpe
Fully featured AudioContext music player for the web.
## Euterpe in production:
- Hypertrance ( [site](https://hypertrance.eu/), [repository](https://github.com/nuphory/hypertrance.eu) )
Features:
- "Local" library/database for songs, collections, artists, waveforms, artist links and much more!
- Queue and history
- Easy way to create Vector based audio visuals
- Library automatization based on folder/file structure, preprocessing and encoding media files for all platforms
- Safe. Provides wrappers for all functions that are either unsafe or don't give a success return. (very Rust inspired, yes.)
- Frontend library agnostic
## How to use:
#### Simple demo [here](https://github.com/euterpe-js/euterpe-source/tree/master/packages/euterpe-web-test)
Since this package is just a compilation of our smaller modules, you can read individual modules' tutorials on their respective npm page:
- [Euterpe Player](https://www.npmjs.com/package/@euterpe.js/player)
- [Euterpe Visualizer](https://www.npmjs.com/package/@euterpe.js/visualizer)
- [Euterpe Music Library](https://www.npmjs.com/package/@euterpe.js/music-library)
You can further check out how to automate database creation from folder structure, auto encode media for all platforms and create waveform svgs for songs here:
- [Euterpe Preprocessor](https://www.npmjs.com/package/@euterpe.js/preprocessor)
This module builds on those, and further adds functions for playing backwards, forwards and managing the queue.
First we create a database with our songs
`db.ts`
```ts
import { DB, Song, Artist, Ref, RefTo, Platforms } from "@euterpe.js/music-library"
export const db = new DB
db.add([
//The IDs are added incrementally & are 0 based., so first artists ID added is 0, next 1 etc...
//You can specify the ID manually if you want
new Artist({
name: "Machinedrum",
}),
new Artist({
name: "Tanerélle",
}),
new Artist({
name: "Mono/Poly",
}),
new Artist({
name: "IMANU",
links: [
[Platforms.Spotify, new URL("https://open.spotify.com/artist/5Y7rFm0tiJTVDzGLMzz0W1?si=DRaZyugTTIqlBHDkMGKVqA&nd=1")]
]
}),
])
db.add([
new Song({
//Refrences are constructed as such. This allows to get to the artist from either collection or song
artists: [new Ref(RefTo.Artists, 2), new Ref(RefTo.Artists, 3), new Ref(RefTo.Artists, 4)],
duration: 252,
name: "Star",
remix_artists: [new Ref(RefTo.Artists, 5)],
url: new URL("http://" + window.location.host + "/Machinedrum, Tanerelle & Mono Poly - Star (IMANU Remix) final.mp3")
}),
])
```
Then we build our Euterpe player and assign the db to it. Then it's just a matter of creating event listeners to the dom and binding them to Euterpes functions.
`main.ts`
```ts
import { db } from "./db";
import { EuterpeBuilder } from "@euterpe.js/euterpe"
let is_seeking = false
const euterpe = new EuterpeBuilder(document.querySelector("#audio")!, db)
.build()
document.querySelector("#seek")?.addEventListener("mouseup", (e) => {
try {
euterpe.try_seek(e.target?.valueAsNumber)
} catch {
alert("Failed seeking! " + e)
}
is_seeking = false
})
euterpe.on_song_change((_, song_name) => {
document.querySelector("#text-playing")!.innerHTML = song_name
})
document.querySelector("#previous")?.addEventListener("click", () => {
euterpe.try_previous_song_looping().catch((e) => alert(e + "Failed to change song"))
})
document.querySelector("#next")?.addEventListener("click", () => {
euterpe.try_next_song_looping().catch((e) => alert(e + "Failed to change song"))
})
document.querySelector("#mute")?.addEventListener("click", () => {
euterpe.mute()
})
document.querySelector("#unmute")?.addEventListener("click", () => {
euterpe.unmute()
})
document.querySelector("#toggle-play")?.addEventListener("click", () => {
euterpe.try_play_toggle().catch((e) => alert("failed to toggle pause/play!" + e))
})
document.querySelector("#volume")?.addEventListener("input", (e) => {
euterpe.change_volume(e.target?.valueAsNumber)
})
//disables time updates so the time slider doesn't slip away from user
document.querySelector("#seek")?.addEventListener("mousedown", () => {
is_seeking = true
})
```
Then we can set up listeners to Euterpes events to keep the UI up todate as well
`main.ts`
```ts
//...
// Subscriptions to song and AudioContext changes, eg. time, name..
euterpe.on_duration_formatted((time) => {
document.querySelector("#duration")!.innerHTML = time
document.querySelector("#seek")!.max = "" + euterpe.current_song_duration
})
euterpe.on_time_tick_formatted((time) => {
document.querySelector("#current")!.innerHTML = time
})
euterpe.on_time_tick((time) => {
if (is_seeking) return
document.querySelector("#seek")!.value = "" + time
dev_queue_update()
dev_history_update()
})
euterpe.on_song_change((_, song_name) => {
document.querySelector("#text-playing")!.innerHTML = song_name
})
//preload after setting all listeners to make sure you capture the song update!
euterpe.try_preload_song(0).catch((e) => console.log(e + " Failed to preload"))
//..
function dev_queue_update() {
const p = document.querySelector("#queue-info") as HTMLParagraphElement
const dev_arr = []
for (const song of euterpe.queue) {
dev_arr.push(`Name: ${song.name}, ID: ${song.id} |`)
}
p.innerHTML = dev_arr.toString()
}
function dev_history_update() {
const p = document.querySelector("#history-info") as HTMLParagraphElement
const dev_arr = []
for (const song of euterpe.played_history) {
dev_arr.push(`Name: ${song.name}, ID: ${song.id} |`)
}
p.innerHTML = dev_arr.toString()
}
```
and it's done!
For vizualizer demo, or how to use the core parts of the Euterpe libraries separately, check out the individual repos readmes.

View file

@ -0,0 +1,33 @@
{
"name": "@euterpe.js/euterpe",
"version": "2.1.0",
"type": "module",
"description": "Fully featured solution for playing music on the web. Support for local library, audio visuals and more!",
"main": "./src/index.js",
"author": {
"name": "Djkáťo",
"email": "djkatovfx@gmail.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/euterpe-js/euterpe-source.git"
},
"homepage": "https://github.com/euterpe-js/euterpe-source/tree/master/packages/euterpe#readme",
"keywords": [
"audio",
"library",
"music-database",
"audio-player",
"webaudio",
"database",
"db"
],
"exports": {
".": {
"types": "./src/index.d.ts",
"import": "./src/index.js",
"require": "./src/lib/euterpe.js"
}
}
}

View file

@ -0,0 +1,40 @@
{
"name": "euterpe",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/euterpe/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/euterpe",
"main": "packages/euterpe/src/index.ts",
"tsConfig": "packages/euterpe/tsconfig.lib.json",
"assets": [
"packages/euterpe/*.md"
]
}
},
"publish": {
"command": "node tools/scripts/publish.mjs euterpe {args.ver} {args.tag}",
"dependsOn": [
"build"
]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"packages/euterpe/**/*.ts"
]
}
}
},
"tags": []
}

View file

@ -0,0 +1,3 @@
export function euterpe(): string {
return "euterpe"
}

View file

@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View file

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

View file

@ -0,0 +1,33 @@
{
"extends": [
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

View file

@ -0,0 +1,33 @@
{
"name": "@euterpe.js/music-library",
"version": "1.0.12",
"type": "module",
"description": "A simple music library, acting as a Local DB as JS Object. Contains everything a person would need to store their music data for website playback.",
"main": "./src/index.js",
"author": {
"name": "Djkáťo",
"email": "djkatovfx@gmail.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/euterpe-js/euterpe-source.git"
},
"homepage": "https://github.com/euterpe-js/euterpe-source/tree/master/packages/music-library#readme",
"keywords": [
"audio",
"library",
"music-database",
"audio-player",
"webaudio",
"database",
"db"
],
"exports": {
".": {
"types": "./src/index.d.ts",
"import": "./src/index.js",
"require": "./src/lib/music-library.js"
}
}
}

View file

@ -0,0 +1,46 @@
{
"name": "music-library",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/music-library/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/music-library",
"main": "packages/music-library/src/index.ts",
"tsConfig": "packages/music-library/tsconfig.lib.json",
"assets": [
"packages/music-library/*.md"
]
}
},
"publish": {
"command": "node tools/scripts/publish.mjs music-library {args.ver} {args.tag}",
"dependsOn": [
"build"
]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"packages/music-library/**/*.ts"
]
}
},
"run": {
"dependsOn": [
"build"
],
"command": "node ./dist/packages/music-library/src/index.js"
}
},
"tags": []
}

View file

@ -0,0 +1,3 @@
export function musicLibrary(): string {
return "music-library"
}

View file

@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "esnext",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View file

@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": [
"node"
]
},
"include": [
"src/**/*.ts",
"README.md"
],
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts"
]
}

View file

@ -0,0 +1,33 @@
{
"extends": [
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,33 @@
{
"extends": [
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

View file

@ -0,0 +1,32 @@
{
"name": "@euterpe.js/player",
"version": "2.1.0",
"type": "module",
"description": "A simple, safe AudioContext web music player",
"main": "./src/index.js",
"author": {
"name": "Djkáťo",
"email": "djkatovfx@gmail.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/euterpe-js/euterpe-source.git"
},
"homepage": "https://github.com/euterpe-js/euterpe-source/tree/master/packages/player#readme",
"keywords": [
"audio",
"player",
"music-player",
"audio-visualizer",
"webaudio",
"vizualizer"
],
"exports": {
".": {
"types": "./src/index.d.ts",
"import": "./src/index.js",
"require": "./src/lib/player.js"
}
}
}

View file

@ -0,0 +1,39 @@
{
"name": "player",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/player/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/player",
"main": "packages/player/src/index.ts",
"tsConfig": "packages/player/tsconfig.lib.json",
"assets": [
"packages/player/README.md"
]
}
},
"publish": {
"command": "node tools/scripts/publish.mjs player {args.ver} {args.tag}",
"dependsOn": [
"build"
]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"packages/player/**/*.ts"
]
}
}
}
}

View file

@ -0,0 +1,3 @@
export function player(): string {
return "player"
}

View file

@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "esnext",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View file

@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": [
"node"
]
},
"include": [
"src/**/*.ts",
"README.md"
],
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts"
]
}

30
packages/preprocessor/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# public samples
public/samples/*
public/media/*
src/song_list.ts
src/db.js

Some files were not shown because too many files have changed in this diff Show more