mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
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:
parent
2ad61ff6b2
commit
d0bd877b3f
@ -34,17 +34,38 @@ enable_language(ASM_NASM)
|
||||
# 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>")
|
||||
|
||||
# Sointu as a header-only library
|
||||
set(HEADERLIB sointuinterface)
|
||||
add_library(${HEADERLIB} INTERFACE)
|
||||
target_include_directories(${HEADERLIB} INTERFACE ${PROJECT_SOURCE_DIR}/include)
|
||||
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")
|
||||
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
|
||||
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)
|
||||
target_link_libraries(${STATICLIB} ${HEADERLIB})
|
||||
target_compile_definitions(${STATICLIB} PUBLIC SU_USE_INTROSPECTION RUNTIME_TABLES)
|
||||
target_include_directories(${STATICLIB} INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# We should put examples here
|
||||
# add_subdirectory(examples)
|
||||
|
1
go.mod
1
go.mod
@ -12,5 +12,6 @@ require (
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.11 // 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
|
||||
)
|
||||
|
1
go.sum
1
go.sum
@ -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/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/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package go4k_test
|
||||
package bridge_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -9,19 +9,18 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/vsariola/sointu/go4k"
|
||||
"github.com/vsariola/sointu/go4k/bridge"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestAllAsmFiles(t *testing.T) {
|
||||
bridge.Init()
|
||||
_, 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 {
|
||||
t.Fatalf("cannot glob files in the test directory: %v", err)
|
||||
}
|
||||
@ -37,89 +36,16 @@ func TestAllAsmFiles(t *testing.T) {
|
||||
if err != nil {
|
||||
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 {
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("Compiling patch failed: %v", err)
|
||||
}
|
||||
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, 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)
|
||||
@ -157,7 +83,7 @@ func TestSerializingAllAsmFiles(t *testing.T) {
|
||||
|
||||
func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname))
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "tests", "expected_output", rawname))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read expected: %v", err)
|
||||
}
|
||||
@ -179,7 +105,7 @@ func compareToRawFloat32(t *testing.T, buffer []float32, rawname string) {
|
||||
|
||||
func compareToRawInt16(t *testing.T, buffer []int16, rawname string) {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", rawname))
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "tests", "expected_output", rawname))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read expected: %v", err)
|
||||
}
|
@ -1,74 +1,48 @@
|
||||
package bridge
|
||||
|
||||
// #cgo CFLAGS: -I"${SRCDIR}/../../build/"
|
||||
// #cgo LDFLAGS: "${SRCDIR}/../../build/libsointu.a"
|
||||
// #include <sointu.h>
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/vsariola/sointu/go4k"
|
||||
"github.com/vsariola/sointu/go4k/compiler"
|
||||
)
|
||||
|
||||
// #cgo CFLAGS: -I"${SRCDIR}/../../include/sointu"
|
||||
// #cgo LDFLAGS: "${SRCDIR}/../../build/libsointu.a"
|
||||
// #include <sointu.h>
|
||||
import "C"
|
||||
|
||||
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")
|
||||
func Synth(patch go4k.Patch) (*C.Synth, error) {
|
||||
s := new(C.Synth)
|
||||
comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error compiling patch: %v", err)
|
||||
}
|
||||
if e.errcode&0x04 != 0 {
|
||||
reasons = append(reasons, "FPU divide by zero")
|
||||
if len(comPatch.Commands) > 2048 { // TODO: 2048 could probably be pulled automatically from cgo
|
||||
return nil, errors.New("bridge supports at most 2048 commands; the compiled patch has more")
|
||||
}
|
||||
if e.errcode&0x01 != 0 {
|
||||
reasons = append(reasons, "FPU invalid operation")
|
||||
if len(comPatch.Values) > 16384 { // TODO: 16384 could probably be pulled automatically from cgo
|
||||
return nil, errors.New("bridge supports at most 16384 values; the compiled patch has more")
|
||||
}
|
||||
if e.errcode&0x3800 != 0 {
|
||||
reasons = append(reasons, "FPU stack push/pops are not balanced")
|
||||
for i, v := range comPatch.Commands {
|
||||
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
|
||||
@ -101,131 +75,37 @@ func (synth *C.Synth) Render(buffer []float32, maxtime int) (int, int, error) {
|
||||
return int(samples), int(time), nil
|
||||
}
|
||||
|
||||
func Synth(patch go4k.Patch) (*C.Synth, error) {
|
||||
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
|
||||
}
|
||||
|
||||
// Trigger is part of C.Synths' implementation of go4k.Synth interface
|
||||
func (s *C.Synth) Trigger(voice int, note byte) {
|
||||
s.SynthWrk.Voices[voice] = C.Voice{}
|
||||
s.SynthWrk.Voices[voice].Note = C.int(note)
|
||||
}
|
||||
|
||||
// Release is part of C.Synths' implementation of go4k.Synth interface
|
||||
func (s *C.Synth) Release(voice int) {
|
||||
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, ", ")
|
||||
}
|
||||
|
@ -12,25 +12,14 @@ import (
|
||||
"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) {
|
||||
patch := go4k.Patch{
|
||||
Instruments: []go4k.Instrument{
|
||||
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{"envelope", 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}},
|
||||
}}},
|
||||
SampleOffsets: []go4k.SampleOffset{},
|
||||
DelayTimes: []int{}}
|
||||
go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
|
||||
go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}},
|
||||
go4k.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
|
||||
}}}}
|
||||
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
@ -64,6 +53,7 @@ func TestBridge(t *testing.T) {
|
||||
for i, v := range createdb {
|
||||
if expectedb[i] != v {
|
||||
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{
|
||||
Instruments: []go4k.Instrument{
|
||||
go4k.Instrument{1, []go4k.Unit{
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
}}},
|
||||
SampleOffsets: []go4k.SampleOffset{},
|
||||
DelayTimes: []int{}}
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
}}}}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
@ -91,10 +79,8 @@ func TestStackBalancing(t *testing.T) {
|
||||
patch := go4k.Patch{
|
||||
Instruments: []go4k.Instrument{
|
||||
go4k.Instrument{1, []go4k.Unit{
|
||||
go4k.Unit{"push", map[string]int{}},
|
||||
}}},
|
||||
SampleOffsets: []go4k.SampleOffset{},
|
||||
DelayTimes: []int{}}
|
||||
go4k.Unit{Type: "push", Parameters: map[string]int{}},
|
||||
}}}}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
@ -110,27 +96,25 @@ func TestStackOverflow(t *testing.T) {
|
||||
patch := go4k.Patch{
|
||||
Instruments: []go4k.Instrument{
|
||||
go4k.Instrument{1, []go4k.Unit{
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
}}},
|
||||
SampleOffsets: []go4k.SampleOffset{},
|
||||
DelayTimes: []int{}}
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
}}}}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
@ -146,12 +130,10 @@ func TestDivideByZero(t *testing.T) {
|
||||
patch := go4k.Patch{
|
||||
Instruments: []go4k.Instrument{
|
||||
go4k.Instrument{1, []go4k.Unit{
|
||||
go4k.Unit{"loadval", map[string]int{"value": 128}},
|
||||
go4k.Unit{"invgain", map[string]int{"invgain": 0}},
|
||||
go4k.Unit{"pop", map[string]int{}},
|
||||
}}},
|
||||
SampleOffsets: []go4k.SampleOffset{},
|
||||
DelayTimes: []int{}}
|
||||
go4k.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}},
|
||||
go4k.Unit{Type: "invgain", Parameters: map[string]int{"invgain": 0}},
|
||||
go4k.Unit{Type: "pop", Parameters: map[string]int{}},
|
||||
}}}}
|
||||
synth, err := bridge.Synth(patch)
|
||||
if err != nil {
|
||||
t.Fatalf("bridge compile error: %v", err)
|
||||
|
@ -1,6 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package bridge
|
||||
|
||||
func Init() {
|
||||
}
|
@ -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
|
||||
}
|
8
go4k/bridge/init_windows.go
Normal file
8
go4k/bridge/init_windows.go
Normal 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
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package go4k_test
|
||||
package bridge_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -25,16 +25,14 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS
|
||||
func TestPlayer(t *testing.T) {
|
||||
patch := go4k.Patch{
|
||||
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{"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{"mulp", 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{"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{"mulp", map[string]int{"stereo": 0}},
|
||||
go4k.Unit{"out", map[string]int{"stereo": 1, "gain": 128}},
|
||||
}}},
|
||||
DelayTimes: []int{},
|
||||
SampleOffsets: []go4k.SampleOffset{}}
|
||||
go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
go4k.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}},
|
||||
go4k.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}},
|
||||
go4k.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
go4k.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine, "lfo": 0, "unison": 0}},
|
||||
go4k.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}},
|
||||
go4k.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
|
||||
}}}}
|
||||
patterns := [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}
|
||||
tracks := []go4k.Track{go4k.Track{1, []byte{0}}}
|
||||
song := go4k.Song{BPM: 100, Patterns: patterns, Tracks: tracks, Patch: patch, Output16Bit: false, Hold: 1}
|
||||
@ -47,7 +45,7 @@ func TestPlayer(t *testing.T) {
|
||||
t.Fatalf("Render failed: %v", err)
|
||||
}
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "tests", "expected_output", "test_oscillat_sine.raw"))
|
||||
expectedb, err := ioutil.ReadFile(path.Join(path.Dir(filename), "..", "..", "tests", "expected_output", "test_oscillat_sine.raw"))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot read expected: %v", err)
|
||||
}
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/vsariola/sointu/go4k"
|
||||
"github.com/vsariola/sointu/go4k/audio/oto"
|
||||
"github.com/vsariola/sointu/go4k/bridge"
|
||||
"github.com/vsariola/sointu/go4k/compiler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -41,12 +42,21 @@ func main() {
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
var comp *compiler.Compiler
|
||||
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
|
||||
}
|
||||
needsRendering := *play || *exactLength || *rawOut
|
||||
if needsRendering {
|
||||
bridge.Init()
|
||||
needsCompile := *headerOut || *asmOut
|
||||
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 {
|
||||
output := func(extension string, contents []byte) error {
|
||||
@ -87,11 +97,7 @@ func main() {
|
||||
var song go4k.Song
|
||||
if errJSON := json.Unmarshal(inputBytes, &song); errJSON != nil {
|
||||
if errYaml := yaml.Unmarshal(inputBytes, &song); errYaml != nil {
|
||||
song2, errAsm := go4k.ParseAsm(string(inputBytes))
|
||||
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
|
||||
return fmt.Errorf("The song could not be parsed as .json (%v) or .yml (%v)", errJSON, errYaml)
|
||||
}
|
||||
}
|
||||
var buffer []float32
|
||||
@ -121,23 +127,26 @@ func main() {
|
||||
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
|
||||
if *exactLength {
|
||||
|
||||
maxSamples = len(buffer) / 2
|
||||
}
|
||||
header := go4k.CHeader(&song, maxSamples)
|
||||
if err := output(".h", []byte(header)); err != nil {
|
||||
var err error
|
||||
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)
|
||||
}
|
||||
}
|
||||
if *asmOut {
|
||||
asmCode, err := go4k.Compile(&song, *targetArch, *targetOs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not format the song as asm file: %v", err)
|
||||
}
|
||||
if err := output(".asm", []byte(asmCode)); err != nil {
|
||||
if err := output(".asm", []byte(compiledPlayer["asm"])); err != nil {
|
||||
return fmt.Errorf("Error outputting asm file: %v", err)
|
||||
}
|
||||
}
|
||||
|
81
go4k/compiler/compiler.go
Normal file
81
go4k/compiler/compiler.go
Normal 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
|
||||
}
|
144
go4k/compiler/encoded_patch.go
Normal file
144
go4k/compiler/encoded_patch.go
Normal 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
206
go4k/compiler/featureset.go
Normal 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
61
go4k/compiler/generate.go
Normal 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()
|
||||
}
|
@ -1,16 +1,11 @@
|
||||
package go4k
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/vsariola/sointu/go4k"
|
||||
)
|
||||
|
||||
type OplistEntry struct {
|
||||
@ -19,141 +14,56 @@ type OplistEntry struct {
|
||||
}
|
||||
|
||||
type Macros struct {
|
||||
Opcodes []OplistEntry
|
||||
Polyphony bool
|
||||
MultivoiceTracks bool
|
||||
PolyphonyBitmask int
|
||||
Stacklocs []string
|
||||
Output16Bit bool
|
||||
Clip bool
|
||||
Amd64 bool
|
||||
OS string
|
||||
DisableSections bool
|
||||
Sine int // TODO: how can we elegantly access global constants in template, without wrapping each one by one
|
||||
Trisaw int
|
||||
Pulse int
|
||||
Gate int
|
||||
Sample int
|
||||
usesFloatConst map[float32]bool
|
||||
usesIntConst map[int]bool
|
||||
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)
|
||||
Stacklocs []string
|
||||
Output16Bit bool
|
||||
Clip bool
|
||||
Library bool
|
||||
Sine int // TODO: how can we elegantly access global constants in template, without wrapping each one by one
|
||||
Trisaw int
|
||||
Pulse int
|
||||
Gate int
|
||||
Sample int
|
||||
usesFloatConst map[float32]bool
|
||||
usesIntConst map[int]bool
|
||||
floatConsts []float32
|
||||
intConsts []int
|
||||
calls map[string]bool
|
||||
stackframes map[string][]string
|
||||
FeatureSet
|
||||
Compiler
|
||||
}
|
||||
|
||||
type PlayerMacros struct {
|
||||
Song *Song
|
||||
VoiceTrackBitmask int
|
||||
JumpTable []string
|
||||
Code []byte
|
||||
Values []byte
|
||||
Macros
|
||||
}
|
||||
|
||||
func NewPlayerMacros(song *Song, targetArch string, targetOS string) *PlayerMacros {
|
||||
unitInputMap := map[string](map[string]int){}
|
||||
for k, v := range UnitTypes {
|
||||
inputMap := map[string]int{}
|
||||
inputCount := 0
|
||||
for _, t := range v {
|
||||
if t.CanModulate {
|
||||
inputMap[t.Name] = inputCount
|
||||
inputCount++
|
||||
}
|
||||
}
|
||||
unitInputMap[k] = inputMap
|
||||
func NewMacros(c Compiler, f FeatureSet) *Macros {
|
||||
return &Macros{
|
||||
calls: map[string]bool{},
|
||||
usesFloatConst: map[float32]bool{},
|
||||
usesIntConst: map[int]bool{},
|
||||
stackframes: map[string][]string{},
|
||||
Sine: go4k.Sine,
|
||||
Trisaw: go4k.Trisaw,
|
||||
Pulse: go4k.Pulse,
|
||||
Gate: go4k.Gate,
|
||||
Sample: go4k.Sample,
|
||||
Compiler: c,
|
||||
FeatureSet: f,
|
||||
}
|
||||
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 {
|
||||
return p.ops[t]
|
||||
func (p *Macros) HasOp(instruction string) bool {
|
||||
_, ok := p.Opcode(instruction)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (p *Macros) Stereo(t string) bool {
|
||||
return p.stereo[t]
|
||||
func (p *Macros) Stereo(unitType string) bool {
|
||||
return p.SupportsParamValue(unitType, "stereo", 1)
|
||||
}
|
||||
|
||||
func (p *Macros) Mono(t string) bool {
|
||||
return p.mono[t]
|
||||
func (p *Macros) Mono(unitType string) bool {
|
||||
return p.SupportsParamValue(unitType, "stereo", 0)
|
||||
}
|
||||
|
||||
func (p *Macros) StereoAndMono(t string) bool {
|
||||
return p.stereo[t] && p.mono[t]
|
||||
func (p *Macros) StereoAndMono(unitType string) bool {
|
||||
return p.Stereo(unitType) && p.Mono(unitType)
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
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) {
|
||||
for i, k := range p.Stacklocs {
|
||||
if k == name {
|
||||
@ -463,7 +389,11 @@ func (p *Macros) FmtStack() string {
|
||||
|
||||
func (p *Macros) ExportFunc(name string, params ...string) string {
|
||||
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" {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
umap, ok := p.unitInputMap[unit]
|
||||
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)
|
||||
}
|
||||
i := p.InputNumber(unit, port)
|
||||
if i != 0 {
|
||||
return fmt.Sprintf("%v + %v", p.INP(), i*4), 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) {
|
||||
umap, ok := p.unitInputMap[unit]
|
||||
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)
|
||||
}
|
||||
i := p.InputNumber(unit, port)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
total := 0
|
||||
for _, instr := range p.Song.Patch.Instruments {
|
||||
@ -560,68 +478,3 @@ func (p *PlayerMacros) NumDelayLines() string {
|
||||
}
|
||||
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
|
||||
}
|
240
go4k/go4k.go
240
go4k/go4k.go
@ -2,6 +2,7 @@ package go4k
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
@ -9,6 +10,7 @@ import (
|
||||
type Unit struct {
|
||||
Type string
|
||||
Parameters map[string]int `yaml:",flow"`
|
||||
VarArgs []int
|
||||
}
|
||||
|
||||
const (
|
||||
@ -25,17 +27,9 @@ type Instrument struct {
|
||||
Units []Unit
|
||||
}
|
||||
|
||||
type SampleOffset struct {
|
||||
Start int
|
||||
LoopStart int
|
||||
LoopLength int
|
||||
}
|
||||
|
||||
// Patch is simply a list of instruments used in a song
|
||||
type Patch struct {
|
||||
Instruments []Instrument
|
||||
DelayTimes []int `yaml:",flow"`
|
||||
SampleOffsets []SampleOffset
|
||||
Instruments []Instrument
|
||||
}
|
||||
|
||||
func (p Patch) TotalVoices() int {
|
||||
@ -73,30 +67,13 @@ type Synth interface {
|
||||
|
||||
func Render(synth Synth, buffer []float32) error {
|
||||
s, _, err := synth.Render(buffer, math.MaxInt32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("go4k.Render failed: %v", err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
// and what parameters they take.
|
||||
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: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true},
|
||||
{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}},
|
||||
"compressor": []UnitParameter{
|
||||
{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: "type", MinValue: int(Sine), MaxValue: int(Sample), 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{
|
||||
{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false},
|
||||
{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: "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
|
||||
}
|
||||
|
136
go4k/song.go
136
go4k/song.go
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -14,15 +14,12 @@ var defaultSong = go4k.Song{
|
||||
},
|
||||
Patch: go4k.Patch{
|
||||
Instruments: []go4k.Instrument{{NumVoices: 2, 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, "type": go4k.Sine}},
|
||||
{"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, "type": go4k.Sine}},
|
||||
{"mulp", map[string]int{"stereo": 0}},
|
||||
{"out", map[string]int{"stereo": 1, "gain": 128}},
|
||||
}}},
|
||||
DelayTimes: []int{},
|
||||
SampleOffsets: []go4k.SampleOffset{},
|
||||
},
|
||||
{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": go4k.Sine}},
|
||||
{Type: "mulp", Parameters: map[string]int{"stereo": 0}},
|
||||
{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}},
|
||||
{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": go4k.Sine}},
|
||||
{Type: "mulp", Parameters: map[string]int{"stereo": 0}},
|
||||
{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}},
|
||||
}}}},
|
||||
}
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
304
render.asm
304
render.asm
@ -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
|
@ -1,4 +1,4 @@
|
||||
{{- if .Opcode "pop"}}
|
||||
{{- if .HasOp "pop"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; POP opcode: remove (discard) the topmost signal from the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -24,7 +24,7 @@ su_op_pop_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "add"}}
|
||||
{{- if .HasOp "add"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; ADD opcode: add the two top most signals on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -58,7 +58,7 @@ su_op_add_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "addp"}}
|
||||
{{- if .HasOp "addp"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; ADDP opcode: add the two top most signals on the stack and pop
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -84,7 +84,7 @@ su_op_addp_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "loadnote"}}
|
||||
{{- if .HasOp "loadnote"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; LOADNOTE opcode: load the current note, scaled to [-1,1]
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -109,7 +109,7 @@ su_op_addp_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "mul"}}
|
||||
{{- if .HasOp "mul"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; MUL opcode: multiply the two top most signals on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -129,7 +129,7 @@ su_op_mul_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "mulp"}}
|
||||
{{- if .HasOp "mulp"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; MULP opcode: multiply the two top most signals on the stack and pop
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -155,7 +155,7 @@ su_op_mulp_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "push"}}
|
||||
{{- if .HasOp "push"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; PUSH opcode: push the topmost signal on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -181,7 +181,7 @@ su_op_push_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "xch"}}
|
||||
{{- if .HasOp "xch"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; XCH opcode: exchange the signals on the stack
|
||||
;-------------------------------------------------------------------------------
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{- if .Opcode "distort"}}
|
||||
{{- if .HasOp "distort"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; DISTORT opcode: apply distortion on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -14,7 +14,7 @@
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "hold"}}
|
||||
{{- if .HasOp "hold"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; HOLD opcode: sample and hold the signal, reducing sample rate
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -46,7 +46,7 @@ su_op_hold_holding:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "crush"}}
|
||||
{{- if .HasOp "crush"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; CRUSH opcode: quantize the signal to finite number of levels
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -64,7 +64,7 @@ su_op_hold_holding:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "gain"}}
|
||||
{{- if .HasOp "gain"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; GAIN opcode: apply gain on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -88,7 +88,7 @@ su_op_gain_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "invgain"}}
|
||||
{{- if .HasOp "invgain"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; INVGAIN opcode: apply inverse gain on the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -112,7 +112,7 @@ su_op_invgain_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "filter"}}
|
||||
{{- if .HasOp "filter"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; 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
|
||||
fstp dword [{{.WRK}}+8] ; b'=f2*h'+b
|
||||
fldz ; 0
|
||||
{{- if .HasParamValue "filter" "lowpass" 1}}
|
||||
{{- if .SupportsParamValue "filter" "lowpass" 1}}
|
||||
test al, byte 0x40
|
||||
jz short su_op_filter_skiplowpass
|
||||
fadd dword [{{.WRK}}]
|
||||
su_op_filter_skiplowpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "bandpass" 1}}
|
||||
{{- if .SupportsParamValue "filter" "bandpass" 1}}
|
||||
test al, byte 0x20
|
||||
jz short su_op_filter_skipbandpass
|
||||
fadd dword [{{.WRK}}+8]
|
||||
su_op_filter_skipbandpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "highpass" 1}}
|
||||
{{- if .SupportsParamValue "filter" "highpass" 1}}
|
||||
test al, byte 0x10
|
||||
jz short su_op_filter_skiphighpass
|
||||
fadd dword [{{.WRK}}+4]
|
||||
su_op_filter_skiphighpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "negbandpass" 1}}
|
||||
{{- if .SupportsParamValue "filter" "negbandpass" 1}}
|
||||
test al, byte 0x08
|
||||
jz short su_op_filter_skipnegbandpass
|
||||
fsub dword [{{.WRK}}+8]
|
||||
su_op_filter_skipnegbandpass:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "filter" "neghighpass" 1}}
|
||||
{{- if .SupportsParamValue "filter" "neghighpass" 1}}
|
||||
test al, byte 0x04
|
||||
jz short su_op_filter_skipneghighpass
|
||||
fsub dword [{{.WRK}}+4]
|
||||
@ -173,7 +173,7 @@ su_op_filter_skipneghighpass:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "clip"}}
|
||||
{{- if .HasOp "clip"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; CLIP opcode: clips the signal into [-1,1] range
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -188,7 +188,7 @@ su_op_filter_skipneghighpass:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "pan" -}}
|
||||
{{- if .HasOp "pan" -}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; PAN opcode: pan the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -220,7 +220,7 @@ su_op_pan_do:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "delay"}}
|
||||
{{- if .HasOp "delay"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; DELAY opcode: adds delay effect to the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -234,12 +234,13 @@ su_op_pan_do:
|
||||
lodsw ; al = delay index, ah = delay count
|
||||
{{- .PushRegs .VAL "DelayVal" .COM "DelayCom" | indent 4}}
|
||||
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
|
||||
{{.Prepare "su_delay_times" | indent 4}}
|
||||
{{- if .Library}}
|
||||
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]
|
||||
{{- else}}
|
||||
{{- .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
|
||||
{{- end}}
|
||||
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
|
||||
{{- 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
|
||||
mov {{.PTRWORD}} [{{.Stack "DelayWorkSpace"}}],{{.CX}} ; move delay workspace pointer back to stack.
|
||||
{{- .PopRegs .VAL .COM | indent 4}}
|
||||
{{- if .UsesDelayModulation}}
|
||||
{{- if .SupportsModulation "delay" "delaytime"}}
|
||||
xor eax, eax
|
||||
mov dword [{{.Modulation "delay" "delaytime"}}], eax
|
||||
{{- end}}
|
||||
@ -281,9 +282,9 @@ su_op_delay_mono: ; flow into mono delay
|
||||
fxch ; y p*p*x
|
||||
fmul dword [{{.Input "delay" "dry"}}] ; dr*y p*p*x
|
||||
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
|
||||
{{- 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
|
||||
jne su_op_delay_skipnotesync
|
||||
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
|
||||
su_op_delay_skipnotesync:
|
||||
{{- end}}
|
||||
{{- if .UsesDelayModulation}}
|
||||
{{- if .SupportsModulation "delay" "delaytime"}}
|
||||
fld dword [{{.Modulation "delay" "delaytime"}}]
|
||||
{{- .Float 32767.0 | .Prepare | indent 8}}
|
||||
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}}
|
||||
|
||||
|
||||
{{- if .Opcode "compressor"}}
|
||||
{{- if .HasOp "compressor"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; COMPRES opcode: push compressor gain to stack
|
||||
;-------------------------------------------------------------------------------
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{- if .Opcode "speed" -}}
|
||||
{{- if .HasOp "speed" -}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; SPEED opcode: modulate the speed (bpm) of the song based on ST0
|
||||
;-------------------------------------------------------------------------------
|
||||
|
@ -1,3 +1,5 @@
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Sample}}
|
||||
|
||||
{{- if eq .OS "windows"}}
|
||||
{{.ExportFunc "su_load_gmdls"}}
|
||||
{{- if .Amd64}}
|
||||
@ -24,7 +26,7 @@
|
||||
call ReadFile ; Readfile(handle,&su_sample_table,SAMPLE_TABLE_SIZE,&bytes_read,NULL)
|
||||
add rsp, 40 ; shadow space, as required by Win64 ABI
|
||||
ret
|
||||
{{- else}}
|
||||
{{else}}
|
||||
mov edx, su_sample_table
|
||||
mov ecx, su_gmdls_path1
|
||||
su_gmdls_pathloop:
|
||||
@ -50,9 +52,9 @@ extern _ReadFile@20 ; requires windows
|
||||
db 'drivers/gm.dls',0
|
||||
su_gmdls_path2:
|
||||
db 'drivers/etc/gm.dls',0
|
||||
{{end}}
|
||||
|
||||
{{.SectBss "susamtable"}}
|
||||
su_sample_table:
|
||||
resb 3440660 ; size of gmdls.
|
||||
|
||||
{{end}}
|
||||
|
@ -1,113 +1,10 @@
|
||||
; 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
|
||||
{{template "structs.asm" .}}
|
||||
|
||||
struc su_synth
|
||||
.synthwrk resb su_synthworkspace.size
|
||||
.delaywrks resb su_delayline_wrk.size * 64
|
||||
.synth_wrk resb su_synthworkspace.size
|
||||
.delay_wrks resb su_delayline_wrk.size * 64
|
||||
.delaytimes resw 768
|
||||
.sampleoffs resb su_sampleoff.size * 256
|
||||
.sampleoffs resb su_sample_offset.size * 256
|
||||
.randseed resd 1
|
||||
.globaltime resd 1
|
||||
.commands resb 32 * 64
|
||||
@ -116,189 +13,122 @@ struc su_synth
|
||||
.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
|
||||
{{.ExportFunc "su_render" "SynthStateParam" "BufferPtrParam" "SamplesParam" "TimeParam"}}
|
||||
{{- if .Amd64}}
|
||||
{{- if eq .OS "windows"}}
|
||||
{{- .PushRegs "rdi" "NonVolatileRDI" "rsi" "NonVolatileRSI" "rbx" "NonVolatileRBX" "rbp" "NonVolatileRBP" | indent 4}}
|
||||
mov rsi, r8 ; rsi = &samples
|
||||
mov rbx, r9 ; rbx = &time
|
||||
{{- else}} ; SystemV amd64 ABI, linux mac or hopefully something similar
|
||||
{{- .PushRegs "rbx" "NonVolatileRBX" "rbp" "NonVolatileRBP" | indent 4}}
|
||||
mov rbx, rcx ; rbx points to time
|
||||
xchg rsi, rdx ; rdx points to buffer, rsi points to samples
|
||||
mov rcx, rdi ; rcx = &Synthstate
|
||||
{{- end}}
|
||||
{{- else}}
|
||||
{{- .PushRegs | indent 4 }} ; push registers
|
||||
mov ecx, [{{.Stack "SynthStateParam"}}] ; ecx = &synthState
|
||||
mov edx, [{{.Stack "BufferPtrParam"}}] ; edx = &buffer
|
||||
mov esi, [{{.Stack "SamplesParam"}}] ; esi = &samples
|
||||
mov ebx, [{{.Stack "TimeParam"}}] ; ebx = &time
|
||||
{{- end}}
|
||||
{{.SaveFPUState | indent 4}} ; save the FPU state to stack & reset the FPU
|
||||
{{.Push .SI "Samples"}}
|
||||
{{.Push .BX "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
|
||||
{{.Push .AX "BufSample"}}
|
||||
mov esi, [{{.SI}}] ; zero extend dereferenced pointer
|
||||
{{.Push .SI "BufSize"}}
|
||||
{{.Push .DX "BufPtr"}}
|
||||
{{.Push .CX "SynthState"}}
|
||||
lea {{.AX}}, [{{.CX}} + su_synth.sampleoffs]
|
||||
{{.Push .AX "SampleTable"}}
|
||||
lea {{.AX}}, [{{.CX}} + su_synth.delaytimes]
|
||||
{{.Push .AX "DelayTable"}}
|
||||
mov eax, [{{.CX}} + su_synth.randseed]
|
||||
{{.Push .AX "RandSeed"}}
|
||||
mov eax, [{{.CX}} + su_synth.globaltime]
|
||||
{{.Push .AX "GlobalTick"}}
|
||||
mov ebx, dword [{{.BX}}] ; zero extend dereferenced pointer
|
||||
{{.Push .BX "RowLength"}} ; 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!
|
||||
push {{.DI}}
|
||||
fnstsw [{{.SP}}] ; store the FPU status flag to stack top
|
||||
pop {{.DI}} ; {{.DI}} = FPU status flag
|
||||
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!
|
||||
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
|
||||
mov ecx, [_SP + PTRSIZE*7] ; ecx = buffer length in samples
|
||||
cmp [_SP + PTRSIZE*8], ecx ; if samples >= maxsamples
|
||||
mov ecx, [{{.Stack "BufSize"}}] ; ecx = buffer length in samples
|
||||
cmp [{{.Stack "BufSample"}}], 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]
|
||||
inc dword [{{.Stack "BufSample"}}] ; samples++
|
||||
mov {{.CX}}, [{{.Stack "SynthState"}}]
|
||||
{{.Push .AX "Sample"}}
|
||||
mov eax, [{{.CX}} + su_synth.polyphony]
|
||||
{{.Push .AX "PolyphonyBitmask"}}
|
||||
mov eax, [{{.CX}} + su_synth.numvoices]
|
||||
{{.Push .AX "VoicesRemain"}}
|
||||
lea {{.DX}}, [{{.CX}}+ su_synth.synth_wrk]
|
||||
lea {{.COM}}, [{{.CX}}+ su_synth.commands]
|
||||
lea {{.VAL}}, [{{.CX}}+ su_synth.values]
|
||||
lea {{.WRK}}, [{{.DX}} + su_synthworkspace.voices]
|
||||
lea {{.CX}}, [{{.CX}}+ su_synth.delay_wrks - su_delayline_wrk.filtstate]
|
||||
{{.Call "su_run_vm"}}
|
||||
{{.Pop .AX}}
|
||||
{{.Pop .AX}}
|
||||
mov {{.DI}}, [{{.Stack "BufPtr"}}] ; edi containts buffer ptr
|
||||
mov {{.CX}}, [{{.Stack "SynthState"}}]
|
||||
lea {{.SI}}, [{{.CX}} + su_synth.synth_wrk + 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]
|
||||
mov [{{.Stack "BufPtr"}}], {{.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
|
||||
{{.Pop .AX}}
|
||||
inc dword [{{.Stack "GlobalTick"}}] ; 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
|
||||
{{.Pop .CX}}
|
||||
{{.Pop .BX}}
|
||||
{{.Pop .DX}}
|
||||
{{.Pop .CX}}
|
||||
{{.Pop .CX}}
|
||||
{{.Pop .CX}}
|
||||
mov [{{.CX}} + su_synth.randseed], edx
|
||||
mov [{{.CX}} + su_synth.globaltime], ebx
|
||||
{{.Pop .BX}}
|
||||
{{.Pop .BX}}
|
||||
{{.Pop .DX}}
|
||||
{{.Pop .BX}}
|
||||
{{.Pop .SI}}
|
||||
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 {{.VAL}}ue
|
||||
{{.LoadFPUState | indent 4}} ; load the FPU state from stack
|
||||
{{- if .Amd64}}
|
||||
{{- if eq .OS "windows"}}
|
||||
{{- .PopRegs "rdi" "rsi" "rbx" "rbp" | indent 4}}
|
||||
{{- else}} ; SystemV amd64 ABI, linux mac or hopefully something similar
|
||||
{{- .PopRegs "rbx" "rbp" | indent 4}}
|
||||
{{- end}}
|
||||
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
|
||||
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
|
||||
{{template "patch.asm" .}}
|
||||
|
||||
; 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
|
||||
;-------------------------------------------------------------------------------
|
||||
; Constants
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.SectData "constants"}}
|
||||
{{.Constants}}
|
||||
|
@ -38,11 +38,11 @@ typedef struct SampleOffset {
|
||||
|
||||
typedef struct Synth {
|
||||
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];
|
||||
struct SampleOffset SampleOffsets[256];
|
||||
unsigned int RandSeed;
|
||||
unsigned int GlobalTick;
|
||||
unsigned int GlobalTick;
|
||||
unsigned char Commands[32 * 64];
|
||||
unsigned char Values[32 * 64 * 8];
|
||||
unsigned int Polyphony;
|
||||
@ -69,7 +69,7 @@ void CALLCONV su_load_gmdls(void);
|
||||
// actually rendered and how many time ticks were actually advanced.
|
||||
//
|
||||
// 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 ...
|
||||
// samples pointer to the maximum number of samples to be rendered.
|
||||
// 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.
|
||||
int CALLCONV su_render(Synth* synth, float* buffer, int* samples, int* time);
|
||||
|
||||
// Arithmetic opcode ids
|
||||
extern const int su_add_id;
|
||||
extern const int su_addp_id;
|
||||
extern const int su_pop_id;
|
||||
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;
|
||||
#define SU_ADVANCE_ID 0
|
||||
{{- range $index, $element := .Instructions}}
|
||||
#define {{printf "su_%v_id" $element | upper | printf "%-20v"}}{{add1 $index | mul 2}}
|
||||
{{- end}}
|
||||
|
||||
#endif // _SOINTU_H
|
@ -25,8 +25,8 @@ su_run_vm_loop: ; loop until all voices done
|
||||
xor ecx, ecx ; counter = 0
|
||||
xor eax, eax ; clear out high bits of eax, as lodsb only sets al
|
||||
su_transform_values_loop:
|
||||
{{- .Prepare "su_vm_transformcounts" | indent 4}}
|
||||
cmp cl, byte [{{.Use "su_vm_transformcounts"}}+{{.DI}}] ; compare the counter to the value in the param count table
|
||||
{{- .Prepare "su_vm_transformcounts-1" | indent 4}}
|
||||
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
|
||||
lodsb ; load the byte value from VAL stream
|
||||
push {{.AX}} ; push it to memory so FPU can read it
|
||||
@ -43,11 +43,12 @@ su_transform_values_loop:
|
||||
su_transform_values_out:
|
||||
bt dword [{{.COM}}-1],0 ; LSB of COM = stereo bit => carry
|
||||
{{- .SaveStack "Opcode"}}
|
||||
{{- .Prepare "su_vm_jumptable" | indent 4}}
|
||||
call [{{.Use "su_vm_jumptable"}}+{{.DI}}*{{.PTRSIZE}}] ; call the function corresponding to the instruction
|
||||
{{- $x := printf "su_vm_jumptable-%v" .PTRSIZE}}
|
||||
{{- .Prepare $x | indent 4}}
|
||||
call [{{.Use $x}}+{{.DI}}*{{.PTRSIZE}}] ; call the function corresponding to the instruction
|
||||
jmp su_run_vm_loop
|
||||
su_run_vm_advance:
|
||||
{{- if .Polyphony}}
|
||||
{{- if .SupportsPolyphony}}
|
||||
mov {{.WRK}}, [{{.Stack "Voice"}}] ; WRK points to start of current 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
|
||||
@ -173,18 +174,15 @@ su_clip_do:
|
||||
; 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.
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_vm_jumptable_offset"}}
|
||||
su_vm_jumptable equ $ - {{.PTRSIZE}} ; Advance is not in the opcode table
|
||||
{{- $x := .}}
|
||||
{{- range .Opcodes}}
|
||||
{{$x.DPTR}} su_op_{{.Type}}
|
||||
{{.Data "su_vm_jumptable"}}
|
||||
{{- range .Instructions}}
|
||||
{{$.DPTR}} su_op_{{.}}
|
||||
{{- end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The number of transformed parameters each opcode takes
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_vm_transformcounts_offset"}}
|
||||
su_vm_transformcounts equ $ - 1 ; Advance is not in the opcode table
|
||||
{{- range .Opcodes}}
|
||||
db {{.NumParams}}
|
||||
{{.Data "su_vm_transformcounts"}}
|
||||
{{- range .Instructions}}
|
||||
db {{$.TransformCount .}}
|
||||
{{- end}}
|
||||
|
@ -26,7 +26,7 @@ su_synth_obj:
|
||||
{{- end}}
|
||||
{{- $prologsize := len .Stacklocs}}
|
||||
xor eax, eax
|
||||
{{- if .MultivoiceTracks}}
|
||||
{{- if ne .VoiceTrackBitmask 0}}
|
||||
{{.Push (.VoiceTrackBitmask | printf "%v") "VoiceTrackBitmask"}}
|
||||
{{- end}}
|
||||
{{.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
|
||||
su_render_sampleloop: ; loop through every sample in the row
|
||||
{{.Push .AX "Sample"}}
|
||||
{{- if .Polyphony}}
|
||||
{{- if .SupportsPolyphony}}
|
||||
{{.Push (.PolyphonyBitmask | printf "%v") "PolyphonyBitmask"}} ; does the next voice reuse the current opcodes?
|
||||
{{- end}}
|
||||
{{.Push (.Song.Patch.TotalVoices | printf "%v") "VoicesRemain"}}
|
||||
mov {{.DX}}, {{.PTRWORD}} su_synth_obj ; {{.DX}} points to the synth object
|
||||
mov {{.COM}}, {{.PTRWORD}} su_patch_code ; COM points to vm code
|
||||
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]
|
||||
{{- end}}
|
||||
lea {{.WRK}}, [{{.DX}} + su_synthworkspace.voices] ; WRK points to the first voice
|
||||
{{.Call "su_run_vm"}} ; run through the VM code
|
||||
{{.Pop .AX}}
|
||||
{{- if .Polyphony}}
|
||||
{{- if .SupportsPolyphony}}
|
||||
{{.Pop .AX}}
|
||||
{{- end}}
|
||||
{{- 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}}
|
||||
jl su_render_rowloop
|
||||
; rewind the stack the entropy of multiple pop {{.AX}} is probably lower than add
|
||||
{{- $x := .}}
|
||||
{{- range (.Sub (len .Stacklocs) $prologsize | .Count)}}
|
||||
{{$x.Pop $x.AX}}
|
||||
{{- range slice .Stacklocs $prologsize}}
|
||||
{{$.Pop $.AX}}
|
||||
{{- end}}
|
||||
{{- if .Amd64}}
|
||||
{{- if eq .OS "windows"}}
|
||||
@ -89,7 +88,7 @@ su_render_sampleloop: ; loop through every sample in the row
|
||||
; Dirty: pretty much everything
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_update_voices"}}
|
||||
{{- if .MultivoiceTracks}}
|
||||
{{- if ne .VoiceTrackBitmask 0}}
|
||||
; The more complicated implementation: one track can trigger multiple voices
|
||||
xor edx, edx
|
||||
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 ","}}
|
||||
{{- end}}
|
||||
|
||||
{{- if gt (.Song.Patch.SampleOffsets | len) 0}}
|
||||
{{- if gt (.SampleOffsets | len) 0}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; Sample offsets
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_sample_offsets"}}
|
||||
{{- range .Song.Patch.SampleOffsets}}
|
||||
{{- range .SampleOffsets}}
|
||||
dd {{.Start}}
|
||||
dw {{.LoopStart}}
|
||||
dw {{.LoopLength}}
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
{{- if gt (.Song.Patch.DelayTimes | len ) 0}}
|
||||
{{- if gt (.DelayTimes | len ) 0}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; Delay times
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_delay_times"}}
|
||||
dw {{.Song.Patch.DelayTimes | toStrings | join ","}}
|
||||
dw {{.DelayTimes | toStrings | join ","}}
|
||||
{{end}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The code for this patch, basically indices to vm jump table
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Data "su_patch_code"}}
|
||||
db {{.Code | toStrings | join ","}}
|
||||
db {{.Commands | toStrings | join ","}}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The parameters / inputs to each opcode
|
||||
|
50
templates/player.h
Normal file
50
templates/player.h
Normal 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
|
@ -1,4 +1,4 @@
|
||||
{{- if .Opcode "out"}}
|
||||
{{- if .HasOp "out"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; OUT opcode: outputs and pops the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -26,7 +26,7 @@ su_op_out_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "outaux"}}
|
||||
{{- if .HasOp "outaux"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; OUTAUX opcode: outputs to main and aux1 outputs and pops the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -54,7 +54,7 @@ su_op_outaux_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "aux"}}
|
||||
{{- if .HasOp "aux"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; AUX opcode: outputs the signal to aux (or main) port and pops the signal
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -79,7 +79,7 @@ su_op_aux_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "send"}}
|
||||
{{- if .HasOp "send"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; SEND opcode: adds the signal to a port
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -103,7 +103,7 @@ su_op_aux_mono:
|
||||
fxch ; swap them back: l r
|
||||
su_op_send_mono:
|
||||
{{- end}}
|
||||
{{- if .HasParamValueOtherThan "send" "voice" 0}}
|
||||
{{- if .SupportsParamValueOtherThan "send" "voice" 0}}
|
||||
test {{.AX}}, 0x8000
|
||||
jz su_op_send_skipglobal
|
||||
mov {{.CX}}, [{{.Stack "Synth"}}]
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{if .Opcode "envelope" -}}
|
||||
{{if .HasOp "envelope" -}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; ENVELOPE opcode: pushes an ADSR envelope value on stack [0,1]
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -62,7 +62,7 @@ su_op_envelope_leave2:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "noise"}}
|
||||
{{- if .HasOp "noise"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; NOISE opcode: creates noise
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -91,7 +91,7 @@ su_op_noise_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "oscillator"}}
|
||||
{{- if .HasOp "oscillator"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; OSCILLAT opcode: oscillator, the heart of the synth
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -100,11 +100,10 @@ su_op_noise_mono:
|
||||
;-------------------------------------------------------------------------------
|
||||
{{.Func "su_op_oscillator" "Opcode"}}
|
||||
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
|
||||
{{- if .Library}}
|
||||
mov {{.DI}}, [{{.Stack "SampleTable"}}]; 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
|
||||
{{- end}}
|
||||
fld dword [{{.Input "oscillator" "detune"}}] ; e, where e is the detune [0,1]
|
||||
{{- .Prepare (.Float 0.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
|
||||
su_op_oscillat_mono:
|
||||
{{- end}}
|
||||
{{- if .HasParamValueOtherThan "oscillator" "unison" 0}}
|
||||
{{- if .SupportsParamValueOtherThan "oscillator" "unison" 0}}
|
||||
{{.PushRegs .AX "" .WRK "OscWRK" .AX "OscFlags"}}
|
||||
fldz ; 0 d
|
||||
fxch ; d a=0, "accumulated signal"
|
||||
@ -171,7 +170,7 @@ su_op_oscillat_normalized:
|
||||
fadd dword [{{.WRK}}]
|
||||
fst dword [{{.WRK}}]
|
||||
fadd dword [{{.Input "oscillator" "phase"}}]
|
||||
{{- if .HasParamValue "oscillator" "type" .Sample}}
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Sample}}
|
||||
test al, byte 0x80
|
||||
jz short su_op_oscillat_not_sample
|
||||
{{.Call "su_oscillat_sample"}}
|
||||
@ -185,25 +184,25 @@ su_op_oscillat_not_sample:
|
||||
fstp st1
|
||||
fld dword [{{.Input "oscillator" "color"}}] ; // c p
|
||||
; every oscillator test included if needed
|
||||
{{- if .HasParamValue "oscillator" "type" .Sine}}
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Sine}}
|
||||
test al, byte 0x40
|
||||
jz short su_op_oscillat_notsine
|
||||
{{.Call "su_oscillat_sine"}}
|
||||
su_op_oscillat_notsine:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "oscillator" "type" .Trisaw}}
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Trisaw}}
|
||||
test al, byte 0x20
|
||||
jz short su_op_oscillat_not_trisaw
|
||||
{{.Call "su_oscillat_trisaw"}}
|
||||
su_op_oscillat_not_trisaw:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "oscillator" "type" .Pulse}}
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Pulse}}
|
||||
test al, byte 0x10
|
||||
jz short su_op_oscillat_not_pulse
|
||||
{{.Call "su_oscillat_pulse"}}
|
||||
su_op_oscillat_not_pulse:
|
||||
{{- end}}
|
||||
{{- if .HasParamValue "oscillator" "type" .Gate}}
|
||||
{{- if .SupportsParamValue "oscillator" "type" .Gate}}
|
||||
test al, byte 0x04
|
||||
jz short su_op_oscillat_not_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
|
||||
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
|
||||
{{- if .Library}}
|
||||
lea {{.DI}}, [{{.DI}} + {{.AX}}*8] ; edi points now to the sample table entry
|
||||
{{- else}}
|
||||
{{- .Prepare "su_sample_offsets" | indent 4}}
|
||||
lea {{.DI}}, [{{.Use "su_sample_offsets"}} + {{.AX}}*8]; edi points now to the sample table entry
|
||||
%endif
|
||||
{{- end}}
|
||||
{{- .Float 84.28074964676522 | .Prepare | indent 4}}
|
||||
fmul dword [{{.Float 84.28074964676522 | .Use}}] ; p*r
|
||||
fistp dword [{{.SP}}]
|
||||
@ -329,7 +328,7 @@ su_oscillat_sample_not_looping:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "loadval"}}
|
||||
{{- if .HasOp "loadval"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; LOADVAL opcode
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -356,7 +355,7 @@ su_op_loadval_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "receive"}}
|
||||
{{- if .HasOp "receive"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; RECEIVE opcode
|
||||
;-------------------------------------------------------------------------------
|
||||
@ -387,7 +386,7 @@ su_op_receive_mono:
|
||||
{{end}}
|
||||
|
||||
|
||||
{{- if .Opcode "in"}}
|
||||
{{- if .HasOp "in"}}
|
||||
;-------------------------------------------------------------------------------
|
||||
; IN opcode: inputs and clears a global port
|
||||
;-------------------------------------------------------------------------------
|
||||
|
@ -41,3 +41,13 @@ struc su_delayline_wrk
|
||||
.buffer resd 65536
|
||||
.size:
|
||||
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
|
@ -5,17 +5,9 @@ function(regression_test testname)
|
||||
set(asmfile ${testname}.asm)
|
||||
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(
|
||||
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}
|
||||
)
|
||||
|
||||
@ -56,25 +48,13 @@ function(regression_test testname)
|
||||
endif()
|
||||
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
|
||||
# starting
|
||||
add_custom_command(
|
||||
OUTPUT ${sointuexe}
|
||||
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)
|
||||
|
@ -17,5 +17,3 @@ patch:
|
||||
parameters: {stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -27,5 +27,3 @@ patch:
|
||||
parameters: {stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -23,5 +23,3 @@ patch:
|
||||
parameters: {stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -17,5 +17,3 @@ patch:
|
||||
parameters: {stereo: 0, value: 96}
|
||||
- type: aux
|
||||
parameters: {channel: 0, gain: 128, stereo: 0}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -29,5 +29,3 @@ patch:
|
||||
parameters: {stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -25,5 +25,3 @@ patch:
|
||||
parameters: {stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -27,5 +27,3 @@ patch:
|
||||
parameters: {gain: 64, stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -27,5 +27,3 @@ patch:
|
||||
parameters: {gain: 64, stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -51,5 +51,3 @@ patch:
|
||||
parameters: {stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -51,5 +51,3 @@ patch:
|
||||
parameters: {stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -27,5 +27,3 @@ patch:
|
||||
parameters: {resolution: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -25,5 +25,3 @@ patch:
|
||||
parameters: {resolution: 32, stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -16,10 +16,9 @@ patch:
|
||||
- type: mulp
|
||||
parameters: {stereo: 0}
|
||||
- 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
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: [11025]
|
||||
sampleoffsets: []
|
||||
|
@ -16,7 +16,8 @@ patch:
|
||||
- type: mulp
|
||||
parameters: {stereo: 0}
|
||||
- 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
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- 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}
|
||||
- type: send
|
||||
parameters: {amount: 32, port: 3, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
delaytimes: [11025]
|
||||
sampleoffsets: []
|
||||
|
@ -16,7 +16,8 @@ patch:
|
||||
- type: mulp
|
||||
parameters: {stereo: 0}
|
||||
- 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
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- 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}
|
||||
- type: send
|
||||
parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
delaytimes: [11025]
|
||||
sampleoffsets: []
|
||||
|
@ -16,7 +16,8 @@ patch:
|
||||
- type: mulp
|
||||
parameters: {stereo: 0}
|
||||
- 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
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- 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}
|
||||
- type: send
|
||||
parameters: {amount: 32, port: 2, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
delaytimes: [11025]
|
||||
sampleoffsets: []
|
||||
|
@ -16,7 +16,8 @@ patch:
|
||||
- type: mulp
|
||||
parameters: {stereo: 0}
|
||||
- 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
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- 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}
|
||||
- type: send
|
||||
parameters: {amount: 65, port: 4, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
delaytimes: [1000]
|
||||
sampleoffsets: []
|
||||
|
@ -20,7 +20,8 @@ patch:
|
||||
- type: filter
|
||||
parameters: {bandpass: 1, frequency: 32, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0}
|
||||
- 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
|
||||
parameters: {bandpass: 1, frequency: 24, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0}
|
||||
- type: mulp
|
||||
@ -29,5 +30,3 @@ patch:
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: [10787]
|
||||
sampleoffsets: []
|
||||
|
@ -16,7 +16,8 @@ patch:
|
||||
- type: mulp
|
||||
parameters: {stereo: 0}
|
||||
- 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
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- 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}
|
||||
- type: send
|
||||
parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
delaytimes: [11025]
|
||||
sampleoffsets: []
|
||||
|
@ -16,10 +16,9 @@ patch:
|
||||
- type: mulp
|
||||
parameters: {stereo: 0}
|
||||
- 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
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: [1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618]
|
||||
sampleoffsets: []
|
||||
|
@ -18,8 +18,7 @@ patch:
|
||||
- type: pan
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- 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
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: [11025, 21025]
|
||||
sampleoffsets: []
|
||||
|
@ -19,5 +19,3 @@ patch:
|
||||
parameters: {drive: 96, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -25,5 +25,3 @@ patch:
|
||||
parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -15,5 +15,3 @@ patch:
|
||||
parameters: {drive: 96, stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -15,5 +15,3 @@ patch:
|
||||
parameters: {attack: 95, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -15,5 +15,3 @@ patch:
|
||||
parameters: {attack: 95, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -25,5 +25,3 @@ patch:
|
||||
parameters: {amount: 68, port: 4, sendpop: 1, stereo: 0, unit: 1, voice: 0}
|
||||
- type: out
|
||||
parameters: {gain: 110, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -13,5 +13,3 @@ patch:
|
||||
parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 1, sustain: 64}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -21,5 +21,3 @@ patch:
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -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}
|
||||
- type: send
|
||||
parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -21,5 +21,3 @@ patch:
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -21,5 +21,3 @@ patch:
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -21,5 +21,3 @@ patch:
|
||||
parameters: {panning: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -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}
|
||||
- type: send
|
||||
parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -21,5 +21,3 @@ patch:
|
||||
parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -17,5 +17,3 @@ patch:
|
||||
parameters: {gain: 64, stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -19,5 +19,3 @@ patch:
|
||||
parameters: {gain: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -19,5 +19,3 @@ patch:
|
||||
parameters: {holdfreq: 3, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -25,5 +25,3 @@ patch:
|
||||
parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -17,5 +17,3 @@ patch:
|
||||
parameters: {holdfreq: 3, stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -27,5 +27,3 @@ patch:
|
||||
parameters: {stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -25,5 +25,3 @@ patch:
|
||||
parameters: {stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -19,5 +19,3 @@ patch:
|
||||
parameters: {invgain: 64, stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -17,5 +17,3 @@ patch:
|
||||
parameters: {invgain: 64, stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -15,5 +15,3 @@ patch:
|
||||
parameters: {stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -13,5 +13,3 @@ patch:
|
||||
parameters: {stereo: 1}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -15,5 +15,3 @@ patch:
|
||||
parameters: {stereo: 0, value: 80}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -13,5 +13,3 @@ patch:
|
||||
parameters: {stereo: 1, value: 40}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
@ -17,5 +17,3 @@ patch:
|
||||
parameters: {stereo: 0}
|
||||
- type: out
|
||||
parameters: {gain: 128, stereo: 1}
|
||||
delaytimes: []
|
||||
sampleoffsets: []
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user