Пример генерации аудио минимального C
Пример ниже генерирует чистый синус 1000 кГц в необработанном формате. При обычной частоте дискретизации 44,1 кГц она будет длиться около 4 секунд.
main.c:
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(void) {
FILE *f;
const double PI2 = 2 * acos(-1.0);
const double SAMPLE_FREQ = 44100;
const unsigned int NSAMPLES = 4 * SAMPLE_FREQ;
uint16_t ampl;
uint8_t bytes[2];
unsigned int t;
f = fopen("out.raw", "wb");
for (t = 0; t < NSAMPLES; ++t) {
ampl = UINT16_MAX * 0.5 * (1.0 + sin(PI2 * t * 1000.0 / SAMPLE_FREQ));
bytes[0] = ampl >> 8;
bytes[1] = ampl & 0xFF;
fwrite(bytes, 2, sizeof(uint8_t), f);
}
fclose(f);
return EXIT_SUCCESS;
}
GitHub upstream .
Генерировать out.raw
:
gcc -std=c99 -o main main.c -lm
./main
Воспроизвести out.raw
напрямую:
sudo apt-get install ffmpeg
ffplay -autoexit -f u16be -ar 44100 -ac 1 out.raw
или конвертируйте в более распространенный аудиоформат, а затем играйте с более распространенным аудиоплеером:
ffmpeg -f u16be -ar 44100 -ac 1 -i out.raw out.flac
vlc out.flac
Параметры поясняются по адресу: https://superuser.com/a/1063230/128124
Проверено на Ubuntu 18.04.
Canon in D in C
Вот более интересный пример синтеза.
Итог: https://www.youtube.com/watch?v=JISozfHATms
main.c
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
typedef uint16_t point_type_t;
double PI2;
void write_ampl(FILE *f, point_type_t ampl) {
uint8_t bytes[2];
bytes[0] = ampl >> 8;
bytes[1] = ampl & 0xFF;
fwrite(bytes, 2, sizeof(uint8_t), f);
}
/* https://en.wikipedia.org/wiki/Piano_key_frequencies */
double piano_freq(unsigned int i) {
return 440.0 * pow(2, (i - 49.0) / 12.0);
}
/* Chord formed by the nth note of the piano. */
point_type_t piano_sum(unsigned int max_ampl, unsigned int time,
double sample_freq, unsigned int nargs, unsigned int *notes) {
unsigned int i;
double sum = 0;
for (i = 0 ; i < nargs; ++i)
sum += sin(PI2 * time * piano_freq(notes[i]) / sample_freq);
return max_ampl * 0.5 * (nargs + sum) / nargs;
}
enum notes {
A0 = 1, AS0, B0,
C1, C1S, D1, D1S, E1, F1, F1S, G1, G1S, A1, A1S, B1,
C2, C2S, D2, D2S, E2, F2, F2S, G2, G2S, A2, A2S, B2,
C3, C3S, D3, D3S, E3, F3, F3S, G3, G3S, A3, A3S, B3,
C4, C4S, D4, D4S, E4, F4, F4S, G4, G4S, A4, A4S, B4,
C5, C5S, D5, D5S, E5, F5, F5S, G5, G5S, A5, A5S, B5,
C6, C6S, D6, D6S, E6, F6, F6S, G6, G6S, A6, A6S, B6,
C7, C7S, D7, D7S, E7, F7, F7S, G7, G7S, A7, A7S, B7,
C8,
};
int main(void) {
FILE *f;
PI2 = 2 * acos(-1.0);
const double SAMPLE_FREQ = 44100;
point_type_t ampl;
point_type_t max_ampl = UINT16_MAX;
unsigned int t, i;
unsigned int samples_per_unit = SAMPLE_FREQ * 0.375;
unsigned int *ip[] = {
(unsigned int[]){4, 2, C3, E4},
(unsigned int[]){4, 2, G3, D4},
(unsigned int[]){4, 2, A3, C4},
(unsigned int[]){4, 2, E3, B3},
(unsigned int[]){4, 2, F3, A3},
(unsigned int[]){4, 2, C3, G3},
(unsigned int[]){4, 2, F3, A3},
(unsigned int[]){4, 2, G3, B3},
(unsigned int[]){4, 3, C3, G4, E5},
(unsigned int[]){4, 3, G3, B4, D5},
(unsigned int[]){4, 2, A3, C5},
(unsigned int[]){4, 3, E3, G4, B4},
(unsigned int[]){4, 3, F3, C4, A4},
(unsigned int[]){4, 3, C3, G4, G4},
(unsigned int[]){4, 3, F3, F4, A4},
(unsigned int[]){4, 3, G3, D4, B4},
(unsigned int[]){2, 3, C4, E4, C5},
(unsigned int[]){2, 3, C4, E4, C5},
(unsigned int[]){2, 3, G3, D4, D5},
(unsigned int[]){2, 3, G3, D4, B4},
(unsigned int[]){2, 3, A3, C4, C5},
(unsigned int[]){2, 3, A3, C4, E5},
(unsigned int[]){2, 2, E3, G5},
(unsigned int[]){2, 2, E3, G4},
(unsigned int[]){2, 3, F3, A3, A4},
(unsigned int[]){2, 3, F3, A3, F4},
(unsigned int[]){2, 3, C3, E4},
(unsigned int[]){2, 3, C3, G4},
(unsigned int[]){2, 3, F3, A3, F4},
(unsigned int[]){2, 3, F3, A3, C5},
(unsigned int[]){2, 3, G3, B3, B4},
(unsigned int[]){2, 3, G3, B3, G4},
(unsigned int[]){2, 3, C4, E4, C5},
(unsigned int[]){1, 3, C4, E4, E5},
(unsigned int[]){1, 3, C4, E4, G5},
(unsigned int[]){1, 2, G3, G5},
(unsigned int[]){1, 2, G3, A5},
(unsigned int[]){1, 2, G3, G5},
(unsigned int[]){1, 2, G3, F5},
(unsigned int[]){3, 3, A3, C4, E5},
(unsigned int[]){1, 3, A3, C4, E5},
(unsigned int[]){1, 3, E3, G3, E5},
(unsigned int[]){1, 3, E3, G3, F5},
(unsigned int[]){1, 3, E3, G3, E5},
(unsigned int[]){1, 3, E3, G3, D5},
};
f = fopen("canon.raw", "wb");
for (i = 0; i < sizeof(ip) / sizeof(int*); ++i) {
unsigned int *cur = ip[i];
unsigned int total = samples_per_unit * cur[0];
for (t = 0; t < total; ++t) {
ampl = piano_sum(max_ampl, t, SAMPLE_FREQ, cur[1], &cur[2]);
write_ampl(f, ampl);
}
}
fclose(f);
return EXIT_SUCCESS;
}
GitHub upstream .
Для YouTube я подготовил его как:
wget -O canon.png https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/The_C_Programming_Language_logo.svg/564px-The_C_Programming_Language_logo.svg.png
ffmpeg -loop 1 -y -i canon.png -i canon.flac -shortest -acodec copy -vcodec vp9 canon.mkv
как объяснено: https://superuser.com/questions/1041816/combine-one-image-one-audio-file-to-make-one-video-using-ffmpeg/1041818#1041818
Вот более физически ориентированный взгляд на генерацию звука: Как звук представлен числами?
Проверено на Ubuntu 18.04.
Физика
Аудио кодируется как одно число для каждого момента времени. Сравните это с видео, которое нуждается в числах WIDTH * HEIGHT в каждый момент времени.
Затем это число преобразуется в линейное смещение диафрагмы вашего динамика:
| /
| /
|-/
| | A I R
|-\
| \
| \
<-> displacement
| /
| /
|---/
| | A I R
|---\
| \
| \
<---> displacement
| /
| /
|-----/
| | A I R
|-----\
| \
| \
<-----> displacement
Смещение выталкивает воздух назад и вперед, создавая перепады давления, которые проходят через воздух как P-волны .
Только смещение имеет значение: постоянный сигнал, даже максимальный, не производит звука: диафрагма просто остается в фиксированном положении.
Частота дискретизации определяет, как быстро должны выполняться смещения.
44,1 кГц - это обычная частота дискретизации, поскольку люди могут слышать до 20 кГц, и из-за теоремы Найквиста – Шеннона .
Частота дискретизации аналогична частоте кадров в секунду для видео, хотя она имеет гораздо более высокое значение по сравнению с диапазоном 25 (кинотеатр) - 144 (жесткие игровые мониторы), который мы обычно видим для видео.
Форматы
.raw
- это недостаточно определенный формат, который содержит только байты амплитуды и не содержит метаданных.
Мы должны передать несколько параметров метаданных в командной строке, например, частоту дискретизации, поскольку формат не содержит эти данные.
Существуют также другие несжатые форматы, которые содержат все необходимые метаданные, например, .wav
, см .: Синтез файлов WAV с нуля - C
На практике, однако, большинство людей имеют дело исключительно со сжатыми форматами, которые делают файлы / потоки намного меньше. Некоторые из этих форматов учитывают характеристики человеческого уха для дальнейшего сжатия звука с потерями.
Биология
Люди воспринимают звук главным образом по их частотному разложению (AKA Преобразование Фурье ).
Я думаю, это потому, что во внутреннем ухе есть части, которые резонируют с разными частотами (подтверждение TODO).
Поэтому при синтезировании музыки мы больше думаем о сложении частот, а не моментов времени. Это иллюстрируется в этом примере .
Это приводит к мысли в терминах одномерного вектора между 20 Гц и 20 кГц для каждого момента времени.
Математическое преобразование Фурье теряет понятие времени, поэтому при синтезе мы собираем группы точек, суммируем частоты для этой группы и принимаем там преобразование Фурье.
К счастью, преобразование Фурье является линейным, поэтому мы можем просто суммировать и нормализовать смещения напрямую.
Размер каждой группы точек приводит к компромиссу между точностью и временем, опосредованным той же математикой, что и Принцип неопределенности Гейзенберга .
Вейвлеты могут быть более точным математическим описанием этого промежуточного частотно-временного описания.