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:
CGO_LDFLAGS: ${{ matrix.config.cgo_ldflags }}
run: |
cd go4k
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
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}"
)

View File

@ -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

View File

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

View File

@ -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 <sointu.h>
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
}

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"
"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 {

View File

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

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

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

View File

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

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 (
"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
}

View File

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

View File

@ -53,8 +53,8 @@ endfunction(regression_test)
# starting
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)

View File

@ -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}},
}}}},

View File

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