diff --git a/CHANGELOG.md b/CHANGELOG.md index cfbe0e2..84de66b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). home directory of the user. - Instrument presets. The presets are embedded in the executable and there's a button to open a menu to load one of the presets. +- Frequency modulation target for oscillator, as it was in 4klang ### Fixed - The sointu-vsti-native plugin has different plugin ID and plugin name diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fa5c928..bb3a584 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -124,6 +124,8 @@ regression_test(test_oscillat_colormod "VCO_SINE;ENVELOPE;FOP_MULP;FOP_PUSH;SEND regression_test(test_oscillat_shapemod "VCO_SINE;ENVELOPE;FOP_MULP;FOP_PUSH;SEND") regression_test(test_oscillat_gainmod OSCGAINMOD "VCO_SINE;ENVELOPE;FOP_MULP;FOP_PUSH;SEND") regression_test(test_oscillat_gainmod_stereo "" OSCGAINMOD) +regression_test(test_oscillat_frequencymod "VCO_SINE;ENVELOPE;FOP_MULP;FOP_PUSH;SEND") +regression_test(test_oscillat_frequencymod_stereo "VCO_SINE;ENVELOPE;FOP_MULP;FOP_PUSH;SEND") regression_test(test_distort ENVELOPE) regression_test(test_distort_mod "VCO_SINE;ENVELOPE;SEND") diff --git a/tests/expected_output/test_oscillat_frequencymod.raw b/tests/expected_output/test_oscillat_frequencymod.raw new file mode 100644 index 0000000..1b4fe8c Binary files /dev/null and b/tests/expected_output/test_oscillat_frequencymod.raw differ diff --git a/tests/expected_output/test_oscillat_frequencymod_stereo.raw b/tests/expected_output/test_oscillat_frequencymod_stereo.raw new file mode 100644 index 0000000..1b4fe8c Binary files /dev/null and b/tests/expected_output/test_oscillat_frequencymod_stereo.raw differ diff --git a/tests/test_oscillat_frequencymod.yml b/tests/test_oscillat_frequencymod.yml new file mode 100644 index 0000000..147ea80 --- /dev/null +++ b/tests/test_oscillat_frequencymod.yml @@ -0,0 +1,27 @@ +bpm: 100 +rowsperbeat: 4 +score: + rowsperpattern: 16 + length: 1 + tracks: + - numvoices: 1 + order: [0] + patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + id: 1 + - type: mulp + parameters: {stereo: 0} + - type: push + parameters: {stereo: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 68, port: 6, sendpop: 1, stereo: 0, target: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_frequencymod_stereo.yml b/tests/test_oscillat_frequencymod_stereo.yml new file mode 100644 index 0000000..4658483 --- /dev/null +++ b/tests/test_oscillat_frequencymod_stereo.yml @@ -0,0 +1,27 @@ +bpm: 100 +rowsperbeat: 4 +score: + rowsperpattern: 16 + length: 1 + tracks: + - numvoices: 1 + order: [0] + patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: push + parameters: {stereo: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 1, transpose: 64, type: 0, unison: 0} + id: 1 + - type: mulp + parameters: {stereo: 1} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 68, port: 6, sendpop: 1, stereo: 0, target: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/unittype.go b/unittype.go index 4aa037a..a8b329a 100644 --- a/unittype.go +++ b/unittype.go @@ -105,6 +105,7 @@ var UnitTypes = map[string]([]UnitParameter){ {Name: "color", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, {Name: "shape", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "frequency", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}, {Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false}, {Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, {Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false}, diff --git a/vm/compiler/templates/amd64-386/sources.asm b/vm/compiler/templates/amd64-386/sources.asm index 85e48ff..f6bf727 100644 --- a/vm/compiler/templates/amd64-386/sources.asm +++ b/vm/compiler/templates/amd64-386/sources.asm @@ -99,6 +99,11 @@ su_op_noise_mono: ; Stereo: push l r on stack, where l has opposite detune compared to r ;------------------------------------------------------------------------------- {{.Func "su_op_oscillator" "Opcode"}} +{{- if .SupportsModulation "oscillator" "frequency"}} + push 0 + pop {{.CX}} ; clear cx without affecting flags + xchg ecx, dword [{{.Modulation "oscillator" "frequency"}}] +{{- end}} lodsb ; load the flags {{- if .Library}} mov {{.DI}}, [{{.Stack "SampleTable"}}]; we need to put this in a register, as the stereo & unisons screw the stack positions @@ -175,6 +180,11 @@ su_op_oscillat_normalize_note: fmul dword [{{.Float 0.000092696138 | .Use}}] ; // st0 is now frequency su_op_oscillat_normalized: fadd dword [{{.WRK}}] +{{- if .SupportsModulation "oscillator" "frequency"}} + push {{.CX}} + fadd dword [{{.SP}}] + pop {{.CX}} +{{- end}} {{- if .SupportsParamValue "oscillator" "type" .Sample}} test al, byte 0x80 jz short su_op_oscillat_not_sample diff --git a/vm/compiler/templates/wasm/sources.wat b/vm/compiler/templates/wasm/sources.wat index 597bf52..236653f 100644 --- a/vm/compiler/templates/wasm/sources.wat +++ b/vm/compiler/templates/wasm/sources.wat @@ -107,9 +107,16 @@ {{- if .SupportsParamValueOtherThan "oscillator" "unison" 0}} (local $unison i32) (local $WRK_stash i32) (local $detune_stash f32) {{- end}} +{{- if .SupportsModulation "oscillator" "frequency"}} + (local $freqMod f32) +{{- end}} {{- if .Stereo "oscillator"}} (local $WRK_stereostash i32) (local.set $WRK_stereostash (global.get $WRK)) +{{- end}} +{{- if .SupportsModulation "oscillator" "frequency"}} + (local.set $freqMod (f32.load offset={{.InputNumber "oscillator" "frequency" | mul 4 | add 32}} (global.get $WRK))) + (f32.store offset={{.InputNumber "oscillator" "frequency" | mul 4 | add 32}} (global.get $WRK) (f32.const 0)) {{- end}} (local.set $flags (call $scanValueByte)) (local.set $detune (call $inputSigned (i32.const {{.InputNumber "oscillator" "detune"}}))) @@ -146,6 +153,9 @@ (f32.const 0.000092696138) ;; scaling constant to get middle-C to where it should be (i32.and (local.get $flags) (i32.const 0x8)) )) +{{- if .SupportsModulation "oscillator" "frequency"}} + (f32.add (local.get $freqMod)) +{{- end}} (f32.add (f32.load (global.get $WRK))) ;; add the current phase of the oscillator ) (f32.floor (local.get $phase)) diff --git a/vm/interpreter.go b/vm/interpreter.go index ad398cb..9ff778c 100644 --- a/vm/interpreter.go +++ b/vm/interpreter.go @@ -454,6 +454,7 @@ 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 } + omega += float64(unit.ports[6]) // add frequency modulation var amplitude float32 *statevar += float32(omega) if flags&0x80 == 0x80 { // if this is a sample oscillator @@ -515,6 +516,7 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i stack = append(stack, output) detuneStereo = -detuneStereo } + unit.ports[6] = 0 case opDelay: pregain2 := params[0] * params[0] damp := params[3]