mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -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:
|
env:
|
||||||
CGO_LDFLAGS: ${{ matrix.config.cgo_ldflags }}
|
CGO_LDFLAGS: ${{ matrix.config.cgo_ldflags }}
|
||||||
run: |
|
run: |
|
||||||
cd go4k
|
|
||||||
go test . ./bridge
|
go test . ./bridge
|
||||||
|
@ -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}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
@ -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)
|
||||||
}
|
}
|
@ -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
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"
|
"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 {
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
@ -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 {
|
@ -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 {
|
@ -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
|
@ -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() {
|
@ -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()
|
||||||
}
|
}
|
@ -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 (
|
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
|
||||||
}
|
}
|
@ -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}}
|
||||||
|
@ -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)
|
||||||
|
@ -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}},
|
||||||
}}}},
|
}}}},
|
@ -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)
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user