mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
refactor(go): Move everything from go4k to root package sointu
This commit is contained in:
parent
d0bd877b3f
commit
224b8dcb70
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@ -61,5 +61,4 @@ jobs:
|
||||
env:
|
||||
CGO_LDFLAGS: ${{ matrix.config.cgo_ldflags }}
|
||||
run: |
|
||||
cd go4k
|
||||
go test . ./bridge
|
||||
|
@ -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}"
|
||||
)
|
||||
|
||||
|
@ -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
|
@ -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)
|
||||
}
|
@ -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
146
bridge/bridge_test.go
Normal 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
66
bridge/song_test.go
Normal 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])
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
@ -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"
|
||||
)
|
||||
|
@ -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 {
|
@ -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 {
|
@ -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
|
@ -13,7 +13,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/vsariola/sointu/go4k/compiler"
|
||||
"github.com/vsariola/sointu/compiler"
|
||||
)
|
||||
|
||||
func main() {
|
@ -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()
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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}}
|
||||
|
@ -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)
|
||||
|
@ -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}},
|
||||
}}}},
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user