diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f965fc0..dd7c926 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,5 +61,4 @@ jobs: env: CGO_LDFLAGS: ${{ matrix.config.cgo_ldflags }} run: | - cd go4k go test . ./bridge diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f3a377..f67568e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,8 +42,8 @@ endif() # the tests include the entire ASM but we still want to rebuild when they change file(GLOB templates ${PROJECT_SOURCE_DIR}/templates/*.asm) -file(GLOB go4k "${PROJECT_SOURCE_DIR}/go4k/*.go" "${PROJECT_SOURCE_DIR}/go4k/cmd/sointu-cli/*.go") -file(GLOB compilersrc "${PROJECT_SOURCE_DIR}/go4k/compiler/*.go") +file(GLOB go4k "${PROJECT_SOURCE_DIR}/*.go" "${PROJECT_SOURCE_DIR}/cmd/sointu-cli/*.go") +file(GLOB compilersrc "${PROJECT_SOURCE_DIR}/compiler/*.go") if(DEFINED CMAKE_C_SIZEOF_DATA_PTR AND CMAKE_C_SIZEOF_DATA_PTR EQUAL 8) set(arch "amd64") @@ -59,7 +59,7 @@ set(sointuasm sointu.asm) add_custom_command( OUTPUT ${sointuasm} - COMMAND go run ${PROJECT_SOURCE_DIR}/go4k/compiler/generate.go -arch=${arch} ${CMAKE_CURRENT_BINARY_DIR} + COMMAND go run ${PROJECT_SOURCE_DIR}/compiler/generate.go -arch=${arch} ${CMAKE_CURRENT_BINARY_DIR} DEPENDS "${templates}" "${go4k}" "${compilersrc}" ) diff --git a/go4k/audio/convertbuffer.go b/audio/convertbuffer.go similarity index 100% rename from go4k/audio/convertbuffer.go rename to audio/convertbuffer.go diff --git a/go4k/audio/convertbuffer_test.go b/audio/convertbuffer_test.go similarity index 100% rename from go4k/audio/convertbuffer_test.go rename to audio/convertbuffer_test.go diff --git a/go4k/audio/oto/otoplayer.go b/audio/oto/otoplayer.go similarity index 96% rename from go4k/audio/oto/otoplayer.go rename to audio/oto/otoplayer.go index 577ff0a..ac9997b 100644 --- a/go4k/audio/oto/otoplayer.go +++ b/audio/oto/otoplayer.go @@ -3,7 +3,7 @@ package oto import ( "fmt" "github.com/hajimehoshi/oto" - "github.com/vsariola/sointu/go4k/audio" + "github.com/vsariola/sointu/audio" ) // OtoPlayer wraps github.com/hajimehoshi/oto to play sointu-style float32[] audio diff --git a/go4k/audio/player.go b/audio/player.go similarity index 100% rename from go4k/audio/player.go rename to audio/player.go diff --git a/go4k/bridge/asmformat_test.go b/bridge/asmformat_test.go similarity index 92% rename from go4k/bridge/asmformat_test.go rename to bridge/asmformat_test.go index fba232e..cc84f4e 100644 --- a/go4k/bridge/asmformat_test.go +++ b/bridge/asmformat_test.go @@ -13,14 +13,14 @@ import ( "strings" "testing" - "github.com/vsariola/sointu/go4k" - "github.com/vsariola/sointu/go4k/bridge" + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/bridge" "gopkg.in/yaml.v2" ) func TestAllAsmFiles(t *testing.T) { _, myname, _, _ := runtime.Caller(0) - files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "..", "tests", "*.yml")) + files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "tests", "*.yml")) if err != nil { t.Fatalf("cannot glob files in the test directory: %v", err) } @@ -36,7 +36,7 @@ func TestAllAsmFiles(t *testing.T) { if err != nil { t.Fatalf("cannot read the .asm file: %v", filename) } - var song go4k.Song + var song sointu.Song err = yaml.Unmarshal(asmcode, &song) if err != nil { t.Fatalf("could not parse the .yml file: %v", err) @@ -45,12 +45,12 @@ func TestAllAsmFiles(t *testing.T) { if err != nil { t.Fatalf("Compiling patch failed: %v", err) } - buffer, err := go4k.Play(synth, song) + buffer, err := sointu.Play(synth, song) buffer = buffer[:song.TotalRows()*song.SamplesPerRow()*2] // extend to the nominal length always. if err != nil { t.Fatalf("Play failed: %v", err) } - if os.Getenv("GO4K_TEST_SAVE_OUTPUT") == "YES" { + if os.Getenv("SOINTU_TEST_SAVE_OUTPUT") == "YES" { outputpath := path.Join(path.Dir(myname), "actual_output") if _, err := os.Stat(outputpath); os.IsNotExist(err) { os.Mkdir(outputpath, 0755) @@ -83,7 +83,7 @@ func TestAllAsmFiles(t *testing.T) { func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) { _, filename, _, _ := runtime.Caller(0) - expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "tests", "expected_output", rawname)) + expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname)) if err != nil { t.Fatalf("cannot read expected: %v", err) } @@ -105,7 +105,7 @@ func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) { func compareToRawInt16(t *testing.T, buffer []int16, rawname string) { _, filename, _, _ := runtime.Caller(0) - expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "tests", "expected_output", rawname)) + expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname)) if err != nil { t.Fatalf("cannot read expected: %v", err) } diff --git a/go4k/bridge/bridge.go b/bridge/bridge.go similarity index 90% rename from go4k/bridge/bridge.go rename to bridge/bridge.go index 8ef658e..81e4355 100644 --- a/go4k/bridge/bridge.go +++ b/bridge/bridge.go @@ -1,7 +1,7 @@ package bridge -// #cgo CFLAGS: -I"${SRCDIR}/../../build/" -// #cgo LDFLAGS: "${SRCDIR}/../../build/libsointu.a" +// #cgo CFLAGS: -I"${SRCDIR}/../build/" +// #cgo LDFLAGS: "${SRCDIR}/../build/libsointu.a" // #include import "C" import ( @@ -9,11 +9,11 @@ import ( "fmt" "strings" - "github.com/vsariola/sointu/go4k" - "github.com/vsariola/sointu/go4k/compiler" + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/compiler" ) -func Synth(patch go4k.Patch) (*C.Synth, error) { +func Synth(patch sointu.Patch) (*C.Synth, error) { s := new(C.Synth) comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{}) if err != nil { @@ -75,13 +75,13 @@ func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) { return int(samples), int(time), nil } -// Trigger is part of C.Synths' implementation of go4k.Synth interface +// Trigger is part of C.Synths' implementation of sointu.Synth interface func (s *C.Synth) Trigger(voice int, note byte) { s.SynthWrk.Voices[voice] = C.Voice{} s.SynthWrk.Voices[voice].Note = C.int(note) } -// Release is part of C.Synths' implementation of go4k.Synth interface +// Release is part of C.Synths' implementation of sointu.Synth interface func (s *C.Synth) Release(voice int) { s.SynthWrk.Voices[voice].Release = 1 } diff --git a/bridge/bridge_test.go b/bridge/bridge_test.go new file mode 100644 index 0000000..2a027c2 --- /dev/null +++ b/bridge/bridge_test.go @@ -0,0 +1,146 @@ +package bridge_test + +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "path" + "runtime" + "testing" + + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/bridge" +) + +func TestBridge(t *testing.T) { + patch := sointu.Patch{ + Instruments: []sointu.Instrument{ + sointu.Instrument{1, []sointu.Unit{ + sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}}, + sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}}, + sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, + }}}} + + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + synth.Trigger(0, 64) + buffer := make([]float32, 2*su_max_samples) + err = sointu.Render(synth, buffer[:len(buffer)/2]) + if err != nil { + t.Fatalf("first render gave an error") + } + synth.Release(0) + err = sointu.Render(synth, buffer[len(buffer)/2:]) + if err != nil { + t.Fatalf("first render gave an error") + } + _, filename, _, _ := runtime.Caller(0) + expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", "test_render_samples.raw")) + if err != nil { + t.Fatalf("cannot read expected: %v", err) + } + var createdbuf bytes.Buffer + err = binary.Write(&createdbuf, binary.LittleEndian, buffer) + if err != nil { + t.Fatalf("error converting buffer: %v", err) + } + createdb := createdbuf.Bytes() + if len(createdb) != len(expectedb) { + t.Fatalf("buffer length mismatch, got %v, expected %v", len(createdb), len(expectedb)) + } + for i, v := range createdb { + if expectedb[i] != v { + t.Errorf("byte mismatch @ %v, got %v, expected %v", i, v, expectedb[i]) + break + } + } +} + +func TestStackUnderflow(t *testing.T) { + patch := sointu.Patch{ + Instruments: []sointu.Instrument{ + sointu.Instrument{1, []sointu.Unit{ + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + }}}} + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + buffer := make([]float32, 2) + err = sointu.Render(synth, buffer) + if err == nil { + t.Fatalf("rendering should have failed due to stack underflow") + } +} + +func TestStackBalancing(t *testing.T) { + patch := sointu.Patch{ + Instruments: []sointu.Instrument{ + sointu.Instrument{1, []sointu.Unit{ + sointu.Unit{Type: "push", Parameters: map[string]int{}}, + }}}} + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + buffer := make([]float32, 2) + err = sointu.Render(synth, buffer) + if err == nil { + t.Fatalf("rendering should have failed due to unbalanced stack push/pop") + } +} + +func TestStackOverflow(t *testing.T) { + patch := sointu.Patch{ + Instruments: []sointu.Instrument{ + sointu.Instrument{1, []sointu.Unit{ + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + 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{}}, + 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{}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + }}}} + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + buffer := make([]float32, 2) + err = sointu.Render(synth, buffer) + if err == nil { + t.Fatalf("rendering should have failed due to stack overflow, despite balanced push/pops") + } +} + +func TestDivideByZero(t *testing.T) { + patch := sointu.Patch{ + Instruments: []sointu.Instrument{ + sointu.Instrument{1, []sointu.Unit{ + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "invgain", Parameters: map[string]int{"invgain": 0}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + }}}} + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("bridge compile error: %v", err) + } + buffer := make([]float32, 2) + err = sointu.Render(synth, buffer) + if err == nil { + t.Fatalf("rendering should have failed due to divide by zero") + } +} diff --git a/go4k/bridge/init_windows.go b/bridge/init_windows.go similarity index 100% rename from go4k/bridge/init_windows.go rename to bridge/init_windows.go diff --git a/bridge/song_test.go b/bridge/song_test.go new file mode 100644 index 0000000..630edee --- /dev/null +++ b/bridge/song_test.go @@ -0,0 +1,66 @@ +package bridge_test + +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "path" + "runtime" + "testing" + + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/bridge" + // TODO: test the song using a mocks instead +) + +const BPM = 100 +const SAMPLE_RATE = 44100 +const TOTAL_ROWS = 16 +const SAMPLES_PER_ROW = SAMPLE_RATE * 4 * 60 / (BPM * 16) + +const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS + +// const bufsize = su_max_samples * 2 + +func TestPlayer(t *testing.T) { + patch := sointu.Patch{ + Instruments: []sointu.Instrument{sointu.Instrument{1, []sointu.Unit{ + sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, + sointu.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": sointu.Sine, "lfo": 0, "unison": 0}}, + sointu.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}}, + sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, + sointu.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": sointu.Sine, "lfo": 0, "unison": 0}}, + sointu.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}}, + sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, + }}}} + patterns := [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}} + tracks := []sointu.Track{sointu.Track{1, []byte{0}}} + song := sointu.Song{BPM: 100, Patterns: patterns, Tracks: tracks, Patch: patch, Output16Bit: false, Hold: 1} + synth, err := bridge.Synth(patch) + if err != nil { + t.Fatalf("Compiling patch failed: %v", err) + } + buffer, err := sointu.Play(synth, song) + if err != nil { + t.Fatalf("Render failed: %v", err) + } + _, filename, _, _ := runtime.Caller(0) + expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", "test_oscillat_sine.raw")) + if err != nil { + t.Fatalf("cannot read expected: %v", err) + } + var createdbuf bytes.Buffer + err = binary.Write(&createdbuf, binary.LittleEndian, buffer) + if err != nil { + t.Fatalf("error converting buffer: %v", err) + } + createdb := createdbuf.Bytes() + if len(createdb) != len(expectedb) { + t.Fatalf("buffer length mismatch, got %v, expected %v", len(createdb), len(expectedb)) + } + for i, v := range createdb { + if expectedb[i] != v { + t.Fatalf("byte mismatch @ %v, got %v, expected %v", i, v, expectedb[i]) + } + } +} diff --git a/go4k/cmd/sointu-cli/main.go b/cmd/sointu-cli/main.go similarity index 95% rename from go4k/cmd/sointu-cli/main.go rename to cmd/sointu-cli/main.go index 9a5b35b..495c19d 100644 --- a/go4k/cmd/sointu-cli/main.go +++ b/cmd/sointu-cli/main.go @@ -14,10 +14,10 @@ import ( "gopkg.in/yaml.v3" - "github.com/vsariola/sointu/go4k" - "github.com/vsariola/sointu/go4k/audio/oto" - "github.com/vsariola/sointu/go4k/bridge" - "github.com/vsariola/sointu/go4k/compiler" + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/audio/oto" + "github.com/vsariola/sointu/bridge" + "github.com/vsariola/sointu/compiler" ) func main() { @@ -94,7 +94,7 @@ func main() { if err != nil { return fmt.Errorf("Could not read file %v: %v", filename, err) } - var song go4k.Song + var song sointu.Song if errJSON := json.Unmarshal(inputBytes, &song); errJSON != nil { if errYaml := yaml.Unmarshal(inputBytes, &song); errYaml != nil { return fmt.Errorf("The song could not be parsed as .json (%v) or .yml (%v)", errJSON, errYaml) @@ -106,9 +106,9 @@ func main() { if err != nil { return fmt.Errorf("Could not create synth based on the patch: %v", err) } - buffer, err = go4k.Play(synth, song) // render the song to calculate its length + buffer, err = sointu.Play(synth, song) // render the song to calculate its length if err != nil { - return fmt.Errorf("go4k.Play failed: %v", err) + return fmt.Errorf("sointu.Play failed: %v", err) } } if *play { diff --git a/go4k/cmd/sointu-tracker/main.go b/cmd/sointu-tracker/main.go similarity index 83% rename from go4k/cmd/sointu-tracker/main.go rename to cmd/sointu-tracker/main.go index fe6c843..485e7bd 100644 --- a/go4k/cmd/sointu-tracker/main.go +++ b/cmd/sointu-tracker/main.go @@ -4,8 +4,8 @@ import ( "fmt" "gioui.org/app" "gioui.org/unit" - "github.com/vsariola/sointu/go4k/audio/oto" - "github.com/vsariola/sointu/go4k/tracker" + "github.com/vsariola/sointu/audio/oto" + "github.com/vsariola/sointu/tracker" "os" ) diff --git a/go4k/compiler/compiler.go b/compiler/compiler.go similarity index 92% rename from go4k/compiler/compiler.go rename to compiler/compiler.go index ba53b83..2ecc701 100644 --- a/go4k/compiler/compiler.go +++ b/compiler/compiler.go @@ -9,7 +9,7 @@ import ( "text/template" "github.com/Masterminds/sprig" - "github.com/vsariola/sointu/go4k" + "github.com/vsariola/sointu" ) //go:generate go run generate.go @@ -24,7 +24,7 @@ type Compiler struct { // New returns a new compiler using the default .asm templates func New() (*Compiler, error) { _, myname, _, _ := runtime.Caller(0) - templateDir := filepath.Join(path.Dir(myname), "..", "..", "templates") + templateDir := filepath.Join(path.Dir(myname), "..", "templates") compiler, err := NewFromTemplates(templateDir) return compiler, err } @@ -62,7 +62,7 @@ func (com *Compiler) Library() (map[string]string, error) { return map[string]string{"asm": asmCode, "h": header}, nil } -func (com *Compiler) Player(song *go4k.Song, maxSamples int) (map[string]string, error) { +func (com *Compiler) Player(song *sointu.Song, maxSamples int) (map[string]string, error) { features := NecessaryFeaturesFor(song.Patch) encodedPatch, err := Encode(&song.Patch, features) if err != nil { diff --git a/go4k/compiler/encoded_patch.go b/compiler/encoded_patch.go similarity index 92% rename from go4k/compiler/encoded_patch.go rename to compiler/encoded_patch.go index 936c1fc..9920eea 100644 --- a/go4k/compiler/encoded_patch.go +++ b/compiler/encoded_patch.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/vsariola/sointu/go4k" + "github.com/vsariola/sointu" ) type EncodedPatch struct { @@ -22,7 +22,7 @@ type SampleOffset struct { LoopLength uint16 } -func Encode(patch *go4k.Patch, featureSet FeatureSet) (*EncodedPatch, error) { +func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) { var c EncodedPatch sampleOffsetMap := map[SampleOffset]int{} for _, instr := range patch.Instruments { @@ -75,13 +75,13 @@ func Encode(patch *go4k.Patch, featureSet FeatureSet) (*EncodedPatch, error) { return &c, nil } -func EncodeUnit(unit go4k.Unit, featureSet FeatureSet) (byte, []byte, error) { +func EncodeUnit(unit sointu.Unit, featureSet FeatureSet) (byte, []byte, error) { opcode, ok := featureSet.Opcode(unit.Type) if !ok { return 0, nil, fmt.Errorf(`the targeted virtual machine is not configured to support unit type "%v"`, unit.Type) } var values []byte - for _, v := range go4k.UnitTypes[unit.Type] { + for _, v := range sointu.UnitTypes[unit.Type] { if v.CanModulate && v.CanSet { values = append(values, byte(unit.Parameters[v.Name])) } @@ -93,15 +93,15 @@ func EncodeUnit(unit go4k.Unit, featureSet FeatureSet) (byte, []byte, error) { } else if unit.Type == "oscillator" { flags := 0 switch unit.Parameters["type"] { - case go4k.Sine: + case sointu.Sine: flags = 0x40 - case go4k.Trisaw: + case sointu.Trisaw: flags = 0x20 - case go4k.Pulse: + case sointu.Pulse: flags = 0x10 - case go4k.Gate: + case sointu.Gate: flags = 0x04 - case go4k.Sample: + case sointu.Sample: flags = 0x80 } if unit.Parameters["lfo"] == 1 { diff --git a/go4k/compiler/featureset.go b/compiler/featureset.go similarity index 95% rename from go4k/compiler/featureset.go rename to compiler/featureset.go index 696a6c3..523ed50 100644 --- a/go4k/compiler/featureset.go +++ b/compiler/featureset.go @@ -3,7 +3,7 @@ package compiler import ( "sort" - "github.com/vsariola/sointu/go4k" + "github.com/vsariola/sointu" ) // FeatureSet defines what opcodes / parameters are included in the compiled virtual machine @@ -81,12 +81,12 @@ var allInputs map[paramKey]int var allTransformCounts map[string]int func init() { - allInstructions = make([]string, len(go4k.UnitTypes)) + allInstructions = make([]string, len(sointu.UnitTypes)) allOpcodes = map[string]int{} allTransformCounts = map[string]int{} allInputs = map[paramKey]int{} i := 0 - for k, v := range go4k.UnitTypes { + for k, v := range sointu.UnitTypes { inputCount := 0 transformCount := 0 for _, t := range v { @@ -117,7 +117,7 @@ type NecessaryFeatures struct { polyphony bool } -func NecessaryFeaturesFor(patch go4k.Patch) NecessaryFeatures { +func NecessaryFeaturesFor(patch sointu.Patch) NecessaryFeatures { features := NecessaryFeatures{opcodes: map[string]int{}, supportsParamValue: map[paramKey](map[int]bool){}, supportsModulation: map[paramKey]bool{}} for instrNo, instrument := range patch.Instruments { for _, unit := range instrument.Units { @@ -146,7 +146,7 @@ func NecessaryFeaturesFor(patch go4k.Patch) NecessaryFeatures { } targetUnit := patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]] modulatedPortNo := 0 - for _, v := range go4k.UnitTypes[targetUnit.Type] { + for _, v := range sointu.UnitTypes[targetUnit.Type] { if v.CanModulate { if modulatedPortNo == unit.Parameters["port"] { features.supportsModulation[paramKey{targetUnit.Type, v.Name}] = true diff --git a/go4k/compiler/generate.go b/compiler/generate.go similarity index 97% rename from go4k/compiler/generate.go rename to compiler/generate.go index f607614..4c34cc3 100644 --- a/go4k/compiler/generate.go +++ b/compiler/generate.go @@ -13,7 +13,7 @@ import ( "path/filepath" "runtime" - "github.com/vsariola/sointu/go4k/compiler" + "github.com/vsariola/sointu/compiler" ) func main() { diff --git a/go4k/compiler/macros.go b/compiler/macros.go similarity index 97% rename from go4k/compiler/macros.go rename to compiler/macros.go index df0cf46..a7e572e 100644 --- a/go4k/compiler/macros.go +++ b/compiler/macros.go @@ -5,7 +5,7 @@ import ( "math" "strings" - "github.com/vsariola/sointu/go4k" + "github.com/vsariola/sointu" ) type OplistEntry struct { @@ -39,11 +39,11 @@ func NewMacros(c Compiler, f FeatureSet) *Macros { usesFloatConst: map[float32]bool{}, usesIntConst: map[int]bool{}, stackframes: map[string][]string{}, - Sine: go4k.Sine, - Trisaw: go4k.Trisaw, - Pulse: go4k.Pulse, - Gate: go4k.Gate, - Sample: go4k.Sample, + Sine: sointu.Sine, + Trisaw: sointu.Trisaw, + Pulse: sointu.Pulse, + Gate: sointu.Gate, + Sample: sointu.Sample, Compiler: c, FeatureSet: f, } @@ -442,14 +442,14 @@ func (p *Macros) Use(value string, regs ...string) (string, error) { } type PlayerMacros struct { - Song *go4k.Song + Song *sointu.Song VoiceTrackBitmask int MaxSamples int Macros EncodedPatch } -func NewPlayerMacros(c Compiler, f FeatureSet, s *go4k.Song, e *EncodedPatch, maxSamples int) *PlayerMacros { +func NewPlayerMacros(c Compiler, f FeatureSet, s *sointu.Song, e *EncodedPatch, maxSamples int) *PlayerMacros { if maxSamples == 0 { maxSamples = s.SamplesPerRow() * s.TotalRows() } diff --git a/go4k/bridge/bridge_test.go b/go4k/bridge/bridge_test.go deleted file mode 100644 index d7e4456..0000000 --- a/go4k/bridge/bridge_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package bridge_test - -import ( - "bytes" - "encoding/binary" - "io/ioutil" - "path" - "runtime" - "testing" - - "github.com/vsariola/sointu/go4k" - "github.com/vsariola/sointu/go4k/bridge" -) - -func TestBridge(t *testing.T) { - patch := go4k.Patch{ - Instruments: []go4k.Instrument{ - go4k.Instrument{1, []go4k.Unit{ - go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}}, - go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}}, - go4k.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, - }}}} - - synth, err := bridge.Synth(patch) - if err != nil { - t.Fatalf("bridge compile error: %v", err) - } - synth.Trigger(0, 64) - buffer := make([]float32, 2*su_max_samples) - err = go4k.Render(synth, buffer[:len(buffer)/2]) - if err != nil { - t.Fatalf("first render gave an error") - } - synth.Release(0) - err = go4k.Render(synth, buffer[len(buffer)/2:]) - if err != nil { - t.Fatalf("first render gave an error") - } - _, filename, _, _ := runtime.Caller(0) - expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "tests", "expected_output", "test_render_samples.raw")) - if err != nil { - t.Fatalf("cannot read expected: %v", err) - } - var createdbuf bytes.Buffer - err = binary.Write(&createdbuf, binary.LittleEndian, buffer) - if err != nil { - t.Fatalf("error converting buffer: %v", err) - } - createdb := createdbuf.Bytes() - if len(createdb) != len(expectedb) { - t.Fatalf("buffer length mismatch, got %v, expected %v", len(createdb), len(expectedb)) - } - for i, v := range createdb { - if expectedb[i] != v { - t.Errorf("byte mismatch @ %v, got %v, expected %v", i, v, expectedb[i]) - break - } - } -} - -func TestStackUnderflow(t *testing.T) { - patch := go4k.Patch{ - Instruments: []go4k.Instrument{ - go4k.Instrument{1, []go4k.Unit{ - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - }}}} - synth, err := bridge.Synth(patch) - if err != nil { - t.Fatalf("bridge compile error: %v", err) - } - buffer := make([]float32, 2) - err = go4k.Render(synth, buffer) - if err == nil { - t.Fatalf("rendering should have failed due to stack underflow") - } -} - -func TestStackBalancing(t *testing.T) { - patch := go4k.Patch{ - Instruments: []go4k.Instrument{ - go4k.Instrument{1, []go4k.Unit{ - go4k.Unit{Type: "push", Parameters: map[string]int{}}, - }}}} - synth, err := bridge.Synth(patch) - if err != nil { - t.Fatalf("bridge compile error: %v", err) - } - buffer := make([]float32, 2) - err = go4k.Render(synth, buffer) - if err == nil { - t.Fatalf("rendering should have failed due to unbalanced stack push/pop") - } -} - -func TestStackOverflow(t *testing.T) { - patch := go4k.Patch{ - Instruments: []go4k.Instrument{ - go4k.Instrument{1, []go4k.Unit{ - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - }}}} - synth, err := bridge.Synth(patch) - if err != nil { - t.Fatalf("bridge compile error: %v", err) - } - buffer := make([]float32, 2) - err = go4k.Render(synth, buffer) - if err == nil { - t.Fatalf("rendering should have failed due to stack overflow, despite balanced push/pops") - } -} - -func TestDivideByZero(t *testing.T) { - patch := go4k.Patch{ - Instruments: []go4k.Instrument{ - go4k.Instrument{1, []go4k.Unit{ - go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - go4k.Unit{Type: "invgain", Parameters: map[string]int{"invgain": 0}}, - go4k.Unit{Type: "pop", Parameters: map[string]int{}}, - }}}} - synth, err := bridge.Synth(patch) - if err != nil { - t.Fatalf("bridge compile error: %v", err) - } - buffer := make([]float32, 2) - err = go4k.Render(synth, buffer) - if err == nil { - t.Fatalf("rendering should have failed due to divide by zero") - } -} diff --git a/go4k/bridge/song_test.go b/go4k/bridge/song_test.go deleted file mode 100644 index f26262c..0000000 --- a/go4k/bridge/song_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package bridge_test - -import ( - "bytes" - "encoding/binary" - "io/ioutil" - "path" - "runtime" - "testing" - - "github.com/vsariola/sointu/go4k" - "github.com/vsariola/sointu/go4k/bridge" - // TODO: test the song using a mocks instead -) - -const BPM = 100 -const SAMPLE_RATE = 44100 -const TOTAL_ROWS = 16 -const SAMPLES_PER_ROW = SAMPLE_RATE * 4 * 60 / (BPM * 16) - -const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS - -// const bufsize = su_max_samples * 2 - -func TestPlayer(t *testing.T) { - patch := go4k.Patch{ - Instruments: []go4k.Instrument{go4k.Instrument{1, []go4k.Unit{ - go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, - go4k.Unit{Type: "oscillator", Parameters: 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{Type: "mulp", Parameters: map[string]int{"stereo": 0}}, - go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, - go4k.Unit{Type: "oscillator", Parameters: 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{Type: "mulp", Parameters: map[string]int{"stereo": 0}}, - go4k.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, - }}}} - 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{BPM: 100, Patterns: patterns, Tracks: tracks, Patch: patch, Output16Bit: false, Hold: 1} - synth, err := bridge.Synth(patch) - if err != nil { - t.Fatalf("Compiling patch failed: %v", err) - } - buffer, err := go4k.Play(synth, song) - if err != nil { - t.Fatalf("Render failed: %v", err) - } - _, filename, _, _ := runtime.Caller(0) - expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "tests", "expected_output", "test_oscillat_sine.raw")) - if err != nil { - t.Fatalf("cannot read expected: %v", err) - } - var createdbuf bytes.Buffer - err = binary.Write(&createdbuf, binary.LittleEndian, buffer) - if err != nil { - t.Fatalf("error converting buffer: %v", err) - } - createdb := createdbuf.Bytes() - if len(createdb) != len(expectedb) { - t.Fatalf("buffer length mismatch, got %v, expected %v", len(createdb), len(expectedb)) - } - for i, v := range createdb { - if expectedb[i] != v { - t.Fatalf("byte mismatch @ %v, got %v, expected %v", i, v, expectedb[i]) - } - } -} diff --git a/go4k/go4k.go b/sointu.go similarity index 98% rename from go4k/go4k.go rename to sointu.go index 638873e..92fef70 100644 --- a/go4k/go4k.go +++ b/sointu.go @@ -1,4 +1,4 @@ -package go4k +package sointu import ( "errors" @@ -68,10 +68,10 @@ type Synth interface { func Render(synth Synth, buffer []float32) error { s, _, err := synth.Render(buffer, math.MaxInt32) if err != nil { - return fmt.Errorf("go4k.Render failed: %v", err) + return fmt.Errorf("sointu.Render failed: %v", err) } if s != len(buffer)/2 { - return errors.New("in go4k.Render, synth.Render should have filled the whole buffer but did not") + return errors.New("in sointu.Render, synth.Render should have filled the whole buffer but did not") } return nil } diff --git a/templates/sources.asm b/templates/sources.asm index e2af319..323c6e5 100644 --- a/templates/sources.asm +++ b/templates/sources.asm @@ -279,9 +279,9 @@ su_oscillat_sine_do: pop {{.AX}} and al, 0xf ; ax=int(16*p) & 15, stack: 1 bt word [{{.VAL}}-4],ax ; if bit ax of the gate word is set - jc go4kVCO_gate_bit ; goto gate_bit + jc su_oscillat_gate_bit ; goto gate_bit fsub st0, st0 ; stack: 0 -go4kVCO_gate_bit: ; stack: 0/1, let's call it x +su_oscillat_gate_bit: ; stack: 0/1, let's call it x fld dword [{{.WRK}}+16] ; g x, g is gatestate, x is the input to this filter 0/1 fsub st1 ; g-x x {{- .Float 0.99609375 | .Prepare | indent 4}} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8476446..468f1f8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,11 +1,11 @@ -function(regression_test testname) - +function(regression_test testname) + if(${ARGC} LESS 4) set(source ${testname}.yml) set(asmfile ${testname}.asm) set (headerfile ${CMAKE_CURRENT_BINARY_DIR}/${testname}.h) - add_custom_command( + add_custom_command( OUTPUT ${asmfile} COMMAND ${sointuexe} -a -c -w -arch=${arch} -d ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${source} DEPENDS ${source} ${sointuexe} @@ -16,7 +16,7 @@ function(regression_test testname) else() set(source ${ARGV3}) add_executable(${testname} ${source} test_renderer.c) - endif() + endif() add_test(${testname} ${testname}) target_link_libraries(${testname} ${HEADERLIB}) @@ -31,7 +31,7 @@ function(regression_test testname) add_dependencies(${testname} ${testname}_rawcopy) target_include_directories(${testname} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) - target_compile_definitions(${testname} PUBLIC TEST_NAME="${testname}") + target_compile_definitions(${testname} PUBLIC TEST_NAME="${testname}") if(ARGC GREATER 1) if (ARGV1) @@ -49,12 +49,12 @@ function(regression_test testname) endfunction(regression_test) -# Build sointu-cli only once because go run has everytime quite a bit of delay when +# Build sointu-cli only once because go run has everytime quite a bit of delay when # starting -add_custom_command( +add_custom_command( OUTPUT ${sointuexe} - COMMAND go build -o ${sointuexe} ${PROJECT_SOURCE_DIR}/go4k/cmd/sointu-cli/main.go - DEPENDS "${templates}" "${go4k}" "${compilersrc}" ${STATICLIB} + COMMAND go build -o ${sointuexe} ${PROJECT_SOURCE_DIR}/cmd/sointu-cli/main.go + DEPENDS "${templates}" "${sointu}" "${compilersrc}" ${STATICLIB} ) regression_test(test_envelope "" ENVELOPE) diff --git a/go4k/tracker/defaultsong.go b/tracker/defaultsong.go similarity index 78% rename from go4k/tracker/defaultsong.go rename to tracker/defaultsong.go index 78065b7..e381617 100644 --- a/go4k/tracker/defaultsong.go +++ b/tracker/defaultsong.go @@ -1,24 +1,24 @@ package tracker -import "github.com/vsariola/sointu/go4k" +import "github.com/vsariola/sointu" -var defaultSong = go4k.Song{ +var defaultSong = sointu.Song{ BPM: 100, Patterns: [][]byte{ {64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}, {0, 0, 64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0}, }, - Tracks: []go4k.Track{ + Tracks: []sointu.Track{ {NumVoices: 1, Sequence: []byte{0}}, {NumVoices: 1, Sequence: []byte{1}}, }, - Patch: go4k.Patch{ - Instruments: []go4k.Instrument{{NumVoices: 2, Units: []go4k.Unit{ + Patch: sointu.Patch{ + Instruments: []sointu.Instrument{{NumVoices: 2, Units: []sointu.Unit{ {Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, - {Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine}}, + {Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": sointu.Sine}}, {Type: "mulp", Parameters: map[string]int{"stereo": 0}}, {Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, - {Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine}}, + {Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": sointu.Sine}}, {Type: "mulp", Parameters: map[string]int{"stereo": 0}}, {Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, }}}}, diff --git a/go4k/tracker/keyevent.go b/tracker/keyevent.go similarity index 100% rename from go4k/tracker/keyevent.go rename to tracker/keyevent.go diff --git a/go4k/tracker/label.go b/tracker/label.go similarity index 100% rename from go4k/tracker/label.go rename to tracker/label.go diff --git a/go4k/tracker/layout.go b/tracker/layout.go similarity index 100% rename from go4k/tracker/layout.go rename to tracker/layout.go diff --git a/go4k/tracker/music.go b/tracker/music.go similarity index 100% rename from go4k/tracker/music.go rename to tracker/music.go diff --git a/go4k/tracker/panels.go b/tracker/panels.go similarity index 100% rename from go4k/tracker/panels.go rename to tracker/panels.go diff --git a/go4k/tracker/run.go b/tracker/run.go similarity index 100% rename from go4k/tracker/run.go rename to tracker/run.go diff --git a/go4k/tracker/sequencer.go b/tracker/sequencer.go similarity index 100% rename from go4k/tracker/sequencer.go rename to tracker/sequencer.go diff --git a/go4k/tracker/theme.go b/tracker/theme.go similarity index 100% rename from go4k/tracker/theme.go rename to tracker/theme.go diff --git a/go4k/tracker/track.go b/tracker/track.go similarity index 100% rename from go4k/tracker/track.go rename to tracker/track.go diff --git a/go4k/tracker/tracker.go b/tracker/tracker.go similarity index 85% rename from go4k/tracker/tracker.go rename to tracker/tracker.go index e025081..9e6a05a 100644 --- a/go4k/tracker/tracker.go +++ b/tracker/tracker.go @@ -3,14 +3,14 @@ package tracker import ( "fmt" "gioui.org/widget" - "github.com/vsariola/sointu/go4k" - "github.com/vsariola/sointu/go4k/audio" - "github.com/vsariola/sointu/go4k/bridge" + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/audio" + "github.com/vsariola/sointu/bridge" ) type Tracker struct { QuitButton *widget.Clickable - song go4k.Song + song sointu.Song CursorRow int CursorColumn int DisplayPattern int @@ -24,12 +24,12 @@ type Tracker struct { rowJump chan int patternJump chan int player audio.Player - synth go4k.Synth + synth sointu.Synth playBuffer []float32 closer chan struct{} } -func (t *Tracker) LoadSong(song go4k.Song) error { +func (t *Tracker) LoadSong(song sointu.Song) error { if err := song.Validate(); err != nil { return fmt.Errorf("invalid song: %w", err) }