2010-09-18 7 views
11

Wer kennt eine sinnvolle Möglichkeit, eine ARBITRARY-Klangwelle in C# zu erstellen und sie über die Lautsprecher wiederzugeben?Wirklich niedrige Klangerzeugung in C#?

Dieses Problem ist seit Jahren immer wieder da, ich gebe es immer wieder nach einer Menge Fehler auf, ohne eine Lösung zu finden.

Was ich tun möchte, ist wie ein Reverse-Visualizer, das heißt, ich will keine "Zahlen" aus dem Sound generieren, ich möchte Sound aus Zahlen erzeugen.

Wie bekomme ich eine Funktion, die ich mit Sampling-Rate, Sample-Größe und die Sound-Daten (ein Array von ganzen Zahlen zum Beispiel), und es würde die entsprechende WAV-Datei daraus erzeugen (Echtzeit-Sound-Wiedergabe wäre ideal) , aber ich wäre auch mehr als zufrieden damit).

Ich weiß, dass die wav Dateispezifikationen über das Interweb sind, und machte mehrere Versuche, die obige Funktion zu erstellen, hatte etwas Erfolg für niedrige Frequenzen, aber sobald ich anfing, mit Bits pro Probe usw. zu spielen ... wird es ein RIESIGES , unkontrollierbares Durcheinander.

Ist dies noch nicht in irgendeiner Weise geschehen? Ich hätte nichts dagegen, was es verwendet, solange es ein .NET verwaltet Wrapper dafür (und ich kann es von der letzten VS zu Zeit zugreifen). XNA unterstützt auf diese Weise kein Low-Level-Audio. Habe auch einige Beispiele gefunden, die behaupten, etwas Ähnliches zu erreichen, aber sie funktionieren entweder gar nicht oder machen etwas völlig anderes.

Vielen Dank.

Antwort

8

sah interessant, so habe ich eine einfache Anwendung, die oben geklopft:

  • Erzeugt die Proben für zwei Sekunden eines reinen Ton (440Hz A).
  • Konvertiert sie in ein Byte-Array im WAV-Dateiformat.
  • Spielt den Sound ab, indem das Byte-Array an die PlaySound-API übergeben wird.
  • Enthält auch Code zum Speichern der WAV-Daten in einer WAV-Datei.

Sie können die Abtastrate, die Tonfrequenz und die Dauer der Probe ganz einfach ändern. Der Code ist sehr hässlich und platz-ineffizient, aber es funktioniert. Hier finden Sie eine vollständige Befehlszeilen App:

 
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

Das sieht wirklich toll aus, und ich schäme mich wirklich, habe aber noch nicht die Zeit, wirklich damit zu spielen. Nur eine Frage: Ist es einfach, 4 Bytes pro Sample zu machen? – jssyjrm

+0

Sie können es 4 Bytes pro Beispiel machen, aber ich weiß nicht, ob Windows es spielen wird. Es könnte, ich weiß es einfach nicht. Wie auch immer, wenn Sie dies tun wollen, ändern Sie alle Verweise auf sizeof (short) auf sizeof (int), ändern Sie den Probentyp in int, ändern Sie den Skalierungsfaktor (short.MaxValue) in int.MaxValue und beheben Sie die Schleife, die das Bytearray füllt, um vier Bytes pro Beispiel hinzuzufügen. Aber ich wäre überrascht, wenn Sie einen Unterschied hören könnten. – arx

+0

Vielen Dank dafür. Wie kann ich die Funktion "Stop" (und möglicherweise "Pause") hier hinzufügen? Ich nehme an, ich würde einen Hintergrundarbeiter benötigen, damit der Rest der GUI für die Eingabe frei ist. Welche Art von Code würde ein "Stop-Sound" aussehen? –

2

FMOD kann Beispielladungen aus dem Speicher ausführen und verfügt über einen C# -Wrapper. Diese

+0

Okay, hatte gerade eine Menge Dinge kommen so nicht konnte experimentieren viel noch tut mir leid. FMOD kann es definitiv, aber es hat einen schrecklichen automatisch generierten verwalteten Wrapper. Es gibt ein spezielles Beispiel dafür, das mit bestimmten Einstellungen zu tun, aber es ist ein Schmerz, diese Einstellungen zu ändern, und es zwingt Entwickler, überall unsicheren Code zu verwenden. Danke, dass du es dir ausgesucht hast, wenn ich mehr Zeit habe, werde ich sie fragen, warum ich nicht mehr als 2 Bytes pro Beispieleinstellungen verwenden kann. – jssyjrm

2

How to play from an array unter

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