From 6a83c9b84f49d541f39726712b8a0331590555d2 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 12 Mar 2018 01:42:41 +0100 Subject: basic linear spectral manipulation! --- client/lib/spectrum.js | 184 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 171 insertions(+), 13 deletions(-) (limited to 'client/lib/spectrum.js') diff --git a/client/lib/spectrum.js b/client/lib/spectrum.js index 2b30792..1dd9619 100644 --- a/client/lib/spectrum.js +++ b/client/lib/spectrum.js @@ -1,13 +1,12 @@ import Tone from 'tone' -import output from './output' +import { shuffle, quantize, mod } from './util' -const signalWindows = require('signal-windows').windows -const FFTJS = require('fft.js') +import { windows as signalWindows } from 'signal-windows' +import FFTJS from 'fft.js' -const fft_size = 2 << 10 +const fft_size = 1024 const fft_overlap = fft_size / 4 -const half_fft_size = fft_size / 2 const fft = new FFTJS(fft_size) @@ -49,15 +48,15 @@ function fromSpectrum(spec){ const audioBuffer = Tone.context.createBuffer(1, pcm_length, sr) const pcm = audioBuffer.getChannelData(0); - let i, j, u, v, _r, _i, col + let i, j, u, col for (i = 0; i < spec_len; i++) { col = data[i] + // for (j = fft_size; j < fft_size << 1; j++) { + // col[j] = 0 + // } fft.inverseTransform(out, col) u = i * (fft_overlap) - // for (j = fft_size * 1/4; j < fft_size * 3/4; j++) { - // pcm[u+j] = out[j*2] / ham[j] || 0 - // } for (j = 0; j < fft_size; j++) { pcm[u+j] += out[j*2] * ham[j] || 0 } @@ -78,12 +77,171 @@ function fadeInOut(pcm, fade_size){ pcm[pcm_length - i] *= fade } } -function fadeOut(pcm){ - +function rotatePhase(spec, theta){ + let { data, fft_size } = spec + let i, j, col, len = data.length + for (i = 0; i < len; i++) { + col = data[i] + for (j = 0; j < fft_size; j++) { + col[j*2+1] += theta + } + } + return spec +} + +function linearBins(spec, n){ + n = n || 1 + + let bins = [], i, q_i + for (q_i = 0; q_i < n; q_i++) { + bins[q_i] = [] + } + const step = Math.floor(spec.fft_size / n) + const len_quantize_n = quantize(spec.fft_size, n) + for (i = 0; i < len_quantize_n; i++) { + q_i = Math.floor(i/step) + bins[q_i] = bins[q_i] || [] + bins[q_i].push(i) + } + // leftover bins get put at end + for (; i < spec.fft_size; i++) { + bins[q_i].push(i) + } + return bins +} +function logarithmicBins(spec){ + let bins = [], i, j, q_i + let binCount = Math.log2(spec.fft_size) - 1 + for (i = 0, q_i = 0, j = 0; i < binCount; i++) { + j += 1 << i + bins[i] = [] + for (; q_i < j; q_i++) { + bins[i].push(q_i) + } + } + return bins +} +function concatBins(bins){ + return bins.reduce((acc, cv) => acc.concat(cv), []) +} +function reverseBins(bins){ + return bins.map( bin => bin.reverse() ) +} +function minBins(bins){ + return bins.map( bin => { + const b = bin[0] + return bin.map(() => b) + }) +} +function maxBins(bins){ + return bins.map( bin => { + const b = bin[bin.length-1] + return bin.map(() => b) + }) +} +function rotateSpectrum(spec, n){ + const { fft_size } = spec + if (n && n < 1) { + n -= 0.5 + n *= fft_size + } + n = Math.floor(n) + let order = new Array(fft_size), i + for (i = 0; i < fft_size; i++) { + order[i] = mod(i + n, fft_size/2) + } + return reorderBins(spec, order) +} +function cloneSpectrum(spec){ + const { + data, + fft_size, + sr, fft_overlap + } = spec + const spec_len = data.length + + let new_data = new Array(spec_len) + let i + for (i = 0; i < spec_len; i++) { + new_data[i] = data[i].concat() + new_data[i][2] = 0 + } + + return { + data: new_data, + fft_size, + sr, fft_overlap, + } +} +function reverseSpectrum(spec){ + let new_spec = cloneSpectrum(spec) + new_spec.data = new_spec.data.reverse() + return new_spec +} +function shuffleSpectrum(spec){ + const { fft_size } = spec + let order = new Array(fft_size), i + for (i = 0; i < fft_size; i++) { + order[i] = i + } + shuffle(order) + return reorderBins(spec, order) +} +function invertSpectrum(spec){ + const { fft_size } = spec + let order = new Array(fft_size), i + for (i = 0; i < fft_size; i++) { + order[i] = fft_size - i - 1 + } + return reorderBins(spec, order) +} +function reorderBins(spec, order){ + let new_spec = cloneSpectrum(spec) + const { + data, + sr, + fft_size, + fft_overlap, + } = spec + const spec_len = data.length + const { data: new_data } = new_spec + + let i, j, col, new_col + for (j = order.length; j < fft_size; j++) { + order[j] = j + } + + for (i = 0; i < spec_len; i++) { + col = data[i] + new_col = new_data[i] = data[i].concat() + col[0] = 0 + // col[2] = 0 + // col[4] = 0 + for (j = 0; j < fft_size/2; j++) { + new_col[j*2] = col[order[j]*2] + new_col[j*2+1] = col[order[j]*2+1] + } + for (; j < fft_size; j++) { + new_col[j*2] = 0 + new_col[j*2+1] = 0 + } + } + + return { + data: new_data, + sr, fft_size, fft_overlap, + } } export default { - toSpectrum, - fromSpectrum + toSpectrum, fromSpectrum, + fadeInOut, + cloneSpectrum, + reverseSpectrum, shuffleSpectrum, invertSpectrum, rotateSpectrum, + reorderBins, + linearBins, logarithmicBins, + concatBins, + reverseBins, minBins, maxBins, + rotatePhase, } -- cgit v1.2.3-70-g09d2