refactor(go4k): Remove all special treatment from samples and map Song 1-1 to what's in the .asm file.

Whoever uses it, probably wants their own Patch format, as now it is pretty cumbersome to work with sampleoffsets and delays, as the user needs to construct the delaytimes tables and sampleoffset tables.
This commit is contained in:
Veikko Sariola 2020-11-20 22:21:21 +02:00
parent f076409eb1
commit 95c8c9c2b7
9 changed files with 106 additions and 128 deletions

View File

@ -102,26 +102,30 @@ func FindSuperIntArray(arrays [][]int) ([]int, []int) {
// as possible. Especially: if two delay units use exactly the same
// delay times, they appear in the table only once.
func ConstructDelayTimeTable(patch Patch) ([]int, [][]int) {
ind := make([][]int, len(patch))
ind := make([][]int, len(patch.Instruments))
var subarrays [][]int
// flatten the delay times into one array of arrays
// saving the indices where they were placed
for i, instr := range patch {
for i, instr := range patch.Instruments {
ind[i] = make([]int, len(instr.Units))
for j, unit := range instr.Units {
// only include delay times for delays. Only delays
// should use delay times
if unit.Type == "delay" {
ind[i][j] = len(subarrays)
subarrays = append(subarrays, unit.DelayTimes)
end := unit.Parameters["count"]
if unit.Parameters["stereo"] > 0 {
end *= 2
}
subarrays = append(subarrays, patch.DelayTimes[unit.Parameters["delay"]:end])
}
}
}
delayTable, indices := FindSuperIntArray(subarrays)
// cancel the flattening, so unitindices can be used to
// to find the index of each delay in the delay table
unitindices := make([][]int, len(patch))
for i, instr := range patch {
unitindices := make([][]int, len(patch.Instruments))
for i, instr := range patch.Instruments {
unitindices[i] = make([]int, len(instr.Units))
for j, unit := range instr.Units {
if unit.Type == "delay" {
@ -139,10 +143,10 @@ func ConstructDelayTimeTable(patch Patch) ([]int, [][]int) {
// table by instrument i / unit j (units other than sample oscillators
// have the value 0)
func ConstructSampleOffsetTable(patch Patch) ([]SampleOffset, [][]int) {
unitindices := make([][]int, len(patch))
unitindices := make([][]int, len(patch.Instruments))
var offsetTable []SampleOffset
offsetMap := map[SampleOffset]int{}
for i, instr := range patch {
for i, instr := range patch.Instruments {
unitindices[i] = make([]int, len(instr.Units))
for j, unit := range instr.Units {
if unit.Type == "oscillator" && unit.Parameters["type"] == Sample {

View File

@ -16,8 +16,6 @@ func DeserializeAsm(asmcode string) (*Song, error) {
tracks := make([]Track, 0)
var patch Patch
var instr Instrument
var delayTimes []int
var sampleOffsets [][]int
paramReg, err := regexp.Compile(`([a-zA-Z]\w*)\s*\(\s*([0-9]+)\s*\)`) // matches FOO(42), groups "FOO" and "42"
if err != nil {
return nil, err
@ -111,7 +109,7 @@ func DeserializeAsm(asmcode string) (*Song, error) {
instr = Instrument{NumVoices: ints[0], Units: []Unit{}}
inInstrument = true
case "END_INSTRUMENT":
patch = append(patch, instr)
patch.Instruments = append(patch.Instruments, instr)
inInstrument = false
case "DELTIME":
ints, err := parseNumbers(rest)
@ -119,14 +117,17 @@ func DeserializeAsm(asmcode string) (*Song, error) {
return nil, err
}
for _, v := range ints {
delayTimes = append(delayTimes, v)
patch.DelayTimes = append(patch.DelayTimes, v)
}
case "SAMPLE_OFFSET":
ints, err := parseNumbers(rest)
if err != nil {
return nil, err
}
sampleOffsets = append(sampleOffsets, ints)
patch.SampleOffsets = append(patch.SampleOffsets, SampleOffset{
Start: ints[0],
LoopStart: ints[1],
LoopLength: ints[2]})
}
if inInstrument && strings.HasPrefix(word, "SU_") {
unittype := strings.ToLower(word[3:])
@ -157,26 +158,6 @@ func DeserializeAsm(asmcode string) (*Song, error) {
}
}
}
for i := range patch {
for u := range patch[i].Units {
if patch[i].Units[u].Type == "delay" {
s := patch[i].Units[u].Parameters["delay"]
e := patch[i].Units[u].Parameters["count"]
if patch[i].Units[u].Parameters["stereo"] == 1 {
e *= 2 // stereo delays use 'count' number of delaytimes, but for both channels
}
patch[i].Units[u].DelayTimes = append(patch[i].Units[u].DelayTimes, delayTimes[s:e]...)
delete(patch[i].Units[u].Parameters, "delay")
delete(patch[i].Units[u].Parameters, "count")
} else if patch[i].Units[u].Type == "oscillator" && patch[i].Units[u].Parameters["type"] == Sample {
sampleno := patch[i].Units[u].Parameters["color"]
patch[i].Units[u].Parameters["start"] = sampleOffsets[sampleno][0]
patch[i].Units[u].Parameters["loopstart"] = sampleOffsets[sampleno][1]
patch[i].Units[u].Parameters["looplength"] = sampleOffsets[sampleno][2]
delete(patch[i].Units[u].Parameters, "color")
}
}
}
s := Song{BPM: bpm, Patterns: patterns, Tracks: tracks, Patch: patch, SongLength: -1}
return &s, nil
}
@ -269,15 +250,13 @@ func SerializeAsm(song *Song) (string, error) {
}
indentation--
}
delayTable, delayIndices := ConstructDelayTimeTable(song.Patch)
sampleTable, sampleIndices := ConstructSampleOffsetTable(song.Patch)
// The actual printing starts here
println("%%define BPM %d", song.BPM)
// delay modulation is pretty much the only %define that the asm preprocessor cannot figure out
// as the preprocessor has no clue if a SEND modulates a delay unit. So, unfortunately, for the
// time being, we need to figure during export if INCLUDE_DELAY_MODULATION needs to be defined.
delaymod := false
for i, instrument := range song.Patch {
for i, instrument := range song.Patch.Instruments {
for j, unit := range instrument.Units {
if unit.Type == "send" {
targetInstrument := i
@ -288,10 +267,10 @@ func SerializeAsm(song *Song) (string, error) {
}
targetInstrument = v
}
if unit.Parameters["unit"] < 0 || unit.Parameters["unit"] >= len(song.Patch[targetInstrument].Units) {
if unit.Parameters["unit"] < 0 || unit.Parameters["unit"] >= len(song.Patch.Instruments[targetInstrument].Units) {
return "", fmt.Errorf("INSTRUMENT #%v / SEND #%v target unit %v out of range", i, j, unit.Parameters["unit"])
}
if song.Patch[targetInstrument].Units[unit.Parameters["unit"]].Type == "delay" && unit.Parameters["port"] == 5 {
if song.Patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]].Type == "delay" && unit.Parameters["port"] == 5 {
delaymod = true
}
}
@ -330,22 +309,12 @@ func SerializeAsm(song *Song) (string, error) {
println("END_TRACKS\n")
println("BEGIN_PATCH")
indentation++
for i, instrument := range song.Patch {
for _, instrument := range song.Patch.Instruments {
var instrTable [][]string
for j, unit := range instrument.Units {
for _, unit := range instrument.Units {
row := []string{fmt.Sprintf("SU_%v", strings.ToUpper(unit.Type))}
for _, parname := range paramorder[unit.Type] {
if unit.Type == "oscillator" && unit.Parameters["type"] == Sample && parname == "color" {
row = append(row, fmt.Sprintf("COLOR(%v)", strconv.Itoa(sampleIndices[i][j])))
} else if unit.Type == "delay" && parname == "count" {
count := len(unit.DelayTimes)
if unit.Parameters["stereo"] == 1 {
count /= 2
}
row = append(row, fmt.Sprintf("COUNT(%v)", strconv.Itoa(count)))
} else if unit.Type == "delay" && parname == "delay" {
row = append(row, fmt.Sprintf("DELAY(%v)", strconv.Itoa(delayIndices[i][j])))
} else if unit.Type == "oscillator" && parname == "type" {
if unit.Type == "oscillator" && parname == "type" {
switch unit.Parameters["type"] {
case Sine:
row = append(row, "TYPE(SINE)")
@ -372,9 +341,9 @@ func SerializeAsm(song *Song) (string, error) {
}
indentation--
println("END_PATCH\n")
if len(delayTable) > 0 {
if len(song.Patch.DelayTimes) > 0 {
var delStrTable [][]string
for _, v := range delayTable {
for _, v := range song.Patch.DelayTimes {
row := []string{"DELTIME", strconv.Itoa(int(v))}
delStrTable = append(delStrTable, row)
}
@ -382,9 +351,9 @@ func SerializeAsm(song *Song) (string, error) {
printTable(align(delStrTable, "lr"))
println("END_DELTIMES\n")
}
if len(sampleTable) > 0 {
if len(song.Patch.SampleOffsets) > 0 {
var samStrTable [][]string
for _, v := range sampleTable {
for _, v := range song.Patch.SampleOffsets {
samStrTable = append(samStrTable, []string{
"SAMPLE_OFFSET",
fmt.Sprintf("START(%d)", v.Start),

View File

@ -9,6 +9,7 @@ import (
"os"
"path"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
@ -101,15 +102,18 @@ func TestSerializingAllAsmFiles(t *testing.T) {
if err != nil {
t.Fatalf("Could not serialize asm file: %v", err)
}
song, err = go4k.DeserializeAsm(str) // deserialize again. The rendered song should still give same results.
song2, err := go4k.DeserializeAsm(str) // deserialize again. The rendered song should still give same results.
if err != nil {
t.Fatalf("could not parse the serialized asm code: %v", err)
}
synth, err := bridge.Synth(song.Patch)
if !reflect.DeepEqual(song, song2) {
t.Fatalf("serialize/deserialize does not result equal songs, before: %v, after %v", song, song2)
}
synth, err := bridge.Synth(song2.Patch)
if err != nil {
t.Fatalf("Compiling patch failed: %v", err)
}
buffer, err := go4k.Play(synth, *song)
buffer, err := go4k.Play(synth, *song2)
if err != nil {
t.Fatalf("Play failed: %v", err)
}

View File

@ -34,7 +34,7 @@ var opcodeTable = map[string]opTableEntry{
"filter": opTableEntry{C.su_filter_id, []string{"frequency", "resonance"}},
"clip": opTableEntry{C.su_clip_id, []string{}},
"pan": opTableEntry{C.su_pan_id, []string{"panning"}},
"delay": opTableEntry{C.su_delay_id, []string{"pregain", "dry", "feedback", "damp", "delaycount"}},
"delay": opTableEntry{C.su_delay_id, []string{"pregain", "dry", "feedback", "damp", "delay", "count"}},
"compressor": opTableEntry{C.su_compres_id, []string{"attack", "release", "invgain", "threshold", "ratio"}},
"speed": opTableEntry{C.su_speed_id, []string{}},
"out": opTableEntry{C.su_out_id, []string{"gain"}},
@ -81,16 +81,11 @@ func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) {
func Synth(patch go4k.Patch) (*C.Synth, error) {
s := new(C.Synth)
sampleno := 0
totalVoices := 0
commands := make([]byte, 0)
values := make([]byte, 0)
polyphonyBitmask := 0
delayTable, delayIndices := go4k.ConstructDelayTimeTable(patch)
for i, v := range delayTable {
s.DelayTimes[i] = C.ushort(v)
}
for insid, instr := range patch {
for insid, instr := range patch.Instruments {
if len(instr.Units) > 63 {
return nil, errors.New("An instrument can have a maximum of 63 units")
}
@ -105,26 +100,12 @@ func Synth(patch go4k.Patch) (*C.Synth, error) {
}
commands = append(commands, byte(opCode))
for _, paramname := range val.parameterList {
if unit.Type == "delay" && paramname == "delaycount" {
if unit.Parameters["stereo"] == 1 && len(unit.DelayTimes)%2 != 0 {
return nil, errors.New("Stereo delays should have even number of delaytimes")
}
values = append(values, byte(delayIndices[insid][unitid]))
count := len(unit.DelayTimes)
if unit.Parameters["stereo"] == 1 {
count /= 2
}
count = count*2 - 1
if unit.Type == "delay" && paramname == "count" {
count := unit.Parameters["count"]*2 - 1
if unit.Parameters["notetracking"] == 1 {
count++
}
values = append(values, byte(count))
} else if unit.Type == "oscillator" && unit.Parameters["type"] == go4k.Sample && paramname == "color" {
values = append(values, byte(sampleno))
s.SampleOffsets[sampleno].Start = (C.uint)(unit.Parameters["start"])
s.SampleOffsets[sampleno].LoopStart = (C.ushort)(unit.Parameters["loopstart"])
s.SampleOffsets[sampleno].LoopLength = (C.ushort)(unit.Parameters["looplength"])
sampleno++
} else if pval, ok := unit.Parameters[paramname]; ok {
values = append(values, byte(pval))
} else {
@ -204,6 +185,14 @@ func Synth(patch go4k.Patch) (*C.Synth, error) {
for i := range values {
s.Values[i] = (C.uchar)(values[i])
}
for i, deltime := range patch.DelayTimes {
s.DelayTimes[i] = (C.ushort)(deltime)
}
for i, samoff := range patch.SampleOffsets {
s.SampleOffsets[i].Start = (C.uint)(samoff.Start)
s.SampleOffsets[i].LoopStart = (C.ushort)(samoff.LoopStart)
s.SampleOffsets[i].LoopLength = (C.ushort)(samoff.LoopLength)
}
s.NumVoices = C.uint(totalVoices)
s.Polyphony = C.uint(polyphonyBitmask)
s.RandSeed = 1

View File

@ -22,12 +22,16 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
// const bufsize = su_max_samples * 2
func TestBridge(t *testing.T) {
patch := []go4k.Instrument{
go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}, []int{}},
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}, []int{}},
go4k.Unit{"out", map[string]int{"stereo": 1, "gain": 128}, []int{}},
}}}
patch := go4k.Patch{
Instruments: []go4k.Instrument{
go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
go4k.Unit{"out", map[string]int{"stereo": 1, "gain": 128}},
}}},
SampleOffsets: []go4k.SampleOffset{},
DelayTimes: []int{}}
synth, err := bridge.Synth(patch)
if err != nil {
t.Fatalf("bridge compile error: %v", err)

View File

@ -9,7 +9,6 @@ import (
type Unit struct {
Type string
Parameters map[string]int
DelayTimes []int
}
const (
@ -26,12 +25,22 @@ type Instrument struct {
Units []Unit
}
type SampleOffset struct {
Start int
LoopStart int
LoopLength int
}
// Patch is simply a list of instruments used in a song
type Patch []Instrument
type Patch struct {
Instruments []Instrument
DelayTimes []int
SampleOffsets []SampleOffset
}
func (p Patch) TotalVoices() int {
ret := 0
for _, i := range p {
for _, i := range p.Instruments {
ret += i.NumVoices
}
return ret
@ -41,7 +50,7 @@ func (patch Patch) InstrumentForVoice(voice int) (int, error) {
if voice < 0 {
return 0, errors.New("voice cannot be negative")
}
for i, instr := range patch {
for i, instr := range patch.Instruments {
if voice < instr.NumVoices {
return i, nil
} else {
@ -70,12 +79,6 @@ func Render(synth Synth, buffer []float32) error {
return err
}
type SampleOffset struct {
Start int
LoopStart int
LoopLength int
}
// UnitParameter documents one parameter that an unit takes
type UnitParameter struct {
Name string // thould be found with this name in the Unit.Parameters map
@ -171,7 +174,8 @@ var UnitTypes = []UnitType{
{Name: "feedback", 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: "delay", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true},
{Name: "delay", MinValue: 0, MaxValue: 255, CanSet: true, CanModulate: true},
{Name: "count", MinValue: 0, MaxValue: 255, CanSet: true, CanModulate: false},
}},
{
Name: "compressor",
@ -245,9 +249,6 @@ var UnitTypes = []UnitType{
{Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false},
{Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
{Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false},
{Name: "start", MinValue: 0, MaxValue: 3440659, CanSet: true, CanModulate: false}, // if type is "sample", then the waveform starts at this position
{Name: "loopstart", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}, // if type is "sample", then the loop starts at this position, relative to "start"
{Name: "looplength", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}, // if type is "sample", then the loop length is this i.e. loop ends at "start" + "loopstart" + "looplength"
}},
{
Name: "loadval",

View File

@ -8,7 +8,7 @@ import (
"github.com/vsariola/sointu/go4k"
)
const expectedMarshaled = `{"BPM":100,"Patterns":["QABEACAAAABLAE4AAAAAAA=="],"Tracks":[{"NumVoices":1,"Sequence":"AA=="}],"SongLength":0,"Patch":[{"NumVoices":1,"Units":[{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64},"DelayTimes":[]},{"Type":"oscillator","Parameters":{"color":96,"detune":64,"flags":64,"gain":128,"phase":0,"shape":64,"stereo":0,"transpose":64},"DelayTimes":[]},{"Type":"mulp","Parameters":{"stereo":0},"DelayTimes":[]},{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64},"DelayTimes":[]},{"Type":"oscillator","Parameters":{"color":64,"detune":64,"flags":64,"gain":128,"phase":64,"shape":96,"stereo":0,"transpose":72},"DelayTimes":[]},{"Type":"mulp","Parameters":{"stereo":0},"DelayTimes":[]},{"Type":"out","Parameters":{"gain":128,"stereo":1},"DelayTimes":[]}]}]}`
const expectedMarshaled = `{"BPM":100,"Patterns":["QABEACAAAABLAE4AAAAAAA=="],"Tracks":[{"NumVoices":1,"Sequence":"AA=="}],"SongLength":0,"Patch":{"Instruments":[{"NumVoices":1,"Units":[{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":96,"detune":64,"flags":64,"gain":128,"phase":0,"shape":64,"stereo":0,"transpose":64}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":64,"detune":64,"flags":64,"gain":128,"phase":64,"shape":96,"stereo":0,"transpose":72}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"out","Parameters":{"gain":128,"stereo":1}}]}],"DelayTimes":[],"SampleOffsets":[]}}`
var testSong = go4k.Song{
BPM: 100,
@ -18,15 +18,17 @@ var testSong = go4k.Song{
},
SongLength: 0,
Patch: go4k.Patch{
go4k.Instrument{NumVoices: 1, Units: []go4k.Unit{
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "flags": 0x40}, []int{}},
{"mulp", map[string]int{"stereo": 0}, []int{}},
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "flags": 0x40}, []int{}},
{"mulp", map[string]int{"stereo": 0}, []int{}},
{"out", map[string]int{"stereo": 1, "gain": 128}, []int{}},
}},
Instruments: []go4k.Instrument{{NumVoices: 1, Units: []go4k.Unit{
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "flags": 0x40}},
{"mulp", map[string]int{"stereo": 0}},
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "flags": 0x40}},
{"mulp", map[string]int{"stereo": 0}},
{"out", map[string]int{"stereo": 1, "gain": 128}},
}}},
DelayTimes: []int{},
SampleOffsets: []go4k.SampleOffset{},
},
}

View File

@ -23,15 +23,18 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
// const bufsize = su_max_samples * 2
func TestPlayer(t *testing.T) {
patch := []go4k.Instrument{go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
go4k.Unit{"oscillator", map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}, []int{}},
go4k.Unit{"mulp", map[string]int{"stereo": 0}, []int{}},
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
go4k.Unit{"oscillator", map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}, []int{}},
go4k.Unit{"mulp", map[string]int{"stereo": 0}, []int{}},
go4k.Unit{"out", map[string]int{"stereo": 1, "gain": 128}, []int{}},
}}}
patch := go4k.Patch{
Instruments: []go4k.Instrument{go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
go4k.Unit{"oscillator", map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}},
go4k.Unit{"mulp", map[string]int{"stereo": 0}},
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
go4k.Unit{"oscillator", map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}},
go4k.Unit{"mulp", map[string]int{"stereo": 0}},
go4k.Unit{"out", map[string]int{"stereo": 1, "gain": 128}},
}}},
DelayTimes: []int{},
SampleOffsets: []go4k.SampleOffset{}}
patterns := [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}
tracks := []go4k.Track{go4k.Track{1, []byte{0}}}
song := go4k.Song{100, patterns, tracks, 0, patch}

View File

@ -14,14 +14,16 @@ var defaultSong = go4k.Song{
},
SongLength: 0,
Patch: go4k.Patch{
go4k.Instrument{NumVoices: 2, Units: []go4k.Unit{
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine}, []int{}},
{"mulp", map[string]int{"stereo": 0}, []int{}},
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine}, []int{}},
{"mulp", map[string]int{"stereo": 0}, []int{}},
{"out", map[string]int{"stereo": 1, "gain": 128}, []int{}},
}},
Instruments: []go4k.Instrument{{NumVoices: 2, Units: []go4k.Unit{
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine}},
{"mulp", map[string]int{"stereo": 0}},
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine}},
{"mulp", map[string]int{"stereo": 0}},
{"out", map[string]int{"stereo": 1, "gain": 128}},
}}},
DelayTimes: []int{},
SampleOffsets: []go4k.SampleOffset{},
},
}