synthdown

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 18, 2024 License: BSD-3-Clause Imports: 7 Imported by: 0

README

Go Report Card Quality Gate Status Vulnerabilities Bugs Security Rating Coverage Status

synthdown

Synthdown is:

  1. A notation format for describing modular synthesiser patches
  2. A reference implementation of a parser for this notation
  3. A CLI that reads valid notations and generates all kinds of diagrams and stuff

The Synthdown Notation

Synthdown notation is based on linking modules together with patch cables.

Which is fair, because that's what modular synthensisers are based on.

A simple patch might look like:

sequencer[bpm: 90](output)
  -> (trigger)envelope[A:0, D:2, S:0, R:2](out)
  -> (control)sine[Level: 10, Tone: 1.5];

This, in essence, says

  1. Set your sequencer to 90bpm and patch from the output jack to the trigger jack of an envelope
  2. Set the envelope ADSR values accordingly, then patch from the out jack to the control jack of a sine module
  3. Set control to 10 and the tone to 1.5 and patch from the out jack to wherever you're off next

The patching its self is pretty obvious; the symbol -> represents a patch cable.

A module, such as (trigger)envelope[A:0, D:2, S:0, R:2](out) is made up of:

  • (trigger) - the input jack, which can be either audio or CV (we don't make any distinction)
  • envelope[A:0, D:2, S:0, R:2] - the module its self (envelope) and whichever knobs and twiddler values apply
  • (out) - the output jack

There are two special cases; the first module in a patch, and the last module in a patch. The first must not have an input, and the last must not have an output since this would be an absurdity.

Finally, a patch ends with a semicolon; this allows us to add many patches to a single input file, should we want to.

EBNF

The following EBNF describes the notation above.

SynthdownFile = (Patch ";")* .
Patch = Module ("-" ">" Module)* .
Module = Jack? <ident> "[" Arg* ("," Arg)* "]" Jack? .
Jack = "(" <ident> ")" .
Arg = <ident> ":" Value .
Value = <string> | <float> | <int> .

Licence

BSD 3-Clause License

Copyright (c) 2024, James Condron All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Arg

type Arg struct {
	Pos lexer.Position

	Key   string `parser:"@Ident ':'"`
	Value *Value `parser:"@@"`
}

Arg represents the various knobs and dials and twiddles on a synth module, and the value it should be set to, such as `Volume: 11` on a mixer module

type DoubledUpJacksError

type DoubledUpJacksError struct {
	// contains filtered or unexported fields
}

DoubledUpJacksError returns when one or more jacks exist in many patches yet shouldn't.

This error may be incorrect for a number of reasons, including:

  1. Not all jacks on a module have a unique name, such as the sequencer on a POM-400
  2. You're using a stacking jack, or a splitter, or a multiplier

In these instances the solution is to:

  1. Artificially number each jack, like 'output1', 'output2'
  2. Set `StackedPatches: true` in the synthdown.ValidationConfiguration struct

passed to (SynthdownFile).Validate()

func (DoubledUpJacksError) Error

func (e DoubledUpJacksError) Error() string

Error returns a message indicating the number of errors found, plus the location of each individual error

type FirstPatchHasInputError

type FirstPatchHasInputError struct {
	// contains filtered or unexported fields
}

FirstPatchHasInputError is an error signifying that the patch starts with an input, which is a logical absurdity; if it has an input from _somewhere_ then it cannot be the start of a patch unless it plugs into its self, or is a snippet.

Neither of which are supported, and wont be until we suddenly realise it should be

func (FirstPatchHasInputError) Error

func (e FirstPatchHasInputError) Error() string

Error satisifies the `error` interface, returning a message explaining where the errant initial input lives

type Jack

type Jack struct {
	Pos lexer.Position

	Name string `parser:"'(' @Ident ')'"`
}

Jack represents a named jack on a synth module, such as the FM jack on a wave generator

type LastPatchHasOutputError

type LastPatchHasOutputError struct {
	// contains filtered or unexported fields
}

LastPatchHasOutputError is an error signifying that the patch starts with an output, which is a logical absurdity; if it has an output from _somewhere_ then it cannot be the start of a patch unless it plugs into its self, or is a snippet.

Neither of which are supported, and wont be until we suddenly realise it should be

func (LastPatchHasOutputError) Error

func (e LastPatchHasOutputError) Error() string

Error satisifies the `error` interface, returning a message explaining where the errant initial output lives

type MissingInputError

type MissingInputError struct {
	// contains filtered or unexported fields
}

MissingInputError is raised where a module should have an input, but doesn't

func (MissingInputError) Error

func (e MissingInputError) Error() string

Error returns an explanation of which module is missing an input jack

type MissingOutputError

type MissingOutputError struct {
	// contains filtered or unexported fields
}

MissingOutputError is raised where a module should have an output, but doesn't

func (MissingOutputError) Error

func (e MissingOutputError) Error() string

Error returns an explanation of which module is missing an output jack

type Module

type Module struct {
	Pos lexer.Position

	Input  *Jack  `parser:"@@?"`
	Name   string `parser:"@Ident"`
	Args   []Arg  `parser:"'[' @@* (',' @@)* ']'"`
	Output *Jack  `parser:"@@?"`
}

Module represents a modular synthesiser 'module', such as a Square Wave generator, or an Envelope Filter.

Modules contain:

  1. An input- an audio or control voltage jack (except the first module in a patch which mustn't contain an input)
  2. A name for the module (so 'Sine', or 'VCA', or whatever)
  3. Arguments, such as A/D/S/R for an Envelope
  4. An output jack (except the last module which must omit the final output)

This, effectively, allows us to represent the pertinent information to draw a patch diagram

type Patch

type Patch struct {
	Pos lexer.Position

	Modules []Module `parser:"@@ ('-' '>' @@)*"`
}

Patch represents a set of modules cabled together from jack to jack which hopefully makes an interesting noise

func (Patch) Validate

func (p Patch) Validate() (err error)

Validate runs through a patch and checks it against a set of rules

type SynthdownFile

type SynthdownFile struct {
	Pos lexer.Position

	Patches []Patch `parser:"( @@ ';' )*"`
}

SynthdownFile contains a list of Patches, which describe how modular synth modules are wired to one another.

func New

func New(fn string) (p *SynthdownFile, err error)

New takes a filename containing, hopefully, valid synthdown notation. It returns a SynthdownFile type when such a file is found, or one of a series of errors, including:

  • `fs.ErrNotExist` - the specified file does not exist
  • `*participle.UnexpectedTokenError` - the synthdown notation is invalid
  • `FirstPatchHasInputError` - the first module in a patch has an input, which is an absurdity

func (SynthdownFile) Validate

func (sf SynthdownFile) Validate(config *ValidationConfiguration) (err error)

Validate runs through a fully read synthdown file in order to:

  1. Run Patch.Validate() on each patch
  2. Ensure inputs and outputs aren't reused

Note: checking input/ output reuse can be disabled when multipliers and/or stacking patch cables are used. See the configuration struct passed to this function for more information

type ValidationConfiguration

type ValidationConfiguration struct {
	// StackedPatches implies the use of multipliers/ splitter
	// cables/ stacking plugs which allows us to, effectively,
	// reuse input/output jacks on a module
	StackedPatches bool
}

ValidationConfiguration holds options for enabling/ disabling certain validation options.

The default behaviour of any validation is to enable _every_ validation, which is why this struct is only ever passed as a reference.

type Value

type Value struct {
	Str   *string  `parser:"@String"`
	Float *float64 `parser:"| @Float"`
	Int   *int     `parser:"| @Int"`
}

Value represents, typically, the value part of an arg. Or, for example, the `3` in the args `[A: 3]`.

Because an argument can typically be an integer, float, or a string we must support each type.

The zero value (ie: when none of the inner values are set) is a 0

func (Value) Float64

func (v Value) Float64() float64

Float64 returns the float64 representation of a Value, casting appropriately so that:

  1. A float64 is returned as is
  2. An int is cast as a float64; and
  3. A string is `strconv`d as a float64

This function panics if a string is passed in which cannot be `strconv`d as a float64.

func (Value) String

func (v Value) String() string

String returns a string representation of the value, which is especially useful for diagramming where you don't really care about what's set

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL