When calling `osc.stop()`

, a running oscillator is stopped immediately, which can result in a nasty click. This is because the oscillator is stopped right in the middle of a cycle.

Here’s a nice tutorial on how to get rid of the click, using automation curves for smooth gain fade-outs: http://alemangui.github.io/blog//2015/12/26/ramp-to-value.html

But there’s another way: When stopping the oscillator, we could just wait for cycle completion before actually stopping it.

## Calculating the moment of next cycle completion

Let’s say, we have our sine oscillator running at a known frequency. `startTime`

is also a known value:

```
var osc = context.createOscillator();
osc.type = "sine";
osc.frequency.value = freq;
osc.connect(context.destination);
osc.start(startTime);
```

Some time later, we want to stop it, though not immediately, but right at its next cycle completion:

`osc.stop(timeAtNextCC);`

But how to calculate `timeAtNextCC`

(using `context.currentTime`

and `freq`

)?

First of all, let’s calculate the duration of one cycle:

`var cycleDuration = 1 / freq;`

Now, we can calculate how many cycles have already been completed since startTime:

```
var runningTime = context.currentTime - startTime;
var completedCycles = Math.floor(runningTime / cycleDuration);
```

From this, we know at what point in time the last cycle completion took place:

`var timeOfLastCC = startTime + (cycleDuration * completedCycles);`

Now, we can just add the duration of one cycle and we know when the next cycle will be completed:

`var timeOfNextCC = timeOfLastCC + cycleDuration;`

Now we can stop the oscillator using that value:

`osc.stop(timeOfNextCC);`

## Complete code

```
var osc = context.createOscillator();
osc.type = "sine";
osc.frequency.value = freq;
osc.connect(context.destination);
osc.start(startTime);
var cycleDuration = 1 / freq;
var runningTime = context.currentTime - startTime;
var completedCycles = Math.floor(runningTime / cycleDuration);
var timeOfLastCC = startTime + (cycleDuration * completedCycles);
var timeOfNextCC = timeOfLastCC + cycleDuration;
osc.stop(timeOfNextCC);
```

## Scheduled stopping

If we want to schedule the stop event right after calling `osc.start()`

, we do not have to use `context.currentTime`

, because we already know how long the oscillator will run (it is determined by us):

```
osc.start(startTime);
var stopTime = startTime + toneDuration;
var cycleDuration = 1 / freq;
var completedCycles = Math.floor(toneDuration / cycleDuration);
var timeOfLastCC = startTime + (cycleDuration * completedCycles);
var timeOfNextCC = timeOfLastCC + cycleDuration;
osc.stop(timeOfNextCC);
```

## Stop at zero-crossing

If we want to stop at the next zero-crossing instead, we just have to do the maths with half-cycles instead. The code looks like this:

```
var halfCycleDuration = 0.5 / freq;
var runningTime = context.currentTime - startTime;
var completedHalfCycles = Math.floor(runningTime / halfCycleDuration);
var timeOfLastZC = startTime + (halfCycleDuration * completedHalfCycles);
var timeOfNextZC = timeOfLastZC + halfCycleDuration;
osc.stop(timeOfNextZC);
```

Be aware that this zero-crossing method only works with waveforms that actually cross zero after half a cycle. This is the case with the default waveforms like sine, triangle, square, but not necessarily with custom waveforms.