TuningFork.Sfz (TuningFork v0.1.11)

Copy Markdown View Source

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)

The library carries no instruments. The application registers the ones it uses — register/2, or the :sfz key of the :tuning_fork environment in the same shape — each with its source, its licence and who to credit; instruments/0 lists them. load/2 takes a registered name, a github: source, a URL or a local path. The kit loads a registered name itself the first time a pattern plays it, so s("fingerbass") in Strudel needs nothing more than the registration.

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.

Summary

Types

An instrument by name: where its SFZ file is, its licence, who to credit, what it is, and the keys to read.

t()

Functions

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.

Whether name is one of instruments/0.

The instruments the application has registered, by name: each a :source for load/2, its :licence, who to :credit, :what it is and, when given, the :keys read. The library registers none of its own.

Read an instrument into 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.

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.

Register every instrument in instruments, a map of name to instrument as register/2 takes them.

Register an instrument under name, so load/2 and the kit know it by that name. instrument must carry :source, :licence, :credit and :what; :keys is optional.

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.

Types

bank()

@type bank() :: %{required(float()) => [entry()]}

entry()

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

instrument()

@type instrument() :: %{
  :source => String.t(),
  :licence => String.t(),
  :credit => String.t(),
  :what => String.t(),
  optional(:keys) => Range.t()
}

An instrument by name: where its SFZ file is, its licence, who to credit, what it is, and the keys to read.

region()

@type region() :: %{required(String.t()) => String.t()}

t()

@type t() :: %{control: %{required(String.t()) => String.t()}, regions: [region()]}

Functions

bank(map, opts)

@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?(name)

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

Whether name is one of instruments/0.

instruments()

@spec instruments() :: %{required(String.t()) => instrument()}

The instruments the application has registered, by name: each a :source for load/2, its :licence, who to :credit, :what it is and, when given, the :keys read. The library registers none of its own.

load(source, opts \\ [])

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

Read an instrument into 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 registered instrument brings its own :keys
  • :prefetch — start fetching every recording in the background, default false

parse(text, opts \\ [])

@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.

register(instruments)

@spec register(%{required(String.t()) => instrument()}) :: :ok

Register every instrument in instruments, a map of name to instrument as register/2 takes them.

register(name, instrument)

@spec register(String.t(), instrument()) :: :ok

Register an instrument under name, so load/2 and the kit know it by that name. instrument must carry :source, :licence, :credit and :what; :keys is optional.

TuningFork.Sfz.register("fingerbass", %{
  source: "github:freepats/electric-bass-YR/master/FingerBassYR 20190930.sfz",
  licence: "CC0 1.0",
  credit: "FreePats, Yamaha RBX bass",
  what: "an electric bass, fingered"
})

url(url)

@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.