Estoy tratando de crear un sonido usando coeficientes de Fourier.
En primer lugar, déjame mostrarte cómo obtuve los coeficientes de Fourier.
(1) Tomé una instantánea de una forma de onda del sonido de un micrófono.
Los datos se ven como a continuación: (Cintiqué Uint8Array, que es el valor de retorno de getByteTimeDomainData() , y agregué la propiedad de length para cambiar este objeto a Array más tarde)
const raw = '{"length": 512,"0":126,"1":121,"2":121,"3":124,"4":129,"5":135,"6":140,"7":147,"8":153,"9":156,"10":152,"11":141,"12":125,"13":112,"14":106,"15":108,"16":113,"17":120,"18":127,"19":132,"20":138,"21":142,"22":141,"23":136,"24":126,"25":115,"26":106,"27":103,"28":105,"29":111,"30":117,"31":121,"32":123,"33":124,"34":124,"35":120,"36":112,"37":103,"38":97,"39":95,"40":96,"41":98,"42":101,"43":106,"44":112,"45":117,"46":117,"47":113,"48":105,"49":98,"50":93,"51":91,"52":91,"53":92,"54":93,"55":95,"56":97,"57":101,"58":105,"59":108,"60":106,"61":101,"62":96,"63":95,"64":97,"65":100,"66":100,"67":97,"68":94,"69":94,"70":99,"71":104,"72":106,"73":105,"74":104,"75":105,"76":108,"77":111,"78":112,"79":110,"80":108,"81":105,"82":105,"83":107,"84":110,"85":113,"86":114,"87":115,"88":116,"89":120,"90":123,"91":125,"92":124,"93":121,"94":120,"95":121,"96":123,"97":124,"98":124,"99":126,"100":128,"101":131,"102":133,"103":134,"104":134,"105":134,"106":134,"107":134,"108":134,"109":133,"110":132,"111":131,"112":131,"113":134,"114":137,"115":139,"116":141,"117":142,"118":143,"119":142,"120":142,"121":139,"122":136,"123":131,"124":128,"125":128,"126":131,"127":134,"128":137,"129":139,"130":140,"131":141,"132":142,"133":141,"134":137,"135":132,"136":126,"137":122,"138":123,"139":127,"140":132,"141":135,"142":135,"143":134,"144":134,"145":135,"146":134,"147":130,"148":125,"149":121,"150":120,"151":121,"152":124,"153":129,"154":132,"155":134,"156":134,"157":133,"158":131,"159":129,"160":128,"161":127,"162":125,"163":124,"164":123,"165":124,"166":125,"167":128,"168":130,"169":131,"170":132,"171":132,"172":131,"173":129,"174":129,"175":129,"176":130,"177":129,"178":129,"179":128,"180":129,"181":132,"182":134,"183":135,"184":134,"185":133,"186":131,"187":131,"188":131,"189":132,"190":134,"191":134,"192":134,"193":134,"194":137,"195":140,"196":142,"197":142,"198":141,"199":138,"200":136,"201":135,"202":137,"203":138,"204":137,"205":135,"206":134,"207":137,"208":142,"209":147,"210":148,"211":147,"212":146,"213":144,"214":144,"215":144,"216":144,"217":142,"218":138,"219":136,"220":137,"221":141,"222":145,"223":149,"224":150,"225":150,"226":150,"227":150,"228":150,"229":148,"230":145,"231":142,"232":142,"233":144,"234":146,"235":146,"236":146,"237":147,"238":150,"239":153,"240":153,"241":149,"242":145,"243":143,"244":141,"245":141,"246":142,"247":143,"248":143,"249":142,"250":144,"251":148,"252":153,"253":152,"254":142,"255":130,"256":123,"257":123,"258":127,"259":130,"260":132,"261":134,"262":139,"263":147,"264":154,"265":155,"266":148,"267":134,"268":119,"269":108,"270":106,"271":110,"272":115,"273":119,"274":124,"275":129,"276":136,"277":141,"278":141,"279":135,"280":125,"281":115,"282":108,"283":105,"284":105,"285":108,"286":111,"287":115,"288":119,"289":122,"290":121,"291":116,"292":110,"293":106,"294":104,"295":101,"296":98,"297":96,"298":98,"299":103,"300":110,"301":115,"302":116,"303":112,"304":104,"305":98,"306":95,"307":95,"308":94,"309":91,"310":88,"311":88,"312":94,"313":101,"314":107,"315":110,"316":107,"317":103,"318":100,"319":99,"320":99,"321":98,"322":95,"323":89,"324":87,"325":89,"326":96,"327":103,"328":107,"329":109,"330":110,"331":111,"332":113,"333":113,"334":110,"335":105,"336":102,"337":102,"338":104,"339":105,"340":107,"341":110,"342":115,"343":120,"344":123,"345":123,"346":122,"347":120,"348":120,"349":121,"350":123,"351":124,"352":123,"353":122,"354":122,"355":126,"356":133,"357":137,"358":136,"359":132,"360":128,"361":129,"362":134,"363":139,"364":139,"365":135,"366":131,"367":131,"368":135,"369":141,"370":144,"371":143,"372":140,"373":138,"374":138,"375":140,"376":142,"377":140,"378":136,"379":131,"380":130,"381":133,"382":138,"383":141,"384":141,"385":140,"386":140,"387":140,"388":139,"389":136,"390":132,"391":129,"392":128,"393":128,"394":129,"395":131,"396":133,"397":135,"398":136,"399":136,"400":135,"401":132,"402":129,"403":125,"404":123,"405":123,"406":125,"407":126,"408":126,"409":126,"410":128,"411":131,"412":133,"413":133,"414":130,"415":127,"416":125,"417":125,"418":125,"419":125,"420":125,"421":125,"422":125,"423":126,"424":129,"425":131,"426":132,"427":131,"428":128,"429":126,"430":126,"431":128,"432":129,"433":130,"434":130,"435":130,"436":132,"437":134,"438":136,"439":135,"440":133,"441":131,"442":129,"443":128,"444":129,"445":130,"446":132,"447":134,"448":136,"449":138,"450":140,"451":142,"452":143,"453":142,"454":140,"455":137,"456":135,"457":134,"458":134,"459":134,"460":134,"461":135,"462":137,"463":139,"464":143,"465":147,"466":148,"467":147,"468":146,"469":145,"470":144,"471":141,"472":139,"473":137,"474":136,"475":137,"476":139,"477":142,"478":145,"479":149,"480":150,"481":151,"482":152,"483":152,"484":151,"485":146,"486":141,"487":138,"488":140,"489":145,"490":147,"491":146,"492":145,"493":147,"494":152,"495":157,"496":156,"497":151,"498":145,"499":140,"500":137,"501":139,"502":143,"503":147,"504":147,"505":144,"506":143,"507":146,"508":152,"509":152,"510":143,"511":129}';※ Si dibujamos estos datos en el lienzo, podemos ver lo siguiente: (Este es un sonido de la vocal 'i' (suena como 'ee'))
Parece que se capturan 2 períodos de la ola. Dado que la longitud es 512, podemos suponer que los datos de un período se encuentran en el índice 0 ~ 255.
(2) Procesé los datos.
const parsed = JSON.parse(raw); const arrayfied = Array.from(parsed); const sliced = arrayfied.slice(0, 256); const refined = []; // According to the Web Audio API specification, // "The values stored in the unsigned byte array are computed in the following way. // Let x[k] be the time-domain data. Then the byte value, b[k], is // b[k]=⌊128(1+x[k])⌋." So, I manipulate the array like the following: for (let i = 0; i < sliced.length; i++) { refined[i] = (sliced[i] / 128) - 1; }(3) Calculé los coeficientes de Fourier.
// This function calculates Riemann sum (area approximation using rectangles) // fn: function to be calculated // initial: calculation start point // final: calculation end point // division: number of rectangles to use // nth: used for an, bn (please see below) function numerical_integration(fn, initial, final, division, nth = null) { let accumulation = 0; const STEP = (final - initial) / division; for (let i = initial; i <= final; i++) { // calculate an area of a rectangle and add accumulation += fn(i, initial, final, nth) * STEP; } return accumulation; } // This is f(t) function f0(t) { const result = refined[t]; return result; } // This is f(t) * cos(nwt) // ※ w = 2 * Math.PI / period function fc(t, i, f, n) { const result = f0(t) * Math.cos(n * 2 * Math.PI * t / (f - i)); return result; } // This is f(t) * sin(nwt) function fs(t, i, f, n) { const result = f0(t) * Math.sin(n * 2 * Math.PI * t / (f - i)); return result; } // This function returns a0 value // period is 256 (0 ~ 255) and the last element of array refined is at index 255, // so I subtract one. function getA0(period) { const result = numerical_integration(f0, 0, period - 1, 100) / period; return result; } // This function returns an values function getAn(period) { const result = []; for (let i = 1; i <= 49; i++) { result.push(numerical_integration(fc, 0, period - 1, 100, i) * 2 / period); } return result; } // This function returns bn values function getBn(period) { const result = []; for (let i = 1; i <= 49; i++) { result.push(numerical_integration(fs, 0, period - 1, 100, i) * 2 / period); } return result; }¡Hasta aquí todo bien! ¡Ahora podemos verificar si nuestros coeficientes de Fourier están bien calculados haciendo una función de onda usando los coeficientes y dibujándola en el lienzo!
const a0 = getA0(refined.length); const an = getAn(refined.length); const bn = getBn(refined.length); // returns y coordinate function getY(t) { let anSum = 0; let bnSum = 0; for (let i = 0; i <= 48; i++) { anSum += an[i] * Math.cos((i + 1) * 2 * Math.PI * t / refined.length); bnSum += bn[i] * Math.sin((i + 1) * 2 * Math.PI * t / refined.length); } const result = a0 + anSum + bnSum; return result; } // draw canvasContext.lineTo(x, getY(t));¡Guau! ¡Bien hecho! ¡Es casi igual que la ola original!
Entonces podrías preguntar "Entonces, ¿cuál es tu pregunta?" Por lo tanto, voy a hacer mi pregunta: ¿Cómo reproducir el sonido usando los coeficientes de Fourier? (No sé mucho sobre Web Audio API y sonido digital)
Lo que he pensado son tres cosas:
Probé AudioWorklet pero sonaba como saturado (?) A4 (quizás) con un sonido crepitante de 'tic tac'. El código de AudioWorklet es el siguiente:
class IWaveProducer extends AudioWorkletProcessor { constructor() { super(); this.t = 0; } process(inputs, outputs, parameters) { const output = outputs[0]; output.forEach(channel => { for (let i = 0; i < channel.length; i++) { channel[i] = getY(this.t); } }); this.t++; return true; } } registerProcessor('i-wave-producer', IWaveProducer);Y este es el gráfico:
Así que esta vez probé PeriodicWave y OscillatorNode pero también falló. El código es el siguiente:
const real = new Float32Array(50); const imag = new Float32Array(50); real[0] = a0; imag[0] = 0; for (let i = 1; i <= 48; i++) { real[i + 1] = an[i]; imag[i + 1] = bn[i]; } const wave = new PeriodicWave(audioCtx, { real, imag, disableNormalization: false }); const osc = new OscillatorNode(audioCtx, { periodicWave: wave }); osc.connect(analyser) .connect(audioCtx.destination); osc.start();Y este es el gráfico:
Sonaba como una onda de diente de sierra A4 (quizás). Además, curiosamente, parece que todos los datos se insertaron correctamente, ya que la forma de la onda es bastante similar a la imagen de arriba (consulte la imagen de 'forma de onda aproximada'). (Su patrón: una montaña alta y una montaña pequeña)
... ¡Pero esto no es lo que quiero! ¡Lo que quiero es reproducir un sonido de vocal 'i'! ¿Cómo puedo lograr mi objetivo? Por favor, hágamelo saber si sabe algo. Podria ser muy apreciado. Estoy muerta de curiosidad. Por favor, ayúdame ㅠㅠ. Muchas gracias por leer esta larga pregunta.
¿O es imposible hacer 'voz' con Web Audio API? Pero he visto una biblioteca haciendo voz usando JavaScript antes. Por ejemplo:
¡Hola de nuevo! ¡Creo que acabo de encontrar la respuesta! La respuesta es... 「AudioBuffer」. Literalmente estoy llorando... de alegría... De todos modos, ¡aquí está el código!
// Since the length of wave is 256 // and I guess (maybe wrong) that it means // this wave lasts for 256 / 44100 seconds (= 0.0058). // Thus, in order to make it longer, // multiply 1000. So this sound will exist for 5.8 seconds. // (Since sampling rate is 44100 per sec, // the formula results in the length of this buffer--256000.) const audioBuffer = new AudioBuffer({ numberOfChannels: 1, length: 1000 * audioCtx.sampleRate * 256 / 44100, sampleRate: audioCtx.sampleRate }); const buffering = audioBuffer.getChannelData(0); let count = 0; for (let i = 0; i < audioBuffer.length; i++) { buffering[i] = refined[count]; if (count === 255) { count = 0; } else { count++; } } const source = new AudioBufferSourceNode(audioCtx, { buffer: audioBuffer }); source.connect(analyser) .connect(audioCtx.destination); source.start();¡El sonido resultante es un poco gracioso! Pero creo que seguramente suena como 'i (ee)'. También suena como una nota 'fa'. ¿Porqué es eso? Pensémoslo juntos. Primero creo que necesitamos calcular la frecuencia de la onda. Dado que el período de la onda es de 0,0058 segundos, la frecuencia es 1/0,0058, que es 172,4138 Hz.
A continuación, A4 es de 440 Hz. Por lo tanto A3 es 220Hz. Cuatro notas debajo es F3 (A3, G#4, G4, F#3, F3). Entonces la frecuencia de F3 es 220 * 2^(-4/12) = 174,6141 Hz.
172 es casi lo mismo con 174. !!!!! ¡Lo cual tiene absolutamente sentido! El secreto ahora está resuelto. Es por eso que ese sonido suena como fa.
Gracias por leer mi dura, pero al mismo tiempo hermosa historia de lucha contra Web Audio API. ¡Chau!
¡Hola de nuevo! ¡Acabo de descubrir que PeriodicWave y OscillatorNode también pueden ser la respuesta!
const osc = new OscillatorNode(audioCtx, { periodicWave: wave, frequency: 174 });¡Configurar el parámetro de frecuencia es la clave! ¡Adiós de nuevo!
Entonces el único que queda es AudioWorklet. ¿Puede ser también la respuesta? Me da curiosidad.