detomon / BlipKit

C library for creating the beautiful sound of old sound chips
https://blipkit.audio
Other
34 stars 5 forks source link

Setting Master Clock Rate Seems to Break Divider Callbacks #2

Closed AuzFox closed 3 years ago

AuzFox commented 3 years ago

Hello! I've been using this library to write my own chiptune tracker, and things have been going pretty well, but I've noticed an issue that occurs when using BKSetPtr() to set the tick rate of the master clock. My thought process was that I could set the tick rate of the master clock to a value calculated from the desired BPM of the song, and use divider callbacks to trigger notes/fx/etc after a given number of ticks. Simply using different divider values at the default clock rate would lead to rounding errors and inaccurate BPM rates, as mentioned in the documentation/examples.

The issue is that changing the period of the master clock seems to cause the divider callbacks to only be executed once and are not called again. I've written a simplified C++ program that illustrates this here:

#include <BlipKit/BlipKit.h>
#include <SDL2/SDL.h>

// BlipKit stuff:
BKCallback bkCallback;
BKContext* context;
BKDivider* divider;
BKTrack* track;

// SDL stuff:
SDL_AudioSpec want, have;
SDL_Window* window;
SDL_AudioDeviceID device;

// program/"song" stuff:
#define NOTEDATA_LEN 16
#define EMPTY_NOTE -3
bool running = true;
int noteDataIndex = 0;
BKInt noteData[NOTEDATA_LEN] = {
  BK_G_3,
  EMPTY_NOTE,
  EMPTY_NOTE,
  EMPTY_NOTE,
  BK_E_3,
  EMPTY_NOTE,
  EMPTY_NOTE,
  EMPTY_NOTE,
  BK_D_3,
  EMPTY_NOTE,
  EMPTY_NOTE,
  EMPTY_NOTE,
  BK_C_3,
  EMPTY_NOTE,
  EMPTY_NOTE,
  EMPTY_NOTE
};

// not called by default:
void setClockRate(double ticksPerSecond) {
  BKTime time = BKTimeFromSeconds(context, 1.0 / ticksPerSecond);
  BKSetPtr(context, BK_CLOCK_PERIOD, &time, sizeof(time));
}

void sdlAudioCallback(void* userdata, Uint8* stream, int len) {
  BKUInt numFrames = len / sizeof(BKFrame) / have.channels;
  BKContextGenerate(context, (BKFrame*) stream, numFrames);
}

BKEnum dividerCallback(BKCallbackInfo* info, void* userdata) {
  BKInt note = noteData[noteDataIndex];

  if (note != EMPTY_NOTE) {
    if (note >= 0) {
      note *= BK_FINT20_UNIT;
    }
    BKSetAttr(track, BK_NOTE, note);
  }

  noteDataIndex++;
  if (noteDataIndex >= NOTEDATA_LEN) {
    noteDataIndex = 0;
  }

  return 0;
}

int main(int argc, char** argv) {
  // init SDL stuff:
  SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO);

  window = SDL_CreateWindow(
    "BlipKit Divider Test",
    SDL_WINDOWPOS_CENTERED,
    SDL_WINDOWPOS_CENTERED,
    400,
    400,
    0
  );

  want.freq = 44100;
  want.format = AUDIO_S16SYS;
  want.channels = 2;
  want.samples = 1024;
  want.callback = sdlAudioCallback;
  want.userdata = nullptr;
  device = SDL_OpenAudioDevice(
    nullptr,
    0,
    &want,
    &have,
    SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE
  );

  // init BlipKit stuff:
  BKContextAlloc(&context, have.channels, have.freq);

  BKTrackAlloc(&track, BK_SQUARE);
  BKSetAttr(track, BK_MASTER_VOLUME, (1.0 / 6) * BK_MAX_VOLUME);
  BKSetAttr(track, BK_VOLUME, 0.50 * BK_MAX_VOLUME);

  BKTrackAttach(track, context);

  bkCallback.func = dividerCallback;
  bkCallback.userInfo = nullptr;
  BKDividerAlloc(&divider, 6, &bkCallback);

  BKContextAttachDivider(context, divider, BK_CLOCK_TYPE_BEAT);

  SDL_PauseAudioDevice(device, 0);

  // main loop:
  SDL_Event e;
  while (running) {
    while (SDL_PollEvent(&e)) {
      switch (e.type) {
        case SDL_QUIT:
          running = false;
          break;
        default:
          break;
      }
    }
    SDL_Delay(2);
  }

  SDL_CloseAudioDevice(device);
  SDL_DestroyWindow(window);
  SDL_Quit();
  BKDispose(track);
  BKDispose(divider);
  BKDispose(context);

  return 0;
}

When I compile and run the above code as is, everything works as intended and a series of 4 notes are played repeatedly. But when a call to setClockRate() is made any time after initialization, only the first note is played and held indefinitely.

I've even tried calling setClockRate(240.0) to set the rate to the default value, and the same problem still occurred.

Apologies if this is just a case of user error, and I understand this project hasn't seen much activity lately, but any help would be appreciated. :)

detomon commented 3 years ago

Hello @AuzFox

You did everything correct. The issue was, that the clock uses two different version on 32-bit and 64-bit systems. Compiling the library via make did work correctly. But when the header was included directly (#include <BlipKit/BlipKit.h>) it always recognised the system as 32-bit, which interprets the BKTime struct differently. The latest commit fixes this issue.

Great to see that someone uses my library :) Feel free to ask any question.