2010-04-27 12 views
22

Tengo un proyecto de C# que reproduce código Morse para fuentes RSS. Lo escribo usando Managed DirectX, solo para descubrir que Managed DirectX es viejo y obsoleto. La tarea que tengo es reproducir ráfagas de onda sinusoidal pura intercaladas con períodos de silencio (el código) que están cronometrados con precisión en cuanto a su duración. Necesito poder llamar a una función que reproduce un tono puro durante tantos milisegundos, luego Thread.Sleep() y luego reproducir otro, etc. En su forma más rápida, los tonos y espacios pueden ser tan cortos como 40 ms.¿Cómo producir un tono y silencio con precisión de tiempo?

Funciona bastante bien en Managed DirectX. Para obtener el tono preciso y preciso, creo 1 seg. de onda sinusoidal en una memoria intermedia secundaria, luego, para reproducir un tono de cierta duración, busco dentro de x milisegundos del final de la memoria intermedia y luego juego.

He intentado System.Media.SoundPlayer. Es un perdedor [Editar - vea mi respuesta a continuación] porque tiene que Reproducir(), Reposo(), luego Detener() para longitudes de tono arbitrarias. El resultado es un tono que es demasiado largo, variable según la carga de la CPU. Se necesita una cantidad de tiempo indeterminada para detener el tono.

Luego me embarqué en un intento largo intento de usar NAudio 1.3. Terminé con una secuencia residente en la memoria que proporciona los datos de tono, y de nuevo buscando dejar la longitud deseada de tono restante en la transmisión, y luego reproducir. Esto funcionó bien en la clase DirectSoundOut por un tiempo (ver a continuación) pero la clase WaveOut muere rápidamente con una afirmación interna que dice que los búferes todavía están en la cola a pesar de PlayerStopped = true. Esto es extraño ya que toco hasta el final y pongo una espera de la misma duración entre el final del tono y el inicio del siguiente. Pensarías que 80 ms después de iniciar Play de un tono de 40 ms no tendría buffers en la cola.

DirectSoundOut funciona bien por un tiempo, pero su problema es que por cada reproducción de tono Play() produce un hilo separado. Eventualmente (5 minutos más o menos) simplemente deja de funcionar. Puede ver el hilo después del hilo después de salir del hilo en la ventana de Salida mientras ejecuta el proyecto en VS2008 IDE. No creo objetos nuevos durante la reproducción, simplemente busco() la secuencia de tonos y luego llamo a Play() una y otra vez, así que no creo que sea un problema con los buffers huérfanos/lo que sea que se acumulen hasta que se ahogue.

No tengo paciencia con esto, así que estoy esperando con la esperanza de que alguien aquí haya enfrentado un requisito similar y pueda orientarme en una dirección con una solución probable.

+0

Acabo de editar ligeramente la publicación y ya tiene 4 votos? Guau. –

+0

Es una aplicación interesante, y usted hace algunas buenas preguntas. Así que sí, upvote. –

+0

Si lo tiene funcionando en Managed DirectX (MDX) ¿por qué es un problema que ya no sea compatible con Microsoft? El hecho de que no sea compatible no significa que no pueda usarlo. Cualquier máquina que tenga .NET 2 y DirectX 9 instalado debería continuar ejecutando aplicaciones MDX sin problemas. – Ash

Respuesta

7

No puedo creerlo ... Volví a System.Media.SoundPlayer y lo hice para hacer lo que quiero ... ninguna biblioteca de dependencia gigante con 95% de código no utilizado y/o peculiaridades esperando ser descubierto :-). ¡Además, se ejecuta en MacOSX bajo Mono (2.6)! [mal - no hay sonido, se haga la pregunta separada]

he usado un MemoryStream y BinaryWriter a la cuna de un archivo WAV, con la cabecera RIFF y fragmentación. No se necesita ningún fragmento de "hecho", esto es muestras de 16 bits a 44100Hz. Así que ahora tengo un MemoryStream con 1000 ms de muestras y envuelto por un BinaryReader.

En un archivo RIFF hay dos longitudes de 4 bytes/32 bits, la longitud "general" que es de 4 bytes en la secuencia (justo después de "RIFF" en ASCII), y una longitud de "datos" justo antes los bytes de datos de muestra. Mi estrategia era buscar en la transmisión y usar BinaryWriter para alterar las dos longitudes para engañar al SoundPlayer y hacerle pensar que la transmisión de audio es de la duración/duración que quiero, y luego reproducirla(). La próxima vez, la duración es diferente, así que una vez más, sobrescriba las longitudes en el MemoryStream con BinaryWriter, Flush() y, una vez más, llame a Play().

Cuando probé esto, no pude obtener el SoundPlayer para ver los cambios en la secuencia, incluso si configuré su propiedad Stream. Me vi obligado a crear un nuevo SoundPlayer ... ¿cada 40 milisegundos? No.

Bien, quiero volver a ese código hoy y comencé a buscar a los miembros de SoundPlayer. Vi "SoundLocation" y lo leí. Allí dijo que un efecto secundario de establecer SoundLocation sería anular la propiedad Stream, y viceversa para Stream. Así que agregué una línea de código para establecer la propiedad SOUNDLocation en algo falso, "x", luego establecí la propiedad Stream en mi (recién modificado) MemoryStream. Maldita sea si no entendía y tocaba un tono con precisión todo el tiempo que yo le pedía. No parece haber ningún efecto secundario loco como el tiempo muerto después o el aumento de la memoria, o ??? Toma 1-2 milisegundos para hacer ese ajuste de la corriente WAV y luego cargar/iniciar el reproductor, pero es muy pequeño y el precio es correcto.

También implementé una propiedad Frequency que vuelve a generar las muestras y utiliza el truco Seek/BinaryWriter para superponer los datos antiguos en RIFF/WAV MemoryStream con el mismo número de muestras pero para una frecuencia diferente, y nuevamente Lo mismo para una propiedad de amplitud.

This project is on SourceForge. Puede obtener el código C# para este truco en SPTones.CS desde this page in the SVN browser. Gracias a todos los que proporcionaron información sobre esto, incluido @arke, cuyo pensamiento era muy cercano al mío. Lo aprecio

+1

+1 para regresar y describir lo que hizo, aunque no necesite más ayuda. Puede marcar su propia respuesta como la correcta en stackoverflow. Si tu quieres. – CaptainCasey

+0

Casey - Lo haré mañana ... StackOverflow dice que tienes que esperar 2 días antes de poder marcar tu propia respuesta para que tu respuesta sea la "correcta" ... no sorprendente :-) –

1

Pruebe usar XNA.

Deberá proporcionar un archivo, o una secuencia a un tono estático, que pueda repetir. Luego puede cambiar el tono y el volumen de ese tono.

Dado que XNA está hecho para juegos, no tendrá ningún problema con retrasos de 40 ms.

+0

¿Entonces XNA se puede usar desde C#? No lo sabía. ¿Hay alguna biblioteca grande que mis usuarios necesiten instalar para que funcione? Voy a mirar mientras espero tu respuesta. ¡¡Gracias!! –

+0

Sí, sí, sí! http: //weblogs.asp.net/scottgu/archive/2006/12/19/building-killer-games-using-net-and-xna-game-studio-express.aspx –

+0

http://msdn.microsoft.com/en-us/library/ microsoft.xna.framework.audio.aspx – Svisstack

3

Lo mejor es generar las ondas sinusoidales y silenciarlas juntas en un búfer al que toque. Es decir, siempre juegue algo, pero escriba lo que necesite al lado de ese búfer.

Conoce la frecuencia de muestreo, y dado el porcentaje de muestreo, puede calcular la cantidad de muestras que necesita para escribir.

uint numSamples = timeWantedInSeconds * sampleRate; 

Esa es la cantidad de muestras que necesita para generar una onda sinusoidal o silencio, cualquiera que sea. Luego simplemente llena el buffer según sea necesario. De esta forma, obtienes el momento más preciso posible.

+0

Una vez más, arke - ¡gracias! Esta puede ser la manera "correcta" de ir. Sin embargo, será complejo porque el Código Morse estadounidense tiene "matices" en el momento. Ya tengo todo esto resuelto en código que proporciona una serie de valores de milisegundos para la "marca" y el "espacio". Es por eso que estoy buscando una manera de producir tonos arbitrarios y longitudes de espacio. Eso es lo que estoy haciendo con Managed DirectX ahora y es prácticamente perfecto. –

+0

Estoy viendo ASIO.NET en este momento http://www.codeproject.com/KB/audio-video/Asio_Net.aspx –

1

Debería ser bastante fácil convertir de ManagedDX a SlimDX ...

Edit: ¿Qué te detiene, por cierto, simplemente pre-generar 'n' muestras de onda sinusoidal? (Donde n es el más cercano a la cantidad de milisegundos que desea). Realmente no lleva tanto tiempo generar los datos. Además de eso, si tienes un buffer de 22Khz y quieres las 100 muestras finales, ¿por qué no envías 'buffer + 21950' y configuras la longitud del buffer en 100 muestras?

+0

Eso es lo que básicamente estoy haciendo, simplemente estableciendo la posición de inicio de secondBuffer dentro de 'n' milisegundos del final, luego jugando. Esto funciona muy bien en Managed DirectX. Estoy viendo ASIO en este momento. Con ASIO4ALL luce muy ligero y utiliza el dispositivo WDM para la salida. –

+0

Ah, y miré a SlimDX y puedo ver de nuevo. Necesita demasiado pegamento, y es un producto comercial que tiene una licencia gratuita. No necesito todo lo de dibujo/3D. Pero no le di un buen apretón. Hay un límite en el tiempo que puedo gastar en esto, y los límites de redistribución en SlimDX parecen una desventaja. –

Cuestiones relacionadas