feat(asm&go4k): Rewrote both library & player to use text/template compiler

There is no more plain .asms, both library & player are created from the templates using go text/template package.
This commit is contained in:
Veikko Sariola
2020-12-14 15:46:12 +02:00
parent 2ad61ff6b2
commit d0bd877b3f
141 changed files with 1195 additions and 5542 deletions

View File

@ -34,17 +34,38 @@ enable_language(ASM_NASM)
# By putting them there, we can pass the same compile definitions to C and ASM # By putting them there, we can pass the same compile definitions to C and ASM
set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> <INCLUDES> <DEFINES> <FLAGS> -f ${CMAKE_ASM_NASM_OBJECT_FORMAT} -o <OBJECT> <SOURCE>") set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> <INCLUDES> <DEFINES> <FLAGS> -f ${CMAKE_ASM_NASM_OBJECT_FORMAT} -o <OBJECT> <SOURCE>")
# Sointu as a header-only library if(WIN32)
set(HEADERLIB sointuinterface) set(sointuexe ${CMAKE_CURRENT_BINARY_DIR}/sointu-cli.exe)
add_library(${HEADERLIB} INTERFACE) else()
target_include_directories(${HEADERLIB} INTERFACE ${PROJECT_SOURCE_DIR}/include) set(sointuexe ${CMAKE_CURRENT_BINARY_DIR}/sointu-cli)
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")
if(DEFINED CMAKE_C_SIZEOF_DATA_PTR AND CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
set(arch "amd64")
elseif(DEFINED CMAKE_CXX_SIZEOF_DATA_PTR AND CMAKE_CXX_SIZEOF_DATA_PTR EQUAL 8)
set(arch "amd64")
else()
set(arch "386")
endif()
# Sointu as static library # Sointu as static library
set(STATICLIB sointu) set(STATICLIB sointu)
add_library(${STATICLIB} render.asm) 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}
DEPENDS "${templates}" "${go4k}" "${compilersrc}"
)
add_library(${STATICLIB} ${sointuasm})
set_target_properties(${STATICLIB} PROPERTIES LINKER_LANGUAGE C) set_target_properties(${STATICLIB} PROPERTIES LINKER_LANGUAGE C)
target_link_libraries(${STATICLIB} ${HEADERLIB}) target_include_directories(${STATICLIB} INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
target_compile_definitions(${STATICLIB} PUBLIC SU_USE_INTROSPECTION RUNTIME_TABLES)
# We should put examples here # We should put examples here
# add_subdirectory(examples) # add_subdirectory(examples)

1
go.mod
View File

@ -12,5 +12,6 @@ require (
github.com/huandu/xstrings v1.3.2 // indirect github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.11 // indirect github.com/imdario/mergo v0.3.11 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
) )

1
go.sum
View File

@ -48,6 +48,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,170 +0,0 @@
package go4k
// FindSuperIntArray finds a small super array containing all
// the subarrays passed to it. Returns the super array and indices where
// the subarrays can be found. For example:
// FindSuperIntArray([][]int{{4,5,6},{1,2,3},{3,4}})
// returns {1,2,3,4,5,6},{3,0,2}
// Implemented using a greedy search, so does not necessarily find
// the true optimal (the problem is NP-hard and analogous to traveling
// salesman problem).
//
// Used to construct a small delay time table without unnecessary repetition
// of delay times.
func FindSuperIntArray(arrays [][]int) ([]int, []int) {
// If we go past MAX_MERGES, the algorithm could get slow and hang the computer
// So this is a safety limit: after this problem size, just merge any arrays
// until we get into more manageable range
const maxMerges = 1000
min := func(a int, b int) int {
if a < b {
return a
}
return b
}
overlap := func(a []int, b []int) (int, int) {
minShift := len(a)
for shift := len(a) - 1; shift >= 0; shift-- {
overlapping := true
for k := shift; k < min(len(a), len(b)+shift); k++ {
if a[k] != b[k-shift] {
overlapping = false
break
}
}
if overlapping {
minShift = shift
}
}
overlap := min(len(a)-minShift, len(b))
return overlap, minShift
}
sliceNumbers := make([]int, len(arrays))
startIndices := make([]int, len(arrays))
var processedArrays [][]int
for i := range arrays {
if len(arrays[i]) == 0 {
// Zero length arrays do not need to be processed at all
// They will 'start' at index 0 always as they have no length.
sliceNumbers[i] = -1
} else {
sliceNumbers[i] = len(processedArrays)
processedArrays = append(processedArrays, arrays[i])
}
}
if len(processedArrays) == 0 {
return []int{}, startIndices // no arrays with len>0 to process, just return empty array and all indices as 0
}
for len(processedArrays) > 1 { // there's at least two candidates that could be be merged
maxO, maxI, maxJ, maxS := -1, -1, -1, -1
if len(processedArrays) < maxMerges {
// find the pair i,j that results in the largest overlap with array i coming first, followed by potentially overlapping array j
for i := range processedArrays {
for j := range processedArrays {
if i == j {
continue
}
overlap, shift := overlap(processedArrays[i], processedArrays[j])
if overlap > maxO {
maxI, maxJ, maxO, maxS = i, j, overlap, shift
}
}
}
} else {
// The task is daunting, we have over MAX_MERGES overlaps to test. Just merge two first ones until the task is more manageable size
overlap, shift := overlap(processedArrays[0], processedArrays[1])
maxI, maxJ, maxO, maxS = 0, 1, overlap, shift
}
for k := range sliceNumbers {
if sliceNumbers[k] == maxJ {
// update slice pointers to point maxI instead of maxJ (maxJ will be appended to maxI, taking overlap into account)
sliceNumbers[k] = maxI
startIndices[k] += maxS // the array j starts at index maxS in array i
}
if sliceNumbers[k] > maxJ {
// pointers maxJ reduced by 1 as maxJ will be deleted
sliceNumbers[k]--
}
}
// if array j was not entirely included within array j
if maxO < len(processedArrays[maxJ]) {
// append array maxJ to array maxI, without duplicating the overlapping part
processedArrays[maxI] = append(processedArrays[maxI], processedArrays[maxJ][maxO:]...)
}
// finally, remove element maxJ from processedArrays
processedArrays = append(processedArrays[:maxJ], processedArrays[maxJ+1:]...)
}
return processedArrays[0], startIndices // there should be only one slice left in the arrays after the loop
}
// ConstructDelayTimeTable tries to construct the delay times table
// abusing overlapping between different delay times tables as much
// as possible. Especially: if two delay units use exactly the same
// delay times, they appear in the table only once.
func ConstructDelayTimeTable(patch Patch) ([]int, [][]int) {
ind := make([][]int, len(patch.Instruments))
var subarrays [][]int
// flatten the delay times into one array of arrays
// saving the indices where they were placed
for i, instr := range patch.Instruments {
ind[i] = make([]int, len(instr.Units))
for j, unit := range instr.Units {
// only include delay times for delays. Only delays
// should use delay times
if unit.Type == "delay" {
ind[i][j] = len(subarrays)
end := unit.Parameters["count"]
if unit.Parameters["stereo"] > 0 {
end *= 2
}
subarrays = append(subarrays, patch.DelayTimes[unit.Parameters["delay"]:end])
}
}
}
delayTable, indices := FindSuperIntArray(subarrays)
// cancel the flattening, so unitindices can be used to
// to find the index of each delay in the delay table
unitindices := make([][]int, len(patch.Instruments))
for i, instr := range patch.Instruments {
unitindices[i] = make([]int, len(instr.Units))
for j, unit := range instr.Units {
if unit.Type == "delay" {
unitindices[i][j] = indices[ind[i][j]]
}
}
}
return delayTable, unitindices
}
// ConstructSampleOffsetTable collects the sample offests from
// all sample-based oscillators and collects them in a table,
// so that they appear in the table only once. Returns the collected
// table and [][]int array where element [i][j] is the index in the
// table by instrument i / unit j (units other than sample oscillators
// have the value 0)
func ConstructSampleOffsetTable(patch Patch) ([]SampleOffset, [][]int) {
unitindices := make([][]int, len(patch.Instruments))
var offsetTable []SampleOffset
offsetMap := map[SampleOffset]int{}
for i, instr := range patch.Instruments {
unitindices[i] = make([]int, len(instr.Units))
for j, unit := range instr.Units {
if unit.Type == "oscillator" && unit.Parameters["type"] == Sample {
offset := SampleOffset{
Start: unit.Parameters["start"],
LoopStart: unit.Parameters["loopstart"],
LoopLength: unit.Parameters["looplength"],
}
if ind, ok := offsetMap[offset]; ok {
unitindices[i][j] = ind // the sample has been already added to table, reuse the index
} else {
ind = len(offsetTable)
unitindices[i][j] = ind
offsetMap[offset] = ind
offsetTable = append(offsetTable, offset)
}
}
}
}
return offsetTable, unitindices
}

View File

@ -1,36 +0,0 @@
package go4k_test
import (
"fmt"
"reflect"
"testing"
"github.com/vsariola/sointu/go4k"
)
func TestFindSuperIntArray(t *testing.T) {
var tests = []struct {
input [][]int
wantSuper []int
wantIndices []int
}{
{[][]int{}, []int{}, []int{}},
{[][]int{nil, nil}, []int{}, []int{0, 0}},
{[][]int{{3, 4, 5}, {1, 2, 3}}, []int{1, 2, 3, 4, 5}, []int{2, 0}},
{[][]int{{3, 4, 5}, {1, 2, 3}, nil}, []int{1, 2, 3, 4, 5}, []int{2, 0, 0}},
{[][]int{{3, 4, 5}, {1, 2, 3}, {}}, []int{1, 2, 3, 4, 5}, []int{2, 0, 0}},
{[][]int{{3, 4, 5}, {1, 2, 3}, {2, 3}}, []int{1, 2, 3, 4, 5}, []int{2, 0, 1}},
{[][]int{{1, 2, 3, 4, 5}, {1, 2, 3}}, []int{1, 2, 3, 4, 5}, []int{0, 0}},
{[][]int{{1, 2, 3, 4, 5}, {2, 3}}, []int{1, 2, 3, 4, 5}, []int{0, 1}},
{[][]int{{1, 2, 3, 4, 5}, {2, 3}, {5, 6, 7}}, []int{1, 2, 3, 4, 5, 6, 7}, []int{0, 1, 4}},
{[][]int{{1, 2, 3, 4}, {3, 4, 1}, {2, 3, 4, 5}}, []int{3, 4, 1, 2, 3, 4, 5}, []int{2, 0, 3}},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("TestFindSuperIntArray %d", i), func(t *testing.T) {
super, indices := go4k.FindSuperIntArray(tt.input)
if !reflect.DeepEqual(super, tt.wantSuper) || !reflect.DeepEqual(indices, tt.wantIndices) {
t.Errorf("FindSuperIntArray(%v) got (%v,%v), want (%v,%v)", tt.input, super, indices, tt.wantSuper, tt.wantIndices)
}
})
}
}

View File

@ -1,346 +0,0 @@
package go4k
import (
"bufio"
"fmt"
"regexp"
"strconv"
"strings"
)
func ParseAsm(asmcode string) (*Song, error) {
paramReg, err := regexp.Compile(`(?:([a-zA-Z]\w*)\s*\(\s*([0-9]+)\s*\)|([0-9]+))`) // matches FOO(42), groups "FOO" and "42" OR just numbers
if err != nil {
return nil, fmt.Errorf("error compiling paramReg: %v", err)
}
parseParams := func(s string) (map[string]int, []int, error) {
matches := paramReg.FindAllStringSubmatch(s, 256)
namedParams := map[string]int{}
unnamedParams := make([]int, 0)
for _, match := range matches {
if match[1] == "" { // the second part of OR fired
val, err := strconv.Atoi(match[3])
if err != nil {
return nil, nil, fmt.Errorf("error converting %v to integer, which is unexpected as regexp matches only numbers: %v", match[3], err)
}
unnamedParams = append(unnamedParams, val)
} else {
val, err := strconv.Atoi(match[2])
if err != nil {
return nil, nil, fmt.Errorf("error converting %v to integer, which is unexpected as regexp matches only numbers: %v", match[2], err)
}
namedParams[strings.ToLower(match[1])] = val
}
}
return namedParams, unnamedParams, nil
}
wordReg, err := regexp.Compile(`\s*([a-zA-Z_][a-zA-Z0-9_]*)([^;\n]*)`) // matches a word and "the rest", until newline or a comment
if err != nil {
return nil, err
}
toBytes := func(ints []int) []byte {
ret := []byte{}
for _, v := range ints {
ret = append(ret, byte(v))
}
return ret
}
var song Song
scanner := bufio.NewScanner(strings.NewReader(asmcode))
for scanner.Scan() {
line := scanner.Text()
macroMatch := wordReg.FindStringSubmatch(line)
if macroMatch == nil {
continue
}
word, rest := macroMatch[1], macroMatch[2]
namedParams, unnamedParams, err := parseParams(rest)
if err != nil {
return nil, fmt.Errorf("error parsing parameters: %v", err)
}
switch word {
case "BEGIN_SONG":
song = Song{BPM: namedParams["bpm"], Patterns: nil, Tracks: nil, Patch: Patch{}, Output16Bit: namedParams["output_16bit"] == 1, Hold: byte(namedParams["hold"])}
case "PATTERN":
song.Patterns = append(song.Patterns, toBytes(unnamedParams))
case "TRACK":
song.Tracks = append(song.Tracks, Track{namedParams["voices"], toBytes(unnamedParams)})
case "BEGIN_INSTRUMENT":
song.Patch.Instruments = append(song.Patch.Instruments, Instrument{NumVoices: namedParams["voices"], Units: []Unit{}})
case "DELTIME":
song.Patch.DelayTimes = append(song.Patch.DelayTimes, unnamedParams...)
case "SAMPLE_OFFSET":
song.Patch.SampleOffsets = append(song.Patch.SampleOffsets, SampleOffset{
Start: namedParams["start"],
LoopStart: namedParams["loopstart"],
LoopLength: namedParams["looplength"]})
}
if strings.HasPrefix(word, "SU_") {
unittype := strings.ToLower(word[3:])
unit := Unit{Type: unittype, Parameters: namedParams}
lastIndex := len(song.Patch.Instruments) - 1
if lastIndex < 0 {
return nil, fmt.Errorf("opcode %v before BEGIN_INSTRUMENT", word)
}
song.Patch.Instruments[lastIndex].Units = append(song.Patch.Instruments[lastIndex].Units, unit)
}
}
return &song, nil
}
func FormatAsm(song *Song) (string, error) {
paramorder := map[string][]string{
"add": []string{"stereo"},
"addp": []string{"stereo"},
"pop": []string{"stereo"},
"loadnote": []string{"stereo"},
"mul": []string{"stereo"},
"mulp": []string{"stereo"},
"push": []string{"stereo"},
"xch": []string{"stereo"},
"distort": []string{"stereo", "drive"},
"hold": []string{"stereo", "holdfreq"},
"crush": []string{"stereo", "resolution"},
"gain": []string{"stereo", "gain"},
"invgain": []string{"stereo", "invgain"},
"filter": []string{"stereo", "frequency", "resonance", "lowpass", "bandpass", "highpass", "negbandpass", "neghighpass"},
"clip": []string{"stereo"},
"pan": []string{"stereo", "panning"},
"delay": []string{"stereo", "pregain", "dry", "feedback", "damp", "delay", "count", "notetracking"},
"compressor": []string{"stereo", "attack", "release", "invgain", "threshold", "ratio"},
"speed": []string{},
"out": []string{"stereo", "gain"},
"outaux": []string{"stereo", "outgain", "auxgain"},
"aux": []string{"stereo", "gain", "channel"},
"send": []string{"stereo", "amount", "voice", "unit", "port", "sendpop"},
"envelope": []string{"stereo", "attack", "decay", "sustain", "release", "gain"},
"noise": []string{"stereo", "shape", "gain"},
"oscillator": []string{"stereo", "transpose", "detune", "phase", "color", "shape", "gain", "type", "lfo", "unison"},
"loadval": []string{"stereo", "value"},
"receive": []string{"stereo"},
"in": []string{"stereo", "channel"},
}
indentation := 0
indent := func() string {
return strings.Repeat(" ", indentation*4)
}
var b strings.Builder
println := func(format string, params ...interface{}) {
if len(format) > 0 {
fmt.Fprintf(&b, "%v", indent())
fmt.Fprintf(&b, format, params...)
}
fmt.Fprintf(&b, "\n")
}
align := func(table [][]string, format string) [][]string {
var maxwidth []int
// find the maximum width of each column
for _, row := range table {
for k, elem := range row {
l := len(elem)
if len(maxwidth) <= k {
maxwidth = append(maxwidth, l)
} else {
if maxwidth[k] < l {
maxwidth[k] = l
}
}
}
}
// align each column, depending on the specified formatting
for _, row := range table {
for k, elem := range row {
l := len(elem)
var f byte
if k >= len(format) {
f = format[len(format)-1] // repeat the last format specifier for all remaining columns
} else {
f = format[k]
}
switch f {
case 'n': // no alignment
row[k] = elem
case 'l': // left align
row[k] = elem + strings.Repeat(" ", maxwidth[k]-l)
case 'r': // right align
row[k] = strings.Repeat(" ", maxwidth[k]-l) + elem
}
}
}
return table
}
printTable := func(table [][]string) {
indentation++
for _, row := range table {
println("%v %v", row[0], strings.Join(row[1:], ","))
}
indentation--
}
// delay modulation is pretty much the only %define that the asm preprocessor cannot figure out
// as the preprocessor has no clue if a SEND modulates a delay unit. So, unfortunately, for the
// time being, we need to figure during export if INCLUDE_DELAY_MODULATION needs to be defined.
delaymod := 0
for i, instrument := range song.Patch.Instruments {
for j, unit := range instrument.Units {
if unit.Type == "send" {
targetInstrument := i
if unit.Parameters["voice"] > 0 {
v, err := song.Patch.InstrumentForVoice(unit.Parameters["voice"] - 1)
if err != nil {
return "", fmt.Errorf("INSTRUMENT #%v / SEND #%v targets voice %v, which does not exist", i, j, unit.Parameters["voice"])
}
targetInstrument = v
}
if unit.Parameters["unit"] < 0 || unit.Parameters["unit"] >= len(song.Patch.Instruments[targetInstrument].Units) {
return "", fmt.Errorf("INSTRUMENT #%v / SEND #%v target unit %v out of range", i, j, unit.Parameters["unit"])
}
if song.Patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]].Type == "delay" && unit.Parameters["port"] == 5 {
delaymod = 1
}
}
}
}
// The actual printing starts here
output_16bit := 0
if song.Output16Bit {
output_16bit = 1
}
println("%%include \"sointu/header.inc\"\n")
println("BEGIN_SONG BPM(%v),OUTPUT_16BIT(%v),CLIP_OUTPUT(0),DELAY_MODULATION(%v),HOLD(%v)\n", song.BPM, output_16bit, delaymod, song.Hold)
var patternTable [][]string
for _, pattern := range song.Patterns {
row := []string{"PATTERN"}
for _, v := range pattern {
row = append(row, strconv.Itoa(int(v)))
}
patternTable = append(patternTable, row)
}
println("BEGIN_PATTERNS")
printTable(align(patternTable, "lr"))
println("END_PATTERNS\n")
var trackTable [][]string
for _, track := range song.Tracks {
row := []string{"TRACK", fmt.Sprintf("VOICES(%d)", track.NumVoices)}
for _, v := range track.Sequence {
row = append(row, strconv.Itoa(int(v)))
}
trackTable = append(trackTable, row)
}
println("BEGIN_TRACKS")
printTable(align(trackTable, "lr"))
println("END_TRACKS\n")
println("BEGIN_PATCH")
indentation++
for _, instrument := range song.Patch.Instruments {
var instrTable [][]string
for _, unit := range instrument.Units {
row := []string{fmt.Sprintf("SU_%v", strings.ToUpper(unit.Type))}
for _, parname := range paramorder[unit.Type] {
if v, ok := unit.Parameters[parname]; ok {
row = append(row, fmt.Sprintf("%v(%v)", strings.ToUpper(parname), strconv.Itoa(int(v))))
} else {
return "", fmt.Errorf("The parameter map for unit %v does not contain %v, even though it should", unit.Type, parname)
}
}
instrTable = append(instrTable, row)
}
println("BEGIN_INSTRUMENT VOICES(%d)", instrument.NumVoices)
printTable(align(instrTable, "ln"))
println("END_INSTRUMENT")
}
indentation--
println("END_PATCH\n")
if len(song.Patch.DelayTimes) > 0 {
var delStrTable [][]string
for _, v := range song.Patch.DelayTimes {
row := []string{"DELTIME", strconv.Itoa(int(v))}
delStrTable = append(delStrTable, row)
}
println("BEGIN_DELTIMES")
printTable(align(delStrTable, "lr"))
println("END_DELTIMES\n")
}
if len(song.Patch.SampleOffsets) > 0 {
var samStrTable [][]string
for _, v := range song.Patch.SampleOffsets {
samStrTable = append(samStrTable, []string{
"SAMPLE_OFFSET",
fmt.Sprintf("START(%d)", v.Start),
fmt.Sprintf("LOOPSTART(%d)", v.LoopStart),
fmt.Sprintf("LOOPLENGTH(%d)", v.LoopLength),
})
}
println("BEGIN_SAMPLE_OFFSETS")
printTable(align(samStrTable, "r"))
println("END_SAMPLE_OFFSETS\n")
}
println("END_SONG")
ret := b.String()
return ret, nil
}
func CHeader(song *Song, maxSamples int) string {
template :=
`// auto-generated by Sointu, editing not recommended
#ifndef SU_RENDER_H
#define SU_RENDER_H
#define SU_MAX_SAMPLES %v
#define SU_BUFFER_LENGTH (SU_MAX_SAMPLES*2)
#define SU_SAMPLE_RATE 44100
#define SU_BPM %v
#define SU_PATTERN_SIZE %v
#define SU_MAX_PATTERNS %v
#define SU_TOTAL_ROWS (SU_MAX_PATTERNS*SU_PATTERN_SIZE)
#define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*4*60/(SU_BPM*16))
#include <stdint.h>
#if UINTPTR_MAX == 0xffffffff
#if defined(__clang__) || defined(__GNUC__)
#define SU_CALLCONV __attribute__ ((stdcall))
#elif defined(_WIN32)
#define SU_CALLCONV __stdcall
#endif
#else
#define SU_CALLCONV
#endif
typedef %v SUsample;
#define SU_SAMPLE_RANGE %v
#ifdef __cplusplus
extern "C" {
#endif
void SU_CALLCONV su_render_song(SUsample *buffer);
%v
#ifdef __cplusplus
}
#endif
#endif
`
maxSamplesText := "SU_TOTAL_ROWS*SU_SAMPLES_PER_ROW"
if maxSamples > 0 {
maxSamplesText = fmt.Sprintf("%v", maxSamples)
}
sampleType := "float"
sampleRange := "1.0f"
if song.Output16Bit {
sampleType = "short"
sampleRange = "32768"
}
defineGmdls := ""
for _, instr := range song.Patch.Instruments {
for _, unit := range instr.Units {
if unit.Type == "oscillator" && unit.Parameters["type"] == Sample {
defineGmdls = "\n#define SU_LOAD_GMDLS\nvoid SU_CALLCONV su_load_gmdls(void);"
break
}
}
}
header := fmt.Sprintf(template, maxSamplesText, song.BPM, song.PatternRows(), song.SequenceLength(), sampleType, sampleRange, defineGmdls)
return header
}

View File

@ -1,4 +1,4 @@
package go4k_test package bridge_test
import ( import (
"bytes" "bytes"
@ -9,19 +9,18 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu/go4k"
"github.com/vsariola/sointu/go4k/bridge" "github.com/vsariola/sointu/go4k/bridge"
"gopkg.in/yaml.v2"
) )
func TestAllAsmFiles(t *testing.T) { func TestAllAsmFiles(t *testing.T) {
bridge.Init()
_, myname, _, _ := runtime.Caller(0) _, myname, _, _ := runtime.Caller(0)
files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "tests", "*.asm")) 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)
} }
@ -37,89 +36,16 @@ 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)
} }
song, err := go4k.ParseAsm(string(asmcode)) var song go4k.Song
err = yaml.Unmarshal(asmcode, &song)
if err != nil { if err != nil {
t.Fatalf("could not parse the .asm file: %v", err) t.Fatalf("could not parse the .yml file: %v", err)
} }
synth, err := bridge.Synth(song.Patch) synth, err := bridge.Synth(song.Patch)
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 := go4k.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" {
outputpath := path.Join(path.Dir(myname), "actual_output")
if _, err := os.Stat(outputpath); os.IsNotExist(err) {
os.Mkdir(outputpath, 0755)
}
outFileName := path.Join(path.Dir(myname), "actual_output", testname+".raw")
outfile, err := os.OpenFile(outFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
defer outfile.Close()
if err != nil {
t.Fatalf("Creating file failed: %v", err)
}
var createdbuf bytes.Buffer
err = binary.Write(&createdbuf, binary.LittleEndian, buffer)
if err != nil {
t.Fatalf("error converting buffer: %v", err)
}
_, err = outfile.Write(createdbuf.Bytes())
if err != nil {
log.Fatal(err)
}
}
if song.Output16Bit {
int16Buffer := convertToInt16Buffer(buffer)
compareToRawInt16(t, int16Buffer, testname+".raw")
} else {
compareToRawFloat32(t, buffer, testname+".raw")
}
})
}
}
func TestSerializingAllAsmFiles(t *testing.T) {
bridge.Init()
_, myname, _, _ := runtime.Caller(0)
files, err := filepath.Glob(path.Join(path.Dir(myname), "..", "tests", "*.asm"))
if err != nil {
t.Fatalf("cannot glob files in the test directory: %v", err)
}
for _, filename := range files {
basename := filepath.Base(filename)
testname := strings.TrimSuffix(basename, path.Ext(basename))
t.Run(testname, func(t *testing.T) {
if runtime.GOOS != "windows" && strings.Contains(testname, "sample") {
t.Skip("Samples (gm.dls) available only on Windows")
return
}
asmcode, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("cannot read the .asm file: %v", filename)
}
song, err := go4k.ParseAsm(string(asmcode)) // read the asm
if err != nil {
t.Fatalf("could not parse the .asm file: %v", err)
}
str, err := go4k.FormatAsm(song) // serialize again
if err != nil {
t.Fatalf("Could not serialize asm file: %v", err)
}
song2, err := go4k.ParseAsm(str) // deserialize again. The rendered song should still give same results.
if err != nil {
t.Fatalf("could not parse the serialized asm code: %v", err)
}
if !reflect.DeepEqual(song, song2) {
t.Fatalf("serialize/deserialize does not result equal songs, before: %v, after %v", song, song2)
}
synth, err := bridge.Synth(song2.Patch)
if err != nil {
t.Fatalf("Compiling patch failed: %v", err)
}
buffer, err := go4k.Play(synth, *song2)
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)
@ -157,7 +83,7 @@ func TestSerializingAllAsmFiles(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)
} }
@ -179,7 +105,7 @@ func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
func compareToRawInt16(t *testing.T, buffer []int16, rawname string) { func compareToRawInt16(t *testing.T, buffer []int16, rawname string) {
_, filename, _, _ := runtime.Caller(0) _, filename, _, _ := runtime.Caller(0)
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname)) expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "tests", "expected_output", rawname))
if err != nil { if err != nil {
t.Fatalf("cannot read expected: %v", err) t.Fatalf("cannot read expected: %v", err)
} }

View File

@ -1,74 +1,48 @@
package bridge package bridge
// #cgo CFLAGS: -I"${SRCDIR}/../../build/"
// #cgo LDFLAGS: "${SRCDIR}/../../build/libsointu.a"
// #include <sointu.h>
import "C"
import ( import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu/go4k"
"github.com/vsariola/sointu/go4k/compiler"
) )
// #cgo CFLAGS: -I"${SRCDIR}/../../include/sointu" func Synth(patch go4k.Patch) (*C.Synth, error) {
// #cgo LDFLAGS: "${SRCDIR}/../../build/libsointu.a" s := new(C.Synth)
// #include <sointu.h> comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{})
import "C" if err != nil {
return nil, fmt.Errorf("error compiling patch: %v", err)
type opTableEntry struct {
opcode C.int
parameterList []string
}
var opcodeTable = map[string]opTableEntry{
"add": opTableEntry{C.su_add_id, []string{}},
"addp": opTableEntry{C.su_addp_id, []string{}},
"pop": opTableEntry{C.su_pop_id, []string{}},
"loadnote": opTableEntry{C.su_loadnote_id, []string{}},
"mul": opTableEntry{C.su_mul_id, []string{}},
"mulp": opTableEntry{C.su_mulp_id, []string{}},
"push": opTableEntry{C.su_push_id, []string{}},
"xch": opTableEntry{C.su_xch_id, []string{}},
"distort": opTableEntry{C.su_distort_id, []string{"drive"}},
"hold": opTableEntry{C.su_hold_id, []string{"holdfreq"}},
"crush": opTableEntry{C.su_crush_id, []string{"resolution"}},
"gain": opTableEntry{C.su_gain_id, []string{"gain"}},
"invgain": opTableEntry{C.su_invgain_id, []string{"invgain"}},
"filter": opTableEntry{C.su_filter_id, []string{"frequency", "resonance"}},
"clip": opTableEntry{C.su_clip_id, []string{}},
"pan": opTableEntry{C.su_pan_id, []string{"panning"}},
"delay": opTableEntry{C.su_delay_id, []string{"pregain", "dry", "feedback", "damp", "delay", "count"}},
"compressor": opTableEntry{C.su_compres_id, []string{"attack", "release", "invgain", "threshold", "ratio"}},
"speed": opTableEntry{C.su_speed_id, []string{}},
"out": opTableEntry{C.su_out_id, []string{"gain"}},
"outaux": opTableEntry{C.su_outaux_id, []string{"outgain", "auxgain"}},
"aux": opTableEntry{C.su_aux_id, []string{"gain", "channel"}},
"send": opTableEntry{C.su_send_id, []string{"amount"}},
"envelope": opTableEntry{C.su_envelope_id, []string{"attack", "decay", "sustain", "release", "gain"}},
"noise": opTableEntry{C.su_noise_id, []string{"shape", "gain"}},
"oscillator": opTableEntry{C.su_oscillat_id, []string{"transpose", "detune", "phase", "color", "shape", "gain"}},
"loadval": opTableEntry{C.su_loadval_id, []string{"value"}},
"receive": opTableEntry{C.su_receive_id, []string{}},
"in": opTableEntry{C.su_in_id, []string{"channel"}},
}
type RenderError struct {
errcode int
}
func (e *RenderError) Error() string {
var reasons []string
if e.errcode&0x40 != 0 {
reasons = append(reasons, "FPU stack over/underflow")
} }
if e.errcode&0x04 != 0 { if len(comPatch.Commands) > 2048 { // TODO: 2048 could probably be pulled automatically from cgo
reasons = append(reasons, "FPU divide by zero") return nil, errors.New("bridge supports at most 2048 commands; the compiled patch has more")
} }
if e.errcode&0x01 != 0 { if len(comPatch.Values) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo
reasons = append(reasons, "FPU invalid operation") return nil, errors.New("bridge supports at most 16384 values; the compiled patch has more")
} }
if e.errcode&0x3800 != 0 { for i, v := range comPatch.Commands {
reasons = append(reasons, "FPU stack push/pops are not balanced") s.Commands[i] = (C.uchar)(v)
} }
return "RenderError: " + strings.Join(reasons, ", ") for i, v := range comPatch.Values {
s.Values[i] = (C.uchar)(v)
}
for i, v := range comPatch.DelayTimes {
s.DelayTimes[i] = (C.ushort)(v)
}
for i, v := range comPatch.SampleOffsets {
s.SampleOffsets[i].Start = (C.uint)(v.Start)
s.SampleOffsets[i].LoopStart = (C.ushort)(v.LoopStart)
s.SampleOffsets[i].LoopLength = (C.ushort)(v.LoopLength)
}
s.NumVoices = C.uint(comPatch.NumVoices)
s.Polyphony = C.uint(comPatch.PolyphonyBitmask)
s.RandSeed = 1
return s, nil
} }
// Render renders until the buffer is full or the modulated time is reached, whichever // Render renders until the buffer is full or the modulated time is reached, whichever
@ -101,131 +75,37 @@ func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) {
return int(samples), int(time), nil return int(samples), int(time), nil
} }
func Synth(patch go4k.Patch) (*C.Synth, error) { // Trigger is part of C.Synths' implementation of go4k.Synth interface
s := new(C.Synth)
totalVoices := 0
commands := make([]byte, 0)
values := make([]byte, 0)
polyphonyBitmask := 0
for insid, instr := range patch.Instruments {
if len(instr.Units) > 63 {
return nil, errors.New("An instrument can have a maximum of 63 units")
}
if instr.NumVoices < 1 {
return nil, errors.New("Each instrument must have at least 1 voice")
}
for unitid, unit := range instr.Units {
if val, ok := opcodeTable[unit.Type]; ok {
opCode := val.opcode
if unit.Parameters["stereo"] == 1 {
opCode++
}
commands = append(commands, byte(opCode))
for _, paramname := range val.parameterList {
if unit.Type == "delay" && paramname == "count" {
count := unit.Parameters["count"]*2 - 1
if unit.Parameters["notetracking"] == 1 {
count++
}
values = append(values, byte(count))
} else if pval, ok := unit.Parameters[paramname]; ok {
values = append(values, byte(pval))
} else {
return nil, fmt.Errorf("Unit parameter undefined: %v (at instrument %v, unit %v)", paramname, insid, unitid)
}
}
if unit.Type == "oscillator" {
flags := 0
switch unit.Parameters["type"] {
case go4k.Sine:
flags = 0x40
case go4k.Trisaw:
flags = 0x20
case go4k.Pulse:
flags = 0x10
case go4k.Gate:
flags = 0x04
case go4k.Sample:
flags = 0x80
}
if unit.Parameters["lfo"] == 1 {
flags += 0x08
}
flags += unit.Parameters["unison"]
values = append(values, byte(flags))
} else if unit.Type == "filter" {
flags := 0
if unit.Parameters["lowpass"] == 1 {
flags += 0x40
}
if unit.Parameters["bandpass"] == 1 {
flags += 0x20
}
if unit.Parameters["highpass"] == 1 {
flags += 0x10
}
if unit.Parameters["negbandpass"] == 1 {
flags += 0x08
}
if unit.Parameters["neghighpass"] == 1 {
flags += 0x04
}
values = append(values, byte(flags))
} else if unit.Type == "send" {
address := unit.Parameters["unit"]*16 + 24 + unit.Parameters["port"]
if unit.Parameters["voice"] > 0 {
address += 0x4000 + 16 + (unit.Parameters["voice"]-1)*1024 // global send, address is computed relative to synthworkspace
}
if unit.Parameters["sendpop"] == 1 {
address += 0x8000
}
values = append(values, byte(address&255), byte(address>>8))
}
} else {
return nil, fmt.Errorf("Unknown unit type: %v (at instrument %v, unit %v)", unit.Type, insid, unitid)
}
}
commands = append(commands, byte(C.su_advance_id))
totalVoices += instr.NumVoices
for k := 0; k < instr.NumVoices-1; k++ {
polyphonyBitmask = (polyphonyBitmask << 1) + 1
}
polyphonyBitmask <<= 1
}
if totalVoices > 32 {
return nil, errors.New("Sointu does not support more than 32 concurrent voices")
}
if len(commands) > 2048 { // TODO: 2048 could probably be pulled automatically from cgo
return nil, errors.New("The patch would result in more than 2048 commands")
}
if len(values) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo
return nil, errors.New("The patch would result in more than 16384 values")
}
for i := range commands {
s.Commands[i] = (C.uchar)(commands[i])
}
for i := range values {
s.Values[i] = (C.uchar)(values[i])
}
for i, deltime := range patch.DelayTimes {
s.DelayTimes[i] = (C.ushort)(deltime)
}
for i, samoff := range patch.SampleOffsets {
s.SampleOffsets[i].Start = (C.uint)(samoff.Start)
s.SampleOffsets[i].LoopStart = (C.ushort)(samoff.LoopStart)
s.SampleOffsets[i].LoopLength = (C.ushort)(samoff.LoopLength)
}
s.NumVoices = C.uint(totalVoices)
s.Polyphony = C.uint(polyphonyBitmask)
s.RandSeed = 1
return s, nil
}
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
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
} }
// Render error stores the exact errorcode, which is actually just the x87 FPU flags,
// with only the critical failure flags masked. Useful if you are interested exactly
// what went wrong with the patch.
type RenderError struct {
errcode int
}
func (e *RenderError) Error() string {
var reasons []string
if e.errcode&0x40 != 0 {
reasons = append(reasons, "FPU stack over/underflow")
}
if e.errcode&0x04 != 0 {
reasons = append(reasons, "FPU divide by zero")
}
if e.errcode&0x01 != 0 {
reasons = append(reasons, "FPU invalid operation")
}
if e.errcode&0x3800 != 0 {
reasons = append(reasons, "FPU stack push/pops are not balanced")
}
return "RenderError: " + strings.Join(reasons, ", ")
}

View File

@ -12,25 +12,14 @@ import (
"github.com/vsariola/sointu/go4k/bridge" "github.com/vsariola/sointu/go4k/bridge"
) )
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 TestBridge(t *testing.T) { func TestBridge(t *testing.T) {
patch := go4k.Patch{ patch := go4k.Patch{
Instruments: []go4k.Instrument{ Instruments: []go4k.Instrument{
go4k.Instrument{1, []go4k.Unit{ go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"envelope", 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": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 95, "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{"out", map[string]int{"stereo": 1, "gain": 128}}, go4k.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
}}}, }}}}
SampleOffsets: []go4k.SampleOffset{},
DelayTimes: []int{}}
synth, err := bridge.Synth(patch) synth, err := bridge.Synth(patch)
if err != nil { if err != nil {
@ -64,6 +53,7 @@ func TestBridge(t *testing.T) {
for i, v := range createdb { for i, v := range createdb {
if expectedb[i] != v { if expectedb[i] != v {
t.Errorf("byte mismatch @ %v, got %v, expected %v", i, v, expectedb[i]) t.Errorf("byte mismatch @ %v, got %v, expected %v", i, v, expectedb[i])
break
} }
} }
} }
@ -72,10 +62,8 @@ func TestStackUnderflow(t *testing.T) {
patch := go4k.Patch{ patch := go4k.Patch{
Instruments: []go4k.Instrument{ Instruments: []go4k.Instrument{
go4k.Instrument{1, []go4k.Unit{ go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
}}}, }}}}
SampleOffsets: []go4k.SampleOffset{},
DelayTimes: []int{}}
synth, err := bridge.Synth(patch) synth, err := bridge.Synth(patch)
if err != nil { if err != nil {
t.Fatalf("bridge compile error: %v", err) t.Fatalf("bridge compile error: %v", err)
@ -91,10 +79,8 @@ func TestStackBalancing(t *testing.T) {
patch := go4k.Patch{ patch := go4k.Patch{
Instruments: []go4k.Instrument{ Instruments: []go4k.Instrument{
go4k.Instrument{1, []go4k.Unit{ go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"push", map[string]int{}}, go4k.Unit{Type: "push", Parameters: map[string]int{}},
}}}, }}}}
SampleOffsets: []go4k.SampleOffset{},
DelayTimes: []int{}}
synth, err := bridge.Synth(patch) synth, err := bridge.Synth(patch)
if err != nil { if err != nil {
t.Fatalf("bridge compile error: %v", err) t.Fatalf("bridge compile error: %v", err)
@ -110,27 +96,25 @@ func TestStackOverflow(t *testing.T) {
patch := go4k.Patch{ patch := go4k.Patch{
Instruments: []go4k.Instrument{ Instruments: []go4k.Instrument{
go4k.Instrument{1, []go4k.Unit{ go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
}}}, }}}}
SampleOffsets: []go4k.SampleOffset{},
DelayTimes: []int{}}
synth, err := bridge.Synth(patch) synth, err := bridge.Synth(patch)
if err != nil { if err != nil {
t.Fatalf("bridge compile error: %v", err) t.Fatalf("bridge compile error: %v", err)
@ -146,12 +130,10 @@ func TestDivideByZero(t *testing.T) {
patch := go4k.Patch{ patch := go4k.Patch{
Instruments: []go4k.Instrument{ Instruments: []go4k.Instrument{
go4k.Instrument{1, []go4k.Unit{ go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"loadval", map[string]int{"value": 128}}, go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
go4k.Unit{"invgain", map[string]int{"invgain": 0}}, go4k.Unit{Type: "invgain", Parameters: map[string]int{"invgain": 0}},
go4k.Unit{"pop", map[string]int{}}, go4k.Unit{Type: "pop", Parameters: map[string]int{}},
}}}, }}}}
SampleOffsets: []go4k.SampleOffset{},
DelayTimes: []int{}}
synth, err := bridge.Synth(patch) synth, err := bridge.Synth(patch)
if err != nil { if err != nil {
t.Fatalf("bridge compile error: %v", err) t.Fatalf("bridge compile error: %v", err)

View File

@ -1,6 +0,0 @@
// +build !windows
package bridge
func Init() {
}

View File

@ -1,12 +0,0 @@
// +build windows
package bridge
// #cgo CFLAGS: -I"${SRCDIR}/../../include/sointu"
// #cgo LDFLAGS: "${SRCDIR}/../../build/libsointu.a"
// #include <sointu.h>
import "C"
func Init() {
C.su_load_gmdls() // GM.DLS is an windows specific sound bank so samples work currently only on windows
}

View File

@ -0,0 +1,8 @@
package bridge
// #include "sointu.h"
import "C"
func init() {
C.su_load_gmdls() // GM.DLS is an windows specific sound bank so samples work currently only on windows
}

View File

@ -1,4 +1,4 @@
package go4k_test package bridge_test
import ( import (
"bytes" "bytes"
@ -25,16 +25,14 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
func TestPlayer(t *testing.T) { func TestPlayer(t *testing.T) {
patch := go4k.Patch{ patch := go4k.Patch{
Instruments: []go4k.Instrument{go4k.Instrument{1, []go4k.Unit{ Instruments: []go4k.Instrument{go4k.Instrument{1, []go4k.Unit{
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
go4k.Unit{"oscillator", 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: "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{"mulp", map[string]int{"stereo": 0}}, go4k.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}},
go4k.Unit{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
go4k.Unit{"oscillator", 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: "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{"mulp", map[string]int{"stereo": 0}}, go4k.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}},
go4k.Unit{"out", map[string]int{"stereo": 1, "gain": 128}}, go4k.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
}}}, }}}}
DelayTimes: []int{},
SampleOffsets: []go4k.SampleOffset{}}
patterns := [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}} 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}}} tracks := []go4k.Track{go4k.Track{1, []byte{0}}}
song := go4k.Song{BPM: 100, Patterns: patterns, Tracks: tracks, Patch: patch, Output16Bit: false, Hold: 1} song := go4k.Song{BPM: 100, Patterns: patterns, Tracks: tracks, Patch: patch, Output16Bit: false, Hold: 1}
@ -47,7 +45,7 @@ func TestPlayer(t *testing.T) {
t.Fatalf("Render failed: %v", err) t.Fatalf("Render failed: %v", err)
} }
_, filename, _, _ := runtime.Caller(0) _, filename, _, _ := runtime.Caller(0)
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", "test_oscillat_sine.raw")) expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "tests", "expected_output", "test_oscillat_sine.raw"))
if err != nil { if err != nil {
t.Fatalf("cannot read expected: %v", err) t.Fatalf("cannot read expected: %v", err)
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu/go4k"
"github.com/vsariola/sointu/go4k/audio/oto" "github.com/vsariola/sointu/go4k/audio/oto"
"github.com/vsariola/sointu/go4k/bridge" "github.com/vsariola/sointu/go4k/bridge"
"github.com/vsariola/sointu/go4k/compiler"
) )
func main() { func main() {
@ -41,12 +42,21 @@ func main() {
flag.Usage() flag.Usage()
os.Exit(0) os.Exit(0)
} }
var comp *compiler.Compiler
if !*asmOut && !*jsonOut && !*rawOut && !*headerOut && !*play && !*yamlOut && *tmplDir == "" { if !*asmOut && !*jsonOut && !*rawOut && !*headerOut && !*play && !*yamlOut && *tmplDir == "" {
*play = true // if the user gives nothing to output, then the default behaviour is just to play the file *play = true // if the user gives nothing to output, then the default behaviour is just to play the file
} }
needsRendering := *play || *exactLength || *rawOut needsRendering := *play || *exactLength || *rawOut
if needsRendering { needsCompile := *headerOut || *asmOut
bridge.Init() if needsCompile {
var err error
comp, err = compiler.New()
if err != nil {
fmt.Fprintf(os.Stderr, `error creating compiler: %v`, err)
os.Exit(1)
}
comp.Amd64 = *targetArch == "amd64"
comp.OS = *targetOs
} }
process := func(filename string) error { process := func(filename string) error {
output := func(extension string, contents []byte) error { output := func(extension string, contents []byte) error {
@ -87,11 +97,7 @@ func main() {
var song go4k.Song var song go4k.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 {
song2, errAsm := go4k.ParseAsm(string(inputBytes)) return fmt.Errorf("The song could not be parsed as .json (%v) or .yml (%v)", errJSON, errYaml)
if errAsm != nil {
return fmt.Errorf("The song could not be parsed as .json (%v), .yml (%v) nor .asm (%v)", errJSON, errYaml, errAsm)
}
song = *song2
} }
} }
var buffer []float32 var buffer []float32
@ -121,23 +127,26 @@ func main() {
return fmt.Errorf("error updating the hold value of the song: %v", err) return fmt.Errorf("error updating the hold value of the song: %v", err)
} }
} }
if *headerOut { var compiledPlayer map[string]string
if needsCompile {
maxSamples := 0 // 0 means it is calculated automatically maxSamples := 0 // 0 means it is calculated automatically
if *exactLength { if *exactLength {
maxSamples = len(buffer) / 2 maxSamples = len(buffer) / 2
} }
header := go4k.CHeader(&song, maxSamples) var err error
if err := output(".h", []byte(header)); err != nil { compiledPlayer, err = comp.Player(&song, maxSamples)
if err != nil {
return fmt.Errorf("compiling player failed: %v", err)
}
}
if *headerOut {
if err := output(".h", []byte(compiledPlayer["h"])); err != nil {
return fmt.Errorf("Error outputting header file: %v", err) return fmt.Errorf("Error outputting header file: %v", err)
} }
} }
if *asmOut { if *asmOut {
asmCode, err := go4k.Compile(&song, *targetArch, *targetOs) if err := output(".asm", []byte(compiledPlayer["asm"])); err != nil {
if err != nil {
return fmt.Errorf("Could not format the song as asm file: %v", err)
}
if err := output(".asm", []byte(asmCode)); err != nil {
return fmt.Errorf("Error outputting asm file: %v", err) return fmt.Errorf("Error outputting asm file: %v", err)
} }
} }

81
go4k/compiler/compiler.go Normal file
View File

@ -0,0 +1,81 @@
package compiler
import (
"bytes"
"fmt"
"path"
"path/filepath"
"runtime"
"text/template"
"github.com/Masterminds/sprig"
"github.com/vsariola/sointu/go4k"
)
//go:generate go run generate.go
type Compiler struct {
Template *template.Template
Amd64 bool
OS string
DisableSections bool
}
// 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")
compiler, err := NewFromTemplates(templateDir)
return compiler, err
}
func NewFromTemplates(directory string) (*Compiler, error) {
globPtrn := filepath.Join(directory, "*.*")
tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(globPtrn)
if err != nil {
return nil, fmt.Errorf(`could not create template based on directory "%v": %v`, directory, err)
}
return &Compiler{Template: tmpl, Amd64: runtime.GOARCH == "amd64", OS: runtime.GOOS}, nil
}
func (com *Compiler) compile(templateName string, data interface{}) (string, error) {
result := bytes.NewBufferString("")
err := com.Template.ExecuteTemplate(result, templateName, data)
return result.String(), err
}
func (com *Compiler) Library() (map[string]string, error) {
features := AllFeatures{}
m := NewMacros(*com, features)
m.Library = true
asmCode, err := com.compile("library.asm", m)
if err != nil {
return nil, fmt.Errorf(`could not execute template "library.asm": %v`, err)
}
m = NewMacros(*com, features)
m.Library = true
header, err := com.compile("library.h", &m)
if err != nil {
return nil, fmt.Errorf(`could not execute template "library.h": %v`, err)
}
return map[string]string{"asm": asmCode, "h": header}, nil
}
func (com *Compiler) Player(song *go4k.Song, maxSamples int) (map[string]string, error) {
features := NecessaryFeaturesFor(song.Patch)
encodedPatch, err := Encode(&song.Patch, features)
if err != nil {
return nil, fmt.Errorf(`could not encode patch: %v`, err)
}
asmCode, err := com.compile("player.asm", NewPlayerMacros(*com, features, song, encodedPatch, maxSamples))
if err != nil {
return nil, fmt.Errorf(`could not execute template "player.asm": %v`, err)
}
header, err := com.compile("player.h", NewPlayerMacros(*com, features, song, encodedPatch, maxSamples))
if err != nil {
return nil, fmt.Errorf(`could not execute template "player.h": %v`, err)
}
return map[string]string{"asm": asmCode, "h": header}, nil
}

View File

@ -0,0 +1,144 @@
package compiler
import (
"errors"
"fmt"
"github.com/vsariola/sointu/go4k"
)
type EncodedPatch struct {
Commands []byte
Values []byte
DelayTimes []uint16
SampleOffsets []SampleOffset
PolyphonyBitmask uint32
NumVoices uint32
}
type SampleOffset struct {
Start uint32
LoopStart uint16
LoopLength uint16
}
func Encode(patch *go4k.Patch, featureSet FeatureSet) (*EncodedPatch, error) {
var c EncodedPatch
sampleOffsetMap := map[SampleOffset]int{}
for _, instr := range patch.Instruments {
if len(instr.Units) > 63 {
return nil, errors.New("An instrument can have a maximum of 63 units")
}
if instr.NumVoices < 1 {
return nil, errors.New("Each instrument must have at least 1 voice")
}
for _, unit := range instr.Units {
if unit.Type == "oscillator" && unit.Parameters["type"] == 4 {
s := SampleOffset{Start: uint32(unit.Parameters["start"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])}
index, ok := sampleOffsetMap[s]
if !ok {
index = len(c.SampleOffsets)
sampleOffsetMap[s] = index
c.SampleOffsets = append(c.SampleOffsets, s)
}
unit.Parameters["color"] = index
}
if unit.Type == "delay" {
unit.Parameters["delay"] = len(c.DelayTimes)
if unit.Parameters["stereo"] == 1 {
unit.Parameters["count"] = len(unit.VarArgs) / 2
} else {
unit.Parameters["count"] = len(unit.VarArgs)
}
for _, v := range unit.VarArgs {
c.DelayTimes = append(c.DelayTimes, uint16(v))
}
}
command, values, err := EncodeUnit(unit, featureSet)
if err != nil {
return nil, fmt.Errorf(`encoding unit failed: %v`, err)
}
c.Commands = append(c.Commands, command)
c.Values = append(c.Values, values...)
}
c.Commands = append(c.Commands, byte(0)) // advance
c.NumVoices += uint32(instr.NumVoices)
for k := 0; k < instr.NumVoices-1; k++ {
c.PolyphonyBitmask = (c.PolyphonyBitmask << 1) + 1
}
c.PolyphonyBitmask <<= 1
}
if c.NumVoices > 32 {
return nil, fmt.Errorf("Sointu does not support more than 32 concurrent voices; patch uses %v", c.NumVoices)
}
return &c, nil
}
func EncodeUnit(unit go4k.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] {
if v.CanModulate && v.CanSet {
values = append(values, byte(unit.Parameters[v.Name]))
}
}
if unit.Type == "aux" {
values = append(values, byte(unit.Parameters["channel"]))
} else if unit.Type == "in" {
values = append(values, byte(unit.Parameters["channel"]))
} else if unit.Type == "oscillator" {
flags := 0
switch unit.Parameters["type"] {
case go4k.Sine:
flags = 0x40
case go4k.Trisaw:
flags = 0x20
case go4k.Pulse:
flags = 0x10
case go4k.Gate:
flags = 0x04
case go4k.Sample:
flags = 0x80
}
if unit.Parameters["lfo"] == 1 {
flags += 0x08
}
flags += unit.Parameters["unison"]
values = append(values, byte(flags))
} else if unit.Type == "filter" {
flags := 0
if unit.Parameters["lowpass"] == 1 {
flags += 0x40
}
if unit.Parameters["bandpass"] == 1 {
flags += 0x20
}
if unit.Parameters["highpass"] == 1 {
flags += 0x10
}
if unit.Parameters["negbandpass"] == 1 {
flags += 0x08
}
if unit.Parameters["neghighpass"] == 1 {
flags += 0x04
}
values = append(values, byte(flags))
} else if unit.Type == "send" {
address := ((unit.Parameters["unit"] + 1) << 4) + unit.Parameters["port"] // each unit is 16 dwords, 8 workspace followed by 8 ports. +1 is for skipping the note/release/inputs
if unit.Parameters["voice"] > 0 {
address += 0x8000 + 16 + (unit.Parameters["voice"]-1)*1024 // global send, +16 is for skipping the out/aux ports
}
if unit.Parameters["sendpop"] == 1 {
address += 0x8
}
values = append(values, byte(address&255), byte(address>>8))
} else if unit.Type == "delay" {
countTrack := (unit.Parameters["count"] << 1) - 1 + unit.Parameters["notetracking"] // 1 means no note tracking and 1 delay, 2 means notetracking with 1 delay, 3 means no note tracking and 2 delays etc.
values = append(values, byte(unit.Parameters["delay"]), byte(countTrack))
}
return byte(opcode + unit.Parameters["stereo"]), values, nil
}

206
go4k/compiler/featureset.go Normal file
View File

@ -0,0 +1,206 @@
package compiler
import (
"sort"
"github.com/vsariola/sointu/go4k"
)
// FeatureSet defines what opcodes / parameters are included in the compiled virtual machine
// It is used by the compiler to decide how to encode opcodes
type FeatureSet interface {
Opcode(unitType string) (int, bool)
TransformCount(unitType string) int
Instructions() []string
InputNumber(unitType string, paramName string) int
SupportsParamValue(unitType string, paramName string, value int) bool
SupportsParamValueOtherThan(unitType string, paramName string, value int) bool
SupportsModulation(unitType string, paramName string) bool
SupportsPolyphony() bool
}
type Instruction struct {
Name string
TransformCount int
}
type paramKey struct {
Unit string
Param string
}
type paramValueKey struct {
Unit string
Param string
Value int
}
// AllFeatures is used by the library compilation / bridging to configure a virtual machine
// that supports every conceivable parameter, so it needs no members and just returns "true" to all
// queries about what it supports. Contrast this NecessaryFeatures that only returns true if the patch
// needs support for that feature
type AllFeatures struct {
}
func (_ AllFeatures) SupportsParamValue(unit string, paramName string, value int) bool {
return true
}
func (_ AllFeatures) SupportsParamValueOtherThan(unit string, paramName string, value int) bool {
return true
}
func (_ AllFeatures) SupportsModulation(unit string, port string) bool {
return true
}
func (_ AllFeatures) SupportsPolyphony() bool {
return true
}
func (_ AllFeatures) Opcode(unitType string) (int, bool) {
code, ok := allOpcodes[unitType]
return code, ok
}
func (_ AllFeatures) TransformCount(unitType string) int {
return allTransformCounts[unitType]
}
func (_ AllFeatures) Instructions() []string {
return allInstructions
}
func (_ AllFeatures) InputNumber(unitType string, paramName string) int {
return allInputs[paramKey{unitType, paramName}]
}
var allOpcodes map[string]int
var allInstructions []string
var allInputs map[paramKey]int
var allTransformCounts map[string]int
func init() {
allInstructions = make([]string, len(go4k.UnitTypes))
allOpcodes = map[string]int{}
allTransformCounts = map[string]int{}
allInputs = map[paramKey]int{}
i := 0
for k, v := range go4k.UnitTypes {
inputCount := 0
transformCount := 0
for _, t := range v {
if t.CanModulate {
allInputs[paramKey{k, t.Name}] = inputCount
inputCount++
}
if t.CanModulate && t.CanSet {
transformCount++
}
}
allInstructions[i] = k // Opcode 0 is reserved for instrument advance, so opcodes start from 1
allTransformCounts[k] = transformCount
i++
}
sort.Strings(allInstructions) // sort the opcodes to have predictable ordering, as maps don't guarantee the order the items
for i, instruction := range allInstructions {
allOpcodes[instruction] = (i + 1) * 2 // make a map to find out the opcode number based on the type
}
}
// NecessaryFeatures returns true only if the patch actually needs the support for the feature
type NecessaryFeatures struct {
opcodes map[string]int
instructions []string
supportsParamValue map[paramKey](map[int]bool)
supportsModulation map[paramKey]bool
polyphony bool
}
func NecessaryFeaturesFor(patch go4k.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 {
if _, ok := features.opcodes[unit.Type]; !ok {
features.instructions = append(features.instructions, unit.Type)
features.opcodes[unit.Type] = len(features.instructions) * 2 // note that the first opcode gets value 1, as 0 is always reserved for advance
}
for k, v := range unit.Parameters {
key := paramKey{unit.Type, k}
if features.supportsParamValue[key] == nil {
features.supportsParamValue[key] = map[int]bool{}
}
features.supportsParamValue[key][v] = true
}
if unit.Type == "send" {
targetInstrument := instrNo
if unit.Parameters["voice"] > 0 {
v, err := patch.InstrumentForVoice(unit.Parameters["voice"] - 1)
if err != nil {
continue
}
targetInstrument = v
}
if unit.Parameters["unit"] < 0 || unit.Parameters["unit"] >= len(patch.Instruments[targetInstrument].Units) {
continue // send is modulating outside the range of the target instrument; probably a bug in patch, but at least it's not modulating the uniType we're after
}
targetUnit := patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]]
modulatedPortNo := 0
for _, v := range go4k.UnitTypes[targetUnit.Type] {
if v.CanModulate {
if modulatedPortNo == unit.Parameters["port"] {
features.supportsModulation[paramKey{targetUnit.Type, v.Name}] = true
}
modulatedPortNo++
}
}
}
}
if instrument.NumVoices > 1 {
features.polyphony = true
}
}
return features
}
func (n NecessaryFeatures) SupportsParamValue(unit string, paramName string, value int) bool {
m, ok := n.supportsParamValue[paramKey{unit, paramName}]
if !ok {
return false
}
return m[value]
}
func (n NecessaryFeatures) SupportsParamValueOtherThan(unit string, paramName string, value int) bool {
for paramValue := range n.supportsParamValue[paramKey{unit, paramName}] {
if paramValue != value {
return true
}
}
return false
}
func (n NecessaryFeatures) SupportsModulation(unit string, param string) bool {
return n.supportsModulation[paramKey{unit, param}]
}
func (n NecessaryFeatures) SupportsPolyphony() bool {
return n.polyphony
}
func (n NecessaryFeatures) Opcode(unitType string) (int, bool) {
code, ok := n.opcodes[unitType]
return code, ok
}
func (n NecessaryFeatures) Instructions() []string {
return n.instructions
}
func (n NecessaryFeatures) InputNumber(unitType string, paramName string) int {
return allInputs[paramKey{unitType, paramName}]
}
func (_ NecessaryFeatures) TransformCount(unitType string) int {
return allTransformCounts[unitType]
}

61
go4k/compiler/generate.go Normal file
View File

@ -0,0 +1,61 @@
// The following directive is necessary to make the package coherent:
// +build ignore
// This program generates the library headers and assembly files for the library
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"github.com/vsariola/sointu/go4k/compiler"
)
func main() {
targetArch := flag.String("arch", runtime.GOARCH, "Target architecture. Defaults to Go architecture. Possible values: amd64, 386 (anything else is assumed 386)")
targetOs := flag.String("os", runtime.GOOS, "Target OS. Defaults to current Go OS. Possible values: windows, darwin, linux (anything else is assumed linux)")
flag.Usage = printUsage
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
os.Exit(0)
}
comp, err := compiler.New()
if err != nil {
fmt.Fprintf(os.Stderr, `error creating compiler: %v`, err)
os.Exit(1)
}
comp.Amd64 = *targetArch == "amd64"
comp.OS = *targetOs
library, err := comp.Library()
if err != nil {
fmt.Fprintf(os.Stderr, `error compiling library: %v`, err)
os.Exit(1)
}
filenames := map[string]string{"h": "sointu.h", "asm": "sointu.asm"}
for t, contents := range library {
filename := filenames[t]
err := ioutil.WriteFile(filepath.Join(flag.Args()[0], filename), []byte(contents), os.ModePerm)
if err != nil {
fmt.Fprintf(os.Stderr, `could not write to file "%v": %v`, filename, err)
os.Exit(1)
}
}
os.Exit(0)
}
func printUsage() {
fmt.Fprintf(os.Stderr, "Sointu command line utility for generating the library .asm and .h files.\nUsage: %s [flags] outputDirectory\n", os.Args[0])
flag.PrintDefaults()
}

View File

@ -1,16 +1,11 @@
package go4k package compiler
import ( import (
"bytes"
"fmt" "fmt"
"math" "math"
"path"
"path/filepath"
"runtime"
"strings" "strings"
"text/template"
"github.com/Masterminds/sprig" "github.com/vsariola/sointu/go4k"
) )
type OplistEntry struct { type OplistEntry struct {
@ -19,141 +14,56 @@ type OplistEntry struct {
} }
type Macros struct { type Macros struct {
Opcodes []OplistEntry Stacklocs []string
Polyphony bool Output16Bit bool
MultivoiceTracks bool Clip bool
PolyphonyBitmask int Library bool
Stacklocs []string Sine int // TODO: how can we elegantly access global constants in template, without wrapping each one by one
Output16Bit bool Trisaw int
Clip bool Pulse int
Amd64 bool Gate int
OS string Sample int
DisableSections bool usesFloatConst map[float32]bool
Sine int // TODO: how can we elegantly access global constants in template, without wrapping each one by one usesIntConst map[int]bool
Trisaw int floatConsts []float32
Pulse int intConsts []int
Gate int calls map[string]bool
Sample int stackframes map[string][]string
usesFloatConst map[float32]bool FeatureSet
usesIntConst map[int]bool Compiler
floatConsts []float32
intConsts []int
calls map[string]bool
stereo map[string]bool
mono map[string]bool
ops map[string]bool
stackframes map[string][]string
unitInputMap map[string](map[string]int)
} }
type PlayerMacros struct { func NewMacros(c Compiler, f FeatureSet) *Macros {
Song *Song return &Macros{
VoiceTrackBitmask int calls: map[string]bool{},
JumpTable []string usesFloatConst: map[float32]bool{},
Code []byte usesIntConst: map[int]bool{},
Values []byte stackframes: map[string][]string{},
Macros Sine: go4k.Sine,
} Trisaw: go4k.Trisaw,
Pulse: go4k.Pulse,
func NewPlayerMacros(song *Song, targetArch string, targetOS string) *PlayerMacros { Gate: go4k.Gate,
unitInputMap := map[string](map[string]int){} Sample: go4k.Sample,
for k, v := range UnitTypes { Compiler: c,
inputMap := map[string]int{} FeatureSet: f,
inputCount := 0
for _, t := range v {
if t.CanModulate {
inputMap[t.Name] = inputCount
inputCount++
}
}
unitInputMap[k] = inputMap
} }
jumpTable, code, values := song.Patch.Encode()
amd64 := targetArch == "amd64"
p := &PlayerMacros{
Song: song,
JumpTable: jumpTable,
Code: code,
Values: values,
Macros: Macros{
mono: map[string]bool{},
stereo: map[string]bool{},
calls: map[string]bool{},
ops: map[string]bool{},
usesFloatConst: map[float32]bool{},
usesIntConst: map[int]bool{},
stackframes: map[string][]string{},
unitInputMap: unitInputMap,
Amd64: amd64,
OS: targetOS,
Sine: Sine,
Trisaw: Trisaw,
Pulse: Pulse,
Gate: Gate,
Sample: Sample,
}}
for _, track := range song.Tracks {
if track.NumVoices > 1 {
p.MultivoiceTracks = true
}
}
trackVoiceNumber := 0
for _, t := range song.Tracks {
for b := 0; b < t.NumVoices-1; b++ {
p.VoiceTrackBitmask += 1 << trackVoiceNumber
trackVoiceNumber++
}
trackVoiceNumber++ // set all bits except last one
}
totalVoices := 0
for _, instr := range song.Patch.Instruments {
if instr.NumVoices > 1 {
p.Polyphony = true
}
for _, unit := range instr.Units {
if !p.ops[unit.Type] {
p.ops[unit.Type] = true
numParams := 0
for _, v := range UnitTypes[unit.Type] {
if v.CanSet && v.CanModulate {
numParams++
}
}
p.Opcodes = append(p.Opcodes, OplistEntry{
Type: unit.Type,
NumParams: numParams,
})
}
if unit.Parameters["stereo"] == 1 {
p.stereo[unit.Type] = true
} else {
p.mono[unit.Type] = true
}
}
totalVoices += instr.NumVoices
for k := 0; k < instr.NumVoices-1; k++ {
p.PolyphonyBitmask = (p.PolyphonyBitmask << 1) + 1
}
p.PolyphonyBitmask <<= 1
}
p.Output16Bit = song.Output16Bit
return p
} }
func (p *Macros) Opcode(t string) bool { func (p *Macros) HasOp(instruction string) bool {
return p.ops[t] _, ok := p.Opcode(instruction)
return ok
} }
func (p *Macros) Stereo(t string) bool { func (p *Macros) Stereo(unitType string) bool {
return p.stereo[t] return p.SupportsParamValue(unitType, "stereo", 1)
} }
func (p *Macros) Mono(t string) bool { func (p *Macros) Mono(unitType string) bool {
return p.mono[t] return p.SupportsParamValue(unitType, "stereo", 0)
} }
func (p *Macros) StereoAndMono(t string) bool { func (p *Macros) StereoAndMono(unitType string) bool {
return p.stereo[t] && p.mono[t] return p.Stereo(unitType) && p.Mono(unitType)
} }
// Macros and functions to accumulate constants automagically // Macros and functions to accumulate constants automagically
@ -431,6 +341,22 @@ func (p *Macros) Pop(register string) string {
return fmt.Sprintf("pop %v ; %v = %v, Stack: %v ", register, register, last, p.FmtStack()) return fmt.Sprintf("pop %v ; %v = %v, Stack: %v ", register, register, last, p.FmtStack())
} }
func (p *Macros) SaveFPUState() string {
i := 0
for ; i < 108; i += p.PTRSIZE() {
p.Stacklocs = append(p.Stacklocs, fmt.Sprintf("F%v", i))
}
return fmt.Sprintf("sub %[1]v, %[2]v\nfsave [%[1]v]", p.SP(), i)
}
func (p *Macros) LoadFPUState() string {
i := 0
for ; i < 108; i += p.PTRSIZE() {
p.Stacklocs = p.Stacklocs[:len(p.Stacklocs)-1]
}
return fmt.Sprintf("frstor [%[1]v]\nadd %[1]v, %[2]v", p.SP(), i)
}
func (p *Macros) Stack(name string) (string, error) { func (p *Macros) Stack(name string) (string, error) {
for i, k := range p.Stacklocs { for i, k := range p.Stacklocs {
if k == name { if k == name {
@ -463,7 +389,11 @@ func (p *Macros) FmtStack() string {
func (p *Macros) ExportFunc(name string, params ...string) string { func (p *Macros) ExportFunc(name string, params ...string) string {
if !p.Amd64 { if !p.Amd64 {
p.Stacklocs = append(params, "retaddr_"+name) // in 32-bit, we use stdcall and parameters are in the stack reverseParams := make([]string, len(params))
for i, param := range params {
reverseParams[len(params)-1-i] = param
}
p.Stacklocs = append(reverseParams, "retaddr_"+name) // in 32-bit, we use stdcall and parameters are in the stack
if p.OS == "windows" { if p.OS == "windows" {
return fmt.Sprintf("%[1]v\nglobal _%[2]v@%[3]v\n_%[2]v@%[3]v:", p.SectText(name), name, len(params)*4) return fmt.Sprintf("%[1]v\nglobal _%[2]v@%[3]v\n_%[2]v@%[3]v:", p.SectText(name), name, len(params)*4)
} }
@ -474,54 +404,16 @@ func (p *Macros) ExportFunc(name string, params ...string) string {
return fmt.Sprintf("%[1]v\nglobal %[2]v\n%[2]v:", p.SectText(name), name) return fmt.Sprintf("%[1]v\nglobal %[2]v\n%[2]v:", p.SectText(name), name)
} }
func (p *Macros) Count(count int) []int {
s := make([]int, count)
for i := range s {
s[i] = i
}
return s
}
func (p *Macros) Sub(a int, b int) int {
return a - b
}
func (p *Macros) Input(unit string, port string) (string, error) { func (p *Macros) Input(unit string, port string) (string, error) {
umap, ok := p.unitInputMap[unit] i := p.InputNumber(unit, port)
if !ok {
return "", fmt.Errorf(`trying to find input for unknown unit "%v"`, unit)
}
i, ok := umap[port]
if !ok {
return "", fmt.Errorf(`trying to find input for unknown input "%v" for unit "%v"`, port, unit)
}
if i != 0 { if i != 0 {
return fmt.Sprintf("%v + %v", p.INP(), i*4), nil return fmt.Sprintf("%v + %v", p.INP(), i*4), nil
} }
return p.INP(), nil return p.INP(), nil
} }
func (p *Macros) InputNumber(unit string, port string) (string, error) {
umap, ok := p.unitInputMap[unit]
if !ok {
return "", fmt.Errorf(`trying to find InputNumber for unknown unit "%v"`, unit)
}
i, ok := umap[port]
if !ok {
return "", fmt.Errorf(`trying to find InputNumber for unknown input "%v" for unit "%v"`, port, unit)
}
return fmt.Sprintf("%v", i), nil
}
func (p *Macros) Modulation(unit string, port string) (string, error) { func (p *Macros) Modulation(unit string, port string) (string, error) {
umap, ok := p.unitInputMap[unit] i := p.InputNumber(unit, port)
if !ok {
return "", fmt.Errorf(`trying to find input for unknown unit "%v"`, unit)
}
i, ok := umap[port]
if !ok {
return "", fmt.Errorf(`trying to find input for unknown input "%v" for unit "%v"`, port, unit)
}
return fmt.Sprintf("%v + %v", p.WRK(), i*4+32), nil return fmt.Sprintf("%v + %v", p.WRK(), i*4+32), nil
} }
@ -549,6 +441,32 @@ func (p *Macros) Use(value string, regs ...string) (string, error) {
return value, nil return value, nil
} }
type PlayerMacros struct {
Song *go4k.Song
VoiceTrackBitmask int
MaxSamples int
Macros
EncodedPatch
}
func NewPlayerMacros(c Compiler, f FeatureSet, s *go4k.Song, e *EncodedPatch, maxSamples int) *PlayerMacros {
if maxSamples == 0 {
maxSamples = s.SamplesPerRow() * s.TotalRows()
}
macros := *NewMacros(c, f)
macros.Output16Bit = s.Output16Bit // TODO: should we actually store output16bit in Songs or not?
p := PlayerMacros{Song: s, Macros: macros, MaxSamples: maxSamples, EncodedPatch: *e}
trackVoiceNumber := 0
for _, t := range s.Tracks {
for b := 0; b < t.NumVoices-1; b++ {
p.VoiceTrackBitmask += 1 << trackVoiceNumber
trackVoiceNumber++
}
trackVoiceNumber++ // set all bits except last one
}
return &p
}
func (p *PlayerMacros) NumDelayLines() string { func (p *PlayerMacros) NumDelayLines() string {
total := 0 total := 0
for _, instr := range p.Song.Patch.Instruments { for _, instr := range p.Song.Patch.Instruments {
@ -560,68 +478,3 @@ func (p *PlayerMacros) NumDelayLines() string {
} }
return fmt.Sprintf("%v", total) return fmt.Sprintf("%v", total)
} }
func (p *PlayerMacros) UsesDelayModulation() (bool, error) {
for i, instrument := range p.Song.Patch.Instruments {
for j, unit := range instrument.Units {
if unit.Type == "send" {
targetInstrument := i
if unit.Parameters["voice"] > 0 {
v, err := p.Song.Patch.InstrumentForVoice(unit.Parameters["voice"] - 1)
if err != nil {
return false, fmt.Errorf("INSTRUMENT #%v / SEND #%v targets voice %v, which does not exist", i, j, unit.Parameters["voice"])
}
targetInstrument = v
}
if unit.Parameters["unit"] < 0 || unit.Parameters["unit"] >= len(p.Song.Patch.Instruments[targetInstrument].Units) {
return false, fmt.Errorf("INSTRUMENT #%v / SEND #%v target unit %v out of range", i, j, unit.Parameters["unit"])
}
if p.Song.Patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]].Type == "delay" && unit.Parameters["port"] == 4 {
return true, nil
}
}
}
}
return false, nil
}
func (p *PlayerMacros) HasParamValue(unitType string, paramName string, value int) bool {
for _, instr := range p.Song.Patch.Instruments {
for _, unit := range instr.Units {
if unit.Type == unitType {
if unit.Parameters[paramName] == value {
return true
}
}
}
}
return false
}
func (p *PlayerMacros) HasParamValueOtherThan(unitType string, paramName string, value int) bool {
for _, instr := range p.Song.Patch.Instruments {
for _, unit := range instr.Units {
if unit.Type == unitType {
if unit.Parameters[paramName] != value {
return true
}
}
}
}
return false
}
func Compile(song *Song, targetArch string, targetOs string) (string, error) {
_, myname, _, _ := runtime.Caller(0)
templateDir := filepath.Join(path.Dir(myname), "..", "templates", "*.asm")
tmpl, err := template.New("base").Funcs(sprig.TxtFuncMap()).ParseGlob(templateDir)
if err != nil {
return "", fmt.Errorf(`could not create template based on dir "%v": %v`, templateDir, err)
}
b := bytes.NewBufferString("")
err = tmpl.ExecuteTemplate(b, "player.asm", NewPlayerMacros(song, targetArch, targetOs))
if err != nil {
return "", fmt.Errorf(`could not execute template "player.asm": %v`, err)
}
return b.String(), nil
}

View File

@ -2,6 +2,7 @@ package go4k
import ( import (
"errors" "errors"
"fmt"
"math" "math"
) )
@ -9,6 +10,7 @@ import (
type Unit struct { type Unit struct {
Type string Type string
Parameters map[string]int `yaml:",flow"` Parameters map[string]int `yaml:",flow"`
VarArgs []int
} }
const ( const (
@ -25,17 +27,9 @@ type Instrument struct {
Units []Unit Units []Unit
} }
type SampleOffset struct {
Start int
LoopStart int
LoopLength int
}
// Patch is simply a list of instruments used in a song // Patch is simply a list of instruments used in a song
type Patch struct { type Patch struct {
Instruments []Instrument Instruments []Instrument
DelayTimes []int `yaml:",flow"`
SampleOffsets []SampleOffset
} }
func (p Patch) TotalVoices() int { func (p Patch) TotalVoices() int {
@ -73,30 +67,13 @@ 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 {
return fmt.Errorf("go4k.Render failed: %v", err)
}
if s != len(buffer)/2 { if s != len(buffer)/2 {
return errors.New("synth.Render should have filled the whole buffer") return errors.New("in go4k.Render, synth.Render should have filled the whole buffer but did not")
} }
return err return nil
}
func (p *Patch) Encode() ([]string, []byte, []byte) {
var code []byte
var values []byte
var jumpTable []string
assignedIds := map[string]byte{}
for _, instr := range p.Instruments {
for _, unit := range instr.Units {
if _, ok := assignedIds[unit.Type]; !ok {
jumpTable = append(jumpTable, unit.Type)
assignedIds[unit.Type] = byte(len(jumpTable) * 2)
}
stereo, unitValues := Encode(unit)
code = append(code, stereo+assignedIds[unit.Type])
values = append(values, unitValues...)
}
code = append(code, 0)
}
return jumpTable, code, values
} }
// UnitParameter documents one parameter that an unit takes // UnitParameter documents one parameter that an unit takes
@ -108,70 +85,6 @@ type UnitParameter struct {
CanModulate bool // if this parameter can be modulated i.e. has a port number in "send" unit CanModulate bool // if this parameter can be modulated i.e. has a port number in "send" unit
} }
func Encode(unit Unit) (byte, []byte) {
var values []byte
for _, v := range UnitTypes[unit.Type] {
if v.CanSet && v.CanModulate {
values = append(values, byte(unit.Parameters[v.Name]))
}
}
if unit.Type == "aux" {
values = append(values, byte(unit.Parameters["channel"]))
} else if unit.Type == "in" {
values = append(values, byte(unit.Parameters["channel"]))
} else if unit.Type == "oscillator" {
flags := 0
switch unit.Parameters["type"] {
case Sine:
flags = 0x40
case Trisaw:
flags = 0x20
case Pulse:
flags = 0x10
case Gate:
flags = 0x04
case Sample:
flags = 0x80
}
if unit.Parameters["lfo"] == 1 {
flags += 0x08
}
flags += unit.Parameters["unison"]
values = append(values, byte(flags))
} else if unit.Type == "filter" {
flags := 0
if unit.Parameters["lowpass"] == 1 {
flags += 0x40
}
if unit.Parameters["bandpass"] == 1 {
flags += 0x20
}
if unit.Parameters["highpass"] == 1 {
flags += 0x10
}
if unit.Parameters["negbandpass"] == 1 {
flags += 0x08
}
if unit.Parameters["neghighpass"] == 1 {
flags += 0x04
}
values = append(values, byte(flags))
} else if unit.Type == "send" {
address := ((unit.Parameters["unit"] + 1) << 4) + unit.Parameters["port"] // each unit is 16 dwords, 8 workspace followed by 8 ports. +1 is for skipping the note/release/inputs
if unit.Parameters["voice"] > 0 {
address += 0x8000 + 16 + (unit.Parameters["voice"]-1)*1024 // global send, +16 is for skipping the out/aux ports
}
if unit.Parameters["sendpop"] == 1 {
address += 0x8
}
values = append(values, byte(address&255), byte(address>>8))
} else if unit.Type == "delay" {
countTrack := (unit.Parameters["count"] << 1) - 1 + unit.Parameters["notetracking"] // 1 means no note tracking and 1 delay, 2 means notetracking with 1 delay, 3 means no note tracking and 2 delays etc.
values = append(values, byte(unit.Parameters["delay"]), byte(countTrack))
}
return byte(unit.Parameters["stereo"]), values
}
// UnitTypes documents all the available unit types and if they support stereo variant // UnitTypes documents all the available unit types and if they support stereo variant
// and what parameters they take. // and what parameters they take.
var UnitTypes = map[string]([]UnitParameter){ var UnitTypes = map[string]([]UnitParameter){
@ -218,8 +131,6 @@ var UnitTypes = map[string]([]UnitParameter){
{Name: "feedback", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, {Name: "feedback", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
{Name: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, {Name: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
{Name: "notetracking", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, {Name: "notetracking", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
{Name: "delay", MinValue: 0, MaxValue: 255, CanSet: true, CanModulate: false},
{Name: "count", MinValue: 0, MaxValue: 255, CanSet: true, CanModulate: false},
{Name: "delaytime", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}}, {Name: "delaytime", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}},
"compressor": []UnitParameter{ "compressor": []UnitParameter{
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
@ -268,7 +179,10 @@ var UnitTypes = map[string]([]UnitParameter){
{Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
{Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false}, {Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false},
{Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, {Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
{Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false}}, {Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false},
{Name: "samplestart", MinValue: 0, MaxValue: 3440659, CanSet: true, CanModulate: false},
{Name: "loopstart", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false},
{Name: "looplength", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}},
"loadval": []UnitParameter{ "loadval": []UnitParameter{
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
{Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, {Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}},
@ -280,3 +194,133 @@ var UnitTypes = map[string]([]UnitParameter){
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
{Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false}}, {Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false}},
} }
type Song struct {
BPM int
Output16Bit bool
Hold byte
Patterns [][]byte `yaml:",flow"`
Tracks []Track
Patch Patch
}
func (s *Song) PatternRows() int {
return len(s.Patterns[0])
}
func (s *Song) SequenceLength() int {
return len(s.Tracks[0].Sequence)
}
func (s *Song) TotalRows() int {
return s.PatternRows() * s.SequenceLength()
}
func (s *Song) SamplesPerRow() int {
return 44100 * 60 / (s.BPM * 4)
}
func (s *Song) FirstTrackVoice(track int) int {
ret := 0
for _, t := range s.Tracks[:track] {
ret += t.NumVoices
}
return ret
}
// TBD: Where shall we put methods that work on pure domain types and have no dependencies
// e.g. Validate here
func (s *Song) Validate() error {
if s.BPM < 1 {
return errors.New("BPM should be > 0")
}
for i := range s.Patterns[:len(s.Patterns)-1] {
if len(s.Patterns[i]) != len(s.Patterns[i+1]) {
return errors.New("Every pattern should have the same length")
}
}
for i := range s.Tracks[:len(s.Tracks)-1] {
if len(s.Tracks[i].Sequence) != len(s.Tracks[i+1].Sequence) {
return errors.New("Every track should have the same sequence length")
}
}
totalTrackVoices := 0
for _, track := range s.Tracks {
totalTrackVoices += track.NumVoices
for _, p := range track.Sequence {
if p < 0 || int(p) >= len(s.Patterns) {
return errors.New("Tracks use a non-existing pattern")
}
}
}
if totalTrackVoices > s.Patch.TotalVoices() {
return errors.New("Tracks use too many voices")
}
return nil
}
func Play(synth Synth, song Song) ([]float32, error) {
err := song.Validate()
if err != nil {
return nil, err
}
curVoices := make([]int, len(song.Tracks))
for i := range curVoices {
curVoices[i] = song.FirstTrackVoice(i)
}
initialCapacity := song.TotalRows() * song.SamplesPerRow() * 2
buffer := make([]float32, 0, initialCapacity)
rowbuffer := make([]float32, song.SamplesPerRow()*2)
for row := 0; row < song.TotalRows(); row++ {
patternRow := row % song.PatternRows()
pattern := row / song.PatternRows()
for t := range song.Tracks {
patternIndex := song.Tracks[t].Sequence[pattern]
note := song.Patterns[patternIndex][patternRow]
if note > 0 && note <= song.Hold { // anything but hold causes an action.
continue
}
synth.Release(curVoices[t])
if note > song.Hold {
curVoices[t]++
first := song.FirstTrackVoice(t)
if curVoices[t] >= first+song.Tracks[t].NumVoices {
curVoices[t] = first
}
synth.Trigger(curVoices[t], note)
}
}
tries := 0
for rowtime := 0; rowtime < song.SamplesPerRow(); {
samples, time, _ := synth.Render(rowbuffer, song.SamplesPerRow()-rowtime)
rowtime += time
buffer = append(buffer, rowbuffer[:samples*2]...)
if tries > 100 {
return nil, fmt.Errorf("Song speed modulation likely so slow that row never advances; error at pattern %v, row %v", pattern, patternRow)
}
}
}
return buffer, nil
}
func (s *Song) UpdateHold(newHold byte) error {
if newHold == 0 {
return errors.New("hold value cannot be 0, 0 is reserved for release")
}
for _, pat := range s.Patterns {
for _, v := range pat {
if v > s.Hold && v <= newHold {
return errors.New("song uses note values greater or equal to the new hold value")
}
}
}
for _, pat := range s.Patterns {
for i, v := range pat {
if v > 0 && v <= s.Hold {
pat[i] = newHold
}
}
}
s.Hold = newHold
return nil
}

View File

@ -1,136 +0,0 @@
package go4k
import (
"errors"
"fmt"
)
type Song struct {
BPM int
Output16Bit bool
Hold byte
Patterns [][]byte `yaml:",flow"`
Tracks []Track
Patch Patch
}
func (s *Song) PatternRows() int {
return len(s.Patterns[0])
}
func (s *Song) SequenceLength() int {
return len(s.Tracks[0].Sequence)
}
func (s *Song) TotalRows() int {
return s.PatternRows() * s.SequenceLength()
}
func (s *Song) SamplesPerRow() int {
return 44100 * 60 / (s.BPM * 4)
}
func (s *Song) FirstTrackVoice(track int) int {
ret := 0
for _, t := range s.Tracks[:track] {
ret += t.NumVoices
}
return ret
}
// TBD: Where shall we put methods that work on pure domain types and have no dependencies
// e.g. Validate here
func (s *Song) Validate() error {
if s.BPM < 1 {
return errors.New("BPM should be > 0")
}
for i := range s.Patterns[:len(s.Patterns)-1] {
if len(s.Patterns[i]) != len(s.Patterns[i+1]) {
return errors.New("Every pattern should have the same length")
}
}
for i := range s.Tracks[:len(s.Tracks)-1] {
if len(s.Tracks[i].Sequence) != len(s.Tracks[i+1].Sequence) {
return errors.New("Every track should have the same sequence length")
}
}
totalTrackVoices := 0
for _, track := range s.Tracks {
totalTrackVoices += track.NumVoices
for _, p := range track.Sequence {
if p < 0 || int(p) >= len(s.Patterns) {
return errors.New("Tracks use a non-existing pattern")
}
}
}
if totalTrackVoices > s.Patch.TotalVoices() {
return errors.New("Tracks use too many voices")
}
return nil
}
func Play(synth Synth, song Song) ([]float32, error) {
err := song.Validate()
if err != nil {
return nil, err
}
curVoices := make([]int, len(song.Tracks))
for i := range curVoices {
curVoices[i] = song.FirstTrackVoice(i)
}
initialCapacity := song.TotalRows() * song.SamplesPerRow() * 2
buffer := make([]float32, 0, initialCapacity)
rowbuffer := make([]float32, song.SamplesPerRow()*2)
for row := 0; row < song.TotalRows(); row++ {
patternRow := row % song.PatternRows()
pattern := row / song.PatternRows()
for t := range song.Tracks {
patternIndex := song.Tracks[t].Sequence[pattern]
note := song.Patterns[patternIndex][patternRow]
if note > 0 && note <= song.Hold { // anything but hold causes an action.
continue
}
synth.Release(curVoices[t])
if note > song.Hold {
curVoices[t]++
first := song.FirstTrackVoice(t)
if curVoices[t] >= first+song.Tracks[t].NumVoices {
curVoices[t] = first
}
synth.Trigger(curVoices[t], note)
}
}
tries := 0
for rowtime := 0; rowtime < song.SamplesPerRow(); {
samples, time, _ := synth.Render(rowbuffer, song.SamplesPerRow()-rowtime)
rowtime += time
buffer = append(buffer, rowbuffer[:samples*2]...)
if tries > 100 {
return nil, fmt.Errorf("Song speed modulation likely so slow that row never advances; error at pattern %v, row %v", pattern, patternRow)
}
}
}
return buffer, nil
}
func (s *Song) UpdateHold(newHold byte) error {
if newHold == 0 {
return errors.New("hold value cannot be 0, 0 is reserved for release")
}
for _, pat := range s.Patterns {
for _, v := range pat {
if v > s.Hold && v <= newHold {
return errors.New("song uses note values greater or equal to the new hold value")
}
}
}
for _, pat := range s.Patterns {
for i, v := range pat {
if v > 0 && v <= s.Hold {
pat[i] = newHold
}
}
}
s.Hold = newHold
return nil
}

View File

@ -1,54 +0,0 @@
package go4k_test
import (
"encoding/json"
"reflect"
"testing"
"github.com/vsariola/sointu/go4k"
)
const expectedMarshaled = `{"BPM":100,"Output16Bit":false,"Hold":1,"Patterns":["QABEACAAAABLAE4AAAAAAA=="],"Tracks":[{"NumVoices":1,"Sequence":"AA=="}],"Patch":{"Instruments":[{"NumVoices":1,"Units":[{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":96,"detune":64,"flags":64,"gain":128,"phase":0,"shape":64,"stereo":0,"transpose":64}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":64,"detune":64,"flags":64,"gain":128,"phase":64,"shape":96,"stereo":0,"transpose":72}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"out","Parameters":{"gain":128,"stereo":1}}]}],"DelayTimes":[],"SampleOffsets":[]}}`
var testSong = go4k.Song{
BPM: 100,
Patterns: [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}},
Tracks: []go4k.Track{
{NumVoices: 1, Sequence: []byte{0}},
},
Patch: go4k.Patch{
Instruments: []go4k.Instrument{{NumVoices: 1, Units: []go4k.Unit{
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "flags": 0x40}},
{"mulp", map[string]int{"stereo": 0}},
{"envelope", map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
{"oscillator", map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "flags": 0x40}},
{"mulp", map[string]int{"stereo": 0}},
{"out", map[string]int{"stereo": 1, "gain": 128}},
}}},
DelayTimes: []int{},
SampleOffsets: []go4k.SampleOffset{},
},
Hold: 1,
}
func TestSongMarshalJSON(t *testing.T) {
songbytes, err := json.Marshal(testSong)
if err != nil {
t.Fatalf("cannot marshal song: %v", err)
}
if string(songbytes) != expectedMarshaled {
t.Fatalf("expectedMarshaled song to unexpected result, got %v, expected %v", string(songbytes), expectedMarshaled)
}
}
func TestSongUnmarshalJSON(t *testing.T) {
var song go4k.Song
err := json.Unmarshal([]byte(expectedMarshaled), &song)
if err != nil {
t.Fatalf("cannot unmarshal song: %v", err)
}
if !reflect.DeepEqual(song, testSong) {
t.Fatalf("unmarshaled song to unexpected result, got %#v, expected %#v", song, testSong)
}
}

View File

@ -14,15 +14,12 @@ var defaultSong = go4k.Song{
}, },
Patch: go4k.Patch{ Patch: go4k.Patch{
Instruments: []go4k.Instrument{{NumVoices: 2, Units: []go4k.Unit{ Instruments: []go4k.Instrument{{NumVoices: 2, Units: []go4k.Unit{
{"envelope", 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}},
{"oscillator", 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": go4k.Sine}},
{"mulp", map[string]int{"stereo": 0}}, {Type: "mulp", Parameters: map[string]int{"stereo": 0}},
{"envelope", 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}},
{"oscillator", 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": go4k.Sine}},
{"mulp", map[string]int{"stereo": 0}}, {Type: "mulp", Parameters: map[string]int{"stereo": 0}},
{"out", map[string]int{"stereo": 1, "gain": 128}}, {Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
}}}, }}}},
DelayTimes: []int{},
SampleOffsets: []go4k.SampleOffset{},
},
} }

View File

@ -1,173 +0,0 @@
SECT_TEXT(suarithm)
;-------------------------------------------------------------------------------
; POP opcode: remove (discard) the topmost signal from the stack
;-------------------------------------------------------------------------------
; Mono: a -> (empty)
; Stereo: a b -> (empty)
;-------------------------------------------------------------------------------
%if POP_ID > -1
EXPORT MANGLE_FUNC(su_op_pop,0)
%ifdef INCLUDE_STEREO_POP
jnc su_op_pop_mono
fstp st0
su_op_pop_mono:
%endif
fstp st0
ret
%endif
;-------------------------------------------------------------------------------
; ADD opcode: add the two top most signals on the stack
;-------------------------------------------------------------------------------
; Mono: a b -> a+b b
; Stereo: a b c d -> a+c b+d c d
;-------------------------------------------------------------------------------
%if ADD_ID > -1
EXPORT MANGLE_FUNC(su_op_add,0)
%ifdef INCLUDE_STEREO_ADD
jnc su_op_add_mono
fadd st0, st2
fxch
fadd st0, st3
fxch
ret
su_op_add_mono:
%endif
fadd st1
ret
%endif
;-------------------------------------------------------------------------------
; ADDP opcode: add the two top most signals on the stack and pop
;-------------------------------------------------------------------------------
; Mono: a b -> a+b
; Stereo: a b c d -> a+c b+d
;-------------------------------------------------------------------------------
%if ADDP_ID > -1
EXPORT MANGLE_FUNC(su_op_addp,0)
%ifdef INCLUDE_STEREO_ADDP
jnc su_op_addp_mono
faddp st2, st0
faddp st2, st0
ret
su_op_addp_mono:
%endif
faddp st1, st0
ret
%endif
;-------------------------------------------------------------------------------
; LOADNOTE opcode: load the current note, scaled to [-1,1]
;-------------------------------------------------------------------------------
; Mono: (empty) -> n, where n is the note
; Stereo: (empty) -> n n
;-------------------------------------------------------------------------------
%if LOADNOTE_ID > -1
EXPORT MANGLE_FUNC(su_op_loadnote,0)
%ifdef INCLUDE_STEREO_LOADNOTE
jnc su_op_loadnote_mono
call su_op_loadnote_mono
su_op_loadnote_mono:
%endif
fild dword [INP-su_voice.inputs+su_voice.note]
do fmul dword [,c_i128,]
do fsub dword [,c_0_5,] ; s-.5
fadd st0, st0 ; 2*s-1
ret
%endif
;-------------------------------------------------------------------------------
; MUL opcode: multiply the two top most signals on the stack
;-------------------------------------------------------------------------------
; Mono: a b -> a*b a
; Stereo: a b c d -> a*c b*d c d
;-------------------------------------------------------------------------------
%if MUL_ID > -1
EXPORT MANGLE_FUNC(su_op_mul,0)
%ifdef INCLUDE_STEREO_MUL
jnc su_op_mul_mono
fmul st0, st2
fxch
fadd st0, st3
fxch
ret
su_op_mul_mono:
%endif
fmul st1
ret
%endif
;-------------------------------------------------------------------------------
; MULP opcode: multiply the two top most signals on the stack and pop
;-------------------------------------------------------------------------------
; Mono: a b -> a*b
; Stereo: a b c d -> a*c b*d
;-------------------------------------------------------------------------------
%if MULP_ID > -1
EXPORT MANGLE_FUNC(su_op_mulp,0)
%ifdef INCLUDE_STEREO_MULP
jnc su_op_mulp_mono
fmulp st2, st0
fmulp st2, st0
ret
su_op_mulp_mono:
%endif
fmulp st1
ret
%endif
;-------------------------------------------------------------------------------
; PUSH opcode: push the topmost signal on the stack
;-------------------------------------------------------------------------------
; Mono: a -> a a
; Stereo: a b -> a b a b
;-------------------------------------------------------------------------------
%if PUSH_ID > -1
EXPORT MANGLE_FUNC(su_op_push,0)
%ifdef INCLUDE_STEREO_PUSH
jnc su_op_push_mono
fld st1
fld st1
ret
su_op_push_mono:
%endif
fld st0
ret
%endif
;-------------------------------------------------------------------------------
; XCH opcode: exchange the signals on the stack
;-------------------------------------------------------------------------------
; Mono: a b -> b a
; stereo: a b c d -> c d a b
;-------------------------------------------------------------------------------
%if XCH_ID > -1
EXPORT MANGLE_FUNC(su_op_xch,0)
%ifdef INCLUDE_STEREO_XCH
jnc su_op_xch_mono
fxch st0, st2 ; c b a d
fxch st0, st1 ; b c a d
fxch st0, st3 ; d c a b
su_op_xch_mono:
%endif
fxch st0, st1
ret
%endif

View File

@ -1,168 +0,0 @@
;-------------------------------------------------------------------------------
; ADDP related defines
;-------------------------------------------------------------------------------
%assign ADDP_ID -1
%macro USE_ADDP 0
%if ADDP_ID == -1
%assign ADDP_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_addp,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_ADDP 1
USE_ADDP
%xdefine CMDS CMDS ADDP_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_ADDP
%endif
%endmacro
;-------------------------------------------------------------------------------
; ADD related defines
;-------------------------------------------------------------------------------
%assign ADD_ID -1
%macro USE_ADD 0
%if ADD_ID == -1
%assign ADD_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_add,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%assign ADD_ID -1
%macro SU_ADD 1
USE_ADD
%xdefine CMDS CMDS ADD_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_ADD
%endif
%endmacro
;-------------------------------------------------------------------------------
; POP related defines
;-------------------------------------------------------------------------------
%assign POP_ID -1
%macro USE_POP 0
%if POP_ID == -1
%assign POP_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_pop,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_POP 1
USE_POP
%xdefine CMDS CMDS POP_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_POP
%endif
%endmacro
;-------------------------------------------------------------------------------
; LOADNOTE related defines
;-------------------------------------------------------------------------------
%assign LOADNOTE_ID -1
%macro USE_LOADNOTE 0
%if LOADNOTE_ID == -1
%assign LOADNOTE_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_loadnote,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_LOADNOTE 1
USE_LOADNOTE
%xdefine CMDS CMDS LOADNOTE_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_LOADNOTE
%endif
%endmacro
;-------------------------------------------------------------------------------
; MUL related defines
;-------------------------------------------------------------------------------
%assign MUL_ID -1
%macro USE_MUL 0
%if MUL_ID == -1
%assign MUL_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_mul,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_MUL 1
USE_MUL
%xdefine CMDS CMDS MUL_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_MUL
%endif
%endmacro
;-------------------------------------------------------------------------------
; MULP related defines
;-------------------------------------------------------------------------------
%assign MULP_ID -1
%macro USE_MULP 0
%if MULP_ID == -1
%assign MULP_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_mulp,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_MULP 1
USE_MULP
%xdefine CMDS CMDS MULP_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_MULP
%endif
%endmacro
;-------------------------------------------------------------------------------
; PUSH related defines
;-------------------------------------------------------------------------------
%assign PUSH_ID -1
%macro USE_PUSH 0
%if PUSH_ID == -1
%assign PUSH_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_push,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_PUSH 1
USE_PUSH
%xdefine CMDS CMDS PUSH_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_PUSH
%endif
%endmacro
;-------------------------------------------------------------------------------
; XCH related defines
;-------------------------------------------------------------------------------
%assign XCH_ID -1
%macro USE_XCH 0
%if XCH_ID == -1
%assign XCH_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_xch,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_XCH 1
USE_XCH
%xdefine CMDS CMDS XCH_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_XCH
%endif
%endmacro

View File

@ -1,478 +0,0 @@
;-------------------------------------------------------------------------------
; DISTORT opcode: apply distortion on the signal
;-------------------------------------------------------------------------------
; Mono: x -> x*a/(1-a+(2*a-1)*abs(x)) where x is clamped first
; Stereo: l r -> l*a/(1-a+(2*a-1)*abs(l)) r*a/(1-a+(2*a-1)*abs(r))
;-------------------------------------------------------------------------------
%if DISTORT_ID > -1
SECT_TEXT(sudistrt)
EXPORT MANGLE_FUNC(su_op_distort,0)
%ifdef INCLUDE_STEREO_DISTORT
call su_effects_stereohelper
%define INCLUDE_EFFECTS_STEREOHELPER
%endif
fld dword [INP+su_distort_ports.drive]
%define SU_INCLUDE_WAVESHAPER
; flow into waveshaper
%endif
%ifdef SU_INCLUDE_WAVESHAPER
su_waveshaper:
fxch ; x a
call su_clip
fxch ; a x' (from now on just called x)
fld st0 ; a a x
do fsub dword [,c_0_5,] ; a-.5 a x
fadd st0 ; 2*a-1 a x
fld st2 ; x 2*a-1 a x
fabs ; abs(x) 2*a-1 a x
fmulp st1 ; (2*a-1)*abs(x) a x
fld1 ; 1 (2*a-1)*abs(x) a x
faddp st1 ; 1+(2*a-1)*abs(x) a x
fsub st1 ; 1-a+(2*a-1)*abs(x) a x
fdivp st1, st0 ; a/(1-a+(2*a-1)*abs(x)) x
fmulp st1 ; x*a/(1-a+(2*a-1)*abs(x))
ret
%define SU_INCLUDE_CLIP
%endif ; SU_USE_DST
;-------------------------------------------------------------------------------
; HOLD opcode: sample and hold the signal, reducing sample rate
;-------------------------------------------------------------------------------
; Mono version: holds the signal at a rate defined by the freq parameter
; Stereo version: holds both channels
;-------------------------------------------------------------------------------
%if HOLD_ID > -1
SECT_TEXT(suhold)
EXPORT MANGLE_FUNC(su_op_hold,0)
%ifdef INCLUDE_STEREO_HOLD
call su_effects_stereohelper
%define INCLUDE_EFFECTS_STEREOHELPER
%endif
fld dword [INP+su_hold_ports.freq] ; f x
fmul st0, st0 ; f^2 x
fchs ; -f^2 x
fadd dword [WRK+su_hold_wrk.phase] ; p-f^2 x
fst dword [WRK+su_hold_wrk.phase] ; p <- p-f^2
fldz ; 0 p x
fucomip st1 ; p x
fstp dword [_SP-4] ; t=p, x
jc short su_op_hold_holding ; if (0 < p) goto holding
fld1 ; 1 x
fadd dword [_SP-4] ; 1+t x
fstp dword [WRK+su_hold_wrk.phase] ; x
fst dword [WRK+su_hold_wrk.holdval] ; save holded value
ret ; x
su_op_hold_holding:
fstp st0 ;
fld dword [WRK+su_hold_wrk.holdval] ; x
ret
%endif ; HOLD_ID > -1
;-------------------------------------------------------------------------------
; CRUSH opcode: quantize the signal to finite number of levels
;-------------------------------------------------------------------------------
; Mono: x -> e*int(x/e)
; Stereo: l r -> e*int(l/e) e*int(r/e)
;-------------------------------------------------------------------------------
%if CRUSH_ID > -1
SECT_TEXT(sucrush)
EXPORT MANGLE_FUNC(su_op_crush,0)
%ifdef INCLUDE_STEREO_CRUSH
call su_effects_stereohelper
%define INCLUDE_EFFECTS_STEREOHELPER
%endif
fdiv dword [INP+su_crush_ports.resolution]
frndint
fmul dword [INP+su_crush_ports.resolution]
ret
%endif ; CRUSH_ID > -1
;-------------------------------------------------------------------------------
; GAIN opcode: apply gain on the signal
;-------------------------------------------------------------------------------
; Mono: x -> x*g
; Stereo: l r -> l*g r*g
;-------------------------------------------------------------------------------
%if GAIN_ID > -1
SECT_TEXT(sugain)
%ifdef INCLUDE_STEREO_GAIN
EXPORT MANGLE_FUNC(su_op_gain,0)
fld dword [INP+su_gain_ports.gain] ; g l (r)
jnc su_op_gain_mono
fmul st2, st0 ; g l r/g
su_op_gain_mono:
fmulp st1, st0 ; l/g (r/)
ret
%else
EXPORT MANGLE_FUNC(su_op_gain,0)
fmul dword [INP+su_gain_ports.gain]
ret
%endif
%endif ; GAIN_ID > -1
;-------------------------------------------------------------------------------
; INVGAIN opcode: apply inverse gain on the signal
;-------------------------------------------------------------------------------
; Mono: x -> x/g
; Stereo: l r -> l/g r/g
;-------------------------------------------------------------------------------
%if INVGAIN_ID > -1
SECT_TEXT(suingain)
%ifdef INCLUDE_STEREO_INVGAIN
EXPORT MANGLE_FUNC(su_op_invgain,0)
fld dword [INP+su_invgain_ports.invgain] ; g l (r)
jnc su_op_invgain_mono
fdiv st2, st0 ; g l r/g
su_op_invgain_mono:
fdivp st1, st0 ; l/g (r/)
ret
%else
EXPORT MANGLE_FUNC(su_op_invgain,0)
fdiv dword [INP+su_invgain_ports.invgain]
ret
%endif
%endif ; INVGAIN_ID > -1
;-------------------------------------------------------------------------------
; FILTER opcode: perform low/high/band-pass/notch etc. filtering on the signal
;-------------------------------------------------------------------------------
; Mono: x -> filtered(x)
; Stereo: l r -> filtered(l) filtered(r)
;-------------------------------------------------------------------------------
%if FILTER_ID > -1
SECT_TEXT(sufilter)
EXPORT MANGLE_FUNC(su_op_filter,0)
lodsb ; load the flags to al
%ifdef INCLUDE_STEREO_FILTER
call su_effects_stereohelper
%define INCLUDE_EFFECTS_STEREOHELPER
%endif
fld dword [INP+su_filter_ports.res] ; r x
fld dword [INP+su_filter_ports.freq]; f r x
fmul st0, st0 ; f2 x (square the input so we never get negative and also have a smoother behaviour in the lower frequencies)
fst dword [_SP-4] ; f2 r x
fmul dword [WRK+su_filter_wrk.band] ; f2*b r x
fadd dword [WRK+su_filter_wrk.low] ; f2*b+l r x
fst dword [WRK+su_filter_wrk.low] ; l'=f2*b+l r x
fsubp st2, st0 ; r x-l'
fmul dword [WRK+su_filter_wrk.band] ; r*b x-l'
fsubp st1, st0 ; x-l'-r*b
fst dword [WRK+su_filter_wrk.high] ; h'=x-l'-r*b
fmul dword [_SP-4] ; f2*h'
fadd dword [WRK+su_filter_wrk.band] ; f2*h'+b
fstp dword [WRK+su_filter_wrk.band] ; b'=f2*h'+b
fldz ; 0
%ifdef INCLUDE_LOWPASS
test al, byte LOWPASSFLAG
jz short su_op_filter_skiplowpass
fadd dword [WRK+su_filter_wrk.low]
su_op_filter_skiplowpass:
%endif
%ifdef INCLUDE_BANDPASS
test al, byte BANDPASSFLAG
jz short su_op_filter_skipbandpass
fadd dword [WRK+su_filter_wrk.band]
su_op_filter_skipbandpass:
%endif
%ifdef INCLUDE_HIGHPASS
test al, byte HIGHPASSFLAG
jz short su_op_filter_skiphighpass
fadd dword [WRK+su_filter_wrk.high]
su_op_filter_skiphighpass:
%endif
%ifdef INCLUDE_NEGBANDPASS
test al, byte NEGBANDPASSFLAG
jz short su_op_filter_skipnegbandpass
fsub dword [WRK+su_filter_wrk.band]
su_op_filter_skipnegbandpass:
%endif
%ifdef INCLUDE_NEGHIGHPASS
test al, byte NEGHIGHPASSFLAG
jz short su_op_filter_skipneghighpass
fsub dword [WRK+su_filter_wrk.high]
su_op_filter_skipneghighpass:
%endif
ret
%endif ; SU_INCLUDE_FILTER
;-------------------------------------------------------------------------------
; CLIP opcode: clips the signal into [-1,1] range
;-------------------------------------------------------------------------------
; Mono: x -> min(max(x,-1),1)
; Stereo: l r -> min(max(l,-1),1) min(max(r,-1),1)
;-------------------------------------------------------------------------------
SECT_TEXT(suclip)
%if CLIP_ID > -1
EXPORT MANGLE_FUNC(su_op_clip,0)
%ifdef INCLUDE_STEREO_CLIP
call su_effects_stereohelper
%define INCLUDE_EFFECTS_STEREOHELPER
%endif
%define SU_INCLUDE_CLIP
; flow into su_doclip
%endif ; CLIP_ID > -1
%ifdef SU_INCLUDE_CLIP
su_clip:
fld1 ; 1 x a
fucomi st1 ; if (1 <= x)
jbe short su_clip_do ; goto Clip_Do
fchs ; -1 x a
fucomi st1 ; if (-1 < x)
fcmovb st0, st1 ; x x a
su_clip_do:
fstp st1 ; x' a, where x' = clamp(x)
ret
%endif ; SU_INCLUDE_CLIP
;-------------------------------------------------------------------------------
; PAN opcode: pan the signal
;-------------------------------------------------------------------------------
; Mono: s -> s*(1-p) s*p
; Stereo: l r -> l*(1-p) r*p
;
; where p is the panning in [0,1] range
;-------------------------------------------------------------------------------
%if PAN_ID > -1
SECT_TEXT(supan)
%ifdef INCLUDE_STEREO_PAN
EXPORT MANGLE_FUNC(su_op_pan,0)
jc su_op_pan_do ; this time, if this is mono op...
fld st0 ; ...we duplicate the mono into stereo first
su_op_pan_do:
fld dword [INP+su_pan_ports.panning] ; p l r
fld1 ; 1 p l r
fsub st1 ; 1-p p l r
fmulp st2 ; p (1-p)*l r
fmulp st2 ; (1-p)*l p*r
ret
%else ; ifndef INCLUDE_STEREO_PAN
EXPORT MANGLE_FUNC(su_op_pan,0)
fld dword [INP+su_pan_ports.panning] ; p s
fmul st1 ; p*s s
fsub st1, st0 ; p*s s-p*s
; Equal to
; s*p s*(1-p)
fxch ; s*(1-p) s*p SHOULD PROBABLY DELETE, WHY BOTHER
ret
%endif ; INCLUDE_STEREO_PAN
%endif ; SU_USE_PAN
;-------------------------------------------------------------------------------
; su_effects_stereohelper: moves the workspace to next, does the filtering for
; right channel (pulling the calling address from stack), rewinds the
; workspace and returns
;-------------------------------------------------------------------------------
%ifdef INCLUDE_EFFECTS_STEREOHELPER
su_effects_stereohelper:
jnc su_effects_stereohelper_mono ; carry is still the stereo bit
add WRK, 16
fxch ; r l
call [_SP] ; call whoever called me...
fxch ; l r
sub WRK, 16 ; move WRK back to where it was
su_effects_stereohelper_mono:
ret ; return to process l/mono sound
%endif
;-------------------------------------------------------------------------------
; DELAY opcode: adds delay effect to the signal
;-------------------------------------------------------------------------------
; Mono: perform delay on ST0, using delaycount delaylines starting
; at delayindex from the delaytable
; Stereo: perform delay on ST1, using delaycount delaylines starting
; at delayindex + delaycount from the delaytable (so the right delays
; can be different)
;-------------------------------------------------------------------------------
%if DELAY_ID > -1
SECT_TEXT(sudelay)
EXPORT MANGLE_FUNC(su_op_delay,0)
lodsw ; al = delay index, ah = delay count
push_registers VAL, COM ; these are non-volatile according to our convention
movzx ebx, al
%ifdef RUNTIME_TABLES ; when using runtime tables, delaytimes is pulled from the stack so can be a pointer to heap
mov _SI, [_SP + su_stack.delaytimes + PUSH_REG_SIZE(2)]
lea _BX, [_SI + _BX*2]
%else
do{lea _BX,[},MANGLE_DATA(su_delay_times),_BX*2,] ; _BP now points to the right position within delay time table
%endif
movzx esi, word [_SP + su_stack.tick + PUSH_REG_SIZE(2)] ; notice that we load word, so we wrap at 65536
mov _CX, PTRWORD [_SP + su_stack.delaywrk + PUSH_REG_SIZE(2)] ; WRK is now the separate delay workspace, as they require a lot more space
%ifdef INCLUDE_STEREO_DELAY
jnc su_op_delay_mono
push _AX ; save _ah (delay count)
fxch ; r l
call su_op_delay_do ; D(r) l process delay for the right channel
pop _AX ; restore the count for second run
fxch ; l D(r)
su_op_delay_mono: ; flow into mono delay
%endif
call su_op_delay_do ; when stereo delay is not enabled, we could inline this to save 5 bytes, but I expect stereo delay to be farely popular so maybe not worth the hassle
mov PTRWORD [_SP + su_stack.delaywrk + PUSH_REG_SIZE(2)],_CX ; move delay workspace pointer back to stack.
pop_registers VAL, COM
%ifdef INCLUDE_DELAY_MODULATION
xor eax, eax
mov dword [WRK+su_unit.ports+su_delay_ports.delaymod], eax ; zero it
%endif
ret
%ifdef INCLUDE_DELAY_MODULATION
%define INCLUDE_DELAY_FLOAT_TIME
%endif
;-------------------------------------------------------------------------------
; su_op_delay_do: executes the actual delay
;-------------------------------------------------------------------------------
; Pseudocode:
; q = dr*x
; for (i = 0;i < count;i++)
; s = b[(t-delaytime[i+offset])&65535]
; q += s
; o[i] = o[i]*da+s*(1-da)
; b[t] = f*o[i] +p^2*x
; Perform dc-filtering q and output q
;-------------------------------------------------------------------------------
su_op_delay_do: ; x y
fld st0
fmul dword [INP+su_delay_ports.pregain] ; p*x y
fmul dword [INP+su_delay_ports.pregain] ; p*p*x y
fxch ; y p*p*x
fmul dword [INP+su_delay_ports.dry] ; dr*y p*p*x
su_op_delay_loop:
%ifdef INCLUDE_DELAY_FLOAT_TIME ; delaytime modulation or note syncing require computing the delay time in floats
fild word [_BX] ; k dr*y p*p*x, where k = delay time
%ifdef INCLUDE_DELAY_NOTETRACKING
test ah, 1 ; note syncing is the least significant bit of ah, 0 = ON, 1 = OFF
jne su_op_delay_skipnotesync
fild dword [INP-su_voice.inputs+su_voice.note]
do fmul dword [,c_i12,]
call MANGLE_FUNC(su_power,0)
fdivp st1, st0 ; use 10787 for delaytime to have neutral transpose
su_op_delay_skipnotesync:
%endif
%ifdef INCLUDE_DELAY_MODULATION
fld dword [WRK+su_unit.ports+su_delay_ports.delaymod]
do fmul dword [,c_32767,] ; scale it up, as the modulations would be too small otherwise
faddp st1, st0
%define USE_C_32767
%endif
fistp dword [_SP-4] ; dr*y p*p*x, dword [_SP-4] = integer amount of delay (samples)
mov edi, esi ; edi = esi = current time
sub di, word [_SP-4] ; we perform the math in 16-bit to wrap around
%else
mov edi, esi
sub di, word [_BX] ; we perform the math in 16-bit to wrap around
%endif
fld dword [_CX+su_delayline_wrk.buffer+_DI*4]; s dr*y p*p*x, where s is the sample from delay buffer
fadd st1, st0 ; s dr*y+s p*p*x (add comb output to current output)
fld1 ; 1 s dr*y+s p*p*x
fsub dword [INP+su_delay_ports.damp] ; 1-da s dr*y+s p*p*x
fmulp st1, st0 ; s*(1-da) dr*y+s p*p*x
fld dword [INP+su_delay_ports.damp] ; da s*(1-da) dr*y+s p*p*x
fmul dword [_CX+su_delayline_wrk.filtstate] ; o*da s*(1-da) dr*y+s p*p*x, where o is stored
faddp st1, st0 ; o*da+s*(1-da) dr*y+s p*p*x
fst dword [_CX+su_delayline_wrk.filtstate] ; o'=o*da+s*(1-da), o' dr*y+s p*p*x
fmul dword [INP+su_delay_ports.feedback] ; f*o' dr*y+s p*p*x
fadd st0, st2 ; f*o'+p*p*x dr*y+s p*p*x
fstp dword [_CX+su_delayline_wrk.buffer+_SI*4]; save f*o'+p*p*x to delay buffer
add _BX,2 ; move to next index
add _CX, su_delayline_wrk.size ; go to next delay delay workspace
sub ah, 2
jg su_op_delay_loop ; if ah > 0, goto loop
fstp st1 ; dr*y+s1+s2+s3+...
; DC-filtering
fld dword [_CX+su_delayline_wrk.dcout] ; o s
do fmul dword [,c_dc_const,] ; c*o s
fsub dword [_CX+su_delayline_wrk.dcin] ; c*o-i s
fxch ; s c*o-i
fst dword [_CX+su_delayline_wrk.dcin] ; i'=s, s c*o-i
faddp st1 ; s+c*o-i
do fadd dword [,c_0_5,] ; add and sub small offset to prevent denormalization
do fsub dword [,c_0_5,]
fst dword [_CX+su_delayline_wrk.dcout] ; o'=s+c*o-i
ret
%define USE_C_DC_CONST
%endif ; DELAY_ID > -1
;-------------------------------------------------------------------------------
; COMPRES opcode: push compressor gain to stack
;-------------------------------------------------------------------------------
; Mono: push g on stack, where g is a suitable gain for the signal
; you can either MULP to compress the signal or SEND it to a GAIN
; somewhere else for compressor side-chaining.
; Stereo: push g g on stack, where g is calculated using l^2 + r^2
;-------------------------------------------------------------------------------
%if COMPRES_ID > -1
SECT_TEXT(sucompr)
EXPORT MANGLE_FUNC(su_op_compressor,0)
fdiv dword [INP+su_compres_ports.invgain]; l/g, we'll call this pre inverse gained signal x from now on
fld st0 ; x x
fmul st0, st0 ; x^2 x
%ifdef INCLUDE_STEREO_COMPRES
jnc su_op_compressor_mono
fld st2 ; r x^2 l/g r
fdiv dword [INP+su_compres_ports.invgain]; r/g, we'll call this pre inverse gained signal y from now on
fst st3 ; y x^2 l/g r/g
fmul st0, st0 ; y^2 x^2 l/g r/g
faddp st1, st0 ; y^2+x^2 l/g r/g
call su_op_compressor_mono ; So, for stereo, we square both left & right and add them up
fld st0 ; and return the computed gain two times, ready for MULP STEREO
ret
su_op_compressor_mono:
%endif
fld dword [WRK+su_compres_wrk.level] ; l x^2 x
fucomi st0, st1
setnb al ; if (st0 >= st1) al = 1; else al = 0;
fsubp st1, st0 ; x^2-l x
call su_nonlinear_map ; c x^2-l x, c is either attack or release parameter mapped in a nonlinear way
fmulp st1, st0 ; c*(x^2-l) x
fadd dword [WRK+su_compres_wrk.level] ; l+c*(x^2-l) x // we could've kept level in the stack and save a few bytes, but su_env_map uses 3 stack (c + 2 temp), so the stack was getting quite big.
fst dword [WRK+su_compres_wrk.level] ; l'=l+c*(x^2-l), l' x
fld dword [INP+su_compres_ports.threshold] ; t l' x
fmul st0, st0 ; t*t l' x
fxch ; l' t*t x
fucomi st0, st1 ; if l' < t*t
fcmovb st0, st1 ; l'=t*t
fdivp st1, st0 ; t*t/l' x
fld dword [INP+su_compres_ports.ratio] ; r t*t/l' x
do fmul dword [,c_0_5,] ; p=r/2 t*t/l' x
fxch ; t*t/l' p x
fyl2x ; p*log2(t*t/l') x
jmp MANGLE_FUNC(su_power,0) ; 2^(p*log2(t*t/l')) x
; tail call ; Equal to:
; (t*t/l')^p x
; if ratio is at minimum => p=0 => 1 x
; if ratio is at maximum => p=0.5 => t/x => t/x*x=t
%endif ; COMPRES_ID > -1

View File

@ -1,388 +0,0 @@
;-------------------------------------------------------------------------------
; Filter (LOWPASS, BANDPASS...) effect related defines
;-------------------------------------------------------------------------------
%assign FILTER_ID -1
%macro USE_FILTER 0
%if FILTER_ID == -1
%assign FILTER_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_filter,0),
%xdefine NUMPARAMS NUMPARAMS 2,
%endif
%endmacro
%define LOWPASSFLAG 0x40
%define BANDPASSFLAG 0x20
%define HIGHPASSFLAG 0x10
%define NEGBANDPASSFLAG 0x08
%define NEGHIGHPASSFLAG 0x04
%macro SU_FILTER 8
db %2
db %3
db (%4*LOWPASSFLAG)+(%5*BANDPASSFLAG)+(%6*HIGHPASSFLAG)+(%7*NEGBANDPASSFLAG)+(%8*NEGHIGHPASSFLAG)
USE_FILTER
%xdefine CMDS CMDS FILTER_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_FILTER
%endif
%if %4 > 0
%define INCLUDE_LOWPASS
%endif
%if %5 > 0
%define INCLUDE_BANDPASS
%endif
%if %6 > 0
%define INCLUDE_HIGHPASS
%endif
%if %7 > 0
%define INCLUDE_NEGBANDPASS
%endif
%if %8 > 0
%define INCLUDE_NEGHIGHPASS
%endif
%endmacro
%define FREQUENCY(val) val
%define RESONANCE(val) val
%define FLAGS(val) val
%define LOWPASS(val) val
%define BANDPASS(val) val
%define HIGHPASS(val) val
%define NEGBANDPASS(val) val
%define NEGHIGHPASS(val) val
struc su_filter_ports
.freq resd 1
.res resd 1
endstruc
struc su_filter_wrk
.low resd 1
.high resd 1
.band resd 1
endstruc
;-------------------------------------------------------------------------------
; PAN effect related defines
;-------------------------------------------------------------------------------
%assign PAN_ID -1
%macro USE_PAN 0
%if PAN_ID == -1
%assign PAN_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_pan,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_PAN 2
db %2
USE_PAN
%xdefine CMDS CMDS PAN_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_PAN
%endif
%endmacro
%define PANNING(val) val
struc su_pan_ports
.panning resd 1
endstruc
;-------------------------------------------------------------------------------
; DISTORT effect related defines
;-------------------------------------------------------------------------------
%assign DISTORT_ID -1
%macro USE_DISTORT 0
%if DISTORT_ID == -1
%assign DISTORT_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_distort,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_DISTORT 2
db %2
USE_DISTORT
%xdefine CMDS CMDS DISTORT_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_DISTORT
%endif
%endmacro
%define DRIVE(val) val
struc su_distort_ports
.drive resd 1
endstruc
;-------------------------------------------------------------------------------
; HOLD effect related defines
;-------------------------------------------------------------------------------
%assign HOLD_ID -1
%macro USE_HOLD 0
%if HOLD_ID == -1
%assign HOLD_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_hold,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_HOLD 2
db %2
USE_HOLD
%xdefine CMDS CMDS HOLD_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_HOLD
%endif
%endmacro
%define HOLDFREQ(val) val
struc su_hold_ports
.freq resd 1
endstruc
struc su_hold_wrk
.phase resd 1
.holdval resd 1
endstruc
;-------------------------------------------------------------------------------
; CRUSH effect related defines
;-------------------------------------------------------------------------------
%assign CRUSH_ID -1
%macro USE_CRUSH 0
%if CRUSH_ID == -1
%assign CRUSH_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_crush,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_CRUSH 2
db %2
USE_CRUSH
%xdefine CMDS CMDS CRUSH_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_CRUSH
%endif
%endmacro
%define RESOLUTION(val) val
struc su_crush_ports
.resolution resd 1
endstruc
;-------------------------------------------------------------------------------
; GAIN effect related defines
;-------------------------------------------------------------------------------
%assign GAIN_ID -1
%macro USE_GAIN 0
%if GAIN_ID == -1
%assign GAIN_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_gain,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_GAIN 2
db %2
USE_GAIN
%xdefine CMDS CMDS GAIN_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_GAIN
%endif
%endmacro
%define GAIN(val) val
struc su_gain_ports
.gain resd 1
endstruc
;-------------------------------------------------------------------------------
; INVGAIN effect related defines
;-------------------------------------------------------------------------------
%assign INVGAIN_ID -1
%macro USE_INVGAIN 0
%if INVGAIN_ID == -1
%assign INVGAIN_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_invgain,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_INVGAIN 2
db %2
USE_INVGAIN
%xdefine CMDS CMDS INVGAIN_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_INVGAIN
%endif
%endmacro
%define INVGAIN(val) val
struc su_invgain_ports
.invgain resd 1
endstruc
;-------------------------------------------------------------------------------
; CLIP effect related defines
;-------------------------------------------------------------------------------
%assign CLIP_ID -1
%macro USE_CLIP 0
%if CLIP_ID == -1
%assign CLIP_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_clip,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_CLIP 1
USE_CLIP
%xdefine CMDS CMDS CLIP_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_CLIP
%endif
%endmacro
;-------------------------------------------------------------------------------
; Delay effect related defines
;-------------------------------------------------------------------------------
%assign DELAY_ID -1
%macro USE_DELAY 0
%if DELAY_ID == -1
%assign DELAY_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_delay,0),
%xdefine NUMPARAMS NUMPARAMS 4,
%endif
%endmacro
%define MAX_DELAY 65536 ; warning: this is pretty much fixed, as we use 16-bit math to wraparound the delay buffers
%assign NUM_DELAY_LINES 0
%macro SU_DELAY 8
db %2
db %3
db %4
db %5
db %6
db (2*%7-1)+%8
USE_DELAY
%xdefine CMDS CMDS DELAY_ID + %1,
%assign NUM_DELAY_LINES NUM_DELAY_LINES + %7 * (1+%1)
%if %1 == 1
%define INCLUDE_STEREO_DELAY
%endif
%if %8 > 0
%define INCLUDE_DELAY_NOTETRACKING
%define INCLUDE_DELAY_FLOAT_TIME
%endif
%endmacro
%macro BEGIN_DELTIMES 0
SECT_DATA(sudeltim)
EXPORT MANGLE_DATA(su_delay_times)
%endmacro
%define END_DELTIMES
%macro DELTIME 1-*
%rep %0
dw %1
%rotate 1
%endrep
%endmacro
%define PREGAIN(val) val
%define DRY(val) val
%define FEEDBACK(val) val
%define DEPTH(val) val
%define DAMP(val) val
%define DELAY(val) val
%define COUNT(val) val
%define NOTETRACKING(val) val
struc su_delay_ports
.pregain resd 1
.dry resd 1
.feedback resd 1
.damp resd 1
.freq resd 1
.delaymod resd 1 ; note that this is not converted from integer, only modulated
endstruc
struc su_delayline_wrk
.dcin resd 1
.dcout resd 1
.filtstate resd 1
.buffer resd MAX_DELAY
.size:
endstruc
;-------------------------------------------------------------------------------
; COMPRES effect related defines
;-------------------------------------------------------------------------------
%assign COMPRES_ID -1
%macro USE_COMPRES 0
%if COMPRES_ID == -1
%assign COMPRES_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_compressor,0),
%xdefine NUMPARAMS NUMPARAMS 5,
%endif
%endmacro
%macro SU_COMPRESSOR 6
db %2
db %3
db %4
db %5
db %6
USE_COMPRES
%xdefine CMDS CMDS COMPRES_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_COMPRES
%endif
%endmacro
%define ATTAC(val) val
%define RELEASE(val) val
%define INVGAIN(val) val
%define THRESHOLD(val) val
%define RATIO(val) val
struc su_compres_ports
.attack resd 1
.release resd 1
.invgain resd 1
.threshold resd 1
.ratio resd 1
endstruc
struc su_compres_wrk
.level resd 1
endstruc

View File

@ -1,65 +0,0 @@
;-------------------------------------------------------------------------------
; ADVANCE opcode: advances from one voice to next
;-------------------------------------------------------------------------------
; Checks if this was the last voice of current instrument. If so, moves to
; next opcodes and updates the stack to reflect the instrument change.
; If this instrument has more voices to process, rolls back the COM and VAL
; pointers to where they were when this instrument started.
;
; There is no stereo version.
;-------------------------------------------------------------------------------
SECT_TEXT(suopadvn)
EXPORT MANGLE_FUNC(su_op_advance,0)
%ifdef INCLUDE_POLYPHONY
mov WRK, [_SP+su_stack.wrk] ; WRK points to start of current voice
add WRK, su_voice.size ; move to next voice
mov [_SP+su_stack.wrk], WRK ; update the pointer in the stack to point to the new voice
mov ecx, [_SP+su_stack.voiceno] ; ecx = how many voices remain to process
dec ecx ; decrement number of voices to process
bt dword [_SP+su_stack.polyphony], ecx ; if voice bit of su_polyphonism not set
jnc su_op_advance_next_instrument ; goto next_instrument
mov VAL, PTRWORD [_SP+su_stack.val] ; if it was set, then repeat the opcodes for the current voice
mov COM, PTRWORD [_SP+su_stack.com]
su_op_advance_next_instrument:
mov PTRWORD [_SP+su_stack.val], VAL ; save current VAL as a checkpoint
mov PTRWORD [_SP+su_stack.com], COM ; save current COM as a checkpoint
su_op_advance_finish:
mov [_SP+su_stack.voiceno], ecx
ret
%else
mov WRK, PTRWORD [_SP+su_stack.wrk] ; WRK = wrkptr
add WRK, su_voice.size ; move to next voice
mov PTRWORD [_SP+su_stack.wrk], WRK ; update stack
dec PTRWORD [_SP+su_stack.voiceno] ; voices--
ret
%endif
;-------------------------------------------------------------------------------
; SPEED opcode: modulate the speed (bpm) of the song based on ST0
;-------------------------------------------------------------------------------
; Mono: adds or subtracts the ticks, a value of 0.5 is neutral & will7
; result in no speed change.
; There is no STEREO version.
;-------------------------------------------------------------------------------
%if SPEED_ID > -1
SECT_TEXT(suspeed)
EXPORT MANGLE_FUNC(su_op_speed,0)
do fmul dword [,c_bpmscale,] ; (2*s-1)*64/24, let's call this p from now on
call MANGLE_FUNC(su_power,0) ; 2^p, this is how many ticks we should be taking
fld1 ; 1 2^p
fsubp st1, st0 ; 2^p-1, the player is advancing 1 tick by its own
fadd dword [WRK+su_speed_wrk.remainder] ; t+2^p-1, t is the remainder from previous rounds as ticks have to be rounded to 1
push _AX
fist dword [_SP] ; Main stack: k=int(t+2^p-1)
fisub dword [_SP] ; t+2^p-1-k, the remainder
pop _AX
add dword [_SP+su_stack.rowtick], eax ; add the whole ticks to row tick count
fstp dword [WRK+su_speed_wrk.remainder] ; save the remainder for future
ret
%define USE_C_BPMSCALE
%endif

View File

@ -1,21 +0,0 @@
;-------------------------------------------------------------------------------
; SPEED related defines
;-------------------------------------------------------------------------------
%assign SPEED_ID -1
%macro USE_SPEED 0
%if SPEED_ID == -1
%assign SPEED_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_speed,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_SPEED 0
USE_SPEED
%xdefine CMDS CMDS SPEED_ID,
%endmacro
struc su_speed_wrk
.remainder resd 1
endstruc

View File

@ -1,559 +0,0 @@
%if BITS == 64
%define WRK rbp ; alias for unit workspace
%define VAL rsi ; alias for unit values (transformed/untransformed)
%define COM rbx ; alias for instrument opcodes
%define INP rdx ; alias for transformed inputs
%define _AX rax ; push and offsets have to be r* on 64-bit and e* on 32-bit
%define _BX rbx
%define _CX rcx
%define _DX rdx
%define _SP rsp
%define _SI rsi
%define _DI rdi
%define _BP rbp
%define PTRSIZE 8
%define PTRWORD qword
%define RESPTR resq
%define DPTR dq
%macro do 2
mov r9, qword %2
%1 r9
%endmacro
%macro do 3
mov r9, qword %2
%1 r9 %3
%endmacro
%macro do 4
mov r9, qword %2
%1 r9+%3 %4
%endmacro
%macro do 5
mov r9, qword %2
lea r9, [r9+%3]
%1 r9+%4 %5
%endmacro
%macro push_registers 1-*
%rep %0
push %1
%rotate 1
%endrep
%endmacro
%macro pop_registers 1-*
%rep %0
%rotate -1
pop %1
%endrep
%endmacro
%define PUSH_REG_SIZE(n) (n*8)
%ifidn __OUTPUT_FORMAT__,win64
%define render_prologue push_registers rcx,rdi,rsi,rbx,rbp ; rcx = ptr to buf. rdi,rsi,rbx,rbp nonvolatile
%macro render_epilogue 0
pop_registers rcx,rdi,rsi,rbx,rbp
ret
%endmacro
%else ; 64 bit mac & linux
%define render_prologue push_registers rdi,rbx,rbp ; rdi = ptr to buf. rbx & rbp nonvolatile
%macro render_epilogue 0
pop_registers rdi,rbx,rbp
ret
%endmacro
%endif
%else
%define WRK ebp ; alias for unit workspace
%define VAL esi ; alias for unit values (transformed/untransformed)
%define COM ebx ; alias for instrument opcodes
%define INP edx ; alias for transformed inputs
%define _AX eax
%define _BX ebx
%define _CX ecx
%define _DX edx
%define _SP esp
%define _SI esi
%define _DI edi
%define _BP ebp
%define PTRSIZE 4
%define PTRWORD dword
%define RESPTR resd
%define DPTR dd
%macro do 2
%1 %2
%endmacro
%macro do 3
%1 %2 %3
%endmacro
%macro do 4
%1 %2+%3 %4
%endmacro
%macro do 5
%1 %2+%3+%4 %5
%endmacro
%macro push_registers 1-*
pushad ; in 32-bit mode, this is the easiest way to store all the registers
%endmacro
%macro pop_registers 1-*
popad
%endmacro
%define PUSH_REG_SIZE(n) 32
%define render_prologue pushad ; stdcall & everything nonvolatile except eax, ecx, edx
%macro render_epilogue 0
popad
ret 4 ; clean the passed parameter from stack.
%endmacro
%endif
section .text ; yasm throws section redeclaration warnings if strucs are defined without a plain .text section
struc su_stack ; the structure of stack _as the units see it_
.retaddr RESPTR 1
%if BITS == 32 ; we dump everything with pushad, so this is unused in 32-bit
RESPTR 1
%endif
.val RESPTR 1
.wrk RESPTR 1
%if BITS == 32 ; we dump everything with pushad, so this is unused in 32-bit
RESPTR 1
%endif
.com RESPTR 1
.synth RESPTR 1
.delaywrk RESPTR 1
%if BITS == 32 ; we dump everything with pushad, so this is unused in 32-bit
RESPTR 1
%endif
.retaddrvm RESPTR 1
.voiceno RESPTR 1
%ifdef INCLUDE_POLYPHONY
.polyphony RESPTR 1
%endif
.output_sound:
.rowtick RESPTR 1 ; which tick within this row are we at
.update_voices:
.row RESPTR 1 ; which total row of the song are we at
.tick RESPTR 1 ; which total tick of the song are we at
.randseed RESPTR 1
%ifdef RUNTIME_TABLES ; RUNTIME_TABLES move these table ptrs to stack
%if DELAY_ID > -1 ; allowing them to be in the heap &
.delaytimes RESPTR 1 ; modifying them safely & having many synths
%endif
%ifdef INCLUDE_SAMPLES
.sampleoffs RESPTR 1
%endif
%endif
%ifdef INCLUDE_MULTIVOICE_TRACKS
.voicetrack RESPTR 1
%endif
.render_epilogue:
%if BITS == 32
RESPTR 8 ; registers
.retaddr_pl RESPTR 1
%elifidn __OUTPUT_FORMAT__,win64
RESPTR 4 ; registers
%else
RESPTR 2 ; registers
%endif
.bufferptr RESPTR 1
.size:
endstruc
;===============================================================================
; Uninitialized data: The one and only synth object
;===============================================================================
SECT_BSS(susynth)
su_synth_obj resb su_synthworkspace.size
%if DELAY_ID > -1 ; if we use delay, then the synth obj should be immediately followed by the delay workspaces
resb NUM_DELAY_LINES*su_delayline_wrk.size
%endif
;===============================================================================
; The opcode table jump table. This is constructed to only include the opcodes
; that are used so that the jump table is as small as possible.
;===============================================================================
SECT_DATA(suoptabl)
su_synth_commands DPTR OPCODES
;===============================================================================
; The number of transformed parameters each opcode takes
;===============================================================================
SECT_DATA(suparcnt)
su_opcode_numparams db NUMPARAMS
;-------------------------------------------------------------------------------
; su_run_vm function: runs the entire virtual machine once, creating 1 sample
;-------------------------------------------------------------------------------
; Input: su_synth_obj.left : Set to 0 before calling
; su_synth_obj.right : Set to 0 before calling
; _CX : Pointer to delay workspace (if needed)
; _DX : Pointer to synth object
; COM : Pointer to command stream
; VAL : Pointer to value stream
; WRK : Pointer to the last workspace processed
; _DI : Number of voices to process
; Output: su_synth_obj.left : left sample
; su_synth_obj.right : right sample
; Dirty: everything
;-------------------------------------------------------------------------------
SECT_TEXT(surunvm)
EXPORT MANGLE_FUNC(su_run_vm,0)
push_registers _CX, _DX, COM, WRK, VAL ; save everything to stack
su_run_vm_loop: ; loop until all voices done
movzx edi, byte [COM] ; edi = command byte
inc COM ; move to next instruction
add WRK, su_unit.size ; move WRK to next unit
shr edi, 1 ; shift out the LSB bit = stereo bit
mov INP, [_SP+su_stack.wrk-PTRSIZE] ; reset INP to point to the inputs part of voice
add INP, su_voice.inputs
xor ecx, ecx ; counter = 0
xor eax, eax ; clear out high bits of eax, as lodsb only sets al
su_transform_values_loop:
do{cmp cl, byte [},su_opcode_numparams,_DI,] ; compare the counter to the value in the param count table
je su_transform_values_out
lodsb ; load the byte value from VAL stream
push _AX ; push it to memory so FPU can read it
fild dword [_SP] ; load the value to FPU stack
do fmul dword [,c_i128,] ; divide it by 128 (0 => 0, 128 => 1.0)
fadd dword [WRK+su_unit.ports+_CX*4] ; add the modulations in the current workspace
fstp dword [INP+_CX*4] ; store the modulated value in the inputs section of voice
xor eax, eax
mov dword [WRK+su_unit.ports+_CX*4], eax ; clear out the modulation ports
pop _AX
inc ecx
jmp su_transform_values_loop
su_transform_values_out:
bt dword [COM-1],0 ; LSB of COM = stereo bit => carry
do call [,su_synth_commands,_DI*PTRSIZE,] ; call the function corresponding to the instruction
cmp dword [_SP+su_stack.voiceno-PTRSIZE],0 ; do we have more voices to process?
jne su_run_vm_loop ; if there's more voices to process, goto vm_loop
pop_registers _CX, _DX, COM, WRK, VAL ; pop everything from stack
ret
;-------------------------------------------------------------------------------
; su_nonlinear_map function: returns 2^(-24*x) of parameter number _AX
;-------------------------------------------------------------------------------
; Input: _AX : parameter number (e.g. for envelope: 0 = attac, 1 = decay...)
; INP : pointer to transformed values
; Output: st0 : 2^(-24*x), where x is the parameter in the range 0-1
;-------------------------------------------------------------------------------
SECT_TEXT(supower)
%if ENVELOPE_ID > -1 || COMPRES_ID > -1
su_nonlinear_map:
fld dword [INP+_AX*4] ; x, where x is the parameter in the range 0-1
do fimul dword [,c_24,] ; 24*x
fchs ; -24*x
; flow into Power function, which outputs 2^(-24*x)
%endif
;-------------------------------------------------------------------------------
; su_power function: computes 2^x
;-------------------------------------------------------------------------------
; Input: st0 : x
; Output: st0 : 2^x
;-------------------------------------------------------------------------------
EXPORT MANGLE_FUNC(su_power,0)
fld1 ; 1 x
fld st1 ; x 1 x
fprem ; mod(x,1) 1 x
f2xm1 ; 2^mod(x,1)-1 1 x
faddp st1,st0 ; 2^mod(x,1) x
fscale ; 2^mod(x,1)*2^trunc(x) x
; Equal to:
; 2^x x
fstp st1 ; 2^x
ret
%ifndef SU_DISABLE_PLAYER
;-------------------------------------------------------------------------------
; output_sound macro: used by the render function to write sound to buffer
;-------------------------------------------------------------------------------
; The macro contains the ifdef hell to handle 16bit output and clipping cases
; to keep the main function more readable
; Stack : sample row pushad output_ptr
;-------------------------------------------------------------------------------
%macro output_sound 0
%ifndef SU_OUTPUT_16BIT
%ifndef SU_CLIP_OUTPUT ; The modern way. No need to clip; OS can do it.
mov _DI, [_SP+su_stack.bufferptr - su_stack.output_sound] ; edi containts ptr
mov _SI, PTRWORD su_synth_obj + su_synthworkspace.left
movsd ; copy left channel to output buffer
movsd ; copy right channel to output buffer
mov [_SP+su_stack.bufferptr - su_stack.output_sound], _DI ; save back the updated ptr
lea _DI, [_SI-8]
xor eax, eax
stosd ; clear left channel so the VM is ready to write them again
stosd ; clear right channel so the VM is ready to write them again
%else
mov _SI, qword [_SP+su_stack.bufferptr - su_stack.output_sound] ; esi points to the output buffer
xor _CX,_CX
xor eax,eax
%%loop: ; loop over two channels, left & right
do fld dword [,su_synth_obj+su_synthworkspace.left,_CX*4,]
call su_clip
fstp dword [_SI]
do mov dword [,su_synth_obj+su_synthworkspace.left,_CX*4,{],eax} ; clear the sample so the VM is ready to write it
add _SI,4
cmp ecx,2
jl %%loop
mov dword [_SP+su_stack.bufferptr - su_stack.output_sound], _SI ; save esi back to stack
%define SU_INCLUDE_CLIP
%endif
%else ; 16-bit output, always clipped. This is a bit legacy method.
mov _SI, [_SP+su_stack.bufferptr - su_stack.output_sound] ; esi points to the output buffer
mov _DI, PTRWORD su_synth_obj+su_synthworkspace.left
mov ecx, 2
%%loop: ; loop over two channels, left & right
fld dword [_DI]
call su_clip
do fmul dword [,c_32767,]
push _AX
fistp dword [_SP]
pop _AX
mov word [_SI],ax ; // store integer converted right sample
xor eax,eax
stosd
add _SI,2
loop %%loop
mov [_SP+su_stack.bufferptr - su_stack.output_sound], _SI ; save esi back to stack
%define USE_C_32767
%define SU_INCLUDE_CLIP
%endif
%endmacro
;-------------------------------------------------------------------------------
; su_render_song function: the entry point for the synth
;-------------------------------------------------------------------------------
; Has the signature su_render_song(void *ptr), where ptr is a pointer to
; the output buffer. Renders the compile time hard-coded song to the buffer.
; Stack: output_ptr
;-------------------------------------------------------------------------------
SECT_TEXT(surensng)
EXPORT MANGLE_FUNC(su_render_song,PTRSIZE) ; Stack: ptr
render_prologue
xor eax, eax
%ifdef INCLUDE_MULTIVOICE_TRACKS
push VOICETRACK_BITMASK
%endif
%ifdef RUNTIME_TABLES ; runtime tables is actually only useful in the api use case, but it's nice it works also in the render case
%ifdef INCLUDE_SAMPLES
do push ,MANGLE_DATA(su_sample_offsets) ; &su_sample_offsets is in the stack, instead of hard coded
%endif
%if DELAY_ID > -1
do push ,MANGLE_DATA(su_delay_times) ; &su_delay_times is in the stack, instead of hard coded
%endif
%endif
push 1 ; randseed
push _AX ; global tick time
su_render_rowloop: ; loop through every row in the song
push _AX ; Stack: row pushad ptr
call su_update_voices ; update instruments for the new row
xor eax, eax ; ecx is the current sample within row
su_render_sampleloop: ; loop through every sample in the row
push _AX ; Stack: sample row pushad ptr
%ifdef INCLUDE_POLYPHONY
push POLYPHONY_BITMASK ; does the next voice reuse the current opcodes?
%endif
push MAX_VOICES
mov _DX, PTRWORD su_synth_obj ; _DX points to the synth object
mov COM, PTRWORD MANGLE_DATA(su_commands) ; COM points to vm code
mov VAL, PTRWORD MANGLE_DATA(su_params) ; VAL points to unit params
%if DELAY_ID > -1
lea _CX, [_DX + su_synthworkspace.size - su_delayline_wrk.filtstate]
%endif
lea WRK, [_DX + su_synthworkspace.voices] ; WRK points to the first voice
call MANGLE_FUNC(su_run_vm,0) ; run through the VM code
pop _AX
%ifdef INCLUDE_POLYPHONY
pop _AX
%endif
output_sound ; *ptr++ = left, *ptr++ = right
pop _AX
inc dword [_SP + PTRSIZE] ; increment global time, used by delays
inc eax
cmp eax, SAMPLES_PER_ROW
jl su_render_sampleloop
pop _AX ; Stack: pushad ptr
inc eax
cmp eax, TOTAL_ROWS
jl su_render_rowloop
; rewind the stack. Use add when there's a lot to clean, repeated pops if only a little
%if su_stack.render_epilogue - su_stack.tick > 3*PTRSIZE
add _SP, su_stack.render_epilogue - su_stack.tick ; rewind the remaining tack
%else
%rep (su_stack.render_epilogue - su_stack.tick)/PTRSIZE
pop _AX ; the entropy of 3 x pop _AX is probably lower than 3 byte add
%endrep ; but this needs to be confirmed
%endif
render_epilogue
;-------------------------------------------------------------------------------
; su_update_voices function: polyphonic & chord implementation
;-------------------------------------------------------------------------------
; Input: eax : current row within song
; Dirty: pretty much everything
;-------------------------------------------------------------------------------
SECT_TEXT(suupdvce)
%ifdef INCLUDE_MULTIVOICE_TRACKS
su_update_voices: ; Stack: retaddr row
xor edx, edx
mov ebx, PATTERN_SIZE ; we could do xor ebx,ebx; mov bl,PATTERN_SIZE, but that would limit patternsize to 256...
div ebx ; eax = current pattern, edx = current row in pattern
do{lea _SI, [},MANGLE_DATA(su_tracks),_AX,] ; esi points to the pattern data for current track
xor eax, eax ; eax is the first voice of next track
xor ebx, ebx ; ebx is the first voice of current track
mov _BP, PTRWORD su_synth_obj ; ebp points to the current_voiceno array
su_update_voices_trackloop:
movzx eax, byte [_SI] ; eax = current pattern
imul eax, PATTERN_SIZE ; eax = offset to current pattern data
do{movzx eax,byte [},MANGLE_DATA(su_patterns),_AX,_DX,] ; eax = note
push _DX ; Stack: ptrnrow
xor edx, edx ; edx=0
mov ecx, ebx ; ecx=first voice of the track to be done
su_calculate_voices_loop: ; do {
bt dword [_SP + su_stack.voicetrack - su_stack.update_voices + 2*PTRSIZE],ecx ; test voicetrack_bitmask// notice that the incs don't set carry
inc edx ; edx++ // edx=numvoices
inc ecx ; ecx++ // ecx=the first voice of next track
jc su_calculate_voices_loop ; } while bit ecx-1 of bitmask is on
push _CX ; Stack: next_instr ptrnrow
cmp al, SU_HOLDVALUE ; anything but hold causes action
je short su_update_voices_nexttrack
mov cl, byte [_BP]
mov edi, ecx
add edi, ebx
shl edi, MAX_UNITS_SHIFT + 6 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
do inc dword [,su_synth_obj+su_synthworkspace.voices+su_voice.release,_DI,] ; set the voice currently active to release; notice that it could increment any number of times
cmp al, SU_HOLDVALUE ; if cl < HLD (no new note triggered)
jl su_update_voices_nexttrack ; goto nexttrack
inc ecx ; curvoice++
cmp ecx, edx ; if (curvoice >= num_voices)
jl su_update_voices_skipreset
xor ecx,ecx ; curvoice = 0
su_update_voices_skipreset:
mov byte [_BP],cl
add ecx, ebx
shl ecx, MAX_UNITS_SHIFT + 6 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
do{lea _DI,[},su_synth_obj+su_synthworkspace.voices,_CX,]
stosd ; save note
mov ecx, (su_voice.size - su_voice.release)/4
xor eax, eax
rep stosd ; clear the workspace of the new voice, retriggering oscillators
su_update_voices_nexttrack:
pop _BX ; ebx=first voice of next instrument, Stack: ptrnrow
pop _DX ; edx=patrnrow
add _SI, MAX_PATTERNS
inc _BP
do{cmp _BP,},su_synth_obj+MAX_TRACKS
jl su_update_voices_trackloop
ret
%else ; INCLUDE_MULTIVOICE_TRACKS not defined -> one voice per track ve_SIon
su_update_voices: ; Stack: retaddr row
xor edx, edx
xor ebx, ebx
mov bl, PATTERN_SIZE
div ebx ; eax = current pattern, edx = current row in pattern
do{lea _SI, [},MANGLE_DATA(su_tracks),_AX,]; esi points to the pattern data for current track
mov _DI, PTRWORD su_synth_obj+su_synthworkspace.voices
mov bl, MAX_TRACKS ; MAX_TRACKS is always <= 32 so this is ok
su_update_voices_trackloop:
movzx eax, byte [_SI] ; eax = current pattern
imul eax, PATTERN_SIZE ; eax = offset to current pattern data
do{movzx eax, byte [}, MANGLE_DATA(su_patterns),_AX,_DX,] ; ecx = note
cmp al, SU_HOLDVALUE ; anything but hold causes action
je short su_update_voices_nexttrack
inc dword [_DI+su_voice.release] ; set the voice currently active to release; notice that it could increment any number of times
jb su_update_voices_nexttrack ; if cl < HLD (no new note triggered) goto nexttrack
su_update_voices_retrigger:
stosd ; save note
mov ecx, (su_voice.size - su_voice.release)/4 ; could be xor ecx, ecx; mov ch,...>>8, but will it actually be smaller after compression?
xor eax, eax
rep stosd ; clear the workspace of the new voice, retriggering oscillators
jmp short su_update_voices_skipadd
su_update_voices_nexttrack:
add _DI, su_voice.size
su_update_voices_skipadd:
add _SI, MAX_PATTERNS
dec ebx
jnz short su_update_voices_trackloop
ret
%endif ;INCLUDE_MULTIVOICE_TRACKS
%endif ; SU_DISABLE_PLAYER
;-------------------------------------------------------------------------------
; Include the rest of the code
;-------------------------------------------------------------------------------
%include "sointu/arithmetic_footer.inc"
%include "sointu/flowcontrol_footer.inc"
%include "sointu/sources_footer.inc"
%include "sointu/sinks_footer.inc"
; warning: at the moment effects has to be assembled after
; sources, as sources.asm defines SU_USE_WAVESHAPER
; if needed.
%include "sointu/effects_footer.inc"
%ifidn __OUTPUT_FORMAT__,win64
%include "sointu/win64/gmdls_win64_footer.inc"
%endif
%ifidn __OUTPUT_FORMAT__,win32
%include "sointu/win32/gmdls_win32_footer.inc"
%endif
;-------------------------------------------------------------------------------
; Constants
;-------------------------------------------------------------------------------
SECT_DATA(suconst)
c_24 dd 24
c_i128 dd 0.0078125
c_RandDiv dd 65536*32768
c_0_5 dd 0.5
c_i12 dd 0x3DAAAAAA
c_lfo_normalize dd 0.000038
c_freq_normalize dd 0.000092696138 ; // 220.0/(2^(69/12)) / 44100.0
%ifdef USE_C_DC_CONST
c_dc_const dd 0.99609375 ; R = 1 - (pi*2 * frequency /samplerate)
%endif
%ifdef USE_C_32767
c_32767 dd 32767.0
%endif
%ifdef USE_C_BPMSCALE
c_bpmscale dd 2.206896551724138 ; 64/29, 29 values will be double speed, so you can go from ~ 1/2.2 speed to 2.2x speed
%endif
%ifdef USE_C_16
c_16 dd 16.0
%endif
%ifdef USE_C_SAMPLEFREQ_SCALING
c_samplefreq_scaling dd 84.28074964676522 ; o = 0.000092696138, n = 72, f = 44100*o*2**(n/12), scaling = 22050/f <- so note 72 plays at the "normal rate"
%endif

View File

@ -1,279 +0,0 @@
%ifndef SOINTU_INC
%define SOINTU_INC
%macro EXPORT 1
global %1
%1:
%endmacro
%ifidn __OUTPUT_FORMAT__,win32
%define WINDOWS
%assign BITS 32
%define MANGLE_FUNC(f,n) _ %+ f %+ @ %+ n
%define MANGLE_DATA(d) _ %+ d
%endif
%ifidn __OUTPUT_FORMAT__,win64
%define WINDOWS
%assign BITS 64
%define MANGLE_FUNC(f,n) f
%define MANGLE_DATA(d) d
%endif
%ifidn __OUTPUT_FORMAT__,macho32
%define MACOS
%assign BITS 32
%endif
%ifidn __OUTPUT_FORMAT__,macho64
%define MACOS
%assign BITS 64
%endif
%ifidn __OUTPUT_FORMAT__,elf
%define LINUX
%assign BITS 32
%endif
%ifidn __OUTPUT_FORMAT__,elf64
%define LINUX
%assign BITS 64
%endif
%ifdef WINDOWS
; Windows has crinkler so one may put everything in custom sections to aid crinkler.
; Maybe mac users need it too
%ifndef DISABLE_SECTIONS
%define SECT_BSS(n) section . %+ n bss align=256 ; a high alignment on the uninitialized sections should compress better
%define SECT_DATA(n) section . %+ n data align=1
%define SECT_TEXT(n) section . %+ n code align=1
%else
%define SECT_BSS(n) section .bss align=256
%define SECT_DATA(n) section .data align=1
%define SECT_TEXT(n) section .code align=1
%endif
%elifdef MACOS
%define MANGLE_FUNC(f,n) _ %+ f
%define MANGLE_DATA(d) _ %+ d
; macho does not seem to support named sections, so DISABLE_SECTIONS
; is "always on" / ignored
%define SECT_BSS(n) section .bss align=256
%define SECT_DATA(n) section .data align=1
%define SECT_TEXT(n) section .text align=1
%else ; Linux, or hopefully something similar
%define MANGLE_FUNC(f,n) f
%define MANGLE_DATA(d) d
%ifndef DISABLE_SECTIONS
%define SECT_BSS(n) section .bss. %+ n nobits alloc noexec write align=256
%define SECT_DATA(n) section .data. %+ n progbits alloc noexec write align=1
%define SECT_TEXT(n) section .text. %+ n progbits alloc exec nowrite align=1
%else
%define SECT_BSS(n) section .bss. nobits alloc noexec write align=256
%define SECT_DATA(n) section .data. progbits alloc noexec write align=1
%define SECT_TEXT(n) section .text. progbits alloc exec nowrite align=1
%endif
%endif
%assign CUR_ID 2
%define CMDS ; CMDS is empty at first, no commands defined
%define OPCODES MANGLE_FUNC(su_op_advance,0),
%define NUMPARAMS 0,
%define SU_ADVANCE_ID 0
%define STEREO(val) val
section .text ; yasm throws section redeclaration warnings if strucs are defined without a plain .text section
%include "sointu/flowcontrol_header.inc"
%include "sointu/arithmetic_header.inc"
%include "sointu/effects_header.inc"
%include "sointu/sources_header.inc"
%include "sointu/sinks_header.inc"
;-------------------------------------------------------------------------------
; synth defines
;-------------------------------------------------------------------------------
%define MAX_DELAY 65536
%assign MAX_UNITS_SHIFT 6
%assign MAX_UNITS ((1 << MAX_UNITS_SHIFT)-1) ; this is carefully chosen to align su_unit to 2^n boundary
%define ABSOLUTE_MAX_VOICES 32
%ifndef SAMPLE_RATE
%define SAMPLE_RATE 44100
%endif
%define TOTAL_ROWS (MAX_PATTERNS*PATTERN_SIZE)
%define SAMPLES_PER_ROW (SAMPLE_RATE*4*60/(SU_BPM*16))
%macro BEGIN_SONG 5
%xdefine SU_BPM %1
%if %2 == 1
%define SU_OUTPUT_16BIT
%endif
%if %3 == 1
%define SU_CLIP_OUTPUT
%endif
%if %4 == 1
%define INCLUDE_DELAY_MODULATION
%endif
%xdefine SU_HOLDVALUE %5
%endmacro
%macro END_SONG 0
%include "sointu/footer.inc"
%endmacro
%define BPM(val) val
%define OUTPUT_16BIT(val) val
%define CLIP_OUTPUT(val) val
%define DELAY_MODULATION(val) val
%define HOLD(val) val
%macro BEGIN_PATCH 0
SECT_DATA(params)
EXPORT MANGLE_DATA(su_params)
%endmacro
%macro END_PATCH 0 ; After the patch is finished, saves the accumulated commands
SECT_DATA(sucomnds)
EXPORT MANGLE_DATA(su_commands)
db CMDS
%endmacro
%define POLYPHONY_BITMASK 0
%assign MAX_VOICES 0
%assign MAX_TRACKS 0
%macro BEGIN_INSTRUMENT 1
; increment MAX_VOICES equal to %1 and construct the POLYPHONY_BITMASK so that
; for every except the last, the bit is on
%rep %1-1
%assign POLYPHONY_BITMASK (POLYPHONY_BITMASK << 1) + 1
%assign MAX_VOICES MAX_VOICES + 1
%endrep
%assign POLYPHONY_BITMASK (POLYPHONY_BITMASK << 1)
%assign MAX_VOICES MAX_VOICES + 1 ; the last voice increment, without adding one bit to the mask
%if MAX_VOICES > 32
%error Error: cannot have more than 32 voices!
%endif
%if %1 > 1
%define INCLUDE_POLYPHONY
%endif
%endmacro
%define VOICES(val) val
%define TRACKS(val) val
%macro END_INSTRUMENT 0
%xdefine CMDS CMDS SU_ADVANCE_ID,
%endmacro
%assign PATTERN_LENGTH -1
%macro BEGIN_PATTERNS 0
SECT_DATA(supatrns)
EXPORT MANGLE_DATA(su_patterns)
%define USE_PLAYER
%endmacro
%define END_PATTERNS
%assign PATTERN_SIZE -1
%macro PATTERN 1-*
%rep %0
db %1
%rotate 1
%endrep
%if %0 >= 256
%error 'Pattern size should be < 256'
%endif
%if PATTERN_SIZE == -1
%assign PATTERN_SIZE %0
%else
%if %0 != PATTERN_SIZE
%error 'All patterns should have the same length!'
%endif
%endif
%endmacro
%macro BEGIN_TRACKS 0
SECT_DATA(sutracks)
EXPORT MANGLE_DATA(su_tracks)
%define USE_PLAYER
%endmacro
%assign MAX_PATTERNS -1
%assign MAX_TRACKS 0
%assign VOICETRACK_BITMASK 0
%assign VOICETRACK_COUNT 0
%macro TRACK 2-* ; first param number of voices, rest are the patterns
%rep %0-1
db %2
%rotate 1
%endrep
%rotate 1
%if MAX_PATTERNS == -1
%assign MAX_PATTERNS %0-1
%else
%if %0-1 != MAX_PATTERNS
%error 'All tracks should have same number of patterns!'
%endif
%endif
%assign MAX_TRACKS MAX_TRACKS + 1
%if MAX_TRACKS > 32
%error Error: cannot have more than 32 tracks!
%endif
; increment MAX_TRACKS equal to %2 and construct the CHORD_BITMASK so that
; for every track except the last track of an instrument, the bit is on
%rep %1-1
%assign VOICETRACK_BITMASK VOICETRACK_BITMASK + (1 << VOICETRACK_COUNT)
%assign VOICETRACK_COUNT VOICETRACK_COUNT + 1
%endrep
%assign VOICETRACK_COUNT VOICETRACK_COUNT + 1 ; the last voice increment, without adding bit mask
%if VOICETRACK_COUNT > 32
%error Error: cannot have more than a total of 32 voices assigned to tracks.
%endif
%if %1 > 1
%define INCLUDE_MULTIVOICE_TRACKS
%endif
%endmacro
%define END_TRACKS
;-------------------------------------------------------------------------------
; unit struct
;-------------------------------------------------------------------------------
struc su_unit
.state resd 8
.ports resd 8
.size:
endstruc
;-------------------------------------------------------------------------------
; voice struct
;-------------------------------------------------------------------------------
struc su_voice
.note resd 1
.release resd 1
.inputs resd 8
.reserved resd 6 ; this is done to so the whole voice is 2^n long, see polyphonic player
.workspace resb MAX_UNITS * su_unit.size
.size:
endstruc
;-------------------------------------------------------------------------------
; synthworkspace struct
;-------------------------------------------------------------------------------
struc su_synthworkspace
.curvoices resb 32 ; these are used by the multitrack player to store which voice is playing on which track
.left resd 1
.right resd 1
.aux resd 6 ; 3 auxiliary signals
.voices resb ABSOLUTE_MAX_VOICES * su_voice.size
.size:
endstruc
%endif ; SOINTU_INC

View File

@ -1,125 +0,0 @@
;-------------------------------------------------------------------------------
; OUT opcode: outputs and pops the signal
;-------------------------------------------------------------------------------
; Mono: add ST0 to main left port
; Stereo: also add ST1 to main right port
;-------------------------------------------------------------------------------
%if OUT_ID > -1
SECT_TEXT(suopout)
EXPORT MANGLE_FUNC(su_op_out,0) ; l r
mov _AX, [_SP + su_stack.synth]
%ifdef INCLUDE_STEREO_OUT
jnc su_op_out_mono
call su_op_out_mono
add _AX, 4
su_op_out_mono:
%endif
fmul dword [INP + su_out_ports.gain] ; g*l
fadd dword [_AX + su_synthworkspace.left] ; g*l+o
fstp dword [_AX + su_synthworkspace.left] ; o'=g*l+o
ret
%endif ; SU_OUT_ID > -1
;-------------------------------------------------------------------------------
; OUTAUX opcode: outputs to main and aux1 outputs and pops the signal
;-------------------------------------------------------------------------------
; Mono: add outgain*ST0 to main left port and auxgain*ST0 to aux1 left
; Stereo: also add outgain*ST1 to main right port and auxgain*ST1 to aux1 right
;-------------------------------------------------------------------------------
%if OUTAUX_ID > -1
SECT_TEXT(suoutaux)
EXPORT MANGLE_FUNC(su_op_outaux,0) ; l r
mov _AX, [_SP + su_stack.synth]
%ifdef INCLUDE_STEREO_OUTAUX
jnc su_op_outaux_mono
call su_op_outaux_mono
add _AX, 4
su_op_outaux_mono:
%endif
fld st0 ; l l
fmul dword [INP + su_outaux_ports.outgain] ; g*l
fadd dword [_AX + su_synthworkspace.left] ; g*l+o
fstp dword [_AX + su_synthworkspace.left] ; o'=g*l+o
fmul dword [INP + su_outaux_ports.auxgain] ; h*l
fadd dword [_AX + su_synthworkspace.aux] ; h*l+a
fstp dword [_AX + su_synthworkspace.aux] ; a'=h*l+a
ret
%endif ; SU_OUTAUX_ID > -1
;-------------------------------------------------------------------------------
; AUX opcode: outputs the signal to aux (or main) port and pops the signal
;-------------------------------------------------------------------------------
; Mono: add gain*ST0 to left port
; Stereo: also add gain*ST1 to right port
;-------------------------------------------------------------------------------
%if AUX_ID > -1
SECT_TEXT(suopaux)
EXPORT MANGLE_FUNC(su_op_aux,0) ; l r
lodsb
mov _DI, [_SP + su_stack.synth]
%ifdef INCLUDE_STEREO_AUX
jnc su_op_aux_mono
call su_op_aux_mono
add _DI, 4
su_op_aux_mono:
%endif
fmul dword [INP + su_aux_ports.gain] ; g*l
fadd dword [_DI + su_synthworkspace.left + _AX*4] ; g*l+o
fstp dword [_DI + su_synthworkspace.left + _AX*4] ; o'=g*l+o
ret
%endif ; SU_AUX_ID > -1
;-------------------------------------------------------------------------------
; SEND opcode: adds the signal to a port
;-------------------------------------------------------------------------------
; Mono: adds signal to a memory address, defined by a word in VAL stream
; Stereo: also add right signal to the following address
;-------------------------------------------------------------------------------
%if SEND_ID > -1
SECT_TEXT(susend)
EXPORT MANGLE_FUNC(su_op_send,0)
lodsw
mov _CX, [_SP + su_stack.wrk]
%ifdef INCLUDE_STEREO_SEND
jnc su_op_send_mono
mov _DI, _AX
inc _AX ; send the right channel first
fxch ; r l
call su_op_send_mono ; (r) l
mov _AX, _DI ; move back to original address
test _AX, SENDPOPFLAG ; if r was not popped and is still in the stack
jnz su_op_send_mono
fxch ; swap them back: l r
su_op_send_mono:
%endif
%ifdef INCLUDE_GLOBAL_SEND
test _AX, SEND_GLOBAL
jz su_op_send_skipglobal
mov _CX, [_SP + su_stack.synth]
su_op_send_skipglobal:
%endif
test _AX, SENDPOPFLAG ; if the SEND_POP bit is not set
jnz su_op_send_skippush
fld st0 ; duplicate the signal on stack: s s
su_op_send_skippush: ; there is signal s, but maybe also another: s (s)
fld dword [INP+su_send_ports.amount] ; a l (l)
do fsub dword [,c_0_5,] ; a-.5 l (l)
fadd st0 ; g=2*a-1 l (l)
and _AX, 0x0000ffff - SENDPOPFLAG - SEND_GLOBAL ; eax = send address
fmulp st1, st0 ; g*l (l)
fadd dword [_CX + _AX*4] ; g*l+L (l),where L is the current value
fstp dword [_CX + _AX*4] ; (l)
ret
%endif ; SU_USE_SEND > -1

View File

@ -1,128 +0,0 @@
;-------------------------------------------------------------------------------
; OUT structs
;-------------------------------------------------------------------------------
%assign OUT_ID -1
%macro USE_OUT 0
%if OUT_ID == -1
%assign OUT_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_out,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_OUT 2
db %2
USE_OUT
%xdefine CMDS CMDS OUT_ID+%1,
%if %1 == 1
%define INCLUDE_STEREO_OUT
%endif
%endmacro
%define GAIN(val) val
struc su_out_ports
.gain resd 1
endstruc
;-------------------------------------------------------------------------------
; OUTAUX structs
;-------------------------------------------------------------------------------
%assign OUTAUX_ID -1
%macro USE_OUTAUX 0
%if OUTAUX_ID == -1
%assign OUTAUX_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_outaux,0),
%xdefine NUMPARAMS NUMPARAMS 2,
%endif
%endmacro
%macro SU_OUTAUX 3
db %2
db %3
USE_OUTAUX
%xdefine CMDS CMDS OUTAUX_ID+%1,
%if %1 == 1
%define INCLUDE_STEREO_OUTAUX
%endif
%endmacro
%define OUTGAIN(val) val
%define AUXGAIN(val) val
struc su_outaux_ports
.outgain resd 1
.auxgain resd 1
endstruc
;-------------------------------------------------------------------------------
; AUX defines
;-------------------------------------------------------------------------------
%assign AUX_ID -1
%macro USE_AUX 0
%if AUX_ID == -1
%assign AUX_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_aux,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_AUX 3
db %2
db %3
USE_AUX
%xdefine CMDS CMDS AUX_ID+%1,
%if %1 == 1
%define INCLUDE_STEREO_AUX
%endif
%endmacro
%define CHANNEL(val) val
struc su_aux_ports
.gain resd 1
endstruc
;-------------------------------------------------------------------------------
; SEND structs
;-------------------------------------------------------------------------------
%assign SEND_ID -1
%macro USE_SEND 0
%if SEND_ID == -1
%assign SEND_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_send,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_SEND 6 ; global send (params: STEREO, AMOUNT, VOICE, UNIT, PORT, SENDPOP)
db %2
%if (%3) > 0
dw SEND_GLOBAL + (su_synthworkspace.voices+(%3-1)*su_voice.size+su_voice.workspace+%4*su_unit.size + su_unit.ports)/4 + %5 + (SENDPOPFLAG * %6)
%define INCLUDE_GLOBAL_SEND
%else
dw ((%4+1)*su_unit.size + su_unit.ports)/4 + %5 + (SENDPOPFLAG * %6)
%endif
USE_SEND
%xdefine CMDS CMDS SEND_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_SEND
%endif
%endmacro
%define VOICE(val) val
%define UNIT(val) val
%define PORT(val) val
%define AMOUNT(val) val
%define OUTPORT 0
%define SENDPOP(val) val
%define SENDPOPFLAG 0x8000
%define SEND_GLOBAL 0x4000
struc su_send_ports
.amount resd 1
endstruc

View File

@ -1,422 +0,0 @@
;-------------------------------------------------------------------------------
; ENVELOPE opcode: pushes an ADSR envelope value on stack [0,1]
;-------------------------------------------------------------------------------
; Mono: push the envelope value on stack
; Stereo: push the envelope valeu on stack twice
;-------------------------------------------------------------------------------
%if ENVELOPE_ID > -1
SECT_TEXT(suenvelo)
EXPORT MANGLE_FUNC(su_op_envelope,0)
%ifdef INCLUDE_STEREO_ENVELOPE
jnc su_op_envelope_mono
call su_op_envelope_mono
fld st0
ret
su_op_envelope_mono:
%endif
mov eax, dword [INP-su_voice.inputs+su_voice.release] ; eax = su_instrument.release
test eax, eax ; if (eax == 0)
je su_op_envelope_process ; goto process
mov dword [WRK+su_env_work.state], ENV_STATE_RELEASE ; [state]=RELEASE
su_op_envelope_process:
mov eax, dword [WRK+su_env_work.state] ; al=[state]
fld dword [WRK+su_env_work.level] ; x=[level]
cmp al, ENV_STATE_SUSTAIN ; if (al==SUSTAIN)
je short su_op_envelope_leave2 ; goto leave2
su_op_envelope_attac:
cmp al, ENV_STATE_ATTAC ; if (al!=ATTAC)
jne short su_op_envelope_decay ; goto decay
call su_nonlinear_map ; a x, where a=attack
faddp st1, st0 ; a+x
fld1 ; 1 a+x
fucomi st1 ; if (a+x<=1) // is attack complete?
fcmovnb st0, st1 ; a+x a+x
jbe short su_op_envelope_statechange ; else goto statechange
su_op_envelope_decay:
cmp al, ENV_STATE_DECAY ; if (al!=DECAY)
jne short su_op_envelope_release ; goto release
call su_nonlinear_map ; d x, where d=decay
fsubp st1, st0 ; x-d
fld dword [INP+su_env_ports.sustain] ; s x-d, where s=sustain
fucomi st1 ; if (x-d>s) // is decay complete?
fcmovb st0, st1 ; x-d x-d
jnc short su_op_envelope_statechange ; else goto statechange
su_op_envelope_release:
cmp al, ENV_STATE_RELEASE ; if (al!=RELEASE)
jne short su_op_envelope_leave ; goto leave
call su_nonlinear_map ; r x, where r=release
fsubp st1, st0 ; x-r
fldz ; 0 x-r
fucomi st1 ; if (x-r>0) // is release complete?
fcmovb st0, st1 ; x-r x-r, then goto leave
jc short su_op_envelope_leave
su_op_envelope_statechange:
inc dword [WRK+su_env_work.state] ; [state]++
su_op_envelope_leave:
fstp st1 ; x', where x' is the new value
fst dword [WRK+su_env_work.level] ; [level]=x'
su_op_envelope_leave2:
fmul dword [INP+su_env_ports.gain] ; [gain]*x'
ret
%endif ; SU_USE_ENVELOPE
;-------------------------------------------------------------------------------
; NOISE opcode: creates noise
;-------------------------------------------------------------------------------
; Mono: push a random value [-1,1] value on stack
; Stereo: push two (differeent) random values on stack
;-------------------------------------------------------------------------------
%if NOISE_ID > -1
SECT_TEXT(sunoise)
EXPORT MANGLE_FUNC(su_op_noise,0)
mov _CX,_SP
%ifdef INCLUDE_STEREO_NOISE
jnc su_op_noise_mono
call su_op_noise_mono
su_op_noise_mono:
%endif
imul eax, [_CX + su_stack.randseed],16007
mov [_CX + su_stack.randseed],eax
fild dword [_CX + su_stack.randseed]
do fidiv dword [,c_RandDiv,]
fld dword [INP+su_noise_ports.shape]
call su_waveshaper
fld dword [INP+su_noise_ports.gain]
fmulp st1, st0
ret
%define SU_INCLUDE_WAVESHAPER
%endif
;-------------------------------------------------------------------------------
; OSCILLAT opcode: oscillator, the heart of the synth
;-------------------------------------------------------------------------------
; Mono: push oscillator value on stack
; Stereo: push l r on stack, where l has opposite detune compared to r
;-------------------------------------------------------------------------------
%if OSCILLAT_ID > -1
SECT_TEXT(suoscill)
EXPORT MANGLE_FUNC(su_op_oscillat,0)
lodsb ; load the flags
%ifdef RUNTIME_TABLES
%ifdef INCLUDE_SAMPLES
mov _DI, [_SP + su_stack.sampleoffs]; we need to put this in a register, as the stereo & unisons screw the stack positions
%endif ; ain't we lucky that _DI was unused throughout
%endif
fld dword [INP+su_osc_ports.detune] ; e, where e is the detune [0,1]
do fsub dword [,c_0_5,] ; e-.5
fadd st0, st0 ; d=2*e-.5, where d is the detune [-1,1]
%ifdef INCLUDE_STEREO_OSCILLAT
jnc su_op_oscillat_mono
fld st0 ; d d
call su_op_oscillat_mono ; r d
add WRK, 4 ; state vars: r1 l1 r2 l2 r3 l3 r4 l4, for the unison osc phases
fxch ; d r
fchs ; -d r, negate the detune for second round
su_op_oscillat_mono:
%endif
%ifdef INCLUDE_UNISONS
push_registers _AX, WRK, _AX
fldz ; 0 d
fxch ; d a=0, "accumulated signal"
su_op_oscillat_unison_loop:
fst dword [_SP] ; save the current detune, d. We could keep it in fpu stack but it was getting big.
call su_op_oscillat_single ; s a
faddp st1, st0 ; a+=s
test al, UNISONBITS
je su_op_oscillat_unison_out
add WRK, 8
fld dword [INP+su_osc_ports.phaseofs] ; p s
do fadd dword [,c_i12,] ; p s, add some little phase offset to unison oscillators so they don't start in sync
fstp dword [INP+su_osc_ports.phaseofs] ; s note that this changes the phase for second, possible stereo run. That's probably ok
fld dword [_SP] ; d s
do fmul dword [,c_0_5,] ; .5*d s // negate and halve the detune of each oscillator
fchs ; -.5*d s // negate and halve the detune of each oscillator
dec eax
jmp short su_op_oscillat_unison_loop
su_op_oscillat_unison_out:
pop_registers _AX, WRK, _AX
ret
su_op_oscillat_single:
%endif
fld dword [INP+su_osc_ports.transpose]
do fsub dword [,c_0_5,]
do fdiv dword [,c_i128,]
faddp st1
test al, byte LFOFLAG
jnz su_op_oscillat_skipnote
fiadd dword [INP-su_voice.inputs+su_voice.note] ; // st0 is note, st1 is t+d offset
su_op_oscillat_skipnote:
do fmul dword [,c_i12,]
call MANGLE_FUNC(su_power,0)
test al, byte LFOFLAG
jz short su_op_oscillat_normalize_note
do fmul dword [,c_lfo_normalize,] ; // st0 is now frequency for lfo
jmp short su_op_oscillat_normalized
su_op_oscillat_normalize_note:
do fmul dword [,c_freq_normalize,] ; // st0 is now frequency
su_op_oscillat_normalized:
fadd dword [WRK+su_osc_wrk.phase]
fst dword [WRK+su_osc_wrk.phase]
fadd dword [INP+su_osc_ports.phaseofs]
%ifdef INCLUDE_SAMPLES
test al, byte SAMPLE
jz short su_op_oscillat_not_sample
call su_oscillat_sample
jmp su_op_oscillat_shaping ; skip the rest to avoid color phase normalization and colorloading
su_op_oscillat_not_sample:
%endif
fld1
fadd st1, st0
fxch
fprem
fstp st1
fld dword [INP+su_osc_ports.color] ; // c p
; every oscillator test included if needed
%ifdef INCLUDE_SINE
test al, byte SINE
jz short su_op_oscillat_notsine
call su_oscillat_sine
su_op_oscillat_notsine:
%endif
%ifdef INCLUDE_TRISAW
test al, byte TRISAW
jz short su_op_oscillat_not_trisaw
call su_oscillat_trisaw
su_op_oscillat_not_trisaw:
%endif
%ifdef INCLUDE_PULSE
test al, byte PULSE
jz short su_op_oscillat_not_pulse
call su_oscillat_pulse
su_op_oscillat_not_pulse:
%endif
%ifdef INCLUDE_GATE
test al, byte GATE
jz short su_op_oscillat_not_gate
call su_oscillat_gate
jmp su_op_oscillat_gain ; skip waveshaping as the shape parameter is reused for gateshigh
su_op_oscillat_not_gate:
%endif
su_op_oscillat_shaping:
; finally, shape the oscillator and apply gain
fld dword [INP+su_osc_ports.shape]
call su_waveshaper
su_op_oscillat_gain:
fld dword [INP+su_osc_ports.gain]
fmulp st1, st0
ret
%define SU_INCLUDE_WAVESHAPER
%endif
; PULSE
%ifdef INCLUDE_PULSE
SECT_TEXT(supulse)
su_oscillat_pulse:
fucomi st1 ; // c p
fld1
jnc short su_oscillat_pulse_up ; // +1 c p
fchs ; // -1 c p
su_oscillat_pulse_up:
fstp st1 ; // +-1 p
fstp st1 ; // +-1
ret
%endif
; TRISAW
%ifdef INCLUDE_TRISAW
SECT_TEXT(sutrisaw)
su_oscillat_trisaw:
fucomi st1 ; // c p
jnc short su_oscillat_trisaw_up
fld1 ; // 1 c p
fsubr st2, st0 ; // 1 c 1-p
fsubrp st1, st0 ; // 1-c 1-p
su_oscillat_trisaw_up:
fdivp st1, st0 ; // tp'/tc
fadd st0 ; // 2*''
fld1 ; // 1 2*''
fsubp st1, st0 ; // 2*''-1
ret
%endif
; SINE
%ifdef INCLUDE_SINE
SECT_TEXT(susine)
su_oscillat_sine:
fucomi st1 ; // c p
jnc short su_oscillat_sine_do
fstp st1
fsub st0, st0 ; // 0
ret
su_oscillat_sine_do:
fdivp st1, st0 ; // p/c
fldpi ; // pi p
fadd st0 ; // 2*pi p
fmulp st1, st0 ; // 2*pi*p
fsin ; // sin(2*pi*p)
ret
%endif
%ifdef INCLUDE_GATE
SECT_TEXT(sugate)
su_oscillat_gate:
fxch ; p c
fstp st1 ; p
do fmul dword [,c_16,] ; 16*p
push _AX
push _AX
fistp dword [_SP] ; s=int(16*p), stack empty
fld1 ; 1
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
fsub st0, st0 ; stack: 0
go4kVCO_gate_bit: ; stack: 0/1, let's call it x
fld dword [WRK+su_osc_wrk.gatestate] ; g x, g is gatestate, x is the input to this filter 0/1
fsub st1 ; g-x x
do fmul dword [,c_dc_const,] ; c(g-x) x
faddp st1, st0 ; x+c(g-x)
fst dword [WRK+su_osc_wrk.gatestate]; g'=x+c(g-x)
pop _AX ; Another way to see this (c~0.996)
ret ; g'=cg+(1-c)x
; This is a low-pass to smooth the gate transitions
%define USE_C_16
%define USE_C_DC_CONST
%endif
; SAMPLES
%ifdef INCLUDE_SAMPLES
SECT_TEXT(suoscsam)
su_oscillat_sample: ; p
push_registers _AX,_DX,_CX,_BX ; edx must be saved, eax & ecx if this is stereo osc
push _AX
mov al, byte [VAL-4] ; reuse "color" as the sample number
%ifdef RUNTIME_TABLES ; when using RUNTIME_TABLES, assumed the sample_offset ptr is in _DI
do{lea _DI, [}, _DI, _AX*8,] ; edi points now to the sample table entry
%else
do{lea _DI, [}, MANGLE_DATA(su_sample_offsets), _AX*8,]; edi points now to the sample table entry
%endif
do fmul dword [,c_samplefreq_scaling,] ; p*r
fistp dword [_SP]
pop _DX ; edx is now the sample number
movzx ebx, word [_DI + su_sample_offset.loopstart] ; ecx = loopstart
sub edx, ebx ; if sample number < loop start
jl su_oscillat_sample_not_looping ; then we're not looping yet
mov eax, edx ; eax = sample number
movzx ecx, word [_DI + su_sample_offset.looplength] ; edi is now the loop length
xor edx, edx ; div wants edx to be empty
div ecx ; edx is now the remainder
su_oscillat_sample_not_looping:
add edx, ebx ; sampleno += loopstart
add edx, dword [_DI + su_sample_offset.start]
do fild word [,MANGLE_DATA(su_sample_table),_DX*2,]
do fdiv dword [,c_32767,]
pop_registers _AX,_DX,_CX,_BX
ret
%define USE_C_32767
%define USE_C_SAMPLEFREQ_SCALING
%endif
;-------------------------------------------------------------------------------
; LOADVAL opcode
;-------------------------------------------------------------------------------
; Mono: push 2*v-1 on stack, where v is the input to port "value"
; Stereo: push 2*v-1 twice on stack
;-------------------------------------------------------------------------------
%if LOADVAL_ID > -1
SECT_TEXT(suloadvl)
EXPORT MANGLE_FUNC(su_op_loadval,0)
%ifdef INCLUDE_STEREO_LOADVAL
jnc su_op_loadval_mono
call su_op_loadval_mono
su_op_loadval_mono:
%endif
fld dword [INP+su_load_val_ports.value] ; v
do fsub dword [,c_0_5,]
fadd st0 ; 2*v-1
ret
%endif ; SU_USE_LOAD_VAL
;-------------------------------------------------------------------------------
; RECEIVE opcode
;-------------------------------------------------------------------------------
; Mono: push l on stack, where l is the left channel received
; Stereo: push l r on stack
;-------------------------------------------------------------------------------
%if RECEIVE_ID > -1
SECT_TEXT(sureceiv)
EXPORT MANGLE_FUNC(su_op_receive,0)
lea _DI, [WRK+su_unit.ports]
%ifdef INCLUDE_STEREO_RECEIVE
jnc su_op_receive_mono
xor ecx,ecx
fld dword [_DI+su_receive_ports.right]
mov dword [_DI+su_receive_ports.right],ecx
su_op_receive_mono:
%endif
xor ecx,ecx
fld dword [_DI+su_receive_ports.left]
mov dword [_DI+su_receive_ports.left],ecx
ret
%endif ; RECEIVE_ID > -1
;-------------------------------------------------------------------------------
; IN opcode: inputs and clears a global port
;-------------------------------------------------------------------------------
; Mono: push the left channel of a global port (out or aux)
; Stereo: also push the right channel (stack in l r order)
;-------------------------------------------------------------------------------
%if IN_ID > -1
SECT_TEXT(suopin)
EXPORT MANGLE_FUNC(su_op_in,0)
lodsb
mov _DI, [_SP + su_stack.synth]
%ifdef INCLUDE_STEREO_IN
jnc su_op_in_mono
xor ecx, ecx ; we cannot xor before jnc, so we have to do it mono & stereo. LAHF / SAHF could do it, but is the same number of bytes with more entropy
fld dword [_DI + su_synthworkspace.right + _AX*4]
mov dword [_DI + su_synthworkspace.right + _AX*4], ecx
su_op_in_mono:
%endif
xor ecx, ecx
fld dword [_DI + su_synthworkspace.left + _AX*4]
mov dword [_DI + su_synthworkspace.left + _AX*4], ecx
ret
%endif ; SU_IN_ID > -1

View File

@ -1,262 +0,0 @@
;-------------------------------------------------------------------------------
; ENVELOPE structs
;-------------------------------------------------------------------------------
%assign ENVELOPE_ID -1
%macro USE_ENVELOPE 0
%if ENVELOPE_ID == -1
%assign ENVELOPE_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_envelope,0),
%xdefine NUMPARAMS NUMPARAMS 5,
%endif
%endmacro
%macro SU_ENVELOPE 6
db %2
db %3
db %4
db %5
db %6
USE_ENVELOPE
%xdefine CMDS CMDS ENVELOPE_ID+%1,
%if %1 == 1
%define INCLUDE_STEREO_ENVELOPE
%endif
%endmacro
%define ATTACK(val) val
%define DECAY(val) val
%define SUSTAIN(val) val
%define RELEASE(val) val
%define GAIN(val) val
struc su_env_ports
.attac resd 1
.decay resd 1
.sustain resd 1
.release resd 1
.gain resd 1
endstruc
struc su_env_work
.state resd 1
.level resd 1
endstruc
%define ENV_STATE_ATTAC 0
%define ENV_STATE_DECAY 1
%define ENV_STATE_SUSTAIN 2
%define ENV_STATE_RELEASE 3
%define ENV_STATE_OFF 4
;-------------------------------------------------------------------------------
; OSCILLAT structs
;-------------------------------------------------------------------------------
%assign OSCILLAT_ID -1
%macro USE_OSCILLAT 0
%if OSCILLAT_ID == -1
%assign OSCILLAT_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_oscillat,0),
%xdefine NUMPARAMS NUMPARAMS 6,
%endif
%endmacro
%define SAMPLE 0x80
%define SINE 0x40
%define TRISAW 0x20
%define PULSE 0x10
%define LFOFLAG 0x08
%define GATE 0x04
%define UNISONBITS 0x03
%macro SU_OSCILLATOR 10
db %2
db %3
db %4
db %5
db %6
db %7
USE_OSCILLAT
%xdefine CMDS CMDS OSCILLAT_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_OSCILLAT
%endif
%if %8 == 0 ; 0 = SINE
db SINE + (LFOFLAG * %9) + %10 ; TYPE + LFO + UNISON
%define INCLUDE_SINE
%elif %8 == 1 ; 1 = TRISAW
db TRISAW + (LFOFLAG * %9) + %10 ; TYPE + LFO + UNISON
%define INCLUDE_TRISAW
%elif %8 == 2
db PULSE + (LFOFLAG * %9) + %10 ; TYPE + LFO + UNISON
%define INCLUDE_PULSE
%elif %8 == 3
db GATE + (LFOFLAG * %9) + %10 ; TYPE + LFO + UNISON
%define INCLUDE_GATE
%elif %8 == 4
db SAMPLE + (LFOFLAG * %9) + %10 ; TYPE + LFO + UNISON
%define INCLUDE_SAMPLES
%else
%error Unknown oscillator type!
%endif
%if (%10) > 0
%define INCLUDE_UNISONS
%endif
%endmacro
struc su_osc_ports
.transpose resd 1
.detune resd 1
.phaseofs resd 1
.color resd 1
.shape resd 1
.gain resd 1
endstruc
struc su_osc_wrk
.phase resd 1
.gatestate equ 16 ; we put is late so only UNISON3 and UNISON4 are unusable with gate
endstruc
%define TRANSPOSE(val) val
%define DETUNE(val) val
%define PHASE(val) val
%define COLOR(val) val
%define SHAPE(val) val
%define TYPE(val) val
%define LFO(val) val
%define UNISON(val) val
%define FLAGS(val) val
;-------------------------------------------------------------------------------
; Sample related defines
;-------------------------------------------------------------------------------
%macro BEGIN_SAMPLE_OFFSETS 0
SECT_DATA(susamoff)
EXPORT MANGLE_DATA(su_sample_offsets)
%endmacro
%macro SAMPLE_OFFSET 3
dd %1
dw %2
dw %3
%endmacro
%define START(val) val
%define LOOPSTART(val) val
%define LOOPLENGTH(val) val
%define END_SAMPLE_OFFSETS
struc su_sample_offset ; length conveniently 8, so easy to index
.start resd 1
.loopstart resw 1
.looplength resw 1
endstruc
;-------------------------------------------------------------------------------
; NOISE structs
;-------------------------------------------------------------------------------
%assign NOISE_ID -1
%macro USE_NOISE 0
%if NOISE_ID == -1
%assign NOISE_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_noise,0),
%xdefine NUMPARAMS NUMPARAMS 2,
%endif
%endmacro
%macro SU_NOISE 3
db %2
db %3
USE_NOISE
%xdefine CMDS CMDS NOISE_ID + %1,
%if %1 == 1
%define INCLUDE_STEREO_NOISE
%endif
%endmacro
struc su_noise_ports
.shape resd 1
.gain resd 1
endstruc
;-------------------------------------------------------------------------------
; LOAD_VAL structs
;-------------------------------------------------------------------------------
%assign LOADVAL_ID -1
%macro USE_LOAD_VAL 0
%if LOADVAL_ID == -1
%assign LOADVAL_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_loadval,0),
%xdefine NUMPARAMS NUMPARAMS 1,
%endif
%endmacro
%macro SU_LOADVAL 2
db %2
USE_LOAD_VAL
%xdefine CMDS CMDS LOADVAL_ID+%1,
%if %1 == 1
%define INCLUDE_STEREO_LOADVAL
%endif
%endmacro
%define VALUE(val) val
struc su_load_val_ports
.value resd 1
endstruc
;-------------------------------------------------------------------------------
; RECEIVE structs
;-------------------------------------------------------------------------------
%assign RECEIVE_ID -1
%macro USE_RECEIVE 0
%if RECEIVE_ID == -1
%assign RECEIVE_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_receive,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_RECEIVE 1
USE_RECEIVE
%xdefine CMDS CMDS RECEIVE_ID+%1,
%if %1 == 1
%define INCLUDE_STEREO_RECEIVE
%endif
%endmacro
struc su_receive_ports
.left resd 1
.right resd 1
endstruc
;-------------------------------------------------------------------------------
; IN defines
;-------------------------------------------------------------------------------
%assign IN_ID -1
%macro USE_IN 0
%if IN_ID == -1
%assign IN_ID CUR_ID
%assign CUR_ID CUR_ID + 2
%xdefine OPCODES OPCODES MANGLE_FUNC(su_op_in,0),
%xdefine NUMPARAMS NUMPARAMS 0,
%endif
%endmacro
%macro SU_IN 2
db %2
USE_IN
%xdefine CMDS CMDS IN_ID+%1,
%if %1 == 1
%define INCLUDE_STEREO_IN
%endif
%endmacro

View File

@ -1,41 +0,0 @@
%ifdef INCLUDE_SAMPLES
%define SAMPLE_TABLE_SIZE 3440660 ; size of gmdls
extern _OpenFile@12 ; requires windows
extern _ReadFile@20 ; requires windows
SECT_TEXT(sugmdls)
EXPORT MANGLE_FUNC(su_load_gmdls,0)
mov edx, MANGLE_DATA(su_sample_table)
mov ecx, su_gmdls_path1
su_gmdls_pathloop:
push 0 ; OF_READ
push edx ; &ofstruct, blatantly reuse the sample table
push ecx ; path
call _OpenFile@12 ; eax = OpenFile(path,&ofstruct,OF_READ)
add ecx, su_gmdls_path2 - su_gmdls_path1 ; if we ever get to third, then crash
cmp eax, -1 ; eax == INVALID?
je su_gmdls_pathloop
push 0 ; NULL
push edx ; &bytes_read, reusing sample table again; it does not matter that the first four bytes are trashed
push SAMPLE_TABLE_SIZE ; number of bytes to read
push edx ; here we actually pass the sample table to readfile
push eax ; handle to file
call _ReadFile@20 ; Readfile(handle,&su_sample_table,SAMPLE_TABLE_SIZE,&bytes_read,NULL)
ret
SECT_DATA(sugmpath)
su_gmdls_path1:
db 'drivers/gm.dls',0
su_gmdls_path2:
db 'drivers/etc/gm.dls',0
SECT_BSS(susamtbl)
EXPORT MANGLE_DATA(su_sample_table)
resb SAMPLE_TABLE_SIZE ; size of gmdls.
%endif

View File

@ -1,45 +0,0 @@
%ifdef INCLUDE_SAMPLES
%define SAMPLE_TABLE_SIZE 3440660 ; size of gmdls
extern OpenFile ; requires windows
extern ReadFile ; requires windows
SECT_TEXT(sugmdls)
EXPORT MANGLE_FUNC(su_load_gmdls,0)
; Win64 ABI: RCX, RDX, R8, and R9
sub rsp, 40 ; Win64 ABI requires "shadow space" + space for one parameter.
mov rdx, PTRWORD MANGLE_DATA(su_sample_table)
mov rcx, PTRWORD su_gmdls_path1
su_gmdls_pathloop:
xor r8,r8 ; OF_READ
push rdx ; &ofstruct, blatantly reuse the sample table
push rcx
call OpenFile ; eax = OpenFile(path,&ofstruct,OF_READ)
pop rcx
add rcx, su_gmdls_path2 - su_gmdls_path1 ; if we ever get to third, then crash
pop rdx
cmp eax, -1 ; ecx == INVALID?
je su_gmdls_pathloop
movsxd rcx, eax
mov qword [rsp+32],0
mov r9, rdx
mov r8d, SAMPLE_TABLE_SIZE ; number of bytes to read
call ReadFile ; Readfile(handle,&su_sample_table,SAMPLE_TABLE_SIZE,&bytes_read,NULL)
add rsp, 40 ; shadow space, as required by Win64 ABI
ret
SECT_DATA(sugmpath)
su_gmdls_path1:
db 'drivers/gm.dls',0
su_gmdls_path2:
db 'drivers/etc/gm.dls',0
SECT_BSS(susamtbl)
EXPORT MANGLE_DATA(su_sample_table)
resb SAMPLE_TABLE_SIZE ; size of gmdls.
%endif

View File

@ -1,304 +0,0 @@
; source file for compiling sointu as a library
%define SU_DISABLE_PLAYER
%include "sointu/header.inc"
; use every opcode
USE_ADD
USE_ADDP
USE_POP
USE_LOADNOTE
USE_MUL
USE_MULP
USE_PUSH
USE_XCH
USE_DISTORT
USE_HOLD
USE_CRUSH
USE_GAIN
USE_INVGAIN
USE_FILTER
USE_CLIP
USE_PAN
USE_DELAY
USE_COMPRES
USE_SPEED
USE_OUT
USE_OUTAUX
USE_AUX
USE_SEND
USE_ENVELOPE
USE_NOISE
USE_OSCILLAT
USE_LOAD_VAL
USE_RECEIVE
USE_IN
; include stereo variant of each opcode
%define INCLUDE_STEREO_ADD
%define INCLUDE_STEREO_ADDP
%define INCLUDE_STEREO_POP
%define INCLUDE_STEREO_LOADNOTE
%define INCLUDE_STEREO_MUL
%define INCLUDE_STEREO_MULP
%define INCLUDE_STEREO_PUSH
%define INCLUDE_STEREO_XCH
%define INCLUDE_STEREO_DISTORT
%define INCLUDE_STEREO_HOLD
%define INCLUDE_STEREO_CRUSH
%define INCLUDE_STEREO_GAIN
%define INCLUDE_STEREO_INVGAIN
%define INCLUDE_STEREO_FILTER
%define INCLUDE_STEREO_CLIP
%define INCLUDE_STEREO_PAN
%define INCLUDE_STEREO_DELAY
%define INCLUDE_STEREO_COMPRES
%define INCLUDE_STEREO_SPEED
%define INCLUDE_STEREO_OUT
%define INCLUDE_STEREO_OUTAUX
%define INCLUDE_STEREO_AUX
%define INCLUDE_STEREO_SEND
%define INCLUDE_STEREO_ENVELOPE
%define INCLUDE_STEREO_NOISE
%define INCLUDE_STEREO_OSCILLAT
%define INCLUDE_STEREO_LOADVAL
%define INCLUDE_STEREO_RECEIVE
%define INCLUDE_STEREO_IN
; include all features inside all opcodes
%define INCLUDE_TRISAW
%define INCLUDE_SINE
%define INCLUDE_PULSE
%define INCLUDE_GATE
%define INCLUDE_UNISONS
%define INCLUDE_POLYPHONY
%define INCLUDE_MULTIVOICE_TRACKS
%define INCLUDE_DELAY_MODULATION
%define INCLUDE_LOWPASS
%define INCLUDE_BANDPASS
%define INCLUDE_HIGHPASS
%define INCLUDE_NEGBANDPASS
%define INCLUDE_NEGHIGHPASS
%define INCLUDE_GLOBAL_SEND
%define INCLUDE_DELAY_NOTETRACKING
%define INCLUDE_DELAY_FLOAT_TIME
%ifidn __OUTPUT_FORMAT__,win32
%define INCLUDE_SAMPLES
%define INCLUDE_GMDLS
%endif
%ifidn __OUTPUT_FORMAT__,win64
%define INCLUDE_SAMPLES
%define INCLUDE_GMDLS
%endif
%include "sointu/footer.inc"
section .text
struc su_sampleoff
.start resd 1
.loopstart resw 1
.looplength resw 1
.size:
endstruc
struc su_synth
.synthwrk resb su_synthworkspace.size
.delaywrks resb su_delayline_wrk.size * 64
.delaytimes resw 768
.sampleoffs resb su_sampleoff.size * 256
.randseed resd 1
.globaltime resd 1
.commands resb 32 * 64
.values resb 32 * 64 * 8
.polyphony resd 1
.numvoices resd 1
endstruc
SECT_TEXT(sursampl)
EXPORT MANGLE_FUNC(su_render,16)
%if BITS == 32 ; stdcall
pushad ; push registers
mov ecx, [esp + 4 + 32] ; ecx = &synthState
mov edx, [esp + 8 + 32] ; edx = &buffer
mov esi, [esp + 12 + 32] ; esi = &samples
mov ebx, [esp + 16 + 32] ; ebx = &time
%else
%ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rcx = &synth, rdx = &buffer, r8 = &bufsize, r9 = &time
push_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
mov rsi, r8 ; rsi = &samples
mov rbx, r9 ; rbx = &time
%else ; System V ABI: rdi = &synth, rsi = &buffer, rdx = &samples, rcx = &time
push_registers rbx, rbp ; System V ABI: these registers are non-volatile
mov rbx, rcx ; rbx points to time
xchg rsi, rdx ; rdx points to buffer, rsi points to samples
mov rcx, rdi ; rcx = &Synthstate
%endif
%endif
sub _SP,108 ; allocate space on stack for the FPU state
fsave [_SP] ; save the FPU state to stack & reset the FPU
push _SI ; push the pointer to samples
push _BX ; push the pointer to time
xor eax, eax ; samplenumber starts at 0
push _AX ; push samplenumber to stack
mov esi, [_SI] ; zero extend dereferenced pointer
push _SI ; push bufsize
push _DX ; push bufptr
push _CX ; this takes place of the voicetrack
lea _AX, [_CX + su_synth.sampleoffs]
push _AX
lea _AX, [_CX + su_synth.delaytimes]
push _AX
mov eax, [_CX + su_synth.randseed]
push _AX ; randseed
mov eax, [_CX + su_synth.globaltime]
push _AX ; global tick time
mov ebx, dword [_BX] ; zero extend dereferenced pointer
push _BX ; the nominal rowlength should be time_in
xor eax, eax ; rowtick starts at 0
su_render_samples_loop:
push _DI
fnstsw [_SP] ; store the FPU status flag to stack top
pop _DI ; _DI = FPU status flag
and _DI, 0011100001000101b ; mask TOP pointer, stack error, zero divide and invalid operation
test _DI,_DI ; all the aforementioned bits should be 0!
jne su_render_samples_time_finish ; otherwise, we exit due to error
cmp eax, [_SP] ; if rowtick >= maxtime
jge su_render_samples_time_finish ; goto finish
mov ecx, [_SP + PTRSIZE*7] ; ecx = buffer length in samples
cmp [_SP + PTRSIZE*8], ecx ; if samples >= maxsamples
jge su_render_samples_time_finish ; goto finish
inc eax ; time++
inc dword [_SP + PTRSIZE*8] ; samples++
mov _CX, [_SP + PTRSIZE*5]
push _AX ; push rowtick
mov eax, [_CX + su_synth.polyphony]
push _AX ;polyphony
mov eax, [_CX + su_synth.numvoices]
push _AX ;numvoices
lea _DX, [_CX+ su_synth.synthwrk]
lea COM, [_CX+ su_synth.commands]
lea VAL, [_CX+ su_synth.values]
lea WRK, [_DX + su_synthworkspace.voices]
lea _CX, [_CX+ su_synth.delaywrks - su_delayline_wrk.filtstate]
call MANGLE_FUNC(su_run_vm,0)
pop _AX
pop _AX
mov _DI, [_SP + PTRSIZE*7] ; edi containts buffer ptr
mov _CX, [_SP + PTRSIZE*6]
lea _SI, [_CX + su_synth.synthwrk + su_synthworkspace.left]
movsd ; copy left channel to output buffer
movsd ; copy right channel to output buffer
mov [_SP + PTRSIZE*7], _DI ; save back the updated ptr
lea _DI, [_SI-8]
xor eax, eax
stosd ; clear left channel so the VM is ready to write them again
stosd ; clear right channel so the VM is ready to write them again
pop _AX
inc dword [_SP + PTRSIZE] ; increment global time, used by delays
jmp su_render_samples_loop
su_render_samples_time_finish:
pop _CX
pop _BX
pop _DX
pop _CX ; discard delaytimes ptr
pop _CX ; discard samplesoffs ptr
pop _CX
mov [_CX + su_synth.randseed], edx
mov [_CX + su_synth.globaltime], ebx
pop _BX
pop _BX
pop _DX
pop _BX ; pop the pointer to time
pop _SI ; pop the pointer to samples
mov dword [_SI], edx ; *samples = samples rendered
mov dword [_BX], eax ; *time = time ticks rendered
mov _AX,_DI ; _DI was the masked FPU status flag, _AX is return value
frstor [_SP] ; restore fpu state
add _SP,108 ; rewind the stack allocate for FPU state
%if BITS == 32 ; stdcall
mov [_SP + 28],eax ; we want to return eax, but popad pops everything, so put eax to stack for popad to pop
popad
ret 16
%else
%ifidn __OUTPUT_FORMAT__,win64
pop_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
%else
pop_registers rbx, rbp ; System V ABI: these registers are non-volatile
%endif
ret
%endif
SECT_DATA(opcodeid)
; Arithmetic opcode ids
EXPORT MANGLE_DATA(su_add_id)
dd ADD_ID
EXPORT MANGLE_DATA(su_addp_id)
dd ADDP_ID
EXPORT MANGLE_DATA(su_pop_id)
dd POP_ID
EXPORT MANGLE_DATA(su_loadnote_id)
dd LOADNOTE_ID
EXPORT MANGLE_DATA(su_mul_id)
dd MUL_ID
EXPORT MANGLE_DATA(su_mulp_id)
dd MULP_ID
EXPORT MANGLE_DATA(su_push_id)
dd PUSH_ID
EXPORT MANGLE_DATA(su_xch_id)
dd XCH_ID
; Effect opcode ids
EXPORT MANGLE_DATA(su_distort_id)
dd DISTORT_ID
EXPORT MANGLE_DATA(su_hold_id)
dd HOLD_ID
EXPORT MANGLE_DATA(su_crush_id)
dd CRUSH_ID
EXPORT MANGLE_DATA(su_gain_id)
dd GAIN_ID
EXPORT MANGLE_DATA(su_invgain_id)
dd INVGAIN_ID
EXPORT MANGLE_DATA(su_filter_id)
dd FILTER_ID
EXPORT MANGLE_DATA(su_clip_id)
dd CLIP_ID
EXPORT MANGLE_DATA(su_pan_id)
dd PAN_ID
EXPORT MANGLE_DATA(su_delay_id)
dd DELAY_ID
EXPORT MANGLE_DATA(su_compres_id)
dd COMPRES_ID
; Flowcontrol opcode ids
EXPORT MANGLE_DATA(su_advance_id)
dd SU_ADVANCE_ID
EXPORT MANGLE_DATA(su_speed_id)
dd SPEED_ID
; Sink opcode ids
EXPORT MANGLE_DATA(su_out_id)
dd OUT_ID
EXPORT MANGLE_DATA(su_outaux_id)
dd OUTAUX_ID
EXPORT MANGLE_DATA(su_aux_id)
dd AUX_ID
EXPORT MANGLE_DATA(su_send_id)
dd SEND_ID
; Source opcode ids
EXPORT MANGLE_DATA(su_envelope_id)
dd ENVELOPE_ID
EXPORT MANGLE_DATA(su_noise_id)
dd NOISE_ID
EXPORT MANGLE_DATA(su_oscillat_id)
dd OSCILLAT_ID
EXPORT MANGLE_DATA(su_loadval_id)
dd LOADVAL_ID
EXPORT MANGLE_DATA(su_receive_id)
dd RECEIVE_ID
EXPORT MANGLE_DATA(su_in_id)
dd IN_ID

View File

@ -1,4 +1,4 @@
{{- if .Opcode "pop"}} {{- if .HasOp "pop"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; POP opcode: remove (discard) the topmost signal from the stack ; POP opcode: remove (discard) the topmost signal from the stack
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -24,7 +24,7 @@ su_op_pop_mono:
{{end}} {{end}}
{{- if .Opcode "add"}} {{- if .HasOp "add"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; ADD opcode: add the two top most signals on the stack ; ADD opcode: add the two top most signals on the stack
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -58,7 +58,7 @@ su_op_add_mono:
{{end}} {{end}}
{{- if .Opcode "addp"}} {{- if .HasOp "addp"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; ADDP opcode: add the two top most signals on the stack and pop ; ADDP opcode: add the two top most signals on the stack and pop
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -84,7 +84,7 @@ su_op_addp_mono:
{{end}} {{end}}
{{- if .Opcode "loadnote"}} {{- if .HasOp "loadnote"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; LOADNOTE opcode: load the current note, scaled to [-1,1] ; LOADNOTE opcode: load the current note, scaled to [-1,1]
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -109,7 +109,7 @@ su_op_addp_mono:
{{end}} {{end}}
{{- if .Opcode "mul"}} {{- if .HasOp "mul"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; MUL opcode: multiply the two top most signals on the stack ; MUL opcode: multiply the two top most signals on the stack
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -129,7 +129,7 @@ su_op_mul_mono:
{{end}} {{end}}
{{- if .Opcode "mulp"}} {{- if .HasOp "mulp"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; MULP opcode: multiply the two top most signals on the stack and pop ; MULP opcode: multiply the two top most signals on the stack and pop
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -155,7 +155,7 @@ su_op_mulp_mono:
{{end}} {{end}}
{{- if .Opcode "push"}} {{- if .HasOp "push"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; PUSH opcode: push the topmost signal on the stack ; PUSH opcode: push the topmost signal on the stack
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -181,7 +181,7 @@ su_op_push_mono:
{{end}} {{end}}
{{- if .Opcode "xch"}} {{- if .HasOp "xch"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; XCH opcode: exchange the signals on the stack ; XCH opcode: exchange the signals on the stack
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------

View File

@ -1,4 +1,4 @@
{{- if .Opcode "distort"}} {{- if .HasOp "distort"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; DISTORT opcode: apply distortion on the signal ; DISTORT opcode: apply distortion on the signal
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -14,7 +14,7 @@
{{end}} {{end}}
{{- if .Opcode "hold"}} {{- if .HasOp "hold"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; HOLD opcode: sample and hold the signal, reducing sample rate ; HOLD opcode: sample and hold the signal, reducing sample rate
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -46,7 +46,7 @@ su_op_hold_holding:
{{end}} {{end}}
{{- if .Opcode "crush"}} {{- if .HasOp "crush"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; CRUSH opcode: quantize the signal to finite number of levels ; CRUSH opcode: quantize the signal to finite number of levels
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -64,7 +64,7 @@ su_op_hold_holding:
{{end}} {{end}}
{{- if .Opcode "gain"}} {{- if .HasOp "gain"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; GAIN opcode: apply gain on the signal ; GAIN opcode: apply gain on the signal
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -88,7 +88,7 @@ su_op_gain_mono:
{{end}} {{end}}
{{- if .Opcode "invgain"}} {{- if .HasOp "invgain"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; INVGAIN opcode: apply inverse gain on the signal ; INVGAIN opcode: apply inverse gain on the signal
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -112,7 +112,7 @@ su_op_invgain_mono:
{{end}} {{end}}
{{- if .Opcode "filter"}} {{- if .HasOp "filter"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; FILTER opcode: perform low/high/band-pass/notch etc. filtering on the signal ; FILTER opcode: perform low/high/band-pass/notch etc. filtering on the signal
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -139,31 +139,31 @@ su_op_invgain_mono:
fadd dword [{{.WRK}}+8] ; f2*h'+b fadd dword [{{.WRK}}+8] ; f2*h'+b
fstp dword [{{.WRK}}+8] ; b'=f2*h'+b fstp dword [{{.WRK}}+8] ; b'=f2*h'+b
fldz ; 0 fldz ; 0
{{- if .HasParamValue "filter" "lowpass" 1}} {{- if .SupportsParamValue "filter" "lowpass" 1}}
test al, byte 0x40 test al, byte 0x40
jz short su_op_filter_skiplowpass jz short su_op_filter_skiplowpass
fadd dword [{{.WRK}}] fadd dword [{{.WRK}}]
su_op_filter_skiplowpass: su_op_filter_skiplowpass:
{{- end}} {{- end}}
{{- if .HasParamValue "filter" "bandpass" 1}} {{- if .SupportsParamValue "filter" "bandpass" 1}}
test al, byte 0x20 test al, byte 0x20
jz short su_op_filter_skipbandpass jz short su_op_filter_skipbandpass
fadd dword [{{.WRK}}+8] fadd dword [{{.WRK}}+8]
su_op_filter_skipbandpass: su_op_filter_skipbandpass:
{{- end}} {{- end}}
{{- if .HasParamValue "filter" "highpass" 1}} {{- if .SupportsParamValue "filter" "highpass" 1}}
test al, byte 0x10 test al, byte 0x10
jz short su_op_filter_skiphighpass jz short su_op_filter_skiphighpass
fadd dword [{{.WRK}}+4] fadd dword [{{.WRK}}+4]
su_op_filter_skiphighpass: su_op_filter_skiphighpass:
{{- end}} {{- end}}
{{- if .HasParamValue "filter" "negbandpass" 1}} {{- if .SupportsParamValue "filter" "negbandpass" 1}}
test al, byte 0x08 test al, byte 0x08
jz short su_op_filter_skipnegbandpass jz short su_op_filter_skipnegbandpass
fsub dword [{{.WRK}}+8] fsub dword [{{.WRK}}+8]
su_op_filter_skipnegbandpass: su_op_filter_skipnegbandpass:
{{- end}} {{- end}}
{{- if .HasParamValue "filter" "neghighpass" 1}} {{- if .SupportsParamValue "filter" "neghighpass" 1}}
test al, byte 0x04 test al, byte 0x04
jz short su_op_filter_skipneghighpass jz short su_op_filter_skipneghighpass
fsub dword [{{.WRK}}+4] fsub dword [{{.WRK}}+4]
@ -173,7 +173,7 @@ su_op_filter_skipneghighpass:
{{end}} {{end}}
{{- if .Opcode "clip"}} {{- if .HasOp "clip"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; CLIP opcode: clips the signal into [-1,1] range ; CLIP opcode: clips the signal into [-1,1] range
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -188,7 +188,7 @@ su_op_filter_skipneghighpass:
{{end}} {{end}}
{{- if .Opcode "pan" -}} {{- if .HasOp "pan" -}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; PAN opcode: pan the signal ; PAN opcode: pan the signal
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -220,7 +220,7 @@ su_op_pan_do:
{{end}} {{end}}
{{- if .Opcode "delay"}} {{- if .HasOp "delay"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; DELAY opcode: adds delay effect to the signal ; DELAY opcode: adds delay effect to the signal
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -234,12 +234,13 @@ su_op_pan_do:
lodsw ; al = delay index, ah = delay count lodsw ; al = delay index, ah = delay count
{{- .PushRegs .VAL "DelayVal" .COM "DelayCom" | indent 4}} {{- .PushRegs .VAL "DelayVal" .COM "DelayCom" | indent 4}}
movzx ebx, al movzx ebx, al
; %ifdef RUNTIME_TABLES ; when using runtime tables, delaytimes is pulled from the stack so can be a pointer to heap {{- if .Library}}
; mov _SI, [{{.SP}} + su_stack.delaytimes + PUSH_REG_SIZE(2)] mov {{.SI}}, [{{.Stack "DelayTable"}}] ; when using runtime tables, delaytimes is pulled from the stack so can be a pointer to heap
; lea _BX, [_SI + _BX*2] lea {{.BX}}, [{{.SI}} + {{.BX}}*2]
; %else {{- else}}
{{.Prepare "su_delay_times" | indent 4}} {{- .Prepare "su_delay_times" | indent 4}}
lea {{.BX}},[{{.Use "su_delay_times"}} + {{.BX}}*2] ; BX now points to the right position within delay time table lea {{.BX}},[{{.Use "su_delay_times"}} + {{.BX}}*2] ; BX now points to the right position within delay time table
{{- end}}
movzx esi, word [{{.Stack "GlobalTick"}}] ; notice that we load word, so we wrap at 65536 movzx esi, word [{{.Stack "GlobalTick"}}] ; notice that we load word, so we wrap at 65536
mov {{.CX}}, {{.PTRWORD}} [{{.Stack "DelayWorkSpace"}}] ; {{.WRK}} is now the separate delay workspace, as they require a lot more space mov {{.CX}}, {{.PTRWORD}} [{{.Stack "DelayWorkSpace"}}] ; {{.WRK}} is now the separate delay workspace, as they require a lot more space
{{- if .StereoAndMono "delay"}} {{- if .StereoAndMono "delay"}}
@ -256,7 +257,7 @@ su_op_delay_mono: ; flow into mono delay
call su_op_delay_do ; when stereo delay is not enabled, we could inline this to save 5 bytes, but I expect stereo delay to be farely popular so maybe not worth the hassle call su_op_delay_do ; when stereo delay is not enabled, we could inline this to save 5 bytes, but I expect stereo delay to be farely popular so maybe not worth the hassle
mov {{.PTRWORD}} [{{.Stack "DelayWorkSpace"}}],{{.CX}} ; move delay workspace pointer back to stack. mov {{.PTRWORD}} [{{.Stack "DelayWorkSpace"}}],{{.CX}} ; move delay workspace pointer back to stack.
{{- .PopRegs .VAL .COM | indent 4}} {{- .PopRegs .VAL .COM | indent 4}}
{{- if .UsesDelayModulation}} {{- if .SupportsModulation "delay" "delaytime"}}
xor eax, eax xor eax, eax
mov dword [{{.Modulation "delay" "delaytime"}}], eax mov dword [{{.Modulation "delay" "delaytime"}}], eax
{{- end}} {{- end}}
@ -281,9 +282,9 @@ su_op_delay_mono: ; flow into mono delay
fxch ; y p*p*x fxch ; y p*p*x
fmul dword [{{.Input "delay" "dry"}}] ; dr*y p*p*x fmul dword [{{.Input "delay" "dry"}}] ; dr*y p*p*x
su_op_delay_loop: su_op_delay_loop:
{{- if or .UsesDelayModulation (.HasParamValue "delay" "notetracking" 1)}} ; delaytime modulation or note syncing require computing the delay time in floats {{- if or (.SupportsModulation "delay" "delaytime") (.SupportsParamValue "delay" "notetracking" 1)}} ; delaytime modulation or note syncing require computing the delay time in floats
fild word [{{.BX}}] ; k dr*y p*p*x, where k = delay time fild word [{{.BX}}] ; k dr*y p*p*x, where k = delay time
{{- if .HasParamValue "delay" "notetracking" 1}} {{- if .SupportsParamValue "delay" "notetracking" 1}}
test ah, 1 ; note syncing is the least significant bit of ah, 0 = ON, 1 = OFF test ah, 1 ; note syncing is the least significant bit of ah, 0 = ON, 1 = OFF
jne su_op_delay_skipnotesync jne su_op_delay_skipnotesync
fild dword [{{.INP}}-su_voice.inputs+su_voice.note] fild dword [{{.INP}}-su_voice.inputs+su_voice.note]
@ -293,7 +294,7 @@ su_op_delay_loop:
fdivp st1, st0 ; use 10787 for delaytime to have neutral transpose fdivp st1, st0 ; use 10787 for delaytime to have neutral transpose
su_op_delay_skipnotesync: su_op_delay_skipnotesync:
{{- end}} {{- end}}
{{- if .UsesDelayModulation}} {{- if .SupportsModulation "delay" "delaytime"}}
fld dword [{{.Modulation "delay" "delaytime"}}] fld dword [{{.Modulation "delay" "delaytime"}}]
{{- .Float 32767.0 | .Prepare | indent 8}} {{- .Float 32767.0 | .Prepare | indent 8}}
fmul dword [{{.Float 32767.0 | .Use}}] ; scale it up, as the modulations would be too small otherwise fmul dword [{{.Float 32767.0 | .Use}}] ; scale it up, as the modulations would be too small otherwise
@ -339,7 +340,7 @@ su_op_delay_loop:
{{end}} {{end}}
{{- if .Opcode "compressor"}} {{- if .HasOp "compressor"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; COMPRES opcode: push compressor gain to stack ; COMPRES opcode: push compressor gain to stack
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------

View File

@ -1,4 +1,4 @@
{{- if .Opcode "speed" -}} {{- if .HasOp "speed" -}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; SPEED opcode: modulate the speed (bpm) of the song based on ST0 ; SPEED opcode: modulate the speed (bpm) of the song based on ST0
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------

View File

@ -1,3 +1,5 @@
{{- if .SupportsParamValue "oscillator" "type" .Sample}}
{{- if eq .OS "windows"}} {{- if eq .OS "windows"}}
{{.ExportFunc "su_load_gmdls"}} {{.ExportFunc "su_load_gmdls"}}
{{- if .Amd64}} {{- if .Amd64}}
@ -24,7 +26,7 @@
call ReadFile ; Readfile(handle,&su_sample_table,SAMPLE_TABLE_SIZE,&bytes_read,NULL) call ReadFile ; Readfile(handle,&su_sample_table,SAMPLE_TABLE_SIZE,&bytes_read,NULL)
add rsp, 40 ; shadow space, as required by Win64 ABI add rsp, 40 ; shadow space, as required by Win64 ABI
ret ret
{{- else}} {{else}}
mov edx, su_sample_table mov edx, su_sample_table
mov ecx, su_gmdls_path1 mov ecx, su_gmdls_path1
su_gmdls_pathloop: su_gmdls_pathloop:
@ -50,9 +52,9 @@ extern _ReadFile@20 ; requires windows
db 'drivers/gm.dls',0 db 'drivers/gm.dls',0
su_gmdls_path2: su_gmdls_path2:
db 'drivers/etc/gm.dls',0 db 'drivers/etc/gm.dls',0
{{end}}
{{.SectBss "susamtable"}} {{.SectBss "susamtable"}}
su_sample_table: su_sample_table:
resb 3440660 ; size of gmdls. resb 3440660 ; size of gmdls.
{{end}} {{end}}

View File

@ -1,113 +1,10 @@
; source file for compiling sointu as a library {{template "structs.asm" .}}
%define SU_DISABLE_PLAYER
%include "sointu/header.inc"
; use every opcode
USE_ADD
USE_ADDP
USE_POP
USE_LOADNOTE
USE_MUL
USE_MULP
USE_PUSH
USE_XCH
USE_DISTORT
USE_HOLD
USE_CRUSH
USE_GAIN
USE_INVGAIN
USE_FILTER
USE_CLIP
USE_PAN
USE_DELAY
USE_COMPRES
USE_SPEED
USE_OUT
USE_OUTAUX
USE_AUX
USE_SEND
USE_ENVELOPE
USE_NOISE
USE_OSCILLAT
USE_LOAD_VAL
USE_RECEIVE
USE_IN
; include stereo variant of each opcode
%define INCLUDE_STEREO_ADD
%define INCLUDE_STEREO_ADDP
%define INCLUDE_STEREO_POP
%define INCLUDE_STEREO_LOADNOTE
%define INCLUDE_STEREO_MUL
%define INCLUDE_STEREO_MULP
%define INCLUDE_STEREO_PUSH
%define INCLUDE_STEREO_XCH
%define INCLUDE_STEREO_DISTORT
%define INCLUDE_STEREO_HOLD
%define INCLUDE_STEREO_CRUSH
%define INCLUDE_STEREO_GAIN
%define INCLUDE_STEREO_INVGAIN
%define INCLUDE_STEREO_FILTER
%define INCLUDE_STEREO_CLIP
%define INCLUDE_STEREO_PAN
%define INCLUDE_STEREO_DELAY
%define INCLUDE_STEREO_COMPRES
%define INCLUDE_STEREO_SPEED
%define INCLUDE_STEREO_OUT
%define INCLUDE_STEREO_OUTAUX
%define INCLUDE_STEREO_AUX
%define INCLUDE_STEREO_SEND
%define INCLUDE_STEREO_ENVELOPE
%define INCLUDE_STEREO_NOISE
%define INCLUDE_STEREO_OSCILLAT
%define INCLUDE_STEREO_LOADVAL
%define INCLUDE_STEREO_RECEIVE
%define INCLUDE_STEREO_IN
; include all features inside all opcodes
%define INCLUDE_TRISAW
%define INCLUDE_SINE
%define INCLUDE_PULSE
%define INCLUDE_GATE
%define INCLUDE_UNISONS
%define INCLUDE_POLYPHONY
%define INCLUDE_MULTIVOICE_TRACKS
%define INCLUDE_DELAY_MODULATION
%define INCLUDE_LOWPASS
%define INCLUDE_BANDPASS
%define INCLUDE_HIGHPASS
%define INCLUDE_NEGBANDPASS
%define INCLUDE_NEGHIGHPASS
%define INCLUDE_GLOBAL_SEND
%define INCLUDE_DELAY_NOTETRACKING
%define INCLUDE_DELAY_FLOAT_TIME
%ifidn __OUTPUT_FORMAT__,win32
%define INCLUDE_SAMPLES
%define INCLUDE_GMDLS
%endif
%ifidn __OUTPUT_FORMAT__,win64
%define INCLUDE_SAMPLES
%define INCLUDE_GMDLS
%endif
%include "sointu/footer.inc"
section .text
struc su_sampleoff
.start resd 1
.loopstart resw 1
.looplength resw 1
.size:
endstruc
struc su_synth struc su_synth
.synthwrk resb su_synthworkspace.size .synth_wrk resb su_synthworkspace.size
.delaywrks resb su_delayline_wrk.size * 64 .delay_wrks resb su_delayline_wrk.size * 64
.delaytimes resw 768 .delaytimes resw 768
.sampleoffs resb su_sampleoff.size * 256 .sampleoffs resb su_sample_offset.size * 256
.randseed resd 1 .randseed resd 1
.globaltime resd 1 .globaltime resd 1
.commands resb 32 * 64 .commands resb 32 * 64
@ -116,189 +13,122 @@ struc su_synth
.numvoices resd 1 .numvoices resd 1
endstruc endstruc
SECT_TEXT(sursampl) {{.ExportFunc "su_render" "SynthStateParam" "BufferPtrParam" "SamplesParam" "TimeParam"}}
{{- if .Amd64}}
EXPORT MANGLE_FUNC(su_render,16) {{- if eq .OS "windows"}}
%if BITS == 32 ; stdcall {{- .PushRegs "rdi" "NonVolatileRDI" "rsi" "NonVolatileRSI" "rbx" "NonVolatileRBX" "rbp" "NonVolatileRBP" | indent 4}}
pushad ; push registers mov rsi, r8 ; rsi = &samples
mov ecx, [esp + 4 + 32] ; ecx = &synthState mov rbx, r9 ; rbx = &time
mov edx, [esp + 8 + 32] ; edx = &buffer {{- else}} ; SystemV amd64 ABI, linux mac or hopefully something similar
mov esi, [esp + 12 + 32] ; esi = &samples {{- .PushRegs "rbx" "NonVolatileRBX" "rbp" "NonVolatileRBP" | indent 4}}
mov ebx, [esp + 16 + 32] ; ebx = &time mov rbx, rcx ; rbx points to time
%else xchg rsi, rdx ; rdx points to buffer, rsi points to samples
%ifidn __OUTPUT_FORMAT__,win64 ; win64 ABI: rcx = &synth, rdx = &buffer, r8 = &bufsize, r9 = &time mov rcx, rdi ; rcx = &Synthstate
push_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile {{- end}}
mov rsi, r8 ; rsi = &samples {{- else}}
mov rbx, r9 ; rbx = &time {{- .PushRegs | indent 4 }} ; push registers
%else ; System V ABI: rdi = &synth, rsi = &buffer, rdx = &samples, rcx = &time mov ecx, [{{.Stack "SynthStateParam"}}] ; ecx = &synthState
push_registers rbx, rbp ; System V ABI: these registers are non-volatile mov edx, [{{.Stack "BufferPtrParam"}}] ; edx = &buffer
mov rbx, rcx ; rbx points to time mov esi, [{{.Stack "SamplesParam"}}] ; esi = &samples
xchg rsi, rdx ; rdx points to buffer, rsi points to samples mov ebx, [{{.Stack "TimeParam"}}] ; ebx = &time
mov rcx, rdi ; rcx = &Synthstate {{- end}}
%endif {{.SaveFPUState | indent 4}} ; save the FPU state to stack & reset the FPU
%endif {{.Push .SI "Samples"}}
sub _SP,108 ; allocate space on stack for the FPU state {{.Push .BX "Time"}}
fsave [_SP] ; save the FPU state to stack & reset the FPU
push _SI ; push the pointer to samples
push _BX ; push the pointer to time
xor eax, eax ; samplenumber starts at 0 xor eax, eax ; samplenumber starts at 0
push _AX ; push samplenumber to stack {{.Push .AX "BufSample"}}
mov esi, [_SI] ; zero extend dereferenced pointer mov esi, [{{.SI}}] ; zero extend dereferenced pointer
push _SI ; push bufsize {{.Push .SI "BufSize"}}
push _DX ; push bufptr {{.Push .DX "BufPtr"}}
push _CX ; this takes place of the voicetrack {{.Push .CX "SynthState"}}
lea _AX, [_CX + su_synth.sampleoffs] lea {{.AX}}, [{{.CX}} + su_synth.sampleoffs]
push _AX {{.Push .AX "SampleTable"}}
lea _AX, [_CX + su_synth.delaytimes] lea {{.AX}}, [{{.CX}} + su_synth.delaytimes]
push _AX {{.Push .AX "DelayTable"}}
mov eax, [_CX + su_synth.randseed] mov eax, [{{.CX}} + su_synth.randseed]
push _AX ; randseed {{.Push .AX "RandSeed"}}
mov eax, [_CX + su_synth.globaltime] mov eax, [{{.CX}} + su_synth.globaltime]
push _AX ; global tick time {{.Push .AX "GlobalTick"}}
mov ebx, dword [_BX] ; zero extend dereferenced pointer mov ebx, dword [{{.BX}}] ; zero extend dereferenced pointer
push _BX ; the nominal rowlength should be time_in {{.Push .BX "RowLength"}} ; the nominal rowlength should be time_in
xor eax, eax ; rowtick starts at 0 xor eax, eax ; rowtick starts at 0
su_render_samples_loop: su_render_samples_loop:
push _DI push {{.DI}}
fnstsw [_SP] ; store the FPU status flag to stack top fnstsw [{{.SP}}] ; store the FPU status flag to stack top
pop _DI ; _DI = FPU status flag pop {{.DI}} ; {{.DI}} = FPU status flag
and _DI, 0011100001000101b ; mask TOP pointer, stack error, zero divide and invalid operation and {{.DI}}, 0b0011100001000101 ; mask TOP pointer, stack error, zero divide and in{{.VAL}}id operation
test _DI,_DI ; all the aforementioned bits should be 0! test {{.DI}},{{.DI}} ; all the aforementioned bits should be 0!
jne su_render_samples_time_finish ; otherwise, we exit due to error jne su_render_samples_time_finish ; otherwise, we exit due to error
cmp eax, [_SP] ; if rowtick >= maxtime cmp eax, [{{.Stack "RowLength"}}] ; if rowtick >= maxtime
jge su_render_samples_time_finish ; goto finish jge su_render_samples_time_finish ; goto finish
mov ecx, [_SP + PTRSIZE*7] ; ecx = buffer length in samples mov ecx, [{{.Stack "BufSize"}}] ; ecx = buffer length in samples
cmp [_SP + PTRSIZE*8], ecx ; if samples >= maxsamples cmp [{{.Stack "BufSample"}}], ecx ; if samples >= maxsamples
jge su_render_samples_time_finish ; goto finish jge su_render_samples_time_finish ; goto finish
inc eax ; time++ inc eax ; time++
inc dword [_SP + PTRSIZE*8] ; samples++ inc dword [{{.Stack "BufSample"}}] ; samples++
mov _CX, [_SP + PTRSIZE*5] mov {{.CX}}, [{{.Stack "SynthState"}}]
push _AX ; push rowtick {{.Push .AX "Sample"}}
mov eax, [_CX + su_synth.polyphony] mov eax, [{{.CX}} + su_synth.polyphony]
push _AX ;polyphony {{.Push .AX "PolyphonyBitmask"}}
mov eax, [_CX + su_synth.numvoices] mov eax, [{{.CX}} + su_synth.numvoices]
push _AX ;numvoices {{.Push .AX "VoicesRemain"}}
lea _DX, [_CX+ su_synth.synthwrk] lea {{.DX}}, [{{.CX}}+ su_synth.synth_wrk]
lea COM, [_CX+ su_synth.commands] lea {{.COM}}, [{{.CX}}+ su_synth.commands]
lea VAL, [_CX+ su_synth.values] lea {{.VAL}}, [{{.CX}}+ su_synth.values]
lea WRK, [_DX + su_synthworkspace.voices] lea {{.WRK}}, [{{.DX}} + su_synthworkspace.voices]
lea _CX, [_CX+ su_synth.delaywrks - su_delayline_wrk.filtstate] lea {{.CX}}, [{{.CX}}+ su_synth.delay_wrks - su_delayline_wrk.filtstate]
call MANGLE_FUNC(su_run_vm,0) {{.Call "su_run_vm"}}
pop _AX {{.Pop .AX}}
pop _AX {{.Pop .AX}}
mov _DI, [_SP + PTRSIZE*7] ; edi containts buffer ptr mov {{.DI}}, [{{.Stack "BufPtr"}}] ; edi containts buffer ptr
mov _CX, [_SP + PTRSIZE*6] mov {{.CX}}, [{{.Stack "SynthState"}}]
lea _SI, [_CX + su_synth.synthwrk + su_synthworkspace.left] lea {{.SI}}, [{{.CX}} + su_synth.synth_wrk + su_synthworkspace.left]
movsd ; copy left channel to output buffer movsd ; copy left channel to output buffer
movsd ; copy right channel to output buffer movsd ; copy right channel to output buffer
mov [_SP + PTRSIZE*7], _DI ; save back the updated ptr mov [{{.Stack "BufPtr"}}], {{.DI}} ; save back the updated ptr
lea _DI, [_SI-8] lea {{.DI}}, [{{.SI}}-8]
xor eax, eax xor eax, eax
stosd ; clear left channel so the VM is ready to write them again stosd ; clear left channel so the VM is ready to write them again
stosd ; clear right channel so the VM is ready to write them again stosd ; clear right channel so the VM is ready to write them again
pop _AX {{.Pop .AX}}
inc dword [_SP + PTRSIZE] ; increment global time, used by delays inc dword [{{.Stack "GlobalTick"}}] ; increment global time, used by delays
jmp su_render_samples_loop jmp su_render_samples_loop
su_render_samples_time_finish: su_render_samples_time_finish:
pop _CX {{.Pop .CX}}
pop _BX {{.Pop .BX}}
pop _DX {{.Pop .DX}}
pop _CX ; discard delaytimes ptr {{.Pop .CX}}
pop _CX ; discard samplesoffs ptr {{.Pop .CX}}
pop _CX {{.Pop .CX}}
mov [_CX + su_synth.randseed], edx mov [{{.CX}} + su_synth.randseed], edx
mov [_CX + su_synth.globaltime], ebx mov [{{.CX}} + su_synth.globaltime], ebx
pop _BX {{.Pop .BX}}
pop _BX {{.Pop .BX}}
pop _DX {{.Pop .DX}}
pop _BX ; pop the pointer to time {{.Pop .BX}}
pop _SI ; pop the pointer to samples {{.Pop .SI}}
mov dword [_SI], edx ; *samples = samples rendered mov dword [{{.SI}}], edx ; *samples = samples rendered
mov dword [_BX], eax ; *time = time ticks rendered mov dword [{{.BX}}], eax ; *time = time ticks rendered
mov _AX,_DI ; _DI was the masked FPU status flag, _AX is return value mov {{.AX}},{{.DI}} ; {{.DI}} was the masked FPU status flag, {{.AX}} is return {{.VAL}}ue
frstor [_SP] ; restore fpu state {{.LoadFPUState | indent 4}} ; load the FPU state from stack
add _SP,108 ; rewind the stack allocate for FPU state {{- if .Amd64}}
%if BITS == 32 ; stdcall {{- if eq .OS "windows"}}
mov [_SP + 28],eax ; we want to return eax, but popad pops everything, so put eax to stack for popad to pop {{- .PopRegs "rdi" "rsi" "rbx" "rbp" | indent 4}}
popad {{- else}} ; SystemV amd64 ABI, linux mac or hopefully something similar
ret 16 {{- .PopRegs "rbx" "rbp" | indent 4}}
%else {{- end}}
%ifidn __OUTPUT_FORMAT__,win64
pop_registers rdi, rsi, rbx, rbp ; win64 ABI: these registers are non-volatile
%else
pop_registers rbx, rbp ; System V ABI: these registers are non-volatile
%endif
ret ret
%endif {{- else}}
mov [{{.Stack "eax"}}],eax ; we want to return eax, but popad pops everything, so put eax to stack for popad to pop
{{- .PopRegs | indent 4 }} ; popad
ret 16
{{- end}}
SECT_DATA(opcodeid)
; Arithmetic opcode ids {{template "patch.asm" .}}
EXPORT MANGLE_DATA(su_add_id)
dd ADD_ID
EXPORT MANGLE_DATA(su_addp_id)
dd ADDP_ID
EXPORT MANGLE_DATA(su_pop_id)
dd POP_ID
EXPORT MANGLE_DATA(su_loadnote_id)
dd LOADNOTE_ID
EXPORT MANGLE_DATA(su_mul_id)
dd MUL_ID
EXPORT MANGLE_DATA(su_mulp_id)
dd MULP_ID
EXPORT MANGLE_DATA(su_push_id)
dd PUSH_ID
EXPORT MANGLE_DATA(su_xch_id)
dd XCH_ID
; Effect opcode ids ;-------------------------------------------------------------------------------
EXPORT MANGLE_DATA(su_distort_id) ; Constants
dd DISTORT_ID ;-------------------------------------------------------------------------------
EXPORT MANGLE_DATA(su_hold_id) {{.SectData "constants"}}
dd HOLD_ID {{.Constants}}
EXPORT MANGLE_DATA(su_crush_id)
dd CRUSH_ID
EXPORT MANGLE_DATA(su_gain_id)
dd GAIN_ID
EXPORT MANGLE_DATA(su_invgain_id)
dd INVGAIN_ID
EXPORT MANGLE_DATA(su_filter_id)
dd FILTER_ID
EXPORT MANGLE_DATA(su_clip_id)
dd CLIP_ID
EXPORT MANGLE_DATA(su_pan_id)
dd PAN_ID
EXPORT MANGLE_DATA(su_delay_id)
dd DELAY_ID
EXPORT MANGLE_DATA(su_compres_id)
dd COMPRES_ID
; Flowcontrol opcode ids
EXPORT MANGLE_DATA(su_advance_id)
dd SU_ADVANCE_ID
EXPORT MANGLE_DATA(su_speed_id)
dd SPEED_ID
; Sink opcode ids
EXPORT MANGLE_DATA(su_out_id)
dd OUT_ID
EXPORT MANGLE_DATA(su_outaux_id)
dd OUTAUX_ID
EXPORT MANGLE_DATA(su_aux_id)
dd AUX_ID
EXPORT MANGLE_DATA(su_send_id)
dd SEND_ID
; Source opcode ids
EXPORT MANGLE_DATA(su_envelope_id)
dd ENVELOPE_ID
EXPORT MANGLE_DATA(su_noise_id)
dd NOISE_ID
EXPORT MANGLE_DATA(su_oscillat_id)
dd OSCILLAT_ID
EXPORT MANGLE_DATA(su_loadval_id)
dd LOADVAL_ID
EXPORT MANGLE_DATA(su_receive_id)
dd RECEIVE_ID
EXPORT MANGLE_DATA(su_in_id)
dd IN_ID

View File

@ -38,11 +38,11 @@ typedef struct SampleOffset {
typedef struct Synth { typedef struct Synth {
struct SynthWorkspace SynthWrk; struct SynthWorkspace SynthWrk;
struct DelayWorkspace DelayWrks[64]; // let's keep this as 64 for now, so the delays take 16 meg. If that's too little or too much, we can change this in future. struct DelayWorkspace DelayWrks[64]; // let's keep this as 64 for now, so the delays take 16 meg. If that's too little or too much, we can change this in future.
unsigned short DelayTimes[768]; unsigned short DelayTimes[768];
struct SampleOffset SampleOffsets[256]; struct SampleOffset SampleOffsets[256];
unsigned int RandSeed; unsigned int RandSeed;
unsigned int GlobalTick; unsigned int GlobalTick;
unsigned char Commands[32 * 64]; unsigned char Commands[32 * 64];
unsigned char Values[32 * 64 * 8]; unsigned char Values[32 * 64 * 8];
unsigned int Polyphony; unsigned int Polyphony;
@ -69,7 +69,7 @@ void CALLCONV su_load_gmdls(void);
// actually rendered and how many time ticks were actually advanced. // actually rendered and how many time ticks were actually advanced.
// //
// Parameters: // Parameters:
// synth pointer to the synthesizer used. RandSeed should be > 0 e.g. 1 // synth pointer to the synthesizer used. RandSeed should be > 0 e.g. 1
// buffer audio sample buffer, L R L R ... // buffer audio sample buffer, L R L R ...
// samples pointer to the maximum number of samples to be rendered. // samples pointer to the maximum number of samples to be rendered.
// buffer should have a length of 2 * maxsamples as the audio // buffer should have a length of 2 * maxsamples as the audio
@ -92,44 +92,9 @@ void CALLCONV su_load_gmdls(void);
// bits 11-13 The top pointer of the fpu stack. Any other value than 0 indicates that some values were left on the stack. // bits 11-13 The top pointer of the fpu stack. Any other value than 0 indicates that some values were left on the stack.
int CALLCONV su_render(Synth* synth, float* buffer, int* samples, int* time); int CALLCONV su_render(Synth* synth, float* buffer, int* samples, int* time);
// Arithmetic opcode ids #define SU_ADVANCE_ID 0
extern const int su_add_id; {{- range $index, $element := .Instructions}}
extern const int su_addp_id; #define {{printf "su_%v_id" $element | upper | printf "%-20v"}}{{add1 $index | mul 2}}
extern const int su_pop_id; {{- end}}
extern const int su_loadnote_id;
extern const int su_mul_id;
extern const int su_mulp_id;
extern const int su_push_id;
extern const int su_xch_id;
// Effect opcode ids
extern const int su_distort_id;
extern const int su_hold_id;
extern const int su_crush_id;
extern const int su_gain_id;
extern const int su_invgain_id;
extern const int su_filter_id;
extern const int su_clip_id;
extern const int su_pan_id;
extern const int su_delay_id;
extern const int su_compres_id;
// Flowcontrol opcode ids
extern const int su_advance_id;
extern const int su_speed_id;
// Sink opcode ids
extern const int su_out_id;
extern const int su_outaux_id;
extern const int su_aux_id;
extern const int su_send_id;
// Source opcode ids
extern const int su_envelope_id;
extern const int su_noise_id;
extern const int su_oscillat_id;
extern const int su_loadval_id;
extern const int su_receive_id;
extern const int su_in_id;
#endif // _SOINTU_H #endif // _SOINTU_H

View File

@ -25,8 +25,8 @@ su_run_vm_loop: ; loop until all voices done
xor ecx, ecx ; counter = 0 xor ecx, ecx ; counter = 0
xor eax, eax ; clear out high bits of eax, as lodsb only sets al xor eax, eax ; clear out high bits of eax, as lodsb only sets al
su_transform_values_loop: su_transform_values_loop:
{{- .Prepare "su_vm_transformcounts" | indent 4}} {{- .Prepare "su_vm_transformcounts-1" | indent 4}}
cmp cl, byte [{{.Use "su_vm_transformcounts"}}+{{.DI}}] ; compare the counter to the value in the param count table cmp cl, byte [{{.Use "su_vm_transformcounts-1"}}+{{.DI}}] ; compare the counter to the value in the param count table
je su_transform_values_out je su_transform_values_out
lodsb ; load the byte value from VAL stream lodsb ; load the byte value from VAL stream
push {{.AX}} ; push it to memory so FPU can read it push {{.AX}} ; push it to memory so FPU can read it
@ -43,11 +43,12 @@ su_transform_values_loop:
su_transform_values_out: su_transform_values_out:
bt dword [{{.COM}}-1],0 ; LSB of COM = stereo bit => carry bt dword [{{.COM}}-1],0 ; LSB of COM = stereo bit => carry
{{- .SaveStack "Opcode"}} {{- .SaveStack "Opcode"}}
{{- .Prepare "su_vm_jumptable" | indent 4}} {{- $x := printf "su_vm_jumptable-%v" .PTRSIZE}}
call [{{.Use "su_vm_jumptable"}}+{{.DI}}*{{.PTRSIZE}}] ; call the function corresponding to the instruction {{- .Prepare $x | indent 4}}
call [{{.Use $x}}+{{.DI}}*{{.PTRSIZE}}] ; call the function corresponding to the instruction
jmp su_run_vm_loop jmp su_run_vm_loop
su_run_vm_advance: su_run_vm_advance:
{{- if .Polyphony}} {{- if .SupportsPolyphony}}
mov {{.WRK}}, [{{.Stack "Voice"}}] ; WRK points to start of current voice mov {{.WRK}}, [{{.Stack "Voice"}}] ; WRK points to start of current voice
add {{.WRK}}, su_voice.size ; move to next voice add {{.WRK}}, su_voice.size ; move to next voice
mov [{{.Stack "Voice"}}], {{.WRK}} ; update the pointer in the stack to point to the new voice mov [{{.Stack "Voice"}}], {{.WRK}} ; update the pointer in the stack to point to the new voice
@ -173,18 +174,15 @@ su_clip_do:
; The opcode table jump table. This is constructed to only include the opcodes ; The opcode table jump table. This is constructed to only include the opcodes
; that are used so that the jump table is as small as possible. ; that are used so that the jump table is as small as possible.
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
{{.Data "su_vm_jumptable_offset"}} {{.Data "su_vm_jumptable"}}
su_vm_jumptable equ $ - {{.PTRSIZE}} ; Advance is not in the opcode table {{- range .Instructions}}
{{- $x := .}} {{$.DPTR}} su_op_{{.}}
{{- range .Opcodes}}
{{$x.DPTR}} su_op_{{.Type}}
{{- end}} {{- end}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; The number of transformed parameters each opcode takes ; The number of transformed parameters each opcode takes
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
{{.Data "su_vm_transformcounts_offset"}} {{.Data "su_vm_transformcounts"}}
su_vm_transformcounts equ $ - 1 ; Advance is not in the opcode table {{- range .Instructions}}
{{- range .Opcodes}} db {{$.TransformCount .}}
db {{.NumParams}}
{{- end}} {{- end}}

View File

@ -26,7 +26,7 @@ su_synth_obj:
{{- end}} {{- end}}
{{- $prologsize := len .Stacklocs}} {{- $prologsize := len .Stacklocs}}
xor eax, eax xor eax, eax
{{- if .MultivoiceTracks}} {{- if ne .VoiceTrackBitmask 0}}
{{.Push (.VoiceTrackBitmask | printf "%v") "VoiceTrackBitmask"}} {{.Push (.VoiceTrackBitmask | printf "%v") "VoiceTrackBitmask"}}
{{- end}} {{- end}}
{{.Push "1" "RandSeed"}} {{.Push "1" "RandSeed"}}
@ -37,20 +37,20 @@ su_render_rowloop: ; loop through every row in the song
xor eax, eax ; ecx is the current sample within row xor eax, eax ; ecx is the current sample within row
su_render_sampleloop: ; loop through every sample in the row su_render_sampleloop: ; loop through every sample in the row
{{.Push .AX "Sample"}} {{.Push .AX "Sample"}}
{{- if .Polyphony}} {{- if .SupportsPolyphony}}
{{.Push (.PolyphonyBitmask | printf "%v") "PolyphonyBitmask"}} ; does the next voice reuse the current opcodes? {{.Push (.PolyphonyBitmask | printf "%v") "PolyphonyBitmask"}} ; does the next voice reuse the current opcodes?
{{- end}} {{- end}}
{{.Push (.Song.Patch.TotalVoices | printf "%v") "VoicesRemain"}} {{.Push (.Song.Patch.TotalVoices | printf "%v") "VoicesRemain"}}
mov {{.DX}}, {{.PTRWORD}} su_synth_obj ; {{.DX}} points to the synth object mov {{.DX}}, {{.PTRWORD}} su_synth_obj ; {{.DX}} points to the synth object
mov {{.COM}}, {{.PTRWORD}} su_patch_code ; COM points to vm code mov {{.COM}}, {{.PTRWORD}} su_patch_code ; COM points to vm code
mov {{.VAL}}, {{.PTRWORD}} su_patch_parameters ; VAL points to unit params mov {{.VAL}}, {{.PTRWORD}} su_patch_parameters ; VAL points to unit params
{{- if .Opcode "delay"}} {{- if .HasOp "delay"}}
lea {{.CX}}, [{{.DX}} + su_synthworkspace.size - su_delayline_wrk.filtstate] lea {{.CX}}, [{{.DX}} + su_synthworkspace.size - su_delayline_wrk.filtstate]
{{- end}} {{- end}}
lea {{.WRK}}, [{{.DX}} + su_synthworkspace.voices] ; WRK points to the first voice lea {{.WRK}}, [{{.DX}} + su_synthworkspace.voices] ; WRK points to the first voice
{{.Call "su_run_vm"}} ; run through the VM code {{.Call "su_run_vm"}} ; run through the VM code
{{.Pop .AX}} {{.Pop .AX}}
{{- if .Polyphony}} {{- if .SupportsPolyphony}}
{{.Pop .AX}} {{.Pop .AX}}
{{- end}} {{- end}}
{{- template "output_sound.asm" .}} ; *ptr++ = left, *ptr++ = right {{- template "output_sound.asm" .}} ; *ptr++ = left, *ptr++ = right
@ -64,9 +64,8 @@ su_render_sampleloop: ; loop through every sample in the row
cmp eax, {{.Song.TotalRows}} cmp eax, {{.Song.TotalRows}}
jl su_render_rowloop jl su_render_rowloop
; rewind the stack the entropy of multiple pop {{.AX}} is probably lower than add ; rewind the stack the entropy of multiple pop {{.AX}} is probably lower than add
{{- $x := .}} {{- range slice .Stacklocs $prologsize}}
{{- range (.Sub (len .Stacklocs) $prologsize | .Count)}} {{$.Pop $.AX}}
{{$x.Pop $x.AX}}
{{- end}} {{- end}}
{{- if .Amd64}} {{- if .Amd64}}
{{- if eq .OS "windows"}} {{- if eq .OS "windows"}}
@ -89,7 +88,7 @@ su_render_sampleloop: ; loop through every sample in the row
; Dirty: pretty much everything ; Dirty: pretty much everything
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
{{.Func "su_update_voices"}} {{.Func "su_update_voices"}}
{{- if .MultivoiceTracks}} {{- if ne .VoiceTrackBitmask 0}}
; The more complicated implementation: one track can trigger multiple voices ; The more complicated implementation: one track can trigger multiple voices
xor edx, edx xor edx, edx
mov ebx, {{.Song.PatternRows}} ; we could do xor ebx,ebx; mov bl,PATTERN_SIZE, but that would limit patternsize to 256... mov ebx, {{.Song.PatternRows}} ; we could do xor ebx,ebx; mov bl,PATTERN_SIZE, but that would limit patternsize to 256...
@ -198,31 +197,31 @@ su_update_voices_skipadd:
db {{.Sequence | toStrings | join ","}} db {{.Sequence | toStrings | join ","}}
{{- end}} {{- end}}
{{- if gt (.Song.Patch.SampleOffsets | len) 0}} {{- if gt (.SampleOffsets | len) 0}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; Sample offsets ; Sample offsets
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
{{.Data "su_sample_offsets"}} {{.Data "su_sample_offsets"}}
{{- range .Song.Patch.SampleOffsets}} {{- range .SampleOffsets}}
dd {{.Start}} dd {{.Start}}
dw {{.LoopStart}} dw {{.LoopStart}}
dw {{.LoopLength}} dw {{.LoopLength}}
{{- end}} {{- end}}
{{end}} {{end}}
{{- if gt (.Song.Patch.DelayTimes | len ) 0}} {{- if gt (.DelayTimes | len ) 0}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; Delay times ; Delay times
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
{{.Data "su_delay_times"}} {{.Data "su_delay_times"}}
dw {{.Song.Patch.DelayTimes | toStrings | join ","}} dw {{.DelayTimes | toStrings | join ","}}
{{end}} {{end}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; The code for this patch, basically indices to vm jump table ; The code for this patch, basically indices to vm jump table
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
{{.Data "su_patch_code"}} {{.Data "su_patch_code"}}
db {{.Code | toStrings | join ","}} db {{.Commands | toStrings | join ","}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; The parameters / inputs to each opcode ; The parameters / inputs to each opcode

50
templates/player.h Normal file
View File

@ -0,0 +1,50 @@
// auto-generated by Sointu, editing not recommended
#ifndef SU_RENDER_H
#define SU_RENDER_H
#define SU_MAX_SAMPLES {{.MaxSamples}}
#define SU_BUFFER_LENGTH (SU_MAX_SAMPLES*2)
#define SU_SAMPLE_RATE 44100
#define SU_BPM {{.Song.BPM}}
#define SU_PATTERN_SIZE {{.Song.PatternRows}}
#define SU_MAX_PATTERNS {{.Song.SequenceLength}}
#define SU_TOTAL_ROWS (SU_MAX_PATTERNS*SU_PATTERN_SIZE)
#define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*4*60/(SU_BPM*16))
#include <stdint.h>
#if UINTPTR_MAX == 0xffffffff
#if defined(__clang__) || defined(__GNUC__)
#define SU_CALLCONV __attribute__ ((stdcall))
#elif defined(_WIN32)
#define SU_CALLCONV __stdcall
#endif
#else
#define SU_CALLCONV
#endif
{{- if .Output16Bit}}
typedef short SUsample;
#define SU_SAMPLE_RANGE 32767.0
{{- else}}
typedef float SUsample;
#define SU_SAMPLE_RANGE 1.0
{{- end}}
#ifdef __cplusplus
extern "C" {
#endif
void SU_CALLCONV su_render_song(SUsample *buffer);
{{- if gt (.SampleOffsets | len) 0}}
void SU_CALLCONV su_load_gmdls();
#define SU_LOAD_GMDLS
{{- end}}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,4 +1,4 @@
{{- if .Opcode "out"}} {{- if .HasOp "out"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; OUT opcode: outputs and pops the signal ; OUT opcode: outputs and pops the signal
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -26,7 +26,7 @@ su_op_out_mono:
{{end}} {{end}}
{{- if .Opcode "outaux"}} {{- if .HasOp "outaux"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; OUTAUX opcode: outputs to main and aux1 outputs and pops the signal ; OUTAUX opcode: outputs to main and aux1 outputs and pops the signal
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -54,7 +54,7 @@ su_op_outaux_mono:
{{end}} {{end}}
{{- if .Opcode "aux"}} {{- if .HasOp "aux"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; AUX opcode: outputs the signal to aux (or main) port and pops the signal ; AUX opcode: outputs the signal to aux (or main) port and pops the signal
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -79,7 +79,7 @@ su_op_aux_mono:
{{end}} {{end}}
{{- if .Opcode "send"}} {{- if .HasOp "send"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; SEND opcode: adds the signal to a port ; SEND opcode: adds the signal to a port
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -103,7 +103,7 @@ su_op_aux_mono:
fxch ; swap them back: l r fxch ; swap them back: l r
su_op_send_mono: su_op_send_mono:
{{- end}} {{- end}}
{{- if .HasParamValueOtherThan "send" "voice" 0}} {{- if .SupportsParamValueOtherThan "send" "voice" 0}}
test {{.AX}}, 0x8000 test {{.AX}}, 0x8000
jz su_op_send_skipglobal jz su_op_send_skipglobal
mov {{.CX}}, [{{.Stack "Synth"}}] mov {{.CX}}, [{{.Stack "Synth"}}]

View File

@ -1,4 +1,4 @@
{{if .Opcode "envelope" -}} {{if .HasOp "envelope" -}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; ENVELOPE opcode: pushes an ADSR envelope value on stack [0,1] ; ENVELOPE opcode: pushes an ADSR envelope value on stack [0,1]
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -62,7 +62,7 @@ su_op_envelope_leave2:
{{end}} {{end}}
{{- if .Opcode "noise"}} {{- if .HasOp "noise"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; NOISE opcode: creates noise ; NOISE opcode: creates noise
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -91,7 +91,7 @@ su_op_noise_mono:
{{end}} {{end}}
{{- if .Opcode "oscillator"}} {{- if .HasOp "oscillator"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; OSCILLAT opcode: oscillator, the heart of the synth ; OSCILLAT opcode: oscillator, the heart of the synth
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -100,11 +100,10 @@ su_op_noise_mono:
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
{{.Func "su_op_oscillator" "Opcode"}} {{.Func "su_op_oscillator" "Opcode"}}
lodsb ; load the flags lodsb ; load the flags
%ifdef RUNTIME_TABLES {{- if .Library}}
%ifdef INCLUDE_SAMPLES mov {{.DI}}, [{{.Stack "SampleTable"}}]; we need to put this in a register, as the stereo & unisons screw the stack positions
mov {{.DI}}, [{{.SP}} + su_stack.sampleoffs]; we need to put this in a register, as the stereo & unisons screw the stack positions ; ain't we lucky that {{.DI}} was unused throughout
%endif ; ain't we lucky that {{.DI}} was unused throughout {{- end}}
%endif
fld dword [{{.Input "oscillator" "detune"}}] ; e, where e is the detune [0,1] fld dword [{{.Input "oscillator" "detune"}}] ; e, where e is the detune [0,1]
{{- .Prepare (.Float 0.5)}} {{- .Prepare (.Float 0.5)}}
fsub dword [{{.Use (.Float 0.5)}}] ; e-.5 fsub dword [{{.Use (.Float 0.5)}}] ; e-.5
@ -120,7 +119,7 @@ su_op_noise_mono:
fchs ; -d r, negate the detune for second round fchs ; -d r, negate the detune for second round
su_op_oscillat_mono: su_op_oscillat_mono:
{{- end}} {{- end}}
{{- if .HasParamValueOtherThan "oscillator" "unison" 0}} {{- if .SupportsParamValueOtherThan "oscillator" "unison" 0}}
{{.PushRegs .AX "" .WRK "OscWRK" .AX "OscFlags"}} {{.PushRegs .AX "" .WRK "OscWRK" .AX "OscFlags"}}
fldz ; 0 d fldz ; 0 d
fxch ; d a=0, "accumulated signal" fxch ; d a=0, "accumulated signal"
@ -171,7 +170,7 @@ su_op_oscillat_normalized:
fadd dword [{{.WRK}}] fadd dword [{{.WRK}}]
fst dword [{{.WRK}}] fst dword [{{.WRK}}]
fadd dword [{{.Input "oscillator" "phase"}}] fadd dword [{{.Input "oscillator" "phase"}}]
{{- if .HasParamValue "oscillator" "type" .Sample}} {{- if .SupportsParamValue "oscillator" "type" .Sample}}
test al, byte 0x80 test al, byte 0x80
jz short su_op_oscillat_not_sample jz short su_op_oscillat_not_sample
{{.Call "su_oscillat_sample"}} {{.Call "su_oscillat_sample"}}
@ -185,25 +184,25 @@ su_op_oscillat_not_sample:
fstp st1 fstp st1
fld dword [{{.Input "oscillator" "color"}}] ; // c p fld dword [{{.Input "oscillator" "color"}}] ; // c p
; every oscillator test included if needed ; every oscillator test included if needed
{{- if .HasParamValue "oscillator" "type" .Sine}} {{- if .SupportsParamValue "oscillator" "type" .Sine}}
test al, byte 0x40 test al, byte 0x40
jz short su_op_oscillat_notsine jz short su_op_oscillat_notsine
{{.Call "su_oscillat_sine"}} {{.Call "su_oscillat_sine"}}
su_op_oscillat_notsine: su_op_oscillat_notsine:
{{- end}} {{- end}}
{{- if .HasParamValue "oscillator" "type" .Trisaw}} {{- if .SupportsParamValue "oscillator" "type" .Trisaw}}
test al, byte 0x20 test al, byte 0x20
jz short su_op_oscillat_not_trisaw jz short su_op_oscillat_not_trisaw
{{.Call "su_oscillat_trisaw"}} {{.Call "su_oscillat_trisaw"}}
su_op_oscillat_not_trisaw: su_op_oscillat_not_trisaw:
{{- end}} {{- end}}
{{- if .HasParamValue "oscillator" "type" .Pulse}} {{- if .SupportsParamValue "oscillator" "type" .Pulse}}
test al, byte 0x10 test al, byte 0x10
jz short su_op_oscillat_not_pulse jz short su_op_oscillat_not_pulse
{{.Call "su_oscillat_pulse"}} {{.Call "su_oscillat_pulse"}}
su_op_oscillat_not_pulse: su_op_oscillat_not_pulse:
{{- end}} {{- end}}
{{- if .HasParamValue "oscillator" "type" .Gate}} {{- if .SupportsParamValue "oscillator" "type" .Gate}}
test al, byte 0x04 test al, byte 0x04
jz short su_op_oscillat_not_gate jz short su_op_oscillat_not_gate
{{.Call "su_oscillat_gate"}} {{.Call "su_oscillat_gate"}}
@ -300,12 +299,12 @@ go4kVCO_gate_bit: ; stack: 0/1, let's call it x
{{- .PushRegs .AX "SampleAx" .DX "SampleDx" .CX "SampleCx" .BX "SampleBx" | indent 4}} ; edx must be saved, eax & ecx if this is stereo osc {{- .PushRegs .AX "SampleAx" .DX "SampleDx" .CX "SampleCx" .BX "SampleBx" | indent 4}} ; edx must be saved, eax & ecx if this is stereo osc
push {{.AX}} push {{.AX}}
mov al, byte [{{.VAL}}-4] ; reuse "color" as the sample number mov al, byte [{{.VAL}}-4] ; reuse "color" as the sample number
%ifdef RUNTIME_TABLES ; when using RUNTIME_TABLES, assumed the sample_offset ptr is in {{.DI}} {{- if .Library}}
do{lea {{.DI}}, [}, {{.DI}}, {{.AX}}*8,] ; edi points now to the sample table entry lea {{.DI}}, [{{.DI}} + {{.AX}}*8] ; edi points now to the sample table entry
%else {{- else}}
{{- .Prepare "su_sample_offsets" | indent 4}} {{- .Prepare "su_sample_offsets" | indent 4}}
lea {{.DI}}, [{{.Use "su_sample_offsets"}} + {{.AX}}*8]; edi points now to the sample table entry lea {{.DI}}, [{{.Use "su_sample_offsets"}} + {{.AX}}*8]; edi points now to the sample table entry
%endif {{- end}}
{{- .Float 84.28074964676522 | .Prepare | indent 4}} {{- .Float 84.28074964676522 | .Prepare | indent 4}}
fmul dword [{{.Float 84.28074964676522 | .Use}}] ; p*r fmul dword [{{.Float 84.28074964676522 | .Use}}] ; p*r
fistp dword [{{.SP}}] fistp dword [{{.SP}}]
@ -329,7 +328,7 @@ su_oscillat_sample_not_looping:
{{end}} {{end}}
{{- if .Opcode "loadval"}} {{- if .HasOp "loadval"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; LOADVAL opcode ; LOADVAL opcode
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -356,7 +355,7 @@ su_op_loadval_mono:
{{end}} {{end}}
{{- if .Opcode "receive"}} {{- if .HasOp "receive"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; RECEIVE opcode ; RECEIVE opcode
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
@ -387,7 +386,7 @@ su_op_receive_mono:
{{end}} {{end}}
{{- if .Opcode "in"}} {{- if .HasOp "in"}}
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------
; IN opcode: inputs and clears a global port ; IN opcode: inputs and clears a global port
;------------------------------------------------------------------------------- ;-------------------------------------------------------------------------------

View File

@ -41,3 +41,13 @@ struc su_delayline_wrk
.buffer resd 65536 .buffer resd 65536
.size: .size:
endstruc endstruc
;-------------------------------------------------------------------------------
; su_sample_offset struct
;-------------------------------------------------------------------------------
struc su_sample_offset ; length conveniently 8 bytes, so easy to index
.start resd 1
.loopstart resw 1
.looplength resw 1
.size:
endstruc

View File

@ -5,17 +5,9 @@ function(regression_test testname)
set(asmfile ${testname}.asm) set(asmfile ${testname}.asm)
set (headerfile ${CMAKE_CURRENT_BINARY_DIR}/${testname}.h) set (headerfile ${CMAKE_CURRENT_BINARY_DIR}/${testname}.h)
if(DEFINED CMAKE_C_SIZEOF_DATA_PTR AND CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
set(arch "-arch=amd64")
elseif(DEFINED CMAKE_CXX_SIZEOF_DATA_PTR AND CMAKE_CXX_SIZEOF_DATA_PTR EQUAL 8)
set(arch "-arch=amd64")
else()
set(arch "-arch=386")
endif()
add_custom_command( add_custom_command(
OUTPUT ${asmfile} OUTPUT ${asmfile}
COMMAND ${sointuexe} -a -c -w ${arch} -d ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${source} COMMAND ${sointuexe} -a -c -w -arch=${arch} -d ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${source}
DEPENDS ${source} ${sointuexe} DEPENDS ${source} ${sointuexe}
) )
@ -56,25 +48,13 @@ function(regression_test testname)
endif() endif()
endfunction(regression_test) endfunction(regression_test)
if(WIN32)
set(sointuexe ${CMAKE_CURRENT_BINARY_DIR}/sointu-cli.exe)
else()
set(sointuexe ${CMAKE_CURRENT_BINARY_DIR}/sointu-cli)
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")
message("templates=${templates}")
message("go4k=${go4k}")
# Build sointu-cli only once because go run has everytime quite a bit of delay when # Build sointu-cli only once because go run has everytime quite a bit of delay when
# starting # 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}/go4k/cmd/sointu-cli/main.go
DEPENDS "${templates}" "${go4k}" DEPENDS "${templates}" "${go4k}" "${compilersrc}" ${STATICLIB}
) )
regression_test(test_envelope "" ENVELOPE) regression_test(test_envelope "" ENVELOPE)

View File

@ -17,5 +17,3 @@ patch:
parameters: {stereo: 0} parameters: {stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -27,5 +27,3 @@ patch:
parameters: {stereo: 0} parameters: {stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -23,5 +23,3 @@ patch:
parameters: {stereo: 0} parameters: {stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -17,5 +17,3 @@ patch:
parameters: {stereo: 0, value: 96} parameters: {stereo: 0, value: 96}
- type: aux - type: aux
parameters: {channel: 0, gain: 128, stereo: 0} parameters: {channel: 0, gain: 128, stereo: 0}
delaytimes: []
sampleoffsets: []

View File

@ -29,5 +29,3 @@ patch:
parameters: {stereo: 1} parameters: {stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -25,5 +25,3 @@ patch:
parameters: {stereo: 1} parameters: {stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -27,5 +27,3 @@ patch:
parameters: {gain: 64, stereo: 1} parameters: {gain: 64, stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -27,5 +27,3 @@ patch:
parameters: {gain: 64, stereo: 1} parameters: {gain: 64, stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -51,5 +51,3 @@ patch:
parameters: {stereo: 0} parameters: {stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -51,5 +51,3 @@ patch:
parameters: {stereo: 1} parameters: {stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -27,5 +27,3 @@ patch:
parameters: {resolution: 64, stereo: 0} parameters: {resolution: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -25,5 +25,3 @@ patch:
parameters: {resolution: 32, stereo: 1} parameters: {resolution: 32, stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -16,10 +16,9 @@ patch:
- type: mulp - type: mulp
parameters: {stereo: 0} parameters: {stereo: 0}
- type: delay - type: delay
parameters: {count: 1, damp: 64, delay: 0, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0}
varargs: [11025]
- type: pan - type: pan
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: [11025]
sampleoffsets: []

View File

@ -16,7 +16,8 @@ patch:
- type: mulp - type: mulp
parameters: {stereo: 0} parameters: {stereo: 0}
- type: delay - type: delay
parameters: {count: 1, damp: 64, delay: 0, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0}
varargs: [11025]
- type: pan - type: pan
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
@ -25,5 +26,3 @@ patch:
parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0}
- type: send - type: send
parameters: {amount: 32, port: 3, sendpop: 1, stereo: 0, unit: 3, voice: 0} parameters: {amount: 32, port: 3, sendpop: 1, stereo: 0, unit: 3, voice: 0}
delaytimes: [11025]
sampleoffsets: []

View File

@ -16,7 +16,8 @@ patch:
- type: mulp - type: mulp
parameters: {stereo: 0} parameters: {stereo: 0}
- type: delay - type: delay
parameters: {count: 1, damp: 64, delay: 0, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0}
varargs: [11025]
- type: pan - type: pan
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
@ -25,5 +26,3 @@ patch:
parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0}
- type: send - type: send
parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, unit: 3, voice: 0} parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, unit: 3, voice: 0}
delaytimes: [11025]
sampleoffsets: []

View File

@ -16,7 +16,8 @@ patch:
- type: mulp - type: mulp
parameters: {stereo: 0} parameters: {stereo: 0}
- type: delay - type: delay
parameters: {count: 1, damp: 64, delay: 0, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0}
varargs: [11025]
- type: pan - type: pan
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
@ -25,5 +26,3 @@ patch:
parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0}
- type: send - type: send
parameters: {amount: 32, port: 2, sendpop: 1, stereo: 0, unit: 3, voice: 0} parameters: {amount: 32, port: 2, sendpop: 1, stereo: 0, unit: 3, voice: 0}
delaytimes: [11025]
sampleoffsets: []

View File

@ -16,7 +16,8 @@ patch:
- type: mulp - type: mulp
parameters: {stereo: 0} parameters: {stereo: 0}
- type: delay - type: delay
parameters: {count: 1, damp: 64, delay: 0, dry: 128, feedback: 0, notetracking: 0, pregain: 40, stereo: 0} parameters: {damp: 64, dry: 128, feedback: 0, notetracking: 0, pregain: 40, stereo: 0}
varargs: [1000]
- type: pan - type: pan
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
@ -25,5 +26,3 @@ patch:
parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 50, type: 0, unison: 0} parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 50, type: 0, unison: 0}
- type: send - type: send
parameters: {amount: 65, port: 4, sendpop: 1, stereo: 0, unit: 3, voice: 0} parameters: {amount: 65, port: 4, sendpop: 1, stereo: 0, unit: 3, voice: 0}
delaytimes: [1000]
sampleoffsets: []

View File

@ -20,7 +20,8 @@ patch:
- type: filter - type: filter
parameters: {bandpass: 1, frequency: 32, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0} parameters: {bandpass: 1, frequency: 32, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0}
- type: delay - type: delay
parameters: {count: 1, damp: 16, delay: 0, dry: 128, feedback: 128, notetracking: 1, pregain: 128, stereo: 0} parameters: {damp: 16, dry: 128, feedback: 128, notetracking: 1, pregain: 128, stereo: 0}
varargs: [10787]
- type: filter - type: filter
parameters: {bandpass: 1, frequency: 24, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0} parameters: {bandpass: 1, frequency: 24, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0}
- type: mulp - type: mulp
@ -29,5 +30,3 @@ patch:
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: [10787]
sampleoffsets: []

View File

@ -16,7 +16,8 @@ patch:
- type: mulp - type: mulp
parameters: {stereo: 0} parameters: {stereo: 0}
- type: delay - type: delay
parameters: {count: 1, damp: 64, delay: 0, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0}
varargs: [11025]
- type: pan - type: pan
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
@ -25,5 +26,3 @@ patch:
parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0}
- type: send - type: send
parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0} parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0}
delaytimes: [11025]
sampleoffsets: []

View File

@ -16,10 +16,9 @@ patch:
- type: mulp - type: mulp
parameters: {stereo: 0} parameters: {stereo: 0}
- type: delay - type: delay
parameters: {count: 8, damp: 64, delay: 0, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0}
varargs: [1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618]
- type: pan - type: pan
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: [1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618]
sampleoffsets: []

View File

@ -18,8 +18,7 @@ patch:
- type: pan - type: pan
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: delay - type: delay
parameters: {count: 1, damp: 64, delay: 0, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 1} parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 1}
varargs: [11025, 21025]
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: [11025, 21025]
sampleoffsets: []

View File

@ -19,5 +19,3 @@ patch:
parameters: {drive: 96, stereo: 0} parameters: {drive: 96, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -25,5 +25,3 @@ patch:
parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0} parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -15,5 +15,3 @@ patch:
parameters: {drive: 96, stereo: 1} parameters: {drive: 96, stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -15,5 +15,3 @@ patch:
parameters: {attack: 95, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} parameters: {attack: 95, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -15,5 +15,3 @@ patch:
parameters: {attack: 95, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} parameters: {attack: 95, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -25,5 +25,3 @@ patch:
parameters: {amount: 68, port: 4, sendpop: 1, stereo: 0, unit: 1, voice: 0} parameters: {amount: 68, port: 4, sendpop: 1, stereo: 0, unit: 1, voice: 0}
- type: out - type: out
parameters: {gain: 110, stereo: 1} parameters: {gain: 110, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -13,5 +13,3 @@ patch:
parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 1, sustain: 64} parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 1, sustain: 64}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -21,5 +21,3 @@ patch:
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -25,5 +25,3 @@ patch:
parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0}
- type: send - type: send
parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0} parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0}
delaytimes: []
sampleoffsets: []

View File

@ -21,5 +21,3 @@ patch:
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -21,5 +21,3 @@ patch:
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -21,5 +21,3 @@ patch:
parameters: {panning: 64, stereo: 0} parameters: {panning: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -25,5 +25,3 @@ patch:
parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0}
- type: send - type: send
parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, unit: 3, voice: 0} parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, unit: 3, voice: 0}
delaytimes: []
sampleoffsets: []

View File

@ -21,5 +21,3 @@ patch:
parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 1} parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -17,5 +17,3 @@ patch:
parameters: {gain: 64, stereo: 1} parameters: {gain: 64, stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -19,5 +19,3 @@ patch:
parameters: {gain: 64, stereo: 0} parameters: {gain: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -19,5 +19,3 @@ patch:
parameters: {holdfreq: 3, stereo: 0} parameters: {holdfreq: 3, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -25,5 +25,3 @@ patch:
parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0} parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -17,5 +17,3 @@ patch:
parameters: {holdfreq: 3, stereo: 1} parameters: {holdfreq: 3, stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -27,5 +27,3 @@ patch:
parameters: {stereo: 1} parameters: {stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -25,5 +25,3 @@ patch:
parameters: {stereo: 1} parameters: {stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -19,5 +19,3 @@ patch:
parameters: {invgain: 64, stereo: 0} parameters: {invgain: 64, stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -17,5 +17,3 @@ patch:
parameters: {invgain: 64, stereo: 1} parameters: {invgain: 64, stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -15,5 +15,3 @@ patch:
parameters: {stereo: 0} parameters: {stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -13,5 +13,3 @@ patch:
parameters: {stereo: 1} parameters: {stereo: 1}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -15,5 +15,3 @@ patch:
parameters: {stereo: 0, value: 80} parameters: {stereo: 0, value: 80}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -13,5 +13,3 @@ patch:
parameters: {stereo: 1, value: 40} parameters: {stereo: 1, value: 40}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

View File

@ -17,5 +17,3 @@ patch:
parameters: {stereo: 0} parameters: {stereo: 0}
- type: out - type: out
parameters: {gain: 128, stereo: 1} parameters: {gain: 128, stereo: 1}
delaytimes: []
sampleoffsets: []

Some files were not shown because too many files have changed in this diff Show More