GithubHelp home page GithubHelp logo

detomon / blipkit Goto Github PK

View Code? Open in Web Editor NEW
32.0 6.0 3.0 1.25 MB

C library for creating the beautiful sound of old sound chips

Home Page: https://blipkit.audio

License: Other

Shell 13.53% C 84.60% Makefile 0.54% M4 1.24% CMake 0.09%
library c sound waveform chiptune music audio sdl waveforms tremolo sound-chips

blipkit's Introduction

BlipKit

Build Status

BlipKit is a C library for creating the beautiful sound of old sound chips.

  • Generate waveforms: square, triangle, noise, sawtooth, sine and custom waveforms
  • Use an unlimited number of individual tracks
  • Use stereo output or up to 8 channels
  • Define instruments to create envelopes and other interesting effects
  • Use effects: portamento, tremolo, vibrato and some more
  • Load multi-channel samples and play them at different pitches

๐Ÿ“– Manual: http://blipkit.audio

๐ŸŽน Also consider to check out the bliplay project

Basic Example

This code demonstrates the basic steps to generate audio data of a square wave in the note A with enabled tremolo effect:

// Context object
BKContext ctx;

// Initialize context with 2 channels (stereo)
// and a sample rate of 44100 Hz
BKContextInit (& ctx, 2, 44100);

// Track object to generate the waveform
BKTrack track;

// Initialize track with square wave
// By default, the square wave has a duty cycle of 12.5%
BKTrackInit (& track, BK_SQUARE);

// Set mix and note volume
BKSetAttr (& track, BK_MASTER_VOLUME, 0.15 * BK_MAX_VOLUME);
BKSetAttr (& track, BK_VOLUME,        1.0 * BK_MAX_VOLUME);

// Set note A in octave 3
BKSetAttr (& track, BK_NOTE, BK_A_3 * BK_FINT20_UNIT);

// Enable tremolo effect
BKInt tremolo [2] = {20, 0.66 * BK_MAX_VOLUME};
BKTrackSetEffect (& track, BK_EFFECT_TREMOLO, tremolo, sizeof (tremolo));

// Attach track to context
BKTrackAttach (& track, & ctx);

// Define audio data buffer
// As there are 2 channels used, the buffer actually must be
// two times the size than number of frames are requested
BKFrame frames [512 * 2];

// Generate 512 frames e.g. as they would be requested by an audio output function
// Subsequent calls to this function generate the next requested number of frames
BKContextGenerate (& ctx, frames, 512);

// The channels are interlaced into the buffer in the form: LRLR...
// Which means that the first frame of the left channel is at frames[0],
// the first frame of the right channel at frames[1] and so on

Building the Library

First execute autogen.sh in the base directory to generate the build system:

sh ./autogen.sh

Next execute configure in the base directory:

./configure

Use the --without-sdl option if you don't want to link against SDL.

./configure --without-sdl

Then execute make to build libblipkit.a in the src directory:

make

Optionally, you may want to execute to install the library and headers on your system:

sudo make install

Building and Running Examples

All examples use SDL (http://www.libsdl.org) to output sound, so you have to install it first. Execute make examplename to build an example in the examples directory.

# in `examples`

make tone
make divider
make stereo
make scratch
make waveform
make envelope

Finally, run examples like this:

# in `examples`

./tone

License

This library is distributed under the MIT license. See LICENSE.

blipkit's People

Contributors

detomon avatar stefanfisk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

blipkit's Issues

Setting Master Clock Rate Seems to Break Divider Callbacks

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. :)

Running Separate Tracks at Different Speeds

Hello! Had a quick question regarding attaching multiple clocks to a context. I hope it's not too much trouble!
I'm trying to write a tracker similar to LSDJ in that each track can set its own play speed based on the tempo of the song. (For example, track A might run at 4 rows per beat while track B runs at 5 rows per beat)
I don't believe this is possible when only using the master clock, but I see that it's possible to attach multiple clocks to a context, so my thinking is to create one clock per track and use that to drive the divider callbacks.
Would this be the correct approach? If so, how would I set this up? If not, what would be the preferred method to accomplish this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.