mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
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:
parent
f076409eb1
commit
95c8c9c2b7
@ -102,26 +102,30 @@ func FindSuperIntArray(arrays [][]int) ([]int, []int) {
|
|||||||
// as possible. Especially: if two delay units use exactly the same
|
// as possible. Especially: if two delay units use exactly the same
|
||||||
// delay times, they appear in the table only once.
|
// delay times, they appear in the table only once.
|
||||||
func ConstructDelayTimeTable(patch Patch) ([]int, [][]int) {
|
func ConstructDelayTimeTable(patch Patch) ([]int, [][]int) {
|
||||||
ind := make([][]int, len(patch))
|
ind := make([][]int, len(patch.Instruments))
|
||||||
var subarrays [][]int
|
var subarrays [][]int
|
||||||
// flatten the delay times into one array of arrays
|
// flatten the delay times into one array of arrays
|
||||||
// saving the indices where they were placed
|
// 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))
|
ind[i] = make([]int, len(instr.Units))
|
||||||
for j, unit := range instr.Units {
|
for j, unit := range instr.Units {
|
||||||
// only include delay times for delays. Only delays
|
// only include delay times for delays. Only delays
|
||||||
// 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)
|
||||||
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)
|
delayTable, indices := FindSuperIntArray(subarrays)
|
||||||
// cancel the flattening, so unitindices can be used to
|
// cancel the flattening, so unitindices can be used to
|
||||||
// to find the index of each delay in the delay table
|
// to find the index of each delay in the delay table
|
||||||
unitindices := make([][]int, len(patch))
|
unitindices := make([][]int, len(patch.Instruments))
|
||||||
for i, instr := range patch {
|
for i, instr := range patch.Instruments {
|
||||||
unitindices[i] = make([]int, len(instr.Units))
|
unitindices[i] = make([]int, len(instr.Units))
|
||||||
for j, unit := range instr.Units {
|
for j, unit := range instr.Units {
|
||||||
if unit.Type == "delay" {
|
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
|
// table by instrument i / unit j (units other than sample oscillators
|
||||||
// have the value 0)
|
// have the value 0)
|
||||||
func ConstructSampleOffsetTable(patch Patch) ([]SampleOffset, [][]int) {
|
func ConstructSampleOffsetTable(patch Patch) ([]SampleOffset, [][]int) {
|
||||||
unitindices := make([][]int, len(patch))
|
unitindices := make([][]int, len(patch.Instruments))
|
||||||
var offsetTable []SampleOffset
|
var offsetTable []SampleOffset
|
||||||
offsetMap := map[SampleOffset]int{}
|
offsetMap := map[SampleOffset]int{}
|
||||||
for i, instr := range patch {
|
for i, instr := range patch.Instruments {
|
||||||
unitindices[i] = make([]int, len(instr.Units))
|
unitindices[i] = make([]int, len(instr.Units))
|
||||||
for j, unit := range instr.Units {
|
for j, unit := range instr.Units {
|
||||||
if unit.Type == "oscillator" && unit.Parameters["type"] == Sample {
|
if unit.Type == "oscillator" && unit.Parameters["type"] == Sample {
|
||||||
|
@ -16,8 +16,6 @@ func DeserializeAsm(asmcode string) (*Song, error) {
|
|||||||
tracks := make([]Track, 0)
|
tracks := make([]Track, 0)
|
||||||
var patch Patch
|
var patch Patch
|
||||||
var instr Instrument
|
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"
|
paramReg, err := regexp.Compile(`([a-zA-Z]\w*)\s*\(\s*([0-9]+)\s*\)`) // matches FOO(42), groups "FOO" and "42"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -111,7 +109,7 @@ func DeserializeAsm(asmcode string) (*Song, error) {
|
|||||||
instr = Instrument{NumVoices: ints[0], Units: []Unit{}}
|
instr = Instrument{NumVoices: ints[0], Units: []Unit{}}
|
||||||
inInstrument = true
|
inInstrument = true
|
||||||
case "END_INSTRUMENT":
|
case "END_INSTRUMENT":
|
||||||
patch = append(patch, instr)
|
patch.Instruments = append(patch.Instruments, instr)
|
||||||
inInstrument = false
|
inInstrument = false
|
||||||
case "DELTIME":
|
case "DELTIME":
|
||||||
ints, err := parseNumbers(rest)
|
ints, err := parseNumbers(rest)
|
||||||
@ -119,14 +117,17 @@ func DeserializeAsm(asmcode string) (*Song, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, v := range ints {
|
for _, v := range ints {
|
||||||
delayTimes = append(delayTimes, v)
|
patch.DelayTimes = append(patch.DelayTimes, v)
|
||||||
}
|
}
|
||||||
case "SAMPLE_OFFSET":
|
case "SAMPLE_OFFSET":
|
||||||
ints, err := parseNumbers(rest)
|
ints, err := parseNumbers(rest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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_") {
|
if inInstrument && strings.HasPrefix(word, "SU_") {
|
||||||
unittype := strings.ToLower(word[3:])
|
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}
|
s := Song{BPM: bpm, Patterns: patterns, Tracks: tracks, Patch: patch, SongLength: -1}
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
@ -269,15 +250,13 @@ func SerializeAsm(song *Song) (string, error) {
|
|||||||
}
|
}
|
||||||
indentation--
|
indentation--
|
||||||
}
|
}
|
||||||
delayTable, delayIndices := ConstructDelayTimeTable(song.Patch)
|
|
||||||
sampleTable, sampleIndices := ConstructSampleOffsetTable(song.Patch)
|
|
||||||
// The actual printing starts here
|
// The actual printing starts here
|
||||||
println("%%define BPM %d", song.BPM)
|
println("%%define BPM %d", song.BPM)
|
||||||
// delay modulation is pretty much the only %define that the asm preprocessor cannot figure out
|
// 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
|
// 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.
|
// time being, we need to figure during export if INCLUDE_DELAY_MODULATION needs to be defined.
|
||||||
delaymod := false
|
delaymod := false
|
||||||
for i, instrument := range song.Patch {
|
for i, instrument := range song.Patch.Instruments {
|
||||||
for j, unit := range instrument.Units {
|
for j, unit := range instrument.Units {
|
||||||
if unit.Type == "send" {
|
if unit.Type == "send" {
|
||||||
targetInstrument := i
|
targetInstrument := i
|
||||||
@ -288,10 +267,10 @@ func SerializeAsm(song *Song) (string, error) {
|
|||||||
}
|
}
|
||||||
targetInstrument = v
|
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"])
|
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
|
delaymod = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -330,22 +309,12 @@ func SerializeAsm(song *Song) (string, error) {
|
|||||||
println("END_TRACKS\n")
|
println("END_TRACKS\n")
|
||||||
println("BEGIN_PATCH")
|
println("BEGIN_PATCH")
|
||||||
indentation++
|
indentation++
|
||||||
for i, instrument := range song.Patch {
|
for _, instrument := range song.Patch.Instruments {
|
||||||
var instrTable [][]string
|
var instrTable [][]string
|
||||||
for j, unit := range instrument.Units {
|
for _, unit := range instrument.Units {
|
||||||
row := []string{fmt.Sprintf("SU_%v", strings.ToUpper(unit.Type))}
|
row := []string{fmt.Sprintf("SU_%v", strings.ToUpper(unit.Type))}
|
||||||
for _, parname := range paramorder[unit.Type] {
|
for _, parname := range paramorder[unit.Type] {
|
||||||
if unit.Type == "oscillator" && unit.Parameters["type"] == Sample && parname == "color" {
|
if unit.Type == "oscillator" && parname == "type" {
|
||||||
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" {
|
|
||||||
switch unit.Parameters["type"] {
|
switch unit.Parameters["type"] {
|
||||||
case Sine:
|
case Sine:
|
||||||
row = append(row, "TYPE(SINE)")
|
row = append(row, "TYPE(SINE)")
|
||||||
@ -372,9 +341,9 @@ func SerializeAsm(song *Song) (string, error) {
|
|||||||
}
|
}
|
||||||
indentation--
|
indentation--
|
||||||
println("END_PATCH\n")
|
println("END_PATCH\n")
|
||||||
if len(delayTable) > 0 {
|
if len(song.Patch.DelayTimes) > 0 {
|
||||||
var delStrTable [][]string
|
var delStrTable [][]string
|
||||||
for _, v := range delayTable {
|
for _, v := range song.Patch.DelayTimes {
|
||||||
row := []string{"DELTIME", strconv.Itoa(int(v))}
|
row := []string{"DELTIME", strconv.Itoa(int(v))}
|
||||||
delStrTable = append(delStrTable, row)
|
delStrTable = append(delStrTable, row)
|
||||||
}
|
}
|
||||||
@ -382,9 +351,9 @@ func SerializeAsm(song *Song) (string, error) {
|
|||||||
printTable(align(delStrTable, "lr"))
|
printTable(align(delStrTable, "lr"))
|
||||||
println("END_DELTIMES\n")
|
println("END_DELTIMES\n")
|
||||||
}
|
}
|
||||||
if len(sampleTable) > 0 {
|
if len(song.Patch.SampleOffsets) > 0 {
|
||||||
var samStrTable [][]string
|
var samStrTable [][]string
|
||||||
for _, v := range sampleTable {
|
for _, v := range song.Patch.SampleOffsets {
|
||||||
samStrTable = append(samStrTable, []string{
|
samStrTable = append(samStrTable, []string{
|
||||||
"SAMPLE_OFFSET",
|
"SAMPLE_OFFSET",
|
||||||
fmt.Sprintf("START(%d)", v.Start),
|
fmt.Sprintf("START(%d)", v.Start),
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -101,15 +102,18 @@ func TestSerializingAllAsmFiles(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not serialize asm file: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("could not parse the serialized asm code: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("Compiling patch failed: %v", err)
|
t.Fatalf("Compiling patch failed: %v", err)
|
||||||
}
|
}
|
||||||
buffer, err := go4k.Play(synth, *song)
|
buffer, err := go4k.Play(synth, *song2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Play failed: %v", err)
|
t.Fatalf("Play failed: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ var opcodeTable = map[string]opTableEntry{
|
|||||||
"filter": opTableEntry{C.su_filter_id, []string{"frequency", "resonance"}},
|
"filter": opTableEntry{C.su_filter_id, []string{"frequency", "resonance"}},
|
||||||
"clip": opTableEntry{C.su_clip_id, []string{}},
|
"clip": opTableEntry{C.su_clip_id, []string{}},
|
||||||
"pan": opTableEntry{C.su_pan_id, []string{"panning"}},
|
"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"}},
|
"compressor": opTableEntry{C.su_compres_id, []string{"attack", "release", "invgain", "threshold", "ratio"}},
|
||||||
"speed": opTableEntry{C.su_speed_id, []string{}},
|
"speed": opTableEntry{C.su_speed_id, []string{}},
|
||||||
"out": opTableEntry{C.su_out_id, []string{"gain"}},
|
"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) {
|
func Synth(patch go4k.Patch) (*C.Synth, error) {
|
||||||
s := new(C.Synth)
|
s := new(C.Synth)
|
||||||
sampleno := 0
|
|
||||||
totalVoices := 0
|
totalVoices := 0
|
||||||
commands := make([]byte, 0)
|
commands := make([]byte, 0)
|
||||||
values := make([]byte, 0)
|
values := make([]byte, 0)
|
||||||
polyphonyBitmask := 0
|
polyphonyBitmask := 0
|
||||||
delayTable, delayIndices := go4k.ConstructDelayTimeTable(patch)
|
for insid, instr := range patch.Instruments {
|
||||||
for i, v := range delayTable {
|
|
||||||
s.DelayTimes[i] = C.ushort(v)
|
|
||||||
}
|
|
||||||
for insid, instr := range patch {
|
|
||||||
if len(instr.Units) > 63 {
|
if len(instr.Units) > 63 {
|
||||||
return nil, errors.New("An instrument can have a maximum of 63 units")
|
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))
|
commands = append(commands, byte(opCode))
|
||||||
for _, paramname := range val.parameterList {
|
for _, paramname := range val.parameterList {
|
||||||
if unit.Type == "delay" && paramname == "delaycount" {
|
if unit.Type == "delay" && paramname == "count" {
|
||||||
if unit.Parameters["stereo"] == 1 && len(unit.DelayTimes)%2 != 0 {
|
count := unit.Parameters["count"]*2 - 1
|
||||||
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.Parameters["notetracking"] == 1 {
|
if unit.Parameters["notetracking"] == 1 {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
values = append(values, byte(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 {
|
} else if pval, ok := unit.Parameters[paramname]; ok {
|
||||||
values = append(values, byte(pval))
|
values = append(values, byte(pval))
|
||||||
} else {
|
} else {
|
||||||
@ -204,6 +185,14 @@ func Synth(patch go4k.Patch) (*C.Synth, error) {
|
|||||||
for i := range values {
|
for i := range values {
|
||||||
s.Values[i] = (C.uchar)(values[i])
|
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.NumVoices = C.uint(totalVoices)
|
||||||
s.Polyphony = C.uint(polyphonyBitmask)
|
s.Polyphony = C.uint(polyphonyBitmask)
|
||||||
s.RandSeed = 1
|
s.RandSeed = 1
|
||||||
|
@ -22,12 +22,16 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
|
|||||||
// const bufsize = su_max_samples * 2
|
// const bufsize = su_max_samples * 2
|
||||||
|
|
||||||
func TestBridge(t *testing.T) {
|
func TestBridge(t *testing.T) {
|
||||||
patch := []go4k.Instrument{
|
patch := go4k.Patch{
|
||||||
go4k.Instrument{1, []go4k.Unit{
|
Instruments: []go4k.Instrument{
|
||||||
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}, []int{}},
|
go4k.Instrument{1, []go4k.Unit{
|
||||||
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}, []int{}},
|
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
|
||||||
go4k.Unit{"out", map[string]int{"stereo": 1, "gain": 128}, []int{}},
|
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)
|
synth, err := bridge.Synth(patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bridge compile error: %v", err)
|
t.Fatalf("bridge compile error: %v", err)
|
||||||
|
29
go4k/go4k.go
29
go4k/go4k.go
@ -9,7 +9,6 @@ import (
|
|||||||
type Unit struct {
|
type Unit struct {
|
||||||
Type string
|
Type string
|
||||||
Parameters map[string]int
|
Parameters map[string]int
|
||||||
DelayTimes []int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -26,12 +25,22 @@ type Instrument struct {
|
|||||||
Units []Unit
|
Units []Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SampleOffset struct {
|
||||||
|
Start int
|
||||||
|
LoopStart int
|
||||||
|
LoopLength int
|
||||||
|
}
|
||||||
|
|
||||||
// Patch is simply a list of instruments used in a song
|
// 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 {
|
func (p Patch) TotalVoices() int {
|
||||||
ret := 0
|
ret := 0
|
||||||
for _, i := range p {
|
for _, i := range p.Instruments {
|
||||||
ret += i.NumVoices
|
ret += i.NumVoices
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
@ -41,7 +50,7 @@ func (patch Patch) InstrumentForVoice(voice int) (int, error) {
|
|||||||
if voice < 0 {
|
if voice < 0 {
|
||||||
return 0, errors.New("voice cannot be negative")
|
return 0, errors.New("voice cannot be negative")
|
||||||
}
|
}
|
||||||
for i, instr := range patch {
|
for i, instr := range patch.Instruments {
|
||||||
if voice < instr.NumVoices {
|
if voice < instr.NumVoices {
|
||||||
return i, nil
|
return i, nil
|
||||||
} else {
|
} else {
|
||||||
@ -70,12 +79,6 @@ func Render(synth Synth, buffer []float32) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type SampleOffset struct {
|
|
||||||
Start int
|
|
||||||
LoopStart int
|
|
||||||
LoopLength int
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnitParameter documents one parameter that an unit takes
|
// UnitParameter documents one parameter that an unit takes
|
||||||
type UnitParameter struct {
|
type UnitParameter struct {
|
||||||
Name string // thould be found with this name in the Unit.Parameters map
|
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: "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: 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",
|
Name: "compressor",
|
||||||
@ -245,9 +249,6 @@ var UnitTypes = []UnitType{
|
|||||||
{Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false},
|
{Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false},
|
||||||
{Name: "lfo", MinValue: 0, MaxValue: 1, 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: "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",
|
Name: "loadval",
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/vsariola/sointu/go4k"
|
"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{
|
var testSong = go4k.Song{
|
||||||
BPM: 100,
|
BPM: 100,
|
||||||
@ -18,15 +18,17 @@ var testSong = go4k.Song{
|
|||||||
},
|
},
|
||||||
SongLength: 0,
|
SongLength: 0,
|
||||||
Patch: go4k.Patch{
|
Patch: go4k.Patch{
|
||||||
go4k.Instrument{NumVoices: 1, Units: []go4k.Unit{
|
Instruments: []go4k.Instrument{{NumVoices: 1, Units: []go4k.Unit{
|
||||||
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
{"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}, []int{}},
|
{"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}, []int{}},
|
{"mulp", map[string]int{"stereo": 0}},
|
||||||
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
{"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}, []int{}},
|
{"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}, []int{}},
|
{"mulp", map[string]int{"stereo": 0}},
|
||||||
{"out", map[string]int{"stereo": 1, "gain": 128}, []int{}},
|
{"out", map[string]int{"stereo": 1, "gain": 128}},
|
||||||
}},
|
}}},
|
||||||
|
DelayTimes: []int{},
|
||||||
|
SampleOffsets: []go4k.SampleOffset{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,15 +23,18 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
|
|||||||
// const bufsize = su_max_samples * 2
|
// const bufsize = su_max_samples * 2
|
||||||
|
|
||||||
func TestPlayer(t *testing.T) {
|
func TestPlayer(t *testing.T) {
|
||||||
patch := []go4k.Instrument{go4k.Instrument{1, []go4k.Unit{
|
patch := go4k.Patch{
|
||||||
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
Instruments: []go4k.Instrument{go4k.Instrument{1, []go4k.Unit{
|
||||||
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{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||||
go4k.Unit{"mulp", map[string]int{"stereo": 0}, []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}},
|
||||||
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
go4k.Unit{"mulp", map[string]int{"stereo": 0}},
|
||||||
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{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||||
go4k.Unit{"mulp", map[string]int{"stereo": 0}, []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}},
|
||||||
go4k.Unit{"out", map[string]int{"stereo": 1, "gain": 128}, []int{}},
|
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}}
|
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}}}
|
tracks := []go4k.Track{go4k.Track{1, []byte{0}}}
|
||||||
song := go4k.Song{100, patterns, tracks, 0, patch}
|
song := go4k.Song{100, patterns, tracks, 0, patch}
|
||||||
|
@ -14,14 +14,16 @@ var defaultSong = go4k.Song{
|
|||||||
},
|
},
|
||||||
SongLength: 0,
|
SongLength: 0,
|
||||||
Patch: go4k.Patch{
|
Patch: go4k.Patch{
|
||||||
go4k.Instrument{NumVoices: 2, Units: []go4k.Unit{
|
Instruments: []go4k.Instrument{{NumVoices: 2, Units: []go4k.Unit{
|
||||||
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
{"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}, []int{}},
|
{"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}, []int{}},
|
{"mulp", map[string]int{"stereo": 0}},
|
||||||
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}, []int{}},
|
{"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}, []int{}},
|
{"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}, []int{}},
|
{"mulp", map[string]int{"stereo": 0}},
|
||||||
{"out", map[string]int{"stereo": 1, "gain": 128}, []int{}},
|
{"out", map[string]int{"stereo": 1, "gain": 128}},
|
||||||
}},
|
}}},
|
||||||
|
DelayTimes: []int{},
|
||||||
|
SampleOffsets: []go4k.SampleOffset{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user