c# - Crear un sistema DSP desde cero
.net audio (8)
Amo la música electrónica y estoy interesado en cómo funciona todo. He encontrado muchas preguntas útiles sobre Stack Overflow en bibliotecas que pueden usarse para reproducir audio, filtros, etc. Pero lo que realmente me interesa es lo que realmente está pasando: ¿cómo se transmiten los datos entre los efectos y los osciladores? He investigado el lado matemático de dsp y tengo ese extremo del problema suscrito, pero no estoy seguro de qué sistema de almacenamiento en búfer usar, etc. El objetivo final es tener una jerarquía de objetos simple y osciladores que pasen los datos entre ellos (tal vez usando multihilo si no termino sacándome todo el pelo tratando de implementarlo). No va a ser la próxima Razón Propellerhead, pero estoy interesado en cómo funciona todo esto y esto es más un ejercicio que algo que producirá un producto final.
En este momento utilizo .net y C # y recientemente aprendí F # (que puede o no conducir a algunas formas interesantes de manejar los datos) pero si estos no son adecuados para el trabajo, puedo aprender otro sistema si es necesario.
La pregunta es: ¿cuál es la mejor manera de obtener grandes cantidades de datos de señal a través del programa utilizando búferes? Por ejemplo, ¿sería mejor usar una cola, una matriz, una lista enlazada, etc.? ¿Debo hacer las muestras inmutables y crear un nuevo conjunto de datos cada vez que aplique un efecto al sistema o simplemente edite los valores en el búfer? Shoud: ¿Tengo un objeto de estilo de agrupación / subprocesador que organiza los datos de paso o las funciones de efecto pasan los datos directamente entre ellos?
Gracias.
EDITAR: otra pregunta relacionada es, ¿cómo usaría la API de Windows para reproducir esta matriz? Realmente no quiero usar DirectShow porque Microsoft prácticamente lo dejó para morir ahora
EDIT2: gracias por todas las respuestas. Después de mirar todas las tecnologías, usaré XNA 4 (pasé un tiempo rastreando Internet y encontré este sitio que explica cómo hacerlo) o NAudio para reproducir la música ... no estoy seguro de cuál, depende de qué tan avanzado el sistema termina siendo. Cuando salga C # 5.0, usaré sus capacidades asíncronas para crear una arquitectura de efectos además de eso. Casi he usado la respuesta de todos por igual, así que ahora tengo un enigma de a quién darle la recompensa ...
¿Has visto VST.NET (http://vstnet.codeplex.com/)? Es una biblioteca para escribir VST usando C # y tiene algunos ejemplos. También puede considerar escribir un VST, para que su código se pueda usar desde cualquier aplicación host (pero incluso si no lo desea, puede ser útil mirar su código).
Los datos de la señal suelen ser grandes y requieren mucho procesamiento. ¡No use una lista vinculada! La mayoría de las bibliotecas que conozco simplemente usan una matriz para poner todos los datos de audio (después de todo, eso es lo que espera la tarjeta de sonido).
De una muestra de VST.NET:
public override void Process(VstAudioBuffer[] inChannels, VstAudioBuffer[] outChannels)
{
VstAudioBuffer audioChannel = outChannels[0];
for (int n = 0; n < audioChannel.SampleCount; n++)
{
audioChannel[n] = Delay.ProcessSample(inChannels[0][n]);
}
}
El audioChannel es un contenedor alrededor de un buffer float * no administrado.
Probablemente guarde sus muestras en una matriz inmutable. Luego, cuando quiera reproducirlos, copie los datos en el búfer de salida (cambie la frecuencia si lo desea) y realice los efectos en este búfer. Tenga en cuenta que puede usar varios buffers de salida (o canales) y resumirlos al final.
Editar
Conozco dos formas de bajo nivel para reproducir su matriz: DirectSound y WaveOut de la API de Windows. C # Ejemplo usando DirectSound . C # ejemplo con WaveOut . Sin embargo, es posible que prefiera utilizar una biblioteca externa de nivel superior, como NAudio . NAudio es conveniente para la manipulación de audio .NET. Consulte esta publicación de blog para enviar una onda sinusoidal a la tarjeta de audio. Puedes ver que también están usando una matriz de float, que es lo que recomiendo (si haces tus cálculos usando bytes, terminarás con muchos alias en el sonido).
Bien, voy a tener una puñalada a la recompensa también :)
De hecho, estoy en una situación muy similar. He estado haciendo música electrónica por años, pero solo en los últimos años he comenzado a explorar el procesamiento de audio real.
Mencionas que has investigado las matemáticas. Creo que eso es crucial. Actualmente me estoy esforzando por obtener el Manual de procesamiento de señales digitales de Ken Steiglitz, con aplicaciones para audio digital y música de computadora. Si no conoce sus números complejos y fasores, va a ser muy difícil.
Soy un chico de Linux, así que comencé a escribir complementos LADSPA en C. Creo que es bueno comenzar en ese nivel básico, realmente entender lo que está pasando. Si estuviera en Windows, descargaría el SDK de VST de Steinberg y escribiría un complemento de prueba rápida de concepto que solo agrega ruido o lo que sea.
Otro beneficio de elegir un marco como VST o LADSPA es que puede usar inmediatamente sus complementos en su suite de audio normal. La satisfacción de aplicar tu primer complemento de fabricación casera a una pista de audio es inmejorable. Además, podrás compartir tus complementos con otros músicos.
Probablemente haya formas de hacerlo en C # / F #, pero recomendaría C ++ si planea escribir complementos VST, solo para evitar cualquier sobrecarga innecesaria. Ese parece ser el estándar de la industria.
En términos de almacenamiento en búfer, he estado utilizando buffers circulares (un buen artículo aquí: http://www.dspguide.com/ch28/2.htm ). Un buen ejercicio es implementar un filtro de respuesta finita (a lo que Steiglitz se refiere como un filtro de feedforward): estos dependen del almacenamiento en búfer y son bastante divertidos para jugar con ellos.
Tengo un repositorio en Github con algunos complementos LADSPA muy básicos. Aparte de la diferencia arquitectónica, también podrían ser útiles para alguien que escriba complementos VST. https://github.com/andreasjansson/my_ladspa_plugins
Otra buena fuente de código de ejemplo es el proyecto CSound. Hay toneladas de código DSP allí, y el software está dirigido principalmente a los músicos.
Con respecto a los problemas de búfer y asincronía / subprocesamiento / sincronización, le sugiero que eche un vistazo a la nueva biblioteca de TPL Data Flow. Con sus primitivas de bloque, estructuras de datos concurrentes, redes de flujo de datos, procesamiento de mensajes asíncronos y abstracción basada en tareas de TPL (que se puede usar con las características async / await C # 5), es una muy buena opción para este tipo de aplicaciones.
F # es probablemente una buena opción aquí, ya que está bien adaptada para manipular funciones. Las funciones son probablemente buenos bloques de construcción para la creación y el procesamiento de señales.
F # también es bueno para manipular colecciones en general, y arrays en particular, gracias a las funciones de orden superior en el módulo Array.
Estas cualidades hacen que F # sea popular en el sector financiero y también son útiles para el procesamiento de señales, supongo.
Visual F # 2010 para Computing Technical tiene una sección dedicada a Fourier Transform, que podría ser relevante para lo que desea hacer. Sin embargo, creo que hay mucha información gratuita sobre la transformación en la red.
Finalmente, para reproducir muestras, puede usar XNA . Creo que la última versión de la API (4.0) también permite la grabación, pero nunca he usado eso. Hay una famosa aplicación de edición de música para Xbox llamada ezmuse + Hamst3r Edition que usa XNA, por lo que definitivamente es posible.
He hecho un poco de DSP en tiempo real, aunque no con audio. Si bien cualquiera de sus ideas (buffer inmutable) vs (buffer mutable modificado en el lugar) podría funcionar, lo que prefiero hacer es crear un único búfer permanente para cada enlace en la ruta de la señal. La mayoría de los efectos no se prestan bien a la modificación en su lugar, ya que cada muestra de entrada afecta a múltiples muestras de salida. La técnica de búfer por cada enlace funciona especialmente bien cuando tiene etapas de remuestreo.
Aquí, cuando llegan las muestras, se sobrescribe el primer buffer. Luego, el primer filtro lee los datos nuevos de su búfer de entrada (el primer búfer) y escribe en su salida (el segundo búfer). Luego invoca la segunda etapa para leer desde el segundo buffer y escribir en el tercero.
Este patrón elimina por completo la asignación dinámica, permite que cada etapa mantenga una cantidad variable de historial (ya que los efectos necesitan algo de memoria), y es muy flexible en cuanto a permitir reorganizar los filtros en la ruta.
No sé si esto es realmente lo que estás buscando, pero este fue uno de mis proyectos personales mientras estaba en la universidad. Realmente no entendía qué tan bien funcionaba DSP hasta que lo implementé yo mismo. Intentaba acercarme lo más posible al hablante, así que lo hice usando solo libsndfile, para manejar las complejidades del formato de archivo para mí.
Básicamente, mi primer proyecto fue crear una gran variedad de dobles, llenarlo con una onda sinusoidal y luego usar sf_writef_double () para escribir esa matriz en un archivo para crear algo que pudiera tocar y ver el resultado en un editor de formas de onda.
Luego, agregué otra función entre la llamada sinusoidal y la llamada de escritura para agregar un efecto.
De esta forma, puede comenzar a jugar con osciladores y efectos de muy bajo nivel, y puede ver los resultados de inmediato. Además, es muy poco código para que funcione algo como esto.
Personalmente, comenzaría con la solución más simple posible y luego agregaría lentamente. Intenta simplemente escribir en un archivo y usar tu reproductor de audio para reproducirlo, para que no tengas que lidiar con las aplicaciones de audio. Solo usa una única matriz para comenzar y modificar en el lugar. Definitivamente comenzar con un solo hilo. A medida que su proyecto crezca, puede comenzar a pasar a otras soluciones, como las tuberías en lugar de la matriz, multiprocesarla o trabajar con la API de audio.
Si desea crear un proyecto que puede enviar, dependiendo de qué es exactamente, probablemente tendrá que pasar a bibliotecas más complejas, como algún procesamiento de audio en tiempo real. Pero lo básico que aprendes haciendo de la manera más simple de arriba definitivamente ayudará cuando llegues a este punto.
¡Buena suerte!
Podrías echar un vistazo a BYOND . Es un entorno para el audio / midi programático y la creación de efectos en C #. Está disponible como independiente y como instrumento y efecto de VST.
REVELACIÓN COMPLETA Soy el desarrollador de BYOND.