c# audio music wav

c# - Escribir notas musicales en un archivo wav



audio (3)

Estoy interesado en cómo tomar notas musicales (por ejemplo, A, B, C #, etc.) o acordes (notas múltiples al mismo tiempo) y escribirlas en un archivo wav.

Por lo que entiendo, cada nota tiene una frecuencia específica asociada (para el tono perfecto); por ejemplo, A4 (el A por encima del medio C) es 440 Hz (lista completa 2/3 del camino hacia abajo de esta página ).

Si mi comprensión es correcta, este tono está en el dominio de la frecuencia, y entonces ¿necesita la transformada de Fourier rápida inversa que se le aplica para generar el equivalente en el dominio del tiempo?

Lo que quiero saber es:

  • ¿Cómo funcionan los acordes? ¿Son el promedio de los lanzamientos?
  • ¿Cómo se especifica el tiempo de reproducción de cada nota cuando el contenido del archivo wav es una forma de onda?
  • ¿cómo se convierte el resultado de múltiples notas en FFT inversa a una matriz de bytes, que componen los datos en un archivo wav?
  • cualquier otra información relevante relacionada con esto.

Gracias por cualquier ayuda que usted puede dar. Si estoy dando ejemplos de código, estoy usando C # y el código que estoy usando actualmente para crear archivos wav es el siguiente:

int channels = 1; int bitsPerSample = 8; //WaveFile is custom class to create a wav file. WaveFile file = new WaveFile(channels, bitsPerSample, 11025); int seconds = 60; int samples = 11025 * seconds; //Create x seconds of audio // Sound Data Size = Number Of Channels * Bits Per Sample * Samples byte[] data = new byte[channels * bitsPerSample/8 * samples]; //Creates a Constant Sound for(int i = 0; i < data.Length; i++) { data[i] = (byte)(256 * Math.Sin(i)); } file.SetData(data, samples);

Esto crea (de alguna manera) un sonido constante, pero no entiendo completamente cómo se correlaciona el código con el resultado.


Estás en el camino correcto. :)

Señal de audio

No es necesario realizar una FFT inversa (podría hacerlo, pero necesitaría encontrar una lib para ello o implementarla, además de generar una señal como entrada). Es mucho más fácil generar directamente el resultado que esperamos de esa IFFT, que es una señal sinusoidal con la frecuencia dada.

El argumento al seno depende tanto de la nota que desea generar como de la frecuencia de muestreo del archivo de onda que genera (a menudo igual a 44100Hz, en su ejemplo está usando 11025Hz).

Para un tono de 1 Hz, debe tener una señal sinusoidal con un período igual a un segundo. Con 44100 Hz, hay 44100 muestras por segundo, lo que significa que necesitamos tener una señal sinusoidal con un período igual a 44100 muestras. Como el período de seno es igual a Tau (2 * Pi) obtenemos:

sin(44100*f) = sin(tau) 44100*f = tau f = tau / 44100 = 2*pi / 44100

Para 440 Hz obtenemos:

sin(44100*f) = sin(440*tau) 44100*f = 440*tau f = 440 * tau / 44100 = 440 * 2 * pi / 44100

En C # esto sería algo como esto:

double toneFreq = 440d; double f = toneFreq * 2d * Math.PI / 44100d; for (int i = 0; i<data.Length; i++) data[i] = (byte)(128 + 127*Math.Sin(f*i));

NOTA: No he probado esto para verificar la exactitud del código. Trataré de hacer eso y corregir cualquier error. Actualización: he actualizado el código a algo que funciona. Perdón por herir tus oídos ;-)

Acordes

Los acordes son una combinación de notas (véase, por ejemplo, acorde menor en Wikipedia ). Entonces la señal sería una combinación (suma) de senos con diferentes frecuencias.

Tonos puros

Sin embargo, esos tonos y acordes no sonarán naturales, porque los instrumentos tradicionales no reproducen tonos de frecuencia únicos. En cambio, cuando juegas un A4, hay una amplia distribución de frecuencias, con una concentración de alrededor de 440 Hz. Ver por ejemplo Timbre .


Estás en el camino correcto.

Echemos un vistazo a su ejemplo:

for(int i = 0; i < data.Length; i++) data[i] = (byte)(256 * Math.Sin(i));

OK, tienes 11025 muestras por segundo. Tienes 60 segundos de muestras. Cada muestra es un número entre 0 y 255 que representa un pequeño cambio en la presión del aire en un punto en el espacio en un momento dado.

Sin embargo, espere un minuto, el seno va de -1 a 1, por lo que las muestras van de -256 a +256, y eso es más grande que el rango de un byte, entonces algo curioso está sucediendo aquí. Repasemos su código para que la muestra esté en el rango correcto.

for(int i = 0; i < data.Length; i++) data[i] = (byte)(128 + 127 * Math.Sin(i));

Ahora tenemos datos que varían suavemente entre 1 y 255, por lo que estamos en el rango de un byte.

Pruébelo y vea cómo suena. Debería sonar mucho más "suave".

El oído humano detecta cambios increíblemente pequeños en la presión del aire. Si esos cambios forman un patrón de repetición, la cóclea en el oído interpreta la frecuencia con la que se repite el patrón como un tono particular. El tamaño del cambio de presión se interpreta como el volumen .

Tu forma de onda tiene sesenta segundos de duración. El cambio va del cambio más pequeño, 1, al cambio más grande, 255. ¿Dónde están los picos ? Es decir, ¿dónde alcanza la muestra un valor de 255, o cerca de ella?

Bueno, seno es 1 a π / 2, 5π / 2, 9π / 2, 13π / 2, y así sucesivamente. Entonces los picos son cada vez que estoy cerca de uno de esos. Es decir, a los 2, 8, 14, 20, ...

¿Qué tan separados en el tiempo son esos? Cada muestra es 1 / 11025th de un segundo, por lo que los picos son aproximadamente 2π / 11025 = aproximadamente 570 microsegundos entre cada pico. ¿Cuántos picos hay por segundo? 11025 / 2π = 1755 Hz. (El Hertz es la medida de frecuencia, cuántos picos por segundo). 1760 Hz es dos octavas por encima de A 440, por lo que este es un tono A levemente plano.

¿Cómo funcionan los acordes? ¿Son el promedio de los lanzamientos?

No. Un acorde que es A440 y una octava arriba, A880 no es equivalente a 660 Hz. No promedias el tono . Sumas la forma de onda .

Piense en la presión del aire. Si tiene una fuente vibratoria que bombea la presión hacia arriba y hacia abajo 440 veces por segundo, y otra que bombea la presión hacia arriba y hacia abajo 880 veces por segundo, la red no es lo mismo que una vibración a 660 veces por segundo. Es igual a la suma de las presiones en cualquier punto dado en el tiempo. Recuerde, eso es todo un archivo WAV es: una gran lista de cambios de presión de aire .

Supongamos que quiere hacer una octava debajo de su muestra. ¿Cuál es la frecuencia? La mitad como mucho. Así que hagamos que suceda la mitad de las veces:

for(int i = 0; i < data.Length; i++) data[i] = (byte)(128 + 127 * Math.Sin(i/2.0));

Tenga en cuenta que tiene que ser 2.0, no 2. ¡No queremos el redondeo de enteros! El 2.0 le dice al compilador que desea el resultado en coma flotante, no enteros.

Si lo haces, obtendrás los picos la mitad de las veces: con i = 4, 16, 28 ... y, por lo tanto, el tono será una octava más baja. (Cada octava hacia abajo reduce a la mitad la frecuencia, cada octava hacia arriba la dobla ).

Pruébelo y vea cómo obtiene el mismo tono, una octava más bajo.

Ahora agrégalos juntos.

for(int i = 0; i < data.Length; i++) data[i] = (byte)(128 + 127 * Math.Sin(i)) + (byte)(128 + 127 * Math.Sin(i/2.0));

Eso probablemente sonaba como una mierda. ¿Que pasó? Nos desbordamos de nuevo ; la suma fue mayor que 256 en muchos puntos. Reduce a la mitad el volumen de ambas ondas :

for(int i = 0; i < data.Length; i++) data[i] = (byte)(128 + (63 * Math.Sin(i/2.0) + 63 * Math.Sin(i)));

Mejor. "63 sin x + 63 sin y" está entre -126 y +126, por lo que no puede desbordar un byte.

(Entonces hay un promedio: esencialmente estamos tomando el promedio de la contribución a la presión de cada tono , no el promedio de las frecuencias ).

Si tocas eso, deberías obtener ambos tonos al mismo tiempo, uno una octava más alto que el otro.

Esa última expresión es complicada y difícil de leer. Vamos a dividirlo en un código que sea más fácil de leer. Pero primero, resuma la historia hasta ahora:

  • 128 está a medio camino entre baja presión (0) y alta presión (255).
  • el volumen del tono es la presión máxima alcanzada por la ola
  • un tono es una onda sinusoidal de una frecuencia dada
  • la frecuencia en Hz es la frecuencia de muestreo (11025) dividida por 2π

Así que vamos a armarlo:

double sampleFrequency = 11025.0; double multiplier = 2.0 * Math.PI / sampleFrequency; int volume = 20; // initialize the data to "flat", no change in pressure, in the middle: for(int i = 0; i < data.Length; i++) data[i] = 128; // Add on a change in pressure equal to A440: for(int i = 0; i < data.Length; i++) data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 440.0))); // Add on a change in pressure equal to A880: for(int i = 0; i < data.Length; i++) data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 880.0)));

Y ahí estás; ahora puedes generar cualquier tono que desees de cualquier frecuencia y volumen. Para hacer un acorde, agrégalos juntos, asegurándote de que no vayas demasiado alto y desbordes el byte.

¿Cómo sabe la frecuencia de una nota que no sea A220, A440, A880, etc.? Cada semitono multiplica la frecuencia anterior por la raíz 12 de 2. Entonces calcule la raíz 12 de 2, multiplique eso por 440, y eso es A #. Multiplique A # por la raíz 12 de 2, eso es B. B veces la raíz 12 de 2 es C, luego C #, y así sucesivamente. Hazlo 12 veces y como es la raíz 12 de 2, obtendrás 880, el doble de lo que comenzaste.

¿Cómo se especifica el tiempo de reproducción de cada nota cuando el contenido del archivo wav es una forma de onda?

Simplemente completa el espacio de muestra donde suena el tono. Supongamos que quiere jugar A440 por 30 segundos y luego A880 por 30 segundos:

// initialize the data to "flat", no change in pressure, in the middle: for(int i = 0; i < data.Length; i++) data[i] = 128; // Add on a change in pressure equal to A440 for 30 seconds: for(int i = 0; i < data.Length / 2; i++) data[i] = (data[i] + volume * Math.Sin(i * multiplier * 440.0))); // Add on a change in pressure equal to A880 for the other 30 seconds: for(int i = data.Length / 2; i < data.Length; i++) data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 880.0)));

¿cómo se convierte el resultado de múltiples notas en FFT inversa a una matriz de bytes, que componen los datos en un archivo wav?

La FFT inversa solo construye las ondas sinusoidales y las agrega juntas, tal como lo estamos haciendo aquí. ¡Eso es todo!

cualquier otra información relevante relacionada con esto?

Ver mis artículos sobre el tema.

http://blogs.msdn.com/b/ericlippert/archive/tags/music/

Las partes uno a tres explican por qué los pianos tienen doce notas por octava.

La cuarta parte es relevante para su pregunta; ahí es donde creamos un archivo WAV desde cero.

Observe que en mi ejemplo estoy usando 44100 muestras por segundo, no 11025, y estoy usando muestras de 16 bits que van desde -16000 a +16000 en lugar de muestras de 8 bits que van de 0 a 255. Pero aparte de esos detalles, es básicamente lo mismo que el tuyo

Recomendaría ir a una tasa de bits más alta si va a hacer algún tipo de forma de onda compleja; 8 bits a 11K muestras por segundo van a sonar terribles para formas de onda complejas. 16 bits por muestra con 44K muestras por segundo es calidad de CD.

Y francamente, es mucho más fácil obtener los cálculos correctos si lo haces en cortos firmados en lugar de bytes sin firmar.

La parte cinco da un ejemplo interesante de una ilusión auditiva.

Además, intente ver sus formas de onda con la visualización de "alcance" en Windows Media Player. Eso te dará una buena idea de lo que está sucediendo realmente.

ACTUALIZAR:

Me he dado cuenta de que al agregar dos notas juntas, puede terminar con un ruido de estallido, debido a que la transición entre las dos formas de onda es demasiado nítida (por ejemplo, terminando en la parte superior de una y comenzando en la parte inferior de la siguiente). ¿Cómo se puede superar este problema?

Excelente pregunta de seguimiento.

Esencialmente, lo que está sucediendo aquí es una transición instantánea de (digamos) alta presión a baja presión, que se escucha como un "pop". Hay un par de formas de lidiar con eso.

Técnica 1: Cambio de fase

Una forma sería "cambiar de fase" el tono subsiguiente en una pequeña cantidad tal que la diferencia entre el valor de inicio del tono subsiguiente y el valor final del tono anterior. Puede agregar un término de cambio de fase como este:

data[i] = (data[i] + volume * Math.Sin(phaseshift + i * multiplier * 440.0)));

Si el cambio de fase es cero, obviamente eso no es un cambio. Un cambio de fase de 2π (o cualquier múltiplo par de π) tampoco cambia, ya que el pecado tiene un período de 2π. Cada valor entre 0 y 2π cambia donde el tono "comienza" un poco más a lo largo de la ola.

Calcular exactamente cuál es el cambio de fase correcto puede ser un poco complicado. Si lees mis artículos sobre la generación de un tono de ilusión Shepard "continuamente descendente", verás que utilicé algunos cálculos simples para asegurarme de que todo cambiara continuamente sin saltos. Puede usar técnicas similares para descubrir cuál es el cambio correcto para hacer desaparecer el pop.

Estoy tratando de encontrar la forma de generar el valor phasehift. Es "ArcSin (((primera muestra de datos de nueva nota) - (última muestra de datos de la nota anterior)) / noteVolume)" ¿verdad?

Bueno, lo primero es darse cuenta de que no puede haber un "valor correcto". Si la nota final es muy alta y termina en un pico, y la nota inicial es muy tranquila, tal vez no tenga sentido el nuevo tono que coincida con el valor del tono antiguo.

Suponiendo que hay una solución, ¿qué es? Tienes una muestra final, llámala y, y quieres encontrar el desplazamiento de fase x tal que

y = v * sin(x + i * freq)

cuando yo soy cero Así que eso es

x = arcsin(y / v)

Sin embargo , eso podría no ser del todo correcto. Supongamos que tiene

y quieres agregar

Hay dos posibles cambios de fase :

y

Adivina qué es lo que suena mejor. :-)

Averiguar si está en la "carrera ascendente" o "descendente" de la ola puede ser un poco complicado. Si no quiere resolver las matemáticas reales, puede hacer algunas heurísticas simples, como "¿cambió el signo de la diferencia entre los puntos de datos sucesivos en la transición?"

Técnica 2: sobre de ADSR

Si está modelando algo que se supone suena como un instrumento real, entonces puede obtener buenos resultados cambiando el volumen de la siguiente manera.

Lo que quiere hacer es tener cuatro secciones diferentes para cada nota, llamadas ataque, decaimiento, sostenimiento y liberación. El volumen de una nota tocada en un instrumento se puede modelar así:

// / /__________ / / / / A D S R

El volumen comienza en cero. Luego ocurre el ataque: el sonido aumenta rápidamente a su volumen máximo. Luego se desintegra levemente a su nivel de sostenimiento. Luego se mantiene en ese nivel, tal vez disminuyendo lentamente mientras se reproduce la nota, y luego vuelve a cero.

Si haces eso, entonces no hay pop porque el inicio y el final de cada nota están en cero volumen. El lanzamiento asegura eso.

Diferentes instrumentos tienen diferentes "sobres". Un órgano de tubos, por ejemplo, tiene un ataque increíblemente corto, decaimiento y liberación; es todo sostenido, y el sostenimiento es infinito. Tu código actual es como un órgano de tubos. Compare con, por ejemplo, un piano. Nuevamente, ataque corto, decaimiento corto, lanzamiento corto, pero el sonido se vuelve gradualmente más silencioso durante el sostenimiento.

Las secciones de ataque, decaimiento y liberación pueden ser muy cortas, demasiado cortas para escucharlas pero lo suficientemente largas como para evitar el pop. Experimente cambiando el volumen a medida que se reproduce la nota y vea qué sucede.


Nadie ha mencionado aún el algoritmo de cuerda desplumada Karplus Strong.

Síntesis de cuerdas Karplus-Strong Es un método extremadamente simple para generar un sonido de cuerdas punteado realista. He escrito instrumentos musicales polifónicos / reproductores MIDI en tiempo real usando esto.

Lo haces así:

Primero, ¿qué frecuencia quieres simular? Digamos el tono de concierto A = 440Hz

Suponiendo que su frecuencia de muestreo es 44.1kHz, eso es 44100/440 = 100.25 muestras por longitud de onda.

Vamos a redondear eso al entero más cercano: 100, y crear una longitud circular de memoria intermedia 100.

Por lo tanto, mantendrá una onda estacionaria de frecuencia ~ 440Hz (tenga en cuenta que no es exacta, hay formas de evitar esto).

Rellene con estática aleatoria entre -1 y +1, y:

DECAY = 0.99 while( n < 99999 ) outbuf[n++] = buf[k] newVal = DECAY * ( buf[k] + buf_prev ) / 2 buf_prev = buf[k] buf[k] = newVal k = (k+1) % 100

Es un algoritmo sorprendente porque es muy simple y genera un súper sonido.

La mejor forma de entender lo que sucede es darse cuenta de que la estática aleatoria en el dominio del tiempo es ruido blanco; aleatorio estático en el dominio de la frecuencia. Puedes imaginarlo como el compuesto de muchas ondas de frecuencia diferente (aleatoria).

Las frecuencias cercanas a 440Hz (o 2 * 440Hz, 3 * 440Hz, etc.) crearán interferencias constructivas consigo mismas, ya que pasan alrededor del anillo una y otra vez. Entonces ellos serán preservados. Otras frecuencias interferirán destructivamente con ellas mismas.

Además, el promedio actúa como filtro de paso bajo: imagine que su secuencia es +1 -1 +1 -1 +1 -1, si está promediando pares, cada promedio sale como 0. pero si tiene una onda más lenta como 0 0.2 0.3 0.33 0.3 0.2 ... luego promediar aún resulta en una ola. Cuanto más larga es la ola, más se conserva su energía, es decir, el promedio causa menos amortiguación.

Por lo tanto, se puede pensar que promediar es un filtro de paso bajo muy simple.

Hay complicaciones, por supuesto, tener que elegir una longitud de memoria intermedia entera obliga a una cuantificación de las frecuencias posibles, que se hace evidente hacia la parte superior del piano. ¡Todo es superable pero se pone difícil!

Campo de golf:

Delicious Max / MSP Tutorial 1: Karplus-Strong

El algoritmo Karplus-Strong

JOS, por lo que puedo ver, es la principal autoridad mundial en la generación de tonos sintéticos, todos los caminos conducen a su sitio web. Pero ten cuidado, se pone complicado muy rápido y requiere matemáticas de nivel universitario.