# files

> Files from @audio-ui/engine: an audio file decoded into samples at a context's rate, sound written to WAV, FLAC or MP3, notes read from and written to a MIDI file, and a score read from and written to MusicXML.

```bash
npm install @audio-ui/engine
```

```ts
import { decodeAudioChannels, decodeAudioSamples, downloadFile, encodeAudio, downloadMidiFile, /* … */ } from "@audio-ui/engine/files";
```

- Used by: `audio-export`
- audio.js: [`@audio/decode-aiff`](https://audio.js.org/modules/decode-aiff/), [`@audio/decode-caf`](https://audio.js.org/modules/decode-caf/), [`@audio/decode-mp4`](https://audio.js.org/modules/decode-mp4/), [`@audio/encode`](https://audio.js.org/modules/encode/), [`@audio/midi-parse`](https://audio.js.org/modules/midi-parse/), [`@audio/midi-write`](https://audio.js.org/modules/midi-write/), [`@audio/musicxml`](https://audio.js.org/modules/musicxml/), [`@audio/resample-sinc`](https://audio.js.org/modules/resample-sinc/)

| Export | Kind | What it is |
| --- | --- | --- |
| `decodeAudioChannels` | function | Decodes raw audio bytes, such as `file.arrayBuffer()`, into every channel's PCM samples at the context's sample rate, ready for `computePeaks`. |
| `decodeAudioSamples` | function | Decodes raw audio bytes into one channel's PCM samples, ready for `downsampleWaveform`: `decodeAudioChannels`, then the channel asked for, or the last there is. |
| `AudioFileFormat` | type | The formats `encodeAudio` writes. |
| `downloadFile` | function | Saves a file through the browser's downloads, named `name`. |
| `EncodeAudioOptions` | interface | What `encodeAudio` writes: the format, its bit depth or bitrate, the channels' sample rate, and a callback for its progress. |
| `encodeAudio` | function | Encodes channels of samples, from -1 to 1, as a file. |
| `downloadMidiFile` | function | Saves the notes as a `.mid` file, named `filename`. |
| `MIDI_FILE_ACCEPT` | const | What a file input or a drop zone accepts as a MIDI file. |
| `MidiFileGrid` | interface | The grid notes are read onto: steps a quarter note, the clip's length in steps, and the pitch and tempo ranges it takes. |
| `readMidiFile` | function | Reads a MIDI file's notes onto a grid of steps, with the file's own tempo (clamped to the BPM range). |
| `readMidiFileParts` | function | Reads a MIDI file as parts, for a host that lays each on a track of its own: a part per track and channel, named after its track (and its channel, when a track holds several), its notes in beats as they were written, not quantized and not cut, at least a tick long, and the first program its channel plays. |
| `ScoreNote` | interface | A note of a score in beats: where it starts and how long it lasts, in quarter notes. |
| `ScorePart` | interface | One part of a score: its name, whether it is drums, and its notes in beats. |
| `writeMidiFile` | function | The notes as a Standard MIDI File, the inverse of `readMidiFile`. |
| `downloadMusicXmlFile` | function | Saves the notes as a `.musicxml` file, named `filename`. |
| `MUSICXML_FILE_ACCEPT` | const | What a file input or a drop zone accepts as a MusicXML file: a score, or one compressed as `.mxl`. |
| `MusicXmlFileGrid` | interface | The grid notes are read onto: steps a quarter note, the clip's length in steps, and the pitch and tempo ranges it takes. |
| `readMusicXmlFile` | function | Reads a MusicXML score's notes onto a grid of steps, with its first tempo (clamped to the BPM range), every part into the one clip. |
| `readMusicXmlFileParts` | function | Reads a MusicXML score as parts, for a host that lays each on a track of its own: a part each, by its name, its notes in beats as they were written, through changes of tempo, not quantized and not cut. |
| `writeMusicXmlFile` | function | The notes as a MusicXML score, the inverse of `readMusicXmlFile`, for a notation app to open: on the grid they sit on, so nothing moves. |

## Audio files

A file's audio as samples at the context's rate, one array per channel: what the browser decodes, and AIFF, CAF or Apple Lossless where it cannot (Chrome does not), with audio.js decoders.

```ts
import { decodeAudioChannels } from "@audio-ui/engine/files";

const channels = await decodeAudioChannels(ctx, await file.arrayBuffer());
```

The samples are at the context's sample rate, so they play as they are and `computePeaks` (from `@audio-ui/engine/analysis`) draws them. The waveform player and the arrangement decode dropped files this way.

## Writing a file

Sound saved as a file: WAV (16 or 24-bit, or 32-bit float), FLAC or MP3 at a bitrate, encoded in chunks with its progress, from what an `OfflineAudioContext` rendered or a recording kept.

```ts
import { downloadFile, encodeAudio } from "@audio-ui/engine/files";

const file = await encodeAudio(channels, { bitDepth: 24, format: "wav", sampleRate, onProgress: (done) => setProgress(done) });
downloadFile(file, "mix.wav");
```

**In React**

[`audio-export`](/docs/lib/audio-export) adds loudness normalization on top of it.

## MIDI files

```tsx
"use client";

import type { Note } from "@audio-ui/core/piano-roll";
import {
  downloadMidiFile,
  MIDI_FILE_ACCEPT,
  readMidiFile,
} from "@audio-ui/engine/files";
import { type ChangeEvent, useCallback, useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import {
  PianoRoll,
  PianoRollGrid,
  PianoRollKeys,
  PianoRollStrip,
  PianoRollViewport,
} from "@/components/audio-ui/piano-roll";
import { Timeline } from "@/components/audio-ui/timeline";

const DURATION = 32;
const MIN_PITCH = 48;
const MAX_PITCH = 84;

const PHRASE: Note[] = [
  { id: "1", length: 4, pitch: 60, start: 0, velocity: 0.9 },
  { id: "2", length: 4, pitch: 64, start: 4, velocity: 0.6 },
  { id: "3", length: 4, pitch: 67, start: 8, velocity: 0.8 },
  { id: "4", length: 4, pitch: 72, start: 12, velocity: 1 },
  { id: "5", length: 8, pitch: 65, start: 16, velocity: 0.7 },
  { id: "6", length: 8, pitch: 62, start: 24, velocity: 0.6 },
];

/** Export the clip as a .mid file, or import one onto its grid, with the file's tempo. */
export function PianoRollMidiFileDemo() {
  const [notes, setNotes] = useState(PHRASE);
  const [bpm, setBpm] = useState(120);
  const inputRef = useRef<HTMLInputElement>(null);

  const openFile = useCallback(() => inputRef.current?.click(), []);
  const exportFile = useCallback(
    () => downloadMidiFile(notes, { bpm, filename: "clip.mid" }),
    [bpm, notes]
  );
  const importFile = async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    event.target.value = "";
    if (file) {
      const imported = await readMidiFile(file, {
        duration: DURATION,
        maxPitch: MAX_PITCH,
        minPitch: MIN_PITCH,
      });
      setNotes(imported.notes);
      setBpm(imported.bpm);
    }
  };

  return (
    <div className="flex w-full flex-col gap-3">
      <div className="flex items-center justify-between gap-2">
        <p className="font-mono text-muted-foreground text-xs tabular-nums">
          {bpm} BPM
        </p>
        <div className="flex gap-2">
          <input
            accept={MIDI_FILE_ACCEPT}
            className="sr-only"
            onChange={importFile}
            ref={inputRef}
            type="file"
          />
          <Button onClick={openFile} size="sm" variant="outline">
            Import
          </Button>
          <Button disabled={notes.length === 0} onClick={exportFile} size="sm">
            Export
          </Button>
        </div>
      </div>
      <Timeline duration={DURATION} snapStep={1}>
        <PianoRoll
          className="h-64 rounded-md border"
          maxPitch={MAX_PITCH}
          minPitch={MIN_PITCH}
          onValueChange={setNotes}
          value={notes}
        >
          <PianoRollViewport>
            <PianoRollStrip className="flex-1" lead={<PianoRollKeys />}>
              <PianoRollGrid accentStep={4} aria-label="Notes" gridStep={1} />
            </PianoRollStrip>
          </PianoRollViewport>
        </PianoRoll>
      </Timeline>
    </div>
  );
}
```

The `midi-file` library reads a `.mid` file onto the roll's steps and writes one back.

```tsx
import { downloadMidiFile, MIDI_FILE_ACCEPT, readMidiFile } from "@audio-ui/engine/files";

<input
  accept={MIDI_FILE_ACCEPT}
  onChange={async (event) => {
    const file = event.target.files?.[0];
    if (file) {
      const { bpm, notes } = await readMidiFile(file, {
        duration: 64,
        maxPitch: 84,
        minPitch: 36,
      });
      setNotes(notes);
      setBpm(bpm);
    }
  }}
  type="file"
/>
<Button onClick={() => downloadMidiFile(notes, { bpm, filename: "clip.mid" })}>
  Export
</Button>
```

`readMidiFile` puts each note on the nearest step, at least one step long, drops the notes starting past `duration`, keeps the pitches between `minPitch` and `maxPitch` and returns the file's tempo, kept between `minBpm` and `maxBpm` (20 and 200 by default). It takes any `File` or `Blob`, so a drop works the same as an input. `writeMidiFile` goes the other way, at 480 ticks a quarter note, and leaves deactivated notes out; `downloadMidiFile` writes the file and saves it. A step is a sixteenth note: set `stepsPerQuarter` on both sides for another grid.

For a host that lays each part on a track of its own, such as a DAW, `readMidiFileParts(file)` reads a file as parts instead: one per track and channel, named after its track, with the General MIDI program it asks for and whether it is the drums of channel 10, its notes in beats as they were written, not quantized. The file is parsed by `@audio/midi-parse`.

## MusicXML files

A MusicXML score onto a roll's steps, a part at a time, and notes back out as a score: what notation software reads and writes.

```ts
import { downloadMusicXmlFile, readMusicXmlFile } from "@audio-ui/engine/files";

const { bpm, notes } = await readMusicXmlFile(file, { duration: 64, maxPitch: 84, minPitch: 36 });
downloadMusicXmlFile(notes, { bpm, filename: "clip.musicxml" });
```

`readMusicXmlFileParts` gives each part on its own; `writeMusicXmlFile` the score as a string. For a `.mid` file, see [`midi-file`](/docs/engine/files#midi-files).
