refactor(go): Move everything from go4k to root package sointu

This commit is contained in:
Veikko Sariola 2020-12-16 21:35:53 +02:00
parent d0bd877b3f
commit 224b8dcb70
34 changed files with 293 additions and 294 deletions

View File

@ -61,5 +61,4 @@ jobs:
env: env:
CGO_LDFLAGS: ${{ matrix.config.cgo_ldflags }} CGO_LDFLAGS: ${{ matrix.config.cgo_ldflags }}
run: | run: |
cd go4k
go test . ./bridge go test . ./bridge

View File

@ -42,8 +42,8 @@ endif()
# the tests include the entire ASM but we still want to rebuild when they change # 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 templates ${PROJECT_SOURCE_DIR}/templates/*.asm)
file(GLOB go4k "${PROJECT_SOURCE_DIR}/go4k/*.go" "${PROJECT_SOURCE_DIR}/go4k/cmd/sointu-cli/*.go") file(GLOB go4k "${PROJECT_SOURCE_DIR}/*.go" "${PROJECT_SOURCE_DIR}/cmd/sointu-cli/*.go")
file(GLOB compilersrc "${PROJECT_SOURCE_DIR}/go4k/compiler/*.go") file(GLOB compilersrc "${PROJECT_SOURCE_DIR}/compiler/*.go")
if(DEFINED CMAKE_C_SIZEOF_DATA_PTR AND CMAKE_C_SIZEOF_DATA_PTR EQUAL 8) if(DEFINED CMAKE_C_SIZEOF_DATA_PTR AND CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
set(arch "amd64") set(arch "amd64")
@ -59,7 +59,7 @@ set(sointuasm sointu.asm)
add_custom_command( add_custom_command(
OUTPUT ${sointuasm} 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}" DEPENDS "${templates}" "${go4k}" "${compilersrc}"
) )

View File

@ -3,7 +3,7 @@ package oto
import ( import (
"fmt" "fmt"
"github.com/hajimehoshi/oto" "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 // OtoPlayer wraps github.com/hajimehoshi/oto to play sointu-style float32[] audio

View File

@ -13,14 +13,14 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu"
"github.com/vsariola/sointu/go4k/bridge" "github.com/vsariola/sointu/bridge"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
func TestAllAsmFiles(t *testing.T) { func TestAllAsmFiles(t *testing.T) {
_, myname, _, _ := runtime.Caller(0) _, 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 { if err != nil {
t.Fatalf("cannot glob files in the test directory: %v", err) t.Fatalf("cannot glob files in the test directory: %v", err)
} }
@ -36,7 +36,7 @@ func TestAllAsmFiles(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("cannot read the .asm file: %v", filename) t.Fatalf("cannot read the .asm file: %v", filename)
} }
var song go4k.Song var song sointu.Song
err = yaml.Unmarshal(asmcode, &song) err = yaml.Unmarshal(asmcode, &song)
if err != nil { if err != nil {
t.Fatalf("could not parse the .yml file: %v", err) t.Fatalf("could not parse the .yml file: %v", err)
@ -45,12 +45,12 @@ func TestAllAsmFiles(t *testing.T) {
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 := sointu.Play(synth, song)
buffer = buffer[:song.TotalRows()*song.SamplesPerRow()*2] // extend to the nominal length always. buffer = buffer[:song.TotalRows()*song.SamplesPerRow()*2] // extend to the nominal length always.
if err != nil { if err != nil {
t.Fatalf("Play failed: %v", err) 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") outputpath := path.Join(path.Dir(myname), "actual_output")
if _, err := os.Stat(outputpath); os.IsNotExist(err) { if _, err := os.Stat(outputpath); os.IsNotExist(err) {
os.Mkdir(outputpath, 0755) os.Mkdir(outputpath, 0755)
@ -83,7 +83,7 @@ func TestAllAsmFiles(t *testing.T) {
func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) { func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
_, filename, _, _ := runtime.Caller(0) _, 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 { if err != nil {
t.Fatalf("cannot read expected: %v", err) 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) { func compareToRawInt16(t *testing.T, buffer []int16, rawname string) {
_, filename, _, _ := runtime.Caller(0) _, 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 { if err != nil {
t.Fatalf("cannot read expected: %v", err) t.Fatalf("cannot read expected: %v", err)
} }

View File

@ -1,7 +1,7 @@
package bridge package bridge
// #cgo CFLAGS: -I"${SRCDIR}/../../build/" // #cgo CFLAGS: -I"${SRCDIR}/../build/"
// #cgo LDFLAGS: "${SRCDIR}/../../build/libsointu.a" // #cgo LDFLAGS: "${SRCDIR}/../build/libsointu.a"
// #include <sointu.h> // #include <sointu.h>
import "C" import "C"
import ( import (
@ -9,11 +9,11 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu"
"github.com/vsariola/sointu/go4k/compiler" "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) s := new(C.Synth)
comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{}) comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{})
if err != nil { 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 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) { func (s *C.Synth) Trigger(voice int, note byte) {
s.SynthWrk.Voices[voice] = C.Voice{} s.SynthWrk.Voices[voice] = C.Voice{}
s.SynthWrk.Voices[voice].Note = C.int(note) 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) { func (s *C.Synth) Release(voice int) {
s.SynthWrk.Voices[voice].Release = 1 s.SynthWrk.Voices[voice].Release = 1
} }

146
bridge/bridge_test.go Normal file
View File

@ -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")
}
}

66
bridge/song_test.go Normal file
View File

@ -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])
}
}
}

View File

@ -14,10 +14,10 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu"
"github.com/vsariola/sointu/go4k/audio/oto" "github.com/vsariola/sointu/audio/oto"
"github.com/vsariola/sointu/go4k/bridge" "github.com/vsariola/sointu/bridge"
"github.com/vsariola/sointu/go4k/compiler" "github.com/vsariola/sointu/compiler"
) )
func main() { func main() {
@ -94,7 +94,7 @@ func main() {
if err != nil { if err != nil {
return fmt.Errorf("Could not read file %v: %v", filename, err) 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 errJSON := json.Unmarshal(inputBytes, &song); errJSON != nil {
if errYaml := yaml.Unmarshal(inputBytes, &song); errYaml != 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) 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 { if err != nil {
return fmt.Errorf("Could not create synth based on the patch: %v", err) 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 { if err != nil {
return fmt.Errorf("go4k.Play failed: %v", err) return fmt.Errorf("sointu.Play failed: %v", err)
} }
} }
if *play { if *play {

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"gioui.org/app" "gioui.org/app"
"gioui.org/unit" "gioui.org/unit"
"github.com/vsariola/sointu/go4k/audio/oto" "github.com/vsariola/sointu/audio/oto"
"github.com/vsariola/sointu/go4k/tracker" "github.com/vsariola/sointu/tracker"
"os" "os"
) )

View File

@ -9,7 +9,7 @@ import (
"text/template" "text/template"
"github.com/Masterminds/sprig" "github.com/Masterminds/sprig"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu"
) )
//go:generate go run generate.go //go:generate go run generate.go
@ -24,7 +24,7 @@ type Compiler struct {
// New returns a new compiler using the default .asm templates // New returns a new compiler using the default .asm templates
func New() (*Compiler, error) { func New() (*Compiler, error) {
_, myname, _, _ := runtime.Caller(0) _, myname, _, _ := runtime.Caller(0)
templateDir := filepath.Join(path.Dir(myname), "..", "..", "templates") templateDir := filepath.Join(path.Dir(myname), "..", "templates")
compiler, err := NewFromTemplates(templateDir) compiler, err := NewFromTemplates(templateDir)
return compiler, err return compiler, err
} }
@ -62,7 +62,7 @@ func (com *Compiler) Library() (map[string]string, error) {
return map[string]string{"asm": asmCode, "h": header}, nil 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) features := NecessaryFeaturesFor(song.Patch)
encodedPatch, err := Encode(&song.Patch, features) encodedPatch, err := Encode(&song.Patch, features)
if err != nil { if err != nil {

View File

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu"
) )
type EncodedPatch struct { type EncodedPatch struct {
@ -22,7 +22,7 @@ type SampleOffset struct {
LoopLength uint16 LoopLength uint16
} }
func Encode(patch *go4k.Patch, featureSet FeatureSet) (*EncodedPatch, error) { func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) {
var c EncodedPatch var c EncodedPatch
sampleOffsetMap := map[SampleOffset]int{} sampleOffsetMap := map[SampleOffset]int{}
for _, instr := range patch.Instruments { for _, instr := range patch.Instruments {
@ -75,13 +75,13 @@ func Encode(patch *go4k.Patch, featureSet FeatureSet) (*EncodedPatch, error) {
return &c, nil 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) opcode, ok := featureSet.Opcode(unit.Type)
if !ok { if !ok {
return 0, nil, fmt.Errorf(`the targeted virtual machine is not configured to support unit type "%v"`, unit.Type) return 0, nil, fmt.Errorf(`the targeted virtual machine is not configured to support unit type "%v"`, unit.Type)
} }
var values []byte var values []byte
for _, v := range go4k.UnitTypes[unit.Type] { for _, v := range sointu.UnitTypes[unit.Type] {
if v.CanModulate && v.CanSet { if v.CanModulate && v.CanSet {
values = append(values, byte(unit.Parameters[v.Name])) 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" { } else if unit.Type == "oscillator" {
flags := 0 flags := 0
switch unit.Parameters["type"] { switch unit.Parameters["type"] {
case go4k.Sine: case sointu.Sine:
flags = 0x40 flags = 0x40
case go4k.Trisaw: case sointu.Trisaw:
flags = 0x20 flags = 0x20
case go4k.Pulse: case sointu.Pulse:
flags = 0x10 flags = 0x10
case go4k.Gate: case sointu.Gate:
flags = 0x04 flags = 0x04
case go4k.Sample: case sointu.Sample:
flags = 0x80 flags = 0x80
} }
if unit.Parameters["lfo"] == 1 { if unit.Parameters["lfo"] == 1 {

View File

@ -3,7 +3,7 @@ package compiler
import ( import (
"sort" "sort"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu"
) )
// FeatureSet defines what opcodes / parameters are included in the compiled virtual machine // 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 var allTransformCounts map[string]int
func init() { func init() {
allInstructions = make([]string, len(go4k.UnitTypes)) allInstructions = make([]string, len(sointu.UnitTypes))
allOpcodes = map[string]int{} allOpcodes = map[string]int{}
allTransformCounts = map[string]int{} allTransformCounts = map[string]int{}
allInputs = map[paramKey]int{} allInputs = map[paramKey]int{}
i := 0 i := 0
for k, v := range go4k.UnitTypes { for k, v := range sointu.UnitTypes {
inputCount := 0 inputCount := 0
transformCount := 0 transformCount := 0
for _, t := range v { for _, t := range v {
@ -117,7 +117,7 @@ type NecessaryFeatures struct {
polyphony bool 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{}} features := NecessaryFeatures{opcodes: map[string]int{}, supportsParamValue: map[paramKey](map[int]bool){}, supportsModulation: map[paramKey]bool{}}
for instrNo, instrument := range patch.Instruments { for instrNo, instrument := range patch.Instruments {
for _, unit := range instrument.Units { for _, unit := range instrument.Units {
@ -146,7 +146,7 @@ func NecessaryFeaturesFor(patch go4k.Patch) NecessaryFeatures {
} }
targetUnit := patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]] targetUnit := patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]]
modulatedPortNo := 0 modulatedPortNo := 0
for _, v := range go4k.UnitTypes[targetUnit.Type] { for _, v := range sointu.UnitTypes[targetUnit.Type] {
if v.CanModulate { if v.CanModulate {
if modulatedPortNo == unit.Parameters["port"] { if modulatedPortNo == unit.Parameters["port"] {
features.supportsModulation[paramKey{targetUnit.Type, v.Name}] = true features.supportsModulation[paramKey{targetUnit.Type, v.Name}] = true

View File

@ -13,7 +13,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"github.com/vsariola/sointu/go4k/compiler" "github.com/vsariola/sointu/compiler"
) )
func main() { func main() {

View File

@ -5,7 +5,7 @@ import (
"math" "math"
"strings" "strings"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu"
) )
type OplistEntry struct { type OplistEntry struct {
@ -39,11 +39,11 @@ func NewMacros(c Compiler, f FeatureSet) *Macros {
usesFloatConst: map[float32]bool{}, usesFloatConst: map[float32]bool{},
usesIntConst: map[int]bool{}, usesIntConst: map[int]bool{},
stackframes: map[string][]string{}, stackframes: map[string][]string{},
Sine: go4k.Sine, Sine: sointu.Sine,
Trisaw: go4k.Trisaw, Trisaw: sointu.Trisaw,
Pulse: go4k.Pulse, Pulse: sointu.Pulse,
Gate: go4k.Gate, Gate: sointu.Gate,
Sample: go4k.Sample, Sample: sointu.Sample,
Compiler: c, Compiler: c,
FeatureSet: f, FeatureSet: f,
} }
@ -442,14 +442,14 @@ func (p *Macros) Use(value string, regs ...string) (string, error) {
} }
type PlayerMacros struct { type PlayerMacros struct {
Song *go4k.Song Song *sointu.Song
VoiceTrackBitmask int VoiceTrackBitmask int
MaxSamples int MaxSamples int
Macros Macros
EncodedPatch 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 { if maxSamples == 0 {
maxSamples = s.SamplesPerRow() * s.TotalRows() maxSamples = s.SamplesPerRow() * s.TotalRows()
} }

View File

@ -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")
}
}

View File

@ -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])
}
}
}

View File

@ -1,4 +1,4 @@
package go4k package sointu
import ( import (
"errors" "errors"
@ -68,10 +68,10 @@ type Synth interface {
func Render(synth Synth, buffer []float32) error { func Render(synth Synth, buffer []float32) error {
s, _, err := synth.Render(buffer, math.MaxInt32) s, _, err := synth.Render(buffer, math.MaxInt32)
if err != nil { if err != nil {
return fmt.Errorf("go4k.Render failed: %v", err) return fmt.Errorf("sointu.Render failed: %v", err)
} }
if s != len(buffer)/2 { 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 return nil
} }

View File

@ -279,9 +279,9 @@ su_oscillat_sine_do:
pop {{.AX}} pop {{.AX}}
and al, 0xf ; ax=int(16*p) & 15, stack: 1 and al, 0xf ; ax=int(16*p) & 15, stack: 1
bt word [{{.VAL}}-4],ax ; if bit ax of the gate word is set 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 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 fld dword [{{.WRK}}+16] ; g x, g is gatestate, x is the input to this filter 0/1
fsub st1 ; g-x x fsub st1 ; g-x x
{{- .Float 0.99609375 | .Prepare | indent 4}} {{- .Float 0.99609375 | .Prepare | indent 4}}

View File

@ -53,8 +53,8 @@ endfunction(regression_test)
# starting # starting
add_custom_command( add_custom_command(
OUTPUT ${sointuexe} OUTPUT ${sointuexe}
COMMAND go build -o ${sointuexe} ${PROJECT_SOURCE_DIR}/go4k/cmd/sointu-cli/main.go COMMAND go build -o ${sointuexe} ${PROJECT_SOURCE_DIR}/cmd/sointu-cli/main.go
DEPENDS "${templates}" "${go4k}" "${compilersrc}" ${STATICLIB} DEPENDS "${templates}" "${sointu}" "${compilersrc}" ${STATICLIB}
) )
regression_test(test_envelope "" ENVELOPE) regression_test(test_envelope "" ENVELOPE)

View File

@ -1,24 +1,24 @@
package tracker package tracker
import "github.com/vsariola/sointu/go4k" import "github.com/vsariola/sointu"
var defaultSong = go4k.Song{ var defaultSong = sointu.Song{
BPM: 100, BPM: 100,
Patterns: [][]byte{ Patterns: [][]byte{
{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}, {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}, {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{0}},
{NumVoices: 1, Sequence: []byte{1}}, {NumVoices: 1, Sequence: []byte{1}},
}, },
Patch: go4k.Patch{ Patch: sointu.Patch{
Instruments: []go4k.Instrument{{NumVoices: 2, Units: []go4k.Unit{ 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: "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: "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: "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: "mulp", Parameters: map[string]int{"stereo": 0}},
{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, {Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
}}}}, }}}},

View File

@ -3,14 +3,14 @@ package tracker
import ( import (
"fmt" "fmt"
"gioui.org/widget" "gioui.org/widget"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu"
"github.com/vsariola/sointu/go4k/audio" "github.com/vsariola/sointu/audio"
"github.com/vsariola/sointu/go4k/bridge" "github.com/vsariola/sointu/bridge"
) )
type Tracker struct { type Tracker struct {
QuitButton *widget.Clickable QuitButton *widget.Clickable
song go4k.Song song sointu.Song
CursorRow int CursorRow int
CursorColumn int CursorColumn int
DisplayPattern int DisplayPattern int
@ -24,12 +24,12 @@ type Tracker struct {
rowJump chan int rowJump chan int
patternJump chan int patternJump chan int
player audio.Player player audio.Player
synth go4k.Synth synth sointu.Synth
playBuffer []float32 playBuffer []float32
closer chan struct{} 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 { if err := song.Validate(); err != nil {
return fmt.Errorf("invalid song: %w", err) return fmt.Errorf("invalid song: %w", err)
} }