Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Juno6 noise oscillator terminates prematurely #164

Open
dpwe opened this issue Aug 18, 2024 · 2 comments
Open

Juno6 noise oscillator terminates prematurely #164

dpwe opened this issue Aug 18, 2024 · 2 comments

Comments

@dpwe
Copy link
Collaborator

dpwe commented Aug 18, 2024

The symptom is that the noise oscillator in the Juno 6 emulation will stop until the next velocity > 0 event once its amplitude is reduced to zero. So, if you have a patch, say, with saw and noise turned up, then while holding down a note fade the whole note to zero with the VCA level slider, then fade it back up, the saw waveform will still be there, but the noise will have been turned off.

I think this is related to the opportunistic oscillator termination using zero_amp_clock, which is intended to "reap" inactive notes even if no note-off is sent. I think I need to rework this to allow oscillators to "revive" after periods of silence (for instance if their amplitude parameters are updated).

@dpwe
Copy link
Collaborator Author

dpwe commented Aug 20, 2024

#175 fixes this for the particular case of the noise osc in Juno, while notes are held down. However, because the "reviving" is conditioned on the note still awaiting a note-off, notes with long decays can still exhibit this extinguishing of the noise osc (set Release to long, turn up the noise osc, play a note, release the note, then during the decay turn the noise down to zero; turning it back up again will not reintroduce noise). This is an edge case I can live with; without this, the "revival" process would bring back entire long-silent notes.

But there's something I don't understand: This "extinguishing" only applied to the noise osc, not the other oscs. The subosc, for instance, is very similar with its own amplitude slider, but happily returns. I'm not sure what's special about the noise osc - perhaps the fact that it doesn't have any chained osc?

I was worried that perhaps only the noise osc was being terminated/suspended on amplitude falling to zero, but I confirmed with amy.send(debug=9) that all oscs are suspended in quiescent state.

@dpwe
Copy link
Collaborator Author

dpwe commented Aug 21, 2024

I feel like it would be valuable to try to design how the lifetime of a note progresses, including both initiation and termination.

Oscillators start in a non-active state. This was originally marked by synth[osc].status = STATUS_OFF, although after #175 this is now usually AUDIBLE_SUSPENDED, meaning the oscillator is no longer being run because it was detected as emitting zero amplitude for multiple frames (MIN_ZERO_AMP_TIME_SAMPS == 10 * AMY_BLOCK_SIZE).

An oscillator starts when it receives a note-on event, which specifically means a nonzero velocity (amy.c:966). The status is set to AUDIBLE and synth[osc].note_on_clock is set to total_samples, the current 'time' in samples. The note_off_clock (time of the most recent note-off) is unset, indicating we are still awaiting a matching note-off (vel=0) event.

Naively, we might expect to be able to wait for the note-off event, at which point we could stop the oscillator and change its status back to off. The problem is "release" - the phase of a note sound dying away after the note-off event. This is the "R" part of an ADSR envelope, and is an important aspect of many notes.

So, instead, we have to wait for the release to end. However, this is not so simple because there isn't a particular envelope generator with a fixed association with the amplitude envelope. We used to wait for all the envelopes to finish their releases, at which point we could be confident the note was over.

However, we switched to a different principle, of just looking at the amplitude of the frames coming out of the oscillator, and noticing when they had fallen to zero (amy.c:1230, which stores the time of the first all-zero block in synth[osc].zero_amp_clock). Then, if the oscillator fails to produce any nonzero samples for MIN_ZERO_AMP_TIME_SAMPLES, we move it to status AUDIBLE_SUSPENDED and stop evaluating it. There's an attractive logic to this - if the oscillator is generating zero output, we don't really care why, we should stop it.

The weakness here was that if an oscillator outputs zero amplitude because of some cause other than the note ending, for instance if its amplitude is being modified by a user twiddling a control knob, or even perhaps by being amplitude-modulated by a pulse that effectively turns it off for some period, then the oscillator can be prematurely terminated. We dealt with knob-twiddling as a special case in #175 by promoting AUDIBLE_SUSPENDED back to AUDIBLE within play_event() (amy.c:908) if the amplitude or envelope parameters are modified, provided we are not in the release portion (i.e., the note_off_clock is still unset, meaning the note-off has yet to be received).

Although looking at the output samples is appealing in its logic, I think we might well want to go back to tracking the envelopes, and keeping an oscillator active only until any amplitude-affecting envelopes have reached terminal zero values. It's not impractical to figure out which ones are connected to amplitude (by looking for nonzero values in amp_coefs[], and it would avoid the unexpected extinction of oscillators during release that #175 exhibits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant