# `Mx.Instrument`
[🔗](https://github.com/mx-music/mx/blob/main/lib/mx/instrument.ex#L1)

Synths written in Elixir, as `defsynth`, which mxs builds when a note starts; the guide "Instruments" introduces them.

A module that uses `Mx.Instrument` declares each synth's options with `attr` lines, as a Phoenix function component declares its assigns, and `envelope_attrs/1` for the seven options of the note's envelope.
`defsynth` defines a synth as a function of one map, the note's options, with clauses that match on it; `defsound` and `defsoundp` define pieces a synth calls.
`defeffect` defines an effect as a function of the sound coming in, one channel, and a map of its options, and a song applies it by name with `fx:`.

When the module compiles, each clause runs once with the note's options as placeholders, and what its body does with them is recorded as a graph.
Data written in the module takes any Elixir, while a placeholder takes only arithmetic, comparisons, `select/3` and the vocabulary of `Mx.Instrument.Vocabulary`.
An `if` on a placeholder fails then, with its file and line, and so does a synth that ends with neither `note_envelope/2` nor `rings/2`.
A module compiled outside mix's own compilation, such as one a song file defines, becomes known to mx as it compiles: songs play its synths by name, and mxs builds them from then on, so a `defsynth` saved in a song plays without restarting mx.

An option is read once, when the note starts, unless the synth reads it with `follow/1`, which glides with each change the song makes while the note sounds.

# `attr`
*macro* 

Declares an option of the next `defsynth`.

An `attr` line applies to the next `defsynth` after it, and only that one.
Declare the options again before each synth.
The synth's documentation lists them under "Options", so a `@doc` written above the first `attr` line gains that section.

`name` is an atom, the key the synth reads from its map: `played.vel` reads the option `:vel`.
`type` says what the value means: `:float`, `:note` for a MIDI note number, `:beats` for a length in beats, or `:atom` for a choice among names.

## Options

  * `:default` - the value a note gets when its song gives none.
  * `:range` - the lowest and highest value the option takes, as a range literal such as `0..1`.
    A song that sets a value outside it is refused when the song is saved.
  * `:values` - for an `:atom` option, the names it takes, as a list.
  * `:required` - when `true`, the option is marked as one a note has to set.
    Defaults to `false`.
  * `:doc` - one sentence saying what the option does, for the synth's documentation and catalog.

## Examples

    @doc "A marimba note."
    attr :note, :note, default: 52, doc: "The pitch."
    attr :vel, :float, default: 0.6, range: 0.05..1, doc: "How hard the bar is struck."
    defsynth marimba(hit) do
      ...
    end

An option that picks among names lists them in `values:`.
Clauses of the synth can then match on each name:

    attr :bol, :atom, values: [:na, :tun, :te], default: :na, doc: "The stroke."

# `defeffect`
*macro* 

Defines an effect, a function of the sound coming in and a map of its options, which returns the sound going out.

`attr` lines before it declare its options, as for a synth.
The effect shapes each channel on its own, so the sound coming in is one channel, and a song applies it by name with `fx:`.

## Examples

    attr :level, :float, default: 0.5, range: 0..1, doc: "How loud the sound comes out."
    defeffect quieter(sound, opts) do
      sound |> gain(opts.level)
    end

# `defsound`
*macro* 

Defines a piece of sound other modules' synths can call.

It is a public function whose body takes the same arithmetic as a synth's.
Use `defsoundp/2` for a piece only this module needs.

## Examples

    defsound mallet(hz, corner, slope), do: slope * max(log2(hz / corner), 0)

# `defsoundp`
*macro* 

Defines a piece of sound only this module's synths call.

It is the private form of `defsound/2`, so other modules cannot call it.

## Examples

    defsoundp above(hz, corner), do: max(log2(hz / corner), 0)

    defsoundp mallet(hz, corner, slope) do
      slope * above(hz, corner)
    end

# `defsynth`
*macro* 

Defines a clause of a synth, a function of the note's options that returns its sound.

The argument is a pattern over the map of options.
The synth is named after the function, and a song plays it by that name.
Clauses of one synth sit next to each other, and the one whose pattern matches the note plays.
Mx records a match only on atoms and numbers, such as `%{bol: :na}`.

The body takes the arithmetic, comparisons and `select/3` described in the moduledoc, and ends with `note_envelope/2` or `rings/2`.
Use `select/3` where you would write an `if` on an option.

## Examples

One clause, reading its options by key:

    attr :note, :note, default: 52, doc: "The pitch."
    envelope_attrs()
    defsynth beep(played) do
      sine(hz: hz(played.note))
      |> note_envelope(played)
    end

A clause per stroke, matched on the option's value:

    attr :bol, :atom, values: [:na, :tun], default: :na, doc: "The stroke."
    defsynth drum(%{bol: :na} = hit), do: ...
    defsynth drum(%{bol: :tun} = hit), do: ...

# `envelope_attrs`
*macro* 

Declares the seven options of the note's envelope: `attack`, `decay`, `sustain`, `release`, `attack_level`, `decay_level` and `sustain_level`.

Each has the usual default and documentation, as the module's `@envelope` lists them.
Pass a keyword list to give some of them another default; the others keep the usual one.
Like `attr/3`, it applies to the next `defsynth`.

## Examples

The usual envelope:

    envelope_attrs()

A note that holds a long time and fades in 0.3 beats:

    envelope_attrs sustain: 1000, release: 0.3

# `known`

Makes a module's synths known to mx: songs can play them, and a running mxs engine builds them.

A module compiled outside mix's own compilation becomes known on its own, so call this only for a module compiled some other way.
Calling it again for the same module is harmless.

## Examples

    Mx.Instrument.known(Mx.Instruments.Flute)

# `matched`

The options the clauses of a known synth written in Elixir match on.

# `modules`

The modules whose synths mx knows, in the order they became known.

## Examples

    Mx.Instrument.modules()
    #=> [Mx.Instruments.Marimba, Mx.Instruments.Flute]

# `register`

Registers each synth and effect of `module` with the mxs engine `engine`, as mx's NIF module, Mx.Engine.Mxs.Native, starts it.

Returns `:ok` once every one is registered, or `{:error, reason}` at the first the engine refuses; the ones after it are not registered.
`known/1` calls it for a running engine, so call it yourself only with an engine you started.

# `specs`

A synth's options as mxs declares them, for its catalog.

`synth` is one entry of a module's `__synths__/0`.
Each option becomes a map with its `name`, `kind`, `values`, `default`, `default_name`, `low`, `high`, `required` and `doc`.
Numbers are floats, and a missing default, low or high is `nil`.

---

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