mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-03 17:18:20 -04:00
With this change, forgetting to initialize unison results in the default behaviour: 0 means one oscillator, 3 means four oscillators in unison.
227 lines
6.0 KiB
Go
227 lines
6.0 KiB
Go
package go4k
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func ParseAsm(reader io.Reader) (*Song, error) {
|
|
var bpm int
|
|
scanner := bufio.NewScanner(reader)
|
|
patterns := make([][]byte, 0)
|
|
tracks := make([]Track, 0)
|
|
var patch Patch
|
|
var instr Instrument
|
|
paramReg, err := regexp.Compile(`([a-zA-Z]\w*)\s*\(\s*([0-9]+)\s*\)`) // matches FOO(42), groups "FOO" and "42"
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parseParams := func(s string) (map[string]int, error) {
|
|
matches := paramReg.FindAllStringSubmatch(s, 256)
|
|
ret := map[string]int{}
|
|
for _, match := range matches {
|
|
val, err := strconv.Atoi(match[2])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error converting %v to integer, which is unexpected as regexp matches only numbers", match[2])
|
|
}
|
|
ret[strings.ToLower(match[1])] = val
|
|
}
|
|
return ret, nil
|
|
}
|
|
flagsReg, err := regexp.Compile(`FLAGS\s*\(\s*(\s*\w+(?:\s*\+\s*\w+)*)\s*\)`) // matches FLAGS ( FOO + FAA), groups "FOO + FAA"
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
flagNameReg, err := regexp.Compile(`\w+`) // matches any alphanumeric word
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parseFlags := func(s string) map[string]bool {
|
|
match := flagsReg.FindStringSubmatch(s)
|
|
if match == nil {
|
|
return nil
|
|
}
|
|
ret := map[string]bool{}
|
|
flagmatches := flagNameReg.FindAllString(match[1], 256)
|
|
for _, f := range flagmatches {
|
|
if f != "NONE" {
|
|
ret[f] = true
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
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
|
|
}
|
|
numberReg, err := regexp.Compile(`-?[0-9]+|HLD`) // finds integer numbers, possibly with a sign in front. HLD is the magic value used by sointu, will be interpreted as 1
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parseNumbers := func(s string) ([]int, error) {
|
|
matches := numberReg.FindAllString(s, 256)
|
|
ret := []int{}
|
|
for _, str := range matches {
|
|
var i int
|
|
var err error
|
|
if str == "HLD" {
|
|
i = 1
|
|
} else {
|
|
i, err = strconv.Atoi(str)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
ret = append(ret, i)
|
|
}
|
|
return ret, nil
|
|
}
|
|
toBytes := func(ints []int) []byte {
|
|
ret := []byte{}
|
|
for _, v := range ints {
|
|
ret = append(ret, byte(v))
|
|
}
|
|
return ret
|
|
}
|
|
unitNameMap := map[string]string{
|
|
"SU_ADD": "add",
|
|
"SU_ADDP": "addp",
|
|
"SU_POP": "pop",
|
|
"SU_LOADNOTE": "loadnote",
|
|
"SU_MUL": "mul",
|
|
"SU_MULP": "mulp",
|
|
"SU_PUSH": "push",
|
|
"SU_XCH": "xch",
|
|
"SU_DISTORT": "distort",
|
|
"SU_HOLD": "hold",
|
|
"SU_CRUSH": "crush",
|
|
"SU_GAIN": "gain",
|
|
"SU_INVGAIN": "invgain",
|
|
"SU_FILTER": "filter",
|
|
"SU_CLIP": "clip",
|
|
"SU_PAN": "pan",
|
|
"SU_DELAY": "delay",
|
|
"SU_COMPRES": "compressor",
|
|
"SU_SPEED": "speed",
|
|
"SU_OUT": "out",
|
|
"SU_OUTAUX": "outaux",
|
|
"SU_AUX": "aux",
|
|
"SU_SEND": "send",
|
|
"SU_ENVELOPE": "envelope",
|
|
"SU_NOISE": "noise",
|
|
"SU_OSCILLAT": "oscillator",
|
|
"SU_LOADVAL": "loadval",
|
|
"SU_RECEIVE": "receive",
|
|
"SU_IN": "in",
|
|
}
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
macroMatch := wordReg.FindStringSubmatch(line)
|
|
if macroMatch != nil {
|
|
word, rest := macroMatch[1], macroMatch[2]
|
|
switch word {
|
|
case "define":
|
|
defineMatch := wordReg.FindStringSubmatch(rest)
|
|
if defineMatch != nil {
|
|
defineName, defineRest := defineMatch[1], defineMatch[2]
|
|
if defineName == "BPM" {
|
|
ints, err := parseNumbers(defineRest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bpm = ints[0]
|
|
}
|
|
}
|
|
case "PATTERN":
|
|
ints, err := parseNumbers(rest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
patterns = append(patterns, toBytes(ints))
|
|
case "TRACK":
|
|
ints, err := parseNumbers(rest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
track := Track{ints[0], toBytes(ints[1:])}
|
|
tracks = append(tracks, track)
|
|
case "BEGIN_INSTRUMENT":
|
|
ints, err := parseNumbers(rest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
instr = Instrument{NumVoices: ints[0], Units: []Unit{}}
|
|
case "END_INSTRUMENT":
|
|
patch = append(patch, instr)
|
|
}
|
|
if unittype, ok := unitNameMap[word]; ok {
|
|
instrMatch := wordReg.FindStringSubmatch(rest)
|
|
if instrMatch != nil {
|
|
stereoMono, instrRest := instrMatch[1], instrMatch[2]
|
|
stereo := stereoMono == "STEREO"
|
|
parameters, err := parseParams(instrRest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error parsing parameters: %v", err)
|
|
}
|
|
flags := parseFlags(instrRest)
|
|
if unittype == "oscillator" {
|
|
if flags["SINE"] {
|
|
parameters["type"] = Sine
|
|
} else if flags["TRISAW"] {
|
|
parameters["type"] = Trisaw
|
|
} else if flags["PULSE"] {
|
|
parameters["type"] = Pulse
|
|
} else if flags["GATE"] {
|
|
parameters["type"] = Gate
|
|
} else if flags["SAMPLE"] {
|
|
parameters["type"] = Sample
|
|
} else {
|
|
return nil, errors.New("Invalid oscillator type")
|
|
}
|
|
if flags["UNISON4"] {
|
|
parameters["unison"] = 3
|
|
} else if flags["UNISON3"] {
|
|
parameters["unison"] = 2
|
|
} else if flags["UNISON2"] {
|
|
parameters["unison"] = 1
|
|
} else {
|
|
parameters["unison"] = 0
|
|
}
|
|
if flags["LFO"] {
|
|
parameters["lfo"] = 1
|
|
} else {
|
|
parameters["lfo"] = 0
|
|
}
|
|
} else if unittype == "filter" {
|
|
for _, flag := range []string{"LOWPASS", "BANDPASS", "HIGHPASS", "NEGBANDPASS", "NEGHIGHPASS"} {
|
|
if flags[flag] {
|
|
parameters[strings.ToLower(flag)] = 1
|
|
} else {
|
|
parameters[strings.ToLower(flag)] = 0
|
|
}
|
|
}
|
|
} else if unittype == "send" {
|
|
if _, ok := parameters["voice"]; !ok {
|
|
parameters["voice"] = -1
|
|
}
|
|
if flags["SEND_POP"] {
|
|
parameters["pop"] = 1
|
|
} else {
|
|
parameters["pop"] = 0
|
|
}
|
|
}
|
|
unit := Unit{Type: unittype, Stereo: stereo, Parameters: parameters}
|
|
instr.Units = append(instr.Units, unit)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
s := Song{BPM: bpm, Patterns: patterns, Tracks: tracks, Patch: patch, SongLength: -1}
|
|
return &s, nil
|
|
}
|