Реальный низкий уровень генерации звука в C#?
Кто-нибудь знает разумный способ создать звуковую волну ARBITRARY в C# и воспроизводить ее из динамиков?
Эта проблема возвращалась время от времени в течение многих лет, и я всегда заканчивал тем, что отказывался от нее после большого количества неудач, не находя решения.
То, что я хочу сделать, это как обратный визуализатор, то есть я не хочу генерировать "цифры" из звука, я хочу генерировать звук из чисел.
Как получить функцию, которую я предоставляю с частотой дискретизации, размером выборки и звуковыми данными (например, массив целых чисел), и он будет генерировать соответствующий файл WAV из него (воспроизведение звука в реальном времени было бы идеально, но я буду более чем доволен этим тоже).
Я знаю, что спецификации файлов wav распространены по всему веб-сайту, и я сделал несколько попыток создания вышеупомянутой функции, что имело некоторый успех для низких частот, но как только я начинаю связываться с битами на семпл и т. Д.... это становится ОГРОМНЫМ, неуправляемым беспорядком.
Разве это еще не сделано в любом случае? Я не возражаю против того, что он использует, если для него есть управляемая оболочка.NET (и я могу получить к ней доступ из самой последней версии VS). Таким образом, XNA не поддерживает низкоуровневое аудио. Также найдено несколько примеров, которые претендуют на достижение чего-то похожего, но они либо не работают вообще, либо делают что-то совершенно другое.
Спасибо.
3 ответа
Это выглядело интересно, поэтому я запустил простое приложение, которое:
- Создает сэмплы в течение двух секунд чистого тона (440 Гц A).
- Преобразует их в байтовый массив в формате файла WAV.
- Воспроизведение звука путем передачи байтового массива в API PlaySound.
- Также включает в себя код для сохранения данных WAV в файл WAV.
Вы можете легко изменить частоту дискретизации, частоту тона и продолжительность выборки. Код очень уродливый и неэффективный, но работает. Ниже приведено полное приложение командной строки:
используя Систему; использование System.Diagnostics; используя 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); //#define SND_SYNC 0x0000 /* играть синхронно (по умолчанию) */ //#define SND_ASYNC 0x0001 /* играть асинхронно * / // # определить SND_NODEFAULT 0x0002 /* тишина (! по умолчанию), если звук не найден * / // # определить SND_MEMORY 0x0004 /* pszSound указывает на файл памяти */ //#define SND_LOOP 0x0008 /* зациклить звук до следующего sndPlaySound */ //#define SND_NOSTOP 0x0010 /* не останавливать любой воспроизводимый в данный момент звук */ //#define SND_NOWAIT 0x00002000L /* не ждите, если драйвер занят */ //#define SND_ALIAS 0x00010000L /* имя - псевдоним реестра */ //#define SND_ALIAS_ID 0x00110000L /* псевдоним - предопределенный идентификатор */ //#define SND_FILENAME 0x00020000L /* имя - это имя файла */ //#define SND_RESOURCE 0x00040004L /* имя - это имя ресурса или атом */ enum PlaySoundFlags { SND_SYNC = 0x0000, SND_ASYNC = 0x0001, SND_MEMORY = 0x0004 } // Воспроизведение файла wav с отображением в формате wav статический массив 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 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'; // Формат заголовка 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; // Размер куска wav[b++] = 1; wav[b++] = 0; // Код сжатия wav [b ++] = channelCount; wav [b ++] = 0; // Количество каналов 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); // скорость байтов для всех каналов 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; // Блок выравнивания (байт на выборку) wav[b++] = sizeof(short) * 8; wav[b++] = 0; // Биты на выборку // Заголовок блока данных 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); возврат WAV; } // Создать простую синусоидальную статическую пустоту CreateSamples(out short[] left, out short[] right, int sampleRate) { const double middleC = 261.626; конст двойной стандарт А = 440; постоянная двойная частота = стандартная А; int count = sampleRate * 2; // две секунды осталось = new short [count]; право = новый короткий [количество]; for (int i = 0; i!= count; ++i) { double t = (double)i / sampleRate; // Время этой выборки в секундах, короткое s = (короткое)Math.Floor(Math.Sin(t * 2 * Math.PI * частота) * short.MaxValue); left[i] = s; право [я] = с; } } static void Main(string[] args) { short[] left; короткий [] правый; int sampleRate = 44100; CreateSamples(слева, справа, sampleRate); byte[] wav = ConvertSamplesToWavFileFormat(left, right, sampleRate); PlayWav(WAV); / * // Записать данные в файл wav с помощью (FileStream fs = new FileStream(@"C:\ Documents and settings\carlos\desktop\a440stereo.wav", FileMode.Create)) { fs.Write(wav, 0, wav.Length); } */ } } }
FMOD может выполнять выборочные загрузки из памяти и имеет оболочку C#.
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();
}