Compor um sinal com pequenos segmentos

CBeginner
Pratique Agora

Introdução

Uma notificação útil pode conter dois tons diferentes separados por uma pausa curta. Você comporá esse padrão com pequenas funções C e tornará o início e o fim de cada tom graduais com um fade linear. O resultado será um WAV real que poderá inspecionar, reproduzir e baixar.

Conclua primeiro Turn a Loop into a Tone ou domine seu laço de amostragem senoidal, PCM mono de 16 kHz, conversão inteira, compilação e redirecionamento de saída. Este ambiente é independente: gravador, chamador e navegador são preparados novamente, sem arquivos ou processos do laboratório anterior. Os 50 minutos são uma estimativa do autor, incluindo leitura e erros comuns.

Colocar três segmentos em ordem

Nesta etapa, você comporá um padrão funcional de dois tons antes de acrescentar fades. Comece em Terminal. Use o caminho absoluto para corrigir um diretório de trabalho errado:

cd /home/labex/project/sound-studio

O primeiro sinal tem um tom de 440 Hz por 300 milissegundos, silêncio de zeros por 150 milissegundos e depois um tom de 660 Hz por 400 milissegundos. A ordem faz parte do resultado. A 16.000 amostras por segundo, os segmentos contêm 4.800, 2.400 e 6.400 amostras: 13.600 no total, ou 0,850 segundo.

Uma pausa deve conter zeros reais. Omitir suas escritas encurtaria o WAV; alterar uma duração no cabeçalho não acrescentaria as amostras ausentes. O main.c fornecido escreve um cabeçalho para todo o sinal e depois chama write_cue. Suas funções acrescentam apenas amostras PCM ao mesmo fluxo. Não escreva um novo cabeçalho para cada segmento.

Leia as declarações e a versão inicial:

cat cue.h cue.c

write_tone contém o laço senoidal simples do laboratório anterior. Seu parâmetro extra fade_ms controlará o fade na etapa 2. Na primeira versão, (void)fade_ms marca explicitamente o parâmetro como não usado para evitar aviso; não gera amostras. As outras duas funções estão incompletas. Substitua as operações vazias por composição e silêncio reais.

Cada chamada de write_tone tem seu próprio i local, iniciado em zero. Seu tempo seconds = (double)i / rate e seu ângulo senoidal recomeçam nesse segmento. O segundo tom de 660 Hz tem seu próprio relógio local, sem usar acidentalmente o índice do primeiro. O laço de silêncio também inicia em zero e chama wav_sample(0) uma vez por amostra.

Como antes, sin recebe radianos, um ciclo vale 2 * pi e seu resultado fica entre -1 e 1. Multiplicar pela amplitude antes de (int) gera uma amostra inteira limitada. O pcm.c fornecido empacota esses inteiros como PCM mono de 16 bits com sinal; nunca fornece um tom ou pausa ausentes. Mantenha main.c, cue.h, pcm.c, pcm.h, inspect_wav.py e audio.py inalterados. Apenas cue.c precisa de implementação.

Escreva esta primeira versão completa. O here-document entre aspas preserva o texto C até a última linha C; > substitui apenas 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

Compile com o chamador e o gravador fornecidos. C11, avisos, nome de saída e a opção final de biblioteca matemática têm os mesmos papéis do primeiro laboratório. Uma compilação bem-sucedida não deve mostrar avisos ou erros.

gcc -std=c11 -Wall -Wextra main.c cue.c pcm.c -lm -o cue

O executável recebe sete argumentos nesta ordem:

Argumento Unidade e intervalo inclusivo
Primeira frequência Hz inteiros, 200–1200
Duração do primeiro tom Milissegundos inteiros, 100–1000
Duração da pausa Milissegundos inteiros, 50–500
Segunda frequência Hz inteiros, 200–1200
Duração do segundo tom Milissegundos inteiros, 100–1000
Fade em cada extremidade Milissegundos inteiros, 5–50
Amplitude antes do fade Unidades inteiras de amostra, 1000–12000

Todos os parâmetros são analisados antes de escrever bytes de cabeçalho. A primeira versão aceita mas ignora deliberadamente o argumento de fade; a etapa 2 o implementará. Gere o padrão normal solicitando fade de 20 ms e amplitude 10.000, mantendo este resultado inicial sem fades separado:

./cue 440 300 150 660 400 20 10000 > outputs/plain-cue.wav
python3 inspect_wav.py outputs/plain-cue.wav

Procure Frames: 13600, Duration: 0.850 s e Peak: 10000. Um cabeçalho completo não prova a ordem dos tons nem o silêncio; a verificação também analisa os dados reais.

Mude para Sound Studio. Clique em Refresh files, escolha plain-cue.wav e clique em Load sound. Confirme que seleção e Ready to play identificam esse arquivo. Reproduza, pause e use Playback position para navegar. Foque o controle com Tab e use Home, End e as setas. O primeiro som deve ser mais grave que o segundo, com uma pausa clara. Um início ou fim brusco ainda é esperado nesta versão sem fades. Use Download WAV para salvar o arquivo real.

Volte a Terminal. Preserve plain-cue.wav, cue.c e o executável atual cue. A primeira verificação aceita esta implementação completa simples ou a implementação completa posterior com fades, para que avançar à etapa 2 não invalide uma verificação anterior. Seu executável entregue e a fonte recompilada devem implementar o mesmo comportamento permitido em todas as receitas; um executável antigo sem fades junto a uma fonte com fades não basta. O arquivo salvo plain-cue.wav permanece sempre como comparação sem fades. As próximas verificações os exigem estritamente.

A verificação exige o arquivo salvo, testa o executável entregue e recompila a fonte inalterada em um diretório descartável com outros parâmetros admitidos. Não cria trabalho ausente. Se a compilação falhar, corrija a linha indicada ou repita o bloco completo e recompile antes de executar novamente; um executável antigo não contém sua edição.

Aplicar fades nas duas extremidades de cada tom

Nesta etapa, você escalará as amostras senoidais com um ganho gradual. Comece em Terminal, em /home/labex/project/sound-studio.

Ganho é um multiplicador: 0.0 silencia a amostra, 0.5 reduz a amplitude pela metade e 1.0 a mantém. Um fade linear altera o ganho pela mesma quantidade a cada amostra. Nesta taxa, 20 milissegundos são 16000 * 20 / 1000 = 320 amostras de fade. Na primeira rampa, os índices locais 0, 80, 160, 240 e 320 têm ganhos 0, 0,25, 0,5, 0,75 e 1.

O fade deve ser local a cada tom. Reinicie em zero no segundo; não estenda um único fade pelo sinal todo. Um tom de count amostras termina no índice count - 1. Portanto, count - 1 - i mede quantas posições restam até esse fim. Dividir a distância por fade dá a rampa descendente, chegando exatamente a zero na última amostra.

Use divisão decimal: (double)i / fade preserva frações. Divisão inteira tornaria quase toda a primeira rampa zero. Os limites garantem fade positivo e espaço para ambas as rampas: até o tom mínimo de 100 ms com o fade máximo de 50 ms é válido. Nesse caso não há patamar de volume completo; as duas rampas discretas se encontram perto do centro. A amplitude é um teto antes do fade, não a promessa de uma amostra exatamente no pico em cada segmento.

Substitua cue.c pela versão completa com fades. As funções de ordem e silêncio não mudam. Só as amostras de tom recebem ganho. As duas instruções if selecionam as rampas inicial e final; entre elas o ganho permanece 1. O ganho multiplica o seno antes da conversão em inteiro.

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

Recompile e salve a versão com fades em outro nome, preservando plain-cue.wav para comparação:

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

O resultado ainda tem 13.600 quadros e dura 0,850 segundo. Fades mudam valores, não comprimentos dos segmentos. A pausa ainda tem exatamente 2.400 zeros. Nesta receita, o primeiro tom ocupa os índices 0–4.799, a pausa 4.800–7.199 e o segundo tom 7.200–13.599. As duas funções continuam usando seus próprios índices locais iniciados em zero, independentemente dessas posições globais.

Mude para Sound Studio, atualize, carregue cue.wav e reproduza. Compare com plain-cue.wav: ordem, altura e pausa são iguais, enquanto os tons com fades começam e terminam gradualmente. Use Pause, o controle de posição e Download WAV no novo resultado. O reprodutor lê esse WAV salvo diretamente; não pode adicionar uma pausa ou fade ausentes.

O sinal com fades carregado no Sound Studio

Seleção e mensagem Ready identificam cue.wav. Suas 13.600 amostras a 16.000 Hz duram 0,850 segundo; Download WAV salva esses bytes reais.

Preserve cue.c com fades, o executável recompilado cue e os dois arquivos. As verificações agora exigem que o programa gere sinais com fades; o arquivo simples antigo permanece uma comparação e não precisa ser regenerado pelo programa alterado.

Como no primeiro laboratório, matemática e arredondamentos equivalentes são permitidos. Para cada tom, o verificador ajusta a portadora senoidal pedida com o ganho linear esperado. A amplitude ajustada pode diferir até 2% da solicitada mais 2 unidades de amostra. O resíduo RMS, a raiz da média dos quadrados dos resíduos e uma medida de erro de amostra de tamanho médio, pode ser no máximo 0,5% da amplitude mais 2 unidades. Picos podem exceder a amplitude em até 2 unidades, e a primeira e a última amostras de cada tom com fades devem estar a até 2 unidades de zero. Essas tolerâncias permitem arredondamento inteiro e expressões discretas equivalentes, incluindo divisão por fade - 1, desde que os mesmos extremos e a envoltória geral atendam aos limites. Contagem exata de quadros, ordem e pausa de zeros continuam obrigatórias. Não há permissão adicional para mudar a frequência. Você não precisa implementar o ajuste.

Manter o tempo correto nos limites

Nesta etapa, você reutilizará o programa sem patamar e quando as durações contêm uma quantidade fracionária de ciclos. Comece em Terminal.

Experimente os tons mais curtos admitidos com o fade mais longo. Um segmento de 100 ms contém 1.600 amostras; uma rampa de 50 ms contém 800. Ambas cabem sem patamar. É uma entrada válida, não um erro a rejeitar.

./cue 200 100 50 1200 100 50 1000 > outputs/edge-cue.wav
python3 inspect_wav.py outputs/edge-cue.wav

Procure 4.000 quadros e 0,250 segundo: 1.600 por tom, mais uma pausa de 800 amostras. O pico pode ficar abaixo de 1.000, pois as rampas e os picos amostrados da senoide nem sempre se alinham. O limite de amplitude continua obrigatório.

Agora use 733 Hz por 101 ms, pausa de 51 ms e 997 Hz por 103 ms, com fades de 7 ms:

./cue 733 101 51 997 103 7 7373 > outputs/odd-cue.wav
python3 inspect_wav.py outputs/odd-cue.wav

Espere 4.080 quadros e 0,255 segundo. O primeiro tom contém 1.616 amostras, a pausa 816 e o segundo 1.648. Frequência vezes duração não precisa resultar em ciclos inteiros. Contar amostras define a duração, e o fade local devolve cada tom a zero no final. Não recorte uma gravação antiga nem altere seu cabeçalho para imitar a nova receita.

Mude para Sound Studio, atualize e reproduza edge-cue.wav e odd-cue.wav. Confira as durações diferentes e use os controles reais para pausar e navegar. Tab mostra o foco; Enter ativa Play; Home, End e as setas operam Playback position. Baixe odd-cue.wav. Reprodução no navegador e testes numéricos mostram propriedades dos arquivos, não validam um alto-falante físico ou a qualidade geral de produção de áudio.

Volte a Terminal. A próxima tentativa solicita intencionalmente um fade inválido de 51 ms. Use um caminho novo e descartável. Como antes, o redirecionamento abre o arquivo antes da validação, então uma rejeição pode deixá-lo vazio; não preserva um arquivo antigo nesse caminho.

./cue 440 300 150 660 400 51 10000 > outputs/rejected-fade.wav

Leia imediatamente o estado nativo original, sem outro comando entre tentativa e leitura:

echo $?

Espere a explicação do intervalo publicado e o código 2. O argumento inválido é o fade, não a pausa de 150 ms. Mude para Sound Studio, atualize, selecione rejected-fade.wav e clique em Load sound. Aparece Cannot load sound, sem recurso reproduzível nem download; a extensão .wav não repara um arquivo vazio. Volte a Terminal e remova só essa saída:

rm -- outputs/rejected-fade.wav

Recupere-se com um fade admitido e um nome novo:

./cue 440 300 150 660 400 20 10000 > outputs/recovered-cue.wav
python3 inspect_wav.py outputs/recovered-cue.wav

Novamente espere 13.600 quadros e 0,850 segundo. Se errar um caminho relativo, use o cd absoluto da etapa 1 e o nome correto. Erro de compilador exige editar e recompilar; rejeição de parâmetro exige corrigir esse argumento; Cannot load sound envolve existência ou formato do arquivo salvo. Nada disso exige reiniciar o ambiente.

Preserve cue.c, o executável funcional cue e os cinco WAVs corretos. A verificação final lê os arquivos reais e testa o executável entregue e a fonte inalterada em testes descartáveis, incluindo extremos publicados e a receita não redonda. A avaliação não sobrescreve saídas, não recompila seu executável local, não repara arquivo ausente nem sintetiza substitutos. Você criou um gerador reutilizável de três segmentos, não uma gravação fixa.

Resumo

Você compôs um WAV com duas chamadas independentes de tom e uma pausa real de amostras zero. Aplicou ganho linear nas duas extremidades de cada tom, manteve limites inteiros exatos de amostras e reutilizou o programa com segmentos curtos, fades longos e receitas não redondas. A fonte, o executável e cinco arquivos reais preservam a comparação inicial e o comportamento final com fades.