voiceedit

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package voiceedit provides in-place byte patching for FZV and FZF voice parameters. Patches are applied atomically: the file is read, modified in memory, and written back via fileutil.WriteAtomic.

Index

Constants

View Source
const (
	// Unchanged is the sentinel value for parameters that should not be
	// modified. It must be outside all valid parameter ranges.
	Unchanged = -1000
)

Variables

View Source
var (
	ErrNotVoiceFile     = errors.New("voiceedit: file does not appear to be a voice file")
	ErrUnsupportedPatch = errors.New("voiceedit: unsupported patch size")
	ErrFileTooSmall     = errors.New("voiceedit: file too small")
)

Sentinel errors. Wrap with %w; match with errors.Is, not on message text.

Functions

func ApplyToFZFSlotBytes added in v0.5.0

func ApplyToFZFSlotBytes(data []byte, slot int, patches []Patch) error

ApplyToFZFSlotBytes applies patches to one voice slot's header inside FZF full dump bytes, in place: the in-memory, slot-addressed sibling of ApplyToFZFVoice. Key-range patches mirror into every bank site that references the slot, exactly as the file-path edit does.

func ApplyToFZFVoice

func ApplyToFZFVoice(path string, voiceName string, patches []Patch) error

ApplyToFZFVoice reads the FZF file at path, locates the voice by name, applies patches to that voice's header, and writes back atomically. It takes the same cross-process lock as ApplyToFZV.

func ApplyToFZV

func ApplyToFZV(path string, patches []Patch) error

ApplyToFZV reads the FZV file at path, applies patches to the voice header, and writes the result back atomically. Offsets are relative to the start of the voice header (byte 0 of the file). The read-modify-write sequence is serialised across processes via fileutil.WithFileLock so concurrent writers can't lose each other's edits.

func ApplyToFZVBytes added in v0.5.0

func ApplyToFZVBytes(data []byte, patches []Patch) error

ApplyToFZVBytes applies patches to FZV voice file bytes in place: the same validation and patching as ApplyToFZV with no filesystem.

func ValidateByte

func ValidateByte(name string, val, lo, hi int) error

ValidateByte checks that val is within the given range.

func ValidateWaveform

func ValidateWaveform(val int) error

ValidateWaveform checks that val is a valid LFO waveform index.

func WaveformIndex

func WaveformIndex(name string) (int, bool)

WaveformIndex returns the index for the named LFO waveform and whether it was found.

Types

type Patch

type Patch struct {
	Offset int
	Size   int    // 1 or 2 (ignored when Bytes is set)
	Value  uint16 // ignored when Bytes is set
	Bytes  []byte // multi-byte payload; when non-nil takes precedence over Size/Value
}

Patch describes a modification to a voice header. When Bytes is non-nil it is written verbatim at Offset and Size/Value are ignored. Otherwise Size (1 or 2) bytes from Value are written as little-endian.

func BuildDCAPatches

func BuildDCAPatches(sustain, end int, rates, stops [disk.EnvelopeStages]int, origRates [disk.EnvelopeStages]uint8) ([]Patch, error)

BuildDCAPatches creates patches for DCA envelope parameters. Pass Unchanged for sustain/end, or for an individual rate/level element, to leave it alone. Rates and levels use the hardware display scale (0 to 99). origRates carries the original rate bytes so the sign bit (envelope direction) survives a magnitude-only change.

func BuildDCFPatches

func BuildDCFPatches(sustain, end int, rates, stops [disk.EnvelopeStages]int, origRates [disk.EnvelopeStages]uint8) ([]Patch, error)

BuildDCFPatches creates patches for DCF envelope parameters, under the same conventions as BuildDCAPatches: Unchanged skips a field or element, rates and levels use the 0 to 99 display scale, and origRates preserves the envelope-direction sign bit.

func BuildFilterPatches

func BuildFilterPatches(cutoff, resonance int) ([]Patch, error)

BuildFilterPatches creates patches for filter cutoff and resonance. Both use the hardware display scale: cutoff 0 to 127, resonance 0 to 127. The resonance byte is stored directly (the full byte is used by the hardware, not just the upper nibble as the spec suggests). Pass Unchanged to leave a parameter unmodified.

func BuildKeyRangePatch

func BuildKeyRangePatch(keyLow, keyHigh, root int) ([]Patch, error)

BuildKeyRangePatch creates patches for the key range (key-low, key-high, root). Each value is a MIDI note number (0 to 127). Pass Unchanged for any parameter to leave it unmodified.

func BuildLFOPatches

func BuildLFOPatches(wave, rate, delay, attack, pitch, amp, filter, q int, origLFOName uint8) ([]Patch, error)

BuildLFOPatches creates patches for LFO parameters. Pass Unchanged for any parameter to leave it unmodified. origLFOName is the current value of the lfo_name byte (spec offset 0x9E): bits 0-6 hold the waveform index, bit 7 is the phase-sync flag. It is used to preserve the phase-sync flag when only the waveform index changes; see disk.LFOWaveformMask / LFOPhaseFlag.

func BuildLoopAttrPatch added in v0.5.0

func BuildLoopAttrPatch(index, xf, tm int) ([]Patch, error)

BuildLoopAttrPatch creates patches for loop index's cross-fade and multi-loop time attributes (loopxf and looptm, spec §2-1). Both are 16-bit little-endian entries; xf ranges 0 to disk.MaxLoopXF (0 disables the cross-fade) and tm ranges 0 to disk.MaxLoopTm (fresh voices carry 0, so 0 passes even though the spec's lower bound is 1).

func BuildLoopPatch added in v0.5.0

func BuildLoopPatch(index int, start, end uint32, origSt, origEd uint32) ([]Patch, error)

BuildLoopPatch creates patches setting loop index's start and end sample addresses. The spec reserves flag bits inside both cells (the loop-fine byte in the upper 8 bits of loopst, the skip flag in the MSB of looped); origSt and origEd carry the current cell values so those bits survive the write. start must be below end, and end must fit the 24-bit loopst address space so the pair stays addressable.

func BuildLoopSelectPatch added in v0.5.0

func BuildLoopSelectPatch(sustain, release int) ([]Patch, error)

BuildLoopSelectPatch creates patches for the sustain and release loop designations (loop_sus, loop_end). Valid values are 0 to 7 for a loop index, or disk.NoSustainLoop (8) for none.

func BuildModulationPatches

func BuildModulationPatches(dcaKF, dcaRS, dcfKF, dcfRS, velDCAKF, velDCFKF, velDCQKF, velDCARS, velDCFRS int) ([]Patch, error)

BuildModulationPatches creates patches for modulation routing parameters. KF parameters (dcaKF, dcaRS, dcfKF, dcfRS) use the hardware display scale (-15 to +15). All five velocity-modulation parameters (velDCAKF, velDCFKF, velDCQKF, velDCARS, velDCFRS) are signed -127 to +127 per spec §2-1 and are stored as two's-complement bytes. Pass Unchanged for any parameter to leave it unmodified.

func BuildNamePatch

func BuildNamePatch(name string) ([]Patch, error)

BuildNamePatch creates a patch for the voice name (max 12 characters). The 12-byte padded name is followed by the two zero bytes the FZ voice header layout requires.

Case is stored verbatim. The FZ-1 supports mixed-case names (factory disks carry "All Voices"), and upper-casing on commit mutates a field the user only tabbed through. findVoiceIndex matches case-insensitively, so lookups still work either way.

func BuildPlaybackModePatch

func BuildPlaybackModePatch(mode string) ([]Patch, error)

BuildPlaybackModePatch creates a patch for the voice playback mode. The mode name is matched case-insensitively. Valid modes: Normal, Reverse, Cue, Synth.

func BuildTunePatch

func BuildTunePatch(tune int) ([]Patch, error)

BuildTunePatch creates a patch for the voice tuning (DCP field). The value is in 1/256-semitone units and stored as a uint16 (two's complement).

Jump to

Keyboard shortcuts

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