2023-08-10 12:02:23 +00:00
|
|
|
import { DB, from_json } from "@euterpe.js/music-library"
|
2023-07-19 16:16:50 +00:00
|
|
|
import { generate_db } from "./generate_db"
|
|
|
|
import { AudioVisualBuilder, SmoothingAlgorythm, ShapeType, WaveformOrientation, WaveformShape } from "@euterpe.js/visualizer"
|
|
|
|
|
2023-08-10 12:02:23 +00:00
|
|
|
let result: AnalyzeReturn | undefined;
|
|
|
|
|
|
|
|
let db = generate_db()
|
|
|
|
//Create all audio nodes
|
|
|
|
const audioEl = document.querySelector("#audio") as HTMLAudioElement
|
|
|
|
const audioContext = new AudioContext()
|
|
|
|
const track = audioContext.createMediaElementSource(audioEl)
|
|
|
|
const gain = audioContext.createGain()
|
|
|
|
gain.gain.value = 0
|
|
|
|
const audioContextAnalyser = audioContext.createAnalyser()
|
|
|
|
audioContextAnalyser.fftSize = 32
|
|
|
|
audioContextAnalyser.smoothingTimeConstant = 0
|
|
|
|
const analyserBufferLength = audioContextAnalyser.frequencyBinCount
|
|
|
|
const FFTDataArray = new Float32Array(analyserBufferLength)
|
|
|
|
//Connect all audio Nodes
|
|
|
|
track.connect(audioContextAnalyser).connect(gain).connect(audioContext.destination)
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById("analyze")!.addEventListener("click", async (ev) => {
|
|
|
|
audioContext.resume()
|
|
|
|
result = await analyze()
|
|
|
|
download(JSON.stringify(result.db), "db.json", "text/plain")
|
|
|
|
})
|
|
|
|
|
|
|
|
document.getElementById("create-svg")!.addEventListener("click", (ev) => {
|
|
|
|
audioContext.resume()
|
|
|
|
svg()
|
2023-07-19 16:16:50 +00:00
|
|
|
})
|
2023-08-10 12:02:23 +00:00
|
|
|
|
|
|
|
document.getElementById("upload")!.addEventListener("change", (ev) => {
|
|
|
|
audioContext.resume()
|
|
|
|
const fileReader = new FileReader()
|
|
|
|
fileReader.readAsText(ev.target.files[0])
|
|
|
|
fileReader.onload = event => {
|
|
|
|
let str = JSON.parse(event.target.result)
|
|
|
|
let new_db = from_json(str)
|
|
|
|
//-infinity get stringified to null, undo that
|
|
|
|
for (const song of new_db.songs) {
|
|
|
|
if (song.fft_data) {
|
|
|
|
for (let i = 0; i < song.fft_data.length; i++) {
|
|
|
|
if (song.fft_data[i] === null || song.fft_data[i] === undefined) song.fft_data[i] = -Infinity
|
|
|
|
}
|
|
|
|
}
|
2023-07-19 16:16:50 +00:00
|
|
|
}
|
2023-08-10 12:02:23 +00:00
|
|
|
result = { db: new_db, analyzer_node: audioContextAnalyser }
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
async function svg() {
|
|
|
|
if (!result) {
|
|
|
|
alert("not analyzed yet!")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
console.log("Creating svgs...")
|
|
|
|
const canvas_wrapper = document.querySelector(".canvas-wrapper") as HTMLElement
|
|
|
|
|
|
|
|
const waveform_canvas = document.querySelector("#waveform-canvas")?.cloneNode() as SVGSVGElement
|
|
|
|
|
|
|
|
canvas_wrapper.childNodes.forEach((c) => c.remove())
|
|
|
|
canvas_wrapper.appendChild(waveform_canvas)
|
|
|
|
|
|
|
|
for (const song of result.db.songs) {
|
|
|
|
console.log("creating waveform for -> " + song.name)
|
|
|
|
const curr_waveform_canvas = waveform_canvas.cloneNode() as SVGSVGElement
|
|
|
|
waveform_canvas.parentElement?.append(curr_waveform_canvas)
|
|
|
|
const waveform_visual_builder = new AudioVisualBuilder(result.analyzer_node, curr_waveform_canvas)
|
|
|
|
.set_fft_data_tresholds({ point_count_i: 100, fft_multiplier_i: .9, fft_offset_i: -65 })
|
|
|
|
.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(), 500))
|
|
|
|
// @ts-ignore
|
|
|
|
song.metadata[0] = curr_waveform_canvas.children[0].getAttribute("d")
|
|
|
|
song.fft_data = []
|
|
|
|
}
|
|
|
|
waveform_canvas.remove()
|
|
|
|
console.dir(result.db, { depth: null })
|
|
|
|
download(JSON.stringify(result.db), "db.json", "text/plain")
|
|
|
|
|
2023-07-19 16:16:50 +00:00
|
|
|
}
|
|
|
|
async function analyze(): Promise<AnalyzeReturn> {
|
|
|
|
console.clear()
|
|
|
|
const audioEl = document.querySelector("#audio") as HTMLAudioElement
|
|
|
|
console.log("analysing...")
|
|
|
|
const samplingRate = 100
|
|
|
|
|
2023-08-03 21:55:55 +00:00
|
|
|
// db.songs.splice(0, 10)
|
|
|
|
// db.songs.splice(2)
|
2023-07-20 15:28:22 +00:00
|
|
|
console.log(db)
|
2023-07-19 16:16:50 +00:00
|
|
|
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
|
2023-08-10 12:02:23 +00:00
|
|
|
console.log(song.fft_data)
|
2023-07-19 16:16:50 +00:00
|
|
|
}
|
|
|
|
console.log("Analyzation finished!")
|
|
|
|
const result: AnalyzeReturn = { analyzer_node: audioContextAnalyser, db: db }
|
|
|
|
return result
|
|
|
|
}
|
2023-07-20 15:28:22 +00:00
|
|
|
function download(content: BlobPart, fileName: string, contentType: string) {
|
2023-08-03 21:55:55 +00:00
|
|
|
var a = document.querySelector("#download") as HTMLAnchorElement;
|
2023-07-20 15:28:22 +00:00
|
|
|
var file = new Blob([content], { type: contentType });
|
|
|
|
a.href = URL.createObjectURL(file);
|
|
|
|
a.download = fileName;
|
2023-08-03 21:55:55 +00:00
|
|
|
// a.click();
|
2023-07-20 15:28:22 +00:00
|
|
|
}
|
2023-07-19 16:16:50 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
2023-08-10 12:02:23 +00:00
|
|
|
}
|