diff --git a/data/sound/beep.ogg b/data/sound/beep.ogg new file mode 100644 index 0000000..71f5128 Binary files /dev/null and b/data/sound/beep.ogg differ diff --git a/data/sprite/breadball.png b/data/sprite/breadball.png new file mode 100644 index 0000000..da1cba7 Binary files /dev/null and b/data/sprite/breadball.png differ diff --git a/data/sprite/bricks.png b/data/sprite/bricks.png new file mode 100644 index 0000000..fe10e29 Binary files /dev/null and b/data/sprite/bricks.png differ diff --git a/data/sprites/breadball.png b/data/sprites/breadball.png deleted file mode 100644 index da1cba7..0000000 Binary files a/data/sprites/breadball.png and /dev/null differ diff --git a/data/sprites/bricks.png b/data/sprites/bricks.png deleted file mode 100644 index fe10e29..0000000 Binary files a/data/sprites/bricks.png and /dev/null differ diff --git a/helianthus/SConstruct b/helianthus/SConstruct index d0e63b6..4bcbc1d 100644 --- a/helianthus/SConstruct +++ b/helianthus/SConstruct @@ -4,7 +4,7 @@ env = Environment() # config -libs = ['gtk+-3.0', 'glib-2.0', 'cairo'] +libs = ['gtk+-3.0', 'glib-2.0', 'cairo', 'SDL2_mixer'] # compute build options @@ -24,6 +24,7 @@ sources = [ 'drawing.c', 'group.c', 'test.c', + 'sound.c', 'sprite.c', 'world.c', 'main.c' ] diff --git a/helianthus/common.c b/helianthus/common.c index c183e83..a46a70f 100644 --- a/helianthus/common.c +++ b/helianthus/common.c @@ -5,6 +5,29 @@ cairo_t *heliCairo; +static char *colors[] = { + "transparent", "0 0 0 0", + + "black", "0 0 0 1", + "white", "1 1 1 1", + "gray", "0.5 0.5 0.5 1", + + "red", "1 0 0 1", + "green" "0 1 0 1", + "blue" "0 0 1 1", + + "yellow", "1 1 0 1", + "magenta", "1 0 1 1", + "cyan", "0 1 1 1", + + "yellow", "1 1 0 1", + "magenta", "1 0 1 1", + "cyan", "0 1 1 1", + + "brown", "0.5 0.5 0 1", +}; + + int randomNumber(int min, int max) { return max <= min ? min : rand()%(max - min + 1) + min; } @@ -44,7 +67,30 @@ int heliStringEndsWithLowcase(const char *s, const char *tail) { void heliLowercase(char *x) { while(*x) { *x = tolower(*x); ++x; } } + void heliParseColor(const char *x, double *color) { - #warning "TODO: heliParseColor: not implemented yet" + color[0] = color[1] = color[2] = 0; + color[3] = 1; + + if (*x == '#') { + ++x; + int hex[8] = { 0, 0, 0, 0, 0, 0, 15, 15 }; + for(int i = 0; *x && i < 8; ++i) { + char c = tolower(*x); + if (c >= '0' && c <= '9') hex[i] = c - '0'; + if (c >= 'a' && c <= 'f') hex[i] = c - 'a' + 10; + } + for(int i = 0; i < 4; ++i) + color[i] = (hex[i*2]*16 + hex[i*2 + 1])/255.0; + } else + if (isalpha(*x)) { + int count = (int)(sizeof(colors)/sizeof(*colors)/2); + for(int i = 0; i < count; ++i) + if (strcmp(x, colors[i*2]) == 0) + { heliParseColor(colors[i*2 + 1], color); return; } + } else { + sscanf(x, "%lf %lf %lf %lf", &color[0], &color[1], &color[2], &color[3]); + } } + diff --git a/helianthus/main.c b/helianthus/main.c index 0749ec6..c287d2c 100644 --- a/helianthus/main.c +++ b/helianthus/main.c @@ -4,16 +4,19 @@ #include "helianthus.h" Sprite ball, brick1, brick2; +Sound beep; void init() { ball = createSpriteEx(200, 200, 64, 64); - spriteSetAnimation(ball, "data/sprites/breadball.png"); + spriteSetAnimation(ball, "data/sprite/breadball.png"); brick1 = createSpriteEx(200-32, 200+64, 64, 64); - spriteSetAnimation(brick1, "data/sprites/bricks.png"); + spriteSetAnimation(brick1, "data/sprite/bricks.png"); brick2 = createSpriteEx(200+32, 200+64, 64, 64); - spriteSetAnimation(brick2, "data/sprites/bricks.png"); + spriteSetAnimation(brick2, "data/sprite/bricks.png"); + + beep = createSound("data/sound/beep.ogg"); } void draw() { @@ -31,6 +34,8 @@ void draw() { spriteSetSpeedAndDirection(ball, speed, spriteGetRotation(ball)); + if (mouseWentDown("left")) soundPlay(beep, FALSE); + drawSprites(); } diff --git a/helianthus/private.h b/helianthus/private.h index bd90baf..95f5c0a 100644 --- a/helianthus/private.h +++ b/helianthus/private.h @@ -25,6 +25,7 @@ int heliStringEndsWithLowcase(const char *s, const char *tail); void heliLowercase(char *x); void heliParseColor(const char *x, double *color); + // pointer array typedef void (*HeliFreeCallback)(void*); @@ -106,6 +107,12 @@ void heliDrawingPrepareFrame(); void heliDrawingFinish(); +// sound + +void heliSoundUpdate(); +void heliSoundFinish(); + + // test void heliDoTests(); diff --git a/helianthus/sound.c b/helianthus/sound.c new file mode 100644 index 0000000..aa36d92 --- /dev/null +++ b/helianthus/sound.c @@ -0,0 +1,163 @@ + +#include + +#include +#include + +#include "private.h" +#include "world.h" + + +#define HELI_SOUND_CHANNELS 64 + + +typedef struct _HeliSoundInstance { + char *path; + Mix_Chunk *chunk; + int refcount; +} HeliSoundInstance; + +typedef struct _HeliSoundTrashItem { + Mix_Chunk *chunk; + gint64 time; +} HeliSoundTrashItem; + +struct _Sound { + HeliSoundInstance *instance; +}; + + +static int initialized; +static int ready; + +static HeliArray cache; +static Sound channels[HELI_SOUND_CHANNELS]; + +static HeliSoundTrashItem *trash; +static size_t trashSize; + + +static void trashAdd(Mix_Chunk *chunk) { + if (!chunk) return; + + gint64 time = g_get_monotonic_time() + 1000000; + for(int i = 0; i < trashSize; ++i) + if (!trash[i].chunk) + { trash[i].chunk = chunk; trash[i].time = time; return; } + + size_t prevSize = trashSize; + trashSize += trashSize/4 + 32; + trash = realloc(trash, trashSize); + memset(&trash[prevSize], 0, (trashSize - prevSize)*sizeof(*trash)); + + trash[prevSize].chunk = chunk; + trash[prevSize].time = time; +} + +static void trashProcess() { + gint64 time = g_get_monotonic_time(); + for(int i = 0; i < trashSize; ++i) + if (trash[i].chunk && trash[i].time <= time) + { Mix_FreeChunk(trash[i].chunk); trash[i].chunk = NULL; } +} + +static void trashDestroy() { + for(int i = 0; i < trashSize; ++i) + if (trash[i].chunk) Mix_FreeChunk(trash[i].chunk); + trashSize = 0; + free(trash); + trash = NULL; +} + + +static void init() { + if (initialized) return; + if (SDL_Init(SDL_INIT_AUDIO) == 0) { + int loaded = Mix_Init(MIX_INIT_OGG | MIX_INIT_FLAC | MIX_INIT_MP3); + if (!(loaded & MIX_INIT_OGG )) fprintf(stderr, "helianthus: cannot initialize OGG support\n"); + if (!(loaded & MIX_INIT_FLAC)) fprintf(stderr, "helianthus: cannot initialize FLAC support\n"); + if (!(loaded & MIX_INIT_MP3 )) fprintf(stderr, "helianthus: cannot initialize MP3 support\n"); + ready = Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) == 0; + if (!ready) fprintf(stderr, "helianthus: cannot open audio device\n"); + } + initialized = TRUE; +} + +static void deinit() { + if (!initialized) return; + heliArrayDestroy(&cache); + if (ready) Mix_CloseAudio(); + trashDestroy(); + Mix_Quit(); + SDL_Quit(); + for(int i = 0; i < HELI_SOUND_CHANNELS; ++i) channels[i] = NULL; + ready = FALSE; + initialized = FALSE; +} + + +static HeliSoundInstance* load(const char *path) { + HeliSoundInstance *s = calloc(1, sizeof(*s)); + s->path = heliStringCopy(path); + if (ready) { + s->chunk = Mix_LoadWAV(path); + if (!s->chunk) + fprintf(stderr, "helianthus: cannot load sound file: %s\n", path); + } + return s; +} + +static void unload(HeliSoundInstance *s) { + assert(!s->refcount); + free(s->path); + trashAdd(s->chunk); + free(s); +} + + +Sound createSound(const char *path) { + init(); + HeliPair *item = heliStringmapGet(&cache, path); + if (!item) item = heliStringmapAdd(&cache, path, load(path), (HeliFreeCallback)&unload); + HeliSoundInstance *s = (HeliSoundInstance*)item->value; + ++s->refcount; + + Sound sound = calloc(1, sizeof(*sound)); + sound->instance = s; + return sound; +} + +void soundDestroy(Sound sound) { + soundStop(sound); + if (--sound->instance->refcount <= 0) + heliStringmapRemove(&cache, sound->instance->path); +} + + +void soundPlay(Sound sound, int loop) { + if (!ready) return; + if (sound->instance->chunk) { + int channel = Mix_PlayChannel(-1, sound->instance->chunk, loop ? -1 : 0); + assert(channel < HELI_SOUND_CHANNELS); + if (channel >= 0 && channel < HELI_SOUND_CHANNELS) + channels[channel] = sound; + } +} + +void soundStop(Sound sound) { + for(int i = 0; i < HELI_SOUND_CHANNELS; ++i) { + if (channels[i] == sound) { + Mix_Pause(i); + Mix_HaltChannel(i); + channels[i] = NULL; + } + } +} + + +void heliSoundUpdate() + { trashProcess(); } + +void heliSoundFinish() + { deinit(); } + diff --git a/helianthus/sprite.c b/helianthus/sprite.c index a6aaf81..48513dc 100644 --- a/helianthus/sprite.c +++ b/helianthus/sprite.c @@ -282,7 +282,7 @@ static void drawSpriteDebug(cairo_t *cr, Sprite s) { cairo_stroke(cr); char buf[1024]; - snprintf(buf, sizeof(buf)-1, "%f", s->depth); + snprintf(buf, sizeof(buf)-1, "%lf", s->depth); double s1 = hw*0.25; double s2 = hh*0.5; diff --git a/helianthus/world.c b/helianthus/world.c index df20ecf..68e34aa 100644 --- a/helianthus/world.c +++ b/helianthus/world.c @@ -48,14 +48,6 @@ static int buttonsCount = (int)(sizeof(buttonNames)/sizeof(*buttonNames)); -void playSound(const char *path, int loop) { - #warning "TODO: playSound: not implemented yet" -} - -void stopSound(const char *path) { - #warning "TODO: stopSound: not implemented yet" -} - int keyDown(const char *code) { return heliStringmapGet(&keysPressed, code) != NULL; } int keyWentDown(const char *code) @@ -181,6 +173,7 @@ static gboolean draw(GtkWidget *widget G_GNUC_UNUSED, cairo_t *cr, gpointer data currentTime += timeStep; ++frameCount; heliSpriteUpdate(dt); + heliSoundUpdate(); heliArrayClear(&keysPressedInFrame); heliArrayClear(&keysReleasedInFrame); @@ -213,18 +206,18 @@ static gboolean handleEvent(GtkWidget *widget G_GNUC_UNUSED, GdkEvent *event, gp return TRUE; case GDK_BUTTON_PRESS: button = event->button.button; - if (button >= 0 && button < buttonsCount) { - heliStringmapAdd(&buttonsPressed, buttonNames[button], NULL, NULL); - heliStringmapAdd(&buttonsPressedInFrame, buttonNames[button], NULL, NULL); + if (button >= 1 && button <= buttonsCount) { + heliStringmapAdd(&buttonsPressed, buttonNames[button - 1], NULL, NULL); + heliStringmapAdd(&buttonsPressedInFrame, buttonNames[button - 1], NULL, NULL); } _mouseX = event->button.x; _mouseY = event->button.y; return TRUE; case GDK_BUTTON_RELEASE: button = event->button.button; - if (button >= 0 && button < buttonsCount) { - heliStringmapRemove(&buttonsPressed, buttonNames[button]); - heliStringmapAdd(&buttonsReleasedInFrame, buttonNames[button], NULL, NULL); + if (button >= 1 && button <= buttonsCount) { + heliStringmapRemove(&buttonsPressed, buttonNames[button - 1]); + heliStringmapAdd(&buttonsReleasedInFrame, buttonNames[button - 1], NULL, NULL); } _mouseX = event->button.x; _mouseY = event->button.y; @@ -277,6 +270,7 @@ void worldRun() { heliSpriteFinish(); heliDrawingFinish(); heliAnimationFinish(); + heliSoundFinish(); heliArrayDestroy(&keysPressed); heliArrayDestroy(&keysPressed); diff --git a/helianthus/world.h b/helianthus/world.h index 2b8b377..c01e67f 100644 --- a/helianthus/world.h +++ b/helianthus/world.h @@ -5,14 +5,16 @@ #include "common.h" #include "sprite.h" - typedef void (*Callback)(); +typedef struct _Sound *Sound; void drawSprites(); -void playSound(const char *path, int loop); -void stopSound(const char *path); +Sound createSound(const char *path); +void soundDestroy(Sound sound); +void soundPlay(Sound sound, int loop); +void soundStop(Sound sound); int keyDown(const char *code); int keyWentDown(const char *code);