Jump to content
  • Advertisement
Sign in to follow this  

[OpenAL] Music Sequencing

This topic is 2441 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello,

I'm working on creating a sound engine based on OpenAL which will play both sound effects and music. The music can either be a straightforward Ogg Vorbis file or a sort of Amiga-style module (details yet to be defined). I'm doing this to allow for easier control of dynamic music in-game and because I want to encourage end users to tinker with the assets (be they graphics or music).

For the music, either samples can be provided or an internal wave generator (sine, square, triangle) can be used for channel instruments. This would facilitate retro chiptunes without storing (in some cases) megabytes of MP3s.

I'll admit I'm a newcomer to music programming in general, I can play Vorbis files but I currently shove the entire decompressed file into memory for playback(!). Streaming is my ultimate objective but I'm having trouble understanding how I can update something that is playing (buffers are tricky :().

To start off, I have created a simple sequencer that renders individual seconds of musical notes as sine wave tones, plays them and waits until playback is finished before continuing the loop. This results in stuttery stutteriness, but again I don't know how best to handle buffering so that I can make changes to what is being played.

The notes are for now stored in a simple (key, duration) format, with key indexing into an array of 84 frequencies corresponding to C1-B7 in semitone increments.

Below is what I've done so far, with a hard-coded two-channel tune until I've worked out a file format. See if you can recognize it. :wink:

Can anyone help with this or give me pointers on how best to achieve my stated objectives?
//C1 = dKeys[0]
//C2 = dKeys[12]
//C3 = dKeys[24]
//C4 = dKeys[36]
//C5 = dKeys[48]
//C6 = dKeys[60]
//C7 = dKeys[72]
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <al\al.h>
#include <al\alc.h>

typedef struct {
char cIndex; // -1 = pause, otherwise index into dKeys
unsigned char ucDuration; // measured in eighths of a second (max 15)
} t_note;

typedef struct {
unsigned int uiFreq; // sampling rate?
unsigned long ulLength; // length *in bytes*
short pBuffer[1];
} t_sound;

typedef struct {
int fFlags; /* First four bits:- 0 = off, 1 = sine, 2 = square, 3 = triangle, 4 = sample
next two bits:- 8 = decay, 16 = sustain, 24 = release */
t_note *pNote; // current note to play
unsigned short usAttack; // attack duration (samples)
unsigned short usDecay; // decay duration (sample)
unsigned char ucSustain; // sustain volume (divided by 128)
unsigned short usRelease; // release duration (samples)
t_sound *pSound; // sound buffer
unsigned int uiPos; // current position in buffer (samples)
unsigned short usVolume; // volume of channel (max 32767)
} t_channel;

#define NUMCHANNELS 2

const double c_TWOPI = 2 * 3.1415926535897931;
const double c_SEMITONE = 1.059463094359; // approx. twelfth root of 2

// Render note into channel buffer as per channel parameters
void render_note(t_channel *pChannel, double *pdKeys)
{
unsigned int ui, uiDuration, uiStatePos;
unsigned short usAmplitude;

uiDuration = (pChannel->pNote->ucDuration & 15) * 6000;
if (pChannel->uiPos + uiDuration > pChannel->pSound->uiFreq)
uiDuration = pChannel->pSound->uiFreq - pChannel->uiPos;

if (pChannel->pNote->cIndex == -1)
{
memset(&pChannel->pSound->pBuffer[pChannel->uiPos], 0, uiDuration * 2);
pChannel->uiPos += uiDuration;
}
else
{
uiStatePos = 0;
usAmplitude = 0;
for (ui=0;ui<uiDuration;ui++)
{
if ((pChannel->fFlags & 24) == 0) // attack
{
if (ui < pChannel->usAttack)
usAmplitude += pChannel->usVolume / pChannel->usAttack;
else
{
uiStatePos = ui;
pChannel->fFlags |= 8;
}

}

if ((pChannel->fFlags & 24) == 8) // decay
{
if (ui - uiStatePos < pChannel->usDecay)
usAmplitude -= (pChannel->usVolume - (pChannel->ucSustain * 128)) / pChannel->usDecay;
else
{
uiStatePos = ui;
pChannel->fFlags = (pChannel->fFlags & ~8) | 16;
}

}

if ((pChannel->fFlags & 24) == 16) // sustain
{
if (uiDuration - pChannel->usRelease < ui)
{
uiStatePos = ui;
pChannel->fFlags |= 8;
}

}

if ((pChannel->fFlags & 24) == 24) // release
{
if (ui- uiStatePos < pChannel->usRelease)
usAmplitude -= (pChannel->ucSustain * 128) / pChannel->usDecay;
else
usAmplitude = 0;

}

if (usAmplitude == 0)
pChannel->pSound->pBuffer[pChannel->uiPos++] = 0;
else
{
if ((pChannel->fFlags & 3) == 1)
pChannel->pSound->pBuffer[pChannel->uiPos++] = (short)(sin(ui * pdKeys[pChannel->pNote->cIndex]) * usAmplitude);
else if ((pChannel->fFlags & 3) == 2)
pChannel->pSound->pBuffer[pChannel->uiPos++] = ((sin(ui * pdKeys[pChannel->pNote->cIndex]) >= 0) ? 1 : -1) * usAmplitude;

}

}

}

pChannel->pNote->ucDuration -= uiDuration / 6000;
if (pChannel->pNote->ucDuration == 0)
pChannel->pNote = NULL;

}

// initialise channel members
int init_channels(unsigned char nChannels, t_channel *pChannels)
{
unsigned short us;

for (us=0;us<nChannels;us++)
{
pChannels[us].fFlags = 1;
pChannels[us].pNote = NULL;
pChannels[us].usVolume = 16384;
pChannels[us].usAttack = 480;
pChannels[us].usDecay = 480;
pChannels[us].ucSustain = 128;
pChannels[us].usRelease = 480;
pChannels[us].pSound = malloc(sizeof(*pChannels[us].pSound) + (47999 * sizeof(short)));
if (!pChannels[us].pSound)
return -1;

pChannels[us].pSound->uiFreq = 48000;
pChannels[us].pSound->ulLength = 96000;
}

return 0;
}

int main(int argc, char **argv)
{
double dKeys[84];
unsigned char uc;
unsigned int ui, ui2;
ALCcontext *alcContext;
ALCdevice *alcDevice;
ALint aliState;
ALuint aluBuffIDs[NUMCHANNELS], aluSrcIDs[NUMCHANNELS];
t_channel channels[NUMCHANNELS];
t_note NotesCh1[39] = {{52, 2}, {52, 2}, {-1, 1}, {52, 2}, {-1, 1}, {48, 2}, {52, 2}, {-1, 1}, {55, 2}, {-1, 6}, {44, 2}, {-1, 6}, {48, 2}, {-1, 4}, {44, 2}, {-1, 4},
{41, 2}, {-1, 4}, {45, 2}, {-1, 2}, {47, 2}, {-1, 2}, {46, 2}, {45, 2}, {-1, 2}, {43, 4}, {52, 2}, {55, 2}, {57, 2}, {-1, 2}, {53, 2}, {55, 2},
{-1, 2}, {52, 2}, {-1, 2}, {48, 2}, {50, 2}, {47, 2}, {-1, 4}};
t_note NotesCh2[37] = {{30, 2}, {29, 2}, {-1, 1}, {29, 2}, {-1, 1}, {30, 2}, {29, 2}, {-1, 1}, {35, 2}, {-1, 14}, {40, 2}, {-1, 4}, {36, 2}, {-1, 4}, {31, 2}, {-1, 4},
{36, 2}, {-1, 2}, {38, 2}, {-1, 2}, {37, 2}, {36, 2}, {-1, 2}, {36, 4}, {43, 2}, {47, 2}, {48, 2}, {-1, 2}, {45, 2}, {47, 2}, {-1, 2}, {45, 2},
{-1, 2}, {40, 2}, {41, 2}, {38, 2}, {-1, 4}};

// initialise A440
dKeys[45] = (c_TWOPI * 440) / 48000;
for (ui=44;ui>0;ui--)
dKeys[ui] = dKeys[ui + 1] / c_SEMITONE;

dKeys[0] = dKeys[1] / c_SEMITONE;
for (ui=46;ui<84;ui++)
dKeys[ui] = dKeys[ui - 1] * c_SEMITONE;

alcDevice = alcOpenDevice(NULL);
if (!alcDevice)
return -1;

alcContext = alcCreateContext(alcDevice, NULL);
if (!alcContext)
return -1;

alcMakeContextCurrent(alcContext);
alGenSources(NUMCHANNELS, aluSrcIDs);
if (alGetError() != AL_NO_ERROR)
{
alcMakeContextCurrent(NULL);
alcDestroyContext(alcContext);
alcCloseDevice(alcDevice);
return -1;
}

alGenBuffers(NUMCHANNELS, aluBuffIDs);
if (alGetError() != AL_NO_ERROR)
{
alDeleteSources(NUMCHANNELS, aluSrcIDs);
alcMakeContextCurrent(NULL);
alcDestroyContext(alcContext);
alcCloseDevice(alcDevice);
return -1;
}

if (init_channels(NUMCHANNELS, channels) == -1)
{
alDeleteBuffers(NUMCHANNELS, aluBuffIDs);
alDeleteSources(NUMCHANNELS, aluSrcIDs);
alcMakeContextCurrent(NULL);
alcDestroyContext(alcContext);
alcCloseDevice(alcDevice);
return -1;
}

ui = 0;
ui2 = 0;
while (ui < 39 && ui2 < 37)
{
for (uc=0;uc<NUMCHANNELS;uc++)
{
channels[uc].uiPos = 0;
while (channels[uc].uiPos < channels[uc].pSound->uiFreq)
{
if (!channels[uc].pNote)
{
if (uc == 0)
{
if (ui == 39)
break;
else
channels[uc].pNote = &NotesCh1[ui++];

}
else
{
if (ui2 == 37)
break;
else
channels[uc].pNote = &NotesCh2[ui2++];

}

channels[uc].fFlags &= ~24;
}

render_note(&channels[uc], dKeys);
}

alBufferData(aluBuffIDs[uc], AL_FORMAT_MONO16, channels[uc].pSound->pBuffer, channels[uc].uiPos * 2, channels[uc].pSound->uiFreq);
alSourcei(aluSrcIDs[uc], AL_BUFFER, aluBuffIDs[uc]);
}

alSourcePlayv(NUMCHANNELS, aluSrcIDs);
do {
alGetSourcei(aluSrcIDs[0], AL_SOURCE_STATE, &aliState);
} while (aliState == AL_PLAYING);
for (uc=0;uc<NUMCHANNELS;uc++)
alSourcei(aluSrcIDs[uc], AL_BUFFER, 0);

}

for (ui=0;ui<NUMCHANNELS;ui++)
free(channels[ui].pSound);

alDeleteBuffers(NUMCHANNELS, aluBuffIDs);
alDeleteSources(NUMCHANNELS, aluSrcIDs);
alcMakeContextCurrent(NULL);
alcDestroyContext(alcContext);
alcCloseDevice(alcDevice);
return 0;
}

Share this post


Link to post
Share on other sites
Advertisement
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!