Compón una señal con segmentos pequeños

CBeginner
Practicar Ahora

Introducción

Una notificación útil puede contener dos tonos distintos separados por una pausa breve. Compondrás ese patrón con pequeñas funciones de C y harás graduales los comienzos y finales de cada tono mediante un fundido lineal. El resultado será un WAV real que podrás inspeccionar, reproducir y descargar.

Completa primero Turn a Loop into a Tone o domina su bucle de muestreo sinusoidal, PCM mono de 16 kHz, conversión a entero, compilación y redirección de salida. Este entorno es independiente: su escritor, llamador y navegador se preparan de nuevo y no necesita archivos ni procesos del laboratorio anterior. Los 50 minutos son una estimación del autor que incluye lectura y errores habituales.

Ordena tres segmentos

En este paso compondrás un patrón funcional de dos tonos antes de añadir fundidos. Empieza en Terminal. Usa la ruta absoluta para recuperarte de un directorio de trabajo equivocado:

cd /home/labex/project/sound-studio

La primera señal contiene un tono de 440 Hz durante 300 milisegundos, silencio de ceros durante 150 milisegundos y después un tono de 660 Hz durante 400 milisegundos. El orden forma parte del resultado. A 16.000 muestras por segundo, esos segmentos contienen 4.800, 2.400 y 6.400 muestras: 13.600 en total, o 0,850 segundos.

Una pausa debe contener ceros reales. Omitir sus escrituras acortaría el WAV; cambiar un campo de duración de la cabecera no añadiría las muestras que faltan. El archivo proporcionado main.c escribe una cabecera para toda la señal y luego llama a write_cue. Tus funciones añaden únicamente muestras PCM al mismo flujo de datos. No escribas una cabecera nueva para cada segmento.

Lee las declaraciones de funciones y la versión inicial:

cat cue.h cue.c

write_tone contiene el bucle sinusoidal sin fundidos del laboratorio anterior. Su parámetro adicional fade_ms controlará el fundido en el paso 2. En esta primera versión, (void)fade_ms marca explícitamente el parámetro como no utilizado para evitar una advertencia; no genera muestras. Las otras dos funciones están incompletas. Sustituye esas operaciones vacías por composición y silencio reales.

Cada llamada a write_tone tiene su propio i local que empieza en cero. Por tanto, el tiempo seconds = (double)i / rate y el ángulo sinusoidal vuelven a empezar en ese segmento. El segundo tono de 660 Hz tiene su propio reloj local en vez de usar por accidente el índice del primero. El bucle de silencio también empieza en cero y llama a wav_sample(0) una vez por muestra.

Como antes, sin recibe radianes, un ciclo equivale a 2 * pi y el resultado está entre -1 y 1. Multiplicar por la amplitud antes de (int) produce una muestra entera acotada. El archivo proporcionado pcm.c empaqueta esos enteros como PCM mono de 16 bits con signo; nunca aporta un tono o una pausa que falten. Mantén sin cambios main.c, cue.h, pcm.c, pcm.h, inspect_wav.py y audio.py. Solo hay que implementar cue.c.

Escribe esta primera versión completa. El here-document entre comillas conserva el texto C hasta la línea final C; > reemplaza únicamente 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

Compila con el llamador y el escritor proporcionados. C11, las advertencias, el nombre de salida y la opción final de la biblioteca matemática tienen las mismas funciones que en el primer laboratorio guiado. Una compilación correcta no debería mostrar advertencias ni errores.

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

El ejecutable recibe siete argumentos en este orden:

Argumento Unidad e intervalo inclusivo
Primera frecuencia Hz enteros, 200–1200
Duración del primer tono Milisegundos enteros, 100–1000
Duración de la pausa Milisegundos enteros, 50–500
Segunda frecuencia Hz enteros, 200–1200
Duración del segundo tono Milisegundos enteros, 100–1000
Fundido en cada extremo Milisegundos enteros, 5–50
Amplitud antes del fundido Unidades enteras de muestra, 1000–12000

Todos los parámetros se analizan antes de escribir bytes de cabecera. La primera versión acepta pero ignora deliberadamente el argumento de fundido; lo implementarás en el paso 2. Genera el patrón normal solicitando un fundido de 20 ms y amplitud 10.000; conserva aparte este resultado inicial sin fundidos:

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

Busca Frames: 13600, Duration: 0.850 s y Peak: 10000. Una cabecera completa no demuestra por sí sola el orden de los tonos ni el silencio; la comprobación también analiza los datos reales.

Cambia a Sound Studio. Pulsa Refresh files, elige plain-cue.wav y pulsa Load sound. Comprueba que la selección y el mensaje Ready to play identifican ese archivo. Reproduce, pausa y usa Playback position para desplazarte. Enfoca el deslizador con Tab y usa Home, End y las flechas. El primer sonido debe ser más grave que el segundo, con una pausa clara entre ambos. En esta versión sin fundidos todavía se espera un comienzo o final brusco. Usa Download WAV para guardar el archivo real.

Vuelve a Terminal. Conserva plain-cue.wav, cue.c y el ejecutable actual cue. La comprobación del primer paso acepta tanto esta implementación completa sin fundidos como la implementación completa posterior con fundidos; avanzar al paso 2 no invalida una comprobación anterior. El ejecutable entregado y la fuente recompilada deben implementar el mismo comportamiento permitido en todas las recetas; un ejecutable antiguo sin fundidos junto a una fuente con fundidos no basta. El archivo guardado plain-cue.wav sigue siendo siempre la comparación sin fundidos. Las comprobaciones posteriores los exigen estrictamente.

La comprobación exige el archivo guardado, prueba el ejecutable entregado y recompila la fuente sin modificar en un directorio desechable con otros parámetros admitidos. No crea trabajo ausente. Si falla una compilación, corrige la línea indicada o repite el bloque completo de fuente y compila antes de volver a ejecutar; un ejecutable antiguo no contiene tu edición.

Atenúa ambos extremos de cada tono

En este paso escalarás las muestras sinusoidales con una ganancia que cambia gradualmente. Empieza en Terminal, en /home/labex/project/sound-studio.

La ganancia es un multiplicador: 0.0 silencia una muestra, 0.5 reduce su amplitud a la mitad y 1.0 la deja igual. Un fundido lineal cambia la ganancia en la misma cantidad por muestra. Con nuestra frecuencia, 20 milisegundos son 16000 * 20 / 1000 = 320 muestras de fundido. En la rampa inicial, los índices locales 0, 80, 160, 240 y 320 tienen ganancias 0, 0,25, 0,5, 0,75 y 1.

El fundido debe ser local a cada tono. Empieza de nuevo desde cero para el segundo; no extiendas un único fundido por toda la señal. En un tono de count muestras, el último índice es count - 1. Por tanto, count - 1 - i mide cuántas posiciones de muestra quedan hasta ese extremo. Dividir esa distancia por fade da la rampa descendente, que llega exactamente a cero en la última muestra.

Usa división decimal: (double)i / fade conserva las fracciones. La división entera convertiría casi toda la primera rampa en ceros. Los límites garantizan un fundido positivo y espacio para ambas rampas: incluso el tono mínimo de 100 ms con el fundido máximo de 50 ms es válido. En ese caso no hay una meseta a volumen completo; las dos rampas discretas se encuentran cerca del centro. La amplitud es un techo previo al fundido, no una promesa de que cada segmento tenga una muestra exactamente en ese pico.

Reemplaza cue.c por la versión completa con fundidos. Las funciones de orden y silencio no cambian. Solo las muestras de tono adquieren ganancia. Las dos instrucciones if seleccionan las rampas inicial y final; las posiciones intermedias conservan ganancia 1. La ganancia multiplica el seno antes de convertir a entero.

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

Compila de nuevo y guarda la versión con fundidos con otro nombre, conservando plain-cue.wav como comparación:

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

El resultado sigue teniendo 13.600 tramas y dura 0,850 segundos. Los fundidos cambian los valores, no las longitudes de los segmentos. La pausa sigue siendo exactamente de 2.400 ceros. En esta receta, el primer tono ocupa los índices 0–4.799, la pausa 4.800–7.199 y el segundo tono 7.200–13.599. Las dos funciones de tono siguen usando su propio índice local desde cero, independientemente de esas posiciones del archivo completo.

Cambia a Sound Studio, actualiza, carga cue.wav y reprodúcelo. Compáralo con plain-cue.wav: el orden, el tono y la pausa son iguales, pero los tonos atenuados empiezan y terminan gradualmente. Usa Pause, el deslizador y Download WAV sobre el resultado nuevo. El reproductor lee directamente ese WAV guardado; no puede añadir una pausa ni un fundido ausentes.

La señal con fundidos cargada en Sound Studio

La selección y el mensaje Ready identifican cue.wav. Sus 13.600 muestras a 16.000 Hz duran 0,850 segundos; Download WAV guarda esos bytes reales.

Conserva cue.c con fundidos, el ejecutable recompilado cue y ambos archivos. Las comprobaciones exigen ahora que el programa actual genere señales con fundidos; el archivo antiguo sin ellos sigue siendo una comparación y no hay que regenerarlo con el programa actualizado.

Como en el primer laboratorio, se permiten matemáticas y redondeos equivalentes. Para cada tono, el comprobador ajusta la portadora sinusoidal solicitada con la ganancia lineal esperada. La amplitud ajustada puede diferir como máximo el 2% de la solicitada más 2 unidades; el residuo RMS (raíz de la media de los cuadrados), un error de muestra de tamaño medio, puede ser como máximo el 0,5% de la amplitud más 2 unidades. Los picos pueden superar la amplitud solicitada en 2 unidades como máximo, y la primera y última muestras de cada tono atenuado deben estar a menos de 2 unidades de cero, inclusive. Estas tolerancias permiten redondeos enteros y expresiones equivalentes de las rampas discretas, incluida la división por fade - 1 cuando los mismos extremos y la envolvente global cumplen los límites. Siguen siendo obligatorios el número exacto de tramas, el orden y la pausa de ceros. No hay permiso adicional para cambiar la frecuencia solicitada. No necesitas implementar el cálculo del ajuste.

Mantén la temporización en los límites

En este paso reutilizarás el mismo programa cuando no existe meseta y cuando las duraciones contienen un número fraccionario de ciclos. Empieza en Terminal.

Prueba los tonos mínimos admitidos con el fundido más largo. Un segmento de 100 ms contiene 1.600 muestras; una rampa de 50 ms contiene 800. Ambas caben sin meseta. Es una entrada válida, no un error que debas rechazar.

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

Busca 4.000 tramas y 0,250 segundos: 1.600 por tono y una pausa de 800 muestras. El pico puede estar por debajo de 1.000 porque las rampas y los picos muestreados del seno no necesariamente coinciden. Sigue siendo obligatorio respetar el límite de amplitud.

Usa ahora 733 Hz durante 101 ms, una pausa de 51 ms y 997 Hz durante 103 ms, con fundidos de 7 ms:

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

Debes obtener 4.080 tramas y 0,255 segundos. El primer tono contiene 1.616 muestras, la pausa 816 y el segundo 1.648. La frecuencia multiplicada por la duración no tiene por qué dar ciclos enteros. El número de muestras define la duración, y el fundido local devuelve cada tono a cero al final. No recortes una grabación antigua ni cambies su cabecera para imitar la receta nueva.

Cambia a Sound Studio, actualiza y reproduce edge-cue.wav y odd-cue.wav. Comprueba sus distintas duraciones y usa los controles reales para pausar y buscar. Tab muestra el foco; Enter activa Play; Home, End y las flechas manejan Playback position. Descarga odd-cue.wav. La reproducción del navegador y las comprobaciones numéricas aportan evidencia sobre los archivos, pero no validan un altavoz físico ni la calidad de producción de audio general.

Vuelve a Terminal. La siguiente prueba solicita deliberadamente un fundido no válido de 51 ms. Usa una ruta nueva y desechable. Como antes, la redirección del shell abre el archivo antes de validar, por lo que un rechazo puede dejarlo vacío; no conserva un archivo antiguo en esa ruta.

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

Lee inmediatamente el estado original sin ejecutar ningún otro comando entre la prueba y esta lectura:

echo $?

Debes ver la explicación del intervalo publicado y el estado 2. El argumento no válido es el fundido, no la pausa de 150 ms. Cambia a Sound Studio, actualiza, selecciona rejected-fade.wav y pulsa Load sound. Indica Cannot load sound, sin recurso reproducible ni descarga; la extensión .wav no repara un archivo vacío. Vuelve a Terminal y elimina únicamente esa salida desechable:

rm -- outputs/rejected-fade.wav

Recupérate con un fundido admitido y un nombre nuevo:

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

De nuevo debes obtener 13.600 tramas y 0,850 segundos. Si escribes mal una ruta relativa, usa el cd absoluto del paso 1 y el nombre correcto. Un error de compilación requiere editar y recompilar; un rechazo de parámetros requiere corregir ese argumento; Cannot load sound se refiere a la presencia o al formato del archivo guardado. Nada de esto requiere reiniciar el entorno.

Conserva cue.c, el ejecutable funcional cue y los cinco WAV correctos. La comprobación final lee esos archivos reales y prueba el ejecutable entregado y la fuente intacta en pruebas desechables, incluidos los extremos publicados y la receta no redonda. La evaluación no sobrescribe salidas, no recompila el ejecutable real, no repara archivos ausentes ni sintetiza sustitutos. Has creado un generador reutilizable de tres segmentos, no una grabación codificada para un único caso.

Resumen

Compusiste un WAV mediante dos llamadas independientes de tono y una pausa real de muestras cero. Aplicaste una ganancia lineal en ambos extremos de cada tono, mantuviste límites enteros exactos de muestras y reutilizaste el programa con duraciones breves, fundidos largos y recetas no redondas. La fuente, el ejecutable y cinco archivos reales conservan tanto la comparación inicial como el comportamiento final con fundidos.