J'ai besoin de mettre tous les échantillons d'un fichier wav dans un tableau (ou deux si vous avez besoin de le faire pour conserver la chaîne stéréo) afin de pouvoir leur appliquer des modifications. Je me demandais si cela se faisait facilement (de préférence sans bibliothèques externes). Je n'ai aucune expérience de la lecture de fichiers audio, donc je ne connais pas grand-chose sur le sujet.
Les fichiers WAV (au moins, ceux non compressés) sont assez simples. Il y a un en-tête, puis les données le suivent.
Voici une excellente référence: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ ( miroir )
Ce code devrait faire l'affaire. Il convertit un fichier wave en un double tableau normalisé (-1 à 1), mais il devrait être trivial d'en faire un tableau int/short à la place (supprimez le /32768.0
bit et ajoutez 32768 à la place). Le right[]
array sera défini sur null si le fichier wav chargé se trouve être mono.
Je ne peux pas prétendre que c'est complètement à l'épreuve des balles (erreurs potentielles de coup par coup), mais après avoir créé un tableau d'échantillons 65536 et créé une onde de -1 à 1, aucun des échantillons ne semble traverser le plafond ou sol.
// convert two bytes to one double in the range -1 to 1
static double bytesToDouble(byte firstByte, byte secondByte) {
// convert two bytes to one short (little endian)
short s = (secondByte << 8) | firstByte;
// convert to range from -1 to (just below) 1
return s / 32768.0;
}
// Returns left and right double arrays. 'right' will be null if sound is mono.
public void openWav(string filename, out double[] left, out double[] right)
{
byte[] wav = File.ReadAllBytes(filename);
// Determine if mono or stereo
int channels = wav[22]; // Forget byte 23 as 99.999% of WAVs are 1 or 2 channels
// Get past all the other sub chunks to get to the data subchunk:
int pos = 12; // First Subchunk ID from 12 to 16
// Keep iterating until we find the data chunk (i.e. 64 61 74 61 ...... (i.e. 100 97 116 97 in decimal))
while(!(wav[pos]==100 && wav[pos+1]==97 && wav[pos+2]==116 && wav[pos+3]==97)) {
pos += 4;
int chunkSize = wav[pos] + wav[pos + 1] * 256 + wav[pos + 2] * 65536 + wav[pos + 3] * 16777216;
pos += 4 + chunkSize;
}
pos += 8;
// Pos is now positioned to start of actual sound data.
int samples = (wav.Length - pos)/2; // 2 bytes per sample (16 bit sound mono)
if (channels == 2) samples /= 2; // 4 bytes per sample (16 bit stereo)
// Allocate memory (right will be null if only mono sound)
left = new double[samples];
if (channels == 2) right = new double[samples];
else right = null;
// Write to double array/s:
int i=0;
while (pos < length) {
left[i] = bytesToDouble(wav[pos], wav[pos + 1]);
pos += 2;
if (channels == 2) {
right[i] = bytesToDouble(wav[pos], wav[pos + 1]);
pos += 2;
}
i++;
}
}
En supposant que votre fichier WAV contient PCM 16 bits (ce qui est le plus courant), vous pouvez utiliser NAudio pour le lire dans un tableau d'octets, puis le copier dans un tableau d'entiers 16 bits pour plus de commodité. S'il est stéréo, les échantillons seront entrelacés à gauche, à droite.
using (WaveFileReader reader = new WaveFileReader("myfile.wav"))
{
Assert.AreEqual(16, reader.WaveFormat.BitsPerSample, "Only works with 16 bit audio");
byte[] buffer = new byte[reader.Length];
int read = reader.Read(buffer, 0, buffer.Length);
short[] sampleBuffer = new short[read / 2];
Buffer.BlockCopy(buffer, 0, sampleBuffer, 0, read);
}
Je sais que vous vouliez éviter les bibliothèques tierces, mais si vous voulez être sûr de faire face aux fichiers WAV avec des morceaux supplémentaires, je suggère d'éviter les approches comme la simple recherche de 44 octets dans le fichier.
Au moment de l'écriture, personne n'a adressé de WAV codés en 32 ou 64 bits.
Le code suivant gère les bits 16/32/64 et mono/stéréo:
static bool readWav( string filename, out float[] L, out float[] R )
{
L = R = null;
//float [] left = new float[1];
//float [] right;
try {
using (FileStream fs = File.Open(filename,FileMode.Open))
{
BinaryReader reader = new BinaryReader(fs);
// chunk 0
int chunkID = reader.ReadInt32();
int fileSize = reader.ReadInt32();
int riffType = reader.ReadInt32();
// chunk 1
int fmtID = reader.ReadInt32();
int fmtSize = reader.ReadInt32(); // bytes for this chunk
int fmtCode = reader.ReadInt16();
int channels = reader.ReadInt16();
int sampleRate = reader.ReadInt32();
int byteRate = reader.ReadInt32();
int fmtBlockAlign = reader.ReadInt16();
int bitDepth = reader.ReadInt16();
if (fmtSize == 18)
{
// Read any extra values
int fmtExtraSize = reader.ReadInt16();
reader.ReadBytes(fmtExtraSize);
}
// chunk 2
int dataID = reader.ReadInt32();
int bytes = reader.ReadInt32();
// DATA!
byte[] byteArray = reader.ReadBytes(bytes);
int bytesForSamp = bitDepth/8;
int samps = bytes / bytesForSamp;
float[] asFloat = null;
switch( bitDepth ) {
case 64:
double[]
asDouble = new double[samps];
Buffer.BlockCopy(byteArray, 0, asDouble, 0, bytes);
asFloat = Array.ConvertAll( asDouble, e => (float)e );
break;
case 32:
asFloat = new float[samps];
Buffer.BlockCopy(byteArray, 0, asFloat, 0, bytes);
break;
case 16:
Int16 []
asInt16 = new Int16[samps];
Buffer.BlockCopy(byteArray, 0, asInt16, 0, bytes);
asFloat = Array.ConvertAll( asInt16, e => e / (float)Int16.MaxValue );
break;
default:
return false;
}
switch( channels ) {
case 1:
L = asFloat;
R = null;
return true;
case 2:
L = new float[samps];
R = new float[samps];
for( int i=0, s=0; i<samps; i++ ) {
L[i] = asFloat[s++];
R[i] = asFloat[s++];
}
return true;
default:
return false;
}
}
}
catch {
Debug.Log( "...Failed to load note: " + filename );
return false;
//left = new float[ 1 ]{ 0f };
}
return false;
}
http://hourlyapps.blogspot.com/2008/07/open-source-wave-graph-c-net-control.html
Voici un contrôle qui affiche le spectre d'un fichier Wav, qui sert également un octet [] de fichier Wav décodé où vous pouvez jouer et/ou modifier leurs valeurs.
Téléchargez simplement le contrôle et c'est assez bon pour la manipulation de fichiers WAV.
Pour obtenir le fichier wav dans un tableau, vous pouvez simplement faire ceci:
byte [] data = File.ReadAllBytes ("FilePath");
mais comme Fletch l'a dit, vous devez isoler les données des en-têtes. Ce devrait être juste un simple décalage.
Essayez Lire les données audio à partir du tablea
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();
}