Last active
March 18, 2021 10:50
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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