grimmdude / MidiWriterJS

♬ A JavaScript library which provides an API for programmatically generating and creating expressive multi-track MIDI files and JSON.
MIT License
547 stars 58 forks source link

Unable to set track tempo #132

Closed Michael-F-Ellis closed 4 months ago

Michael-F-Ellis commented 4 months ago

Thanks for creating MidiWriter! I'm using it to create midi files from the results of parsing a simple rhythm language of my own devising. Every thing seems to be working as expected except that I can't seem to control the midi file tempo. I've tried setTempo() and addEvent(new TempoEvent(). No matter what I specify as a tempo, it plays back the rhythm correctly but at a fixed tempo of about quarter-note = 112. I've included a chunk from my code where the problem shows up.

I must be doing something silly. Any advice?

    // create and play the midi. We step through the sub-beats. At each Attack
    // event, we step through any holds that follow it, summing durations, until
    // we reach a rest or a new attack. We create a new MidiWriter.NoteEvent using
    // the summed durations and add it to the track. For rests we follow a similar process,
    // except that we add a MidiWriter.WaitEvent to the track.
    //
    // Note: MidiWriter uses ticks for durations and waits. One beat is 128
    // ticks.  so we multiply the duration by 128 to get the number of ticks,
    // round it to the nearest integer, and convert it to a string with a 'T'
    // prefix as required by MidiWriter.    

    // Create a new track
    var track = new MidiWriter.Track();
    // track.setTempo(stages.midiparms.qpm)
    track.addEvent(new MidiWriter.TempoEvent({ bpm: 240 }))

    for (let i = 0; i < stages.subBeats.length; i++) {
        let subBeat = stages.subBeats[i]
        if (subBeat.isAttack == true) {
            let j = i + 1
            let duration = subBeat.Length
            while (j < stages.subBeats.length && stages.subBeats[j].isHold == true) {
                duration += stages.subBeats[j].Length
                j++
            }
            // get the pitch and velocity
            if (subBeat.subBeatText == "f") {
                subBeat.Note = stages.midiparms.fNote
                subBeat.Velocity = stages.midiparms.fVelocity
            } else if (subBeat.subBeatText == "m") {
                subBeat.Note = stages.midiparms.mNote
                subBeat.Velocity = stages.midiparms.mVelocity
            } else if (subBeat.subBeatText == "p") {
                subBeat.Note = stages.midiparms.pNote
                subBeat.Velocity = stages.midiparms.pVelocity
            }
            let note = new MidiWriter.NoteEvent({
                pitch: subBeat.Note,
                velocity: subBeat.Velocity,
                duration: 'T' + Math.round(duration * 128),
                channel: stages.midiparms.channel
            })

            track.addEvent(note)
            i = j - 1
        } else if (subBeat.isRest == true) {
            let j = i + 1
            let duration = subBeat.Length
            while (j < stages.subBeats.length && stages.subBeats[j].type == "Hold") {
                duration += stages.subBeats[j].Length
                j++
            }
            let rest = new MidiWriter.NoteEvent({
                duration: 0,
                wait: 'T' + Math.round(duration * 128),
                channel: stages.midiparms.channel
            })
            track.addEvent(rest)
            i = j - 1
        }
    }
    // Generate a data URI for the MIDI file
    var write = new MidiWriter.Writer([track]);
    var dataUri = write.dataUri();
    // Stop any previous playback and play the new MIDI file.
    MIDIjs.stop();
    MIDIjs.play(dataUri);
dirkk0 commented 4 months ago

I can't test this write now, but from my code examples I see that this worked for me:

tracks[0] = new mw.Track()
tracks[0].setTempo(125, 0)
Michael-F-Ellis commented 4 months ago

@dirkk0 Thanks. I played around with some simple cases and confirmed that setTempo does indeed work.

In my main code, I finally figured out that the problem was not disabling the cache while running under DevTools. I'm testing with VSCode Live Server and, apparently, it doesn't reliably force a reload of modules when they change. Opening DevTools and disabling cacheing fixes the problem.

Michael-F-Ellis commented 4 months ago

Closing this.