balafon

package module
v0.8.5 Latest Latest
Warning

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

Go to latest
Published: Nov 24, 2024 License: GPL-3.0 Imports: 24 Imported by: 0

README

Balafon

The balafon is a gourd-resonated xylophone, a type of struck idiophone. It is closely associated with the neighbouring Mandé, Senoufo and Gur peoples of West Africa, particularly the Guinean branch of the Mandinka ethnic group, but is now found across West Africa from Guinea to Mali. Its common name, balafon, is likely a European coinage combining its Mandinka name ߓߟߊ bala with the word ߝߐ߲ fôn 'to speak' or the Greek root phono.

Balafon - From Wikipedia, the free encyclopedia

Introduction

balafon is a multitrack MIDI sequencer language and interpreter. It consists of a live shell, player and a linter.

Install

To install the balafon command from source, go and rtmidi are required. Not tested on platforms other than Linux.

go install github.com/mgnsk/balafon/cmd/balafon@latest

Running

  • The default command lists the available MIDI ports. The default port is the 0 port.
balafon
0: Midi Through:Midi Through Port-0 14:0
1: Hydrogen:Hydrogen Midi-In 135:0
2: VMPK Input:in 128:0
  • Play a file through a specific port. The port name must contain the passed in flag value:
balafon play --port "VMPK" examples/bach.bal
  • Port can also be specified by its number:
balafon play --port 2 examples/bonham.bal
  • Enter live mode:
balafon live --port hydro examples/live_drumset.bal

Live mode is an unbuffered input mode in the shell. Whenever an assigned key is pressed, a note on message is sent to the port.

  • Help.
$ balafon --help
balafon is a MIDI control language and interpreter.

Usage:
   [flags]
   [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  fmt         Format a file
  help        Help about any command
  lint        Lint a file
  live        Load a file and continue in a live shell
  play        Play a file
  smf         Convert a file to SMF2

Flags:
  -h, --help   help for this command

Use " [command] --help" for more information about a command.

Syntax

The language consists of commands and note lists.

Comments

Only block comments are supported for now.

/* This is a block comment. */

/*
This is a multi line block comment.
*/
Commands

Commands begin with a :.

// Assign a note.
:assign c 60

// Start message.
:start

// Stop message.
:stop

// Key signature change on the current channel.
:Key Cmaj

// Set time signature.
:time 4 4

// Set tempo.
:tempo 120

// Set channel.
:channel 10

// Set voice.
:voice 1

// Set velocity.
:velocity 127

// Program change message on the current channel.
:program 0

// Control change message on the current channel.
:control 1 127
Note assignment

Assign a MIDI note number to a note letter.

// Kick drum (on the drum channel).
:assign k 36
// Middle C (on other channels).
:assign c 60
Notes

Notes are written as a letter symbol (must be assigned first) plus properties. The available properties are

  • sharp (#)
  • flat ($)
  • staccato (`) shorten note by 50%
  • accent (>) +5 velocity
  • marcato (^) +10 velocity
  • ghost ()) -5 velocity
  • dot (.)
  • numeric note value (1, 2, 4, 8 and so on)
  • tuplet (/3) (only triplet /3 or quintuplet /5)
  • let ring (*)
Note values
// Whole note.
x1
// Half note.
x2
// Quarter note (same as x4).
x
// 8th note.
x8
// 16th note.
x16
// 32th note.
x32
// And so on...
Rests
// A quarter rest.
-
// An 8th rest.
-8
Dotted notes and tuplets
// Dotted quarter note.
x.
// Double-dotted note.
x..
// Triple-dotted note.
x...
// Dotted 8th note.
x8.
// Quarter triplet note.
x/3
// Dotted 8th quintuplet note.
x8./5
Flat and sharp notes
// A note.
c
// A sharp note (MIDI note number + 1).
c#
// A flat note (MIDI note number - 1).
c$
Note grouping

Notes can be arbitrarily grouped and properties applied to multiple notes at once.

// Ti-Tiri.
x8 x16 x16
// Can be written as:
x8[xx]16

// Three 8th triplet notes.
[xxx]8/3
// Expands to
x8/3 x8/3 x8/3

// Nested groups are also supported:
[[fcg] [fcg]#]8
// Expands to
f8 c8 g8 f#8 c#8 g#8
Additive properties

When used on note groups, these properties are added to the notes' already existing properties:

  • staccato (`)
  • accent (>)
  • marcato (^)
  • ghost ())
  • dot (.)
Unique properties

When used on note groups, these properties override the notes' existing properties of the same type:

  • sharp (#)
  • flat ($)
  • numeric note value (1, 2, 4, 8 and so on)
  • tuplet (/3) (only triplet /3 or quintuplet /5)
  • let ring (*)

The sharp and flat properties are mutually exclusive and may appear only once per note.

Bars

Bars are used to specify multiple tracks playing at once. Only time, velocity, channel and voice commands are scoped to the bar. Other commands, when used inside a bar, have global effect when the bar is played back. The bar is executed with the play command.

// Define a bar.
:bar RockBeat
// Setting `time` makes the interpreter validate the bar length.
// Incomplete bars are filled with silence.
:time 4 4
  [xx xx xx xx]8
  // Using braces for nice alignment.
  [k  s  k  s]
:end

// You can also write the same bar as:
:bar SameBeat
  [xxxxxxxx]8
  ksks
:end

// Play the bar.
:play RockBeat

Neovim configuration

Filetype detection:
vim.api.nvim_create_autocmd({ "BufNewFile", "BufRead" }, {
    pattern = "*.bal",
    callback = function()
        vim.bo.filetype = "balafon"
    end,
})
Neomake
vim.g.neomake_balafon_lint_maker = {
    exe = "balafon",
    args = "lint",
    errorformat = "%f:%l:%c: error: %m",
}

vim.g.neomake_balafon_enabled_makers = { "lint" }
Treesitter

Example configuration for lazy.nvim:

{
    "mgnsk/tree-sitter-balafon",
    dependencies = {
        "nvim-treesitter/nvim-treesitter",
    },
    ft = "balafon",
    build = function(plugin)
        local parser_config = require("nvim-treesitter.parsers").get_parser_configs()

        parser_config["balafon"] = {
            install_info = {
                url = plugin.dir,
                files = { "src/parser.c" },
                generate_requires_npm = true,
                requires_generate_from_grammar = false,
            },
            filetype = "bal",
        }

        vim.cmd("TSUpdateSync balafon")
    end,
},

Examples

The Bonham Half Time Shuffle

examples/bonham.bal

To play into the default port, run

balafon play examples/bonham.bal
J.S. Bach - Musikalisches Opfer - 6. Canon A 2 Per Tonos

examples/bach.bal

To play into the default port, run

balafon play examples/bach.bal
Multichannel

examples/multichannel.bal

To play into the default port, run

balafon play examples/bach.bal

Possible features in the future

  • Tie (a curved line connecting the heads of two notes of the same pitch) - no idea about the syntax. Can be partially emulated by using dotted notes if the rhythm is simple enough.
  • WebAssembly support with Web MIDI for running in browsers.
  • Accelerando/Ritardando.

Documentation

Index

Constants

View Source
const (
	// EOT is keycode for Ctrl+D.
	EOT = 4
)

Variables

This section is empty.

Functions

func Format

func Format(input []byte) ([]byte, error)

Format a balafon script.

func FormatFile

func FormatFile(filename string) error

FormatFile formats a file.

func ToSMF added in v0.8.5

func ToSMF(input []byte) (*smf.SMF, error)

ToSMF converts a balafon script to SMF1.

func ToXML added in v0.8.5

func ToXML(w io.Writer, input []byte) error

ToXML converts a balafon script to MusicXML.

Types

type Bar

type Bar struct {
	Events []Event
	// contains filtered or unexported fields
}

Bar is a single bar of events.

func (*Bar) Cap

func (b *Bar) Cap() uint32

Cap returns the bar's capacity in ticks.

func (*Bar) Duration

func (b *Bar) Duration(tempo float64) time.Duration

Duration returns the bar's duration.

func (*Bar) IsZeroDuration

func (b *Bar) IsZeroDuration() bool

IsZeroDuration returns whether the bar consists of only zero duration events.

func (*Bar) SetTimeSig added in v0.8.5

func (b *Bar) SetTimeSig(num, denom uint8)

SetTimeSig sets the timesig for testing.

func (*Bar) String added in v0.8.5

func (b *Bar) String() string

type Channel added in v0.8.5

type Channel uint8

Channel is a MIDI channel.

func NewChannelFromHuman added in v0.8.5

func NewChannelFromHuman(ch uint8) Channel

func NewChannelFromMIDI added in v0.8.5

func NewChannelFromMIDI(ch uint8) Channel

func (Channel) Human added in v0.8.5

func (c Channel) Human() uint8

func (Channel) Uint8 added in v0.8.5

func (c Channel) Uint8() uint8

type EvalError

type EvalError struct {
	Err error
	Pos Pos
}

EvalError is an eval error.

func (*EvalError) Error

func (e *EvalError) Error() string

type Event

type Event struct {
	Note     *ast.Note // only for note on messages and rests
	Message  smf.Message
	IsFlat   bool   // if the midi note was lowered due to key sig
	Pos      uint32 // in relative ticks from beginning of bar
	Duration uint32 // in ticks
	Voice    Voice
	Track    uint8 // track is the MIDI channel in human value
}

Event is a balafon event.

func (*Event) String added in v0.8.5

func (e *Event) String() string

type Interpreter

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

Interpreter evaluates text input and emits events.

func New

func New() *Interpreter

New creates a balafon interpreter.

func (*Interpreter) Eval

func (it *Interpreter) Eval(input []byte) error

Eval evaluates the input.

func (*Interpreter) EvalFile

func (it *Interpreter) EvalFile(filepath string) error

EvalFile evaluates a file.

func (*Interpreter) EvalString

func (it *Interpreter) EvalString(input string) error

EvalString evaluates the string input.

func (*Interpreter) Flush

func (it *Interpreter) Flush() []*Bar

Flush flushes the parsed bar queue and resets the interpreter.

type LiveShell

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

LiveShell is an unbuffered live shell.

func NewLiveShell

func NewLiveShell(r io.Reader, it *Interpreter, out drivers.Out) *LiveShell

NewLiveShell creates a new live shell.

func (*LiveShell) HandleNext

func (s *LiveShell) HandleNext() error

HandleNext handles the next character from input.

func (*LiveShell) Run

func (s *LiveShell) Run() error

Run the shell.

type ParseError

type ParseError = parseError.Error

ParseError is a parse error.

type Player

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

Player is a MIDI player.

func NewPlayer

func NewPlayer(out drivers.Out) *Player

NewPlayer creates a new player.

func (*Player) Play

func (p *Player) Play(events ...TrackEvent) error

Play the events into the out port.

type Pos added in v0.8.5

type Pos = token.Pos

Pos is a token position.

type SMF added in v0.8.5

type SMF []TrackEvent

SMF is an SMF song.

func (SMF) String added in v0.8.5

func (song SMF) String() string

type Sequencer

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

Sequencer is a MIDI sequencer.

func NewSequencer

func NewSequencer() *Sequencer

NewSequencer creates an SMF sequencer.

func (*Sequencer) AddBars

func (s *Sequencer) AddBars(bars ...*Bar)

AddBars adds bars to te sequence.

func (*Sequencer) Flush

func (s *Sequencer) Flush() SMF

Flush emits the accumulated SMF tracks.

type TrackEvent

type TrackEvent struct {
	Message        smf.Message
	AbsTicks       uint32
	AbsNanoseconds int64
}

TrackEvent is an SMF track event.

func (*TrackEvent) String added in v0.8.5

func (s *TrackEvent) String() string

type Voice added in v0.8.5

type Voice uint8

Voice is a score voice.

func (Voice) Uint8 added in v0.8.5

func (v Voice) Uint8() uint8

Jump to

Keyboard shortcuts

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