mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-27 10:50:23 -04:00
fix(vm/compiler): invert the logic of the release flag in the voices (closes #102)
This makes all envelopes released by default, instead of attacking. Add also test to demonstrate the buggy behaviour.
This commit is contained in:
parent
20b0598a57
commit
1ac2ad3c75
@ -162,7 +162,8 @@ regression_test(test_delay_flanger "ENVELOPE;FOP_MULP;PANNING;VCO_SINE;SEND")
|
|||||||
regression_test(test_envelope_mod "VCO_SINE;ENVELOPE;SEND")
|
regression_test(test_envelope_mod "VCO_SINE;ENVELOPE;SEND")
|
||||||
regression_test(test_envelope_16bit ENVELOPE "" test_envelope "-i")
|
regression_test(test_envelope_16bit ENVELOPE "" test_envelope "-i")
|
||||||
|
|
||||||
regression_test(test_polyphony "ENVELOPE;VCO_SINE")
|
regression_test(test_polyphony "ENVELOPE;VCO_SINE" POLYPHONY)
|
||||||
|
regression_test(test_polyphony_init POLYPHONY)
|
||||||
regression_test(test_chords "ENVELOPE;VCO_SINE")
|
regression_test(test_chords "ENVELOPE;VCO_SINE")
|
||||||
regression_test(test_speed "ENVELOPE;VCO_SINE")
|
regression_test(test_speed "ENVELOPE;VCO_SINE")
|
||||||
regression_test(test_sync "ENVELOPE" "" "" "-r")
|
regression_test(test_sync "ENVELOPE" "" "" "-r")
|
||||||
|
BIN
tests/expected_output/test_polyphony_init.raw
Normal file
BIN
tests/expected_output/test_polyphony_init.raw
Normal file
Binary file not shown.
18
tests/test_polyphony_init.yml
Normal file
18
tests/test_polyphony_init.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
bpm: 100
|
||||||
|
rowsperbeat: 4
|
||||||
|
score:
|
||||||
|
rowsperpattern: 16
|
||||||
|
length: 1
|
||||||
|
tracks:
|
||||||
|
- numvoices: 2
|
||||||
|
order: [0]
|
||||||
|
patterns: [[0,0, 0, 0, 64, 1, 0, 0, 64, 1, 64, 1, 0, 0, 0, 0]]
|
||||||
|
patch:
|
||||||
|
- numvoices: 2
|
||||||
|
units:
|
||||||
|
- type: envelope
|
||||||
|
parameters: {attack: 32, decay: 32, gain: 64, release: 64, stereo: 0, sustain: 64}
|
||||||
|
- type: envelope
|
||||||
|
parameters: {attack: 32, decay: 32, gain: 64, release: 64, stereo: 0, sustain: 64}
|
||||||
|
- type: out
|
||||||
|
parameters: {gain: 128, stereo: 1}
|
@ -26,10 +26,11 @@ void SU_CALLCONV su_render_song(float* buffer) {
|
|||||||
synth->RandSeed = 1;
|
synth->RandSeed = 1;
|
||||||
// triger first voice
|
// triger first voice
|
||||||
synth->SynthWrk.Voices[0].Note = 64;
|
synth->SynthWrk.Voices[0].Note = 64;
|
||||||
|
synth->SynthWrk.Voices[0].Sustain = 1;
|
||||||
samples = SU_LENGTH_IN_SAMPLES / 2;
|
samples = SU_LENGTH_IN_SAMPLES / 2;
|
||||||
time = INT32_MAX;
|
time = INT32_MAX;
|
||||||
retval = su_render(synth, buffer, &samples, &time);
|
retval = su_render(synth, buffer, &samples, &time);
|
||||||
synth->SynthWrk.Voices[0].Release++;
|
synth->SynthWrk.Voices[0].Sustain = 0;
|
||||||
buffer = buffer + SU_LENGTH_IN_SAMPLES;
|
buffer = buffer + SU_LENGTH_IN_SAMPLES;
|
||||||
samples = SU_LENGTH_IN_SAMPLES / 2;
|
samples = SU_LENGTH_IN_SAMPLES / 2;
|
||||||
time = INT32_MAX;
|
time = INT32_MAX;
|
||||||
|
@ -37,6 +37,7 @@ int main(int argc, char* argv[]) {
|
|||||||
buffer = (float*)malloc(2 * sizeof(float) * su_max_samples);
|
buffer = (float*)malloc(2 * sizeof(float) * su_max_samples);
|
||||||
// triger first voice
|
// triger first voice
|
||||||
synth->SynthWrk.Voices[0].Note = 64;
|
synth->SynthWrk.Voices[0].Note = 64;
|
||||||
|
synth->SynthWrk.Voices[0].Sustain = 1;
|
||||||
totalrendered = 0;
|
totalrendered = 0;
|
||||||
// First check that when we render using su_render with 0 time
|
// First check that when we render using su_render with 0 time
|
||||||
// we get nothing done
|
// we get nothing done
|
||||||
@ -110,7 +111,7 @@ int main(int argc, char* argv[]) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
if (i == 8)
|
if (i == 8)
|
||||||
synth->SynthWrk.Voices[0].Release++;
|
synth->SynthWrk.Voices[0].Sustain = 0;
|
||||||
}
|
}
|
||||||
if (totalrendered != su_max_samples)
|
if (totalrendered != su_max_samples)
|
||||||
{
|
{
|
||||||
|
@ -102,6 +102,7 @@ func (bridgesynth *BridgeSynth) Trigger(voice int, note byte) {
|
|||||||
}
|
}
|
||||||
s.SynthWrk.Voices[voice] = C.Voice{}
|
s.SynthWrk.Voices[voice] = C.Voice{}
|
||||||
s.SynthWrk.Voices[voice].Note = C.int(note)
|
s.SynthWrk.Voices[voice].Note = C.int(note)
|
||||||
|
s.SynthWrk.Voices[voice].Sustain = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release is part of C.Synths' implementation of sointu.Synth interface
|
// Release is part of C.Synths' implementation of sointu.Synth interface
|
||||||
@ -110,7 +111,7 @@ func (bridgesynth *BridgeSynth) Release(voice int) {
|
|||||||
if voice < 0 || voice >= len(s.SynthWrk.Voices) {
|
if voice < 0 || voice >= len(s.SynthWrk.Voices) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.SynthWrk.Voices[voice].Release = 1
|
s.SynthWrk.Voices[voice].Sustain = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
|
@ -9,7 +9,7 @@ typedef struct Unit {
|
|||||||
|
|
||||||
typedef struct Voice {
|
typedef struct Voice {
|
||||||
int Note;
|
int Note;
|
||||||
int Release;
|
int Sustain;
|
||||||
float Inputs[8];
|
float Inputs[8];
|
||||||
float Reserved[6];
|
float Reserved[6];
|
||||||
struct Unit Units[63];
|
struct Unit Units[63];
|
||||||
|
@ -137,7 +137,7 @@ su_calculate_voices_loop: ; do {
|
|||||||
add edi, ebx
|
add edi, ebx
|
||||||
shl edi, 12 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
|
shl edi, 12 ; each unit = 64 bytes and there are 1<<MAX_UNITS_SHIFT units + small header
|
||||||
{{- .Prepare "su_synth_obj" | indent 4}}
|
{{- .Prepare "su_synth_obj" | indent 4}}
|
||||||
inc dword [{{.Use "su_synth_obj"}} + su_synthworkspace.voices + su_voice.release + {{.DI}}] ; set the voice currently active to release; notice that it could increment any number of times
|
and dword [{{.Use "su_synth_obj"}} + su_synthworkspace.voices + su_voice.sustain + {{.DI}}], 0 ; set the voice currently active to release; notice that it could increment any number of times
|
||||||
cmp al, {{.Hold}} ; if cl < HLD (no new note triggered)
|
cmp al, {{.Hold}} ; if cl < HLD (no new note triggered)
|
||||||
jl su_update_voices_nexttrack ; goto nexttrack
|
jl su_update_voices_nexttrack ; goto nexttrack
|
||||||
inc ecx ; curvoice++
|
inc ecx ; curvoice++
|
||||||
@ -150,7 +150,8 @@ su_update_voices_skipreset:
|
|||||||
shl ecx, 12 ; each unit = 64 bytes and there are 1<<6 units + small header
|
shl ecx, 12 ; each unit = 64 bytes and there are 1<<6 units + small header
|
||||||
lea {{.DI}},[{{.Use "su_synth_obj"}} + su_synthworkspace.voices + {{.CX}}]
|
lea {{.DI}},[{{.Use "su_synth_obj"}} + su_synthworkspace.voices + {{.CX}}]
|
||||||
stosd ; save note
|
stosd ; save note
|
||||||
mov ecx, (su_voice.size - su_voice.release)/4
|
stosd ; save release
|
||||||
|
mov ecx, (su_voice.size - su_voice.inputs)/4
|
||||||
xor eax, eax
|
xor eax, eax
|
||||||
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
||||||
su_update_voices_nexttrack:
|
su_update_voices_nexttrack:
|
||||||
@ -180,11 +181,12 @@ su_update_voices_trackloop:
|
|||||||
movzx eax, byte [{{.Use "su_patterns" .AX}} + {{.DX}}] ; ecx = note
|
movzx eax, byte [{{.Use "su_patterns" .AX}} + {{.DX}}] ; ecx = note
|
||||||
cmp al, {{.Hold}} ; anything but hold causes action
|
cmp al, {{.Hold}} ; anything but hold causes action
|
||||||
je short su_update_voices_nexttrack
|
je short su_update_voices_nexttrack
|
||||||
inc dword [{{.DI}}+su_voice.release] ; set the voice currently active to release; notice that it could increment any number of times
|
mov dword [{{.DI}}+su_voice.sustain], eax ; set the voice currently active to release
|
||||||
jb su_update_voices_nexttrack ; if cl < HLD (no new note triggered) goto nexttrack
|
jb su_update_voices_nexttrack ; if cl < HLD (no new note triggered) goto nexttrack
|
||||||
su_update_voices_retrigger:
|
su_update_voices_retrigger:
|
||||||
stosd ; save note
|
stosd ; save note
|
||||||
mov ecx, (su_voice.size - su_voice.release)/4 ; could be xor ecx, ecx; mov ch,...>>8, but will it actually be smaller after compression?
|
stosd ; save sustain
|
||||||
|
mov ecx, (su_voice.size - su_voice.inputs)/4 ; could be xor ecx, ecx; mov ch,...>>8, but will it actually be smaller after compression?
|
||||||
xor eax, eax
|
xor eax, eax
|
||||||
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
rep stosd ; clear the workspace of the new voice, retriggering oscillators
|
||||||
jmp short su_update_voices_skipadd
|
jmp short su_update_voices_skipadd
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
ret
|
ret
|
||||||
su_op_envelope_mono:
|
su_op_envelope_mono:
|
||||||
{{- end}}
|
{{- end}}
|
||||||
mov eax, dword [{{.INP}}-su_voice.inputs+su_voice.release] ; eax = su_instrument.release
|
mov eax, dword [{{.INP}}-su_voice.inputs+su_voice.sustain] ; eax = su_instrument.sustain
|
||||||
test eax, eax ; if (eax == 0)
|
test eax, eax ; if (eax != 0)
|
||||||
je su_op_envelope_process ; goto process
|
jne su_op_envelope_process ; goto process
|
||||||
mov al, {{.InputNumber "envelope" "release"}} ; [state]=RELEASE
|
mov al, {{.InputNumber "envelope" "release"}} ; [state]=RELEASE
|
||||||
mov dword [{{.WRK}}], eax ; note that mov al, XXX; mov ..., eax is less bytes than doing it directly
|
mov dword [{{.WRK}}], eax ; note that mov al, XXX; mov ..., eax is less bytes than doing it directly
|
||||||
su_op_envelope_process:
|
su_op_envelope_process:
|
||||||
|
@ -12,7 +12,7 @@ endstruc
|
|||||||
;-------------------------------------------------------------------------------
|
;-------------------------------------------------------------------------------
|
||||||
struc su_voice
|
struc su_voice
|
||||||
.note resd 1
|
.note resd 1
|
||||||
.release resd 1
|
.sustain resd 1
|
||||||
.inputs resd 8
|
.inputs resd 8
|
||||||
.reserved resd 6 ; this is done to so the whole voice is 2^n long, see polyphonic player
|
.reserved resd 6 ; this is done to so the whole voice is 2^n long, see polyphonic player
|
||||||
.workspace resb 63 * su_unit.size
|
.workspace resb 63 * su_unit.size
|
||||||
|
@ -272,7 +272,7 @@
|
|||||||
)
|
)
|
||||||
(i32.const 4096)
|
(i32.const 4096)
|
||||||
)
|
)
|
||||||
(i32.const 1)
|
(i32.const 0)
|
||||||
) ;; release the note
|
) ;; release the note
|
||||||
(if (i32.gt_u (local.get $note) (i32.const {{.Hold}}))(then
|
(if (i32.gt_u (local.get $note) (i32.const {{.Hold}}))(then
|
||||||
(local.set $di (i32.add
|
(local.set $di (i32.add
|
||||||
@ -290,6 +290,7 @@
|
|||||||
))
|
))
|
||||||
(memory.fill (local.get $di) (i32.const 0) (i32.const 4096))
|
(memory.fill (local.get $di) (i32.const 0) (i32.const 4096))
|
||||||
(i32.store (local.get $di) (local.get $note))
|
(i32.store (local.get $di) (local.get $note))
|
||||||
|
(i32.store offset=4 (local.get $di) (local.get $note))
|
||||||
(i32.store8 offset={{index .Labels "su_trackcurrentvoice"}} (local.get $tracksRemaining) (local.get $voiceNo))
|
(i32.store8 offset={{index .Labels "su_trackcurrentvoice"}} (local.get $tracksRemaining) (local.get $voiceNo))
|
||||||
))
|
))
|
||||||
))
|
))
|
||||||
@ -311,10 +312,11 @@
|
|||||||
(i32.load8_u offset={{index .Labels "su_patterns"}})
|
(i32.load8_u offset={{index .Labels "su_patterns"}})
|
||||||
(local.tee $note)
|
(local.tee $note)
|
||||||
(if (i32.ne (i32.const {{.Hold}}))(then
|
(if (i32.ne (i32.const {{.Hold}}))(then
|
||||||
(i32.store offset=4 (local.get $di) (i32.const 1)) ;; release the note
|
(i32.store offset=4 (local.get $di) (i32.const 0)) ;; release the note
|
||||||
(if (i32.gt_u (local.get $note) (i32.const {{.Hold}}))(then
|
(if (i32.gt_u (local.get $note) (i32.const {{.Hold}}))(then
|
||||||
(memory.fill (local.get $di) (i32.const 0) (i32.const 4096))
|
(memory.fill (local.get $di) (i32.const 0) (i32.const 4096))
|
||||||
(i32.store (local.get $di) (local.get $note))
|
(i32.store (local.get $di) (local.get $note))
|
||||||
|
(i32.store offset=4 (local.get $di) (local.get $note))
|
||||||
))
|
))
|
||||||
))
|
))
|
||||||
(local.set $di (i32.add (local.get $di) (i32.const 4096)))
|
(local.set $di (i32.add (local.get $di) (i32.const 4096)))
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
;; Stereo: push the envelope valeu on stack twice
|
;; Stereo: push the envelope valeu on stack twice
|
||||||
;;-------------------------------------------------------------------------------
|
;;-------------------------------------------------------------------------------
|
||||||
(func $su_op_envelope (param $stereo i32) (local $state i32) (local $level f32) (local $delta f32)
|
(func $su_op_envelope (param $stereo i32) (local $state i32) (local $level f32) (local $delta f32)
|
||||||
(if (i32.load offset=4 (global.get $voice)) (then ;; if voice.release > 0
|
(if (i32.eqz (i32.load offset=4 (global.get $voice))) (then ;; if voice.sustain == 0
|
||||||
(i32.store (global.get $WRK) (i32.const {{.InputNumber "envelope" "release"}})) ;; set envelope state to release
|
(i32.store (global.get $WRK) (i32.const {{.InputNumber "envelope" "release"}})) ;; set envelope state to release
|
||||||
))
|
))
|
||||||
(local.set $state (i32.load (global.get $WRK)))
|
(local.set $state (i32.load (global.get $WRK)))
|
||||||
|
@ -41,7 +41,7 @@ type unit struct {
|
|||||||
|
|
||||||
type voice struct {
|
type voice struct {
|
||||||
note byte
|
note byte
|
||||||
release bool
|
sustain bool
|
||||||
units [MAX_UNITS]unit
|
units [MAX_UNITS]unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,10 +105,11 @@ func (s SynthService) Compile(patch sointu.Patch, bpm int) (sointu.Synth, error)
|
|||||||
func (s *Interpreter) Trigger(voiceIndex int, note byte) {
|
func (s *Interpreter) Trigger(voiceIndex int, note byte) {
|
||||||
s.synth.voices[voiceIndex] = voice{}
|
s.synth.voices[voiceIndex] = voice{}
|
||||||
s.synth.voices[voiceIndex].note = note
|
s.synth.voices[voiceIndex].note = note
|
||||||
|
s.synth.voices[voiceIndex].sustain = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Interpreter) Release(voiceIndex int) {
|
func (s *Interpreter) Release(voiceIndex int) {
|
||||||
s.synth.voices[voiceIndex].release = true
|
s.synth.voices[voiceIndex].sustain = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Interpreter) Update(patch sointu.Patch, bpm int) error {
|
func (s *Interpreter) Update(patch sointu.Patch, bpm int) error {
|
||||||
@ -297,7 +298,7 @@ func (s *Interpreter) Render(buffer []float32, maxtime int) (samples int, time i
|
|||||||
stack = append(stack, synth.outputs[channel])
|
stack = append(stack, synth.outputs[channel])
|
||||||
synth.outputs[channel] = 0
|
synth.outputs[channel] = 0
|
||||||
case opEnvelope:
|
case opEnvelope:
|
||||||
if voices[0].release {
|
if !voices[0].sustain {
|
||||||
unit.state[0] = envStateRelease // set state to release
|
unit.state[0] = envStateRelease // set state to release
|
||||||
}
|
}
|
||||||
state := unit.state[0]
|
state := unit.state[0]
|
||||||
|
Loading…
Reference in New Issue
Block a user