Введение
Полезное уведомление может состоять из двух разных тонов с короткой паузой. Вы соберёте этот рисунок из небольших функций C, затем сделаете начало и конец каждого тона плавными с помощью линейного изменения громкости. Результатом станет настоящий WAV, который можно исследовать, воспроизвести и скачать.
Сначала завершите Turn a Loop into a Tone либо освойте его синусоидальный цикл, монофонический PCM на 16 кГц, целочисленные преобразования, компиляцию и перенаправление вывода. Среда независима: модуль записи, вызывающая программа и браузер подготовлены заново, файлы и процессы прошлой работы не нужны. 50 минут — оценка автора с учётом чтения и обычных ошибок.
Расположите три сегмента по порядку
На этом шаге вы соберёте рабочую последовательность двух тонов до добавления плавных краёв. Начните в Terminal. Абсолютный путь возвращает из неверного рабочего каталога:
cd /home/labex/project/sound-studio
Первый сигнал содержит тон 440 Гц длиной 300 миллисекунд, 150 миллисекунд тишины из нулей, затем тон 660 Гц длиной 400 миллисекунд. Порядок — часть результата. При 16 000 отсчётов в секунду сегменты содержат 4 800, 2 400 и 6 400 отсчётов: всего 13 600, или 0,850 секунды.
Пауза должна содержать настоящие нули. Если не записывать паузу, WAV укоротится; изменение поля длительности в заголовке не добавит отсутствующих отсчётов. Предоставленный main.c один раз пишет заголовок для всего сигнала, затем вызывает write_cue. Ваши функции добавляют только PCM-отсчёты в тот же поток. Не пишите отдельный заголовок для каждого сегмента.
Прочитайте объявления и исходную заготовку:
cat cue.h cue.c
write_tone содержит простой синусоидальный цикл из предыдущей работы. Дополнительный параметр fade_ms будет управлять плавными краями на шаге 2. В первой версии (void)fade_ms явно отмечает неиспользуемый параметр во избежание предупреждения; это не создаёт отсчётов. Другие две функции не завершены. Замените пустые операции настоящими композицией и тишиной.
У каждого вызова write_tone свой локальный i, начинающийся с нуля. Поэтому время seconds = (double)i / rate и угол синуса начинаются заново для данного сегмента. Второй тон на 660 Гц получает собственные локальные часы, а не случайно использует индекс первого. Цикл тишины тоже начинается с нуля и один раз на отсчёт вызывает wav_sample(0).
Как и раньше, sin принимает радианы, цикл равен 2 * pi, результат лежит от -1 до 1. Умножение на амплитуду до (int) даёт ограниченный целый отсчёт. Предоставленный pcm.c упаковывает числа в знаковый 16-битный моно PCM, не добавляя отсутствующие тоны и паузы. Не меняйте main.c, cue.h, pcm.c, pcm.h, inspect_wav.py и audio.py. Реализовать нужно только cue.c.
Запишите первую полную версию. Here-document с кавычками сохраняет текст C до завершающей строки C; > заменяет только cue.c.
cat > cue.c <<'C'
#include <math.h>
#include "cue.h"
#include "pcm.h"
void write_tone(int frequency, int milliseconds, int fade_ms, int amplitude) {
const int rate = 16000;
const double pi = 3.14159265358979323846;
int count = rate * milliseconds / 1000;
(void)fade_ms;
for (int i = 0; i < count; i++) {
double seconds = (double)i / rate;
double phase = 2.0 * pi * frequency * seconds;
wav_sample((int)(amplitude * sin(phase)));
}
}
void write_silence(int milliseconds) {
int count = 16000 * milliseconds / 1000;
for (int i = 0; i < count; i++) {
wav_sample(0);
}
}
void write_cue(int first_frequency, int first_ms, int gap_ms,
int second_frequency, int second_ms, int fade_ms, int amplitude) {
write_tone(first_frequency, first_ms, fade_ms, amplitude);
write_silence(gap_ms);
write_tone(second_frequency, second_ms, fade_ms, amplitude);
}
C
Скомпилируйте с предоставленными вызывающим модулем и записью WAV. C11, предупреждения, имя результата и заключительный параметр математической библиотеки играют те же роли, что в первой работе. Успешная компиляция не должна показывать предупреждений и ошибок.
gcc -std=c11 -Wall -Wextra main.c cue.c pcm.c -lm -o cue
Исполняемый файл принимает семь аргументов в таком порядке:
| Аргумент | Единица и включительный диапазон |
|---|---|
| Первая частота | Целые Гц, 200–1200 |
| Длительность первого тона | Целые миллисекунды, 100–1000 |
| Длительность паузы | Целые миллисекунды, 50–500 |
| Вторая частота | Целые Гц, 200–1200 |
| Длительность второго тона | Целые миллисекунды, 100–1000 |
| Плавный переход на каждом краю | Целые миллисекунды, 5–50 |
| Амплитуда до плавного изменения | Целые единицы отсчёта, 1000–12000 |
Все параметры разбираются до записи любых байтов заголовка. Первая версия принимает, но намеренно игнорирует параметр плавного перехода; он реализуется на шаге 2. Создайте обычный рисунок с запрошенными переходами 20 мс и амплитудой 10 000, отдельно сохранив первоначальный результат без плавных краёв:
./cue 440 300 150 660 400 20 10000 > outputs/plain-cue.wav
python3 inspect_wav.py outputs/plain-cue.wav
Найдите Frames: 13600, Duration: 0.850 s и Peak: 10000. Полный заголовок не доказывает правильного порядка или тишины; проверка также анализирует данные.
Переключитесь в Sound Studio. Нажмите Refresh files, выберите plain-cue.wav и нажмите Load sound. Выбор и Ready to play должны указывать на этот файл. Воспроизведите, приостановите и перемещайтесь с Playback position. Перейдите к ползунку через Tab, используйте Home, End и стрелки. Первый звук должен быть ниже второго, с отчётливой паузой между ними. У этой версии без плавных краёв резкое начало или завершение ожидаемо. Сохраните реальный файл с Download WAV.
Вернитесь в Terminal. Сохраните plain-cue.wav, cue.c и текущий исполняемый файл cue. Проверка первого шага принимает эту полную обычную версию или будущую полную версию с плавными краями, поэтому переход к шагу 2 не нарушает повторную проверку первого. Доставленный исполняемый файл и пересобранный исходник должны реализовывать одно и то же допустимое поведение во всех рецептах. Старый файл без переходов в паре с исходником с переходами недостаточен. Сохранённый plain-cue.wav всегда остаётся сравнением без переходов. Дальнейшие проверки строго требуют плавных краёв.
Проверка требует сохранённый файл, испытывает исполняемый файл и пересобирает неизменённый исходник во временном каталоге с другими допустимыми параметрами. Она не создаёт недостающее. Если компиляция не удалась, исправьте указанную строку или повторите полный блок и пересоберите перед запуском: старый исполняемый файл не содержит изменений.
Сделайте оба края каждого тона плавными
На этом шаге вы масштабируете синусоидальные отсчёты постепенно меняющимся усилением. Начните в Terminal, в /home/labex/project/sound-studio.
Коэффициент усиления — множитель: 0.0 делает отсчёт беззвучным, 0.5 уменьшает амплитуду вдвое, 1.0 оставляет её неизменной. Линейный переход меняет коэффициент на одинаковую величину за отсчёт. При нашей частоте 20 миллисекунд — 16000 * 20 / 1000 = 320 отсчётов перехода. На начальном участке локальным индексам 0, 80, 160, 240 и 320 соответствуют коэффициенты 0, 0,25, 0,5, 0,75 и 1.
Плавный переход должен быть локальным для каждого тона. У второго снова начинайте с нуля; не растягивайте один переход на весь сигнал. У тона из count отсчётов последний индекс — count - 1. Поэтому count - 1 - i измеряет оставшееся расстояние до конца в позициях отсчётов. Деление на fade даёт убывающий линейный участок, точно достигающий нуля на последнем отсчёте.
Используйте дробное деление: (double)i / fade сохраняет дроби. Целочисленное деление обнулило бы почти весь начальный участок. Ограничения гарантируют положительную длину перехода и место для обоих краёв: даже самый короткий тон 100 мс с самым длинным переходом 50 мс допустим. В нём нет плато полной громкости: два дискретных наклонных участка встречаются около середины. Амплитуда — потолок до перехода, а не обещание точного пикового значения в каждом сегменте.
Замените cue.c полной версией с плавными краями. Порядок и функции тишины остаются прежними. Только тональные отсчёты получают коэффициент усиления. Два оператора if выбирают начало и конец; между ними сохраняется коэффициент 1. Он умножается на синус до преобразования в целое.
cat > cue.c <<'C'
#include <math.h>
#include "cue.h"
#include "pcm.h"
void write_tone(int frequency, int milliseconds, int fade_ms, int amplitude) {
const int rate = 16000;
const double pi = 3.14159265358979323846;
int count = rate * milliseconds / 1000;
int fade = rate * fade_ms / 1000;
for (int i = 0; i < count; i++) {
double gain = 1.0;
if (i < fade) {
gain = (double)i / fade;
}
if (count - 1 - i < fade) {
gain = (double)(count - 1 - i) / fade;
}
double seconds = (double)i / rate;
double phase = 2.0 * pi * frequency * seconds;
wav_sample((int)(amplitude * gain * sin(phase)));
}
}
void write_silence(int milliseconds) {
int count = 16000 * milliseconds / 1000;
for (int i = 0; i < count; i++) {
wav_sample(0);
}
}
void write_cue(int first_frequency, int first_ms, int gap_ms,
int second_frequency, int second_ms, int fade_ms, int amplitude) {
write_tone(first_frequency, first_ms, fade_ms, amplitude);
write_silence(gap_ms);
write_tone(second_frequency, second_ms, fade_ms, amplitude);
}
C
Пересоберите и сохраните сглаженную версию с новым именем, оставив plain-cue.wav для сравнения:
gcc -std=c11 -Wall -Wextra main.c cue.c pcm.c -lm -o cue
./cue 440 300 150 660 400 20 10000 > outputs/cue.wav
python3 inspect_wav.py outputs/cue.wav
В результате по-прежнему 13 600 кадров и 0,850 секунды. Плавные края меняют значения, а не длины сегментов. Пауза всё ещё состоит ровно из 2 400 нулей. Здесь первый тон занимает индексы 0–4 799, пауза 4 800–7 199, второй тон 7 200–13 599. Обе функции тона продолжают использовать локальные индексы с нуля независимо от позиций во всём файле.
Перейдите в Sound Studio, обновите список, загрузите и воспроизведите cue.wav. Сравните с plain-cue.wav: порядок, высота и пауза совпадают, но теперь тоны начинаются и заканчиваются плавно. Используйте Pause, ползунок и Download WAV для нового результата. Проигрыватель непосредственно читает сохранённый WAV и не может добавить недостающие паузу или плавный переход.

Выбор и сообщение Ready указывают на cue.wav. Его 13 600 отсчётов при 16 000 Гц длятся 0,850 секунды; Download WAV сохраняет именно эти байты.
Сохраните cue.c с плавными краями, пересобранный cue и оба файла. Проверки теперь требуют от текущей программы генерации плавных переходов; старый обычный файл остаётся сравнением, его не нужно повторно создавать изменённой программой.
Как и в первой работе, допускаются эквивалентные вычисления и округление. Для каждого тона проверка подбирает запрошенную синусоидальную несущую с ожидаемым линейным усилением. Отклонение подобранной амплитуды ограничено 2% запрошенной амплитуды плюс 2 единицы. RMS остатков — квадратный корень из среднего квадратов остатков, характеризующий типичную величину ошибки отсчёта, — не более 0,5% амплитуды плюс 2 единицы. Пики могут превышать запрошенную амплитуду не более чем на 2 единицы, а первый и последний отсчёты каждого сглаженного тона должны быть нулевыми с допуском 2 единицы. Это допускает целочисленное округление и эквивалентные дискретные формулы, включая деление на fade - 1, если те же крайние значения и общая огибающая укладываются в ограничения. Точное число кадров, порядок и нулевая пауза остаются обязательными. Дополнительного разрешения менять частоту нет. Реализовывать подбор не требуется.
Сохраните точное время на границах
На этом шаге вы используете ту же программу без плато и для длительностей с дробным числом циклов. Начните в Terminal.
Попробуйте самые короткие допустимые тоны с самым длинным переходом. Сегмент 100 мс содержит 1 600 отсчётов; участок 50 мс — 800. Оба края помещаются без плато. Это корректный ввод, а не ошибка для отклонения.
./cue 200 100 50 1200 100 50 1000 > outputs/edge-cue.wav
python3 inspect_wav.py outputs/edge-cue.wav
Найдите 4 000 кадров и 0,250 секунды: по 1 600 на тон плюс 800 отсчётов паузы. Пик может быть ниже 1 000, поскольку наклонные участки и отсчёты пиков синуса не обязательно совпадают. Ограничение амплитуды по-прежнему действует.
Теперь используйте 733 Гц в течение 101 мс, паузу 51 мс и 997 Гц в течение 103 мс с переходами 7 мс:
./cue 733 101 51 997 103 7 7373 > outputs/odd-cue.wav
python3 inspect_wav.py outputs/odd-cue.wav
Ожидаются 4 080 кадров и 0,255 секунды. Первый тон содержит 1 616 отсчётов, пауза 816, второй 1 648. Частота, умноженная на длительность, не обязана давать целое число циклов. Число отсчётов задаёт длительность, а локальные переходы возвращают каждый тон к нулю на конце. Не обрезайте старую запись и не перемаркируйте её заголовок для имитации нового рецепта.
Перейдите в Sound Studio, обновите список и воспроизведите edge-cue.wav и odd-cue.wav. Проверьте разные длительности, затем используйте настоящие элементы паузы и перемещения. Tab показывает фокус; Enter активирует Play; Home, End и стрелки управляют Playback position. Скачайте odd-cue.wav. Браузер и численные проверки дают сведения о файлах, но не проверяют физический динамик или общее качество аудиопроизводства.
Вернитесь в Terminal. Следующая проба намеренно запрашивает недопустимый переход 51 мс. Используйте новый временный путь. Как прежде, перенаправление оболочки открывает файл до проверки, поэтому отказ может оставить его пустым; старый файл по этому пути не сохраняется.
./cue 440 300 150 660 400 51 10000 > outputs/rejected-fade.wav
Сразу считайте исходный код завершения без промежуточной команды:
echo $?
Ожидайте объяснение опубликованного диапазона и код 2. Неверен переход, а не пауза 150 мс. Перейдите в Sound Studio, обновите список, выберите rejected-fade.wav и нажмите Load sound. Появится Cannot load sound, без воспроизводимого ресурса и скачивания; расширение .wav не чинит пустой файл. Вернитесь в Terminal и удалите только эту временную запись:
rm -- outputs/rejected-fade.wav
Исправьте переход и используйте новое имя результата:
./cue 440 300 150 660 400 20 10000 > outputs/recovered-cue.wav
python3 inspect_wav.py outputs/recovered-cue.wav
Снова ожидаются 13 600 кадров и 0,850 секунды. При опечатке относительного пути используйте абсолютный cd из шага 1 и правильное имя. Ошибка компилятора требует редактирования и пересборки; отказ по параметру — исправления этого аргумента; Cannot load sound касается наличия или формата записи. Ничто из этого не требует сброса среды.
Сохраните cue.c, работающий cue и все пять успешных WAV. Итоговая проверка читает настоящие файлы и тестирует доставленный исполняемый файл и неизменённый исходник во временных тестах, включая опубликованные границы и некруглый рецепт. Оценивание не перезаписывает результаты, не пересобирает программу на месте, не чинит отсутствующий файл и не синтезирует замену. Вы сделали универсальный генератор из трёх сегментов, а не одну жёстко заданную запись.
Резюме
Вы составили WAV из двух независимых вызовов генерации тона и настоящей паузы из нулевых отсчётов. Вы применили линейный коэффициент усиления на обоих концах каждого тона, сохранили точные целочисленные границы и использовали программу для коротких сегментов, длинного затухания и некруглых длительностей. Исходник, исполняемый файл и пять реальных записей сохраняют и первоначальное сравнение, и завершённое поведение с плавными краями.


