mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
feat: add ability to import 4klang patches and instruments
This commit is contained in:
parent
c06ac6ea5e
commit
248ba483c6
519
4klang.go
Normal file
519
4klang.go
Normal file
@ -0,0 +1,519 @@
|
|||||||
|
package sointu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Read4klangPatch(r io.Reader) (patch Patch, err error) {
|
||||||
|
var versionTag uint32
|
||||||
|
var version int
|
||||||
|
var polyphonyUint32 uint32
|
||||||
|
var polyphony int
|
||||||
|
var instrumentNames [_4KLANG_MAX_INSTRS]string
|
||||||
|
patch = make(Patch, 0)
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &versionTag); err != nil {
|
||||||
|
return nil, fmt.Errorf("binary.Read: %w", err)
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
if version, ok = _4klangVersionTags[versionTag]; !ok {
|
||||||
|
return nil, fmt.Errorf("unknown 4klang version tag: %d", versionTag)
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &polyphonyUint32); err != nil {
|
||||||
|
return nil, fmt.Errorf("binary.Read: %w", err)
|
||||||
|
}
|
||||||
|
polyphony = int(polyphonyUint32)
|
||||||
|
if polyphony < 1 {
|
||||||
|
polyphony = 1
|
||||||
|
}
|
||||||
|
for i := range instrumentNames {
|
||||||
|
instrumentNames[i], err = read4klangName(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read4klangName: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m := make(_4klangTargetMap)
|
||||||
|
id := 1
|
||||||
|
for instrIndex := 0; instrIndex < _4KLANG_MAX_INSTRS; instrIndex++ {
|
||||||
|
var units []Unit
|
||||||
|
if units, err = read4klangUnits(r, version, instrIndex, m, &id); err != nil {
|
||||||
|
return nil, fmt.Errorf("read4klangUnits: %w", err)
|
||||||
|
}
|
||||||
|
if len(units) > 0 {
|
||||||
|
patch = append(patch, Instrument{Name: instrumentNames[instrIndex], NumVoices: polyphony, Units: units})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var units []Unit
|
||||||
|
if units, err = read4klangUnits(r, version, _4KLANG_MAX_INSTRS, m, &id); err != nil {
|
||||||
|
return nil, fmt.Errorf("read4klangUnits: %w", err)
|
||||||
|
}
|
||||||
|
if len(units) > 0 {
|
||||||
|
patch = append(patch, Instrument{Name: "Global", NumVoices: 1, Units: units})
|
||||||
|
}
|
||||||
|
for i, instr := range patch {
|
||||||
|
fix4klangTargets(i, instr, m)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Read4klangInstrument(r io.Reader) (instr Instrument, err error) {
|
||||||
|
var versionTag uint32
|
||||||
|
var version int
|
||||||
|
var name string
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &versionTag); err != nil {
|
||||||
|
return Instrument{}, fmt.Errorf("binary.Read: %w", err)
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
if version, ok = _4klangVersionTags[versionTag]; !ok {
|
||||||
|
return Instrument{}, fmt.Errorf("unknown 4klang version tag: %d", versionTag)
|
||||||
|
}
|
||||||
|
if name, err = read4klangName(r); err != nil {
|
||||||
|
return Instrument{}, fmt.Errorf("read4klangName: %w", err)
|
||||||
|
}
|
||||||
|
var units []Unit
|
||||||
|
id := 1
|
||||||
|
m := make(_4klangTargetMap)
|
||||||
|
if units, err = read4klangUnits(r, version, 0, m, &id); err != nil {
|
||||||
|
return Instrument{}, fmt.Errorf("read4klangUnits: %w", err)
|
||||||
|
}
|
||||||
|
ret := Instrument{Name: name, NumVoices: 1, Units: units}
|
||||||
|
fix4klangTargets(0, ret, m)
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
_4klangStackUnit struct {
|
||||||
|
stack, unit int
|
||||||
|
}
|
||||||
|
|
||||||
|
_4klangTargetMap map[_4klangStackUnit]int
|
||||||
|
|
||||||
|
_4klangPorts struct {
|
||||||
|
UnitType string
|
||||||
|
PortName [8]string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_4KLANG_MAX_INSTRS = 16
|
||||||
|
_4KLANG_MAX_UNITS = 64
|
||||||
|
_4KLANG_MAX_SLOTS = 16
|
||||||
|
_4KLANG_MAX_NAME_LEN = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_4klangVersionTags map[uint32]int = map[uint32]int{
|
||||||
|
0x31316b34: 11, // 4k11
|
||||||
|
0x32316b34: 12, // 4k12
|
||||||
|
0x33316b34: 13, // 4k13
|
||||||
|
0x34316b34: 14, // 4k14
|
||||||
|
}
|
||||||
|
|
||||||
|
_4klangDelays []int = []int{ // these are the numerators, if denominator is 48, fraction of beat time
|
||||||
|
4, // 0 = 4.0f * (1.0f/32.0f) * (2.0f/3.0f)
|
||||||
|
6, // 1 = 4.0f * (1.0f/32.0f),
|
||||||
|
9, // 2 = 4.0f * (1.0f/32.0f) * (3.0f/2.0f),
|
||||||
|
8, // 3 = 4.0f * (1.0f/16.0f) * (2.0f/3.0f),
|
||||||
|
12, // 4 = 4.0f * (1.0f/16.0f),
|
||||||
|
18, // 5 = 4.0f * (1.0f/16.0f) * (3.0f/2.0f),
|
||||||
|
16, // 6 = 4.0f * (1.0f/8.0f) * (2.0f/3.0f),
|
||||||
|
24, // 7 = 4.0f * (1.0f/8.0f),
|
||||||
|
36, // 8 = 4.0f * (1.0f/8.0f) * (3.0f/2.0f),
|
||||||
|
32, // 9 = 4.0f * (1.0f/4.0f) * (2.0f/3.0f),
|
||||||
|
48, // 10 = 4.0f * (1.0f/4.0f),
|
||||||
|
72, // 11 = 4.0f * (1.0f/4.0f) * (3.0f/2.0f),
|
||||||
|
64, // 12 = 4.0f * (1.0f/2.0f) * (2.0f/3.0f),
|
||||||
|
96, // 13 = 4.0f * (1.0f/2.0f),
|
||||||
|
144, // 14 = 4.0f * (1.0f/2.0f) * (3.0f/2.0f),
|
||||||
|
128, // 15 = 4.0f * (1.0f) * (2.0f/3.0f),
|
||||||
|
192, // 16 = 4.0f * (1.0f),
|
||||||
|
288, // 17 = 4.0f * (1.0f) * (3.0f/2.0f),
|
||||||
|
256, // 18 = 4.0f * (2.0f) * (2.0f/3.0f),
|
||||||
|
384, // 19 = 4.0f * (2.0f),
|
||||||
|
576, // 20 = 4.0f * (2.0f) * (3.0f/2.0f),
|
||||||
|
72, // 21 = 4.0f * (3.0f/8.0f),
|
||||||
|
120, // 22 = 4.0f * (5.0f/8.0f),
|
||||||
|
168, // 23 = 4.0f * (7.0f/8.0f),
|
||||||
|
216, // 24 = 4.0f * (9.0f/8.0f),
|
||||||
|
264, // 25 = 4.0f * (11.0f/8.0f),
|
||||||
|
312, // 26 = 4.0f * (13.0f/8.0f),
|
||||||
|
360, // 27 = 4.0f * (15.0f/8.0f),
|
||||||
|
144, // 28 = 4.0f * (3.0f/4.0f),
|
||||||
|
240, // 29 = 4.0f * (5.0f/4.0f),
|
||||||
|
336, // 30 = 4.0f * (7.0f/4.0f),
|
||||||
|
288, // 31 = 4.0f * (3.0f/2.0f),
|
||||||
|
288, // 32 = 4.0f * (3.0f/2.0f),
|
||||||
|
}
|
||||||
|
|
||||||
|
_4klangUnitPorts []_4klangPorts = []_4klangPorts{
|
||||||
|
{"", [8]string{"", "", "", "", "", "", "", ""}},
|
||||||
|
{"envelope", [8]string{"", "", "gain", "attack", "decay", "", "release", ""}},
|
||||||
|
{"oscillator", [8]string{"", "transpose", "detune", "", "phase", "color", "shape", "gain"}},
|
||||||
|
{"filter", [8]string{"", "", "", "", "frequency", "resonance", "", ""}},
|
||||||
|
{"envelope", [8]string{"", "", "drive", "frequency", "", "", "", ""}},
|
||||||
|
{"delay", [8]string{"pregain", "feedback", "dry", "damp", "", "", "", ""}},
|
||||||
|
{"", [8]string{"", "", "", "", "", "", "", ""}},
|
||||||
|
{"", [8]string{"", "", "", "", "", "", "", ""}},
|
||||||
|
{"pan", [8]string{"panning", "", "", "", "", "", "", ""}},
|
||||||
|
{"outaux", [8]string{"auxgain", "outgain", "", "", "", "", "", ""}},
|
||||||
|
{"", [8]string{"", "", "", "", "", "", "", ""}},
|
||||||
|
{"load", [8]string{"value", "", "", "", "", "", "", ""}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func read4klangName(r io.Reader) (string, error) {
|
||||||
|
var name [_4KLANG_MAX_NAME_LEN]byte
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &name); err != nil {
|
||||||
|
return "", fmt.Errorf("binary.Read: %w", err)
|
||||||
|
}
|
||||||
|
n := bytes.IndexByte(name[:], 0)
|
||||||
|
if n == -1 {
|
||||||
|
n = _4KLANG_MAX_NAME_LEN
|
||||||
|
}
|
||||||
|
return string(name[:n]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangUnits(r io.Reader, version, instrIndex int, m _4klangTargetMap, id *int) (units []Unit, err error) {
|
||||||
|
numUnits := _4KLANG_MAX_UNITS
|
||||||
|
if version <= 13 {
|
||||||
|
numUnits = 32
|
||||||
|
}
|
||||||
|
units = make([]Unit, 0, numUnits)
|
||||||
|
for unitIndex := 0; unitIndex < numUnits; unitIndex++ {
|
||||||
|
var u []Unit
|
||||||
|
if u, err = read4klangUnit(r, version); err != nil {
|
||||||
|
return nil, fmt.Errorf("read4klangUnit: %w", err)
|
||||||
|
}
|
||||||
|
if u == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[_4klangStackUnit{instrIndex, unitIndex}] = *id
|
||||||
|
for i := range u {
|
||||||
|
u[i].ID = *id
|
||||||
|
*id++
|
||||||
|
}
|
||||||
|
units = append(units, u...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangUnit(r io.Reader, version int) ([]Unit, error) {
|
||||||
|
var unitType byte
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &unitType); err != nil {
|
||||||
|
return nil, fmt.Errorf("binary.Read: %w", err)
|
||||||
|
}
|
||||||
|
var vals [15]byte
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &vals); err != nil {
|
||||||
|
return nil, fmt.Errorf("binary.Read: %w", err)
|
||||||
|
}
|
||||||
|
if version <= 13 {
|
||||||
|
// versions <= 13 had 16 unused slots for each unit
|
||||||
|
if written, err := io.CopyN(io.Discard, r, 16); err != nil || written < 16 {
|
||||||
|
return nil, fmt.Errorf("io.CopyN: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch unitType {
|
||||||
|
case 1:
|
||||||
|
return read4klangENV(vals, version), nil
|
||||||
|
case 2:
|
||||||
|
return read4klangVCO(vals, version), nil
|
||||||
|
case 3:
|
||||||
|
return read4klangVCF(vals, version), nil
|
||||||
|
case 4:
|
||||||
|
return read4klangDST(vals, version), nil
|
||||||
|
case 5:
|
||||||
|
return read4klangDLL(vals, version), nil
|
||||||
|
case 6:
|
||||||
|
return read4klangFOP(vals, version), nil
|
||||||
|
case 7:
|
||||||
|
return read4klangFST(vals, version), nil
|
||||||
|
case 8:
|
||||||
|
return read4klangPAN(vals, version), nil
|
||||||
|
case 9:
|
||||||
|
return read4klangOUT(vals, version), nil
|
||||||
|
case 10:
|
||||||
|
return read4klangACC(vals, version), nil
|
||||||
|
case 11:
|
||||||
|
return read4klangFLD(vals, version), nil
|
||||||
|
default:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangENV(vals [15]byte, version int) []Unit {
|
||||||
|
return []Unit{{
|
||||||
|
Type: "envelope",
|
||||||
|
Parameters: map[string]int{
|
||||||
|
"stereo": 0,
|
||||||
|
"attack": int(vals[0]),
|
||||||
|
"decay": int(vals[1]),
|
||||||
|
"sustain": int(vals[2]),
|
||||||
|
"release": int(vals[3]),
|
||||||
|
"gain": int(vals[4]),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangVCO(vals [15]byte, version int) []Unit {
|
||||||
|
v := vals[:8]
|
||||||
|
var transpose, detune, phase, color, gate, shape, gain, flags, stereo, typ, lfo int
|
||||||
|
transpose, v = int(v[0]), v[1:]
|
||||||
|
detune, v = int(v[0]), v[1:]
|
||||||
|
phase, v = int(v[0]), v[1:]
|
||||||
|
if version <= 11 {
|
||||||
|
gate = 0x55
|
||||||
|
} else {
|
||||||
|
gate, v = int(v[0]), v[1:]
|
||||||
|
}
|
||||||
|
color, v = int(v[0]), v[1:]
|
||||||
|
shape, v = int(v[0]), v[1:]
|
||||||
|
gain, v = int(v[0]), v[1:]
|
||||||
|
flags, v = int(v[0]), v[1:]
|
||||||
|
if flags&0x10 == 0x10 {
|
||||||
|
lfo = 1
|
||||||
|
}
|
||||||
|
if flags&0x40 == 0x40 {
|
||||||
|
stereo = 1
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case flags&0x01 == 0x01: // Sine
|
||||||
|
typ = Sine
|
||||||
|
if version <= 13 {
|
||||||
|
color = 128
|
||||||
|
}
|
||||||
|
case flags&0x02 == 0x02: // Trisaw
|
||||||
|
typ = Trisaw
|
||||||
|
case flags&0x04 == 0x04: // Pulse
|
||||||
|
typ = Pulse
|
||||||
|
case flags&0x08 == 0x08: // Noise is handled differently in sointu
|
||||||
|
return []Unit{{
|
||||||
|
Type: "noise",
|
||||||
|
Parameters: map[string]int{
|
||||||
|
"stereo": stereo,
|
||||||
|
"shape": shape,
|
||||||
|
"gain": gain,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
case flags&0x20 == 0x20: // Gate
|
||||||
|
color = gate
|
||||||
|
}
|
||||||
|
return []Unit{{
|
||||||
|
Type: "oscillator",
|
||||||
|
Parameters: map[string]int{
|
||||||
|
"stereo": stereo,
|
||||||
|
"transpose": transpose,
|
||||||
|
"detune": detune,
|
||||||
|
"phase": phase,
|
||||||
|
"color": color,
|
||||||
|
"shape": shape,
|
||||||
|
"gain": gain,
|
||||||
|
"type": typ,
|
||||||
|
"lfo": lfo,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangVCF(vals [15]byte, version int) []Unit {
|
||||||
|
flags := vals[2]
|
||||||
|
var stereo, lowpass, bandpass, highpass, neghighpass int
|
||||||
|
if flags&0x01 == 0x01 {
|
||||||
|
lowpass = 1
|
||||||
|
}
|
||||||
|
if flags&0x02 == 0x02 {
|
||||||
|
highpass = 1
|
||||||
|
}
|
||||||
|
if flags&0x04 == 0x04 {
|
||||||
|
bandpass = 1
|
||||||
|
}
|
||||||
|
if flags&0x08 == 0x08 {
|
||||||
|
lowpass = 1
|
||||||
|
neghighpass = 1
|
||||||
|
}
|
||||||
|
if flags&0x10 == 0x10 {
|
||||||
|
stereo = 1
|
||||||
|
}
|
||||||
|
return []Unit{{
|
||||||
|
Type: "filter",
|
||||||
|
Parameters: map[string]int{
|
||||||
|
"stereo": stereo,
|
||||||
|
"frequency": int(vals[0]),
|
||||||
|
"resonance": int(vals[1]),
|
||||||
|
"lowpass": lowpass,
|
||||||
|
"bandpass": bandpass,
|
||||||
|
"highpass": highpass,
|
||||||
|
"negbandpass": 0,
|
||||||
|
"neghighpass": neghighpass,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangDST(vals [15]byte, version int) []Unit {
|
||||||
|
return []Unit{
|
||||||
|
{Type: "distort", Parameters: map[string]int{"drive": int(vals[0]), "stereo": int(vals[2])}},
|
||||||
|
{Type: "hold", Parameters: map[string]int{"holdfreq": int(vals[1]), "stereo": int(vals[2])}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangDLL(vals [15]byte, version int) []Unit {
|
||||||
|
var delaytimes []int
|
||||||
|
var notetracking int
|
||||||
|
if vals[11] > 0 {
|
||||||
|
if vals[10] > 0 { // left reverb
|
||||||
|
delaytimes = []int{1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618}
|
||||||
|
} else { // right reverb
|
||||||
|
delaytimes = []int{1140, 1212, 1300, 1380, 1446, 1516, 1580, 1642}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
synctype := vals[9]
|
||||||
|
switch synctype {
|
||||||
|
case 0:
|
||||||
|
delaytimes = []int{int(vals[8]) * 16}
|
||||||
|
case 1: // relative to BPM
|
||||||
|
notetracking = 2
|
||||||
|
index := vals[8] >> 2
|
||||||
|
delaytime := 48
|
||||||
|
if int(index) < len(_4klangDelays) {
|
||||||
|
delaytime = _4klangDelays[index]
|
||||||
|
}
|
||||||
|
delaytimes = []int{delaytime}
|
||||||
|
case 2: // notetracking
|
||||||
|
notetracking = 1
|
||||||
|
delaytimes = []int{10787}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []Unit{{
|
||||||
|
Type: "delay",
|
||||||
|
Parameters: map[string]int{
|
||||||
|
"stereo": 0,
|
||||||
|
"pregain": int(vals[0]),
|
||||||
|
"dry": int(vals[1]),
|
||||||
|
"feedback": int(vals[2]),
|
||||||
|
"damp": int(vals[3]),
|
||||||
|
"notetracking": notetracking,
|
||||||
|
},
|
||||||
|
VarArgs: delaytimes,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangFOP(vals [15]byte, version int) []Unit {
|
||||||
|
var t string
|
||||||
|
var stereo int
|
||||||
|
switch vals[0] {
|
||||||
|
case 1:
|
||||||
|
t, stereo = "pop", 0
|
||||||
|
case 2:
|
||||||
|
t, stereo = "addp", 0
|
||||||
|
case 3:
|
||||||
|
t, stereo = "mulp", 0
|
||||||
|
case 4:
|
||||||
|
t, stereo = "push", 0
|
||||||
|
case 5:
|
||||||
|
t, stereo = "xch", 0
|
||||||
|
case 6:
|
||||||
|
t, stereo = "add", 0
|
||||||
|
case 7:
|
||||||
|
t, stereo = "mul", 0
|
||||||
|
case 8:
|
||||||
|
t, stereo = "addp", 1
|
||||||
|
case 9:
|
||||||
|
return []Unit{{Type: "loadnote", Parameters: map[string]int{"stereo": stereo}}, // 4klang loadnote gives 0..1, sointu gives -1..1
|
||||||
|
{Type: "loadval", Parameters: map[string]int{"value": 128, "stereo": stereo}},
|
||||||
|
{Type: "addp", Parameters: map[string]int{"stereo": stereo}},
|
||||||
|
{Type: "gain", Parameters: map[string]int{"stereo": stereo, "gain": 64}}}
|
||||||
|
default:
|
||||||
|
t, stereo = "mulp", 1
|
||||||
|
}
|
||||||
|
return []Unit{{
|
||||||
|
Type: t,
|
||||||
|
Parameters: map[string]int{"stereo": stereo},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangFST(vals [15]byte, version int) []Unit {
|
||||||
|
sendpop := 0
|
||||||
|
if vals[1]&0x40 == 0x40 {
|
||||||
|
sendpop = 1
|
||||||
|
}
|
||||||
|
return []Unit{{
|
||||||
|
Type: "send",
|
||||||
|
Parameters: map[string]int{
|
||||||
|
"amount": int(vals[0]),
|
||||||
|
"sendpop": sendpop,
|
||||||
|
"dest_stack": int(vals[2]),
|
||||||
|
"dest_unit": int(vals[3]),
|
||||||
|
"dest_slot": int(vals[4]),
|
||||||
|
"dest_id": int(vals[5]),
|
||||||
|
}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fix4klangTargets(instrIndex int, instr Instrument, m _4klangTargetMap) {
|
||||||
|
for _, u := range instr.Units {
|
||||||
|
if u.Type == "send" {
|
||||||
|
destStack := u.Parameters["dest_stack"]
|
||||||
|
if destStack == 255 {
|
||||||
|
destStack = instrIndex
|
||||||
|
}
|
||||||
|
fourKlangTarget := _4klangStackUnit{
|
||||||
|
destStack,
|
||||||
|
u.Parameters["dest_unit"]}
|
||||||
|
u.Parameters["target"] = m[fourKlangTarget]
|
||||||
|
if u.Parameters["dest_id"] < len(_4klangUnitPorts) && u.Parameters["dest_slot"] < 8 {
|
||||||
|
if u.Parameters["dest_id"] == 4 && u.Parameters["dest_slot"] == 3 { // distortion is split into 2 units
|
||||||
|
u.Parameters["target"]++
|
||||||
|
u.Parameters["port"] = 0
|
||||||
|
} else {
|
||||||
|
modTarget := _4klangUnitPorts[u.Parameters["dest_id"]]
|
||||||
|
for i, s := range Ports[modTarget.UnitType] {
|
||||||
|
if s == modTarget.PortName[u.Parameters["dest_slot"]] {
|
||||||
|
u.Parameters["port"] = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(u.Parameters, "dest_stack")
|
||||||
|
delete(u.Parameters, "dest_unit")
|
||||||
|
delete(u.Parameters, "dest_slot")
|
||||||
|
delete(u.Parameters, "dest_id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangPAN(vals [15]byte, version int) []Unit {
|
||||||
|
return []Unit{{
|
||||||
|
Type: "pan",
|
||||||
|
Parameters: map[string]int{
|
||||||
|
"stereo": 0,
|
||||||
|
"panning": int(vals[0]),
|
||||||
|
}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangOUT(vals [15]byte, version int) []Unit {
|
||||||
|
return []Unit{{
|
||||||
|
Type: "outaux",
|
||||||
|
Parameters: map[string]int{
|
||||||
|
"stereo": 1,
|
||||||
|
"outgain": int(vals[0]),
|
||||||
|
"auxgain": int(vals[1])},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangACC(vals [15]byte, version int) []Unit {
|
||||||
|
c := 0
|
||||||
|
if vals[0] != 0 {
|
||||||
|
c = 2
|
||||||
|
}
|
||||||
|
return []Unit{{
|
||||||
|
Type: "in",
|
||||||
|
Parameters: map[string]int{"stereo": 1, "channel": c},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read4klangFLD(vals [15]byte, version int) []Unit {
|
||||||
|
return []Unit{{
|
||||||
|
Type: "loadval",
|
||||||
|
Parameters: map[string]int{"stereo": 0, "value": int(vals[0])},
|
||||||
|
}}
|
||||||
|
}
|
BIN
examples/fourklang/2010_ergon_5.4kp
Normal file
BIN
examples/fourklang/2010_ergon_5.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/LightRythm.4kp
Normal file
BIN
examples/fourklang/LightRythm.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/baghdad.4kp
Normal file
BIN
examples/fourklang/baghdad.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/bf-enlighten.4kp
Normal file
BIN
examples/fourklang/bf-enlighten.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/c0c00n_001.4kp
Normal file
BIN
examples/fourklang/c0c00n_001.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/dollop.4kp
Normal file
BIN
examples/fourklang/dollop.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/example.4kp
Normal file
BIN
examples/fourklang/example.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/example2.4kp
Normal file
BIN
examples/fourklang/example2.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/kevinspacy.4kp
Normal file
BIN
examples/fourklang/kevinspacy.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/punqtured-sundowner.4kp
Normal file
BIN
examples/fourklang/punqtured-sundowner.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/untitled2.4kp
Normal file
BIN
examples/fourklang/untitled2.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang/virgill - 4klang basics.4kp
Normal file
BIN
examples/fourklang/virgill - 4klang basics.4kp
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_Dark.4ki
Normal file
BIN
examples/fourklang_instruments/BA_Dark.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_DarkChorus.4ki
Normal file
BIN
examples/fourklang_instruments/BA_DarkChorus.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_Deepness.4ki
Normal file
BIN
examples/fourklang_instruments/BA_Deepness.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_DirectPunchMS.4ki
Normal file
BIN
examples/fourklang_instruments/BA_DirectPunchMS.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_Mighty.4ki
Normal file
BIN
examples/fourklang_instruments/BA_Mighty.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_Mighty_Feedback.4ki
Normal file
BIN
examples/fourklang_instruments/BA_Mighty_Feedback.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_NotFromThisWorld.4ki
Normal file
BIN
examples/fourklang_instruments/BA_NotFromThisWorld.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_NotFromThisWorld2.4ki
Normal file
BIN
examples/fourklang_instruments/BA_NotFromThisWorld2.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_SawBass.4ki
Normal file
BIN
examples/fourklang_instruments/BA_SawBass.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/BA_SawBassFlanger.4ki
Normal file
BIN
examples/fourklang_instruments/BA_SawBassFlanger.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/GA_RestInPeaceMS.4ki
Normal file
BIN
examples/fourklang_instruments/GA_RestInPeaceMS.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/KY_GarageOrgan.4ki
Normal file
BIN
examples/fourklang_instruments/KY_GarageOrgan.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/KY_GarageOrganChorus.4ki
Normal file
BIN
examples/fourklang_instruments/KY_GarageOrganChorus.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/KY_Lullaby.4ki
Normal file
BIN
examples/fourklang_instruments/KY_Lullaby.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/KY_Lullaby2.4ki
Normal file
BIN
examples/fourklang_instruments/KY_Lullaby2.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/KY_Rhodes.4ki
Normal file
BIN
examples/fourklang_instruments/KY_Rhodes.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/LD_AlphaOmegaMS.4ki
Normal file
BIN
examples/fourklang_instruments/LD_AlphaOmegaMS.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/LD_Farscape.4ki
Normal file
BIN
examples/fourklang_instruments/LD_Farscape.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/LD_More&MoreMS.4ki
Normal file
BIN
examples/fourklang_instruments/LD_More&MoreMS.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/LD_Morpher.4ki
Normal file
BIN
examples/fourklang_instruments/LD_Morpher.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/LD_RestInPeaceMS.4ki
Normal file
BIN
examples/fourklang_instruments/LD_RestInPeaceMS.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/LD_Short&PunchyMS.4ki
Normal file
BIN
examples/fourklang_instruments/LD_Short&PunchyMS.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/PA_Fairies.4ki
Normal file
BIN
examples/fourklang_instruments/PA_Fairies.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/PA_Jarresque.4ki
Normal file
BIN
examples/fourklang_instruments/PA_Jarresque.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/PA_JarresqueChorus.4ki
Normal file
BIN
examples/fourklang_instruments/PA_JarresqueChorus.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/PA_LoFiChoir.4ki
Normal file
BIN
examples/fourklang_instruments/PA_LoFiChoir.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/PA_LongPad.4ki
Normal file
BIN
examples/fourklang_instruments/PA_LongPad.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/PA_Minorium.4ki
Normal file
BIN
examples/fourklang_instruments/PA_Minorium.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/PA_Strangeland.4ki
Normal file
BIN
examples/fourklang_instruments/PA_Strangeland.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/PA_StrangelandChorus.4ki
Normal file
BIN
examples/fourklang_instruments/PA_StrangelandChorus.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/PA_SynastasiaMS.4ki
Normal file
BIN
examples/fourklang_instruments/PA_SynastasiaMS.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/SY_RandomArp.4ki
Normal file
BIN
examples/fourklang_instruments/SY_RandomArp.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/SY_RandomArpFlanger.4ki
Normal file
BIN
examples/fourklang_instruments/SY_RandomArpFlanger.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/airy.4ki
Normal file
BIN
examples/fourklang_instruments/airy.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/basedrum.4ki
Normal file
BIN
examples/fourklang_instruments/basedrum.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/basedrum2.4ki
Normal file
BIN
examples/fourklang_instruments/basedrum2.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/basedrum3.4ki
Normal file
BIN
examples/fourklang_instruments/basedrum3.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/basedrum4.4ki
Normal file
BIN
examples/fourklang_instruments/basedrum4.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/bass.4ki
Normal file
BIN
examples/fourklang_instruments/bass.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/bass2.4ki
Normal file
BIN
examples/fourklang_instruments/bass2.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/clap.4ki
Normal file
BIN
examples/fourklang_instruments/clap.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/guitar.4ki
Normal file
BIN
examples/fourklang_instruments/guitar.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/guitar2.4ki
Normal file
BIN
examples/fourklang_instruments/guitar2.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/hihat.4ki
Normal file
BIN
examples/fourklang_instruments/hihat.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/hihat2.4ki
Normal file
BIN
examples/fourklang_instruments/hihat2.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/pad.4ki
Normal file
BIN
examples/fourklang_instruments/pad.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/pad2.4ki
Normal file
BIN
examples/fourklang_instruments/pad2.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/piano.4ki
Normal file
BIN
examples/fourklang_instruments/piano.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/piano2.4ki
Normal file
BIN
examples/fourklang_instruments/piano2.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/rimshot.4ki
Normal file
BIN
examples/fourklang_instruments/rimshot.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/snare.4ki
Normal file
BIN
examples/fourklang_instruments/snare.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/snare2.4ki
Normal file
BIN
examples/fourklang_instruments/snare2.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/snare3.4ki
Normal file
BIN
examples/fourklang_instruments/snare3.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/snare4.4ki
Normal file
BIN
examples/fourklang_instruments/snare4.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/snare5.4ki
Normal file
BIN
examples/fourklang_instruments/snare5.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/snare6.4ki
Normal file
BIN
examples/fourklang_instruments/snare6.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/string.4ki
Normal file
BIN
examples/fourklang_instruments/string.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/synth.4ki
Normal file
BIN
examples/fourklang_instruments/synth.4ki
Normal file
Binary file not shown.
BIN
examples/fourklang_instruments/synthFlanger.4ki
Normal file
BIN
examples/fourklang_instruments/synthFlanger.4ki
Normal file
Binary file not shown.
12
patch.go
12
patch.go
@ -192,6 +192,18 @@ func (p Patch) ParamHintString(instrIndex, unitIndex int, param string) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf(portList[value])
|
return fmt.Sprintf(portList[value])
|
||||||
}
|
}
|
||||||
|
case "delay":
|
||||||
|
switch param {
|
||||||
|
case "notetracking":
|
||||||
|
switch value {
|
||||||
|
case 0:
|
||||||
|
return "fixed"
|
||||||
|
case 1:
|
||||||
|
return "tracks pitch"
|
||||||
|
case 2:
|
||||||
|
return "tracks BPM"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
6
synth.go
6
synth.go
@ -22,7 +22,7 @@ type Synth interface {
|
|||||||
// delaylines should keep their content. Every change in the Patch triggers
|
// delaylines should keep their content. Every change in the Patch triggers
|
||||||
// an Update and if the Patch would be started fresh every time, it would
|
// an Update and if the Patch would be started fresh every time, it would
|
||||||
// lead to very choppy audio.
|
// lead to very choppy audio.
|
||||||
Update(patch Patch) error
|
Update(patch Patch, bpm int) error
|
||||||
|
|
||||||
// Trigger triggers a note for a given voice. Called between synth.Renders.
|
// Trigger triggers a note for a given voice. Called between synth.Renders.
|
||||||
Trigger(voice int, note byte)
|
Trigger(voice int, note byte)
|
||||||
@ -35,7 +35,7 @@ type Synth interface {
|
|||||||
// SynthService compiles a given Patch into a Synth, throwing errors if the
|
// SynthService compiles a given Patch into a Synth, throwing errors if the
|
||||||
// Patch is malformed.
|
// Patch is malformed.
|
||||||
type SynthService interface {
|
type SynthService interface {
|
||||||
Compile(patch Patch) (Synth, error)
|
Compile(patch Patch, bpm int) (Synth, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render fills an stereo audio buffer using a Synth, disregarding all syncs and
|
// Render fills an stereo audio buffer using a Synth, disregarding all syncs and
|
||||||
@ -62,7 +62,7 @@ func Play(synthService SynthService, song Song, release bool) ([]float32, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
synth, err := synthService.Compile(song.Patch)
|
synth, err := synthService.Compile(song.Patch, song.BPM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("sointu.Play failed: %v", err)
|
return nil, fmt.Errorf("sointu.Play failed: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -106,9 +106,9 @@ func (f *FileDialogStyle) Layout(gtx C) D {
|
|||||||
n = n[0 : len(n)-len(extension)]
|
n = n[0 : len(n)-len(extension)]
|
||||||
switch f.dialog.UseAltExt.Value {
|
switch f.dialog.UseAltExt.Value {
|
||||||
case true:
|
case true:
|
||||||
n += ".json"
|
n += f.ExtAlt
|
||||||
default:
|
default:
|
||||||
n += ".yml"
|
n += f.ExtMain
|
||||||
}
|
}
|
||||||
f.dialog.FileName.SetText(n)
|
f.dialog.FileName.SetText(n)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package gioui
|
package gioui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -66,15 +67,23 @@ func (t *Tracker) SaveInstrument() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) loadSong(filename string) {
|
func (t *Tracker) loadSong(filename string) {
|
||||||
bytes, err := ioutil.ReadFile(filename)
|
b, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var song sointu.Song
|
var song sointu.Song
|
||||||
if errJSON := json.Unmarshal(bytes, &song); errJSON != nil {
|
if errJSON := json.Unmarshal(b, &song); errJSON != nil {
|
||||||
if errYaml := yaml.Unmarshal(bytes, &song); errYaml != nil {
|
if errYaml := yaml.Unmarshal(b, &song); errYaml != nil {
|
||||||
t.Alert.Update(fmt.Sprintf("Error unmarshaling a song file: %v / %v", errYaml, errJSON), Error, time.Second*3)
|
var err4kp error
|
||||||
return
|
var patch sointu.Patch
|
||||||
|
if patch, err4kp = sointu.Read4klangPatch(bytes.NewReader(b)); err4kp != nil {
|
||||||
|
t.Alert.Update(fmt.Sprintf("Error unmarshaling a song file: %v / %v / %v", errYaml, errJSON, err4kp), Error, time.Second*3)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
song = t.Song()
|
||||||
|
song.Score = t.Song().Score.Copy()
|
||||||
|
song.Patch = patch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if song.Score.Length <= 0 || len(song.Score.Tracks) == 0 || len(song.Patch) == 0 {
|
if song.Score.Length <= 0 || len(song.Score.Tracks) == 0 || len(song.Patch) == 0 {
|
||||||
@ -148,17 +157,22 @@ func (t *Tracker) saveInstrument(filename string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracker) loadInstrument(filename string) bool {
|
func (t *Tracker) loadInstrument(filename string) bool {
|
||||||
bytes, err := ioutil.ReadFile(filename)
|
b, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var instrument sointu.Instrument
|
var instrument sointu.Instrument
|
||||||
if errJSON := json.Unmarshal(bytes, &instrument); errJSON != nil {
|
if errJSON := json.Unmarshal(b, &instrument); errJSON != nil {
|
||||||
if errYaml := yaml.Unmarshal(bytes, &instrument); errYaml != nil {
|
if errYaml := yaml.Unmarshal(b, &instrument); errYaml != nil {
|
||||||
t.Alert.Update(fmt.Sprintf("Error unmarshaling an instrument file: %v / %v", errYaml, errJSON), Error, time.Second*3)
|
var err4ki error
|
||||||
return false
|
if instrument, err4ki = sointu.Read4klangInstrument(bytes.NewReader(b)); err4ki != nil {
|
||||||
|
t.Alert.Update(fmt.Sprintf("Error unmarshaling an instrument file: %v / %v / %v", errYaml, errJSON, err4ki), Error, time.Second*3)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// the 4klang instrument names are junk, replace them with the filename without extension
|
||||||
|
instrument.Name = filepath.Base(filename[:len(filename)-len(filepath.Ext(filename))])
|
||||||
if len(instrument.Units) == 0 {
|
if len(instrument.Units) == 0 {
|
||||||
t.Alert.Update("The instrument file is malformed", Error, time.Second*3)
|
t.Alert.Update("The instrument file is malformed", Error, time.Second*3)
|
||||||
return false
|
return false
|
||||||
|
@ -58,6 +58,7 @@ func (t *Tracker) Layout(gtx layout.Context) {
|
|||||||
}
|
}
|
||||||
fstyle := OpenFileDialog(t.Theme, t.OpenSongDialog)
|
fstyle := OpenFileDialog(t.Theme, t.OpenSongDialog)
|
||||||
fstyle.Title = "Open Song File"
|
fstyle.Title = "Open Song File"
|
||||||
|
fstyle.ExtAlt = ".4kp"
|
||||||
fstyle.Layout(gtx)
|
fstyle.Layout(gtx)
|
||||||
for ok, file := t.OpenSongDialog.FileSelected(); ok; ok, file = t.OpenSongDialog.FileSelected() {
|
for ok, file := t.OpenSongDialog.FileSelected(); ok; ok, file = t.OpenSongDialog.FileSelected() {
|
||||||
t.loadSong(file)
|
t.loadSong(file)
|
||||||
@ -89,6 +90,7 @@ func (t *Tracker) Layout(gtx layout.Context) {
|
|||||||
fstyle.Layout(gtx)
|
fstyle.Layout(gtx)
|
||||||
fstyle = OpenFileDialog(t.Theme, t.OpenInstrumentDialog)
|
fstyle = OpenFileDialog(t.Theme, t.OpenInstrumentDialog)
|
||||||
fstyle.Title = "Open Instrument File"
|
fstyle.Title = "Open Instrument File"
|
||||||
|
fstyle.ExtAlt = ".4ki"
|
||||||
for ok, file := t.OpenInstrumentDialog.FileSelected(); ok; ok, file = t.OpenInstrumentDialog.FileSelected() {
|
for ok, file := t.OpenInstrumentDialog.FileSelected(); ok; ok, file = t.OpenInstrumentDialog.FileSelected() {
|
||||||
t.loadInstrument(file)
|
t.loadInstrument(file)
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
ModelSamplesPerRowChangedMessage struct {
|
ModelSamplesPerRowChangedMessage struct {
|
||||||
int
|
BPM, RowsPerBeat int
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelPanicMessage struct {
|
ModelPanicMessage struct {
|
||||||
@ -1088,14 +1088,43 @@ func (m *Model) Param(index int) (Parameter, error) {
|
|||||||
if index < len(unit.VarArgs) {
|
if index < len(unit.VarArgs) {
|
||||||
val := unit.VarArgs[index]
|
val := unit.VarArgs[index]
|
||||||
var text string
|
var text string
|
||||||
if unit.Parameters["notetracking"] == 1 {
|
switch unit.Parameters["notetracking"] {
|
||||||
|
default:
|
||||||
|
case 0:
|
||||||
|
text = fmt.Sprintf("%v / %.3f rows", val, float32(val)/float32(m.song.SamplesPerRow()))
|
||||||
|
return Parameter{Type: IntegerParameter, Min: 1, Max: 65535, Name: "delaytime", Hint: text, Value: val, LargeStep: 256}, nil
|
||||||
|
case 1:
|
||||||
relPitch := float64(val) / 10787
|
relPitch := float64(val) / 10787
|
||||||
semitones := -math.Log2(relPitch) * 12
|
semitones := -math.Log2(relPitch) * 12
|
||||||
text = fmt.Sprintf("%v / %.3f st", val, semitones)
|
text = fmt.Sprintf("%v / %.3f st", val, semitones)
|
||||||
} else {
|
return Parameter{Type: IntegerParameter, Min: 1, Max: 65535, Name: "delaytime", Hint: text, Value: val, LargeStep: 256}, nil
|
||||||
text = fmt.Sprintf("%v / %.3f rows", val, float32(val)/float32(m.song.SamplesPerRow()))
|
case 2:
|
||||||
|
k := 0
|
||||||
|
v := val
|
||||||
|
for v&1 == 0 { // divide val by 2 until it is odd
|
||||||
|
v >>= 1
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
text := ""
|
||||||
|
switch v {
|
||||||
|
case 1:
|
||||||
|
if k <= 7 {
|
||||||
|
text = fmt.Sprintf(" (1/%d triplet)", 1<<(7-k))
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
if k <= 6 {
|
||||||
|
text = fmt.Sprintf(" (1/%d)", 1<<(6-k))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 9:
|
||||||
|
if k <= 5 {
|
||||||
|
text = fmt.Sprintf(" (1/%d dotted)", 1<<(5-k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = fmt.Sprintf("%v / %.3f beats%s", val, float32(val)/48.0, text)
|
||||||
|
return Parameter{Type: IntegerParameter, Min: 1, Max: 576, Name: "delaytime", Hint: text, Value: val, LargeStep: 16}, nil
|
||||||
}
|
}
|
||||||
return Parameter{Type: IntegerParameter, Min: 1, Max: 65535, Name: "delaytime", Hint: text, Value: val, LargeStep: 256}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Parameter{}, errors.New("invalid parameter")
|
return Parameter{}, errors.New("invalid parameter")
|
||||||
@ -1240,7 +1269,7 @@ func (m *Model) notifyScoreChange() {
|
|||||||
|
|
||||||
func (m *Model) notifySamplesPerRowChange() {
|
func (m *Model) notifySamplesPerRowChange() {
|
||||||
select {
|
select {
|
||||||
case m.modelMessages <- ModelSamplesPerRowChangedMessage{m.song.SamplesPerRow()}:
|
case m.modelMessages <- ModelSamplesPerRowChangedMessage{m.song.BPM, m.song.RowsPerBeat}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ type (
|
|||||||
position SongRow
|
position SongRow
|
||||||
samplesSinceEvent []int
|
samplesSinceEvent []int
|
||||||
samplesPerRow int
|
samplesPerRow int
|
||||||
|
bpm int
|
||||||
volume Volume
|
volume Volume
|
||||||
voiceStates [vm.MAX_VOICES]float32
|
voiceStates [vm.MAX_VOICES]float32
|
||||||
|
|
||||||
@ -242,7 +243,9 @@ loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ModelSamplesPerRowChangedMessage:
|
case ModelSamplesPerRowChangedMessage:
|
||||||
p.samplesPerRow = m.int
|
p.samplesPerRow = 44100 * 60 / (m.BPM * m.RowsPerBeat)
|
||||||
|
p.bpm = m.BPM
|
||||||
|
p.compileOrUpdateSynth()
|
||||||
case ModelPlayFromPositionMessage:
|
case ModelPlayFromPositionMessage:
|
||||||
p.playing = true
|
p.playing = true
|
||||||
p.position = m.SongRow
|
p.position = m.SongRow
|
||||||
@ -297,7 +300,7 @@ loop:
|
|||||||
|
|
||||||
func (p *Player) compileOrUpdateSynth() {
|
func (p *Player) compileOrUpdateSynth() {
|
||||||
if p.synth != nil {
|
if p.synth != nil {
|
||||||
err := p.synth.Update(p.patch)
|
err := p.synth.Update(p.patch, p.bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.synth = nil
|
p.synth = nil
|
||||||
p.trySend(PlayerCrashMessage{fmt.Errorf("synth.Update: %w", err)})
|
p.trySend(PlayerCrashMessage{fmt.Errorf("synth.Update: %w", err)})
|
||||||
@ -305,7 +308,7 @@ func (p *Player) compileOrUpdateSynth() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
p.synth, err = p.synthService.Compile(p.patch)
|
p.synth, err = p.synthService.Compile(p.patch, p.bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.synth = nil
|
p.synth = nil
|
||||||
p.trySend(PlayerCrashMessage{fmt.Errorf("synthService.Compile: %w", err)})
|
p.trySend(PlayerCrashMessage{fmt.Errorf("synthService.Compile: %w", err)})
|
||||||
|
@ -58,7 +58,7 @@ var UnitTypes = map[string]([]UnitParameter){
|
|||||||
{Name: "dry", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
{Name: "dry", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||||
{Name: "feedback", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
{Name: "feedback", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||||
{Name: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
{Name: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||||
{Name: "notetracking", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
{Name: "notetracking", MinValue: 0, MaxValue: 2, CanSet: true, CanModulate: false},
|
||||||
{Name: "delaytime", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}},
|
{Name: "delaytime", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}},
|
||||||
"compressor": []UnitParameter{
|
"compressor": []UnitParameter{
|
||||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||||
|
@ -33,7 +33,7 @@ type SampleOffset struct {
|
|||||||
LoopLength uint16
|
LoopLength uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func Encode(patch sointu.Patch, featureSet FeatureSet) (*BytePatch, error) {
|
func Encode(patch sointu.Patch, featureSet FeatureSet, bpm int) (*BytePatch, error) {
|
||||||
c := BytePatch{PolyphonyBitmask: polyphonyBitmask(patch), NumVoices: uint32(patch.NumVoices())}
|
c := BytePatch{PolyphonyBitmask: polyphonyBitmask(patch), NumVoices: uint32(patch.NumVoices())}
|
||||||
if c.NumVoices > 32 {
|
if c.NumVoices > 32 {
|
||||||
return nil, fmt.Errorf("Sointu does not support more than 32 concurrent voices; patch uses %v", c.NumVoices)
|
return nil, fmt.Errorf("Sointu does not support more than 32 concurrent voices; patch uses %v", c.NumVoices)
|
||||||
@ -42,7 +42,7 @@ func Encode(patch sointu.Patch, featureSet FeatureSet) (*BytePatch, error) {
|
|||||||
globalAddrs := map[int]uint16{}
|
globalAddrs := map[int]uint16{}
|
||||||
globalFixups := map[int]([]int){}
|
globalFixups := map[int]([]int){}
|
||||||
voiceNo := 0
|
voiceNo := 0
|
||||||
delayTable, delayIndices := constructDelayTimeTable(patch)
|
delayTable, delayIndices := constructDelayTimeTable(patch, bpm)
|
||||||
c.DelayTimes = make([]uint16, len(delayTable))
|
c.DelayTimes = make([]uint16, len(delayTable))
|
||||||
for i := range delayTable {
|
for i := range delayTable {
|
||||||
c.DelayTimes[i] = uint16(delayTable[i])
|
c.DelayTimes[i] = uint16(delayTable[i])
|
||||||
@ -171,7 +171,7 @@ func Encode(patch sointu.Patch, featureSet FeatureSet) (*BytePatch, error) {
|
|||||||
if count == 0 {
|
if count == 0 {
|
||||||
continue // skip encoding delays without any delay lines
|
continue // skip encoding delays without any delay lines
|
||||||
}
|
}
|
||||||
countTrack := count*2 - 1 + unit.Parameters["notetracking"] // 1 means no note tracking and 1 delay, 2 means notetracking with 1 delay, 3 means no note tracking and 2 delays etc.
|
countTrack := count*2 - 1 + (unit.Parameters["notetracking"] & 1) // 1 means no note tracking and 1 delay, 2 means notetracking with 1 delay, 3 means no note tracking and 2 delays etc.
|
||||||
values = append(values, byte(delayIndices[instrIndex][unitIndex]), byte(countTrack))
|
values = append(values, byte(delayIndices[instrIndex][unitIndex]), byte(countTrack))
|
||||||
}
|
}
|
||||||
c.Commands = append(c.Commands, byte(opcode+unit.Parameters["stereo"]))
|
c.Commands = append(c.Commands, byte(opcode+unit.Parameters["stereo"]))
|
||||||
|
@ -16,17 +16,17 @@ import (
|
|||||||
type BridgeService struct {
|
type BridgeService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s BridgeService) Compile(patch sointu.Patch) (sointu.Synth, error) {
|
func (s BridgeService) Compile(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
synth, err := Synth(patch)
|
synth, err := Synth(patch, bpm)
|
||||||
return synth, err
|
return synth, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Synth(patch sointu.Patch) (*C.Synth, error) {
|
func Synth(patch sointu.Patch, bpm int) (*C.Synth, error) {
|
||||||
s := new(C.Synth)
|
s := new(C.Synth)
|
||||||
if n := patch.NumDelayLines(); n > 64 {
|
if n := patch.NumDelayLines(); n > 64 {
|
||||||
return nil, fmt.Errorf("native bridge has currently a hard limit of 64 delaylines; patch uses %v", n)
|
return nil, fmt.Errorf("native bridge has currently a hard limit of 64 delaylines; patch uses %v", n)
|
||||||
}
|
}
|
||||||
comPatch, err := vm.Encode(patch, vm.AllFeatures{})
|
comPatch, err := vm.Encode(patch, vm.AllFeatures{}, bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error compiling patch: %v", err)
|
return nil, fmt.Errorf("error compiling patch: %v", err)
|
||||||
}
|
}
|
||||||
@ -109,11 +109,11 @@ func (s *C.Synth) Release(voice int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
func (s *C.Synth) Update(patch sointu.Patch) error {
|
func (s *C.Synth) Update(patch sointu.Patch, bpm int) error {
|
||||||
if n := patch.NumDelayLines(); n > 64 {
|
if n := patch.NumDelayLines(); n > 64 {
|
||||||
return fmt.Errorf("native bridge has currently a hard limit of 64 delaylines; patch uses %v", n)
|
return fmt.Errorf("native bridge has currently a hard limit of 64 delaylines; patch uses %v", n)
|
||||||
}
|
}
|
||||||
comPatch, err := vm.Encode(patch, vm.AllFeatures{})
|
comPatch, err := vm.Encode(patch, vm.AllFeatures{}, bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error compiling patch: %v", err)
|
return fmt.Errorf("error compiling patch: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ func TestRenderSamples(t *testing.T) {
|
|||||||
sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
|
sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
|
||||||
}}}
|
}}}
|
||||||
|
|
||||||
synth, err := bridge.Synth(patch)
|
synth, err := bridge.Synth(patch, 120)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ func TestStackUnderflow(t *testing.T) {
|
|||||||
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||||
}}}
|
}}}
|
||||||
synth, err := bridge.Synth(patch)
|
synth, err := bridge.Synth(patch, 120)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
@ -146,7 +146,7 @@ func TestStackBalancing(t *testing.T) {
|
|||||||
sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||||
sointu.Unit{Type: "push", Parameters: map[string]int{}},
|
sointu.Unit{Type: "push", Parameters: map[string]int{}},
|
||||||
}}}
|
}}}
|
||||||
synth, err := bridge.Synth(patch)
|
synth, err := bridge.Synth(patch, 120)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
@ -179,7 +179,7 @@ func TestStackOverflow(t *testing.T) {
|
|||||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||||
}}}
|
}}}
|
||||||
synth, err := bridge.Synth(patch)
|
synth, err := bridge.Synth(patch, 120)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
@ -196,7 +196,7 @@ func TestDivideByZero(t *testing.T) {
|
|||||||
sointu.Unit{Type: "invgain", Parameters: map[string]int{"invgain": 0}},
|
sointu.Unit{Type: "invgain", Parameters: map[string]int{"invgain": 0}},
|
||||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||||
}}}
|
}}}
|
||||||
synth, err := bridge.Synth(patch)
|
synth, err := bridge.Synth(patch, 120)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
features := vm.NecessaryFeaturesFor(song.Patch)
|
features := vm.NecessaryFeaturesFor(song.Patch)
|
||||||
retmap := map[string]string{}
|
retmap := map[string]string{}
|
||||||
encodedPatch, err := vm.Encode(song.Patch, features)
|
encodedPatch, err := vm.Encode(song.Patch, features, song.BPM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(`could not encode patch: %v`, err)
|
return nil, fmt.Errorf(`could not encode patch: %v`, err)
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ func findSuperIntArray(arrays [][]int) ([]int, []int) {
|
|||||||
// Returns the delay time table and two dimensional array of integers where
|
// Returns the delay time table and two dimensional array of integers where
|
||||||
// element [i][u] is the index for instrument i / unit u in the delay table if
|
// element [i][u] is the index for instrument i / unit u in the delay table if
|
||||||
// the unit was a delay unit. For non-delay untis, the element is just 0.
|
// the unit was a delay unit. For non-delay untis, the element is just 0.
|
||||||
func constructDelayTimeTable(patch sointu.Patch) ([]int, [][]int) {
|
func constructDelayTimeTable(patch sointu.Patch, bpm int) ([]int, [][]int) {
|
||||||
ind := make([][]int, len(patch))
|
ind := make([][]int, len(patch))
|
||||||
var subarrays [][]int
|
var subarrays [][]int
|
||||||
// flatten the delay times into one array of arrays
|
// flatten the delay times into one array of arrays
|
||||||
@ -119,11 +119,18 @@ func constructDelayTimeTable(patch sointu.Patch) ([]int, [][]int) {
|
|||||||
// should use delay times
|
// should use delay times
|
||||||
if unit.Type == "delay" {
|
if unit.Type == "delay" {
|
||||||
ind[i][j] = len(subarrays)
|
ind[i][j] = len(subarrays)
|
||||||
end := unit.Parameters["count"]
|
converted := make([]int, len(unit.VarArgs))
|
||||||
if unit.Parameters["stereo"] > 0 {
|
copy(converted, unit.VarArgs)
|
||||||
end *= 2
|
if unit.Parameters["notetracking"] == 2 {
|
||||||
|
for i, t := range converted {
|
||||||
|
delay := 44100 * 60 * t / 48 / bpm
|
||||||
|
if delay > 65535 {
|
||||||
|
delay = 65535
|
||||||
|
}
|
||||||
|
converted[i] = delay
|
||||||
|
}
|
||||||
}
|
}
|
||||||
subarrays = append(subarrays, unit.VarArgs)
|
subarrays = append(subarrays, converted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,8 +63,8 @@ const (
|
|||||||
envStateRelease
|
envStateRelease
|
||||||
)
|
)
|
||||||
|
|
||||||
func Synth(patch sointu.Patch) (sointu.Synth, error) {
|
func Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
bytePatch, err := Encode(patch, AllFeatures{})
|
bytePatch, err := Encode(patch, AllFeatures{}, bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error compiling %v", err)
|
return nil, fmt.Errorf("error compiling %v", err)
|
||||||
}
|
}
|
||||||
@ -73,8 +73,8 @@ func Synth(patch sointu.Patch) (sointu.Synth, error) {
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SynthService) Compile(patch sointu.Patch) (sointu.Synth, error) {
|
func (s SynthService) Compile(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
synth, err := Synth(patch)
|
synth, err := Synth(patch, bpm)
|
||||||
return synth, err
|
return synth, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,8 +87,8 @@ func (s *Interpreter) Release(voiceIndex int) {
|
|||||||
s.synth.voices[voiceIndex].release = true
|
s.synth.voices[voiceIndex].release = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Interpreter) Update(patch sointu.Patch) error {
|
func (s *Interpreter) Update(patch sointu.Patch, bpm int) error {
|
||||||
bytePatch, err := Encode(patch, AllFeatures{})
|
bytePatch, err := Encode(patch, AllFeatures{}, bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error compiling %v", err)
|
return fmt.Errorf("error compiling %v", err)
|
||||||
}
|
}
|
||||||
@ -140,9 +140,11 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
|||||||
stereo := channels == 2
|
stereo := channels == 2
|
||||||
opNoStereo := (op & 0xFE) >> 1
|
opNoStereo := (op & 0xFE) >> 1
|
||||||
if opNoStereo == 0 {
|
if opNoStereo == 0 {
|
||||||
voices = voices[1:]
|
|
||||||
units = voices[0].units[:]
|
|
||||||
voicesRemaining--
|
voicesRemaining--
|
||||||
|
if voicesRemaining > 0 {
|
||||||
|
voices = voices[1:]
|
||||||
|
units = voices[0].units[:]
|
||||||
|
}
|
||||||
if mask := uint32(1) << uint32(voicesRemaining); s.bytePatch.PolyphonyBitmask&mask == mask {
|
if mask := uint32(1) << uint32(voicesRemaining); s.bytePatch.PolyphonyBitmask&mask == mask {
|
||||||
commands, values = commandInstr, valuesInstr
|
commands, values = commandInstr, valuesInstr
|
||||||
} else {
|
} else {
|
||||||
|
@ -76,7 +76,7 @@ func TestStackUnderflow(t *testing.T) {
|
|||||||
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||||
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
sointu.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||||
}}}
|
}}}
|
||||||
synth, err := vm.Synth(patch)
|
synth, err := vm.Synth(patch, 120)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ func TestStackBalancing(t *testing.T) {
|
|||||||
sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{
|
||||||
sointu.Unit{Type: "push", Parameters: map[string]int{}},
|
sointu.Unit{Type: "push", Parameters: map[string]int{}},
|
||||||
}}}
|
}}}
|
||||||
synth, err := vm.Synth(patch)
|
synth, err := vm.Synth(patch, 120)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user