Ein Signal aus kleinen Segmenten zusammensetzen

CBeginner
Jetzt üben

Einführung

Ein nützliches Benachrichtigungssignal kann zwei unterschiedliche Töne mit einer kurzen Pause enthalten. Du setzt dieses Muster aus kleinen C-Funktionen zusammen und lässt danach Anfang und Ende jedes Tons durch lineare Ein- und Ausblendungen allmählich verlaufen. Das Ergebnis ist ein echtes WAV zum Untersuchen, Abspielen und Herunterladen.

Schließe zuerst Turn a Loop into a Tone ab oder beherrsche dessen Sinusabtastschleife, 16-kHz-Mono-PCM, Ganzzahlumwandlung, Kompilierung und Ausgabeumleitung. Diese Umgebung ist unabhängig: Schreiber, Aufrufer und Browser werden neu vorbereitet und benötigen keine Dateien oder Prozesse aus dem vorherigen Lab. Die 50 Minuten sind eine Autorenschätzung einschließlich Lesen und gewöhnlicher Fehler.

Drei Segmente in die richtige Reihenfolge bringen

In diesem Schritt setzt du ein funktionierendes Zweitonmuster zusammen, bevor du Blenden ergänzt. Beginne in Terminal. Verwende bei einem falschen Arbeitsverzeichnis den absoluten Pfad:

cd /home/labex/project/sound-studio

Das erste Signal besteht aus 440 Hz für 300 Millisekunden, 150 Millisekunden Stille mit Nullwerten und danach 660 Hz für 400 Millisekunden. Die Reihenfolge gehört zum Ergebnis. Bei 16.000 Samples pro Sekunde enthalten die Segmente 4.800, 2.400 und 6.400 Samples: insgesamt 13.600 oder 0,850 Sekunden.

Eine Pause muss tatsächliche Nullen enthalten. Fehlende Schreibvorgänge für die Pause würden das WAV verkürzen; eine geänderte Dauer im Header fügt keine Samples hinzu. Das gelieferte main.c schreibt einen Header für das gesamte Signal und ruft anschließend write_cue auf. Deine Funktionen hängen nur PCM-Samples an denselben Datenstrom an. Schreibe keinen neuen Header für jedes Segment.

Lies die Funktionsdeklarationen und den Ausgangscode:

cat cue.h cue.c

write_tone enthält die einfache Sinusschleife aus dem vorherigen Lab. Der zusätzliche Parameter fade_ms steuert die Blende in Schritt 2. Im ersten Entwurf kennzeichnet (void)fade_ms ihn ausdrücklich als unbenutzt, damit der Compiler nicht warnt; es erzeugt keine Samples. Die beiden anderen Funktionen sind unvollständig. Ersetze ihre leeren Operationen durch echte Komposition und Stille.

Jeder Aufruf von write_tone besitzt ein eigenes lokales i ab null. Seine Zeit seconds = (double)i / rate und sein Sinuswinkel beginnen deshalb für dieses Segment neu. Der zweite 660-Hz-Ton erhält eine eigene lokale Uhr, statt versehentlich den Index des ersten Tons zu verwenden. Auch die Stillschleife startet bei null und ruft je Sample einmal wav_sample(0) auf.

Wie zuvor nimmt sin Winkel im Bogenmaß entgegen, ein Zyklus entspricht 2 * pi, und das Ergebnis liegt zwischen -1 und 1. Erst mit der Amplitude multiplizieren und dann (int) anwenden ergibt ein begrenztes ganzzahliges Sample. Das gelieferte pcm.c verpackt diese Zahlen als vorzeichenbehaftetes 16-Bit-Mono-PCM; es ergänzt niemals fehlende Töne oder Pausen. Lass main.c, cue.h, pcm.c, pcm.h, inspect_wav.py und audio.py unverändert. Nur cue.c benötigt eine Implementierung.

Schreibe diese vollständige erste Version. Das Here-Dokument mit Anführungszeichen bewahrt den C-Text bis zur abschließenden Zeile C; > ersetzt nur 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

Kompiliere mit dem gelieferten Aufrufer und Schreiber. C11, Warnoptionen, Ausgabename und abschließende Mathematikbibliothek erfüllen dieselben Aufgaben wie im ersten Lab. Eine erfolgreiche Kompilierung sollte weder Warnungen noch Fehler ausgeben.

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

Das Programm erwartet sieben Argumente in dieser Reihenfolge:

Argument Einheit und inklusiver Bereich
Erste Frequenz Ganze Hz, 200–1200
Erste Tondauer Ganze Millisekunden, 100–1000
Pausendauer Ganze Millisekunden, 50–500
Zweite Frequenz Ganze Hz, 200–1200
Zweite Tondauer Ganze Millisekunden, 100–1000
Blendendauer an jedem Ende Ganze Millisekunden, 5–50
Amplitude vor der Blende Ganzzahlige Sampleeinheiten, 1000–12000

Alle Parameter werden ausgewertet, bevor Headerbytes geschrieben werden. Die erste Version akzeptiert das Blendenargument, ignoriert es jedoch absichtlich; Schritt 2 implementiert es. Erzeuge das normale Muster mit angeforderter 20-ms-Blende und Amplitude 10.000 und behalte dieses anfängliche Ergebnis ohne Blenden getrennt:

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

Achte auf Frames: 13600, Duration: 0.850 s und Peak: 10000. Ein vollständiger Header allein beweist weder Tonreihenfolge noch Stille; die Prüfung analysiert auch die tatsächlichen Daten.

Wechsle zu Sound Studio. Klicke auf Refresh files, wähle plain-cue.wav und klicke auf Load sound. Auswahl und Ready to play müssen diese Datei benennen. Spiele sie ab, pausiere und bewege dich mit Playback position. Fokussiere den Regler mit Tab und nutze Home, End und die Pfeiltasten. Der erste Ton sollte tiefer sein als der zweite, mit einer klaren Pause dazwischen. Bei dieser Version ohne Blenden sind abrupte Anfänge oder Enden noch zu erwarten. Speichere mit Download WAV die tatsächliche Datei.

Kehre zu Terminal zurück. Behalte plain-cue.wav, cue.c und das aktuelle Programm cue. Die erste Prüfung akzeptiert entweder diese vollständige Version ohne Blenden oder die spätere vollständige Version mit Blenden. Schritt 2 macht eine frühere Wiederholungsprüfung daher nicht ungültig. Dein geliefertes Programm und der neu kompilierte Quellcode müssen über alle Rezepte dasselbe erlaubte Verhalten zeigen; eine alte Version ohne Blenden mit einer Quelle mit Blenden reicht nicht. Das gespeicherte plain-cue.wav bleibt der Vergleich ohne Blenden. Spätere Prüfungen verlangen sie strikt.

Die Prüfung benötigt die gespeicherte Datei, testet das gelieferte Programm und kompiliert deine unveränderte Quelle mit weiteren unterstützten Parametern in einem temporären Verzeichnis. Sie erzeugt keine fehlende Arbeit. Schlägt eine Kompilierung fehl, korrigiere die genannte Zeile oder wiederhole den gesamten Quellblock und kompiliere vor dem nächsten Lauf; ein altes Programm enthält deine Änderung nicht.

Beide Enden jedes Tons ein- und ausblenden

In diesem Schritt skalierst du die Sinussamples mit einem allmählich veränderten Verstärkungsfaktor. Beginne in Terminal im Verzeichnis /home/labex/project/sound-studio.

Dieser Faktor, auch Gain genannt, ist ein Multiplikator: 0.0 macht ein Sample stumm, 0.5 halbiert seine Amplitude und 1.0 lässt sie gleich. Eine lineare Blende ändert ihn pro Sample um denselben Betrag. Bei unserer Rate entsprechen 20 Millisekunden 16000 * 20 / 1000 = 320 Blendensamples. Auf der Anfangsrampe lauten die Faktoren an den lokalen Indizes 0, 80, 160, 240 und 320 jeweils 0, 0,25, 0,5, 0,75 und 1.

Die Blende muss lokal zu jedem Ton gehören. Beginne beim zweiten Ton wieder bei null; ziehe keine einzige Blende über das gesamte Signal. Ein Ton mit count Samples hat den letzten Index count - 1. Deshalb misst count - 1 - i, wie viele Samplepositionen bis zum Ende verbleiben. Diese Entfernung geteilt durch fade ergibt die absteigende Rampe, die beim letzten Sample genau null erreicht.

Verwende Dezimaldivision: (double)i / fade bewahrt die Bruchteile. Ganzzahldivision würde fast die gesamte erste Rampe auf null setzen. Die Grenzen garantieren eine positive Blendendauer und Platz für beide Rampen: Selbst der kürzeste 100-ms-Ton mit der längsten 50-ms-Blende ist gültig. Hier gibt es kein Plateau mit voller Lautstärke; die diskreten Rampen treffen sich nahe der Mitte. Die Amplitude ist eine Obergrenze vor der Blende, kein Versprechen, dass jedes Segment ein Sample genau an dieser Spitze besitzt.

Ersetze cue.c durch die vollständige Fassung mit Blenden. Reihenfolge- und Stillefunktionen bleiben gleich. Nur die Tonsamples erhalten einen Verstärkungsfaktor. Die beiden if-Anweisungen wählen Anfangs- und Endrampe; dazwischen bleibt der Faktor 1. Er multipliziert den Sinus vor der Ganzzahlumwandlung.

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

Kompiliere neu und speichere die Fassung mit Blenden unter einem neuen Namen. Bewahre plain-cue.wav zum Vergleich auf:

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

Das Ergebnis besitzt weiterhin 13.600 Frames und dauert 0,850 Sekunden. Blenden ändern Samplewerte, nicht Segmentlängen. Die Pause enthält noch immer genau 2.400 Nullsamples. In diesem Rezept belegt der erste Ton die Indizes 0–4.799, die Pause 4.800–7.199 und der zweite Ton 7.200–13.599. Beide Tonfunktionen verwenden unabhängig von diesen Gesamtdateipositionen ihren eigenen lokalen Index ab null.

Wechsle zu Sound Studio, aktualisiere, lade cue.wav und spiele es ab. Vergleiche mit plain-cue.wav: Reihenfolge, Tonhöhe und Pause bleiben gleich, während die Töne allmählich beginnen und enden. Verwende Pause, den Positionsregler und Download WAV für das neue Ergebnis. Der Player liest direkt dieses gespeicherte WAV; er kann weder eine fehlende Pause noch eine fehlende Blende ergänzen.

Das Signal mit Blenden in Sound Studio

Auswahl und Ready-Meldung benennen cue.wav. Seine 13.600 Samples bei 16.000 Hz dauern 0,850 Sekunden; Download WAV speichert genau diese Bytes.

Behalte cue.c mit Blenden, das neu kompilierte Programm cue und beide Dateien. Die Prüfungen verlangen jetzt, dass das aktuelle Programm Signale mit Blenden erzeugt; die alte Datei ohne Blenden bleibt ein Vergleich und muss nicht neu erzeugt werden.

Wie im ersten Lab sind gleichwertige Mathematik und Rundungen erlaubt. Für jeden Ton passt der Prüfer den angeforderten Sinusträger mit der erwarteten linearen Hüllkurve an. Die angepasste Amplitude darf höchstens um 2 % der angeforderten Amplitude plus 2 Sampleeinheiten abweichen. Die RMS-Abweichung, also die Quadratwurzel aus dem Mittelwert der quadrierten Residuen und damit ein Samplefehler mittlerer Größenordnung, darf höchstens 0,5 % der Amplitude plus 2 Einheiten betragen. Spitzen dürfen die angeforderte Amplitude höchstens um 2 Einheiten überschreiten; das erste und letzte Sample jedes Tons müssen bis auf 2 Einheiten null sein. Diese Toleranzen erlauben Ganzzahlrundungen und gleichwertige diskrete Rampenausdrücke einschließlich Division durch fade - 1, sofern dieselben Endpunkte und die gesamte Hüllkurve die Grenzen einhalten. Exakte Framezahl, Segmentreihenfolge und Nullpause bleiben vorgeschrieben. Die angeforderte Frequenz darf nicht zusätzlich verändert werden. Du musst das Anpassungsverfahren nicht implementieren.

Die Zeitgrenzen korrekt einhalten

In diesem Schritt verwendest du dasselbe Programm ohne Plateau und mit Tondauern, die Bruchteile von Zyklen enthalten. Beginne in Terminal.

Probiere die kürzesten unterstützten Töne mit der längsten Blende. Ein 100-ms-Segment enthält 1.600 Samples, eine 50-ms-Rampe 800. Beide Rampen passen ohne Plateau hinein. Dies ist eine gültige Eingabe, kein abzulehnender Fehler.

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

Achte auf 4.000 Frames und 0,250 Sekunden: jeweils 1.600 Samples pro Ton plus 800 Pausensamples. Die Spitze kann unter 1.000 liegen, weil Rampen und abgetastete Sinusspitzen nicht unbedingt zusammenfallen. Die Amplitudengrenze gilt trotzdem.

Verwende nun 733 Hz für 101 ms, eine 51-ms-Pause und 997 Hz für 103 ms mit 7-ms-Blenden:

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

Erwarte 4.080 Frames und 0,255 Sekunden. Der erste Ton enthält 1.616 Samples, die Pause 816 und der zweite Ton 1.648. Frequenz mal Dauer muss keine ganze Zahl von Zyklen ergeben. Die Samplezahl definiert die Dauer, und die lokale Blende führt jeden Ton am Ende zu null zurück. Schneide keine alte Aufnahme zu und beschrifte ihren Header nicht neu, um das Rezept nachzuahmen.

Wechsle zu Sound Studio, aktualisiere und spiele edge-cue.wav sowie odd-cue.wav. Prüfe die unterschiedlichen Dauern und verwende die tatsächlichen Steuerungen zum Pausieren und Positionieren. Tab zeigt den Tastaturfokus; Enter aktiviert Play; Home, End und Pfeiltasten steuern Playback position. Lade odd-cue.wav herunter. Browserwiedergabe und numerische Prüfungen belegen Dateieigenschaften, nicht physische Lautsprecher oder allgemeine Audioproduktionsqualität.

Kehre zu Terminal zurück. Der nächste Versuch fordert absichtlich eine ungültige 51-ms-Blende an. Verwende einen neuen temporären Ausgabepfad. Wie zuvor öffnet die Shellumleitung die Datei vor der Validierung; eine Ablehnung kann sie leer lassen und bewahrt keine alte Datei am selben Pfad.

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

Lies sofort den ursprünglichen nativen Status, ohne einen Befehl zwischen Versuch und Abfrage auszuführen:

echo $?

Erwarte die Erklärung des veröffentlichten Bereichs und Status 2. Ungültig ist die Blende, nicht die 150-ms-Pause. Wechsle zu Sound Studio, aktualisiere, wähle rejected-fade.wav und klicke auf Load sound. Die Seite meldet Cannot load sound, ohne abspielbare Ressource oder Download; die Endung .wav repariert keine leere Datei. Kehre zu Terminal zurück und entferne nur diese temporäre Ausgabe:

rm -- outputs/rejected-fade.wav

Korrigiere mit einer unterstützten Blende und einem neuen Ausgabenamen:

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

Erwarte wieder 13.600 Frames und 0,850 Sekunden. Bei einem falsch geschriebenen relativen Pfad verwende das absolute cd aus Schritt 1 und den korrekten Namen. Ein Compilerfehler erfordert Bearbeiten und Neukompilieren; eine Parameterablehnung die Korrektur dieses Arguments; Cannot load sound betrifft Existenz oder Format der gespeicherten Datei. Nichts davon erfordert ein Zurücksetzen der Umgebung.

Behalte cue.c, das funktionierende Programm cue und alle fünf erfolgreichen WAVs. Die Abschlussprüfung liest diese echten Dateien und testet geliefertes Programm und unveränderte Quelle in temporären Tests, einschließlich veröffentlichter Grenzwerte und nicht runder Werte. Die Bewertung überschreibt keine Ausgaben, kompiliert das vorhandene Programm nicht neu, repariert keine fehlende Datei und synthetisiert keinen Ersatz. Du hast einen wiederverwendbaren Generator aus drei Segmenten gebaut, keine fest einprogrammierte Aufnahme.

Zusammenfassung

Du hast ein WAV aus zwei unabhängigen Tonaufrufen und einer tatsächlichen Pause aus Nullsamples zusammengesetzt. Du hast einen linearen Verstärkungsfaktor an beiden Enden jedes Tons angewendet, exakte ganzzahlige Samplegrenzen eingehalten und das Programm für kurze Dauern, lange Blenden und nicht runde Werte wiederverwendet. Quelle, Programm und fünf echte Dateien bewahren den anfänglichen Vergleich ebenso wie das fertige Verhalten mit Blenden.