Einführung
Ein kurzer Ton kann signalisieren, dass eine Aufgabe beendet ist. In diesem Lab erzeugst du den Ton selbst: Eine C-Schleife berechnet eine Zahlenfolge, aus der eine abspielbare WAV-Datei entsteht. Höre zuerst ein funktionierendes 440-Hz-Beispiel, ändere nur seine Tonhöhe und vervollständige dann deine eigene wiederverwendbare Abtastfunktion.
Du solltest C-Variablen, Arithmetik, Schleifen, Arrays und Funktionen sowie grundlegende Terminalnavigation kennen. Audioabtastung, Bogenmaß und Binärausgabe werden hier eingeführt; Zeiger und Signalverarbeitung werden nicht vorausgesetzt. Compiler, ein kleiner transparenter WAV-Schreiber und ein Browserplayer, der ausschließlich Dateien liest, sind vorbereitet. Alle Töne werden eigenständig synthetisiert; ein Aufnahmegerät ist nicht nötig. Die 50 Minuten sind eine Schätzung des Autors einschließlich Lesen und gewöhnlicher Fehler.
Einen funktionierenden Ton anhören
In diesem Schritt spielst du einen funktionierenden Ton von einer halben Sekunde ab und änderst einen Wert, damit er höher klingt. Beginne in Terminal. cd wechselt das Verzeichnis; dieser absolute Pfad stellt auch das richtige Arbeitsverzeichnis nach einem Versehen wieder her. Arbeite hier weiter.
cd /home/labex/project/sound-studio
Ein Sample ist eine vorzeichenbehaftete Zahl, die die Wellenform zu einem Zeitpunkt beschreibt. PCM bedeutet Pulscodemodulation: Die Datei speichert diese Samplewerte unmittelbar. Dieses Lab verwendet einen Kanal, also Mono, und 16.000 Samples pro Sekunde. Die vorzeichenbehafteten 16-Bit-Samples stellen ganze Zahlen von -32.768 bis 32.767 dar. Positive und negative Werte bilden die beiden Seiten der Welle; null ist die Mitte. Die Amplitude bestimmt den Abstand von null. Stelle beim Hören über Lautsprecher oder Kopfhörer zunächst eine angenehme Lautstärke ein.
Das vorbereitete sample.c erzeugt eine Sinuswelle mit 440 Hz, also 440 Zyklen pro Sekunde. sin wird in <math.h> deklariert, nimmt einen Winkel im Bogenmaß entgegen und liefert einen Wert zwischen -1 und 1. Ein vollständiger Zyklus entspricht 2 * pi Radiant. Die Multiplikation mit 10.000 skaliert diese Form auf eine mäßige Amplitude. Nach dem Anhören betrachten wir die Zeitberechnung.
Kompiliere das mitgelieferte Beispiel. gcc kompiliert C; -std=c11 wählt C11, -Wall -Wextra aktiviert hilfreiche Warnungen und -o sample benennt die ausführbare Datei. pcm.c stellt den WAV-Schreiber bereit. -lm steht hinter den Quelldateien und bindet die von sin verwendete Mathematikbibliothek ein. Diese Dateien sollten keine Compilerwarnungen verursachen.
gcc -std=c11 -Wall -Wextra sample.c pcm.c -lm -o sample
./sample startet die ausführbare Datei im aktuellen Verzeichnis. Sie schreibt binäre WAV-Bytes auf die Standardausgabe. > leitet diese Bytes in eine Datei um; starte das Programm nicht ohne Umleitung, denn Audiobytes sind kein lesbarer Terminaltext. outputs ist bereits vorbereitet. Verwende diesen neuen Dateinamen:
./sample > outputs/sample-440.wav
Bei Erfolg erscheint kein Text. Wechsle zum Tab Sound Studio. Klicke auf Refresh files, wähle sample-440.wav unter WAV file und klicke auf Load sound. Die Seite sollte Ready to play und 8000 samples · 16000 Hz · 0.500 s anzeigen. Klicke auf Play und probiere Pause sowie Playback position aus. Mit Tab kannst du die Positionssteuerung fokussieren und dann Home, End und die Pfeiltasten verwenden. Native Audiosteuerungen runden eine Dauer unter einer Sekunde eventuell auf 0:00; der Text darüber und die Position in Sekunden zeigen die genaue Dauer.
Klicke auf Download WAV, um die tatsächliche Datei zu speichern. Der Player liest die von dir erzeugte Datei; er generiert keine fehlenden Samples. Cannot load sound meldet eine fehlende oder nicht unterstützte Datei. Das unterscheidet sich von einem Verbindungsfehler, bei dem die Studioseite selbst nicht geöffnet wird. Kehre bei einem Pfadfehler zu Terminal zurück, nutze das obige cd, prüfe den Dateinamen, wiederhole den passenden Befehl und aktualisiere die Dateiliste.

Achte auf das ausgewählte sample-440.wav, die Metadaten zur halben Sekunde und die Steuerungen Play, Playback position und Download WAV.
Wechsle zurück zu Terminal. cat zeigt Text an. Lies das kurze Beispiel:
cat sample.c
Die Schleife durchläuft die Sampleindizes 0 bis 7999. seconds = (double)i / rate wandelt einen Index in Zeit um: Index 0 liegt bei 0 Sekunden, Index 160 bei 0,010 Sekunden. (double) verlangt Dezimaldivision statt Ganzzahldivision. Die Phase 2.0 * pi * frequency * seconds beschreibt den Winkel über die Zyklen. (int) wandelt den skalierten Sinuswert durch Abschneiden des Nachkommateils in eine ganze Zahl um. Das mitgelieferte wav_sample schreibt die zwei PCM-Bytes dieser Zahl; es wählt die Zahl nicht für dich.
Ändere jetzt nur die Frequenz. Der folgende Befehl verwendet sed, einen Texteditor für eine gezielte Ersetzung. -i bearbeitet die genannte Datei direkt; der Ausdruck in Anführungszeichen s/old/new/ ersetzt den wörtlichen Text zwischen dem ersten Schrägstrichpaar durch den Text zwischen dem zweiten. Er bearbeitet ausschließlich sample.c.
sed -i 's/frequency = 440/frequency = 660/' sample.c
Kompiliere neu, damit die Änderung im Programm enthalten ist, und speichere eine andere Datei. Behalte auch die erste WAV-Datei.
gcc -std=c11 -Wall -Wextra sample.c pcm.c -lm -o sample
./sample > outputs/sample-660.wav
Wechsle zu Sound Studio, aktualisiere, lade sample-660.wav und spiele es ab. Es enthält dieselben 8.000 Samples bei einer halben Sekunde Dauer, klingt aber höher. Nur die Frequenz wurde geändert. Behalte das bearbeitete sample.c, das Programm sample und beide Dateien. Dieser Vergleich ist die Vorbereitung; als Nächstes folgen dein wiederverwendbares Programm und dessen getrennte Ergebnisse.
Jedes Sample selbst berechnen
In diesem Schritt vervollständigst du write_tone, damit eine C-Funktion mit unterschiedlichen Tonhöhen, Dauern und Amplituden arbeitet. Beginne in Terminal im Verzeichnis /home/labex/project/sound-studio.
Bei 16.000 Samples pro Sekunde benötigen 300 Millisekunden 16000 * 300 / 1000 = 4800 Samples. Eine Millisekunde ist ein Tausendstel einer Sekunde. Ganze Millisekundendauern ergeben bei dieser Rate exakte ganzzahlige Samplezahlen. Eine Schleife ab i = 0, die läuft, solange i < count gilt, gibt genau count Werte aus; <= würde ein unerwünschtes Sample hinzufügen.
Betrachte ein viel kleineres Beispiel: Bei acht Samples pro Sekunde hat eine Sinuswelle mit einem Zyklus pro Sekunde ungefähr die Werte 0, 0.71, 1, 0.71, 0, -0.71, -1, -0.71 an den ersten acht Indizes. Der Index bewegt die Zeit weiter, sin liefert die vertikale Position. Unsere tatsächliche Rate von 16.000 Hz ergibt wesentlich mehr Werte pro Zyklus.
Lies die vorbereitete Schnittstelle und Ausgangsfunktion:
cat tone.h tone.c
write_tone(frequency, milliseconds, amplitude) muss alle Samples erzeugen. Der Ausgangscode schreibt Nullen, sodass selbst eine erfolgreiche Kompilierung ein strukturell gültiges, aber stummes WAV ergeben kann. Dateiname, Dateigröße oder erfolgreicher Abschluss beweisen keinen Ton.
Lass main.c, tone.h, pcm.h, pcm.c, inspect_wav.py und audio.py unverändert. Das vorbereitete main.c liest drei Argumente, validiert sie, schreibt den Header und ruft deine Funktion auf. Du musst seinen zeigerbasierten Argumentparser nicht verändern. tone.c ist deine Implementierung; der Browser ist vorbereitet.
Der Schreiber trennt Verpackung und Synthese. wav_begin(count) schreibt den Header einmal; wav_sample(value) schreibt eine übergebene ganze Zahl. Keine der Funktionen erzeugt eine Wellenform. Das kleine pcm.c nutzt putchar für ein Byte auf der Standardausgabe und fputs für feste Blocknamen. Es gibt erst das niederwertige und dann das höherwertige Byte aus, statt eine C-Struktur direkt zu speichern. Negative Samples werden in ihrer 16-Bit-Darstellung kodiert; der Schreiber lehnt Werte außerhalb des vorzeichenbehafteten Bereichs ab und begrenzt oder ersetzt sie niemals.
Diese Felder schreibt er. Der Name fmt enthält ein abschließendes Leerzeichen.
| Feld | Bedeutung in diesem Lab |
|---|---|
RIFF, size, WAVE |
Kennzeichnen einen WAV-Container; size ist die Dateibytezahl abzüglich der ersten 8 Bytes |
fmt, size 16 |
Beschreiben die folgenden 16 Formatbytes |
Format 1, channels 1 |
Unkomprimiertes PCM, Mono |
Sample rate 16000 |
Samples pro Sekunde |
Byte rate 32000 |
16.000 Frames pro Sekunde × 2 Bytes pro Frame |
Block alignment 2, sample bits 16 |
Ein Frame enthält ein Sample aus zwei Bytes |
data, size 2 * count |
Kennzeichnen und zählen die tatsächlich folgenden PCM-Bytes |
Mit cat pcm.c kannst du diese bereitgestellte Kapselung lesen. Die Felder beschreiben die Ausgabe; allein ein geänderter Header macht aus stummen Samples keinen Ton.
Implementiere die folgende kurze Funktion. <math.h> deklariert sin. pi ist eine lokale Dezimalkonstante, daher hängt das Programm nicht vom nicht standardisierten Makro M_PI ab. amplitude ist der vor der Ganzzahlumwandlung angewendete Skalierungsfaktor; das Parsermaximum von 12.000 hält diese Sinussamples sicher im 16-Bit-Bereich. Jeder Aufruf startet seinen Zeitindex bei null.
cat > tone.c ersetzt nur diese Quelldatei. Das Here-Dokument <<'C' übergibt alle folgenden Zeilen an cat, bis die letzte Zeile allein C enthält. Die Anführungszeichen am Begrenzer bewahren den C-Text wörtlich. Füge den gesamten Block einschließlich der letzten Zeile ein.
cat > tone.c <<'C'
#include <math.h>
#include "tone.h"
#include "pcm.h"
void write_tone(int frequency, int milliseconds, int amplitude) {
const int rate = 16000;
const double pi = 3.14159265358979323846;
int count = rate * milliseconds / 1000;
for (int i = 0; i < count; i++) {
double seconds = (double)i / rate;
double phase = 2.0 * pi * frequency * seconds;
int sample = (int)(amplitude * sin(phase));
wav_sample(sample);
}
}
C
Kompiliere die Funktion mit vorbereitetem Aufrufer und Schreiber. Meldet die Kompilierung einen Zeilenfehler, korrigiere tone.c oder wiederhole den vollständigen Ersetzungsblock und kompiliere neu. Ein altes Programm nach fehlgeschlagener Kompilierung würde alten Code testen.
gcc -std=c11 -Wall -Wextra main.c tone.c pcm.c -lm -o tone
Das Programm erhält Frequenz in Hz, Dauer in ganzen Millisekunden und Amplitude, in dieser Reihenfolge. Die inklusiven Bereiche sind Frequenz 200–1200, Dauer 100–1000 und Amplitude 1000–12000. Erzeuge zwei neue Ausgaben:
./tone 440 300 8000 > outputs/tone-440.wav
./tone 660 500 10000 > outputs/tone-660.wav
Untersuche mit dem vorbereiteten Leser die tatsächlich gespeicherten Bytes. python3 startet das genannte schreibgeschützte Python-Werkzeug; du musst kein Python schreiben. Es meldet fehlende, unvollständige oder nicht unterstützte Dateien und generiert sie nie neu.
python3 inspect_wav.py outputs/tone-440.wav
Erwartete Ausgabe:
Format: signed 16-bit mono PCM
Rate: 16000 Hz
Frames: 4800
Duration: 0.300 s
Peak: 8000
Wechsle zu Sound Studio. Aktualisiere, lade und spiele tone-440.wav, danach tone-660.wav. Die angezeigten Dauern betragen 0,300 und 0,500 Sekunden. Lade auch eines dieser neuen Ergebnisse herunter. Es sind Ergebnisse deiner Funktion, getrennt von sample-440.wav und sample-660.wav.

Die Auswahl und Ready to play kennzeichnen beide tone-660.wav; die Metadaten beschreiben die tatsächlich gespeicherten Samples.
Behalte tone.c, das funktionierende Programm tone und beide neuen Ausgaben. Die Prüfung liest die gespeicherten WAVs, testet dein geliefertes Programm und kompiliert deine unveränderte Quelle in einem temporären Verzeichnis mit weiteren gültigen Parametern. Alle drei müssen funktionieren; nur Quellcode, ein altes Programm oder ein kopiertes Beispiel reichen nicht. Gleichwertige Mathematik und Ganzzahlrundungen sind erlaubt: Die angepasste Tonamplitude darf höchstens um 2 % der angeforderten Amplitude plus 2 Sampleeinheiten abweichen; Samplepeaks dürfen den angeforderten Wert höchstens um 2 Einheiten überschreiten; die RMS-Abweichung, also die Quadratwurzel aus dem Mittelwert der quadrierten Residuen gegenüber einem Sinus mit angeforderter Frequenz, darf höchstens 0,5 % der Amplitude plus 2 Einheiten betragen. Sie beschreibt einen Samplefehler mittlerer Größenordnung. Diese Toleranz erlaubt Rundungsunterschiede, aber keine zusätzliche Änderung der angeforderten Tonhöhe. Exakte Framezahl und PCM-Format bleiben vorgeschrieben. Die mitgelieferte Formel hält diese Grenzen problemlos ein.
Dauer prüfen und eine Ablehnung beheben
In diesem Schritt verwendest du dieselbe Funktion für eine kurze, nicht runde Dauer und unterscheidest ungültige Eingaben von einer defekten Audiodatei. Beginne in Terminal.
Erzeuge 101 Millisekunden bei 733 Hz mit Amplitude 7.373. Ein Zyklus muss die Segmentdauer nicht genau teilen; count definiert weiterhin die Samplezahl.
./tone 733 101 7373 > outputs/short-tone.wav
python3 inspect_wav.py outputs/short-tone.wav
Achte auf Frames: 1616 und Duration: 0.101 s. Das folgt direkt aus 16000 * 101 / 1000. Das letzte Sample liegt kurz vor dem Segmentende; am Endpunkt wird kein weiteres hinzugefügt. Ein abrupter Beginn oder Schluss kann klicken, weil dieses erste Lab die Amplitude noch nicht ein- und ausblendet. Das nächste Lab zeigt allmähliche Gainänderungen und Pausen zwischen Tönen.
Der folgende Befehl fordert absichtlich 99 Millisekunden an und unterschreitet damit das Minimum. Nutze einen neuen temporären Dateinamen. Die Shell öffnet eine umgeleitete Datei, bevor das Programm Argumente prüft. Eine Ablehnung kann daher eine leere Datei hinterlassen; sie bewahrt nicht garantiert eine vorhandene Datei gleichen Namens.
./tone 440 99 10000 > outputs/rejected.wav
echo $? zeigt unmittelbar den nativen Exitstatus des vorherigen Befehls: null bedeutet Erfolg, ungleich null Ablehnung oder Fehler. Führe zwischen Versuch und Statusabfrage keinen anderen Befehl aus.
echo $?
Erwarte die Erklärung des veröffentlichten Bereichs und Status 2. Dies ist eine normale Eingabeablehnung, kein Grund zum Zurücksetzen. 99 ist das ungültige Dauerargument. Die gültigen WAVs sind separate Dateien und bleiben verfügbar.
Wechsle zu Sound Studio, aktualisiere, wähle rejected.wav und lade es. Die Seite sollte Cannot load sound anzeigen und erklären, dass dies kein vollständiges unterstütztes PCM-WAV ist. Wiedergabe und Download bleiben deaktiviert. Eine leere Datei wird nicht zum Ton, nur weil ihr Name auf .wav endet.
Kehre zu Terminal zurück. rm -- entfernt die genaue Datei nach --, das die Befehlsoptionen beendet. Entferne nur diese fehlgeschlagene temporäre Ausgabe:
rm -- outputs/rejected.wav
Verwende zur Korrektur das unterstützte Minimum und einen anderen neuen Ausgabenamen:
./tone 440 100 10000 > outputs/minimum-tone.wav
python3 inspect_wav.py outputs/minimum-tone.wav
Erwarte jetzt 1.600 Frames und 0,100 Sekunden. Kehre zu Sound Studio zurück, aktualisiere und spiele minimum-tone.wav. Lade dann short-tone.wav und verwende Download WAV. Prüfe die Tastaturbedienung: Gehe mit Tab durch die sichtbaren Steuerungen, nutze Enter auf Play sowie Home, End und Pfeiltasten am Positionsregler. Pausiere vor dem Wechsel zu einer anderen Datei.
Nun besitzt du eine wiederverwendbare Quelldatei, ein dazu passendes Programm und tatsächlich abspielbare Ausgaben. Behalte den Beispielvergleich und deine eigenen Ergebnisse. Die Prüfungen laufen in temporären Verzeichnissen und lesen die vorhandenen Dateien; sie reparieren kein Programm und erzeugen keine fehlende Arbeit. Browserdekodierung und Wiedergabe beweisen keinen angeschlossenen physischen Lautsprecher. Dieser begrenzte WAV-Umfang ist außerdem kein universeller Audioencoder.
Zusammenfassung
Du hast den Zeitindex einer C-Schleife mit einem echten Ton verbunden. Du hast die Tonhöhe eines Beispiels geändert, eine wiederverwendbare Sinusabtastung implementiert, Amplituden im vorzeichenbehafteten 16-Bit-Bereich gehalten und WAVs mit exakten Framezahlen gespeichert. Du hast echte Dateien untersucht, abgespielt und heruntergeladen sowie eine absichtliche Parameterablehnung behoben. Als Nächstes kannst du Töne mit Stille und allmählichen Ein- und Ausblendungen kombinieren.



