mirror of
https://github.com/vsariola/sointu.git
synced 2026-08-16 22:56:58 -04:00
feat: add ability to import 4klang patches and instruments
This commit is contained in:
@@ -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])},
|
||||||
|
}}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-8
@@ -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
|
||||||
|
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
|
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
|
||||||
|
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
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-6
@@ -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 {
|
|
||||||
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
|
return Parameter{Type: IntegerParameter, Min: 1, Max: 65535, Name: "delaytime", Hint: text, Value: val, LargeStep: 256}, nil
|
||||||
|
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{}, 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:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -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)})
|
||||||
|
|||||||
+1
-1
@@ -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},
|
||||||
|
|||||||
+3
-3
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-5
@@ -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
|
||||||
}
|
}
|
||||||
subarrays = append(subarrays, unit.VarArgs)
|
converted[i] = delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subarrays = append(subarrays, converted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-7
@@ -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 {
|
||||||
|
voicesRemaining--
|
||||||
|
if voicesRemaining > 0 {
|
||||||
voices = voices[1:]
|
voices = voices[1:]
|
||||||
units = voices[0].units[:]
|
units = voices[0].units[:]
|
||||||
voicesRemaining--
|
}
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user