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

Phase-offset of oscillator nodes #2402

Open
pendragon-andyh opened this issue Jun 9, 2015 · 44 comments
Open

Phase-offset of oscillator nodes #2402

pendragon-andyh opened this issue Jun 9, 2015 · 44 comments
Labels
category: new feature P2 Significant developers/implementers interest; "want to have" status: needs feedback
Projects

Comments

@pendragon-andyh
Copy link

Can we have an a-rate parameter on the OscillatorNode that allows modulation of the phase offset? This would allow:

  • Linear phase modulation (which is how the DX7 implements FM synthesis).
  • Creation and modulation of band-limited pulse waves (this is normally done by summing 2 sawtooth waves - the pulse width is related to the phase difference).
@ghost
Copy link

ghost commented Jun 17, 2015

+1, agreed that this is needed. A couple more use cases:

  • For a LFO used to control another parameter, the initial phase is pretty important
  • When reconstructing more complicated waveforms as a sum of sine waves, the phase is important (equivalent to the imaginary component in a FFT, for example).

@joeberkovitz
Copy link
Contributor

Seems like a significant gap. We want to get implementors' feedback on the effort involved.

@rtoy
Copy link
Member

rtoy commented Sep 10, 2015

@hoch and I took a quick look at this a while back. Seems doable without huge effort.

@padenot
Copy link
Member

padenot commented Sep 23, 2015

Yeah it's not very hard and very useful, we should do it.

@rtoy
Copy link
Member

rtoy commented Sep 23, 2015

I concur.

On Wed, Sep 23, 2015 at 2:00 AM, Paul Adenot notifications@github.com
wrote:

Yeah it's not very hard and very useful, we should do it.


Reply to this email directly or view it on GitHub
https://github.com/WebAudio/web-audio-api/issues/541#issuecomment-142534272
.

Ray

@mdjp mdjp assigned mdjp and unassigned mdjp Oct 16, 2015
@meszaros-lajos-gyorgy
Copy link

With this I would have more control on the wave and could change the oscillator's gain more smoothly. Currently the gain doesn't wait for the wave to reach 0, so it cuts the wave anywhere, resulting in a clicking sound whenever the gain is changed.

Please look at my bugzilla report for examples: https://bugzilla.mozilla.org/show_bug.cgi?id=1232644

@jesup
Copy link

jesup commented Jan 4, 2016

I agree with @meszaros-lajos-gyorgy -- make it easy/possible to do the most common thing, which would be to avoid pops/clicks. They're almost never what you want...

@meszaros-lajos-gyorgy
Copy link

To add another example: I'm currently developing an app, which heavily relies on the Audio API's oscillators and gains and it is about constantly changing the volume and pitch of the generated frequencies. You can hear the clicking an popping almost every time you change something.

Live project: http://meszaros-lajos-gyorgy.github.io/microtonal/monochord/
Source code: https://github.com/meszaros-lajos-gyorgy/meszaros-lajos-gyorgy.github.io/tree/master/microtonal/monochord

@modosc
Copy link

modosc commented Jan 4, 2016

@meszaros-lajos-gyorgy fwiw i ran into similar issues and worked around them with setValueAtTime - this removed all clicks for both pitch and volume in my app. granted this isn't something we should have to do (setting value directly should work) but at least it's possible to avoid them for now.

@modosc
Copy link

modosc commented Jan 4, 2016

also, it would be great to get a sync input/output on the oscillator node. then to implement hard sync you could do something like:

osc1.sync.connect(osc2.sync)

i suppose i could see this as a callback too (similar to processor.onaudioprocess) but i'm not sure how well this would work if it was buffered. having logic in the sync would be great since you could selectively sync based on the phase of the second oscillator for soft sync, implement reversing sync, etc. probably i should open a separate issue on this?

@modosc
Copy link

modosc commented Jan 4, 2016

sorry, one more thing:

Currently the gain doesn't wait for the wave to reach 0

won't you potentially get weird behavior here if you have something other than a vanilla oscillator connected directly to the GainNode? if you're implementing some sort of weird synthesis technique which results in some unwanted dc shift you may never cross zero in which case gain changes would just get queued up and never occur. with a sync output / input this could be handled (although now we're talking about adding a sync input to the GainNode which is getting more complicated - the behavior would be "if something's connected to the sync input then queue gain changes up until there's a sync input")

@pendragon-andyh
Copy link
Author

@meszaros-lajos-gyorgy - I have added a combination of SetValueAtTime and LinearRampToValueAtTime to your fiddle (see http://jsfiddle.net/0pjapu9c/1/). I think it fixes your problem.

@meszaros-lajos-gyorgy
Copy link

@pendragon-andyh - I can still hear the clicking for every 'boop' in Firefox 43

@padenot
Copy link
Member

padenot commented Jan 6, 2016

This is likely to be a firefox bug.

@svgeesus
Copy link
Contributor

svgeesus commented Apr 7, 2016

This was asked for again (twice) at the WG plenary panel at WAC2016. One was a call for oscillator sync, to the hard sync effect, which can be done by resetting the phase. The second was the request for pulse waves, with a-rate modulation of mark/space ratio - which could be done with phase control, or could be added directly (to oscillator, or as a new pulseOscillator node).

@svgeesus
Copy link
Contributor

svgeesus commented Apr 7, 2016

Note that naive hard-sync will introduce aliasing because the hard-synced waveform is no longer bandlimited. But see "Hard Sync Without Aliasing" http://www.cs.cmu.edu/~eli/papers/icmc01-hardsync.pdf

@toyoshim
Copy link

+1 on this feature request.

What I want to do more by OscillatorNode is

  • Reset each oscillator phase on each note-on timing (this is a common approach to implement a synth)
  • Reset LFO's oscillator phase on starting LFO
  • Start each oscillator from different phases at one, e.g. Osc-C starts from phase 0, Osc-L starts from phase -PI/2, and Osc-R starts from phase +PI/2 (to make a spacey sound)
  • Sync modulation.

The first three could be realized by having AudioParam phase that JavaScript code can manage. But the last one may need a special method to sync automatically as modosc suggested. Or could we do something equivalent by connecting "sawtooth" type OscillatorNode to the phase parameter?

@rtoy
Copy link
Member

rtoy commented Jul 27, 2016

I believe the intent is to add a phase (name TBD) AudioParam.

I have been trying to implement this in Chrome and it's rather hard if we want to preserve the band-limited requirement of the Oscillator. It's easy if you drop this requirement, but I think it's important for the oscillator to be band-limited.

@hoch
Copy link
Member

hoch commented Jul 27, 2016

@toyoshim

Reset each oscillator phase on each note-on timing (this is a common approach to implement a synth)
Reset LFO's oscillator phase on starting LFO

Is this to reuse a single oscillator without creating a new one? This is doable with osc.phase.setValueAtTime(0, time).

  • Start each oscillator from different phases at one, e.g. Osc-C starts from phase 0, Osc-L starts from phase -PI/2, and Osc-R starts from phase +PI/2 (to make a spacey sound)

Yeap. Also doable with oscA.phase.value = -Math.PI/2; oscA.phase.value = +Math.PI/2 and two stereo panners.

  • Sync modulation.

This has been bugging me for a while. I believe hard-syncing 2 oscillators on zero-crossing might not be possible with this AudioParam approach.

@pendragon-andyh
Copy link
Author

Currently Chrome's PeriodicWave::waveDataForFundamentalFrequency() function decides the lower and higher band-limited-tables to use from the frequency passed-in by the OscillatorHandler::process() function.

Instead of indexing the tables by their fundamental frequency, would it be possible to index by their phase-increment? A large phase-increment indicates a high frequency - so you need to cull more of its high harmonics.

@pendragon-andyh
Copy link
Author

@hoch

I anticipated that the phase parameter would have a values like:

  • -1 = 360 degrees behind the current frequency.
  • 0 = What we have at the moment.
  • +1 = 360 degrees ahead of the current frequency.

Is this to reuse a single oscillator without creating a new one? This is doable with osc.phase.setValueAtTime(0, time).

I would not expect this to work. In most designs, the phase parameter is normally an offset to what the unmodified phase of the oscillator would be. An easier way is to just start a new oscillator.

Sync modulation

I have a theory that (if we have a phase parameter) you could achieve this by the following steps:

  • Play a sine wave at 1 octave below your desired frequency.
  • Use a square wave to modulate its gain.
  • Pass the results through a waveshaper that is designed to flatten-out the sine into a true sawtooth (in my experiments you need a curve that has at least 16000 elements).
  • Play the result through a gain node (to apply the ratio between the oscillators).
  • Send the result to the phase parameter of an oscillator that has 0 frequency.

The "(sine*square)=>waveshaper" trick was the most accurate way that I have found of getting a true sawtooth. Most people would reach for a BufferSourceNode - but the interpolation between samples means that the result is a bit noisy.

@toyoshim
Copy link

toyoshim commented Aug 2, 2016

I assume writing values to the phase make the OSC ignore the frequency value virtually. Is this correct?

This is my understanding. Here, I assume the range for a loop is 0 through 1, and the value will be clipped as set value % 1.

osc1.type = "sine";
osc1.frequency.value = x;
osc2.type = "sawtooth";
osc2.frequency.value = freq;
osc2.conect(osc1.phase);

This makes a sine wave in frequency freq. x could not affect under the phase controlled by something connected.

gain.gain.value = 1.5;
osc2.connect(gain).connect(osc1.phase);

This can generate a periodic wave with 1.5 cycle of sine wave.

This is what I meant in my previous comment about sync modulation.

@rtoy
Copy link
Member

rtoy commented Aug 2, 2016

On Tue, Aug 2, 2016 at 2:06 AM, Takashi Toyoshima notifications@github.com
wrote:

I assume writing values to the phase make the OSC ignore the frequency
value virtually. Is this correct?

​That's a good question. No one has actually proposed anything yet, but I
was assuming that the resulting waveform would be something like

s(2_pi_f*t + phi(t))

where phi(t) is the phase input to the node. You can achieve what you want
by setting the frequency to 0.​ This formulation also makes it much easier
to design a cosine wave by setting phi(t) = pi/2.

I was also assuming that s(t) would still be band-limited when including
the effect of the phase term. But neither of these has been decided yet.

This is my understanding. Here, I assume the range for a loop is 0 through
1, and the value will be clipped as set value % 1.

osc1.type = "sine";
osc1.frequency.value = x;
osc2.type = "sawtooth";
osc2.frequency.value = freq;
osc2.conect(osc1.phase);

This makes a sine wave in frequency freq. x could not affect under the
phase controlled by something connected.

​Almost. Since osc2 is band-limited, the resulting sawtooth has ringing so
the output of osc1 won't quite be a sine wave.​

gain.gain.value = 1.5;
osc2.connect(gain).connect(osc1.phase);

This can generate a periodic wave with 1.5 cycle of sine wave.

This is what I meant in my previous comment about sync modulation.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/WebAudio/web-audio-api/issues/541#issuecomment-236847293,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAofPHJw_BoMgkTXBo-CiW5cHhe5sJ1bks5qbwiqgaJpZM4E9mrb
.

Ray

@joeberkovitz
Copy link
Contributor

New feature; need to defer to a later iteration.

@svgeesus
Copy link
Contributor

Discussed at March 2018 f2f. Hard (phase reset) and soft (reversing) sync, difficulty of maintaining bandlimited with hard sync.

@svgeesus
Copy link
Contributor

Hard Sync Without Aliasing - Carnegie Mellon School of Computer Science

@poojarfuturistic5

This comment has been minimized.

@meszaros-lajos-gyorgy
Copy link

Hi! I don't want to rush anyone, but since the ticket has been opened for over 3 years now, I feel an urge to ask: is there any progress with this? is anyone working on this? have every question been answered regarding this feature? can we help in any way to make this feature happen sooner? Thanks!

@hoch
Copy link
Member

hoch commented Aug 20, 2018

It is rather a design problem; how parameters do we expose? Is it involved with 2 oscillator nodes or a new node with multiple oscillators? I think the right course of action is to build a prototype design with AudioWorklet and gather feedback from the dev community.

@rtoy
Copy link
Member

rtoy commented Aug 20, 2018

Let's keep this issue about a phase offset for the OscillatorNode. Those wanting to talk about hard sync and the like, please open a new issue to discuss that.

@mdjp mdjp transferred this issue from WebAudio/web-audio-api Sep 16, 2019
@svgeesus
Copy link
Contributor

Those wanting to talk about hard sync and the like, please open a new issue to discuss that.

hard sync issue

@waterplea
Copy link

Any news on this issue? phase control seems kinda essential when it comes to any kind of synthesis.

@svgeesus
Copy link
Contributor

@padenot
Copy link
Member

padenot commented Sep 24, 2020

I think what we need is to have a clearer idea of what is expected, mostly about band-limiting. A phase AudioParam can trivially jump in value (setValueAtTime(v, t) will provoke an immediate jump to v). Is it expected to have the output of the oscillator to be band-limited? Intuitively, this wouldn't work too well with our wave table based approached, but I haven't thought about this too much.

@pendragon-andyh
Copy link
Author

We get distortion from instantaneously changing most AudioParams. My original thought was that you could apply the phase offset directly to the waveform's lookup index. You would probably get a small amount of aliasing if you modulate like crazy on a square wave.

I don't actually need this feature for pulse width modulation any more (I used a worklet in https://github.com/pendragon-andyh/junox and used polyblep to minimise aliasing). I can use worklets for hard-sync and phase modulation.

@rtoy
Copy link
Member

rtoy commented Sep 24, 2020

This is really nice and kind of the we intended new feature requests to work: a feature is requested, a polyfill with a worklet is created to show how it might work, and this is used to guide the design.

Thanks!

@khoin
Copy link

khoin commented Sep 24, 2020

Is this an implementation issue -- regarding band-limiting?

We already have a-rated frequency AParam, that could easily generate frequencies above Nyquist. Were there any discussions about that before?

@rtoy
Copy link
Member

rtoy commented Sep 24, 2020

The question is if it should be band-limited in principle.

The a-rate frequency was part of the original implementation, before standardization. Certainly, setValueAtTime() causes instantaneous changes in frequency, and I think we're ok with that not being band-limited.

I personally am ok with phase offset potentially not being band-limited since the oscillator itself isn't always band-limited.

@khoin
Copy link

khoin commented Sep 25, 2020

I'm personally okay with that too, and for the same reason.

@khoin
Copy link

khoin commented Sep 27, 2020

Here's my mock-up of OscillatorNode with phase AudioParam. https://khoin.github.io/OscillatorNodeP/
Impl.: https://github.com/khoin/OscillatorNodeP/blob/master/OscillatorNodeP.js

You can simply test that phase works by starting two oscillators. If you're lucky, your oscillators are out of phase completely and you'll hear nothing. You can then simply change the phase of the oscillator until you start hearing something. Conversely, you hear something at first and need to change the phase until they cancel each other out.

@mdjp
Copy link
Member

mdjp commented Jul 22, 2021

We should group WebAudio/web-audio-api-v2#9 WebAudio/web-audio-api-v2#7 & WebAudio/web-audio-api-v2#1 together. Worth discussing with Chris L

@mdjp mdjp transferred this issue from WebAudio/web-audio-api-v2 Sep 23, 2021
@mdjp mdjp added the P1 WG charter deliverables; "need to have" label Sep 23, 2021
@mdjp mdjp added this to Untriaged in v.next via automation Sep 23, 2021
@mdjp mdjp moved this from Untriaged to In discussion in v.next Sep 23, 2021
@WangNianyi2001
Copy link

It's 2022, I & my college mates are making a web audio host, which require precise control of phases of source nodes.
Is this getting anywhere at all?

@braebo
Copy link

braebo commented Aug 26, 2022

What is the recommended way to apply phase offset to an oscilator node?

@hoch hoch added status: needs discussion category: new feature P2 Significant developers/implementers interest; "want to have" and removed P1 WG charter deliverables; "need to have" labels Nov 2, 2022
@hoch
Copy link
Member

hoch commented May 30, 2024

Teleconference 5/30/2024:

To continue with further standardization, the WG needs a prototype based on AudioWorkletNode. (hence needs feedback label)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
category: new feature P2 Significant developers/implementers interest; "want to have" status: needs feedback
Projects
No open projects
v.next
In discussion
Development

No branches or pull requests