Tracks generate waveforms or play samples. Instruments can be used to create envelopes and effects can be enabled to animate certain track attributes.

// The track object
BKTrack track;

// Initialize a track object with a square wave
BKTrackInit (& track, BK_SQUARE);

Volume

The attribute BK_MASTER_VOLUME defines the volume at which the audio data is written into the audio buffer (mix volume). It is 0 after initialization and has to be set explicitly. BK_VOLUME is used to set the loudness of notes and is at its maximum after initialization.

// Set master volume to 20%
BKTrackSetAttr (& track, BK_MASTER_VOLUME, 0.2 * BK_MAX_VOLUME);

// Set volume to 50%
// The final volume is 10%
BKTrackSetAttr (& track, BK_VOLUME, 0.5 * BK_MAX_VOLUME);

Actually, the two values are interchangable, as they are multiplied by each other.

Playing Notes

A track can only play one note at once. The BK_NOTE attribute sets the track’s note which is a fixed-point number that allows to use fractional notes.

// Set note C of octave 3
BKSetAttr (& track, BK_NOTE, BK_C_3 * BK_FINT20_UNIT);

The constants BK_C_0 to BK_C_8 are values between 0 and 96 and used for readability. Of course, their equivalent integer values can be used as well.

To unset the note, it can either be set to BK_NOTE_RELEASE or BK_NOTE_MUTE. The latter has a different behaviour when using instruments; it does not play the envelopes’ release part and mutes the note immediately.

The release constants BK_NOTE_RELEASE and BK_NOTE_MUTE must not be multiplied by BK_FINT20_UNIT.

// Release note
BKTrackSetAttr (& track, BK_NOTE, BK_NOTE_RELEASE);

Generating Audio Data

Audio data is generated by requesting an arbitary number of frames from the generator function BKContextGenerate, which fills a provided buffer with the requested frames, or its variant BKContextGenerateToTime, which outputs the frames to a provided callback.

A call to one of this functions advances each attached track’s time by running its render function. The generated audio data of each tracks is then merged into the audio buffer. Subsequent calls to the generator functions generate the next requested number of frames.

// Define an audio data buffer
// As there are two 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 generates the next requested number of frames
BKContextGenerate (& ctx, frames, 512);

Multiple channels are interleaved into the provided buffer. 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.

Creating a Beat

The context object runs a master clock which has a default tick rate of 240 Hz. Ticks are the heartbeat for effects, instruments and arpeggio notes.

It is recommended to use a beat that is synchronized with the master clock. For this purpose, there are BKDivider objects. They reduce the tick rate of a clock by a given factor and call a provided callback at the specified interval.

For example, initializing a divider object with a value of 24, will call its callback at every 24th tick of the master clock.

// Divider object
BKDivider divider;

// The callback struct is only used for initializing the divider
BKDividerCallback callback = {
	.func     = dividerCallback,
	.userInfo = NULL,
};

// Initialize divider object with a value of 24
// The callback is called every 24th tick of the master clock
BKDividerInit (& divider, 24, & callback);

// Attach the divider to the context's master clock
// When frames are generated, the callback is called in the defined interval
BKContextAttachDivider (& ctx, & divider, BK_CLOCK_TYPE_BEAT);

The callback function receives two arguments: the first one is an information struct that also contains the divider object, the second one is the user defined pointer given at initialization. The function should always return 0, as there are no other values defined at the moment.

// This function is called every 24th tick of the master clock
BKEnum dividerCallback (BKCallbackInfo * info, void * userInfo)
{
	// Update track attributes ...

	// The divider interval can also be set permanently to another value
	// The next call of the function will be in 20 ticks
	info -> divider = 20;

	return 0;
}

Every clock and divider also has a 0th tick at the very beginning, which can be used to initialize the track’s values for the first time.

Attributes

The attributes of the track object.

BK_NOTE

Sets the note. The value is a fixed-point number that ranges from BK_MIN_NOTE to BK_MAX_NOTE.

// Set note C of octave 3
BKSetAttr (& track, BK_NOTE, BK_C_3 * BK_FINT20_UNIT);

To unset the note, it can either be set to BK_NOTE_RELEASE or BK_NOTE_MUTE. The latter has a different behaviour when using instruments; it does not play the envelopes' release part and mutes the note immediately.

The release constants BK_NOTE_RELEASE and BK_NOTE_MUTE must not be multiplied by BK_FINT20_UNIT.

// Release note
BKTrackSetAttr (& track, BK_NOTE, BK_NOTE_RELEASE);
BK_WAVEFORM

Sets a waveform.

// Set sawtooth waveform
BKSetAttr (& track, BK_WAVEFORM, BK_SAWTOOTH);

A custom waveform can be set with the pointer setter function:

// Waveform declared elsewhere
BKData * waveform = ...;

// Set custom waveform
BKSetPtr (& track, BK_WAVEFORM, waveform, 0);
BK_DUTY_CYCLE

Sets the duty cycle of the square wave BK_SQUARE. The value ranges from 1 to 15. Default is 4.

// Set a duty cycle of 50%
BKSetAttr (& track, BK_DUTY_CYCLE, 8);
BK_VOLUME

Sets the note volume. The value ranges from 0 to BK_MAX_VOLUME.

// Set a volume of 75%
BKSetAttr (& track, BK_VOLUME, 0.75 * BK_MAX_VOLUME);
BK_MASTER_VOLUME

Sets the mix volume. The value ranges from 0 to BK_MAX_VOLUME.

// Set a volume of 15%
BKSetAttr (& track, BK_MASTER_VOLUME, 0.15 * BK_MAX_VOLUME);
BK_PANNING

Pans the volume to the left if the value is negative, or to the right if the value is positive, respectively. This attribute has only an effect if the context was initialized with exactly 2 channels. The value ranges from -BK_MAX_VOLUME to BK_MAX_VOLUME.

// Pan 25% to the left
BKSetAttr (& track, BK_PANNING, -0.25 * BK_MAX_VOLUME);
BK_INSTRUMENT

Sets the track's instrument.

// Instrument object declared elsewhere
BKInstrument * instrument = ...;

// Set instrument
BKSetPtr (& track, BK_INSTRUMENT, instrument);
BK_SAMPLE

Sets the sample to play. It is required, that the track is already attached to a context, otherwise BKSetPtr will return BK_INVALID_STATE when setting the sample.

// Sample object declared elsewhere
BKData * sample = ...;

// Set sample
BKSetPtr (& track, BK_SAMPLE, sample);
BK_PITCH

Sets the track's pitch.

Permanently raises or lowers the track's pitch. The value is added to the BK_NOTE attribute. Default is 0.

// Raise the pitch by on octave
BKSetAttr (& track, BK_PITCH, 12 * BK_FINT20_UNIT);
BK_PHASE_WRAP

Sets the number of phases at which the waveform is reset.

This is especially interesting for the noise waveform BK_NOISE. Other waveforms may sound distorted.

The value is reset when a new waveform is set.

// Set noise waveform
BKSetAttr (& track, BK_WAVEFORM, BK_NOISE);

// Noise wave resets after 128 frames
BKSetAttr (& track, BK_PHASE_WRAP, 128);
BK_SAMPLE_RANGE

Sets the frame range of the sample to play.

This is an array of two integers, in which the first value defines the start offset, and the second one the end offset. The end offset itself is not included in the range.

If an offset is negative, it will be relative to the sample end + 1. So a value of -1 for the range end is sample length itself.

The range is reset when a new sample is set.

BKInt range [2] = {6400, 12600};

// Set sample range
BKSetPtr (& track, BK_SAMPLE_RANGE, range, sizeof (range));

The sample is played backwards by switching the two range offsets:

BKInt range [2] = {12600, 6400};

// Set reversed sample range
BKSetPtr (& track, BK_SAMPLE_RANGE, range, sizeof (range));

BKInt range2 [2] = {-1, 0};

// Reverse the whole sample
BKSetPtr (& track, BK_SAMPLE_RANGE, range2, sizeof (range2));
BK_SAMPLE_REPEAT

Loops the sample indefinitely as long as the note is set. If set to BK_REPEAT, the sample is reset to the range start when it ends. If set to BK_PALINDROME, the play direction is reversed when the range boundary is reached. Default is BK_NO_REPEAT.

// Loop sample
BKSetAttr (& track, BK_SAMPLE_REPEAT, BK_REPEAT);

// Loop sample back and forth
BKSetAttr (& track, BK_SAMPLE_REPEAT, BK_PALINDROME);
BK_SAMPLE_PITCH

Sets the sample's pitch. The value is added to the BK_NOTE attribute. Default is 0.

When setting a new sample object, the BK_SAMPLE_PITCH attribute is copied to the track attribute.

// Set sample pitch
BKSetAttr (& track, BK_SAMPLE_PITCH, -0.25 * BK_FINT20_UNIT);