晕厥和人触摸
#javascript #webaudio #retrocomputing

我要实现的最后两个效果与注释时间有关。第一个称为Retrigger Note,可用于从一定数量的刻度后从一开始就重新启动音符,并在一行中继续重复。第二个称为“延迟音符”,用于将音符的开始延迟一定数量。这两种效果都用于为音乐增添更多感觉,并经常添加到鼓声或低音音符中。

  • 效果0'arpeggio (完成)
  • 效果1 - 滑动 (完成)
  • 效果2 - 滑动 (完成)
  • 效果3 - 音portamento (完成)
  • 效果4颤音 (完成)
  • 效果5 –音调门 +音量幻灯片 (完成)
  • <> <>效应6 vibrato +卷幻灯片
  • 效果9样品偏移 (完成)
  • 效果a卷幻灯片 (完成)
  • 效果câset卷 (完成)
  • 效果D图案断裂(完成)
  • 效果e9 retigger Note
  • 效果d延迟注意
  • 效果f - 设置速度 (完成)

效果e9 tracigert注释

要重新验证注释,我需要检查每个tick是否由效果参数中指定的tick数除外。如果是这样,我可以通过将样本位置设置为0:
从一开始就开始样本

    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;

效果ed延迟笔记

使用延迟音符效果时,在指定数量的刻度通过之前,不得中断当前的音符。为了实现这一目标,我需要首先准备仪器,音量和周期的更改中的单独变量,并且仅在延迟通过时才应用它们。这使得play(note)方法更加复杂,但仍然很可读:

    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;
        }
    }

此更改后,卷控件有点损坏,但是仅需要对effect方法进行一个小更改来修复它:

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

为了最终实施延迟,我需要对performTickplayeffect方法进行一些小更改:

    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;
            // ...
        }
    }

修复一个潮汐错误

从头到尾听几首歌时,我注意到有些歌曲会在最后一遍又一遍地重复一次。这是因为我没有在任何地方存储歌曲的长度,因此玩家不知道什么时候停止。要解决此问题,我需要在Mod类中添加length成员变量,然后在PlayerWorklet类的nextRow方法中进行检查:

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

        // The rest of the class is unchanged
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;
        }

结论

这样,玩家现在就可以播放我为支持的所有歌曲。我知道我还没有实施很多效果,但是到目前为止,我对进步感到满意。我对MOD格式的内部工作学历了很多,而且我也学到了很多有关Web音频API的信息。我期待实施更多效果,但我现在要休息一下。

制作该播放器的主要原因是在我的JavaScript remake of Arte by Sanity中使用Artsy。我将使用播放器进行两件事:首先,我将使用它在演示中播放音乐。其次,我将其用作定时源。因此,我将需要实现一种方法,以使播放器在音乐驱动的某些时间点触发事件。这将是下一篇文章的重点,这将是本系列的最后一篇。我希望在大约一周内发布它。在那之前,快乐的改装!

您可以在atornblad.github.io/js-mod-player中尝试此解决方案。

该代码的最新版本始终在the GitHub repository中可用。