Skip to content

Instantly share code, notes, and snippets.

@Slipyx
Last active March 18, 2021 10:50
Show Gist options
  • Save Slipyx/4dc65a3978492c18f53cf1efa7fb81b0 to your computer and use it in GitHub Desktop.
Save Slipyx/4dc65a3978492c18f53cf1efa7fb81b0 to your computer and use it in GitHub Desktop.
Playing OGG music using stb_vorbis and OpenAL, with real-time control using ncurses. Based on code from: https://gist.github.com/Oddity0x0/965399
#include <AL/al.h>
#include <AL/alc.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
//#include <unistd.h>
#define STB_VORBIS_NO_PUSHDATA_API
#include "stb_vorbis.c"
#include <ncurses.h>
typedef enum {
MUS_STATE_STOPPED = 0,
MUS_STATE_PLAYING = 1,
MUS_STATE_PAUSED = 2
} e_mus_state;
typedef struct {
stb_vorbis* stream;
stb_vorbis_info info;
ALuint buffers[2];
ALuint source;
ALenum format;
//size_t samplesLeft;
bool bLoop;
// current state
e_mus_state state;
// private in-memory ogg data
unsigned char* _oggdata;
unsigned int _ogglen;
} mus_t;
// just one global music please
mus_t g_music = { 0 };
// public
bool Mus_Open( mus_t* mus, const char* file );
void Mus_Close( mus_t* mus );
bool Mus_Update( mus_t* mus );
void Mus_Play( mus_t* mus );
void Mus_Pause( mus_t* mus );
void Mus_Stop( mus_t* mus );
// return true if can stream more
static bool Mus_Stream( mus_t* mus, ALuint buffer ) {
if ( mus->stream == NULL ) return false;
#define MUS_BUFFER_SIZE (4098 * 32)
ALshort pcm[MUS_BUFFER_SIZE]; // 256KB
int size = 0, result = 0;
while ( size < MUS_BUFFER_SIZE ) {
result = stb_vorbis_get_samples_short_interleaved( mus->stream, mus->info.channels,
pcm + size, MUS_BUFFER_SIZE - size );
if ( result > 0 )
size += result * mus->info.channels;
else break;
}
if ( size == 0 ) return false;
alBufferData( buffer, mus->format, pcm, size * sizeof(ALshort), mus->info.sample_rate );
//mus->samplesLeft -= size;
#undef MUS_BUFFER_SIZE
return true;
}
// initialize and open a music instance
// return true on success
bool Mus_Open( mus_t* mus, const char* file ) {
memset( mus, 0, sizeof(mus_t) );
alGenSources( 1, &(mus->source) );
alGenBuffers( 2, mus->buffers );
// disable positional
alSourcei( mus->source, AL_SOURCE_RELATIVE, AL_TRUE );
alSource3f( mus->source, AL_POSITION, 0.0f, 0.0f, 0.0f );
mus->bLoop = true;
// load entire file into mem
FILE* mfile = fopen( file, "rb" );
if ( !mfile ) {
fprintf( stderr, "Failed to fopen %s!\n", file );
Mus_Close( mus );
return false;
}
// get len
unsigned int start = (unsigned int)ftell( mfile );
fseek( mfile, 0, SEEK_END );
mus->_ogglen = (unsigned int)ftell( mfile ) - start;
fseek( mfile, start, SEEK_SET );
mus->_oggdata = (unsigned char*)malloc( mus->_ogglen );
fread( mus->_oggdata, mus->_ogglen, 1, mfile );
fclose( mfile );
mus->stream = stb_vorbis_open_memory( mus->_oggdata, mus->_ogglen, NULL, NULL );
if ( !mus->stream ) {
fprintf( stderr, "Failed to open stream for %s!\n", file );
Mus_Close( mus );
return false;
}
mus->info = stb_vorbis_get_info( mus->stream );
if ( mus->info.channels == 2 ) mus->format = AL_FORMAT_STEREO16;
else mus->format = AL_FORMAT_MONO16;
mus->state = MUS_STATE_STOPPED;
// buffer the initial data
if ( !Mus_Stream( mus, mus->buffers[0] ) || !Mus_Stream( mus, mus->buffers[1] ) ) {
Mus_Close( mus );
return false;
}
alSourceQueueBuffers( mus->source, 2, mus->buffers );
//mus->samplesLeft = stb_vorbis_stream_length_in_samples( mus->stream ) * mus->info.channels;
return true;
}
// start playing
void Mus_Play( mus_t* mus ) {
if ( mus->state == MUS_STATE_PLAYING ) return;
if ( !mus->stream ) return;
alSourcePlay( mus->source );
//mus->samplesLeft = stb_vorbis_stream_length_in_samples( mus->stream ) * mus->info.channels;
mus->state = MUS_STATE_PLAYING;
}
// stops the music, will playback from beginning on Mus_Play
void Mus_Stop( mus_t* mus ) {
if ( mus->state == MUS_STATE_STOPPED ) return;
alSourceStop( mus->source );
alSourcei( mus->source, AL_SAMPLE_OFFSET, 0 );
stb_vorbis_seek_start( mus->stream );
ALint queued = 0;
alGetSourcei( mus->source, AL_BUFFERS_QUEUED, &queued );
while ( queued-- ) {
ALuint buf = 0;
alSourceUnqueueBuffers( mus->source, 1, &buf );
Mus_Stream( mus, buf );
alSourceQueueBuffers( mus->source, 1, &buf );
}
//mus->samplesLeft = stb_vorbis_stream_length_in_samples( mus->stream ) * mus->info.channels;
mus->state = MUS_STATE_STOPPED;
}
// pause...
void Mus_Pause( mus_t* mus ) {
if ( mus->state != MUS_STATE_PLAYING ) return;
alSourcePause( mus->source );
mus->state = MUS_STATE_PAUSED;
}
void Mus_Close( mus_t* mus ) {
if ( mus->_oggdata != NULL )
free( mus->_oggdata );
stb_vorbis_close( mus->stream );
alDeleteBuffers( 2, mus->buffers );
alDeleteSources( 1, &(mus->source) );
memset( mus, 0, sizeof(mus_t) );
}
// return true if music was update
bool Mus_Update( mus_t* mus ) {
ALint processed = 0;
if ( mus->stream == NULL ) return false;
if ( mus->state != MUS_STATE_PLAYING ) return false;
alGetSourcei( mus->source, AL_BUFFERS_PROCESSED, &processed );
while ( processed-- ) {
ALuint buffer = 0;
alSourceUnqueueBuffers( mus->source, 1, &buffer );
if ( !Mus_Stream( mus, buffer ) ) {
bool shouldExit = true;
if ( mus->bLoop ) {
stb_vorbis_seek_start( mus->stream );
//mus->samplesLeft = stb_vorbis_stream_length_in_samples( mus->stream ) * mus->info.channels;
shouldExit = !Mus_Stream( mus, buffer );
}
if ( shouldExit ) return false;
}
alSourceQueueBuffers( mus->source, 1, &buffer );
}
return true;
}
int main( int argc, char** argv ) {
// init ncursees
initscr(); cbreak(); noecho(); clear();
timeout(0); // non-blocking getch
// init openal
ALCdevice* alDev = alcOpenDevice( NULL );
if ( !alDev ) { fprintf( stderr, "Failed to open device\n" ); }
ALCcontext* alCtx = alcCreateContext( alDev, NULL );
if ( !alCtx ) { fprintf( stderr, "Failed to create context\n" ); }
else {
ALCboolean bCtxCur = alcMakeContextCurrent( alCtx );
}
//Mus_Close( &g_music );
const char* musfile = "music.ogg";
if ( argc > 1 )
musfile = argv[1];
if ( !Mus_Open( &g_music, musfile ) ) {
printw( "Failed to open music file!\n" );
} else printw( "Opened music!\n" );
refresh();
bool run = true;
while ( run ) {
char ch = getch();
int t = clock();
Mus_Update( &g_music );
t = clock() - t;
if ( ch > 0 ) {
if ( ch == 'p' ) Mus_Play( &g_music );
if ( ch == 's' ) Mus_Stop( &g_music );
if ( ch == 'k' ) Mus_Pause( &g_music );
if ( ch == 'q' ) run = false;
if ( ch == 't' ) mvprintw(0, 0, "time:%fms\n", t / (float)CLOCKS_PER_SEC * 1000);
mvprintw(1, 0,"pressed: %c | state = %d\n",ch, g_music.state);
refresh();
}
//usleep( 1000 );
}
refresh();
endwin();
Mus_Close( &g_music );
alcMakeContextCurrent( NULL );
alcDestroyContext( alCtx );
alcCloseDevice( alDev );
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment