# `TuningFork.Sfz`
[🔗](https://github.com/jaman/tuning_fork/blob/v0.1.7/tuning_fork/lib/tuning_fork/sfz.ex#L1)

SFZ instruments: a text map of recordings by key, velocity and round robin, read into a
pitched `TuningFork.Sample.Bank` the kit plays by name, with the recordings fetched
one by one as notes ask for them.

    {:ok, "fingerbass", 39} = TuningFork.Sfz.load("fingerbass")
    TuningFork.Kit.voice(%{s: "fingerbass", note: "e2"}, 0.5)

`instruments/0` is the library's own list — free instruments on GitHub, each with its
licence and who to credit — plus whatever the application adds under the `:sfz` key of
the `:tuning_fork` environment in the same shape; `load/2` takes one of those names, a
`github:` source, a URL or a local path. The kit loads a listed name itself the first
time a pattern plays it, so `s("fingerbass")` in Strudel needs nothing more.

    config :tuning_fork, sfz: %{"ours" => %{source: "priv/sfz/ours.sfz", licence: "CC0 1.0", credit: "us", what: "a bell"}}

What is read of the format: `<control>`, `<global>`, `<master>`, `<group>` and
`<region>` headers, opcodes on any line in any number, values with spaces, `//`
comments, `#define` and `#include`, `default_path`, `sample`, `key`, `lokey`, `hikey`,
`pitch_keycenter`, `lovel`, `hivel`, `tune`, `transpose`, `volume`, `loop_mode`,
`loop_start`, `loop_end`, `trigger`, `sw_default`, `sw_last` and `locc`. A bank takes one
velocity layer (the regions whose range holds `:velocity`, default 100), every round
robin of a note as one of its files, and leaves out release triggers, regions that need
a controller raised (a pedal, a keyswitch away from the default) and, with `:keys`,
regions outside a range. Envelopes, filters, modulation and controllers are not read.

# `bank`

```elixir
@type bank() :: %{required(float()) =&gt; [entry()]}
```

# `entry`

```elixir
@type entry() :: {String.t(), keyword()}
```

# `region`

```elixir
@type region() :: %{required(String.t()) =&gt; String.t()}
```

# `t`

```elixir
@type t() :: %{control: %{required(String.t()) =&gt; String.t()}, regions: [region()]}
```

# `bank`

```elixir
@spec bank(t(), keyword()) :: bank()
```

The bank a parsed file maps: each recorded pitch, as a MIDI number that may be
fractional, to its files in order of round robin, each `{file, opts}` with `:loop` and
`:gain` as `TuningFork.Sample.Bank.put/3` takes them. Files are resolved against
`:base`, the file's own path or URL, and `default_path`.

## Options

  * `:base` — required; the path or URL the file was read from
  * `:velocity` — the layer to take, default 100
  * `:keys` — a range of MIDI numbers; regions whose key centre lies outside it are left out

# `instrument?`

```elixir
@spec instrument?(String.t()) :: boolean()
```

Whether `name` is one of `instruments/0`.

# `instruments`

```elixir
@spec instruments() :: %{
  required(String.t()) =&gt; %{
    source: String.t(),
    licence: String.t(),
    credit: String.t(),
    what: String.t()
  }
}
```

The instruments known by name, the library's and the application's: each a `:source` for `load/2`, its `:licence`, who to `:credit` and `:what` it is.

# `load`

```elixir
@spec load(String.t(), keyword()) ::
  {:ok, String.t(), non_neg_integer()} | {:error, term()}
```

Register an instrument with the bank: one of `instruments/0` by name, or a source as
`url/1` takes it, or a local path. The file and its includes are read now; the
recordings are fetched as they are played, or all at once with `:prefetch`.

Returns `{:ok, name, notes}` with how many notes the bank holds, or `{:error, reason}`.

## Options

  * `:name` — the bank name, default the instrument's name or the file's basename
  * `:velocity`, `:keys` — as `bank/2` takes them; a listed instrument brings its own `:keys`
  * `:prefetch` — start fetching every recording in the background, default `false`

# `parse`

```elixir
@spec parse(String.t(), keyword()) :: t()
```

The file as headers merged down to regions: `:control` is the `<control>` opcodes, and
each of `:regions` carries its own opcodes over its group's, master's and the global
ones, with `#define` names replaced. `:include` is a function of an `#include` path
returning `{:ok, text}` or `:error`; without it, or when it says `:error`, the include is
left out.

# `url`

```elixir
@spec url(String.t()) :: String.t()
```

The URL of a source: `github:user/repo/branch/path/to/file.sfz` becomes the raw file on
GitHub, a URL is kept, and either has its spaces and other reserved characters escaped.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
