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

Use WebAudio API to loop music (to guarantee gapless loop) #143

Open
Mouradif opened this issue Jun 24, 2021 · 1 comment
Open

Use WebAudio API to loop music (to guarantee gapless loop) #143

Mouradif opened this issue Jun 24, 2021 · 1 comment

Comments

@Mouradif
Copy link

I am switching to Monogatari from Renpy and I was extremely surprised to see that "looped" music has that very characteristic gap from basic loops in vanilla HTML5 Audio element with a "loop" attribute.

I suggest this function is updated to use Web Audio API instead and guarantee a perfect loop, accurately down to the single sample level.

I will try to implement it as a custom loop action instead of using play music for my project for now

@Mouradif
Copy link
Author

I just realized I never posted my loop action. Here it is:

class Loop extends Monogatari.Action {
  static matchString ([action]) {
    return action === 'loop';
  }

  async decode() {
    this.audioData = await this.actx.decodeAudioData(this.arrayBuffer);
  }

  playLoop() {
    this.srcNode = this.actx.createBufferSource();
    this.srcNode.buffer = this.audioData;
    this.srcNode.connect(this.gainNode);
    this.srcNode.loop = true;
    this.srcNode.volume = 1;
    this.srcNode.start();
  }

  fade(time) {
    this.gainNode.gain.setValueAtTime(0, this.actx.currentTime);
    this.gainNode.gain.exponentialRampToValueAtTime(1, this.actx.currentTime + time);
  }

  constructor([loop, track, ...args]) {
    super();
    console.log(args);
    this.actx = new (AudioContext || webkitAudioContext)();
    this.gainNode = this.actx.createGain();
    this.gainNode.gain.setValueAtTime(1, this.actx.currentTime);
    this.gainNode.connect(this.actx.destination);
    this.audioData = null;
    this.arrayBuffer = null;
    this.srcNode = null;
    this.type = 'music';
    this.track = track;
    this.args = args;
    for (let i = 0; i < args.length; i++) {
      switch (args[i]) {
        case 'fade':
          i++;
          this.fade(args[i]);
          break;
        default:
          console.log('unknown arg', args[i]);
      }
    }
    this.media = this.engine.asset (this.type, this.track);
  }

  async willApply() {
    await fetch(`assets/${this.type}/${this.media}`)
      .then((res) => {
        return res.arrayBuffer()
      })
      .then(ar => {
        this.arrayBuffer = ar;
        return this.decode();
      });
    return super.willApply();
  }

  apply() {
    this.playLoop();
    return super.apply();
  }

  async didApply() {
    return {advance: true};
  }
}

Loop.id = 'Loop';

I'm a bit surprised by the lack of interest in this issue to be honest, I was convinced that people would be interested in looping music without the annoying gap between each iteration. This feature should be native to the game engine

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