added preprocessor, changed more db stuff
This commit is contained in:
parent
1878d8db85
commit
920ee5152a
14 changed files with 3144 additions and 1415 deletions
4157
package-lock.json
generated
4157
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -58,9 +58,9 @@ function ref_from_json(ref: any): Ref {
|
|||
|
||||
interface SongConstructor {
|
||||
name: string
|
||||
artists: Ref[]
|
||||
artists?: Ref[]
|
||||
url: URL
|
||||
duration: number
|
||||
duration?: number
|
||||
publish_date?: Date
|
||||
remix_artists?: Ref[]
|
||||
in_collection?: Ref
|
||||
|
@ -69,13 +69,13 @@ interface SongConstructor {
|
|||
key?: string
|
||||
fft_data?: number[]
|
||||
id?: ID,
|
||||
metadata: any
|
||||
metadata?: Map<string, any>
|
||||
}
|
||||
class Song {
|
||||
name: string
|
||||
artists: Ref[]
|
||||
url: URL
|
||||
duration: number
|
||||
duration?: number
|
||||
remix_artists: Ref[]
|
||||
publish_date?: Date
|
||||
in_collection?: Ref
|
||||
|
@ -83,7 +83,7 @@ class Song {
|
|||
bpm?: number
|
||||
key?: string
|
||||
fft_data?: number[]
|
||||
metadata: any
|
||||
metadata: Map<string, any>
|
||||
/**
|
||||
* The ID is always there, don't worry :)
|
||||
*/
|
||||
|
@ -101,7 +101,7 @@ class Song {
|
|||
this.key = data.key
|
||||
this.fft_data = data.fft_data
|
||||
this.id = data.id
|
||||
this.metadata = data.metadata
|
||||
this.metadata = data.metadata || new Map<string, any>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,15 +112,15 @@ interface ArtistConstructor {
|
|||
collections?: Ref[]
|
||||
links?: [Platforms, URL][]
|
||||
id?: ID
|
||||
metadata: any
|
||||
metadata?: Map<string, any>
|
||||
}
|
||||
class Artist {
|
||||
name = ""
|
||||
pfp?: URL | string
|
||||
pfp?: URL
|
||||
songs: Ref[]
|
||||
collections: Ref[]
|
||||
links?: [Platforms, URL][]
|
||||
metadata: any
|
||||
metadata: Map<string, any>
|
||||
/**
|
||||
* The ID is always there, don't worry :)
|
||||
*/
|
||||
|
@ -132,17 +132,17 @@ class Artist {
|
|||
this.collections = data.collections || []
|
||||
this.links = data.links
|
||||
this.id = data.id
|
||||
this.metadata = data.metadata
|
||||
this.metadata = data.metadata || new Map<string, any>
|
||||
}
|
||||
}
|
||||
interface CollectionConstructor {
|
||||
artists: Ref[]
|
||||
songs: Ref[]
|
||||
cover: URL
|
||||
duration: number
|
||||
cover?: URL
|
||||
duration?: number
|
||||
publish_date?: Date
|
||||
id?: ID
|
||||
metadata: any
|
||||
metadata?: Map<string, any>
|
||||
name?: string
|
||||
type?: CollectionType
|
||||
|
||||
|
@ -152,10 +152,10 @@ class Collection {
|
|||
type?: CollectionType
|
||||
artists: Ref[]
|
||||
songs: Ref[]
|
||||
cover: URL
|
||||
duration: number
|
||||
cover?: URL
|
||||
duration?: number
|
||||
publish_date?: Date
|
||||
metadata: any
|
||||
metadata: Map<string, any>
|
||||
/**
|
||||
* The ID is always there, don't worry :)
|
||||
*/
|
||||
|
@ -168,7 +168,7 @@ class Collection {
|
|||
this.publish_date = data.publish_date
|
||||
this.id = data.id
|
||||
this.name = data.name
|
||||
this.metadata = data.metadata
|
||||
this.metadata = data.metadata ? data.metadata : new Map<string, any>
|
||||
}
|
||||
}
|
||||
class DB {
|
||||
|
|
29
packages/preprocessor/.gitignore
vendored
Normal file
29
packages/preprocessor/.gitignore
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
# 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/*
|
||||
src/song_list.ts
|
||||
src/db.js
|
4
packages/preprocessor/crawler.mjs
Normal file
4
packages/preprocessor/crawler.mjs
Normal file
|
@ -0,0 +1,4 @@
|
|||
import filehound from "filehound"
|
||||
import fs from "fs"
|
||||
const songs = filehound.create().path("./public/samples").ext(["ogg", "mp3"]).findSync()
|
||||
fs.writeFile('./src/songs_list.ts', `export const songs = ` + JSON.stringify(songs), 'utf8', () => { 1 + 1 })
|
42
packages/preprocessor/index.html
Normal file
42
packages/preprocessor/index.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FFT generator</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<audio id="audio"></audio>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts">
|
||||
</script>
|
||||
<button id="button">Analyze!</button>
|
||||
<svg id="waveform-canvas" viewBox="0 0 500 500" preserveAspectRatio="none"></svg>
|
||||
<style>
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 1.2rem 2rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-top: 5rem;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
|
||||
</html>
|
21
packages/preprocessor/package.json
Normal file
21
packages/preprocessor/package.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "preprocessor",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"crawl": "node crawler.mjs",
|
||||
"process": "node crawler.mjs && vite",
|
||||
"serve": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"filehound": "^1.17.6",
|
||||
"@euterpe.js/music-library": "*"
|
||||
}
|
||||
}
|
70
packages/preprocessor/project.json
Normal file
70
packages/preprocessor/project.json
Normal file
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"name": "preprocessor",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"sourceRoot": "packages/preprocessor/src",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/vite:build",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"outputPath": "dist/packages/preprocessor"
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"mode": "development"
|
||||
},
|
||||
"production": {
|
||||
"mode": "production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@nx/vite:dev-server",
|
||||
"defaultConfiguration": "development",
|
||||
"options": {
|
||||
"buildTarget": "preprocessor:build"
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"buildTarget": "preprocessor:build:development",
|
||||
"hmr": true
|
||||
},
|
||||
"production": {
|
||||
"buildTarget": "preprocessor:build:production",
|
||||
"hmr": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"executor": "@nx/vite:preview-server",
|
||||
"defaultConfiguration": "development",
|
||||
"options": {
|
||||
"buildTarget": "preprocessor:build"
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"buildTarget": "preprocessor:build:development"
|
||||
},
|
||||
"production": {
|
||||
"buildTarget": "preprocessor:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": [
|
||||
"{options.outputFile}"
|
||||
],
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"packages/preprocessor/**/*.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
packages/preprocessor/public/vite.svg
Normal file
1
packages/preprocessor/public/vite.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
54
packages/preprocessor/src/generate_db.ts
Normal file
54
packages/preprocessor/src/generate_db.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { Collection, Ref, RefTo, Song, DB, } from "@euterpe.js/music-library"
|
||||
import { songs } from "./songs_list"
|
||||
|
||||
export function generate_db() {
|
||||
console.log(songs)
|
||||
//construct db
|
||||
let db = new DB
|
||||
let collections: string[] = new Array()
|
||||
let new_songs = []
|
||||
|
||||
//create collections by folder names
|
||||
for (let i = 0; i < songs.length; i++) {
|
||||
const song = songs[i]
|
||||
const last_i = song.lastIndexOf("\\")
|
||||
const collection_name = song.slice(song.slice(0, last_i).lastIndexOf("\\") + 1, last_i)
|
||||
/*
|
||||
const foreforelast_i = song.slice(0, forelast_i - 1)
|
||||
const foreforeforelast_i = song.slice(0, foreforelast_i - 1).lastIndexOf("\\")
|
||||
*/
|
||||
if (!collections.includes(collection_name)) {
|
||||
console.log(`creating collection ${collection_name}`)
|
||||
db.add([new Collection({
|
||||
name: collection_name,
|
||||
songs: [],
|
||||
artists: [],
|
||||
})])
|
||||
collections.push(collection_name)
|
||||
}
|
||||
let col = db.collections.find(col => col.name == collection_name)!
|
||||
let col_id = col.id
|
||||
new_songs.push({ song: song, collection_id: col_id })
|
||||
}
|
||||
|
||||
//create songs
|
||||
for (let i = 0; i < new_songs.length; i++) {
|
||||
let song = new_songs[i]
|
||||
const last_i = song.song.lastIndexOf("\\")
|
||||
|
||||
const name = song.song.slice(last_i + 1)
|
||||
const song_url = song.song.replace("\\\\", "/").slice(7)
|
||||
|
||||
const db_song = new Song({
|
||||
name: name.slice(0, name.lastIndexOf(".")),
|
||||
artists: [],
|
||||
url: new URL("http://localhost:4200/" + song_url),
|
||||
duration: 0,
|
||||
remix_artists: [],
|
||||
in_collection: new Ref(RefTo.Collections, song.collection_id!)
|
||||
})
|
||||
db.add([db_song])
|
||||
}
|
||||
console.log(db)
|
||||
return db
|
||||
}
|
83
packages/preprocessor/src/main.ts
Normal file
83
packages/preprocessor/src/main.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { DB } from "@euterpe.js/music-library"
|
||||
import { generate_db } from "./generate_db"
|
||||
import { AudioVisualBuilder, SmoothingAlgorythm, ShapeType, WaveformOrientation, WaveformShape } from "@euterpe.js/visualizer"
|
||||
|
||||
document.getElementById("button")!.addEventListener("click", (ev) => {
|
||||
start()
|
||||
})
|
||||
export async function start() {
|
||||
generate_db()
|
||||
analyze().then(async (result) => {
|
||||
console.log("Creating svgs...")
|
||||
const waveform_canvas = document.querySelector("#waveform-canvas") as SVGSVGElement
|
||||
for (const song of result.db.songs) {
|
||||
const waveform_visual_builder = new AudioVisualBuilder(result.analyzer_node, waveform_canvas)
|
||||
.set_fft_data_tresholds({ point_count_i: 100, fft_multiplier_i: 1, fft_offset_i: -80 })
|
||||
.set_fft_time_smoothing(0.8)
|
||||
.set_smoothing_algorythm(SmoothingAlgorythm.CatmullRom)
|
||||
const waveform_visual = waveform_visual_builder.build(ShapeType.Waveform, true, { fft_data: new Float32Array(new Float64Array(song.fft_data!)), orientation: WaveformOrientation.Horizontal, shape_type: WaveformShape.LineLike })
|
||||
waveform_visual.draw_once()
|
||||
await new Promise<void>((done) => setTimeout(() => done(), 300))
|
||||
song.metadata.set("waveform-svg", waveform_canvas.innerHTML)
|
||||
}
|
||||
console.log(result.db)
|
||||
})
|
||||
}
|
||||
async function analyze(): Promise<AnalyzeReturn> {
|
||||
console.clear()
|
||||
const audioEl = document.querySelector("#audio") as HTMLAudioElement
|
||||
console.log("analysing...")
|
||||
const samplingRate = 100
|
||||
//Create all audio nodes
|
||||
const audioContext = new AudioContext()
|
||||
const track = audioContext.createMediaElementSource(audioEl)
|
||||
const gain = audioContext.createGain()
|
||||
gain.gain.value = 0
|
||||
const audioContextAnalyser = audioContext.createAnalyser()
|
||||
audioContextAnalyser.fftSize = 64
|
||||
audioContextAnalyser.smoothingTimeConstant = 0
|
||||
const analyserBufferLength = audioContextAnalyser.frequencyBinCount
|
||||
const FFTDataArray = new Float32Array(analyserBufferLength)
|
||||
//Connect all audio Nodes
|
||||
track.connect(audioContextAnalyser).connect(gain).connect(audioContext.destination)
|
||||
|
||||
let db = generate_db()
|
||||
for (const song of db.songs) {
|
||||
// const song = db.songs[db.songs.length - 1]
|
||||
console.log(`Analyzing ${song.name}, ${db.songs.indexOf(song) + 1}/${db.songs.length}`)
|
||||
//if not loaded yet keep trying
|
||||
audioEl.src = song.url.href
|
||||
await awaitLoad(audioEl)
|
||||
song.duration = audioEl.duration
|
||||
let currentFFTData = []
|
||||
for (let curSecond = 0; curSecond < song.duration; curSecond += song.duration / samplingRate) {
|
||||
console.log("working...")
|
||||
audioEl.currentTime = curSecond
|
||||
await audioEl.play()
|
||||
await new Promise<void>((done) => setTimeout(() => done(), 100))
|
||||
audioContextAnalyser.getFloatFrequencyData(FFTDataArray)
|
||||
let volume = 0
|
||||
FFTDataArray.forEach((element) => {
|
||||
volume += element
|
||||
})
|
||||
currentFFTData.push(Math.round((volume / FFTDataArray.length) * 100) / 100)
|
||||
}
|
||||
song.fft_data = currentFFTData
|
||||
}
|
||||
console.log("Analyzation finished!")
|
||||
const result: AnalyzeReturn = { analyzer_node: audioContextAnalyser, db: db }
|
||||
return result
|
||||
}
|
||||
type AnalyzeReturn = {
|
||||
analyzer_node: AnalyserNode,
|
||||
db: DB
|
||||
}
|
||||
function awaitLoad(audioEl: HTMLAudioElement) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
audioEl.addEventListener("loadeddata", function () {
|
||||
if (audioEl.readyState >= 4) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
1
packages/preprocessor/src/songs_list.ts
Normal file
1
packages/preprocessor/src/songs_list.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const songs = ["public\\samples\\bass\\H2 arp basses 01.ogg","public\\samples\\bass\\H2 arp basses 02.ogg","public\\samples\\bass\\H2 arp basses 03.ogg","public\\samples\\drums\\H2 909 Crash 03.ogg","public\\samples\\drums\\H2 buildsnares cut 01.ogg","public\\samples\\drums\\H2 buildsnares cut 04.ogg","public\\samples\\drums\\H2 buildsnares cut 06.ogg","public\\samples\\drums\\H2 Claps 07.ogg","public\\samples\\drums\\H2 Claps 21.ogg","public\\samples\\drums\\H2 Claps Tails 11.ogg","public\\samples\\drums\\H2 Claps Tails 15.ogg","public\\samples\\drums\\H2 clubsnares 08.ogg","public\\samples\\drums\\H2 clubsnares 15.ogg","public\\samples\\drums\\H2 clubsnares 20.ogg","public\\samples\\drums\\H2 Trancekick 13.ogg","public\\samples\\drums\\H2 Trancekick 24.ogg","public\\samples\\drums\\H2 Trancekick 45.ogg","public\\samples\\loops\\H2 loops 01.ogg","public\\samples\\loops\\H2 loops 02.ogg"]
|
1
packages/preprocessor/src/vite-env.d.ts
vendored
Normal file
1
packages/preprocessor/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
29
packages/preprocessor/tsconfig.json
Normal file
29
packages/preprocessor/tsconfig.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"noEmit": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"skipLibCheck": true,
|
||||
"types": [
|
||||
"vite/client"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
33
packages/preprocessor/vite.config.ts
Normal file
33
packages/preprocessor/vite.config.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/// <reference types="vitest" />
|
||||
import { defineConfig } from "vite"
|
||||
|
||||
import viteTsConfigPaths from "vite-tsconfig-paths"
|
||||
|
||||
export default defineConfig({
|
||||
cacheDir: "../../node_modules/.vite/preprocessor",
|
||||
|
||||
server: {
|
||||
port: 4200,
|
||||
host: "localhost"
|
||||
},
|
||||
|
||||
preview: {
|
||||
port: 4300,
|
||||
host: "localhost"
|
||||
},
|
||||
|
||||
plugins: [
|
||||
viteTsConfigPaths({
|
||||
root: "."
|
||||
})
|
||||
]
|
||||
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [
|
||||
// viteTsConfigPaths({
|
||||
// root: '../../',
|
||||
// }),
|
||||
// ],
|
||||
// },
|
||||
})
|
Loading…
Reference in a new issue