Composer un signal avec de petits segments

CBeginner
Pratiquer maintenant

Introduction

Une notification utile peut réunir deux sons différents séparés par une courte pause. Vous composerez ce motif avec de petites fonctions C, puis rendrez le début et la fin de chaque son progressifs grâce à un fondu linéaire. Vous obtiendrez un vrai WAV à inspecter, lire et télécharger.

Terminez d'abord Turn a Loop into a Tone, ou maîtrisez sa boucle d'échantillonnage sinusoïdal, le PCM mono à 16 kHz, les conversions entières, la compilation et la redirection de sortie. Cet environnement est indépendant : son module d'écriture, son appelant et son navigateur sont préparés à nouveau, sans fichiers ni processus du laboratoire précédent. Les 50 minutes sont une estimation de l'auteur incluant la lecture et les erreurs courantes.

Ordonner trois segments

Dans cette étape, vous composerez un motif fonctionnel de deux sons avant d'ajouter les fondus. Commencez dans Terminal. Utilisez ce chemin absolu pour retrouver le bon répertoire :

cd /home/labex/project/sound-studio

Le premier motif comporte un son à 440 Hz pendant 300 millisecondes, 150 millisecondes de silence à zéro, puis un son à 660 Hz pendant 400 millisecondes. L'ordre fait partie du résultat. À 16 000 échantillons par seconde, ces segments contiennent 4 800, 2 400 et 6 400 échantillons : 13 600 au total, soit 0,850 seconde.

Une pause doit contenir de vrais zéros. Omettre ses écritures raccourcirait le WAV ; changer une durée dans l'en-tête n'ajouterait pas les échantillons manquants. Le fichier fourni main.c écrit un seul en-tête pour tout le motif, puis appelle write_cue. Vos fonctions ajoutent uniquement des échantillons PCM au même flux. N'écrivez pas un nouvel en-tête pour chaque segment.

Lisez les déclarations et le code initial :

cat cue.h cue.c

write_tone contient la boucle sinusoïdale sans fondu du laboratoire précédent. Son paramètre supplémentaire fade_ms contrôlera le fondu à l'étape 2. Pour cette première version, (void)fade_ms indique explicitement que le paramètre est inutilisé afin d'éviter un avertissement ; cela ne génère aucun échantillon. Les deux autres fonctions sont incomplètes. Remplacez leurs opérations vides par une vraie composition et un vrai silence.

Chaque appel de write_tone possède son propre i local commençant à zéro. Son temps seconds = (double)i / rate et son angle sinusoïdal recommencent donc pour ce segment. Le second son à 660 Hz dispose de sa propre horloge locale, au lieu de reprendre accidentellement l'indice du premier. La boucle de silence commence aussi à zéro et appelle wav_sample(0) une fois par échantillon.

Comme précédemment, sin reçoit des radians, un cycle vaut 2 * pi et son résultat se situe entre -1 et 1. Multiplier par l'amplitude avant (int) donne un échantillon entier borné. Le fichier fourni pcm.c encapsule ces entiers en PCM mono signé sur 16 bits ; il ne fournit jamais de son ni de pause manquants. Gardez main.c, cue.h, pcm.c, pcm.h, inspect_wav.py et audio.py inchangés. Seul cue.c doit être implémenté.

Écrivez cette première version complète. Le here-document entre guillemets préserve le texte C jusqu'à la dernière ligne C ; > ne remplace que 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

Compilez avec l'appelant et le module d'écriture fournis. C11, les avertissements, le nom de sortie et l'option finale de bibliothèque mathématique ont les mêmes rôles que dans le premier laboratoire. Une compilation réussie ne doit afficher ni avertissement ni erreur.

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

L'exécutable reçoit sept arguments dans cet ordre :

Argument Unité et intervalle inclusif
Première fréquence Hz entiers, 200–1200
Durée du premier son Millisecondes entières, 100–1000
Durée de pause Millisecondes entières, 50–500
Seconde fréquence Hz entiers, 200–1200
Durée du second son Millisecondes entières, 100–1000
Fondu à chaque extrémité Millisecondes entières, 5–50
Amplitude avant fondu Unités entières d'échantillon, 1000–12000

Tous les paramètres sont analysés avant d'écrire un octet d'en-tête. La première version accepte mais ignore volontairement l'argument de fondu ; l'étape 2 l'implémentera. Générez le motif normal en demandant un fondu de 20 ms et une amplitude de 10 000, en conservant séparément cette première version sans fondu :

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

Repérez Frames: 13600, Duration: 0.850 s et Peak: 10000. Un en-tête complet ne prouve pas l'ordre des sons ni le silence ; la vérification analyse aussi les données réelles.

Passez à Sound Studio. Cliquez sur Refresh files, choisissez plain-cue.wav, puis Load sound. Vérifiez que la sélection et le message Ready to play désignent ce fichier. Lisez-le, mettez-le en pause et déplacez-vous avec Playback position. Avec Tab, ciblez le curseur et utilisez Home, End et les flèches. Le premier son doit être plus grave que le second, avec une pause nette. Un début ou une fin brusques restent attendus dans cette version sans fondu. Utilisez Download WAV pour enregistrer le vrai fichier.

Revenez dans Terminal. Conservez plain-cue.wav, cue.c et l'exécutable courant cue. La première vérification accepte cette implémentation complète sans fondu ou la future implémentation complète avec fondus : avancer à l'étape 2 n'invalide donc pas une revérification antérieure. L'exécutable livré et la source recompilée doivent avoir le même comportement admis pour toutes les recettes ; un ancien exécutable sans fondu associé à une source avec fondus ne suffit pas. Le fichier enregistré plain-cue.wav reste la comparaison sans fondu. Les vérifications suivantes exigent strictement des fondus.

La vérification exige le fichier enregistré, teste l'exécutable livré et recompile votre source inchangée dans un répertoire temporaire avec d'autres paramètres admis. Elle ne crée aucun travail manquant. Si la compilation échoue, corrigez la ligne indiquée ou répétez le bloc source complet, puis recompilez avant de relancer ; un ancien exécutable ne contient pas votre modification.

Appliquer un fondu aux deux extrémités

Dans cette étape, vous multiplierez les échantillons sinusoïdaux par un gain progressif. Commencez dans Terminal, dans /home/labex/project/sound-studio.

Le gain est un multiplicateur : 0.0 rend l'échantillon silencieux, 0.5 divise son amplitude par deux et 1.0 la conserve. Un fondu linéaire change le gain de la même quantité à chaque échantillon. À notre fréquence, 20 millisecondes correspondent à 16000 * 20 / 1000 = 320 échantillons de fondu. Sur la première rampe, les indices locaux 0, 80, 160, 240 et 320 ont les gains 0, 0,25, 0,5, 0,75 et 1.

Le fondu doit être local à chaque son. Recommencez à zéro pour le second ; n'étirez pas un seul fondu sur le motif entier. Pour un son de count échantillons, le dernier indice est count - 1. Donc count - 1 - i mesure le nombre de positions restantes jusqu'à cette extrémité. Diviser cette distance par fade produit la rampe descendante, atteignant exactement zéro au dernier échantillon.

Utilisez une division décimale : (double)i / fade conserve les fractions. Une division entière réduirait presque toute la première rampe à zéro. Les limites garantissent une durée de fondu positive et assez d'espace pour les deux rampes : le son minimal de 100 ms avec le fondu maximal de 50 ms reste valide. Il n'y a alors aucun plateau à plein volume ; les rampes discrètes se rejoignent près du centre. L'amplitude est un plafond avant fondu, pas la promesse que chaque segment contienne une valeur exactement égale à ce pic.

Remplacez cue.c par la version complète avec fondus. Les fonctions d'ordre et de silence restent identiques. Seuls les échantillons sonores reçoivent un gain. Les deux instructions if sélectionnent les rampes initiale et finale ; le gain reste à 1 entre elles. Il multiplie le sinus avant la conversion en entier.

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

Recompilez et enregistrez la version avec fondus sous un nouveau nom, tout en gardant plain-cue.wav pour comparaison :

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

Le résultat compte toujours 13 600 trames et dure 0,850 seconde. Les fondus changent les valeurs, pas la longueur des segments. La pause contient toujours exactement 2 400 zéros. Ici, le premier son occupe les indices 0–4 799, la pause 4 800–7 199 et le second son 7 200–13 599. Chaque fonction utilise toujours son propre indice local commençant à zéro, indépendamment de ces positions globales.

Passez à Sound Studio, actualisez, chargez cue.wav et lisez-le. Comparez-le à plain-cue.wav : ordre, hauteur et pause sont identiques, mais les sons avec fondus commencent et finissent progressivement. Utilisez Pause, le curseur de position et Download WAV sur le nouveau résultat. Le lecteur lit directement ce WAV enregistré ; il ne peut pas ajouter une pause ou un fondu manquants.

Le motif avec fondus chargé dans Sound Studio

La sélection et le message Ready identifient cue.wav. Ses 13 600 échantillons à 16 000 Hz durent 0,850 seconde ; Download WAV enregistre ces octets réels.

Conservez cue.c avec fondus, l'exécutable recompilé cue et les deux fichiers. Les vérifications exigent maintenant un programme générant des fondus ; l'ancien fichier simple reste une comparaison et ne doit pas être régénéré par le programme modifié.

Comme dans le premier laboratoire, les mathématiques et arrondis équivalents sont admis. Pour chaque son, le vérificateur ajuste la porteuse sinusoïdale demandée avec le gain linéaire attendu. L'écart d'amplitude ajustée est limité à 2 % de l'amplitude demandée plus 2 unités ; le résidu RMS (racine de la moyenne des carrés), une erreur d'échantillon de taille moyenne, est limité à 0,5 % de l'amplitude plus 2 unités. Les crêtes peuvent dépasser l'amplitude demandée de 2 unités au maximum, et les premier et dernier échantillons de chaque son avec fondus doivent être nuls à 2 unités près. Ces tolérances autorisent les arrondis entiers et les expressions discrètes équivalentes, y compris la division par fade - 1, si les mêmes extrémités et l'enveloppe globale respectent les limites. Le nombre exact de trames, l'ordre et les zéros de la pause restent obligatoires. Cela n'autorise pas à modifier la fréquence demandée. Vous n'avez pas à implémenter l'ajustement.

Respecter les durées aux limites

Dans cette étape, vous réutiliserez le même programme lorsqu'il n'y a aucun plateau et lorsque les durées contiennent un nombre fractionnaire de cycles. Commencez dans Terminal.

Essayez les sons les plus courts avec le fondu le plus long. Un segment de 100 ms contient 1 600 échantillons ; une rampe de 50 ms en contient 800. Les deux tiennent sans plateau. Cette entrée est valide et ne doit pas être refusée.

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

Repérez 4 000 trames et 0,250 seconde : 1 600 par son, plus une pause de 800 échantillons. Le pic peut être inférieur à 1 000, car les rampes et les crêtes du sinus échantillonné ne coïncident pas forcément. La limite d'amplitude reste applicable.

Utilisez maintenant 733 Hz pendant 101 ms, une pause de 51 ms et 997 Hz pendant 103 ms, avec des fondus de 7 ms :

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

Vous devez obtenir 4 080 trames et 0,255 seconde. Le premier son contient 1 616 échantillons, la pause 816 et le second 1 648. Le produit fréquence-durée n'a pas à correspondre à un nombre entier de cycles. Le comptage des échantillons définit la durée et le fondu local ramène chaque son à zéro à la fin. Ne découpez pas un ancien enregistrement et ne changez pas son en-tête pour imiter cette recette.

Passez à Sound Studio, actualisez et lisez edge-cue.wav et odd-cue.wav. Vérifiez leurs durées différentes, puis utilisez les vrais contrôles pour mettre en pause et chercher une position. Tab affiche le focus ; Enter active Play ; Home, End et les flèches commandent Playback position. Téléchargez odd-cue.wav. La lecture du navigateur et les vérifications numériques prouvent des propriétés des fichiers, pas le fonctionnement d'un haut-parleur physique ni une qualité audio générale.

Revenez dans Terminal. Le prochain essai demande volontairement un fondu invalide de 51 ms. Utilisez un nouveau chemin temporaire. Comme auparavant, la redirection ouvre le fichier avant la validation : le refus peut donc le laisser vide et ne préserve pas un ancien fichier au même emplacement.

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

Lisez immédiatement le code natif original, sans aucune commande entre l'essai et cette lecture :

echo $?

Vous devez voir l'explication de l'intervalle publié et le code 2. L'argument invalide est le fondu, pas la pause de 150 ms. Passez à Sound Studio, actualisez, choisissez rejected-fade.wav et cliquez sur Load sound. La page indique Cannot load sound, sans ressource lisible ni téléchargement ; l'extension .wav ne répare pas un fichier vide. Revenez dans Terminal et supprimez uniquement cette sortie temporaire :

rm -- outputs/rejected-fade.wav

Corrigez avec un fondu admis et un autre nom de sortie :

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

Vous devez à nouveau obtenir 13 600 trames et 0,850 seconde. Si un chemin relatif est mal saisi, utilisez le cd absolu de l'étape 1 et le bon nom. Une erreur de compilation demande une modification suivie d'une recompilation ; un refus de paramètre demande de corriger cet argument ; Cannot load sound concerne l'existence ou le format du fichier enregistré. Aucun de ces cas n'exige une réinitialisation.

Conservez cue.c, l'exécutable fonctionnel cue et les cinq WAV corrects. La vérification finale lit les fichiers réels et teste l'exécutable livré et la source inchangée dans des essais temporaires, y compris aux limites publiées et avec la recette non ronde. L'évaluation ne remplace pas vos sorties, ne recompile pas l'exécutable en place, ne répare pas de fichier absent et ne synthétise pas de substitut. Vous avez construit un générateur réutilisable à trois segments, pas un enregistrement figé.

Résumé

Vous avez composé un WAV avec deux appels de son indépendants et une vraie pause d'échantillons nuls. Vous avez appliqué un gain linéaire aux deux extrémités de chaque son, respecté les limites entières exactes des échantillons et réutilisé le programme pour des durées courtes, des fondus longs et des recettes non rondes. Votre source, votre exécutable et cinq fichiers réels conservent la comparaison initiale et le comportement final avec fondus.