# Lautstärke

Quelle: https://dev.inf.zone/exercises/07/volume/

![Wellenform der WAV-Datei](https://dev.inf.zone/exercises/07/volume/./wav_file.png)

## Aufgabe

**Auf einen Blick**

- **Was:** Sie vervollständigen `volume.c`, ein Programm, das die Lautstärke einer WAV-Audiodatei um einen Faktor ändert.
- **Datei:** `volume.c` im Ordner `volume`.
- **Aufruf:** `./volume input.wav output.wav 2.0` – Eingabedatei, Ausgabedatei, Faktor.
- **Ausgabe:** die Datei `output.wav` mit veränderter Lautstärke; auf dem Bildschirm erscheint nichts.
- **Ihre Arbeit:** die beiden TODOs in `volume.c`: den Header kopieren und alle Samples mit dem Faktor multipliziert in die Ausgabedatei schreiben. Argumente prüfen, Dateien öffnen und den Faktor umwandeln erledigt `volume.c` bereits.

### So ist eine WAV-Datei aufgebaut

[WAV](https://docs.fileformat.com/audio/wav/) ist ein gängiges Dateiformat für Audiodaten.

1. **Header:** Die ersten 44 Byte sind der Header (bei den Dateien dieser Aufgabe immer genau 44). Er beschreibt die Datei, etwa ihre Größe, die Zahl der Samples pro Sekunde und die Größe eines Samples. Er wird unverändert in die Ausgabedatei übernommen.
2. **Samples:** Danach folgen bis zum Dateiende nur noch Samples. Ein Sample ist der Wert des Audiosignals zu einem Zeitpunkt – im Bild oben ein Punkt der Welle.
3. **Größe eines Samples:** Jedes Sample ist eine ganze Zahl mit Vorzeichen aus 2 Byte (16 Bit), also zwischen −32768 und 32767. In C passt dazu der Typ `int16_t`.
4. **Lautstärke:** Multipliziert man jedes Sample mit demselben Faktor, ändert sich die Lautstärke: Ein Faktor über 1 macht die Datei lauter, einer unter 1 leiser.
5. **Nachkommastellen:** Das Produkt ist eine Kommazahl. Beim Speichern in einem `int16_t` fallen die Nachkommastellen weg (es wird nicht gerundet, sondern in Richtung 0 abgeschnitten).
6. **Wertebereich:** Ein Produkt außerhalb von −32768 bis 32767 passt nicht in ein `int16_t`. Solche Faktoren müssen Sie nicht abfangen.

**Beispiel** mit drei Samples:

| Sample | mal 0.5         | mal 2.0 |
| ------ | --------------- | ------- |
| 1000   | 500             | 2000    |
| −3     | −1.5 → −1       | −6      |
| 7      | 3.5 → 3         | 14      |

Was genau im Header steht und warum Faktor 2.0 nicht „doppelt so laut“ klingt, steht am Ende der Seite unter „Zum Weiterlesen“.

## Demo

[Terminal-Aufzeichnung ansehen](https://asciinema.org/a/mc2hPhYDt1spoMjlqNMuqC4Uc)

## Aufgabenmaterial

Für diese Aufgabe werden Sie ein von uns zur Verfügung gestelltes Codegerüst vervollständigen.

**Aufgabenmaterial herunterladen**

Öffnen Sie VS Code entsprechend Ihrem [Setup](https://dev.inf.zone/extras/setup/).

Öffnen Sie Ihr Terminalfenster und führen Sie dann `cd` aus. Die Eingabeaufforderung Ihres Terminalfensters sollte wie folgt aussehen:

```bash
$
```

Zur Erinnerung: Mit `Strg+Shift+V` (bei Windows, ansonsten rechter Mausklick in das Terminalfenster) fügen Sie kopierten Text in die Kommandozeile ein. Frühere Befehle holen Sie mit den Pfeiltasten (hoch/runter) oder `Strg+R` zurück.

Geben Sie dann

```bash
wget https://dev.inf.zone/download/exercises/07/volume.zip
```

ein und führen Sie den Befehl mit der Eingabetaste aus, um eine ZIP-Datei namens `volume.zip` in den aktuellen Ordner herunterzuladen. Achten Sie darauf, dass Sie das Leerzeichen zwischen `wget` und der folgenden URL nicht übersehen, und auch kein anderes Zeichen!

Führen Sie jetzt

```bash
unzip volume.zip
```

aus, um das ZIP-Archiv in einen Ordner namens `volume` zu extrahieren. Sie brauchen die ZIP-Datei nicht mehr, also können Sie

```bash
rm volume.zip
```

ausführen.
Antworten Sie mit "y", gefolgt von der Eingabetaste, um die heruntergeladene ZIP-Datei zu entfernen.

Führen Sie dann

```bash
cd volume
```

aus, um in dieses Verzeichnis zu wechseln. Ihre Eingabeaufforderung sollte nun wie folgt aussehen:

```bash
volume/ $
```

Wenn alles funktioniert hat, sollten Sie nach dem Ausführen von

```bash
ls
```

die Dateien `volume.c` und `input.wav` sehen. Wenn Sie `code volume.c` ausführen, sollte sich die Datei öffnen. In diese Datei werden Sie Ihren Code für diese Aufgabe einfügen. Wenn diese Datei nicht angezeigt wird, verfolgen Sie Ihre Schritte zurück und schauen Sie, ob Sie herausfinden können, wo Sie einen Fehler gemacht haben!

**Überblick über `volume.c`**

`volume.c` erledigt schon alles außer den beiden TODOs:

-   **Argumente:** Ist `argc` ungleich 4 (Programmname plus drei Argumente), gibt das Programm `Usage: ./volume input.wav output.wav factor` aus und beendet sich mit Statuscode 1.
-   **Dateien öffnen:** `argv[1]` wird mit [`fopen`](https://manual.cs50.io/3/fopen) zum Lesen geöffnet (`input`), `argv[2]` zum Schreiben (`output`). Liefert `fopen` `NULL`, erscheint `Could not open file.` und das Programm endet mit 1.
-   **Faktor:** `float factor = atof(argv[3]);` wandelt das dritte Argument in eine Kommazahl um.
-   **Header-Größe:** Die Konstante `HEADER_SIZE` ist 44.
-   **Schließen:** Am Ende schließt `fclose` beide Dateien. Wann immer Sie `fopen` aufrufen, gehört später ein `fclose` dazu.

Dazwischen stehen Ihre beiden Aufgaben:

```c
// TODO: Copy header from input file to output file

// TODO: Read samples from input file and write updated data to output file
```

## Spezifikation

Vervollständigen Sie `volume.c` so, dass es die Lautstärke von `input` um `factor` ändert und das Ergebnis in `output` speichert. Bei `factor` gleich `2.0` werden zum Beispiel alle Samples verdoppelt.

-   Lesen Sie zuerst die 44 Byte des Headers aus der Eingabedatei und schreiben Sie sie unverändert in die Ausgabedatei.
    -   Jedes Byte des Headers können Sie als `uint8_t` behandeln: eine vorzeichenlose (`u` für *unsigned*), also nicht negative, ganze Zahl aus 8 Bit (daher die `8`), genau ein Byte. `uint8_t` ist in `<stdint.h>` deklariert.
-   Lesen Sie danach die restlichen Daten ein Sample nach dem anderen, multiplizieren Sie jedes mit `factor` und schreiben Sie das neue Sample in die Ausgabedatei.
    -   Gehen Sie davon aus, dass die WAV-Datei 16-Bit-Samples mit Vorzeichen enthält. (WAV-Dateien können auch andere Sample-Größen haben, in dieser Aufgabe nicht.)
    -   Jedes Sample können Sie als `int16_t` behandeln: eine ganze Zahl mit Vorzeichen, also positiv oder negativ, aus 16 Bit. `int16_t` ist ebenfalls in `<stdint.h>` deklariert.
-   Ihr Programm darf, falls es `malloc` verwendet, keine Memory-Leaks haben. `malloc` ist für diese Aufgabe aber nicht nötig.

## Hilfestellung

Klicken Sie auf die folgenden Tipps, um einige Ratschläge zu erhalten. Beide Tipps zeigen Schritt für Schritt den Code **einer möglichen Lösung**. Schauen Sie sie deshalb erst an, wenn Sie die Aufgabe ernsthaft selbst versucht haben. Nicht jede Aufgabe hat einen so ausführlichen Lösungsweg; meist ist eine solche Aufgabe eine Aufwärmübung für eine schwierigere, die später folgt.

**WAV-Header von Eingabedatei in Ausgabedatei kopieren**

Ihr erstes TODO: den Header aus `input` lesen und in `output` schreiben.

Bisher kennen Sie Typen wie `int`, `bool`, `char`, `double`, `float` und `long`. In `<stdint.h>` gibt es weitere Typen, bei denen Größe (in Bit) und Vorzeichen genau festgelegt sind. Für WAV-Dateien brauchen Sie zwei davon (siehe [Spezifikation](#details-zur-umsetzung)): `uint8_t` für ein Byte des Headers und `int16_t` für ein Sample.

Für den Header brauchen Sie ein Array von Bytes. Ein Array aus `n` Bytes legen Sie so an:

```c
uint8_t header[n];
```

Für `n` setzen Sie die Größe des Headers ein, also `HEADER_SIZE`. Dann können Sie `header` als Argument für [`fread`](https://manual.cs50.io/3/fread) und [`fwrite`](https://manual.cs50.io/3/fwrite) verwenden: erst den Header aus der Eingabedatei in `header` einlesen, dann von `header` in die Ausgabedatei schreiben.

Achtung, Spoiler! Mit diesem Code ist das erste TODO erledigt:

```c
// Copy header from input file to output file
uint8_t header[HEADER_SIZE];
fread(header, HEADER_SIZE, 1, input);
fwrite(header, HEADER_SIZE, 1, output);
```

**Aktualisierte Daten in die Ausgabedatei schreiben**

Ihr zweites TODO: die Samples aus `input` lesen, ändern und in `output` schreiben. Beim Lesen von Dateien legt man üblicherweise einen „Puffer“ an, der die Daten vorübergehend aufnimmt. Dort ändern Sie die Daten und schreiben sie dann in die neue Datei.

Für ein einzelnes Sample genügt eine Variable vom Typ `int16_t`:

```c
// Create a buffer for a single sample
int16_t buffer;
```

In diesen Puffer lesen Sie mit `fread` ein Sample nach dem anderen. Als Argument übergeben Sie `&buffer`, die Adresse der Variablen:

```c
// Create a buffer for a single sample
int16_t buffer;

// Read single sample into buffer
fread(&buffer, sizeof(int16_t), 1, input);
```

Um die Lautstärke des Samples zu ändern, multiplizieren Sie es mit `factor`:

```c
// Create a buffer for a single sample
int16_t buffer;

// Read single sample into buffer
fread(&buffer, sizeof(int16_t), 1, input);

// Update volume of sample
buffer *= factor;
```

Und schließlich schreiben Sie das neue Sample in `output`:

```c
// Create a buffer for a single sample
int16_t buffer;

// Read single sample from input into buffer
fread(&buffer, sizeof(int16_t), 1, input);

// Update volume of sample
buffer *= factor;

// Write updated sample to new file
fwrite(&buffer, sizeof(int16_t), 1, output);
```

Das muss sich wiederholen, solange noch Samples zu lesen sind:

-   Jeder Aufruf von `fread` liest dort weiter, wo der vorige aufgehört hat. Die geöffnete Datei merkt sich ihre aktuelle Position.
-   `fread` gibt laut [Dokumentation](https://manual.cs50.io/3/fread) zurück, wie viele Elemente (hier: Samples) es vollständig gelesen hat. Am Dateiende ist das 0.
-   Sie können `fread` also direkt in die Bedingung einer `while`-Schleife schreiben:

```c
while (fread(...))
{

}
```

Achtung, Spoiler! So ist das zweite TODO erledigt:

```c
// Create a buffer for a single sample
int16_t buffer;

// Read single sample from input into buffer while there are samples left to read
while (fread(&buffer, sizeof(int16_t), 1, input) != 0)
{
    // Update volume of sample
    buffer *= factor;

    // Write updated sample to new file
    fwrite(&buffer, sizeof(int16_t), 1, output);
}
```

Da C Werte ungleich 0 als `true` und 0 als `false` behandelt, geht es noch kürzer:

```c
// Create a buffer for a single sample
int16_t buffer;

// Read single sample from input into buffer while there are samples left to read
while (fread(&buffer, sizeof(int16_t), 1, input))
{
    // Update volume of sample
    buffer *= factor;

    // Write updated sample to new file
    fwrite(&buffer, sizeof(int16_t), 1, output);
}
```

## Testen

Ihr Programm sollte sich wie in den folgenden Beispielen verhalten:

```bash
./volume input.wav output.wav 2.0
```

Wenn Sie `output.wav` anhören, sollte die Datei deutlich lauter sein als `input.wav`.

```bash
./volume input.wav output.wav 0.5
```

Jetzt sollte `output.wav` deutlich leiser sein als `input.wav`.

Zum Anhören machen Sie einen Rechtsklick auf `output.wav` im Dateibrowser, wählen `Herunterladen...` und öffnen die Datei in einem Audio-Player auf Ihrem Computer. Bei einer lokalen Installation mit Docker können Sie Audio-Dateien auch direkt in VS Code abspielen.

### Korrektheit

Führen Sie in Ihrem Terminal den folgenden Befehl aus, um die Korrektheit Ihrer Arbeit zu überprüfen:

```bash
check50 -l inf-zone/exercises/2026/volume
```

### Style

Führen Sie den folgenden Befehl aus, um den Stil Ihres Codes mit `style50` zu analysieren:

```bash
style50 volume.c
```

## Abgeben

Geben Sie im Ordner `volume` ab:

```bash
inf upload volume
```

Danach sehen Sie, welche Tests Ihr Programm besteht und ob die Übung für die Bonuspunkte zählt. Wie Sie `inf` installieren und sich anmelden, steht unter [Abgeben mit `inf`](https://dev.inf.zone/faq/uebung-solutions/#inf-installieren).

## Zum Weiterlesen: Header und Lautstärke

Für die Lösung der Aufgabe nicht nötig.

**Was im Header von `input.wav` steht:**

| Byte  | Inhalt                            | Wert in `input.wav`         |
| ----- | --------------------------------- | --------------------------- |
| 0–3   | Kennung                           | `RIFF`                      |
| 8–11  | Format                            | `WAVE`                      |
| 22–23 | Zahl der Kanäle                   | 2 (Stereo)                  |
| 24–27 | Samples pro Sekunde und Kanal     | 44100                       |
| 34–35 | Bit pro Sample                    | 16                          |
| 40–43 | Größe der Audiodaten in Byte      | 352800                      |

Bei Stereo wechseln sich die Samples für den linken und den rechten Kanal ab. Für Ihr Programm spielt das keine Rolle, weil alle Samples mit demselben Faktor multipliziert werden. Viele WAV-Dateien in freier Wildbahn haben übrigens längere Header mit zusätzlichen Abschnitten; die 44 Byte gelten nur für den einfachsten Aufbau, wie ihn die Dateien dieser Aufgabe haben.

**Warum Faktor 2.0 nicht doppelt so laut klingt:** Der Faktor verdoppelt die Ausschläge der Welle (die Amplitude). Das entspricht etwa 6 Dezibel mehr. Als „doppelt so laut“ empfinden Menschen nach einer gängigen Faustregel erst etwa 10 Dezibel mehr, also ungefähr die dreifache Amplitude.

**Was bei zu großen Faktoren passiert:** Die Samples in `input.wav` liegen zwischen −8185 und 8117; bis Faktor 4 passt also jedes Ergebnis in ein `int16_t`. Darüber sprengen die lautesten Stellen den Wertebereich. Der C-Standard legt für diesen Fall kein Ergebnis fest, in der Praxis entstehen falsche Werte, die als Knacken und Verzerrung zu hören sind. Ein Programm, das das vermeiden will, müsste zu große Ergebnisse auf 32767 bzw. −32768 begrenzen.
