mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-03 00:58:26 -04:00
feat(vm): add support for gm.dls samples in the go virtual machine (closes #75)
This commit is contained in:
parent
6ec06c760a
commit
7dd2c246a0
@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
### Added
|
### Added
|
||||||
|
- Support for gm.dls samples in the go-written virtual machine
|
||||||
- Ability to lock delay relative to beat duration
|
- Ability to lock delay relative to beat duration
|
||||||
- Ability to import 4klang patches (.4kp) and instruments (.4ki)
|
- Ability to import 4klang patches (.4kp) and instruments (.4ki)
|
||||||
- Ability to run sointu as a vsti plugin, inside vsti host
|
- Ability to run sointu as a vsti plugin, inside vsti host
|
||||||
|
13
README.md
13
README.md
@ -358,12 +358,13 @@ New features since fork
|
|||||||
ports / 4 stereo ports, so even this method of routing is unlikely to run
|
ports / 4 stereo ports, so even this method of routing is unlikely to run
|
||||||
out of ports in small intros.
|
out of ports in small intros.
|
||||||
- **Pattern length does not have to be a power of 2**.
|
- **Pattern length does not have to be a power of 2**.
|
||||||
- **Sample-based oscillators, with samples imported from gm.dls**. Reading
|
- **Sample-based oscillators, with samples imported from gm.dls**. The
|
||||||
gm.dls is obviously Windows only, but with some effort the sample mechanism
|
gm.dls is available from system folder only on Windows, but the
|
||||||
can be used also without it, in case you are working on a 64k and have some
|
non-native tracker looks for it also in the current folder, so
|
||||||
kilobytes to spare. See [this example](tests/test_oscillat_sample.yml), and
|
should you somehow magically get hold of gm.dls on Linux or Mac, you
|
||||||
this go generate [program](cmd/sointu-generate/main.go) parses the gm.dls
|
can drop it in the same folder with the tracker. See [this example](tests/test_oscillat_sample.yml),
|
||||||
file and dumps the sample offsets from it.
|
and this go generate [program](cmd/sointu-generate/main.go) parses
|
||||||
|
the gm.dls file and dumps the sample offsets from it.
|
||||||
- **Unison oscillators**. Multiple copies of the oscillator running slightly
|
- **Unison oscillators**. Multiple copies of the oscillator running slightly
|
||||||
detuned and added up to together. Great for trance leads (supersaw). Unison
|
detuned and added up to together. Great for trance leads (supersaw). Unison
|
||||||
of up to 4, or 8 if you make stereo unison oscillator and add up both left
|
of up to 4, or 8 if you make stereo unison oscillator and add up both left
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/vsariola/sointu"
|
"github.com/vsariola/sointu"
|
||||||
)
|
)
|
||||||
@ -63,6 +66,27 @@ const (
|
|||||||
envStateRelease
|
envStateRelease
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var su_sample_table [3440660]byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var f *os.File
|
||||||
|
var err error
|
||||||
|
if f, err = os.Open("gm.dls"); err == nil { // try to open from current directory first
|
||||||
|
goto success
|
||||||
|
}
|
||||||
|
if f, err = os.Open(filepath.Join(os.Getenv("SystemRoot"), "system32", "drivers", "gm.dls")); err == nil {
|
||||||
|
goto success
|
||||||
|
}
|
||||||
|
if f, err = os.Open(filepath.Join(os.Getenv("SystemRoot"), "SysWOW64", "drivers", "gm.dls")); err == nil {
|
||||||
|
goto success
|
||||||
|
}
|
||||||
|
return
|
||||||
|
success:
|
||||||
|
defer f.Close()
|
||||||
|
// read file, ignoring errors
|
||||||
|
f.Read(su_sample_table[:])
|
||||||
|
}
|
||||||
|
|
||||||
func Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
func Synth(patch sointu.Patch, bpm int) (sointu.Synth, error) {
|
||||||
bytePatch, err := Encode(patch, AllFeatures{}, bpm)
|
bytePatch, err := Encode(patch, AllFeatures{}, bpm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -429,37 +453,53 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
|||||||
} else {
|
} else {
|
||||||
omega *= 0.000038 // pretty random scaling constant to get LFOs into reasonable range. Historical reasons, goes all the way back to 4klang
|
omega *= 0.000038 // pretty random scaling constant to get LFOs into reasonable range. Historical reasons, goes all the way back to 4klang
|
||||||
}
|
}
|
||||||
*statevar += float32(omega)
|
|
||||||
*statevar -= float32(int(*statevar+1) - 1)
|
|
||||||
phase := *statevar
|
|
||||||
phase += params[2]
|
|
||||||
phase -= float32(int(phase))
|
|
||||||
color := params[3]
|
|
||||||
var amplitude float32
|
var amplitude float32
|
||||||
switch {
|
*statevar += float32(omega)
|
||||||
case flags&0x40 == 0x40: // Sine
|
if flags&0x80 == 0x80 { // if this is a sample oscillator
|
||||||
if phase < color {
|
phase := *statevar
|
||||||
amplitude = float32(math.Sin(2 * math.Pi * float64(phase/color)))
|
phase += params[2]
|
||||||
|
sampleno := valuesAtTransform[3] // reuse color as the sample number
|
||||||
|
sampleoffset := s.bytePatch.SampleOffsets[sampleno]
|
||||||
|
sampleindex := int(phase*84.28074964676522 + 0.5)
|
||||||
|
loopstart := int(sampleoffset.LoopStart)
|
||||||
|
if sampleindex >= loopstart {
|
||||||
|
sampleindex -= loopstart
|
||||||
|
sampleindex %= int(sampleoffset.LoopLength)
|
||||||
|
sampleindex += loopstart
|
||||||
}
|
}
|
||||||
case flags&0x20 == 0x20: // Trisaw
|
sampleindex += int(sampleoffset.Start)
|
||||||
if phase >= color {
|
amplitude = float32(int16(binary.LittleEndian.Uint16(su_sample_table[sampleindex*2:]))) / 32767.0
|
||||||
phase = 1 - phase
|
} else {
|
||||||
color = 1 - color
|
*statevar -= float32(int(*statevar+1) - 1)
|
||||||
|
phase := *statevar
|
||||||
|
phase += params[2]
|
||||||
|
phase -= float32(int(phase))
|
||||||
|
color := params[3]
|
||||||
|
switch {
|
||||||
|
case flags&0x40 == 0x40: // Sine
|
||||||
|
if phase < color {
|
||||||
|
amplitude = float32(math.Sin(2 * math.Pi * float64(phase/color)))
|
||||||
|
}
|
||||||
|
case flags&0x20 == 0x20: // Trisaw
|
||||||
|
if phase >= color {
|
||||||
|
phase = 1 - phase
|
||||||
|
color = 1 - color
|
||||||
|
}
|
||||||
|
amplitude = phase/color*2 - 1
|
||||||
|
case flags&0x10 == 0x10: // Pulse
|
||||||
|
if phase >= color {
|
||||||
|
amplitude = -1
|
||||||
|
} else {
|
||||||
|
amplitude = 1
|
||||||
|
}
|
||||||
|
case flags&0x4 == 0x4: // Gate
|
||||||
|
maskLow, maskHigh := valuesAtTransform[3], valuesAtTransform[4]
|
||||||
|
gateBits := (int(maskHigh) << 8) + int(maskLow)
|
||||||
|
amplitude = float32((gateBits >> (int(phase*16+.5) & 15)) & 1)
|
||||||
|
g := unit.state[4+i] // warning: still fucks up with unison = 3
|
||||||
|
amplitude += 0.99609375 * (g - amplitude)
|
||||||
|
unit.state[4+i] = amplitude
|
||||||
}
|
}
|
||||||
amplitude = phase/color*2 - 1
|
|
||||||
case flags&0x10 == 0x10: // Pulse
|
|
||||||
if phase >= color {
|
|
||||||
amplitude = -1
|
|
||||||
} else {
|
|
||||||
amplitude = 1
|
|
||||||
}
|
|
||||||
case flags&0x4 == 0x4: // Gate
|
|
||||||
maskLow, maskHigh := valuesAtTransform[3], valuesAtTransform[4]
|
|
||||||
gateBits := (int(maskHigh) << 8) + int(maskLow)
|
|
||||||
amplitude = float32((gateBits >> (int(phase*16+.5) & 15)) & 1)
|
|
||||||
g := unit.state[4+i] // warning: still fucks up with unison = 3
|
|
||||||
amplitude += 0.99609375 * (g - amplitude)
|
|
||||||
unit.state[4+i] = amplitude
|
|
||||||
}
|
}
|
||||||
if flags&0x4 == 0 {
|
if flags&0x4 == 0 {
|
||||||
output += waveshape(amplitude, params[4]) * params[5]
|
output += waveshape(amplitude, params[4]) * params[5]
|
||||||
|
@ -28,8 +28,8 @@ func TestAllRegressionTests(t *testing.T) {
|
|||||||
basename := filepath.Base(filename)
|
basename := filepath.Base(filename)
|
||||||
testname := strings.TrimSuffix(basename, path.Ext(basename))
|
testname := strings.TrimSuffix(basename, path.Ext(basename))
|
||||||
t.Run(testname, func(t *testing.T) {
|
t.Run(testname, func(t *testing.T) {
|
||||||
if strings.Contains(testname, "sample") {
|
if runtime.GOOS != "windows" && strings.Contains(testname, "sample") {
|
||||||
t.Skip("Samples (gm.dls) not available in the interpreter VM at the moment")
|
t.Skip("Samples (gm.dls) available only on Windows")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
asmcode, err := ioutil.ReadFile(filename)
|
asmcode, err := ioutil.ReadFile(filename)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user