mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -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
|
||||
### Added
|
||||
- Support for gm.dls samples in the go-written virtual machine
|
||||
- Ability to lock delay relative to beat duration
|
||||
- Ability to import 4klang patches (.4kp) and instruments (.4ki)
|
||||
- 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
|
||||
out of ports in small intros.
|
||||
- **Pattern length does not have to be a power of 2**.
|
||||
- **Sample-based oscillators, with samples imported from gm.dls**. Reading
|
||||
gm.dls is obviously Windows only, but with some effort the sample mechanism
|
||||
can be used also without it, in case you are working on a 64k and have some
|
||||
kilobytes to spare. See [this example](tests/test_oscillat_sample.yml), and
|
||||
this go generate [program](cmd/sointu-generate/main.go) parses the gm.dls
|
||||
file and dumps the sample offsets from it.
|
||||
- **Sample-based oscillators, with samples imported from gm.dls**. The
|
||||
gm.dls is available from system folder only on Windows, but the
|
||||
non-native tracker looks for it also in the current folder, so
|
||||
should you somehow magically get hold of gm.dls on Linux or Mac, you
|
||||
can drop it in the same folder with the tracker. See [this example](tests/test_oscillat_sample.yml),
|
||||
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
|
||||
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
|
||||
|
@ -1,9 +1,12 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/vsariola/sointu"
|
||||
)
|
||||
@ -63,6 +66,27 @@ const (
|
||||
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) {
|
||||
bytePatch, err := Encode(patch, AllFeatures{}, bpm)
|
||||
if err != nil {
|
||||
@ -429,37 +453,53 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
||||
} else {
|
||||
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
|
||||
switch {
|
||||
case flags&0x40 == 0x40: // Sine
|
||||
if phase < color {
|
||||
amplitude = float32(math.Sin(2 * math.Pi * float64(phase/color)))
|
||||
*statevar += float32(omega)
|
||||
if flags&0x80 == 0x80 { // if this is a sample oscillator
|
||||
phase := *statevar
|
||||
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
|
||||
if phase >= color {
|
||||
phase = 1 - phase
|
||||
color = 1 - color
|
||||
sampleindex += int(sampleoffset.Start)
|
||||
amplitude = float32(int16(binary.LittleEndian.Uint16(su_sample_table[sampleindex*2:]))) / 32767.0
|
||||
} else {
|
||||
*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 {
|
||||
output += waveshape(amplitude, params[4]) * params[5]
|
||||
|
@ -28,8 +28,8 @@ func TestAllRegressionTests(t *testing.T) {
|
||||
basename := filepath.Base(filename)
|
||||
testname := strings.TrimSuffix(basename, path.Ext(basename))
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
if strings.Contains(testname, "sample") {
|
||||
t.Skip("Samples (gm.dls) not available in the interpreter VM at the moment")
|
||||
if runtime.GOOS != "windows" && strings.Contains(testname, "sample") {
|
||||
t.Skip("Samples (gm.dls) available only on Windows")
|
||||
return
|
||||
}
|
||||
asmcode, err := ioutil.ReadFile(filename)
|
||||
|
Loading…
Reference in New Issue
Block a user