Syncopation and a human touch
Tags: webaudio, javascript, retrocomputing
Published: 2023-06-16 05:45:00

The last two effects that I want to implement have to do with note timing. The first is called Retrigger note, and can be used to restart the note from the beginning after a certain number of ticks, and keep repeating it during one row. The second is called Delay note, and is used to delay the start of a note by a certain number of ticks. Both of these effects are used to add some more feeling to the music, and are often added to drum sounds or bass notes.

- ~~Effect 0 – Arpeggio~~ _(done)_
- ~~Effect 1 – Slide up~~ _(done)_
- ~~Effect 2 – Slide down~~ _(done)_
- ~~Effect 3 – Tone portamento~~ _(done)_
- ~~Effect 4 – Vibrato~~ _(done)_
- ~~Effect 5 – Tone portamento + volume slide~~ _(done)_
- ~~Effect 6 – Vibrato + volume slide~~
- ~~Effect 9 – Sample offset~~ _(done)_
- ~~Effect A – Volume slide~~ _(done)_
- ~~Effect C – Set volume~~ _(done)_
- ~~Effect D – Pattern break~~ _(done)_
- **Effect E9 – Retrigger note** 
- **Effect ED – Delay note** 
- ~~Effect F – Set speed~~ _(done)_

## Effect E9 – Retrigger note

To retrigger a note, I need to check each tick if it is divisible by the number of ticks specified in the effect parameter. When it is, I can start the sample from the beginning again by setting the sample position to 0:

```javascript
    performTick() {
        if (this.volumeSlide && this.worklet.tick > 0) {
            // Unchanged
        }

        if (this.vibrato) {
            // Unchanged
        }
        else if (this.periodDelta) {
            // Unchanged
        }
        else if (this.arpeggio) {
            // Unchanged
        }
        else if (this.retrigger && (this.worklet.tick % this.retrigger) == 0) {
            this.sampleIndex = 0;
        }
        // Rest of performTick unchanged
    }

    effect(raw) {
        // Add one line of initialization:
        this.retrigger = false;

        // Everything unchanged apart from one new case in the switch:
        switch (id) {
            // ...
            case RETRIGGER_NOTE:
                this.retrigger = data;
                break;
```

## Effect ED – Delay note

When the Delay note effect is used, the currently playing note must not be interrupted until the specified number of ticks have passed. To implement this, I need to first prepare by storing the changes of instrument, volume and period in separate variables, and only apply them when the delay has passed. This makes the `play(note)` method a bit more complicated, but it is still quite readable:

```javascript
    play(note) {
        // Store the changes for later:
        this.setInstrument = false;
        this.setVolume = false;
        this.setPeriod = false;

        if (note.instrument) {
            this.setInstrument = this.worklet.mod.instruments[note.instrument - 1];
            this.setVolume = this.setInstrument.volume;
        }

        this.setSampleIndex = false;
        this.setCurrentPeriod = false;

        if (note.period) {
            this.setPeriod = note.period - (this.setInstrument || this.instrument).finetune;
            this.setCurrentPeriod = true;
            this.setSampleIndex = 0;
        }

        this.effect(note.effect);

        if (this.setInstrument) {
            this.instrument = this.setInstrument;
        }
        if (this.setVolume !== false) {
            this.volume = this.setVolume;
            this.currentVolume = this.volume;
        }

        if (this.setPeriod) {
            this.period = this.setPeriod;
        }

        if (this.setCurrentPeriod) {
            this.currentPeriod = this.period;
        }

        if (this.setSampleIndex !== false) {
            this.sampleIndex = this.setSampleIndex;
        }
    }
```

After this change, the volume control is a bit broken, but just a small change in the `effect` method is needed to fix it:

```javascript
    effect(raw) {
        // ...
        case SET_VOLUME:
            this.setVolume = data;
            break;
        // ...
    }
```

To finally implement the delay, I need to do some small changes to the `performTick`, `play` and `effect` methods:

```javascript
    performTick() {
        if (this.volumeSlide && this.worklet.tick > 0) {
            // Unchanged
        }

        if (this.vibrato) {
            // Unchanged
        }
        else if (this.periodDelta) {
            // Unchanged
        }
        else if (this.arpeggio) {
            // Unchanged
        }
        else if (this.retrigger && (this.worklet.tick % this.retrigger) == 0) {
            // Unchanged
        }
        else if (this.delayNote === this.worklet.tick) {
            this.instrument = this.setInstrument;
            this.volume = this.setVolume;
            this.currentVolume = this.volume;
            this.period = this.setPeriod;
            this.currentPeriod = this.period;
            this.sampleIndex = 0;
        }

        // Rest of method unchanged
    }

    play(note) {
        this.setInstrument = false;
        this.setVolume = false;
        this.setPeriod = false;
        this.delayNote = false;

        if (note.instrument) {
            // Unchanged
        }

        this.setSampleIndex = false;
        this.setCurrentPeriod = false;

        if (note.period) {
            // Unchanged
        }

        this.effect(note.effect);

        // If note is delayed, do nothing right now
        if (this.delayNote) return;

        // Rest of method unchanged
    }

    effect(raw) {
        // Add one more line of initialization:
        this.delayNote = false;

        // Everything unchanged apart from one new case in the switch, and
        // a small but important change to SAMPLE_OFFSET:
        switch (id) {
            // ...
            case DELAY_NOTE:
                this.delayNote = data;
                break;
            // ...
            case SAMPLE_OFFSET:
                this.setSampleIndex = data * 256;
                break;
            // ...
        }
    }
```

## Fixing an end-of-song bug

When listening to a few songs from start to end, I noticed that some songs would repeat a single pattern over and over again at the end. This is because I haven't stored the length of the song anywhere, so the player doesn't know when to stop. To fix this, I need to add a `length` member variable to the `Mod` class, and check it in the `nextRow` method of the `PlayerWorklet` class:

```javascript
export class Mod {
    constructor(modfile) {
        // Store the song length
        this.length = new Uint8Array(modfile, 950, 1)[0];

        // The rest of the class is unchanged
```


```javascript
class PlayerWorklet extends AudioWorkletProcessor {
    // ...

    nextRow() {
        ++this.rowIndex;
        if (this.patternBreak !== false) {
            this.rowIndex = this.patternBreak;
            ++this.position;
            this.patternBreak = false;
        }
        else if (this.rowIndex == 64) {
            this.rowIndex = 0;
            ++this.position;
        }
        if (this.position >= this.mod.length) {
            this.position = 0;
        }
```

## Conclusion

With this, the player is now able to play all the songs that I set out to support. I know there are lot of effects that I haven't implemented yet, but I'm happy with the progress so far. I've learned a lot about the inner workings of the MOD format, and I've also learned a lot about the Web Audio API. I'm looking forward to implementing more effects, but I'll take a break from that for now.

The main reason for making this player is to use it in [**Artsy**](https://atornblad.github.io/artsy), my [JavaScript remake of Arte by Sanity](/modernizing-artsy). I will use the player for two things: First, I will use it to play the music in the demo. Second, I will use it as a timing source. Because of this, I will need to implement a way to make the player trigger events at certain points in time, driven by the music. That will be the focus of the next article, which will be the final one in this series. I hope to publish it in about a week. Until then, happy modding!

You can try this solution at [atornblad.github.io/js-mod-player](https://atornblad.github.io/js-mod-player).

The latest version of the code is always available in [the GitHub repository](https://github.com/atornblad/js-mod-player).