UI works!!!!

This commit is contained in:
MindfulMinun committed 2020-05-20 23:37:03 -05:00
1 parent 2d6a517e93
commit 73f6f99ec3
5 files changed
+352 -69

No files matched your search

+95 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+57
View File
@@ -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)
+65
View File
@@ -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.
}