2010-09-18 28 views
11

Quelqu'un sait-il comment créer une onde sonore ARBITRARY en C# et la lire depuis les haut-parleurs?Génération réelle de sons de bas niveau en C#?

Ce problème est survenu de temps en temps depuis des années, je finis toujours par l'abandonner après beaucoup d'échecs sans trouver de solution. Ce que je veux faire est comme un reverse-visualizer, c'est-à-dire que je ne veux pas générer de "nombres" à partir du son, je veux générer du son à partir de nombres. Comme obtenir une fonction que je fournis avec la fréquence d'échantillonnage, la taille de l'échantillon, et les données sonores (un tableau d'entiers par exemple), et cela génèrerait le fichier wav approprié (la lecture du son en temps réel serait idéale mais je serais plus que content de ça aussi). Je sais que les spécifications du fichier wav sont partout dans l'interweb, et j'ai fait plusieurs tentatives en créant la fonction ci-dessus, j'ai eu du succès pour les basses fréquences, mais une fois que je commence à jouer avec des bits par échantillon, etc. , désordre incontrôlable.

N'est-ce pas déjà fait? Cela ne me dérangerait pas ce qu'il utilise, tant qu'il y a un wrapper géré par .NET pour cela (et je peux y accéder du VS le plus récent à l'heure). XNA ne prend pas en charge l'audio de bas niveau de cette façon. J'ai également trouvé plusieurs exemples qui prétendent réaliser quelque chose de similaire, mais soit ils ne fonctionnent pas du tout, soit ils font quelque chose de complètement différent.

Merci.

Répondre

8

Ce semblé intéressant donc j'ai frappé une application simple:

  • Crée les échantillons pendant deux secondes d'un ton pur (440Hz A).
  • Les convertit en un tableau d'octets au format de fichier WAV.
  • Lit le son en transmettant le tableau d'octets à l'API PlaySound.
  • Comprend également le code pour enregistrer les données WAV dans un fichier WAV.

Vous pouvez facilement modifier la fréquence d'échantillonnage, la fréquence de tonalité et la durée de l'échantillon. Le code est très moche et inefficace mais ça marche. Ce qui suit est une application en ligne de commande complète:

 
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

Cela a l'air vraiment génial, et j'ai vraiment honte mais je n'ai pas encore eu le temps de vraiment jouer avec. Juste une question: Est-il facile de faire 4 octets par échantillon? – jssyjrm

+0

Vous pouvez faire 4 octets par échantillon mais je ne sais pas si Windows va le lire. Ça pourrait, je ne sais pas. Quoi qu'il en soit, si vous voulez faire cela, changez toutes les références à sizeof (short) en sizeof (int), changez le type d'échantillon en int, changez le facteur d'échelle (short.MaxValue) en int.MaxValue et corrige la boucle qui remplit le tableau d'octets pour ajouter quatre octets par échantillon. Mais je serais surpris si vous pouvez entendre une différence. – arx

+0

Merci beaucoup pour cela. Comment puis-je ajouter une fonctionnalité d'arrêt (et peut-être une pause) ici? Je suppose que j'ai besoin d'un travailleur de fond afin que le reste de l'interface graphique est libre pour la saisie. Quel genre de code ressemblerait un «son d'arrêt»? –

2

FMOD peut effectuer des échantillons de charges à partir de la mémoire et dispose d'un wrapper C#.

+0

Ok, juste eu beaucoup de choses à venir ne pouvait donc pas expérimenter beaucoup mais je suis désolé. FMOD peut certainement le faire, mais il a un wrapper géré généré automatiquement terrible. Il y a un exemple spécifique de faire cela avec certains paramètres, mais c'est difficile de changer ces paramètres et cela force les développeurs à utiliser du code dangereux partout. Merci de l'avoir fait remarquer, quand j'aurai plus de temps je leur demanderai pourquoi je ne pourrais pas utiliser plus de 2 octets par paramètres d'échantillon. – jssyjrm

2

How to play from an array ci-dessous

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