UI works!!!!
This commit is contained in:
5 files changed
+352
-69
No files matched your search
+95
-12
@@ -9,12 +9,17 @@
|
||||
<title>D♭</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="theme-color" content="#448aff">
|
||||
<meta name="theme-color" content="#1e1e1e">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
|
||||
<style media="screen">
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
:root {
|
||||
--input-color: rgb(255 255 255 / .52);
|
||||
--input-border: #424242;
|
||||
}
|
||||
html, body {
|
||||
font-family: "Roboto", Helvetica, Arial, sans-serif;
|
||||
line-height; 1.15;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background-color: #1e1e1e;
|
||||
@@ -27,6 +32,45 @@
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
}
|
||||
audio {
|
||||
width: 100%;
|
||||
}
|
||||
button {
|
||||
/* display: inline-block;
|
||||
background: 0; border: 0; font: inherit; color: inherit;
|
||||
border: 1px solid #727272;
|
||||
padding: .25em .5em;
|
||||
cursor: pointer; */
|
||||
background: 0; border: 0; font: inherit;
|
||||
color: var(--input-color);
|
||||
font-size: 87.5%;
|
||||
border: 2px solid var(--input-border);
|
||||
outline: 0;
|
||||
padding: .5em .75em;
|
||||
}
|
||||
select {
|
||||
/* background: 0; border: 0; font: inherit; color: inherit; */
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: 0;
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzU0NTQ1NCI+PHBhdGggZD0iTTcgMTBsNSA1IDUtNXoiPjwvcGF0aD48L3N2Zz4=);
|
||||
background-position: right 50%;
|
||||
background-repeat: no-repeat;
|
||||
outline: 0;
|
||||
border: 2px solid var(--input-border);
|
||||
border-radius: 0;
|
||||
color: var(--input-color);
|
||||
font: inherit;
|
||||
font-size: 87.5%;
|
||||
/* padding: .5em 1.5em .5em 1em; */
|
||||
padding: .5em 1.5em .5em .5em;
|
||||
min-width: 120px;
|
||||
}
|
||||
button:focus,
|
||||
select:focus {
|
||||
border-color: #448aff;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 840px;
|
||||
@@ -35,33 +79,72 @@
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css" integrity="sha384-zB1R0rpPzHqg7Kpt0Aljp8JPLqbXI3bhnPWROx27a9N0Ll6ZP/+DiW/UqRcLbRjq" crossorigin="anonymous">
|
||||
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js" integrity="sha384-y23I5Q6l+B6vatafAwxRu/0oK/79VlbSz7Q9aiSZUvyWYIYsd+qj+o24G5ZU2zJz" crossorigin="anonymous"></script>
|
||||
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js" integrity="sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI" crossorigin="anonymous"
|
||||
onload="renderMathInElement(document.body, {
|
||||
fleqn: true,
|
||||
delimiters: [
|
||||
{ left: '$$', right: '$$', display: true },
|
||||
{ left: '$', right: '$', display: false }
|
||||
]
|
||||
});"></script> -->
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<p>
|
||||
Drop a file anywhere or <button id="choose">choose one from your filesystem.</button>
|
||||
Drag and drop an audio file anywhere or <button id="choose">choose one from your filesystem.</button>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
Filter:
|
||||
<select id="processorSelect">
|
||||
<option value="noop" selected>noop</option>
|
||||
<option value="derivative">derivative</option>
|
||||
<option value="integral">integral</option>
|
||||
<option value="subtract-overlap">subtract-overlap</option>
|
||||
<option value="isolate-overlap" disabled>isolate-overlap</option>
|
||||
<option value="mono">mono</option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<select id="" disabled>
|
||||
<option value="derivative-processor">derivative-processor</option>
|
||||
<option value="integral-processor">integral-processor</option>
|
||||
<option value="subtract-overlap" selected>subtract-overlap</option>
|
||||
</select>
|
||||
<audio controls></audio>
|
||||
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<code>derivative-processor</code>
|
||||
<p>Approximates the first derivative of the audio as a function respect to time.</p>
|
||||
<code>noop</code>
|
||||
<p>Leaves the audio unchanged.</p>
|
||||
</li>
|
||||
<li>
|
||||
<code>integral-processor</code>
|
||||
<p>Approximates the first integral of the audio as a function respect to time. Plus C of zero.</p>
|
||||
<code>derivative</code>
|
||||
<p>
|
||||
Approximates the first derivative of the audio as a function respect to time.
|
||||
Sounds like a <strong>high-pass filter</strong>.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<code>integral</code>
|
||||
<p>
|
||||
Approximates the first integral of the audio as a function respect to time. Plus C of zero. Sounds like a <strong>low-pass filter</strong>.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<code>subtract-overlap</code>
|
||||
<p>Inverts the amplitude of every even-numbered channel and merges all channels to mono. This effectively cancels the sounds the left and right channels share in common. Quick-and-dirty way of removing vocals.</p>
|
||||
</li>
|
||||
<li>
|
||||
<code>isolate-overlap</code>
|
||||
<p>The opposite of subtract-overlap. You think it'd be simple but I can't get it to work :/</p>
|
||||
</li>
|
||||
<li>
|
||||
<code>mono</code>
|
||||
<p>
|
||||
Converts the audio to mono by taking the arithmetic average of the samples.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
+31
-29
@@ -1,12 +1,12 @@
|
||||
//@ts-ignore
|
||||
import workletUrl from './processors.worklet.js'
|
||||
import { audio, useProcessor } from './setup.js'
|
||||
|
||||
document.getElementById('choose').addEventListener('click', ev => {
|
||||
document.getElementById('choose').addEventListener('click', () => {
|
||||
document.getElementById('file').click()
|
||||
})
|
||||
|
||||
document.getElementById('file').addEventListener('input', ev => {
|
||||
fileGuard(ev.target.files)
|
||||
document.querySelector('input').addEventListener('input', function () {
|
||||
//@ts-ignore
|
||||
fileGuard(this.files)
|
||||
})
|
||||
|
||||
window.addEventListener('drop', async ev => {
|
||||
@@ -14,15 +14,22 @@ window.addEventListener('drop', async ev => {
|
||||
fileGuard(ev.dataTransfer.files)
|
||||
})
|
||||
|
||||
// Prevent Chrome from replacing the document when a file's dropped over it
|
||||
document.addEventListener('dragover', ev => {
|
||||
ev.preventDefault()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @param {FileList} files
|
||||
*/
|
||||
function fileGuard(files) {
|
||||
const candidates = [...files]
|
||||
const file = candidates.find(file => file.type.startsWith('audio/'))
|
||||
const file =
|
||||
candidates.find(file => file.type.startsWith('audio/')) ||
|
||||
candidates.find(file => file.type.startsWith('video/'))
|
||||
if (!file) {
|
||||
console.log(candidates.map(f => f.type))
|
||||
alert("Be sure to drop an audio file your browser supports :)")
|
||||
return
|
||||
}
|
||||
@@ -30,44 +37,39 @@ function fileGuard(files) {
|
||||
init(file)
|
||||
}
|
||||
|
||||
const select = document.querySelector('select')
|
||||
select.addEventListener('input', () => {
|
||||
useProcessor(select.value)
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {File} audioFile
|
||||
*/
|
||||
async function init(audioFile) {
|
||||
const audioEl = document.querySelector('audio')
|
||||
const audio = new AudioContext()
|
||||
await audio.audioWorklet.addModule('/dist/' + workletUrl)
|
||||
|
||||
const dFlat = new AudioWorkletNode(audio, 'subtract-overlap', {
|
||||
processorOptions: {
|
||||
maxChannels: audio.destination.maxChannelCount
|
||||
}
|
||||
})
|
||||
const audioSrcNode = audio.createMediaElementSource(audioEl)
|
||||
|
||||
audioSrcNode.connect(dFlat)
|
||||
dFlat.connect(audio.destination)
|
||||
audioEl.src = URL.createObjectURL(audioFile)
|
||||
useProcessor(select.value)
|
||||
|
||||
//@ts-ignore
|
||||
if (navigator['mediaSession'] && typeof MediaMetadata !== 'undefined') {
|
||||
//@ts-ignore
|
||||
navigator['mediaSession'].metadata = new MediaMetadata({
|
||||
title: audioFile.name
|
||||
audio.resume().then(() => audioEl.play())
|
||||
|
||||
document.title = `D♭ • ${audioFile.name}`
|
||||
|
||||
if (navigator.mediaSession && typeof MediaMetadata !== 'undefined') {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: audioFile.name,
|
||||
album: "D♭"
|
||||
});
|
||||
navigator['mediaSession'].setActionHandler('play', () => {
|
||||
navigator.mediaSession.setActionHandler('play', () => {
|
||||
audioEl.play()
|
||||
})
|
||||
navigator['mediaSession'].setActionHandler('pause', () => {
|
||||
navigator.mediaSession.setActionHandler('pause', () => {
|
||||
audioEl.pause()
|
||||
})
|
||||
navigator['mediaSession'].setActionHandler('seekbackward', () => {
|
||||
navigator.mediaSession.setActionHandler('seekbackward', () => {
|
||||
audioEl.currentTime -= 5
|
||||
})
|
||||
navigator['mediaSession'].setActionHandler('seekforward', () => {
|
||||
navigator.mediaSession.setActionHandler('seekforward', () => {
|
||||
audioEl.currentTime += 5
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+104
-28
@@ -2,6 +2,49 @@
|
||||
|
||||
const carry = Symbol()
|
||||
|
||||
class NoopProcessor extends AudioWorkletProcessor {
|
||||
/**
|
||||
* @param {Float32Array[][]} inputs
|
||||
* @param {Float32Array[][]} outputs
|
||||
*/
|
||||
process(inputs, outputs) {
|
||||
const input = inputs[0]
|
||||
const output = outputs[0]
|
||||
|
||||
for (let channel = 0; channel < input.length; channel++) {
|
||||
output[channel].set(input[channel])
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
registerProcessor('noop', NoopProcessor)
|
||||
|
||||
class Mono extends AudioWorkletProcessor {
|
||||
/**
|
||||
* @param {Float32Array[][]} inputs
|
||||
* @param {Float32Array[][]} outputs
|
||||
*/
|
||||
process(inputs, outputs) {
|
||||
// This processor has only one input and one output.
|
||||
const input = inputs[0]
|
||||
const output = outputs[0]
|
||||
|
||||
for (let channel = 0; channel < input.length; channel++) {
|
||||
for (let sample = 0; sample < input[0].length; sample++) {
|
||||
// Mono: (L + R) / 2
|
||||
// More generally: 1/n * (\sum_{i = 0}^n C_i)
|
||||
output[0][sample] += input[channel][sample] * (1 / input.length)
|
||||
}
|
||||
if (channel !== 0) {
|
||||
output[channel].set(output[0])
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
registerProcessor('mono', Mono)
|
||||
|
||||
class DerivativeProcessor extends AudioWorkletProcessor {
|
||||
constructor(options) {
|
||||
super()
|
||||
@@ -25,12 +68,10 @@ class DerivativeProcessor extends AudioWorkletProcessor {
|
||||
this[carry][channel] = input[channel][input.length - 1]
|
||||
}
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('derivative-processor', DerivativeProcessor)
|
||||
|
||||
registerProcessor('derivative', DerivativeProcessor)
|
||||
|
||||
class IntegralProcessor extends AudioWorkletProcessor {
|
||||
|
||||
@@ -56,33 +97,34 @@ class IntegralProcessor extends AudioWorkletProcessor {
|
||||
this[carry][channel] = input[channel][input.length - 1]
|
||||
}
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('integral-processor', IntegralProcessor)
|
||||
registerProcessor('integral', IntegralProcessor)
|
||||
|
||||
|
||||
// class BitCrusher extends AudioWorkletProcessor {
|
||||
// /**
|
||||
// * @param {Float32Array[][]} inputs
|
||||
// * @param {Float32Array[][]} outputs
|
||||
// */
|
||||
// process(inputs, outputs) {
|
||||
// const input = inputs[0]
|
||||
// const output = outputs[0]
|
||||
class BitCrusher extends AudioWorkletProcessor {
|
||||
/**
|
||||
* @param {Float32Array[][]} inputs
|
||||
* @param {Float32Array[][]} outputs
|
||||
*/
|
||||
process(inputs, outputs) {
|
||||
const input = inputs[0]
|
||||
const output = outputs[0]
|
||||
|
||||
// for (let channel = 0; channel < input.length; channel++) {
|
||||
// for (let sample = 0; sample < input[channel].length; sample++) {
|
||||
// output[channel][sample] = input[channel][sample] ** 2
|
||||
// }
|
||||
// }
|
||||
let phase = 1
|
||||
const step = 2 * Math.pow(.5, 8)
|
||||
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// registerProcessor('bit-crusher', BitCrusher)
|
||||
for (let channel = 0; channel < input.length; channel++) {
|
||||
for (let sample = 0; sample < input[channel].length; sample++) {
|
||||
output[channel][sample] = input[channel][sample]
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
registerProcessor('bit-crusher', BitCrusher)
|
||||
|
||||
|
||||
/**
|
||||
@@ -95,11 +137,12 @@ class SubtractOverlap extends AudioWorkletProcessor {
|
||||
* @param {Float32Array[][]} outputs
|
||||
*/
|
||||
process(inputs, outputs) {
|
||||
// This processor has only one input and one output.
|
||||
const input = inputs[0]
|
||||
const output = outputs[0]
|
||||
|
||||
for (let channel = 0; channel < input.length; channel++) {
|
||||
for (let sample = 0; sample < input[0].length; sample++) {
|
||||
for (let sample = 0; sample < input[channel].length; sample++) {
|
||||
output[0][sample] += (channel % 2 === 0 ? 1 : -1) * input[channel][sample]
|
||||
}
|
||||
if (channel !== 0) {
|
||||
@@ -107,8 +150,41 @@ class SubtractOverlap extends AudioWorkletProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('subtract-overlap', SubtractOverlap)
|
||||
|
||||
/**
|
||||
* Like `subtract-overlap`, but isolates the overlap instead of removing it.
|
||||
* Quick-and-dirty way to isolate vocals
|
||||
*/
|
||||
class IsolateOverlap extends AudioWorkletProcessor {
|
||||
/**
|
||||
* @param {Float32Array[][]} inputs
|
||||
* @param {Float32Array[][]} outputs
|
||||
*/
|
||||
process(inputs, outputs) {
|
||||
// This processor has only one input and one output.
|
||||
const input = inputs[0]
|
||||
const output = outputs[0]
|
||||
|
||||
// FIXME: I can't get this filter to work :/
|
||||
|
||||
for (let channel = 0; channel < input.length; channel++) {
|
||||
for (let sample = 0; sample < input[channel].length; sample++) {
|
||||
output[0][sample] += (channel % 2 === 0 ? 1 : -1) * input[channel][sample]
|
||||
}
|
||||
}
|
||||
|
||||
for (let channel = input.length - 1; 0 <= channel; channel--) {
|
||||
for (let sample = 0; sample < input[channel].length; sample++) {
|
||||
output[channel][sample] = input[channel][sample] - output[0][sample]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
registerProcessor('isolate-overlap', IsolateOverlap)
|
||||
@@ -0,0 +1,57 @@
|
||||
//@ts-ignore
|
||||
import workletUrl from './processors.worklet.js'
|
||||
|
||||
const audioEl = document.querySelector('audio')
|
||||
const audio = new AudioContext()
|
||||
const audioSrcNode = audio.createMediaElementSource(audioEl)
|
||||
|
||||
// Add the modules
|
||||
audio.audioWorklet.addModule('/dist/' + workletUrl)
|
||||
|
||||
let dFlat = null
|
||||
// let dFlat = null new AudioWorkletNode(audio, 'subtract-overlap', {
|
||||
// processorOptions: {
|
||||
// maxChannels: audio.destination.maxChannelCount
|
||||
// }
|
||||
// })
|
||||
|
||||
// audioSrcNode.connect(dFlat)
|
||||
// dFlat.connect(audio.destination)
|
||||
|
||||
/**
|
||||
* Swaps out the processor
|
||||
* @param {string} name The name of the processor to use
|
||||
*/
|
||||
export function useProcessor(name) {
|
||||
if (dFlat) {
|
||||
dFlat.disconnect()
|
||||
audioSrcNode.disconnect()
|
||||
}
|
||||
|
||||
dFlat = new AudioWorkletNode(audio, name, {
|
||||
processorOptions: {
|
||||
maxChannels: audio.destination.maxChannelCount
|
||||
}
|
||||
})
|
||||
audioSrcNode.connect(dFlat)
|
||||
dFlat.connect(audio.destination)
|
||||
}
|
||||
|
||||
export { audio }
|
||||
|
||||
|
||||
// const audioEl = document.querySelector('audio')
|
||||
// const audio = new AudioContext()
|
||||
// await audio.audioWorklet.addModule('/dist/' + workletUrl)
|
||||
|
||||
// const dFlat = new AudioWorkletNode(audio, 'subtract-overlap', {
|
||||
// // const dFlat = new AudioWorkletNode(audio, 'bit-crusher', {
|
||||
// processorOptions: {
|
||||
// maxChannels: audio.destination.maxChannelCount
|
||||
// }
|
||||
// })
|
||||
// const audioSrcNode = audio.createMediaElementSource(audioEl)
|
||||
|
||||
// audioSrcNode.connect(dFlat)
|
||||
// dFlat.connect(audio.destination)
|
||||
// audioEl.src = URL.createObjectURL(audioFile)
|
||||
Vendored
+65
@@ -14,3 +14,68 @@ function registerProcessor(
|
||||
name: string,
|
||||
processorCtor: AudioWorkletProcessorConstructor
|
||||
): void
|
||||
|
||||
// MediaSession definitions
|
||||
interface Navigator {
|
||||
readonly mediaSession?: MediaSession
|
||||
}
|
||||
|
||||
interface Window {
|
||||
MediaSession?: MediaSession
|
||||
}
|
||||
|
||||
type MediaSessionPlaybackState = 'none' | 'paused' | 'playing'
|
||||
|
||||
type MediaSessionAction =
|
||||
| 'play'
|
||||
| 'pause'
|
||||
| 'seekbackward'
|
||||
| 'seekforward'
|
||||
| 'previoustrack'
|
||||
| 'nexttrack'
|
||||
|
||||
interface MediaSession {
|
||||
// Current media session playback state.
|
||||
playbackState: MediaSessionPlaybackState
|
||||
// Current media session meta data.
|
||||
metadata: MediaMetadata | null
|
||||
|
||||
// Set/Unset actions handlers.
|
||||
setActionHandler(
|
||||
action: MediaSessionAction,
|
||||
listener: (() => void) | null
|
||||
): void
|
||||
}
|
||||
|
||||
interface MediaImage {
|
||||
// URL from which the user agent can fetch the image’s data.
|
||||
src: string
|
||||
// Specify the MediaImage object’s sizes. It follows the spec of sizes attribute in HTML link element.
|
||||
sizes?: string
|
||||
// A hint as to the media type of the image.
|
||||
type?: string
|
||||
}
|
||||
|
||||
interface MediaMetadataInit {
|
||||
// Media's title.
|
||||
title?: string
|
||||
// Media's artist.
|
||||
artist?: string
|
||||
// Media's album.
|
||||
album?: string
|
||||
// Media's artwork.
|
||||
artwork?: MediaImage[]
|
||||
}
|
||||
|
||||
declare class MediaMetadata {
|
||||
public title: string
|
||||
// Media's artist.
|
||||
public artist: string
|
||||
// Media's album.
|
||||
public album: string
|
||||
// Media's artwork.
|
||||
public artwork: MediaImage[]
|
||||
|
||||
constructor(init?: MediaMetadataInit)
|
||||
// Media's title.
|
||||
}
|
||||
Reference in new issue
Block a user