2010-09-18 18 views
11

¿Alguien sabe de una manera sensata de crear una onda de sonido ARBITRARIO en C# y reproducirla desde los altavoces?Generación de sonido real de bajo nivel en C#?

Este problema ha venido apareciendo de vez en cuando durante años, siempre termino renunciando después de un montón de fallas sin encontrar una solución.

Lo que quiero hacer es como un visualizador inverso, es decir, no quiero generar "números" a partir del sonido, quiero generar sonido a partir de los números.

Como obtener una función que proporciono con frecuencia de muestreo, tamaño de muestra y datos de sonido (una matriz de enteros, por ejemplo), y generaría el archivo wav apropiado (la reproducción de sonido en tiempo real sería ideal , pero estaría más que satisfecho con esto también).

Sé que las especificaciones del archivo wav están en todo el interweb, e hice varios intentos para crear la función anterior, tuve cierto éxito para las frecuencias bajas, pero una vez que comencé a jugar con bits por muestra, etc. se convirtió en ENORME , desastre incontrolable.

¿Esto ya no está hecho de ninguna manera? No me importaría lo que use, siempre que haya un contenedor administrado .NET para él (y puedo acceder desde el VS más reciente al tiempo). XNA no admite audio de bajo nivel de esta manera. También se encontraron varios ejemplos que afirman lograr algo similar, pero o bien no funcionan en absoluto, o hacen algo completamente diferente.

Gracias.

Respuesta

8

Esto parecía interesante así que he dejó embarazada a una aplicación sencilla que:

  • crea las muestras durante dos segundos de un tono puro (440Hz A).
  • Convierte en una matriz de bytes en formato de archivo WAV.
  • Reproduce el sonido pasando la matriz de bytes a la API de PlaySound.
  • También incluye código para guardar los datos WAV en un archivo WAV.

Puede cambiar fácilmente la frecuencia de muestreo, la frecuencia de tono y la duración de la muestra. El código es muy feo e ineficiente en el espacio, pero funciona. La siguiente es una aplicación completa de línea de comandos:

 
using System; 
using System.Diagnostics; 
using System.IO; 
using System.Runtime.InteropServices; 

namespace playwav 
{ 
    class Program 
    { 
     [DllImport("winmm.dll", EntryPoint = "PlaySound", SetLastError = true)] 
     private extern static int PlaySound(byte[] wavData, IntPtr hModule, PlaySoundFlags flags); 

     //#define SND_SYNC   0x0000 /* play synchronously (default) */ 
     //#define SND_ASYNC   0x0001 /* play asynchronously */ 
     //#define SND_NODEFAULT  0x0002 /* silence (!default) if sound not found */ 
     //#define SND_MEMORY   0x0004 /* pszSound points to a memory file */ 
     //#define SND_LOOP   0x0008 /* loop the sound until next sndPlaySound */ 
     //#define SND_NOSTOP   0x0010 /* don't stop any currently playing sound */ 

     //#define SND_NOWAIT  0x00002000L /* don't wait if the driver is busy */ 
     //#define SND_ALIAS  0x00010000L /* name is a registry alias */ 
     //#define SND_ALIAS_ID 0x00110000L /* alias is a predefined ID */ 
     //#define SND_FILENAME 0x00020000L /* name is file name */ 
     //#define SND_RESOURCE 0x00040004L /* name is resource name or atom */ 

     enum PlaySoundFlags 
     { 
      SND_SYNC = 0x0000, 
      SND_ASYNC = 0x0001, 
      SND_MEMORY = 0x0004 
     } 

     // Play a wav file appearing in a byte array 
     static void PlayWav(byte[] wav) 
     { 
      PlaySound(wav, System.IntPtr.Zero, PlaySoundFlags.SND_MEMORY | PlaySoundFlags.SND_SYNC); 
     } 

     static byte[] ConvertSamplesToWavFileFormat(short[] left, short[] right, int sampleRate) 
     { 
      Debug.Assert(left.Length == right.Length); 

      const int channelCount = 2; 
      int sampleSize = sizeof(short) * channelCount * left.Length; 
      int totalSize = 12 + 24 + 8 + sampleSize; 

      byte[] wav = new byte[totalSize]; 
      int b = 0; 

      // RIFF header 
      wav[b++] = (byte)'R'; 
      wav[b++] = (byte)'I'; 
      wav[b++] = (byte)'F'; 
      wav[b++] = (byte)'F'; 
      int chunkSize = totalSize - 8; 
      wav[b++] = (byte)(chunkSize & 0xff); 
      wav[b++] = (byte)((chunkSize >> 8) & 0xff); 
      wav[b++] = (byte)((chunkSize >> 16) & 0xff); 
      wav[b++] = (byte)((chunkSize >> 24) & 0xff); 
      wav[b++] = (byte)'W'; 
      wav[b++] = (byte)'A'; 
      wav[b++] = (byte)'V'; 
      wav[b++] = (byte)'E'; 

      // Format header 
      wav[b++] = (byte)'f'; 
      wav[b++] = (byte)'m'; 
      wav[b++] = (byte)'t'; 
      wav[b++] = (byte)' '; 
      wav[b++] = 16; 
      wav[b++] = 0; 
      wav[b++] = 0; 
      wav[b++] = 0; // Chunk size 
      wav[b++] = 1; 
      wav[b++] = 0; // Compression code 
      wav[b++] = channelCount; 
      wav[b++] = 0; // Number of channels 
      wav[b++] = (byte)(sampleRate & 0xff); 
      wav[b++] = (byte)((sampleRate >> 8) & 0xff); 
      wav[b++] = (byte)((sampleRate >> 16) & 0xff); 
      wav[b++] = (byte)((sampleRate >> 24) & 0xff); 
      int byteRate = sampleRate * channelCount * sizeof(short); // byte rate for all channels 
      wav[b++] = (byte)(byteRate & 0xff); 
      wav[b++] = (byte)((byteRate >> 8) & 0xff); 
      wav[b++] = (byte)((byteRate >> 16) & 0xff); 
      wav[b++] = (byte)((byteRate >> 24) & 0xff); 
      wav[b++] = channelCount * sizeof(short); 
      wav[b++] = 0; // Block align (bytes per sample) 
      wav[b++] = sizeof(short) * 8; 
      wav[b++] = 0; // Bits per sample 

      // Data chunk header 
      wav[b++] = (byte)'d'; 
      wav[b++] = (byte)'a'; 
      wav[b++] = (byte)'t'; 
      wav[b++] = (byte)'a'; 
      wav[b++] = (byte)(sampleSize & 0xff); 
      wav[b++] = (byte)((sampleSize >> 8) & 0xff); 
      wav[b++] = (byte)((sampleSize >> 16) & 0xff); 
      wav[b++] = (byte)((sampleSize >> 24) & 0xff); 

      Debug.Assert(b == 44); 

      for (int s = 0; s != left.Length; ++s) 
      { 
       wav[b++] = (byte)(left[s] & 0xff); 
       wav[b++] = (byte)(((ushort)left[s] >> 8) & 0xff); 
       wav[b++] = (byte)(right[s] & 0xff); 
       wav[b++] = (byte)(((ushort)right[s] >> 8) & 0xff); 
      } 

      Debug.Assert(b == totalSize); 

      return wav; 
     } 

     // Create a simple sine wave 
     static void CreateSamples(out short[] left, out short[] right, int sampleRate) 
     { 
      const double middleC = 261.626; 
      const double standardA = 440; 

      const double frequency = standardA; 

      int count = sampleRate * 2; // Two seconds 
      left = new short[count]; 
      right = new short[count]; 

      for (int i = 0; i != count; ++i) 
      { 
       double t = (double)i/sampleRate; // Time of this sample in seconds 
       short s = (short)Math.Floor(Math.Sin(t * 2 * Math.PI * frequency) * short.MaxValue); 
       left[i] = s; 
       right[i] = s; 
      } 
     } 

     static void Main(string[] args) 
     { 
      short[] left; 
      short[] right; 
      int sampleRate = 44100; 
      CreateSamples(out left, out right, sampleRate); 
      byte[] wav = ConvertSamplesToWavFileFormat(left, right, sampleRate); 
      PlayWav(wav); 

      /* 
      // Write the data to a wav file 
      using (FileStream fs = new FileStream(@"C:\documents and settings\carlos\desktop\a440stereo.wav", FileMode.Create)) 
      { 
       fs.Write(wav, 0, wav.Length); 
      } 
      */ 
     } 
    } 
} 
+0

Esto se ve realmente increíble, y me siento realmente avergonzado, pero aún no tuve tiempo de jugar realmente con eso. Solo una pregunta: ¿es fácil hacerlo de 4 bytes por muestra? – jssyjrm

+0

Puede convertirlo en 4 bytes por muestra, pero no sé si Windows lo reproducirá. Es posible, simplemente no lo sé. De todos modos, si desea hacer esto, cambie todas las referencias a sizeof (short) to sizeof (int), cambie el tipo de muestra a int, cambie el factor de escala (short.MaxValue) a int.MaxValue y corrige el bucle que llena la matriz de bytes para agregar cuatro bytes por muestra. Pero me sorprendería si puedes escuchar la diferencia. – arx

+0

Muchas gracias por esto. ¿Cómo puedo agregar funcionalidad stop (y quizás pausa) aquí? Supongo que necesitaría un trabajador de segundo plano para que el resto de la GUI sea gratuito. ¿A qué tipo de código se parecería un "sonido de parada"? –

2

FMOD puede hacer muestreos de cargas desde la memoria y tiene un contenedor C#.

+0

bien, sólo tenía un montón de cosas que vienen y no se podía experimentar mucho todavía lo siento. FMOD definitivamente puede hacerlo, pero tiene un envoltorio administrado autogenerado terrible. Hay un ejemplo específico de hacer esto con ciertas configuraciones, pero es un dolor cambiar esas configuraciones y obliga a los desarrolladores a usar código inseguro en todas partes. Gracias por señalarlo, cuando tenga más tiempo les preguntaré por qué no puedo usar más de 2 bytes por configuración de muestra. – jssyjrm

2

How to play from an array continuación

PlayerEx pl = new PlayerEx(); 

    private static void PlayArray(PlayerEx pl) 
    { 
     double fs = 8000; // sample freq 
     double freq = 1000; // desired tone 
     short[] mySound = new short[4000]; 
     for (int i = 0; i < 4000; i++) 
     { 
      double t = (double)i/fs; // current time 
      mySound[i] = (short)(Math.Cos(t * freq) * (short.MaxValue)); 
     } 
     IntPtr format = AudioCompressionManager.GetPcmFormat(1, 16, (int)fs); 
     pl.OpenPlayer(format); 
     byte[] mySoundByte = new byte[mySound.Length * 2]; 
     Buffer.BlockCopy(mySound, 0, mySoundByte, 0, mySoundByte.Length); 
     pl.AddData(mySoundByte); 
     pl.StartPlay(); 
    } 
Cuestiones relacionadas