2010-09-18 10 views
11

Qualcuno conosce un modo ragionevole per creare un'onda sonora ARBITRARY in C# e riprodurla dagli altoparlanti?Generazione del suono di basso livello in C#?

Questo problema è tornato di tanto in tanto per anni, finisco sempre per rinunciarvi dopo un sacco di insuccessi senza trovare una soluzione.

Quello che voglio fare è come un reverse-visualizer, cioè, non voglio generare "numeri" dal suono, voglio generare il suono dai numeri.

Come ottenere una funzione che fornisco con frequenza di campionamento, dimensione del campione e dati audio (una serie di interi per esempio), e genererebbe il file wav appropriato da esso (la riproduzione del suono in tempo reale sarebbe l'ideale , ma sarei più che contento anche di questo).

So che le specifiche del file wav sono in tutto l'interweb, e ho fatto diversi tentativi di creare la funzione di cui sopra, ha avuto un certo successo per le basse frequenze, ma una volta che inizio a scherzare con bit per campione ecc ... diventa un ENORME , pasticcio incontrollabile.

Questo non è già stato fatto in alcun modo? Non mi dispiacerebbe che usi, a patto che ci sia un wrapper gestito .NET (e posso accedervi dal VS più recente al momento). XNA non supporta l'audio di basso livello in questo modo. Ho anche trovato diversi esempi che pretendono di ottenere qualcosa di simile, ma non funzionano affatto o fanno qualcosa di completamente diverso.

Grazie.

risposta

8

questo sembrava interessante così ho messo incinta una semplice applicazione che:

  • crea i campioni per due secondi di un tono puro (440Hz A).
  • Converte in un array di byte in formato file WAV.
  • Riproduce il suono passando l'array di byte all'API PlaySound.
  • Include anche il codice per salvare i dati WAV in un file WAV.

È possibile modificare facilmente la frequenza di campionamento, la frequenza del tono e la durata del campione. Il codice è molto brutto e poco efficiente nello spazio ma funziona. La seguente è una completa applicazione a riga di comando:

 
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

Sembra davvero fantastico, e mi vergogno davvero ma non ho ancora avuto il tempo di giocarci davvero. Solo una domanda: è facile renderlo 4 byte per campione? – jssyjrm

+0

Puoi renderlo 4 byte per campione ma non so se Windows lo riprodurrà. Potrebbe, io proprio non lo so. Ad ogni modo, se vuoi fare questo cambia tutti i riferimenti a sizeof (breve) a sizeof (int), cambia il tipo di campione in int, cambia il fattore di scala (short.MaxValue) in int.MaxValue e fissa il ciclo che riempie l'array di byte per aggiungere quattro byte per campione. Ma sarei sorpreso se riesci a sentire una differenza. – arx

+0

Grazie mille per questo. Come posso aggiungere qui la funzionalità stop (e forse pausa)? Presumo che avrei bisogno di un assistente in background in modo che il resto della GUI sia libero per l'input. Che tipo di codice sarebbe un 'stop sound'? –

2

FMOD può eseguire il caricamento dei campioni dalla memoria e dispone di un wrapper C#.

+0

Va bene, ha appena avuto un sacco di cose in arrivo così non poteva sperimentare più di tanto ancora mi dispiace. FMOD può sicuramente farlo, ma ha un terribile wrapper gestito generato automaticamente. C'è un esempio specifico di fare questo con determinate impostazioni, ma è difficile cambiare queste impostazioni e costringe gli sviluppatori a usare codice non sicuro ovunque. Grazie per avermelo ricordato, quando avrò più tempo chiederò loro perché non potrei usare più di 2 byte per impostazioni di esempio. – jssyjrm

2

How to play from an array sotto

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(); 
    }