From 8791172e3827d166a60abf77717526d8fdf8cf30 Mon Sep 17 00:00:00 2001 From: Jason Crawford Date: Sun, 17 Mar 2024 18:56:05 -0700 Subject: [PATCH] Reintroduce gomixing back into playback package - fix issues with generally bad mixing - add stereo separation --- channelstate.go | 2 +- filter/amigafilter.go | 2 +- filter/echofilter.go | 2 +- filter/filter.go | 2 +- filter/it_resonantfilter.go | 2 +- format/common/basesong.go | 2 +- format/it/channel/data.go | 2 +- format/it/channel/effect_sampleoffset.go | 2 +- format/it/load/instrument.go | 2 +- format/it/panning/panning.go | 2 +- format/it/voice/modulator_amp.go | 2 +- format/it/voice/modulator_pan.go | 2 +- format/it/voice/sampler.go | 4 +- format/it/voice/voice.go | 4 +- format/it/volume/finevolume.go | 2 +- format/it/volume/volume.go | 2 +- format/s3m/channel/data.go | 2 +- format/s3m/channel/effect_sampleoffset.go | 2 +- format/s3m/load/s3mformat.go | 2 +- format/s3m/panning/panning.go | 2 +- format/s3m/voice/sampler.go | 4 +- format/s3m/volume/finevolume.go | 2 +- format/s3m/volume/volume.go | 2 +- format/xm/channel/data.go | 2 +- format/xm/channel/effect_sampleoffset.go | 2 +- format/xm/load/xmformat.go | 2 +- format/xm/panning/panning.go | 2 +- format/xm/voice/modulator_amp.go | 2 +- format/xm/voice/modulator_pan.go | 2 +- format/xm/voice/sampler.go | 4 +- format/xm/volume/voleffect.go | 2 +- format/xm/volume/volume.go | 2 +- go.mod | 3 +- go.sum | 6 +- instrument/instrument.go | 4 +- instrument/opl2.go | 2 +- instrument/pcm.go | 2 +- instrument/sample.go | 2 +- instrument/util.go | 2 +- internal/examples/bufferload/example.go | 10 +- internal/examples/fileload/example.go | 10 +- mixing/channeldata.go | 19 +++ mixing/mixbuffer.go | 117 ++++++++++++++ mixing/mixer.go | 77 +++++++++ mixing/panmixer.go | 26 +++ mixing/panmixer_mono.go | 23 +++ mixing/panmixer_quad.go | 35 +++++ mixing/panmixer_stereo.go | 57 +++++++ mixing/panning/mixer.go | 8 + mixing/panning/mixer_stereo.go | 17 ++ mixing/panning/position.go | 42 +++++ mixing/sampling/format.go | 27 ++++ mixing/sampling/format_bit16.go | 116 ++++++++++++++ mixing/sampling/format_bit32float.go | 55 +++++++ mixing/sampling/format_bit64float.go | 55 +++++++ mixing/sampling/format_bit8.go | 112 +++++++++++++ mixing/sampling/formatter.go | 52 ++++++ mixing/sampling/position.go | 15 ++ mixing/sampling/sampler.go | 25 +++ mixing/sampling/samplerimpl.go | 24 +++ mixing/volume/matrix.go | 183 ++++++++++++++++++++++ mixing/volume/staticmatrix.go | 4 + mixing/volume/volume.go | 107 +++++++++++++ output/premixdata.go | 4 +- player/machine/channel.go | 2 +- player/machine/channel_notedecode.go | 2 +- player/machine/channel_pastnote.go | 2 +- player/machine/machine.go | 4 +- player/machine/machine_channel.go | 2 +- player/machine/machine_factory.go | 2 +- player/machine/machine_globals.go | 2 +- player/machine/machine_opl2.go | 10 +- player/machine/machine_render.go | 27 ++-- player/machine/newnoteinfo.go | 2 +- player/machine/pastnote.go | 4 +- player/render/channel.go | 7 +- player/sampler/sampler.go | 12 +- song/channeldata.go | 2 +- song/song.go | 2 +- voice/component/modulator_amp.go | 2 +- voice/component/modulator_fadeout.go | 2 +- voice/component/modulator_pan.go | 2 +- voice/component/opl2.go | 2 +- voice/component/sampler.go | 4 +- voice/component/vol0optimization.go | 2 +- voice/fadeout/fadeout.go | 2 +- voice/filter/filterapplier.go | 2 +- voice/mixer/details.go | 13 +- voice/mixer/output.go | 4 +- voice/pcm/bit16.go | 2 +- voice/pcm/bit32float.go | 2 +- voice/pcm/bit64float.go | 2 +- voice/pcm/bit8.go | 2 +- voice/pcm/converter.go | 2 +- voice/pcm/reader.go | 2 +- voice/pcm/sample_native.go | 2 +- voice/pcm/sampledata.go | 2 +- voice/pcm/samplereader.go | 2 +- voice/render.go | 13 +- voice/types/panning.go | 2 +- voice/types/volume.go | 2 +- voice/voice.go | 6 +- 102 files changed, 1331 insertions(+), 140 deletions(-) create mode 100644 mixing/channeldata.go create mode 100644 mixing/mixbuffer.go create mode 100644 mixing/mixer.go create mode 100644 mixing/panmixer.go create mode 100644 mixing/panmixer_mono.go create mode 100644 mixing/panmixer_quad.go create mode 100644 mixing/panmixer_stereo.go create mode 100644 mixing/panning/mixer.go create mode 100644 mixing/panning/mixer_stereo.go create mode 100644 mixing/panning/position.go create mode 100644 mixing/sampling/format.go create mode 100644 mixing/sampling/format_bit16.go create mode 100644 mixing/sampling/format_bit32float.go create mode 100644 mixing/sampling/format_bit64float.go create mode 100644 mixing/sampling/format_bit8.go create mode 100644 mixing/sampling/formatter.go create mode 100644 mixing/sampling/position.go create mode 100644 mixing/sampling/sampler.go create mode 100644 mixing/sampling/samplerimpl.go create mode 100644 mixing/volume/matrix.go create mode 100644 mixing/volume/staticmatrix.go create mode 100644 mixing/volume/volume.go diff --git a/channelstate.go b/channelstate.go index f1f04e1..e06b2b6 100644 --- a/channelstate.go +++ b/channelstate.go @@ -1,7 +1,7 @@ package playback import ( - "github.com/gotracker/gomixing/sampling" + "github.com/gotracker/playback/mixing/sampling" "github.com/gotracker/playback/instrument" "github.com/gotracker/playback/voice/types" diff --git a/filter/amigafilter.go b/filter/amigafilter.go index c8e6895..7a78505 100644 --- a/filter/amigafilter.go +++ b/filter/amigafilter.go @@ -3,8 +3,8 @@ package filter import ( "math" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/frequency" + "github.com/gotracker/playback/mixing/volume" ) type amigaLPFChannelData struct { diff --git a/filter/echofilter.go b/filter/echofilter.go index 27b7eb7..ba48279 100644 --- a/filter/echofilter.go +++ b/filter/echofilter.go @@ -3,8 +3,8 @@ package filter import ( "math" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/frequency" + "github.com/gotracker/playback/mixing/volume" ) type EchoFilterSettings struct { diff --git a/filter/filter.go b/filter/filter.go index 5ef5a56..ba16698 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -1,8 +1,8 @@ package filter import ( - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/frequency" + "github.com/gotracker/playback/mixing/volume" ) type Info struct { diff --git a/filter/it_resonantfilter.go b/filter/it_resonantfilter.go index a04e67e..9ecc2d8 100644 --- a/filter/it_resonantfilter.go +++ b/filter/it_resonantfilter.go @@ -3,7 +3,7 @@ package filter import ( "math" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/frequency" "github.com/heucuva/optional" diff --git a/format/common/basesong.go b/format/common/basesong.go index ba5f2e0..78a0bb0 100644 --- a/format/common/basesong.go +++ b/format/common/basesong.go @@ -4,7 +4,7 @@ import ( "reflect" "time" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/index" "github.com/gotracker/playback/instrument" diff --git a/format/it/channel/data.go b/format/it/channel/data.go index ad78b7d..d18a5a4 100644 --- a/format/it/channel/data.go +++ b/format/it/channel/data.go @@ -5,7 +5,7 @@ import ( "strings" itfile "github.com/gotracker/goaudiofile/music/tracked/it" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback" itNote "github.com/gotracker/playback/format/it/note" diff --git a/format/it/channel/effect_sampleoffset.go b/format/it/channel/effect_sampleoffset.go index 2ff2d01..bd6a169 100644 --- a/format/it/channel/effect_sampleoffset.go +++ b/format/it/channel/effect_sampleoffset.go @@ -3,7 +3,7 @@ package channel import ( "fmt" - "github.com/gotracker/gomixing/sampling" + "github.com/gotracker/playback/mixing/sampling" itPanning "github.com/gotracker/playback/format/it/panning" itVolume "github.com/gotracker/playback/format/it/volume" diff --git a/format/it/load/instrument.go b/format/it/load/instrument.go index 0950a9a..d1c24c6 100644 --- a/format/it/load/instrument.go +++ b/format/it/load/instrument.go @@ -8,8 +8,8 @@ import ( "math" itfile "github.com/gotracker/goaudiofile/music/tracked/it" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/frequency" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/period" "github.com/gotracker/playback/player/feature" "github.com/gotracker/playback/util" diff --git a/format/it/panning/panning.go b/format/it/panning/panning.go index e3f6a20..1a8aef4 100644 --- a/format/it/panning/panning.go +++ b/format/it/panning/panning.go @@ -4,7 +4,7 @@ import ( "math" itfile "github.com/gotracker/goaudiofile/music/tracked/it" - "github.com/gotracker/gomixing/panning" + "github.com/gotracker/playback/mixing/panning" "github.com/gotracker/playback/voice/types" ) diff --git a/format/it/voice/modulator_amp.go b/format/it/voice/modulator_amp.go index 99dfb54..eaccaac 100644 --- a/format/it/voice/modulator_amp.go +++ b/format/it/voice/modulator_amp.go @@ -1,8 +1,8 @@ package voice import ( - "github.com/gotracker/gomixing/volume" itVolume "github.com/gotracker/playback/format/it/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/voice/types" "github.com/heucuva/optional" ) diff --git a/format/it/voice/modulator_pan.go b/format/it/voice/modulator_pan.go index c4cdf78..4780b3c 100644 --- a/format/it/voice/modulator_pan.go +++ b/format/it/voice/modulator_pan.go @@ -1,8 +1,8 @@ package voice import ( - "github.com/gotracker/gomixing/panning" itPanning "github.com/gotracker/playback/format/it/panning" + "github.com/gotracker/playback/mixing/panning" "github.com/gotracker/playback/note" "github.com/gotracker/playback/voice/types" ) diff --git a/format/it/voice/sampler.go b/format/it/voice/sampler.go index 32b0281..c6da459 100644 --- a/format/it/voice/sampler.go +++ b/format/it/voice/sampler.go @@ -1,9 +1,9 @@ package voice import ( - "github.com/gotracker/gomixing/sampling" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/frequency" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" ) type voicerPos interface { diff --git a/format/it/voice/voice.go b/format/it/voice/voice.go index a94df8d..fbe4b36 100644 --- a/format/it/voice/voice.go +++ b/format/it/voice/voice.go @@ -4,8 +4,6 @@ import ( "errors" "fmt" - "github.com/gotracker/gomixing/panning" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/filter" itFilter "github.com/gotracker/playback/format/it/filter" itOscillator "github.com/gotracker/playback/format/it/oscillator" @@ -13,6 +11,8 @@ import ( itVolume "github.com/gotracker/playback/format/it/volume" "github.com/gotracker/playback/frequency" "github.com/gotracker/playback/instrument" + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/period" "github.com/gotracker/playback/voice" "github.com/gotracker/playback/voice/autovibrato" diff --git a/format/it/volume/finevolume.go b/format/it/volume/finevolume.go index 53ac5dd..14e131d 100644 --- a/format/it/volume/finevolume.go +++ b/format/it/volume/finevolume.go @@ -4,7 +4,7 @@ import ( "math" itfile "github.com/gotracker/goaudiofile/music/tracked/it" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/voice/types" ) diff --git a/format/it/volume/volume.go b/format/it/volume/volume.go index 7c0426e..f7f8556 100644 --- a/format/it/volume/volume.go +++ b/format/it/volume/volume.go @@ -4,7 +4,7 @@ import ( "math" itfile "github.com/gotracker/goaudiofile/music/tracked/it" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/voice/types" ) diff --git a/format/s3m/channel/data.go b/format/s3m/channel/data.go index 5116c53..f5d2f20 100644 --- a/format/s3m/channel/data.go +++ b/format/s3m/channel/data.go @@ -5,7 +5,7 @@ import ( "strings" s3mfile "github.com/gotracker/goaudiofile/music/tracked/s3m" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback" s3mPanning "github.com/gotracker/playback/format/s3m/panning" diff --git a/format/s3m/channel/effect_sampleoffset.go b/format/s3m/channel/effect_sampleoffset.go index 1f40c43..7df8a55 100644 --- a/format/s3m/channel/effect_sampleoffset.go +++ b/format/s3m/channel/effect_sampleoffset.go @@ -3,10 +3,10 @@ package channel import ( "fmt" - "github.com/gotracker/gomixing/sampling" s3mPanning "github.com/gotracker/playback/format/s3m/panning" s3mVolume "github.com/gotracker/playback/format/s3m/volume" "github.com/gotracker/playback/index" + "github.com/gotracker/playback/mixing/sampling" "github.com/gotracker/playback/period" "github.com/gotracker/playback/player/machine" ) diff --git a/format/s3m/load/s3mformat.go b/format/s3m/load/s3mformat.go index 21b4a83..4faa84d 100644 --- a/format/s3m/load/s3mformat.go +++ b/format/s3m/load/s3mformat.go @@ -7,7 +7,7 @@ import ( "io" s3mfile "github.com/gotracker/goaudiofile/music/tracked/s3m" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/format/common" "github.com/gotracker/playback/format/s3m/channel" diff --git a/format/s3m/panning/panning.go b/format/s3m/panning/panning.go index 156d6b7..c899cb7 100644 --- a/format/s3m/panning/panning.go +++ b/format/s3m/panning/panning.go @@ -3,7 +3,7 @@ package panning import ( "math" - "github.com/gotracker/gomixing/panning" + "github.com/gotracker/playback/mixing/panning" "github.com/gotracker/playback/voice/types" ) diff --git a/format/s3m/voice/sampler.go b/format/s3m/voice/sampler.go index d72bd4b..c1e7653 100644 --- a/format/s3m/voice/sampler.go +++ b/format/s3m/voice/sampler.go @@ -1,9 +1,9 @@ package voice import ( - "github.com/gotracker/gomixing/sampling" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/frequency" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" ) type voicerPos interface { diff --git a/format/s3m/volume/finevolume.go b/format/s3m/volume/finevolume.go index ba15d50..2a52403 100644 --- a/format/s3m/volume/finevolume.go +++ b/format/s3m/volume/finevolume.go @@ -4,7 +4,7 @@ import ( "math" s3mfile "github.com/gotracker/goaudiofile/music/tracked/s3m" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/voice/types" ) diff --git a/format/s3m/volume/volume.go b/format/s3m/volume/volume.go index f2d0ba5..cb4dc2a 100644 --- a/format/s3m/volume/volume.go +++ b/format/s3m/volume/volume.go @@ -4,7 +4,7 @@ import ( "math" s3mfile "github.com/gotracker/goaudiofile/music/tracked/s3m" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/voice/types" ) diff --git a/format/xm/channel/data.go b/format/xm/channel/data.go index 9263aeb..7f38c78 100644 --- a/format/xm/channel/data.go +++ b/format/xm/channel/data.go @@ -5,7 +5,7 @@ import ( "strings" xmfile "github.com/gotracker/goaudiofile/music/tracked/xm" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback" xmNote "github.com/gotracker/playback/format/xm/note" diff --git a/format/xm/channel/effect_sampleoffset.go b/format/xm/channel/effect_sampleoffset.go index 5b9f2bd..c25ab62 100644 --- a/format/xm/channel/effect_sampleoffset.go +++ b/format/xm/channel/effect_sampleoffset.go @@ -3,7 +3,7 @@ package channel import ( "fmt" - "github.com/gotracker/gomixing/sampling" + "github.com/gotracker/playback/mixing/sampling" xmPanning "github.com/gotracker/playback/format/xm/panning" xmVolume "github.com/gotracker/playback/format/xm/volume" diff --git a/format/xm/load/xmformat.go b/format/xm/load/xmformat.go index 7cbb85f..afbefa0 100644 --- a/format/xm/load/xmformat.go +++ b/format/xm/load/xmformat.go @@ -6,7 +6,7 @@ import ( "math" xmfile "github.com/gotracker/goaudiofile/music/tracked/xm" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/heucuva/optional" "github.com/gotracker/playback/format/common" diff --git a/format/xm/panning/panning.go b/format/xm/panning/panning.go index 4481f08..75878cf 100644 --- a/format/xm/panning/panning.go +++ b/format/xm/panning/panning.go @@ -3,7 +3,7 @@ package panning import ( "math" - "github.com/gotracker/gomixing/panning" + "github.com/gotracker/playback/mixing/panning" "github.com/gotracker/playback/voice/types" ) diff --git a/format/xm/voice/modulator_amp.go b/format/xm/voice/modulator_amp.go index bb7be8c..52c62a8 100644 --- a/format/xm/voice/modulator_amp.go +++ b/format/xm/voice/modulator_amp.go @@ -1,8 +1,8 @@ package voice import ( - "github.com/gotracker/gomixing/volume" xmVolume "github.com/gotracker/playback/format/xm/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/voice/types" ) diff --git a/format/xm/voice/modulator_pan.go b/format/xm/voice/modulator_pan.go index b33ce3a..fee372a 100644 --- a/format/xm/voice/modulator_pan.go +++ b/format/xm/voice/modulator_pan.go @@ -1,8 +1,8 @@ package voice import ( - "github.com/gotracker/gomixing/panning" xmPanning "github.com/gotracker/playback/format/xm/panning" + "github.com/gotracker/playback/mixing/panning" "github.com/gotracker/playback/voice/types" ) diff --git a/format/xm/voice/sampler.go b/format/xm/voice/sampler.go index d2475af..ef84144 100644 --- a/format/xm/voice/sampler.go +++ b/format/xm/voice/sampler.go @@ -1,9 +1,9 @@ package voice import ( - "github.com/gotracker/gomixing/sampling" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/frequency" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" ) type voicerPos interface { diff --git a/format/xm/volume/voleffect.go b/format/xm/volume/voleffect.go index ca26f38..9bf87b9 100644 --- a/format/xm/volume/voleffect.go +++ b/format/xm/volume/voleffect.go @@ -1,6 +1,6 @@ package volume -import "github.com/gotracker/gomixing/volume" +import "github.com/gotracker/playback/mixing/volume" // VolEffect holds the data related to volume and effects from the volume data channel type VolEffect uint8 diff --git a/format/xm/volume/volume.go b/format/xm/volume/volume.go index 53e06d8..bc37241 100644 --- a/format/xm/volume/volume.go +++ b/format/xm/volume/volume.go @@ -3,7 +3,7 @@ package volume import ( "math" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/voice/types" ) diff --git a/go.mod b/go.mod index d0ea5ad..34951f0 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,8 @@ go 1.21 require ( github.com/gotracker/goaudiofile v1.0.16 - github.com/gotracker/gomixing v1.3.1 github.com/gotracker/opl2 v1.0.1 github.com/heucuva/comparison v1.0.0 github.com/heucuva/optional v0.0.1 - golang.org/x/exp v0.0.0-20240119083558-1b970713d09a + golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f ) diff --git a/go.sum b/go.sum index ed098c8..a8aafb6 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/gotracker/goaudiofile v1.0.16 h1:+QlrDbZluWs01NZdg3JOuM+Zm98o1NNFVbtts2Fkw2M= github.com/gotracker/goaudiofile v1.0.16/go.mod h1:mX/CjpkoClUFrGQ8MU6x2hm4ma/ClQTh83wwHhLC7RY= -github.com/gotracker/gomixing v1.3.1 h1:u2AbhZoFtqXRgY0wxQXWDWbnZpIG+rLfSEd91YKIqnI= -github.com/gotracker/gomixing v1.3.1/go.mod h1:y0lfvWy49qzwfQiATw+kBjAlNTK75Ix9NpxPh+8QgPs= github.com/gotracker/opl2 v1.0.1 h1:1PVNs0dXqEAQxdws7fz2WEE3nSKkMb1osTTT7KgEi5g= github.com/gotracker/opl2 v1.0.1/go.mod h1:lW1WbZlh7svEMpurp9LLYWSyf1WPAb750cQ7xGIhCnY= github.com/heucuva/comparison v1.0.0 h1:xxXNKS9GKHetQavOz35FitlAXWvmvM3U6M5IRIw7kN8= @@ -10,7 +8,7 @@ github.com/heucuva/optional v0.0.1 h1:tLbVBMQBKzQVfe43bHQFSxjhTzYcRK8frnTBG6FLks github.com/heucuva/optional v0.0.1/go.mod h1:2AtE/X9279wzrHLkCNvKl0xP7AiEIj3RijGKwbO8R3M= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw= +golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/instrument/instrument.go b/instrument/instrument.go index 0e2775f..43f6b2f 100644 --- a/instrument/instrument.go +++ b/instrument/instrument.go @@ -1,8 +1,8 @@ package instrument import ( - "github.com/gotracker/gomixing/sampling" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" "github.com/heucuva/optional" "github.com/gotracker/playback/filter" diff --git a/instrument/opl2.go b/instrument/opl2.go index 4441a00..7ac6fa6 100644 --- a/instrument/opl2.go +++ b/instrument/opl2.go @@ -3,7 +3,7 @@ package instrument import ( "math" - "github.com/gotracker/gomixing/sampling" + "github.com/gotracker/playback/mixing/sampling" ) // OPL2OperatorData is the operator data for an OPL2/Adlib instrument diff --git a/instrument/pcm.go b/instrument/pcm.go index 49bcf1f..597e508 100644 --- a/instrument/pcm.go +++ b/instrument/pcm.go @@ -1,7 +1,7 @@ package instrument import ( - "github.com/gotracker/gomixing/sampling" + "github.com/gotracker/playback/mixing/sampling" "github.com/gotracker/playback/voice/envelope" "github.com/gotracker/playback/voice/fadeout" "github.com/gotracker/playback/voice/loop" diff --git a/instrument/sample.go b/instrument/sample.go index cb4f65e..95b072c 100644 --- a/instrument/sample.go +++ b/instrument/sample.go @@ -1,7 +1,7 @@ package instrument import ( - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/player/feature" "github.com/gotracker/playback/voice/pcm" ) diff --git a/instrument/util.go b/instrument/util.go index 15baab9..a636e9d 100644 --- a/instrument/util.go +++ b/instrument/util.go @@ -3,7 +3,7 @@ package instrument import ( "fmt" - "github.com/gotracker/gomixing/sampling" + "github.com/gotracker/playback/mixing/sampling" ) // ID is an identifier for an instrument/sample that means something to the format diff --git a/internal/examples/bufferload/example.go b/internal/examples/bufferload/example.go index 9673a2e..d5d6875 100644 --- a/internal/examples/bufferload/example.go +++ b/internal/examples/bufferload/example.go @@ -5,9 +5,9 @@ import ( "errors" "os" - "github.com/gotracker/gomixing/mixing" - "github.com/gotracker/gomixing/sampling" "github.com/gotracker/playback/format" + "github.com/gotracker/playback/mixing" + "github.com/gotracker/playback/mixing/sampling" "github.com/gotracker/playback/output" "github.com/gotracker/playback/player/feature" "github.com/gotracker/playback/player/machine" @@ -112,16 +112,12 @@ func ExamplePlayBufferToStdout() { Channels: channels, } - // A panning mixer knows how to coordinate panning values into a pre-final (penultimate?) mixing - // matrix that can be collapsed into the final form, ready for the sample type conversion. - panMixer := mixing.GetPanMixer(channels) - go func() { // Wait for a pre-mix data blob to show up for premix := range premixDataChannel { // Flatten the data into the final format - this is a very complex process that this one // helper function miraculously does for us, placing into a very handy slice of bytes. - data := m.Flatten(panMixer, premix.SamplesLen, premix.Data, premix.MixerVolume, sampleFormat) + data := m.Flatten(premix.SamplesLen, premix.Data, premix.MixerVolume, sampleFormat) // write it out! If we run into an error, then ignore it for now. This is where a context // with a cancellation would be a good solution to properly coordinate the player update diff --git a/internal/examples/fileload/example.go b/internal/examples/fileload/example.go index 6cfb539..fe3e90c 100644 --- a/internal/examples/fileload/example.go +++ b/internal/examples/fileload/example.go @@ -4,9 +4,9 @@ import ( "errors" "os" - "github.com/gotracker/gomixing/mixing" - "github.com/gotracker/gomixing/sampling" "github.com/gotracker/playback/format" + "github.com/gotracker/playback/mixing" + "github.com/gotracker/playback/mixing/sampling" "github.com/gotracker/playback/output" "github.com/gotracker/playback/player/feature" "github.com/gotracker/playback/player/machine" @@ -111,16 +111,12 @@ func ExamplePlayFileToStdout() { Channels: channels, } - // A panning mixer knows how to coordinate panning values into a pre-final (penultimate?) mixing - // matrix that can be collapsed into the final form, ready for the sample type conversion. - panMixer := mixing.GetPanMixer(channels) - go func() { // Wait for a pre-mix data blob to show up for premix := range premixDataChannel { // Flatten the data into the final format - this is a very complex process that this one // helper function miraculously does for us, placing into a very handy slice of bytes. - data := m.Flatten(panMixer, premix.SamplesLen, premix.Data, premix.MixerVolume, sampleFormat) + data := m.Flatten(premix.SamplesLen, premix.Data, premix.MixerVolume, sampleFormat) // write it out! If we run into an error, then ignore it for now. This is where a context // with a cancellation would be a good solution to properly coordinate the player update diff --git a/mixing/channeldata.go b/mixing/channeldata.go new file mode 100644 index 0000000..f5cb312 --- /dev/null +++ b/mixing/channeldata.go @@ -0,0 +1,19 @@ +package mixing + +import ( + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/volume" +) + +// Data is a single buffer of data at a specific panning position +type Data struct { + Data MixBuffer + PanMatrix panning.PanMixer + Volume volume.Volume + Pos int + SamplesLen int + Flush func() +} + +// ChannelData is a single channel's data +type ChannelData []Data diff --git a/mixing/mixbuffer.go b/mixing/mixbuffer.go new file mode 100644 index 0000000..725fad9 --- /dev/null +++ b/mixing/mixbuffer.go @@ -0,0 +1,117 @@ +package mixing + +import ( + "bytes" + "time" + + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" +) + +// SampleMixIn is the parameters for mixing in a sample into a MixBuffer +type SampleMixIn struct { + Sample sampling.Sampler + StaticVol volume.Volume + PanMatrix panning.PanMixer + MixPos int + MixLen int +} + +// MixBuffer is a buffer of premixed volume data intended to +// be eventually sent out to the sound output device after +// conversion to the output format +type MixBuffer []volume.Matrix + +// C returns a channel and a function that flushes any outstanding mix-ins and closes the channel +func (m *MixBuffer) C() (chan<- SampleMixIn, func()) { + ch := make(chan SampleMixIn, 32) + go func() { + for d := range ch { + m.MixInSample(d) + } + }() + return ch, func() { + for len(ch) != 0 { + time.Sleep(1 * time.Millisecond) + } + close(ch) + } +} + +// MixInSample mixes in a single sample entry into the mix buffer +func (m *MixBuffer) MixInSample(d SampleMixIn) { + pos := d.MixPos + for i := 0; i < d.MixLen; i++ { + dry := d.Sample.GetSample() + samp := dry.Apply(d.StaticVol) + mixed := d.PanMatrix.ApplyToMatrix(samp) + (*m)[pos].Accumulate(mixed) + pos++ + d.Sample.Advance() + } +} + +// Add will mix in another MixBuffer's data +func (m *MixBuffer) Add(pos int, rhs MixBuffer, volMtx volume.Matrix) { + maxLen := len(rhs) + for i := 0; i < maxLen; i++ { + out := volMtx.ApplyToMatrix(rhs[i]) + (*m)[pos+i].Accumulate(out) + } +} + +// ToRenderData converts a mixbuffer into a byte stream intended to be +// output to the output sound device +func (m *MixBuffer) ToRenderData(samples int, channels int, mixerVolume volume.Volume, formatter sampling.Formatter) []byte { + writer := &bytes.Buffer{} + writer.Grow(samples * ((formatter.Size() + 7) / 8) * channels) + for _, samp := range *m { + buf := samp.Apply(mixerVolume) + d := buf.ToChannels(channels) + for i := 0; i < channels; i++ { + _ = formatter.Write(writer, d.StaticMatrix[i]) // lint + } + } + return writer.Bytes() +} + +// ToIntStream converts a mixbuffer into an int stream intended to be +// output to the output sound device +func (m *MixBuffer) ToIntStream(outputChannels int, samples int, bitsPerSample int, mixerVolume volume.Volume) [][]int32 { + data := make([][]int32, outputChannels) + for c := range data { + data[c] = make([]int32, samples) + } + for i, samp := range *m { + buf := samp.Apply(mixerVolume) + d := buf.ToChannels(outputChannels) + for c := 0; c < outputChannels; c++ { + data[c][i] = d.StaticMatrix[c].ToIntSample(bitsPerSample) + } + } + return data +} + +// ToRenderDataWithBufs converts a mixbuffer into a byte stream intended to be +// output to the output sound device +func (m *MixBuffer) ToRenderDataWithBufs(outBuffers [][]byte, samples int, mixerVolume volume.Volume, formatter sampling.Formatter) { + pos := 0 + onum := 0 + out := outBuffers[onum] + for _, samp := range *m { + buf := samp.Apply(mixerVolume) + for c := 0; c < buf.Channels; c++ { + for pos >= len(out) { + onum++ + if onum > len(outBuffers) { + return + } + out = outBuffers[onum] + pos = 0 + } + _ = formatter.WriteAt(out, int64(pos), buf.StaticMatrix[c]) // lint + pos += formatter.Size() + } + } +} diff --git a/mixing/mixer.go b/mixing/mixer.go new file mode 100644 index 0000000..6dec1df --- /dev/null +++ b/mixing/mixer.go @@ -0,0 +1,77 @@ +package mixing + +import ( + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" +) + +// Mixer is a manager for mixing multiple single- and multi-channel samples into a single multi-channel output stream +type Mixer struct { + Channels int +} + +// NewMixBuffer returns a mixer buffer with a number of channels +// of preallocated sample data +func (m Mixer) NewMixBuffer(samples int) MixBuffer { + return make(MixBuffer, samples) +} + +// GetDefaultMixerVolume returns the default mixer volume value based on the number of mixed channels +// not to be confused with the number of output channels +func GetDefaultMixerVolume(numMixedChannels int) volume.Volume { + return 1.0 / volume.Volume(numMixedChannels) +} + +// Flatten will to a final saturation mix of all the row's channel data into a single output buffer +func (m Mixer) Flatten(samplesLen int, row []ChannelData, mixerVolume volume.Volume, sampleFormat sampling.Format) []byte { + data := m.NewMixBuffer(samplesLen) + formatter := sampling.GetFormatter(sampleFormat) + for _, rdata := range row { + for _, cdata := range rdata { + if cdata.Flush != nil { + cdata.Flush() + } + if len(cdata.Data) > 0 { + volMtx := cdata.PanMatrix.Apply(cdata.Volume) + data.Add(cdata.Pos, cdata.Data, volMtx) + } + } + } + return data.ToRenderData(samplesLen, m.Channels, mixerVolume, formatter) +} + +// FlattenToInts runs a flatten on the channel data into separate channel data of int32 variety +// these int32s still respect the bitsPerSample size +func (m Mixer) FlattenToInts(channels, samplesLen, bitsPerSample int, row []ChannelData, mixerVolume volume.Volume) [][]int32 { + data := m.NewMixBuffer(samplesLen) + for _, rdata := range row { + for _, cdata := range rdata { + if cdata.Flush != nil { + cdata.Flush() + } + if len(cdata.Data) > 0 { + volMtx := cdata.PanMatrix.Apply(cdata.Volume) + data.Add(cdata.Pos, cdata.Data, volMtx) + } + } + } + return data.ToIntStream(channels, samplesLen, bitsPerSample, mixerVolume) +} + +// FlattenTo will to a final saturation mix of all the row's channel data into a single output buffer +func (m Mixer) FlattenTo(resultBuffers [][]byte, channels, samplesLen int, row []ChannelData, mixerVolume volume.Volume, sampleFormat sampling.Format) { + data := m.NewMixBuffer(samplesLen) + formatter := sampling.GetFormatter(sampleFormat) + for _, rdata := range row { + for _, cdata := range rdata { + if cdata.Flush != nil { + cdata.Flush() + } + if len(cdata.Data) > 0 { + volMtx := cdata.PanMatrix.Apply(cdata.Volume) + data.Add(cdata.Pos, cdata.Data, volMtx) + } + } + } + data.ToRenderDataWithBufs(resultBuffers, samplesLen, mixerVolume, formatter) +} diff --git a/mixing/panmixer.go b/mixing/panmixer.go new file mode 100644 index 0000000..0898dcb --- /dev/null +++ b/mixing/panmixer.go @@ -0,0 +1,26 @@ +package mixing + +import ( + "github.com/gotracker/playback/mixing/panning" +) + +// PanMixer is a mixer that's specialized for mixing multichannel audio content +type PanMixer interface { + GetMixingMatrix(pan panning.Position, stereoSeparation float32) panning.PanMixer + NumChannels() int +} + +// GetPanMixer returns the panning mixer that can generate a matrix +// based on input pan value +func GetPanMixer(channels int) PanMixer { + switch channels { + case 1: + return PanMixerMono + case 2: + return PanMixerStereo + case 4: + return PanMixerQuad + } + + return nil +} diff --git a/mixing/panmixer_mono.go b/mixing/panmixer_mono.go new file mode 100644 index 0000000..1baa310 --- /dev/null +++ b/mixing/panmixer_mono.go @@ -0,0 +1,23 @@ +package mixing + +import ( + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/volume" +) + +// PanMixerMono is a mixer that's specialized for mixing monaural audio content +var PanMixerMono PanMixer = &panMixerMono{} + +type panMixerMono struct{} + +func (p panMixerMono) GetMixingMatrix(pan panning.Position, stereoSeparation float32) panning.PanMixer { + // distance and angle are ignored on mono + return volume.Matrix{ + StaticMatrix: volume.StaticMatrix{1.0}, + Channels: 1, + } +} + +func (p panMixerMono) NumChannels() int { + return 1 +} diff --git a/mixing/panmixer_quad.go b/mixing/panmixer_quad.go new file mode 100644 index 0000000..d0643f3 --- /dev/null +++ b/mixing/panmixer_quad.go @@ -0,0 +1,35 @@ +package mixing + +import ( + "math" + + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/volume" +) + +// PanMixerQuad is a mixer that's specialized for mixing quadraphonic audio content +var PanMixerQuad PanMixer = &panMixerQuad{} + +type panMixerQuad struct{} + +func (p panMixerQuad) GetMixingMatrix(pan panning.Position, stereoSeparation float32) panning.PanMixer { + pangle := float64(pan.Angle) + sf, cf := math.Sincos(pangle) + sr, cr := math.Sin(pangle+math.Pi/2.0), math.Cos(pangle-math.Pi/2.0) + var d volume.Volume + if pan.Distance > 0 { + d = 1 / volume.Volume(pan.Distance*pan.Distance) + } + lf := d * volume.Volume(sf) + rf := d * volume.Volume(cf) + lr := d * volume.Volume(cr) + rr := d * volume.Volume(sr) + return volume.Matrix{ + StaticMatrix: volume.StaticMatrix{lf, rf, lr, rr}, + Channels: 4, + } +} + +func (p panMixerQuad) NumChannels() int { + return 4 +} diff --git a/mixing/panmixer_stereo.go b/mixing/panmixer_stereo.go new file mode 100644 index 0000000..4dbe47f --- /dev/null +++ b/mixing/panmixer_stereo.go @@ -0,0 +1,57 @@ +package mixing + +import ( + "math" + + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/volume" +) + +// PanMixerStereo is a mixer that's specialized for mixing stereo audio content +var PanMixerStereo PanMixer = &panMixerStereo{} + +type panMixerStereo struct{} + +func (p panMixerStereo) GetMixingMatrix(pan panning.Position, stereoSeparation float32) panning.PanMixer { + s, c := math.Sincos(float64(pan.Angle) * 2.0) + + var d volume.Volume + if pan.Distance > 0 { + d = 1 / volume.Volume(pan.Distance*pan.Distance) + } + l := d * volume.StereoCoeff * volume.Volume(c-s) + r := d * volume.StereoCoeff * volume.Volume(c+s) + + midCoeff := volume.Volume(1.0 / (1.0 + stereoSeparation)) + sideCoeff := volume.Volume(stereoSeparation * 0.5) + + return panning.MixerStereo{ + Matrix: volume.Matrix{ + StaticMatrix: volume.StaticMatrix{l, r}, + Channels: 2, + }, + StereoSeparationFunc: func(in volume.Matrix) volume.Matrix { + if stereoSeparation >= 1 { + return in.AsStereo() + } + + if stereoSeparation <= 0 { + return in.AsMono().AsStereo() + } + + dry := in.AsStereo() + + mid := (dry.StaticMatrix[0] + dry.StaticMatrix[1]) * midCoeff + side := (dry.StaticMatrix[1] - dry.StaticMatrix[0]) * sideCoeff + + wet := dry + wet.StaticMatrix[0] = mid - side + wet.StaticMatrix[1] = mid + side + return wet + }, + } +} + +func (p panMixerStereo) NumChannels() int { + return 2 +} diff --git a/mixing/panning/mixer.go b/mixing/panning/mixer.go new file mode 100644 index 0000000..a24b574 --- /dev/null +++ b/mixing/panning/mixer.go @@ -0,0 +1,8 @@ +package panning + +import "github.com/gotracker/playback/mixing/volume" + +type PanMixer interface { + ApplyToMatrix(mtx volume.Matrix) volume.Matrix + Apply(vol volume.Volume) volume.Matrix +} diff --git a/mixing/panning/mixer_stereo.go b/mixing/panning/mixer_stereo.go new file mode 100644 index 0000000..f844e85 --- /dev/null +++ b/mixing/panning/mixer_stereo.go @@ -0,0 +1,17 @@ +package panning + +import "github.com/gotracker/playback/mixing/volume" + +type MixerStereo struct { + volume.Matrix + StereoSeparationFunc func(volume.Matrix) volume.Matrix +} + +func (m MixerStereo) ApplyToMatrix(mtx volume.Matrix) volume.Matrix { + dry := m.Matrix.ApplyToMatrix(mtx) + return m.StereoSeparationFunc(dry) +} + +func (m MixerStereo) Apply(vol volume.Volume) volume.Matrix { + return m.Matrix.Apply(vol) +} diff --git a/mixing/panning/position.go b/mixing/panning/position.go new file mode 100644 index 0000000..fda9fe1 --- /dev/null +++ b/mixing/panning/position.go @@ -0,0 +1,42 @@ +package panning + +import "math" + +// Position is stored as polar coordinates +// with Angle of 0 radians being calculated from right +// and >0 rotating counter-clockwise from that point +type Position struct { + Angle float32 + Distance float32 +} + +var ( + // CenterAhead is the position directly ahead of the listener + CenterAhead = MakeStereoPosition(0.5, 0, 1) +) + +// MakeStereoPosition creates a stereo panning position based on a linear interpolation between `leftValue` and `RightValue` +func MakeStereoPosition(value float32, leftValue float32, rightValue float32) Position { + if leftValue == rightValue { + panic("leftValue and rightValue should be distinct") + } + d := float64(rightValue - leftValue) + t := float64(value-leftValue) / d + // we're using a 2d rotation matrix to calcuate the left and right channels, so we really want the half angle + prad := (1 - t) * math.Pi / 2.0 + + return Position{ + Angle: float32(prad), + Distance: 1.0, + } +} + +// FromStereoPosition inverts a stereo panning position into a linear interpolation value between `leftValue` and `RightValue` +func FromStereoPosition(pos Position, leftValue float32, rightValue float32) float32 { + if leftValue == rightValue { + panic("leftValue and rightValue should be distinct") + } + prad := pos.Angle + t := 1 - (float64(prad*2.0) / math.Pi) + return leftValue + float32(t)*(rightValue-leftValue) +} diff --git a/mixing/sampling/format.go b/mixing/sampling/format.go new file mode 100644 index 0000000..72b9902 --- /dev/null +++ b/mixing/sampling/format.go @@ -0,0 +1,27 @@ +package sampling + +// Format is the format of the sample data +type Format uint8 + +const ( + // Format8BitUnsigned is for unsigned 8-bit data + Format8BitUnsigned = Format(iota) + // Format8BitSigned is for signed 8-bit data + Format8BitSigned + // Format16BitLEUnsigned is for unsigned, little-endian, 16-bit data + Format16BitLEUnsigned + // Format16BitLESigned is for signed, little-endian, 16-bit data + Format16BitLESigned + // Format16BitBEUnsigned is for unsigned, big-endian, 16-bit data + Format16BitBEUnsigned + // Format16BitBESigned is for signed, big-endian, 16-bit data + Format16BitBESigned + // Format32BitLEFloat is for little-endian, 32-bit floating-point data + Format32BitLEFloat + // Format32BitBEFloat is for big-endian, 32-bit floating-point data + Format32BitBEFloat + // Format64BitLEFloat is for little-endian, 64-bit floating-point data + Format64BitLEFloat + // Format64BitBEFloat is for big-endian, 64-bit floating-point data + Format64BitBEFloat +) diff --git a/mixing/sampling/format_bit16.go b/mixing/sampling/format_bit16.go new file mode 100644 index 0000000..c6a0e13 --- /dev/null +++ b/mixing/sampling/format_bit16.go @@ -0,0 +1,116 @@ +package sampling + +import ( + "encoding/binary" + "io" + + "github.com/gotracker/playback/mixing/volume" +) + +const ( + cSample16BitDataCoeff = 0x8000 + cSample16BitVolumeCoeff = volume.Volume(1) / cSample16BitDataCoeff + cSample16BitBytes = 2 +) + +// Sample16BitSigned is a signed 16-bit sample +type Sample16BitSigned struct { + byteOrder binary.ByteOrder +} + +// Volume returns the volume value for the sample +func (Sample16BitSigned) volume(v int16) volume.Volume { + return volume.Volume(v) * cSample16BitVolumeCoeff +} + +// fromVolume returns the volume value for the sample +func (Sample16BitSigned) fromVolume(v volume.Volume) int16 { + return int16(v.ToIntSample(16)) +} + +// Size returns the size of the sample in bytes +func (Sample16BitSigned) Size() int { + return cSample16BitBytes +} + +// ReadAt reads a value from the reader provided in the byte order provided +func (s Sample16BitSigned) ReadAt(data []byte, ofs int64) (volume.Volume, error) { + if len(data) <= int(ofs)+(cSample16BitBytes-1) { + return 0, io.EOF + } + if ofs < 0 { + ofs = 0 + } + + v := int16(s.byteOrder.Uint16(data[ofs:])) + return s.volume(v), nil +} + +// WriteAt writes a value to the slice provided in the byte order provided +func (s Sample16BitSigned) WriteAt(data []byte, ofs int64, v volume.Volume) error { + if len(data) <= int(ofs) { + return io.EOF + } + if ofs < 0 { + ofs = 0 + } + + s.byteOrder.PutUint16(data[ofs:], uint16(s.fromVolume(v))) + return nil +} + +// Write writes a value to the Writer provided in the byte order provided +func (s Sample16BitSigned) Write(out io.Writer, v volume.Volume) error { + return binary.Write(out, s.byteOrder, s.fromVolume(v)) +} + +// Sample16BitUnsigned is an unsigned 16-bit sample +type Sample16BitUnsigned struct { + byteOrder binary.ByteOrder +} + +// Volume returns the volume value for the sample +func (Sample16BitUnsigned) volume(v uint16) volume.Volume { + return volume.Volume(int16(v-cSample16BitDataCoeff)) * cSample16BitVolumeCoeff +} + +// fromVolume returns the volume value for the sample +func (Sample16BitUnsigned) fromVolume(v volume.Volume) uint16 { + return uint16(v.ToUintSample(16)) +} + +// Size returns the size of the sample in bytes +func (Sample16BitUnsigned) Size() int { + return cSample16BitBytes +} + +// ReadAt reads a value from the reader provided in the byte order provided +func (s Sample16BitUnsigned) ReadAt(data []byte, ofs int64) (volume.Volume, error) { + if len(data) <= int(ofs)+(cSample16BitBytes-1) { + return 0, io.EOF + } + if ofs < 0 { + ofs = 0 + } + + v := uint16(s.byteOrder.Uint16(data[ofs:])) + return s.volume(v), nil +} + +// WriteAt writes a value to the slice provided in the byte order provided +func (s Sample16BitUnsigned) WriteAt(data []byte, ofs int64, v volume.Volume) error { + if len(data) <= int(ofs) { + return io.EOF + } + if ofs < 0 { + ofs = 0 + } + + s.byteOrder.PutUint16(data[ofs:], s.fromVolume(v)) + return nil +} + +// Write writes a value to the Writer provided in the byte order provided +func (s Sample16BitUnsigned) Write(out io.Writer, v volume.Volume) error { + return binary.Write(out, s.byteOrder, s.fromVolume(v)) +} diff --git a/mixing/sampling/format_bit32float.go b/mixing/sampling/format_bit32float.go new file mode 100644 index 0000000..f8b911e --- /dev/null +++ b/mixing/sampling/format_bit32float.go @@ -0,0 +1,55 @@ +package sampling + +import ( + "encoding/binary" + "io" + "math" + + "github.com/gotracker/playback/mixing/volume" +) + +const ( + //cSample32BitFloatVolumeCoeff = volume.Volume(1) + cSample32BitFloatBytes = 4 +) + +// Sample32BitFloat is a 32-bit floating-point sample +type Sample32BitFloat struct { + byteOrder binary.ByteOrder +} + +// Size returns the size of the sample in bytes +func (Sample32BitFloat) Size() int { + return cSample32BitFloatBytes +} + +// ReadAt reads a value from the reader provided in the byte order provided +func (s Sample32BitFloat) ReadAt(data []byte, ofs int64) (volume.Volume, error) { + if len(data) <= int(ofs)+(cSample32BitFloatBytes-1) { + return 0, io.EOF + } + if ofs < 0 { + ofs = 0 + } + + v := math.Float32frombits(s.byteOrder.Uint32(data[ofs:])) + return volume.Volume(v), nil +} + +// WriteAt writes a value to the slice provided in the byte order provided +func (s Sample32BitFloat) WriteAt(data []byte, ofs int64, v volume.Volume) error { + if len(data) <= int(ofs) { + return io.EOF + } + if ofs < 0 { + ofs = 0 + } + + s.byteOrder.PutUint32(data[ofs:], math.Float32bits(float32(v.WithOverflowProtection()))) + return nil +} + +// Write writes a value to the Writer provided in the byte order provided +func (s Sample32BitFloat) Write(out io.Writer, v volume.Volume) error { + return binary.Write(out, s.byteOrder, math.Float32bits(float32(v.WithOverflowProtection()))) +} diff --git a/mixing/sampling/format_bit64float.go b/mixing/sampling/format_bit64float.go new file mode 100644 index 0000000..1c0e5bb --- /dev/null +++ b/mixing/sampling/format_bit64float.go @@ -0,0 +1,55 @@ +package sampling + +import ( + "encoding/binary" + "io" + "math" + + "github.com/gotracker/playback/mixing/volume" +) + +const ( + //cSample64BitFloatVolumeCoeff = volume.Volume(1) + cSample64BitFloatBytes = 8 +) + +// Sample64BitFloat is a 64-bit floating-point sample +type Sample64BitFloat struct { + byteOrder binary.ByteOrder +} + +// Size returns the size of the sample in bytes +func (Sample64BitFloat) Size() int { + return cSample64BitFloatBytes +} + +// ReadAt reads a value from the reader provided in the byte order provided +func (s Sample64BitFloat) ReadAt(data []byte, ofs int64) (volume.Volume, error) { + if len(data) <= int(ofs)+(cSample64BitFloatBytes-1) { + return 0, io.EOF + } + if ofs < 0 { + ofs = 0 + } + + f := math.Float64frombits(s.byteOrder.Uint64(data[ofs:])) + return volume.Volume(f), nil +} + +// WriteAt writes a value to the slice provided in the byte order provided +func (s Sample64BitFloat) WriteAt(data []byte, ofs int64, v volume.Volume) error { + if len(data) <= int(ofs) { + return io.EOF + } + if ofs < 0 { + ofs = 0 + } + + s.byteOrder.PutUint64(data[ofs:], math.Float64bits(v.WithOverflowProtection())) + return nil +} + +// Write writes a value to the Writer provided in the byte order provided +func (s Sample64BitFloat) Write(out io.Writer, v volume.Volume) error { + return binary.Write(out, s.byteOrder, math.Float64bits(v.WithOverflowProtection())) +} diff --git a/mixing/sampling/format_bit8.go b/mixing/sampling/format_bit8.go new file mode 100644 index 0000000..1e9c32e --- /dev/null +++ b/mixing/sampling/format_bit8.go @@ -0,0 +1,112 @@ +package sampling + +import ( + "encoding/binary" + "io" + + "github.com/gotracker/playback/mixing/volume" +) + +const ( + cSample8BitDataCoeff = 0x80 + cSample8BitVolumeCoeff = volume.Volume(1) / cSample8BitDataCoeff + cSample8BitBytes = 1 +) + +// Sample8BitSigned is a signed 8-bit sample +type Sample8BitSigned struct{} + +// toVolume returns the volume value for the sample +func (Sample8BitSigned) toVolume(v int8) volume.Volume { + return volume.Volume(v) * cSample8BitVolumeCoeff +} + +// fromVolume returns the volume value for the sample +func (Sample8BitSigned) fromVolume(v volume.Volume) int8 { + return int8(v.ToIntSample(8)) +} + +// Size returns the size of the sample in bytes +func (Sample8BitSigned) Size() int { + return cSample8BitBytes +} + +// ReadAt reads a value from the reader provided in the byte order provided +func (s Sample8BitSigned) ReadAt(data []byte, ofs int64) (volume.Volume, error) { + if len(data) <= int(ofs) { + return 0, io.EOF + } + if ofs < 0 { + ofs = 0 + } + + v := int8(data[ofs]) + return s.toVolume(v), nil +} + +// WriteAt writes a value to the slice provided in the byte order provided +func (s Sample8BitSigned) WriteAt(data []byte, ofs int64, v volume.Volume) error { + if len(data) <= int(ofs) { + return io.EOF + } + if ofs < 0 { + ofs = 0 + } + + data[ofs] = uint8(s.fromVolume(v)) + return nil +} + +// Write writes a value to the Writer provided in the byte order provided +func (s Sample8BitSigned) Write(out io.Writer, v volume.Volume) error { + return binary.Write(out, binary.LittleEndian, s.fromVolume(v)) +} + +// Sample8BitUnsigned is an unsigned 8-bit sample +type Sample8BitUnsigned struct{} + +// toVolume returns the volume value for the sample +func (Sample8BitUnsigned) toVolume(v uint8) volume.Volume { + return volume.Volume(int8(v-uint8(cSample8BitDataCoeff))) * cSample8BitVolumeCoeff +} + +// fromVolume returns the volume value for the sample +func (Sample8BitUnsigned) fromVolume(v volume.Volume) uint8 { + return uint8(v.ToUintSample(8)) +} + +// Size returns the size of the sample in bytes +func (Sample8BitUnsigned) Size() int { + return cSample8BitBytes +} + +// ReadAt reads a value from the slice provided in the byte order provided +func (s Sample8BitUnsigned) ReadAt(data []byte, ofs int64) (volume.Volume, error) { + if len(data) <= int(ofs) { + return 0, io.EOF + } + if ofs < 0 { + ofs = 0 + } + + v := uint8(data[ofs]) + return s.toVolume(v), nil +} + +// WriteAt writes a value to the slice provided in the byte order provided +func (s Sample8BitUnsigned) WriteAt(data []byte, ofs int64, v volume.Volume) error { + if len(data) <= int(ofs) { + return io.EOF + } + if ofs < 0 { + ofs = 0 + } + + data[ofs] = s.fromVolume(v) + return nil +} + +// Write writes a value to the Writer provided in the byte order provided +func (s Sample8BitUnsigned) Write(out io.Writer, v volume.Volume) error { + return binary.Write(out, binary.LittleEndian, s.fromVolume(v)) +} diff --git a/mixing/sampling/formatter.go b/mixing/sampling/formatter.go new file mode 100644 index 0000000..2124d5a --- /dev/null +++ b/mixing/sampling/formatter.go @@ -0,0 +1,52 @@ +package sampling + +import ( + "encoding/binary" + "io" + + "github.com/gotracker/playback/mixing/volume" +) + +type Formatter interface { + Size() int + ReadAt(data []byte, ofs int64) (volume.Volume, error) + WriteAt(data []byte, ofs int64, v volume.Volume) error + Write(out io.Writer, v volume.Volume) error +} + +func GetFormatter(format Format) Formatter { + switch format { + default: + return nil + case Format8BitUnsigned: + // Format8BitUnsigned is for unsigned 8-bit data + return Sample8BitUnsigned{} + case Format8BitSigned: + // Format8BitSigned is for signed 8-bit data + return Sample8BitSigned{} + case Format16BitLEUnsigned: + // Format16BitLEUnsigned is for unsigned, little-endian, 16-bit data + return Sample16BitUnsigned{byteOrder: binary.LittleEndian} + case Format16BitLESigned: + // Format16BitLESigned is for signed, little-endian, 16-bit data + return Sample16BitSigned{byteOrder: binary.LittleEndian} + case Format16BitBEUnsigned: + // Format16BitBEUnsigned is for unsigned, big-endian, 16-bit data + return Sample16BitUnsigned{byteOrder: binary.BigEndian} + case Format16BitBESigned: + // Format16BitBESigned is for signed, big-endian, 16-bit data + return Sample16BitSigned{byteOrder: binary.BigEndian} + case Format32BitLEFloat: + // Format32BitLEFloat is for little-endian, 32-bit floating-point data + return Sample32BitFloat{byteOrder: binary.LittleEndian} + case Format32BitBEFloat: + // Format32BitBEFloat is for big-endian, 32-bit floating-point data + return Sample32BitFloat{byteOrder: binary.BigEndian} + case Format64BitLEFloat: + // Format64BitLEFloat is for little-endian, 64-bit floating-point data + return Sample64BitFloat{byteOrder: binary.LittleEndian} + case Format64BitBEFloat: + // Format64BitBEFloat is for big-endian, 64-bit floating-point data + return Sample64BitFloat{byteOrder: binary.BigEndian} + } +} diff --git a/mixing/sampling/position.go b/mixing/sampling/position.go new file mode 100644 index 0000000..5386a62 --- /dev/null +++ b/mixing/sampling/position.go @@ -0,0 +1,15 @@ +package sampling + +// Pos stores the integer and fractional portions of a position within a sample +type Pos struct { + Pos int + Frac float32 +} + +// Add increments the internal position values by the specified amount +func (p *Pos) Add(amt float32) { + f := p.Frac + amt + i := int(f) + p.Frac = f - float32(i) + p.Pos += i +} diff --git a/mixing/sampling/sampler.go b/mixing/sampling/sampler.go new file mode 100644 index 0000000..cfd4074 --- /dev/null +++ b/mixing/sampling/sampler.go @@ -0,0 +1,25 @@ +package sampling + +import "github.com/gotracker/playback/mixing/volume" + +// Sampler is an interface to the sampling system +type Sampler interface { + GetPosition() Pos + Advance() + GetSample() volume.Matrix +} + +// SampleStream is an interface to a sample stream (aka: an instrument) +type SampleStream interface { + GetSample(Pos) volume.Matrix +} + +// NewSampler creates a basic sampler that implements the Sampler interface +func NewSampler(ss SampleStream, pos Pos, period float32) Sampler { + s := sampler{ + ss: ss, + pos: pos, + period: period, + } + return &s +} diff --git a/mixing/sampling/samplerimpl.go b/mixing/sampling/samplerimpl.go new file mode 100644 index 0000000..599e509 --- /dev/null +++ b/mixing/sampling/samplerimpl.go @@ -0,0 +1,24 @@ +package sampling + +import "github.com/gotracker/playback/mixing/volume" + +type sampler struct { + ss SampleStream + pos Pos + period float32 +} + +func (s *sampler) GetPosition() Pos { + return s.pos +} + +func (s *sampler) Advance() { + s.pos.Add(s.period) +} + +func (s *sampler) GetSample() volume.Matrix { + if s.ss == nil { + return volume.Matrix{} + } + return s.ss.GetSample(s.pos) +} diff --git a/mixing/volume/matrix.go b/mixing/volume/matrix.go new file mode 100644 index 0000000..c363b34 --- /dev/null +++ b/mixing/volume/matrix.go @@ -0,0 +1,183 @@ +package volume + +import "math" + +// Matrix is an array of Volumes +type Matrix struct { + StaticMatrix + Channels int +} + +const ( + StereoCoeff = math.Sqrt2 / 2.0 +) + +// Apply takes a volume matrix and multiplies it by incoming volumes +func (m Matrix) ApplyToMatrix(mtx Matrix) Matrix { + if mtx.Channels == 0 { + return m + } + + if m.Channels == mtx.Channels { + // simple straight-through + for i := 0; i < m.Channels; i++ { + m.StaticMatrix[i] = mtx.StaticMatrix[i].ApplySingle(m.StaticMatrix[i]) + } + return m + } + + // more complex applications follow... + + if mtx.Channels == 1 { + // right (mtx) is mono, so just do direct mono application + return m.Apply(mtx.StaticMatrix[0]) + } + + // NOTE: recursive + return m.ApplyToMatrix(mtx.ToChannels(m.Channels)) +} + +func (m Matrix) Apply(vol Volume) Matrix { + for i := 0; i < m.Channels; i++ { + m.StaticMatrix[i] = vol.ApplySingle(m.StaticMatrix[i]) + } + return m +} + +func (m *Matrix) Accumulate(in Matrix) { + if m.Channels == 0 { + *m = in + return + } + + dry := in.ToChannels(m.Channels) + for i := 0; i < m.Channels; i++ { + m.StaticMatrix[i] += dry.StaticMatrix[i] + } +} + +func (m *Matrix) Assign(channels int, data []Volume) { + m.Channels = channels + for i := 0; i < channels; i++ { + m.StaticMatrix[i] = data[i] + } +} + +func (m Matrix) ToChannels(channels int) Matrix { + if m.Channels == channels { + return m + } + + switch channels { + case 1: + return m.AsMono() + case 2: + return m.AsStereo() + case 4: + return m.AsQuad() + default: + return Matrix{} + } +} + +// Sum sums all the elements of the Matrix and returns the resulting Volume +func (m Matrix) Sum() Volume { + switch m.Channels { + case 0: + return 0 + case 1: + return m.StaticMatrix[0] + case 2: + return (m.StaticMatrix[0] + m.StaticMatrix[1]) / StereoCoeff + case 4: + return (m.StaticMatrix[0] + m.StaticMatrix[1] + m.StaticMatrix[2] + m.StaticMatrix[3]) / StereoCoeff + default: + c := Volume(1 / float64(m.Channels)) + + var v Volume + for i := 0; i < m.Channels; i++ { + v += m.StaticMatrix[i] * c + } + return v + } +} + +func (m *Matrix) Set(ch int, vol Volume) { + m.StaticMatrix[ch] = vol +} + +func (m Matrix) Get(ch int) Volume { + return m.StaticMatrix[ch] +} + +func (m Matrix) AsMono() Matrix { + switch m.Channels { + case 0: + return Matrix{} + case 1: + return m + default: + return Matrix{ + StaticMatrix: StaticMatrix{m.Sum()}, + Channels: 1, + } + } +} + +func (m Matrix) AsStereo() Matrix { + switch m.Channels { + case 0: + return Matrix{} + case 1: + return Matrix{ + StaticMatrix: StaticMatrix{m.StaticMatrix[0] * StereoCoeff, m.StaticMatrix[0] * StereoCoeff}, + Channels: 2, + } + case 2: + return m + case 4: + return Matrix{ + StaticMatrix: StaticMatrix{(m.StaticMatrix[0] + m.StaticMatrix[2]) / 2.0, (m.StaticMatrix[1] + m.StaticMatrix[3]) / 2.0}, + Channels: 2, + } + default: + return Matrix{} + } +} + +func (m Matrix) AsQuad() Matrix { + switch m.Channels { + case 0: + return Matrix{} + case 1: + return Matrix{ + StaticMatrix: StaticMatrix{m.StaticMatrix[0] * StereoCoeff, m.StaticMatrix[0] * StereoCoeff, m.StaticMatrix[0] * StereoCoeff, m.StaticMatrix[0] * StereoCoeff}, + Channels: 4, + } + case 2: + return Matrix{ + StaticMatrix: StaticMatrix{m.StaticMatrix[0], m.StaticMatrix[1], m.StaticMatrix[0], m.StaticMatrix[1]}, + Channels: 4, + } + case 4: + return m + default: + return Matrix{} + } +} + +func (m Matrix) Lerp(other Matrix, t float32) Matrix { + if other.Channels == 0 || t <= 0 { + return m + } + + out := other.ToChannels(m.Channels) + + // lerp between m and v + for c := 0; c < m.Channels; c++ { + a := m.StaticMatrix[c] + b := out.StaticMatrix[c] + out.StaticMatrix[c] = a + Volume(t)*(b-a) + } + return out +} diff --git a/mixing/volume/staticmatrix.go b/mixing/volume/staticmatrix.go new file mode 100644 index 0000000..da704a5 --- /dev/null +++ b/mixing/volume/staticmatrix.go @@ -0,0 +1,4 @@ +package volume + +// StaticMatrix is an array of Volumes +type StaticMatrix [4]Volume diff --git a/mixing/volume/volume.go b/mixing/volume/volume.go new file mode 100644 index 0000000..bf099e9 --- /dev/null +++ b/mixing/volume/volume.go @@ -0,0 +1,107 @@ +package volume + +import ( + "fmt" + "math" +) + +// Volume is a mixable volume +type Volume float32 + +var ( + // VolumeUseInstVol tells the system to use the volume stored on the instrument + // This is useful for trackers and other musical applications + VolumeUseInstVol = Volume(math.Inf(-1)) +) + +// Int24 is an approximation of a 24-bit integer +type Int24 struct { + Hi int8 + Lo uint16 +} + +// ToSample returns a volume as a typed value supporting the bits per sample provided +func (v Volume) ToSample(bitsPerSample int) interface{} { + val := v.WithOverflowProtection() + switch bitsPerSample { + case 8: + return int8(val * 128.0) + case 16: + return int16(val * 32678.0) + case 24: + s := int32(val * 8388608.0) + return Int24{Hi: int8(s >> 16), Lo: uint16(s & 65535)} + case 32: + return int32(val * 2147483648.0) + } + return 0 +} + +// ToIntSample returns a volume as an int32 value ranged to the bits per sample provided +func (v Volume) ToIntSample(bitsPerSample int) int32 { + val := v.WithOverflowProtection() + switch bitsPerSample { + case 8: + return int32(val * 128.0) + case 16: + return int32(val * 32678.0) + case 24: + return int32(val * 8388608.0) + case 32: + return int32(val * 2147483648.0) + } + return 0 +} + +// ToUintSample returns a volume as a uint32 value ranged to the bits per sample provided +func (v Volume) ToUintSample(bitsPerSample int) uint32 { + val := v.WithOverflowProtection() + switch bitsPerSample { + case 8: + return uint32(val*float64(math.MaxInt8)) ^ 0x80 + case 16: + return uint32(val*float64(math.MaxInt16)) ^ 0x8000 + case 24: + return uint32(val*8388608.0) ^ 0x800000 + case 32: + return uint32(val*float64(math.MaxInt32)) ^ 0x80000000 + } + return 0 +} + +// Apply multiplies the volume to 1 sample, then returns the results +func (v Volume) ApplySingle(samp Volume) Volume { + return samp * v +} + +// Apply multiplies the volume to 1 sample, then returns the results +func (v Volume) ApplyMultiple(samp []Volume) []Volume { + vols := make([]Volume, len(samp)) + for i, s := range samp { + vols[i] = s.ApplySingle(v) + } + return vols +} + +func (v Volume) WithOverflowProtection() float64 { + val := float64(v) + if math.Abs(val) <= 1.0 { + // likely case + return val + } else if math.Signbit(val) { + // overflow, negative + return -1.0 + } + // overflow, positive + return 1.0 +} + +func (v Volume) String() string { + var db float64 + if v > 0 { + db = math.Log10(float64(v)) * 20.0 + } else { + db = math.Inf(-1) + } + return fmt.Sprintf("%0.2f (%0.2fdB)", v, db) +} diff --git a/output/premixdata.go b/output/premixdata.go index 15c7b9c..0f63590 100644 --- a/output/premixdata.go +++ b/output/premixdata.go @@ -1,8 +1,8 @@ package output import ( - "github.com/gotracker/gomixing/mixing" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing" + "github.com/gotracker/playback/mixing/volume" ) // PremixData is a structure containing the audio pre-mix data for a specific row or buffer diff --git a/player/machine/channel.go b/player/machine/channel.go index 87c098e..e2eed15 100644 --- a/player/machine/channel.go +++ b/player/machine/channel.go @@ -1,11 +1,11 @@ package machine import ( - "github.com/gotracker/gomixing/sampling" "github.com/gotracker/playback/filter" "github.com/gotracker/playback/index" "github.com/gotracker/playback/instrument" "github.com/gotracker/playback/memory" + "github.com/gotracker/playback/mixing/sampling" "github.com/gotracker/playback/note" "github.com/gotracker/playback/player/machine/instruction" "github.com/gotracker/playback/song" diff --git a/player/machine/channel_notedecode.go b/player/machine/channel_notedecode.go index 6b79060..4dbc340 100644 --- a/player/machine/channel_notedecode.go +++ b/player/machine/channel_notedecode.go @@ -1,8 +1,8 @@ package machine import ( - "github.com/gotracker/gomixing/sampling" "github.com/gotracker/playback/instrument" + "github.com/gotracker/playback/mixing/sampling" "github.com/gotracker/playback/note" "github.com/gotracker/playback/song" ) diff --git a/player/machine/channel_pastnote.go b/player/machine/channel_pastnote.go index 9caeb4b..28ae28f 100644 --- a/player/machine/channel_pastnote.go +++ b/player/machine/channel_pastnote.go @@ -4,8 +4,8 @@ import ( "slices" "sort" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/index" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/note" "github.com/gotracker/playback/voice" ) diff --git a/player/machine/machine.go b/player/machine/machine.go index 2094eea..138bb95 100644 --- a/player/machine/machine.go +++ b/player/machine/machine.go @@ -4,9 +4,9 @@ import ( "errors" "fmt" - "github.com/gotracker/gomixing/sampling" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/opl2" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/filter" "github.com/gotracker/playback/index" diff --git a/player/machine/machine_channel.go b/player/machine/machine_channel.go index 7c4fa94..d46b87f 100644 --- a/player/machine/machine_channel.go +++ b/player/machine/machine_channel.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/gotracker/gomixing/sampling" + "github.com/gotracker/playback/mixing/sampling" "github.com/gotracker/playback/filter" "github.com/gotracker/playback/index" diff --git a/player/machine/machine_factory.go b/player/machine/machine_factory.go index f71d6bb..6895374 100644 --- a/player/machine/machine_factory.go +++ b/player/machine/machine_factory.go @@ -4,8 +4,8 @@ import ( "fmt" "reflect" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/opl2" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/index" "github.com/gotracker/playback/note" diff --git a/player/machine/machine_globals.go b/player/machine/machine_globals.go index 33107d6..ebd9719 100644 --- a/player/machine/machine_globals.go +++ b/player/machine/machine_globals.go @@ -4,8 +4,8 @@ import ( "errors" "fmt" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/index" + "github.com/gotracker/playback/mixing/volume" ) type globals[TGlobalVolume Volume] struct { diff --git a/player/machine/machine_opl2.go b/player/machine/machine_opl2.go index de3e668..84de378 100644 --- a/player/machine/machine_opl2.go +++ b/player/machine/machine_opl2.go @@ -3,10 +3,10 @@ package machine import ( "errors" - "github.com/gotracker/gomixing/mixing" - "github.com/gotracker/gomixing/panning" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/opl2" + "github.com/gotracker/playback/mixing" + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/player/sampler" "github.com/gotracker/playback/voice" ) @@ -40,7 +40,7 @@ func (m *machine[TPeriod, TGlobalVolume, TMixingVolume, TVolume, TPanning]) setu return nil } -func (m *machine[TPeriod, TGlobalVolume, TMixingVolume, TVolume, TPanning]) renderOPL2Tick(mixerData *mixing.Data, mix *mixing.Mixer, tickSamples int) error { +func (m *machine[TPeriod, TGlobalVolume, TMixingVolume, TVolume, TPanning]) renderOPL2Tick(centerAheadPan panning.PanMixer, mixerData *mixing.Data, mix *mixing.Mixer, tickSamples int) error { // make a stand-alone data buffer for this channel for this tick data := mix.NewMixBuffer(tickSamples) @@ -56,7 +56,7 @@ func (m *machine[TPeriod, TGlobalVolume, TMixingVolume, TVolume, TPanning]) rend } *mixerData = mixing.Data{ Data: data, - Pan: panning.CenterAhead, + PanMatrix: centerAheadPan, Volume: m.gv.ToVolume(), SamplesLen: tickSamples, } diff --git a/player/machine/machine_render.go b/player/machine/machine_render.go index 596e12b..dd80238 100644 --- a/player/machine/machine_render.go +++ b/player/machine/machine_render.go @@ -3,11 +3,11 @@ package machine import ( "fmt" - "github.com/gotracker/gomixing/mixing" - "github.com/gotracker/gomixing/panning" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/frequency" "github.com/gotracker/playback/index" + "github.com/gotracker/playback/mixing" + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/output" "github.com/gotracker/playback/player/render" "github.com/gotracker/playback/player/sampler" @@ -38,14 +38,15 @@ func (m *machine[TPeriod, TGlobalVolume, TMixingVolume, TVolume, TPanning]) rend } details := mixer.Details{ - Mix: s.Mixer(), - Panmixer: s.GetPanMixer(), - SampleRate: frequency.Frequency(s.SampleRate), - Samples: premix.SamplesLen, - Duration: tickDuration, + Mix: s.Mixer(), + Panmixer: s.GetPanMixer(), + SampleRate: frequency.Frequency(s.SampleRate), + StereoSeparation: s.StereoSeparation, + Samples: premix.SamplesLen, + Duration: tickDuration, } - centerAheadPan := details.Panmixer.GetMixingMatrix(panning.CenterAhead) + centerAheadPan := details.Panmixer.GetMixingMatrix(panning.CenterAhead, s.StereoSeparation) var mixData []mixing.Data for i := range m.actualOutputs { @@ -64,7 +65,7 @@ func (m *machine[TPeriod, TGlobalVolume, TMixingVolume, TVolume, TPanning]) rend } else { mixData = append(mixData, mixing.Data{ Data: details.Mix.NewMixBuffer(details.Samples), - Pan: panning.CenterAhead, + PanMatrix: centerAheadPan, Volume: volume.Volume(0), Pos: 0, SamplesLen: details.Samples, @@ -92,7 +93,7 @@ func (m *machine[TPeriod, TGlobalVolume, TMixingVolume, TVolume, TPanning]) rend } else { mixData = append(mixData, mixing.Data{ Data: details.Mix.NewMixBuffer(details.Samples), - Pan: panning.CenterAhead, + PanMatrix: centerAheadPan, Volume: volume.Volume(0), Pos: 0, SamplesLen: details.Samples, @@ -106,7 +107,7 @@ func (m *machine[TPeriod, TGlobalVolume, TMixingVolume, TVolume, TPanning]) rend if m.opl2 != nil { rr := [1]mixing.Data{} - if err := m.renderOPL2Tick(&rr[0], s.Mixer(), premix.SamplesLen); err != nil { + if err := m.renderOPL2Tick(centerAheadPan, &rr[0], s.Mixer(), premix.SamplesLen); err != nil { return nil, err } premix.Data = append(premix.Data, rr[:]) @@ -127,7 +128,7 @@ func (m *machine[TPeriod, TGlobalVolume, TMixingVolume, TVolume, TPanning]) rend premix.Data = append(premix.Data, mixing.ChannelData{ mixing.Data{ Data: details.Mix.NewMixBuffer(details.Samples), - Pan: panning.CenterAhead, + PanMatrix: centerAheadPan, Volume: volume.Volume(0), Pos: 0, SamplesLen: details.Samples, diff --git a/player/machine/newnoteinfo.go b/player/machine/newnoteinfo.go index 35d4fab..d2da909 100644 --- a/player/machine/newnoteinfo.go +++ b/player/machine/newnoteinfo.go @@ -1,8 +1,8 @@ package machine import ( - "github.com/gotracker/gomixing/sampling" "github.com/gotracker/playback/instrument" + "github.com/gotracker/playback/mixing/sampling" "github.com/gotracker/playback/note" "github.com/heucuva/optional" ) diff --git a/player/machine/pastnote.go b/player/machine/pastnote.go index 94552d7..170c370 100644 --- a/player/machine/pastnote.go +++ b/player/machine/pastnote.go @@ -1,8 +1,8 @@ package machine import ( - "github.com/gotracker/gomixing/mixing" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/period" "github.com/gotracker/playback/player/render" "github.com/gotracker/playback/voice/mixer" diff --git a/player/render/channel.go b/player/render/channel.go index 21c30ed..56e496d 100644 --- a/player/render/channel.go +++ b/player/render/channel.go @@ -1,9 +1,10 @@ package render import ( - "github.com/gotracker/gomixing/mixing" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/opl2" + "github.com/gotracker/playback/mixing" + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/filter" "github.com/gotracker/playback/period" @@ -27,7 +28,7 @@ type Channel[TPeriod period.Period] struct { vrem func() // function to call when voice is stopped/removed } -func (c *Channel[TPeriod]) RenderAndTick(pc period.PeriodConverter[TPeriod], centerAheadPan volume.Matrix, details mixer.Details) (*mixing.Data, error) { +func (c *Channel[TPeriod]) RenderAndTick(pc period.PeriodConverter[TPeriod], centerAheadPan panning.PanMixer, details mixer.Details) (*mixing.Data, error) { if filt := c.PluginFilter; filt != nil { filt.SetPlaybackRate(details.SampleRate) } diff --git a/player/sampler/sampler.go b/player/sampler/sampler.go index 6cedefc..ea005e6 100644 --- a/player/sampler/sampler.go +++ b/player/sampler/sampler.go @@ -1,28 +1,30 @@ package sampler import ( - "github.com/gotracker/gomixing/mixing" "github.com/gotracker/playback/frequency" + "github.com/gotracker/playback/mixing" "github.com/gotracker/playback/output" ) // Sampler is a container of sampler/mixer settings type Sampler struct { - SampleRate int - BaseClockRate frequency.Frequency - OnGenerate func(premix *output.PremixData) + SampleRate int + BaseClockRate frequency.Frequency + OnGenerate func(premix *output.PremixData) + StereoSeparation float32 mixer mixing.Mixer } // NewSampler returns a new sampler object based on the input settings -func NewSampler(samplesPerSec, channels int, onGenerate func(premix *output.PremixData)) *Sampler { +func NewSampler(samplesPerSec, channels int, stereoSeparation float32, onGenerate func(premix *output.PremixData)) *Sampler { s := Sampler{ SampleRate: samplesPerSec, OnGenerate: onGenerate, mixer: mixing.Mixer{ Channels: channels, }, + StereoSeparation: stereoSeparation, } return &s } diff --git a/song/channeldata.go b/song/channeldata.go index 8009ca3..d3005a5 100644 --- a/song/channeldata.go +++ b/song/channeldata.go @@ -3,7 +3,7 @@ package song import ( "fmt" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/note" ) diff --git a/song/song.go b/song/song.go index 92888d3..5870655 100644 --- a/song/song.go +++ b/song/song.go @@ -5,9 +5,9 @@ import ( "reflect" "time" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/index" "github.com/gotracker/playback/instrument" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/note" "github.com/gotracker/playback/system" "github.com/gotracker/playback/voice/types" diff --git a/voice/component/modulator_amp.go b/voice/component/modulator_amp.go index 687ab14..af37953 100755 --- a/voice/component/modulator_amp.go +++ b/voice/component/modulator_amp.go @@ -3,8 +3,8 @@ package component import ( "fmt" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/index" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/tracing" "github.com/gotracker/playback/voice/types" "github.com/heucuva/optional" diff --git a/voice/component/modulator_fadeout.go b/voice/component/modulator_fadeout.go index aebc7b3..ee46b0a 100644 --- a/voice/component/modulator_fadeout.go +++ b/voice/component/modulator_fadeout.go @@ -3,8 +3,8 @@ package component import ( "fmt" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/index" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/tracing" ) diff --git a/voice/component/modulator_pan.go b/voice/component/modulator_pan.go index 04ed488..1e06bd9 100755 --- a/voice/component/modulator_pan.go +++ b/voice/component/modulator_pan.go @@ -3,8 +3,8 @@ package component import ( "fmt" - "github.com/gotracker/gomixing/panning" "github.com/gotracker/playback/index" + "github.com/gotracker/playback/mixing/panning" "github.com/gotracker/playback/tracing" "github.com/gotracker/playback/voice/types" ) diff --git a/voice/component/opl2.go b/voice/component/opl2.go index 250bb05..02d5805 100755 --- a/voice/component/opl2.go +++ b/voice/component/opl2.go @@ -1,8 +1,8 @@ package component import ( - "github.com/gotracker/gomixing/volume" "github.com/gotracker/opl2" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/frequency" "github.com/gotracker/playback/index" diff --git a/voice/component/sampler.go b/voice/component/sampler.go index d4f360b..34afb48 100644 --- a/voice/component/sampler.go +++ b/voice/component/sampler.go @@ -3,8 +3,8 @@ package component import ( "fmt" - "github.com/gotracker/gomixing/sampling" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/index" "github.com/gotracker/playback/tracing" diff --git a/voice/component/vol0optimization.go b/voice/component/vol0optimization.go index ab90d34..a5914f5 100644 --- a/voice/component/vol0optimization.go +++ b/voice/component/vol0optimization.go @@ -3,8 +3,8 @@ package component import ( "fmt" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/playback/index" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/tracing" "github.com/gotracker/playback/voice/vol0optimization" ) diff --git a/voice/fadeout/fadeout.go b/voice/fadeout/fadeout.go index d29fb65..d36de50 100755 --- a/voice/fadeout/fadeout.go +++ b/voice/fadeout/fadeout.go @@ -1,7 +1,7 @@ package fadeout import ( - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" ) // Mode is the mode used to process fade-out diff --git a/voice/filter/filterapplier.go b/voice/filter/filterapplier.go index e91e197..fd9a2f5 100644 --- a/voice/filter/filterapplier.go +++ b/voice/filter/filterapplier.go @@ -1,7 +1,7 @@ package filter import ( - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" ) // Applier is an interface for applying a filter to a sample stream diff --git a/voice/mixer/details.go b/voice/mixer/details.go index 3b3ce39..fa4d3d0 100644 --- a/voice/mixer/details.go +++ b/voice/mixer/details.go @@ -3,14 +3,15 @@ package mixer import ( "time" - "github.com/gotracker/gomixing/mixing" "github.com/gotracker/playback/frequency" + "github.com/gotracker/playback/mixing" ) type Details struct { - Mix *mixing.Mixer - Panmixer mixing.PanMixer - SampleRate frequency.Frequency - Samples int - Duration time.Duration + Mix *mixing.Mixer + Panmixer mixing.PanMixer + SampleRate frequency.Frequency + StereoSeparation float32 + Samples int + Duration time.Duration } diff --git a/voice/mixer/output.go b/voice/mixer/output.go index d47ae9b..4b39f62 100644 --- a/voice/mixer/output.go +++ b/voice/mixer/output.go @@ -1,8 +1,8 @@ package mixer import ( - "github.com/gotracker/gomixing/sampling" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" ) // Output applies a filter to a sample stream diff --git a/voice/pcm/bit16.go b/voice/pcm/bit16.go index 6920a35..bcbfece 100755 --- a/voice/pcm/bit16.go +++ b/voice/pcm/bit16.go @@ -3,7 +3,7 @@ package pcm import ( "io" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" ) const ( diff --git a/voice/pcm/bit32float.go b/voice/pcm/bit32float.go index d92b755..171e525 100755 --- a/voice/pcm/bit32float.go +++ b/voice/pcm/bit32float.go @@ -4,7 +4,7 @@ import ( "io" "math" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" ) const ( diff --git a/voice/pcm/bit64float.go b/voice/pcm/bit64float.go index 7f7d298..7859085 100755 --- a/voice/pcm/bit64float.go +++ b/voice/pcm/bit64float.go @@ -4,7 +4,7 @@ import ( "io" "math" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" ) const ( diff --git a/voice/pcm/bit8.go b/voice/pcm/bit8.go index b27ed4e..9c480e8 100755 --- a/voice/pcm/bit8.go +++ b/voice/pcm/bit8.go @@ -3,7 +3,7 @@ package pcm import ( "io" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" ) const ( diff --git a/voice/pcm/converter.go b/voice/pcm/converter.go index 6c080f5..1fd9aca 100755 --- a/voice/pcm/converter.go +++ b/voice/pcm/converter.go @@ -1,6 +1,6 @@ package pcm -import "github.com/gotracker/gomixing/volume" +import "github.com/gotracker/playback/mixing/volume" // SampleConverter is an interface to a sample converter type SampleConverter interface { diff --git a/voice/pcm/reader.go b/voice/pcm/reader.go index 1283f4c..b181c15 100755 --- a/voice/pcm/reader.go +++ b/voice/pcm/reader.go @@ -1,7 +1,7 @@ package pcm import ( - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" ) // SampleReader is a reader interface that can return a whole multichannel sample at the current position diff --git a/voice/pcm/sample_native.go b/voice/pcm/sample_native.go index 245a4ca..f2c03c2 100755 --- a/voice/pcm/sample_native.go +++ b/voice/pcm/sample_native.go @@ -7,7 +7,7 @@ import ( "errors" "math" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" ) var ( diff --git a/voice/pcm/sampledata.go b/voice/pcm/sampledata.go index eec5e5d..d917bff 100755 --- a/voice/pcm/sampledata.go +++ b/voice/pcm/sampledata.go @@ -7,7 +7,7 @@ import ( "errors" "math" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing/volume" ) // Sample is the interface to a sample diff --git a/voice/pcm/samplereader.go b/voice/pcm/samplereader.go index 1c7971b..b94a958 100644 --- a/voice/pcm/samplereader.go +++ b/voice/pcm/samplereader.go @@ -1,6 +1,6 @@ package pcm -import "github.com/gotracker/gomixing/volume" +import "github.com/gotracker/playback/mixing/volume" type SampleType interface { ~int8 | ~uint8 | ~int16 | ~uint16 | ~float32 | ~float64 diff --git a/voice/render.go b/voice/render.go index c7f1b02..fc03aaf 100644 --- a/voice/render.go +++ b/voice/render.go @@ -1,14 +1,15 @@ package voice import ( - "github.com/gotracker/gomixing/mixing" - "github.com/gotracker/gomixing/sampling" - "github.com/gotracker/gomixing/volume" + "github.com/gotracker/playback/mixing" + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/period" "github.com/gotracker/playback/voice/mixer" ) -func RenderAndTick[TPeriod Period](in Voice, pc period.PeriodConverter[TPeriod], centerAheadPan volume.Matrix, details mixer.Details, out mixer.ApplyFilter) (*mixing.Data, error) { +func RenderAndTick[TPeriod Period](in Voice, pc period.PeriodConverter[TPeriod], centerAheadPan panning.PanMixer, details mixer.Details, out mixer.ApplyFilter) (*mixing.Data, error) { if in.IsDone() { return nil, nil } @@ -54,7 +55,7 @@ func RenderAndTick[TPeriod Period](in Voice, pc period.PeriodConverter[TPeriod], sampleData := mixing.SampleMixIn{ Sample: sampler, StaticVol: volume.Volume(1.0), - VolMatrix: centerAheadPan, + PanMatrix: centerAheadPan, MixPos: 0, MixLen: details.Samples, } @@ -63,7 +64,7 @@ func RenderAndTick[TPeriod Period](in Voice, pc period.PeriodConverter[TPeriod], mixBuffer.MixInSample(sampleData) data := &mixing.Data{ Data: mixBuffer, - Pan: pan, + PanMatrix: details.Panmixer.GetMixingMatrix(pan, details.StereoSeparation), Volume: volume.Volume(1.0), Pos: 0, SamplesLen: details.Samples, diff --git a/voice/types/panning.go b/voice/types/panning.go index 6c6ea2b..afefb15 100644 --- a/voice/types/panning.go +++ b/voice/types/panning.go @@ -1,6 +1,6 @@ package types -import "github.com/gotracker/gomixing/panning" +import "github.com/gotracker/playback/mixing/panning" type Panning interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | diff --git a/voice/types/volume.go b/voice/types/volume.go index caedbe3..714fc7c 100644 --- a/voice/types/volume.go +++ b/voice/types/volume.go @@ -1,6 +1,6 @@ package types -import "github.com/gotracker/gomixing/volume" +import "github.com/gotracker/playback/mixing/volume" type Volume interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | diff --git a/voice/voice.go b/voice/voice.go index 14e00f5..faf389d 100755 --- a/voice/voice.go +++ b/voice/voice.go @@ -1,10 +1,10 @@ package voice import ( - "github.com/gotracker/gomixing/panning" - "github.com/gotracker/gomixing/sampling" - "github.com/gotracker/gomixing/volume" "github.com/gotracker/opl2" + "github.com/gotracker/playback/mixing/panning" + "github.com/gotracker/playback/mixing/sampling" + "github.com/gotracker/playback/mixing/volume" "github.com/gotracker/playback/frequency" "github.com/gotracker/playback/index"