dmxcast

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 8, 2026 License: MIT Imports: 17 Imported by: 0

README

go-dmxcast Go Reference

Play OLA DMX show files and stream them over network protocols (currently Art-Net).

This project includes:

  • dmxcast: a player that can run multiple shows at the same time and merge them (HTP/LTP).
  • olashow: a parser/writer for OLA Show text files.
  • cmd/ola_player: a CLI to play one or more shows over Art-Net unicast with merge/loop controls.
  • cmd/ola_recorder: a CLI recorder that captures Art-Net ArtDMX into an OLA Show file.

Install

go get github.com/bstkhq/go-dmxcast

Library usage

Parse a show
show, err := olashow.Open("show.show")
if err != nil {
	// ...
}
Play over Art-Net
tx, err := dmxcast.NewArtNetTransport(&dmxcast.ArtNetConfig{
	DstIP:  net.ParseIP("10.0.100.49"),
	SrcIP:  net.ParseIP("10.0.100.5"), // optional
	Net:    0,
	SubUni: 204,
})
if err != nil {
	// ...
}
defer tx.Close()

player := dmxcast.NewPlayer(tx, &dmxcast.PlayerConfig{
	Mode:          dmxcast.MergeHTP,
	FlushInterval: 0, // defaults to 44 Hz
})
defer player.Close()

h := player.Play(context.Background(), show)

// ...
player.Stop(h)

CLI Usage

Play a show file via Art-Net unicast.

go run ./cmd/ola_player \
 -file ./show.show \
 -ip 10.0.100.49 \
 -net 0 \
 -subuni 204

Common options:

  • -loop / -once: override the show metadata loop.
  • -mode htp|ltp: merge mode (default htp).
  • -hz 44: output refresh rate (default 44 Hz).
  • -stats 1s: print a frame counter periodically (set 0 to disable).

OLA Show format

Standard OLA show header:

OLA Show
<universe> <v1>,<v2>,...,<vn>
<delay_ms>
...
Metadata

This repo supports optional metadata that can be provided in two ways:

Inline metadata (in the .show file)

Metadata lines must appear only at the beginning of the file and must be a consecutive block (no blank lines inside). Each line uses:

# key=value

Example:

# name=My Show
# loop=true
# exclusive=true
# include=intro.show
OLA Show
...
Sidecar metadata file (.metadata)

If the show file is myshow.show, the loader will also look for:

myshow.show.metadata

The sidecar uses the same keys but without the leading #:

name=My Show
loop=3
exclusive=false
include=intro.show
Supported keys
  • name=<string> Optional display name for the show.

  • loop=<bool|int> Controls how many times the show should repeat:

    • true → infinite loop (Loop = -1)
    • false → play once (Loop = 0)
    • <int> → repeat that many times (Loop = <int>)
  • exclusive=<bool> Indicates the show requests exclusive control while playing (the player should stop other running shows before starting this one).

  • include=<file.show> Prepends the frames of another show before the current one. Can be specified multiple times; includes are applied in order.

License

MIT, see LICENSE

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ArtNetConfig

type ArtNetConfig struct {
	// DstIP is the destination IP for UDP unicast Art-Net traffic.
	DstIP net.IP
	// SrcIP is the optional local bind IP (nil = OS default).
	SrcIP net.IP
	// SubUni is the Art-Net SubUni (0..255) field used in ArtDMX packets.
	SubUni uint8
	// Net is the Art-Net Net (0..127) field used in ArtDMX packets.
	Net uint8
}

ArtNetConfig configures an ArtNetTransport.

The transport sends Art-Net ArtDMX packets via UDP unicast to DstIP:6454. Net and SubUni select the target Art-Net universe (as defined by the Art-Net specification).

If SrcIP is provided, the UDP socket is bound to SrcIP:6454 (useful on multi-homed hosts). If SrcIP is nil, the OS default routing and an ephemeral source port are used.

type ArtNetListener

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

ArtNetListener listens for Art-Net ArtDMX packets over UDP and emits frames.

func NewArtNetListener

func NewArtNetListener(cfg ArtNetListenerConfig) (*ArtNetListener, error)

NewArtNetListener binds UDP/6454 and returns an ArtNetListener.

func (*ArtNetListener) Close

func (l *ArtNetListener) Close() error

Close closes the UDP socket.

func (*ArtNetListener) Run

Run reads ArtDMX packets until ctx is done and calls h for each received packet.

type ArtNetListenerConfig

type ArtNetListenerConfig struct {
	// BindIP is the optional local bind IP. Nil means all interfaces.
	BindIP net.IP
	// ReadBuffer sets the UDP socket read buffer (0 = OS default).
	ReadBuffer int
}

ArtNetListenerConfig configures ArtNetListener.

type ArtNetTransport

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

ArtNetTransport sends DMX frames as Art-Net ArtDMX packets over UDP.

func NewArtNetTransport

func NewArtNetTransport(cfg *ArtNetConfig) (*ArtNetTransport, error)

NewArtNetTransport creates a UDP unicast Art-Net sender.

The returned transport maintains an internal ArtDMX sequence counter that increments on each Send. Sequence 0 is skipped (wraps from 255 back to 1).

func (*ArtNetTransport) Close

func (t *ArtNetTransport) Close() error

Close closes the underlying UDP socket.

func (*ArtNetTransport) Send

func (t *ArtNetTransport) Send(dmx [512]byte) error

Send encodes the given DMX frame as an Art-Net ArtDMX packet and sends it via UDP.

type FrameHandler

type FrameHandler func(now time.Time, universe uint16, dmx [512]byte) error

FrameHandler is a transport-agnostic callback for timestamped DMX frames.

type IDFromFilename

type IDFromFilename func(filename string) (id int, nameFromFile string, ok bool)

IDFromFilename extracts a show ID and a default name from a filename.

It must return ok=false for filenames that are not shows.

type Library

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

Library loads OLA show files from a folder and controls playback via Player.

func NewLibrary

func NewLibrary(player *Player, cfg *LibraryConfig) (*Library, error)

NewLibrary loads shows from cfg.Path and wires the library to player.

func (*Library) Get

func (l *Library) Get(id int) (*ShowInfo, bool)

Get returns show info for a id.

func (*Library) GetByName

func (l *Library) GetByName(name string) (*ShowInfo, bool)

Get returns show info for a name.

func (*Library) IsShowPlaying

func (l *Library) IsShowPlaying(id int) bool

IsShowPlaying reports whether the given show ID is currently playing.

func (*Library) List

func (l *Library) List() []ShowInfo

List returns all known shows ordered by ID.

func (*Library) Play

func (l *Library) Play(id int) (ShowHandle, error)

Play starts playing a show by ID.

If the show is already running, Play stops the current instance and starts it again.

func (*Library) RunningShows

func (l *Library) RunningShows() map[int]*PlayInfo

RunningShows currently running shows, returns a map show ID -> PlayInfo.

func (*Library) Stop

func (l *Library) Stop(id int) (wasPlaying bool)

Stop stops a running show by ID.

It returns true if the show was running at the time Stop was requested.

func (*Library) StopAll

func (l *Library) StopAll()

StopAll stops all currently running shows and clears library runtime state.

type LibraryConfig

type LibraryConfig struct {
	// Path is the folder containing show files.
	Path string

	// IDFromFilename extracts (id, nameFromFile) from the filename.
	// If nil, it defaults to parsing "<id>.<name>.show".
	IDFromFilename IDFromFilename

	// OnEvent is an optional callback invoked when the library state changes.
	// It is intended to be set once during initialization.
	OnEvent func(LibraryEvent)
}

LibraryConfig configures a Library instance.

type LibraryEvent

type LibraryEvent struct {
	// Type is the event category (currently only LibraryStateChanged).
	Type LibraryEventType
	// At is the time the event was created.
	At time.Time
	// Reason explains why the state changed.
	Reason LibraryEventReason
	// Show is the show related to this event when applicable.
	// It is nil for events that don't target a single show (e.g. stopall).
	Show *ShowInfo
	// Running is a snapshot of all currently running shows after the change.
	Running []PlayInfo
}

LibraryEvent is emitted when the library state changes.

type LibraryEventReason

type LibraryEventReason string

LibraryEventReason explains why the state changed.

const (
	// PlayLibraryEvent a show started
	PlayLibraryEvent LibraryEventReason = "play"
	// RestartLibraryEvent a show was restarted
	RestartLibraryEvent LibraryEventReason = "restart"
	// StopLibraryEvent a show was stopped
	StopLibraryEvent LibraryEventReason = "stop"
	// StopAllLibraryEvent all shows were stopped
	StopAllLibraryEvent LibraryEventReason = "stopall"
	// FinishedLibraryEvent a show ended naturally
	FinishedLibraryEvent LibraryEventReason = "finished"
)

type LibraryEventType

type LibraryEventType int

LibraryEventType identifies the kind of library event.

const (
	// LibraryStateChanged is emitted when the running set changes.
	LibraryStateChanged LibraryEventType = iota
)

type Listener

type Listener interface {
	Run(ctx context.Context, h FrameHandler) error
	Close() error
}

Listener is a source of timestamped DMX frames. Implementations should stop when ctx is done and return nil in that case.

type MergeMode

type MergeMode int

MergeMode defines how multiple shows are combined into a single DMX output.

const (
	// MergeHTP (Highest Takes Precedence) merges per channel using the maximum value.
	MergeHTP MergeMode = iota
	// MergeLTP (Latest Takes Precedence) merges per channel using the most recently
	// updated value.
	MergeLTP
)

type PlayInfo

type PlayInfo struct {
	PlayingShow
	// HandleID is the player handle ID for this run.
	HandleID int
	// ShowID is the numeric show ID.
	ShowID int
}

PlayInfo describes a running show instance.

type Player

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

Player mixes multiple shows and outputs merged DMX through a Transport.

func NewPlayer

func NewPlayer(tx Transport, cfg *PlayerConfig) *Player

NewPlayer creates a Player that merges concurrent shows and sends the result through tx.

func (*Player) Close

func (e *Player) Close() error

Close stops all running shows, waits for internal goroutines to exit, and closes the underlying Transport.

func (*Player) IsPlaying

func (e *Player) IsPlaying(h ShowHandle) bool

IsPlaying reports whether the show identified by h is currently running.

func (*Player) ListPlaying

func (e *Player) ListPlaying() []PlayingShow

ListPlaying returns a snapshot of currently running shows.

The returned slice is a point-in-time view; shows may start/stop concurrently.

func (*Player) OnShowExited

func (e *Player) OnShowExited(cb func(ShowHandle))

OnShowExited sets a callback invoked when a show goroutine exits and is removed from the player or ctx cancel).

func (*Player) Play

func (e *Player) Play(ctx context.Context, show *olashow.OlaShow) ShowHandle

Play starts playing show until it finishes, ctx is cancelled, or Stop is called.

The OLA frame Universe field is ignored. All frames contribute to the single merged output routed by the Transport.

If show.Exclusive is true, the player stops all other running shows before starting playback of this one.

func (*Player) Stop

func (e *Player) Stop(h ShowHandle)

Stop requests the show identified by h to stop.

Stop is asynchronous: it signals the show goroutine, which will exit shortly after (immediately for zero-delay frames, or after the current frame delay). Calling Stop for an unknown handle is a no-op.

func (*Player) StopAll

func (e *Player) StopAll()

StopAll requests all currently running shows to stop.

StopAll is asynchronous; use IsPlaying or ListPlaying to observe completion.

type PlayerConfig

type PlayerConfig struct {
	// Mode selects the merge method used when multiple shows are playing.
	Mode MergeMode
	// FlushInterval is the period between output frames.
	// If zero, it defaults to 44 Hz (time.Second/44).
	FlushInterval time.Duration
}

PlayerConfig configures a Player/Engine instance.

Mode selects how multiple concurrent shows are merged (HTP or LTP). FlushInterval controls the output refresh period.

type PlayingShow

type PlayingShow struct {
	ID        uint64
	Show      *olashow.OlaShow
	StartedAt time.Time
}

PlayingShow describes a currently running show.

type Recorder

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

Recorder consumes DMX frames from a Listener and writes an OLA Show to an io.Writer.

It records using the same timing strategy as OLA's ola_recorder:

  • Each received frame becomes one OLA show frame.
  • The delay line is written when the *next* frame arrives (ms, truncated).
  • The output ends with a frame line (no trailing delay line).

func NewRecorder

func NewRecorder(l Listener) *Recorder

NewRecorder creates a Recorder bound to a listener.

func (*Recorder) Record

func (r *Recorder) Record(ctx context.Context, w io.Writer) error

Record runs the listener loop and writes an OLA Show to w until ctx is done or the listener returns an error.

This method does not start any goroutines. Cancel ctx to stop recording.

type ShowHandle

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

func (ShowHandle) ID

func (h ShowHandle) ID() uint64

ID returns the unique identifier of the handle.

type ShowInfo

type ShowInfo struct {
	olashow.OlaShow
	// ID is the numeric identifier extracted from the filename.
	ID int
	// FileName is the show filename (e.g. "001.alpha.show").
	FileName string
	// Playing is the current runtime information, if the show is running.
	Playing *PlayInfo
	// contains filtered or unexported fields
}

ShowInfo describes a show discovered on disk plus its runtime state.

type Transport

type Transport interface {
	Send(dmx [512]byte) error
	Close() error
}

Transport sends merged DMX for an output universe.

type Universe

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

Universe stores the latest DMX buffer per source and can merge them on demand.

func NewUniverse

func NewUniverse(mode MergeMode) *Universe

NewUniverse creates a Universe using the given merge mode.

func (*Universe) Apply

func (u *Universe) Apply(showID uint64, seq uint64, dmx [512]byte)

Apply stores the latest DMX for showID. seq must be monotonic across all shows (engine-global).

func (*Universe) Merge

func (u *Universe) Merge() [512]byte

Merge returns the merged DMX snapshot for the current sources.

func (*Universe) Mode

func (u *Universe) Mode() MergeMode

Mode returns the current merge mode.

func (*Universe) Remove

func (u *Universe) Remove(showID uint64)

Remove removes a show from the universe.

func (*Universe) SetMode

func (u *Universe) SetMode(mode MergeMode)

SetMode changes the merge mode.

func (*Universe) SourcesCount

func (u *Universe) SourcesCount() int

SourcesCount returns the number of sources (useful for tests/metrics).

Directories

Path Synopsis
cmd
api command
cmd/server/main.go
cmd/server/main.go
ola_player command
ola_recorder command

Jump to

Keyboard shortcuts

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