import Tone from 'tone' import { shuffle, quantize, mod } from '../util' import { windows as signalWindows } from 'signal-windows' import FFTJS from 'fft.js' const fft_size = 512 const fft_overlap = fft_size / 4 const fft = new FFTJS(fft_size) function toSpectrum(pcm, sr){ sr = sr || 44100 const ham = signalWindows.construct('ham', fft_size) const pcm_in = new Array(fft_size) const pcm_length = pcm.length const pcm_q_length = Math.ceil(pcm_length / fft_size) * fft_size let i, j, fft_out, data = []; for (i = -fft_size; i < pcm_q_length; i += fft_overlap) { for (j = 0; j < fft_size; j++) { pcm_in[j] = pcm[i+j] * ham[j] || 0 } fft_out = fft.createComplexArray() fft.realTransform(fft_out, pcm_in) fft.completeSpectrum(fft_out) data.push(fft_out) } return { data, sr, fft_size, fft_overlap, } } function fromSpectrum(spec){ const data = spec.data const sr = spec.sr const fft_size = spec.fft_size const fft_overlap = spec.fft_overlap const spec_len = data.length const ham = signalWindows.construct('ham', fft_size) const out = fft.createComplexArray() const pcm_length = fft_overlap * spec_len const audioBuffer = Tone.context.createBuffer(1, pcm_length, sr) const pcm = audioBuffer.getChannelData(0); 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 // } // if (i == 0) console.log(col) fft.inverseTransform(out, col) u = i * (fft_overlap) for (j = 0; j < fft_size; j++) { pcm[u+j] += out[j*2] * ham[j] || 0 } } fadeInOut(pcm, fft_size) // console.log(pcm) return audioBuffer } function fromImageData(imageData, sr, _r, _i) { const pixels = imageData.data const w = imageData.width const h = imageData.height let data = new Array(w) let x, y, u, v, v2 for (y = 0; y < h; y++) { let col = data[y] = new Float32Array(h * 4) for (x = 0; x < w; x++) { u = (x * (w) + y) * 4 v = x * 2 col[v] = (pixels[u] / 255 - 0.5) * _r col[v+1] = (pixels[u+1] / 255 - 0.5) * _i v2 = (h-y + h) * 2 col[v2] = col[v] col[v2+1] = 0 // col[v+1] } col[h*2] = col[h*2+1] = col[h*2-1] = col[h*2-2] = 0 } const spec = { data, sr, fft_size, fft_overlap } return spec } function binToHz(spec, i){ return (i / spec.fft_size) * spec.sr } function fadeInOut(pcm, fade_size){ const pcm_length = pcm.length let fade = 0, i for (i = 0; i < fade_size; i++) { fade = i / (fade_size) fade *= fade pcm[i] *= fade pcm[pcm_length - i] *= fade } } 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, fromImageData, binToHz, fadeInOut, cloneSpectrum, reverseSpectrum, shuffleSpectrum, invertSpectrum, rotateSpectrum, reorderBins, linearBins, logarithmicBins, concatBins, reverseBins, minBins, maxBins, rotatePhase, }