[C API] setTempo()'s new float parameter doesn't seem to work as intended

,

I'm messing around with the sound synthesis stuff using the C API and it seems that SDK 2.1.0 change to make playdate->sound->sequence->setTempo() take a float stepsPerSecond doesn't work as intended.

Here's the simple example tune I was experimenting with:

SoundSequence* seq;

void test_music() {
    const struct playdate_sound* snd = pd->sound;

    PDSynth* square = snd->synth->newSynth();
    snd->synth->setWaveform    (square, kWaveformSquare);
    snd->synth->setAttackTime  (square, 0);
    snd->synth->setDecayTime   (square, .2f);
    snd->synth->setSustainLevel(square, .3f);
    snd->synth->setReleaseTime (square, .5f);

    PDSynthInstrument* inst = snd->instrument->newInstrument();
    snd->instrument->addVoice(inst, square, 0, 127, 0);
    snd->channel->addSource(snd->getDefaultChannel(), (SoundSource*)inst);

    seq = snd->sequence->newSequence();

    SequenceTrack* track = snd->sequence->addTrack(seq);
    snd->track->setInstrument(track, inst);

    snd->track->addNoteEvent(track, 0,  1, NOTE_C4,     1.f); // row
    snd->track->addNoteEvent(track, 4,  1, NOTE_C4,     1.f); // row
    snd->track->addNoteEvent(track, 8,  1, NOTE_C4,     1.f); // row
    snd->track->addNoteEvent(track, 11, 1, NOTE_C4 + 2, 1.f); // your
    snd->track->addNoteEvent(track, 12, 1, NOTE_C4 + 4, 1.f); // boat

    snd->sequence->setLoops(seq, 0, 15, 0);
    snd->sequence->play(seq, NULL, NULL);
}

Everything here works as expected. But, if you try to change the tempo, it breaks horribly, seemingly playing all the notes at once, and sometimes it loops, sometimes it doesn't:

    snd->sequence->setTempo(seq, 8.f); // half of the default 16

While trying to figure out what I was doing wrong, I thought I'd call getTempo(), and see what the tempo is really being set to. That's when I noticed getTempo() returns an int, while setTempo() takes a float!

    pd->system->logToConsole("%i", snd->sequence->getTempo(seq)); // 16 (default)
    snd->sequence->setTempo(seq, 8.f); // try to make it play half as fast
    pd->system->logToConsole("%i", snd->sequence->getTempo(seq)); // 1090519040 (!!)

Aha! There's the problem—the tempo is way too fast!

After some experimentation, I discovered that replacing the above code with the following does what I actually wanted it to do, i.e. set the tempo to 8 steps per second:

    int   tempo_i = 8;
    float tempo_f;
    memcpy(&tempo_f, &tempo_i, sizeof(tempo_i));
    snd->sequence->setTempo(seq, tempo_f);

So it seems that the C API setTempo() function takes in a float, but actually stores it internally as an int? Or something?

Here's the "fixed" version of the code:

SoundSequence* seq;

void test_music() {
    const struct playdate_sound* snd = pd->sound;

    PDSynth* square = snd->synth->newSynth();
    snd->synth->setWaveform    (square, kWaveformSquare);
    snd->synth->setAttackTime  (square, 0);
    snd->synth->setDecayTime   (square, .2f);
    snd->synth->setSustainLevel(square, .3f);
    snd->synth->setReleaseTime (square, .5f);

    PDSynthInstrument* inst = snd->instrument->newInstrument();
    snd->instrument->addVoice(inst, square, 0, 127, 0);
    snd->channel->addSource(snd->getDefaultChannel(), (SoundSource*)inst);

    seq = snd->sequence->newSequence();

    SequenceTrack* track = snd->sequence->addTrack(seq);
    snd->track->setInstrument(track, inst);

    snd->track->addNoteEvent(track, 0,  1, NOTE_C4,     1.f); // row
    snd->track->addNoteEvent(track, 4,  1, NOTE_C4,     1.f); // row
    snd->track->addNoteEvent(track, 8,  1, NOTE_C4,     1.f); // row
    snd->track->addNoteEvent(track, 11, 1, NOTE_C4 + 2, 1.f); // your
    snd->track->addNoteEvent(track, 12, 1, NOTE_C4 + 4, 1.f); // boat

    // begin fix
    int   tempo_i = 8;
    float tempo_f;
    memcpy(&tempo_f, &tempo_i, sizeof(tempo_i));
    snd->sequence->setTempo(seq, tempo_f);
    // end fix

    snd->sequence->setLoops(seq, 0, 15, 0);
    snd->sequence->play(seq, NULL, NULL);
}

Looks like I screwed up the version check in the compatibility code that makes that work for code compiled against the older API with the int argument, included 2.1 by accident. :disappointed: This should work correctly on 2.2, which will be rolling out any day now. And I can't believe I forgot to change getTempo's return value.. It's right there next to setTempo in the header. I've filed that and we'll get it fixed in 2.3.

Thanks for catching this!

3 Likes