У меня есть простая программа, которая создает синусоидальную волну одного цикла и помещает числа с плавающей точкой в буфер. Затем это экспортируется в текстовый файл.
Но я хочу иметь возможность экспортировать его в файл WAV (24 бит). Есть ли простой способ сделать это, как в текстовом файле?
Вот код, который у меня есть:
#include <iostream>
#include <fstream>
#include <cmath>
using namespace std;
int main ()
{
long double pi = 3.14159265359; // Declaration of PI
ofstream textfile; // Text object
textfile.open("sine.txt"); // Creating the txt
double samplerate = 44100.00; // Sample rate
double frequency = 200.00; // Frequency
int bufferSize = (1/frequency)*samplerate; // Buffer size
double buffer[bufferSize]; // Buffer
for (int i = 0; i <= (1/frequency)*samplerate; ++i) // Single cycle
{
buffer[i] = sin(frequency * (2 * pi) * i / samplerate); // Putting into buffer the float values
textfile << buffer[i] << endl; // Exporting to txt
}
textfile.close(); // Closing the txt
return 0; // Success
}
Сначала нужно открыть поток для двоичного файла.
ofstream stream;
stream.open("sine.wav", ios::out | ios::binary);
Далее вам нужно написать волновой заголовок. Вы можете искать, чтобы найти подробную информацию о формате файла волны. Важными битами являются частота дискретизации, битовая глубина и длина данных.
int bufferSize = (1/frequency)*samplerate;
stream.write("RIFF", 4); // RIFF chunk
write<int>(stream, 36 + bufferSize*sizeof(int)); // RIFF chunk size in bytes
stream.write("WAVE", 4); // WAVE chunk
stream.write("fmt ", 4); // fmt chunk
write32(stream, 16); // size of fmt chunk
write16(stream, 1); // Format = PCM
write16(stream, 1); // # of Channels
write32(stream, samplerate); // Sample Rate
write32(stream, samplerate*sizeof(int)); // Byte rate
write16(stream, sizeof(int)); // Frame size
write16(stream, 24); // Bits per sample
stream.write("data", 4); // data chunk
write32(stream, bufferSize*sizeof(int)); // data chunk size in bytes
Теперь, когда заголовок отсутствует, вам просто нужно изменить цикл, чтобы сначала преобразовать двойные (-1.0,1.0) сэмплы в 32-битный со знаком int. Обрежьте нижние 8 бит, так как вам нужно только 24 бита, а затем запишите данные. Как вы знаете, обычной практикой является хранение 24-битных сэмплов внутри 32-битного слова, потому что намного проще переходить от нативных типов.
for (int i = 0; i < bufferSize; ++i) // Single cycle
{
double tmp = sin(frequency * (2 * pi) * i / samplerate);
int intVal = (int)(tmp * 2147483647.0) & 0xffffff00;
stream << intVal;
}
Пара других вещей:
1) Я не знаю, как вы не переполняли buffer
используя <=
в вашей петле. Я изменил это на <
,
2) Опять же по поводу размера буфера. Я не уверен, что вы знаете, но вы не можете иметь повторную форму волны, представленную одним циклом для всех частот. Я имею в виду, что для большинства частот, если вы используете этот код и ожидаете повторения сигнала, вы будете слышать сбой на каждом цикле. Он будет работать на хороших синхронных частотах, таких как 1 кГц, потому что в каждом такте будет ровно 48 выборок, и он придет примерно к той же фазе. 999,9 Гц будет другой историей.