Start of euterpe dj!
This commit is contained in:
parent
044e38ccf1
commit
34facec032
13 changed files with 332 additions and 5 deletions
33
packages/dj/.eslintrc.json
Normal file
33
packages/dj/.eslintrc.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"extends": [
|
||||
"../../.eslintrc.json"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"!**/*"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx",
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
13
packages/dj/README.md
Normal file
13
packages/dj/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# dj
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build dj` to build the library.
|
||||
|
||||
|
||||
|
||||
|
5
packages/dj/package.json
Normal file
5
packages/dj/package.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "@euterpe.js/dj",
|
||||
"version": "0.0.1",
|
||||
"type": "module"
|
||||
}
|
40
packages/dj/project.json
Normal file
40
packages/dj/project.json
Normal 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": []
|
||||
}
|
74
packages/dj/src/db_extend.ts
Normal file
74
packages/dj/src/db_extend.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { Artist, Collection, DB, Ref, RefTo, Song } from "@euterpe.js/music-library";
|
||||
export { DJSong, DJDB }
|
||||
type ID = number
|
||||
|
||||
interface SongConstructor {
|
||||
name: string
|
||||
artists?: Ref[]
|
||||
url: URL
|
||||
duration?: number
|
||||
publish_date?: Date
|
||||
remix_artists?: Ref[]
|
||||
in_collection?: Ref
|
||||
cover?: URL
|
||||
bpm?: number
|
||||
key?: string
|
||||
fft_data?: number[]
|
||||
id?: ID
|
||||
metadata?: any[]
|
||||
}
|
||||
|
||||
class DJSong extends Song {
|
||||
audio_buffer?: AudioBuffer
|
||||
constructor(data: SongConstructor, audio_context?: AudioContext) {
|
||||
super(data)
|
||||
|
||||
if (!audio_context) return
|
||||
try {
|
||||
fetch(data.url).then((file) => {
|
||||
file.arrayBuffer().then((buffer) => {
|
||||
audio_context.decodeAudioData(buffer).then((audio_buffer) => {
|
||||
this.audio_buffer = audio_buffer
|
||||
})
|
||||
})
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(new Error("Failed to preprocess DJSong. " + e))
|
||||
}
|
||||
}
|
||||
public async analyze(url: URL, audio_context: AudioContext) {
|
||||
this.audio_buffer = await audio_context.decodeAudioData(await (await fetch(url)).arrayBuffer())
|
||||
}
|
||||
}
|
||||
class DJDB extends DB {
|
||||
dj_add(dj_songs: DJSong[]): void {
|
||||
let inputs
|
||||
typeof dj_songs[Symbol.iterator] == "function" ? inputs = dj_songs : inputs = [dj_songs]
|
||||
for (const input of inputs) {
|
||||
if (input instanceof DJSong) {
|
||||
const song = input as DJSong
|
||||
if (!song.id) song.id = this.songs.length
|
||||
|
||||
if (song.in_collection) {
|
||||
const curr_col = song.in_collection.get(this) as Collection
|
||||
curr_col.songs.push(new Ref(RefTo.Songs, song.id))
|
||||
song.artists.forEach((artist) => curr_col.artists.push(new Ref(RefTo.Artists, artist.get(this)!.id!)))
|
||||
song.remix_artists.forEach((artist) => curr_col.artists.push(new Ref(RefTo.Artists, artist.get(this)!.id!)))
|
||||
}
|
||||
|
||||
for (const artist_ref of song.artists) {
|
||||
const curr_artist = artist_ref.get(this) as Artist
|
||||
curr_artist.songs.push(new Ref(RefTo.Songs, song.id))
|
||||
}
|
||||
|
||||
for (const artist_ref of song.remix_artists) {
|
||||
const curr_artist = artist_ref.get(this) as Artist
|
||||
curr_artist.songs.push(new Ref(RefTo.Songs, song.id))
|
||||
}
|
||||
|
||||
this.songs.push(song)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
125
packages/dj/src/euterpe_extend.ts
Normal file
125
packages/dj/src/euterpe_extend.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
import { Euterpe } from "@euterpe.js/euterpe";
|
||||
import { Song } from "@euterpe.js/music-library";
|
||||
import { MusicPlayer } from "@euterpe.js/player";
|
||||
export { DJ }
|
||||
/**
|
||||
* To change volume of a track, use track[i].gain.gain, to change master volume, use euterpe/music players volume.
|
||||
* Make sure your master bpm isnt >= 300, on_beat will break cause there's a setTimeout of 200ms (60s/300BPM)
|
||||
*/
|
||||
class DJ {
|
||||
tracks: Track[] = []
|
||||
/**in ms */
|
||||
beat_duration?: number
|
||||
beat = { current: 0, max: 4, next_bar_in: 4 }
|
||||
on_beat?: (beat: { current: number, max: number, next_bar_in: number }) => void
|
||||
constructor(public player: Euterpe | MusicPlayer, public master_bpm: number | 120) {
|
||||
this.beat_duration = 60 / master_bpm
|
||||
this.#emit_beats()
|
||||
}
|
||||
#emit_beats() {
|
||||
this.beat.current >= 4 ?
|
||||
(this.beat.current++, this.beat.next_bar_in--) :
|
||||
(this.beat.current = 0, this.beat.next_bar_in = this.beat.max)
|
||||
|
||||
if (this.on_beat) this.on_beat(this.beat)
|
||||
//This makes it break if BPM >= 300!!!!
|
||||
new Promise((resolve) => setTimeout(resolve, 200)).then(() => {
|
||||
requestAnimationFrame(this.#emit_beats.bind(this))
|
||||
})
|
||||
}
|
||||
create_track(song?: Song, should_loop = false) {
|
||||
this.tracks.push(new Track(this.player, song, should_loop))
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param i index of track
|
||||
* @param delay how many beats in should the track start? 0 or undefined for asap, 2 = in two beats etc...
|
||||
* @returns Promise<Error | self>
|
||||
*/
|
||||
async try_queue_track(track_i: number, delay: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.tracks[track_i].try_start(delay).then(() => resolve(this), (e) => reject(e))
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Won't start playback, use try_queue_track() or try_start_track()
|
||||
* @returns Promise<Error | self>
|
||||
*/
|
||||
async try_load_song_into_track(track_i: number, song: Song) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.tracks[track_i].change_song(song).then(() => resolve(this), (e) => reject(e))
|
||||
})
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param i index of track
|
||||
* @returns Promise<Error | self>
|
||||
*/
|
||||
async try_start_track(track_i: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.tracks[track_i].try_start().then(() => resolve(this), (e) => reject(e))
|
||||
})
|
||||
}
|
||||
/**
|
||||
* This function will have to restart every track, so for now implementatino pending c:
|
||||
* @param new_master_bpm number in bpm
|
||||
*/
|
||||
set_master_bpm(new_master_bpm: number) {
|
||||
this.master_bpm = new_master_bpm
|
||||
this.beat_duration = 60 / this.master_bpm
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Track {
|
||||
private audio_buffer?: AudioBuffer
|
||||
private buffer_source?: AudioBufferSourceNode
|
||||
gain: GainNode
|
||||
audio_context: AudioContext | BaseAudioContext
|
||||
|
||||
constructor(public player: MusicPlayer | Euterpe, public current_song?: Song, public should_loop?: boolean) {
|
||||
this.audio_context = player.audio_context
|
||||
this.gain = this.audio_context.createGain()
|
||||
if (current_song) this.change_song(current_song).catch((e) => console.error("error during track construction - " + e))
|
||||
}
|
||||
|
||||
async #prepare() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.current_song) reject(new Error("No current song"))
|
||||
fetch(this.current_song!.url)
|
||||
.then(
|
||||
async (file) => {
|
||||
this.audio_buffer = await this.audio_context.decodeAudioData(await file.arrayBuffer())
|
||||
resolve(this)
|
||||
},
|
||||
(reason) => reject(reason))
|
||||
})
|
||||
}
|
||||
#connect() {
|
||||
if (!this.audio_buffer) throw new Error("Somehow buffer not in track even though it analyzed properly. Report this as a bug")
|
||||
this.buffer_source = this.audio_context.createBufferSource()
|
||||
this.buffer_source.buffer = this.audio_buffer!
|
||||
this.buffer_source.connect(this.gain)
|
||||
this.buffer_source.loop = this.should_loop || false
|
||||
this.gain.connect(this.player.gain)
|
||||
}
|
||||
async change_song(new_song: Song) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.current_song = new_song
|
||||
this.#prepare().then(() => {
|
||||
this.#connect()
|
||||
resolve(this)
|
||||
}, (reason) => reject(reason))
|
||||
})
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param delay in seconds
|
||||
*/
|
||||
async try_start(delay?: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.buffer_source) reject(new Error("No buffer source yet, set a song first"))
|
||||
this.buffer_source!.start(this.audio_context.currentTime + (delay || 0))
|
||||
})
|
||||
}
|
||||
}
|
1
packages/dj/src/index.ts
Normal file
1
packages/dj/src/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
import { MusicPlayerBuilder } from "@euterpe.js/player";
|
19
packages/dj/tsconfig.json
Normal file
19
packages/dj/tsconfig.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
10
packages/dj/tsconfig.lib.json
Normal file
10
packages/dj/tsconfig.lib.json
Normal 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"]
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@euterpe.js/euterpe",
|
||||
"version": "1.0.8",
|
||||
"version": "1.0.9",
|
||||
"type": "module",
|
||||
"description": "Fully featured solution for playing music on the web. Support for local library, audio visuals and more!",
|
||||
"main": "./src/index.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@euterpe.js/player",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5",
|
||||
"type": "module",
|
||||
"description": "A simple, safe AudioContext web music player",
|
||||
"main": "./src/index.js",
|
||||
|
|
|
@ -86,10 +86,10 @@ export class MusicPlayer {
|
|||
time = 0
|
||||
#pub_sub = new PubSub
|
||||
constructor(
|
||||
private audio_context: AudioContext,
|
||||
public audio_context: AudioContext,
|
||||
private audio_element: HTMLAudioElement,
|
||||
public track: MediaElementAudioSourceNode,
|
||||
private gain: GainNode,
|
||||
public gain: GainNode,
|
||||
public volume: number,
|
||||
private current_song_path?: string) {
|
||||
this.#volume_cache = volume
|
||||
|
@ -278,7 +278,6 @@ export class MusicPlayer {
|
|||
try_new_song_async(path: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.audio_element.src = this.current_song_path = path
|
||||
this.current_song_duration = this.audio_element.duration
|
||||
//Found out today about this. Such a nice new way to mass remove event listeners!
|
||||
const controller = new AbortController();
|
||||
|
||||
|
@ -297,6 +296,11 @@ export class MusicPlayer {
|
|||
reject(e)
|
||||
}, { signal: controller.signal })
|
||||
|
||||
//once aborted, try to set current_song_duration
|
||||
controller.signal.addEventListener("abort", () => {
|
||||
this.current_song_duration = this.audio_element.duration
|
||||
})
|
||||
|
||||
this.is_playing = false
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
"skipDefaultLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@euterpe.js/dj": [
|
||||
"packages/dj/src/index.ts"
|
||||
],
|
||||
"@euterpe.js/euterpe": [
|
||||
"packages/euterpe/src/index.ts"
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue