Соберите сигнал из небольших сегментов

CBeginner
Практиковаться сейчас

Введение

Полезное уведомление может состоять из двух разных тонов с короткой паузой. Вы соберёте этот рисунок из небольших функций 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 и не может добавить недостающие паузу или плавный переход.

Сигнал с плавными краями в Sound Studio

Выбор и сообщение 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 из двух независимых вызовов генерации тона и настоящей паузы из нулевых отсчётов. Вы применили линейный коэффициент усиления на обоих концах каждого тона, сохранили точные целочисленные границы и использовали программу для коротких сегментов, длинного затухания и некруглых длительностей. Исходник, исполняемый файл и пять реальных записей сохраняют и первоначальное сравнение, и завершённое поведение с плавными краями.