Abstract audio playback into own file

This commit is contained in:
Doyle Thai 2016-07-27 01:34:26 +10:00
parent 99a700ca13
commit 84a0f755ea
11 changed files with 303 additions and 162 deletions

View File

@ -121,6 +121,7 @@
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="src\AssetManager.c" /> <ClCompile Include="src\AssetManager.c" />
<ClCompile Include="src\Audio.c" />
<ClCompile Include="src\Common.c" /> <ClCompile Include="src\Common.c" />
<ClCompile Include="src\Debug.c" /> <ClCompile Include="src\Debug.c" />
<ClCompile Include="src\dengine.c" /> <ClCompile Include="src\dengine.c" />
@ -139,6 +140,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="src\include\Dengine\Assets.h" /> <ClInclude Include="src\include\Dengine\Assets.h" />
<ClInclude Include="src\include\Dengine\Audio.h" />
<ClInclude Include="src\include\Dengine\Debug.h" /> <ClInclude Include="src\include\Dengine\Debug.h" />
<ClInclude Include="src\include\Dengine\MemoryArena.h" /> <ClInclude Include="src\include\Dengine\MemoryArena.h" />
<ClInclude Include="src\include\Dengine\Platform.h" /> <ClInclude Include="src\include\Dengine\Platform.h" />

View File

@ -45,6 +45,9 @@
<ClCompile Include="src\Debug.c"> <ClCompile Include="src\Debug.c">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="src\Audio.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="data\shaders\default.vert.glsl" /> <None Include="data\shaders\default.vert.glsl" />
@ -95,5 +98,8 @@
<ClInclude Include="src\include\Dengine\MemoryArena.h"> <ClInclude Include="src\include\Dengine\MemoryArena.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="src\include\Dengine\Audio.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,3 +1,5 @@
#define _CRT_SECURE_NO_WARNINGS
#define STBI_FAILURE_USERMSG #define STBI_FAILURE_USERMSG
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include <STB/stb_image.h> #include <STB/stb_image.h>
@ -6,17 +8,32 @@
#define STB_TRUETYPE_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION
#include <STB/stb_truetype.h> #include <STB/stb_truetype.h>
#include "Dengine/AssetManager.h"
#include "Dengine/Debug.h"
#include "Dengine/OpenGL.h"
#include "Dengine/Platform.h"
//#define WT_RENDER_FONT_FILE //#define WT_RENDER_FONT_FILE
#ifdef WT_RENDER_FONT_FILE #ifdef WT_RENDER_FONT_FILE
#define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION
#include <STB/stb_image_write.h> #include <STB/stb_image_write.h>
#endif #endif
#include <STB/stb_vorbis.c>
#include "Dengine/AssetManager.h"
#include "Dengine/Debug.h"
#include "Dengine/OpenGL.h"
#include "Dengine/Platform.h"
AudioVorbis *asset_getVorbis(AssetManager *assetManager,
const enum AudioList type)
{
if (type < audiolist_count)
return &assetManager->audio[type];
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
return NULL;
}
Texture *asset_getTexture(AssetManager *assetManager, const enum TexList type) Texture *asset_getTexture(AssetManager *assetManager, const enum TexList type)
{ {
if (type < texlist_count) if (type < texlist_count)
@ -40,7 +57,7 @@ TexAtlas *asset_getTextureAtlas(AssetManager *assetManager, const enum TexList t
return NULL; return NULL;
} }
Animation *asset_getAnim(AssetManager *assetManager, i32 type) Animation *asset_getAnim(AssetManager *assetManager, const enum AnimList type)
{ {
if (type < animlist_count) if (type < animlist_count)
return &assetManager->anims[type]; return &assetManager->anims[type];
@ -52,6 +69,35 @@ Animation *asset_getAnim(AssetManager *assetManager, i32 type)
return NULL; return NULL;
} }
const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena,
const char *const path, const enum AudioList type)
{
// TODO(doyle): Remember to free vorbis file if we remove from memory
PlatformFileRead fileRead = {0};
platform_readFileToBuffer(arena, path, &fileRead);
i32 error;
AudioVorbis audio = {0};
audio.file =
stb_vorbis_open_memory(fileRead.buffer, fileRead.size, &error, NULL);
if (!audio.file)
{
printf("stb_vorbis_open_memory() failed: Error code %d\n", error);
platform_closeFileRead(arena, &fileRead);
stb_vorbis_close(audio.file);
return 0;
}
audio.info = stb_vorbis_get_info(audio.file);
audio.lengthInSamples = stb_vorbis_stream_length_in_samples(audio.file);
audio.lengthInSeconds = stb_vorbis_stream_length_in_seconds(audio.file);
assetManager->audio[type] = audio;
return 0;
}
const i32 asset_loadTextureImage(AssetManager *assetManager, const i32 asset_loadTextureImage(AssetManager *assetManager,
const char *const path, const enum TexList type) const char *const path, const enum TexList type)
{ {

170
src/Audio.c Normal file
View File

@ -0,0 +1,170 @@
#include <OpenAL/alc.h>
#define STB_VORBIS_HEADER_ONLY
#include <STB/stb_vorbis.c>
#include "Dengine/Assets.h"
#include "Dengine/Audio.h"
#include "Dengine/Common.h"
#include "Dengine/Debug.h"
#define AL_CHECK_ERROR() alCheckError_(__FILE__, __LINE__);
void alCheckError_(const char *file, int line)
{
ALenum errorCode;
while ((errorCode = alGetError()) != AL_NO_ERROR)
{
printf("OPENAL ");
switch(errorCode)
{
case AL_INVALID_NAME:
printf("INVALID_NAME | ");
break;
case AL_INVALID_ENUM:
printf("INVALID_ENUM | ");
break;
case AL_INVALID_VALUE:
printf("INVALID_VALUE | ");
break;
case AL_INVALID_OPERATION:
printf("INVALID_OPERATION | ");
break;
case AL_OUT_OF_MEMORY:
printf("OUT_OF_MEMORY | ");
break;
default:
printf("UNRECOGNISED ERROR CODE | ");
break;
}
printf("Error %08x, %s (%d)\n", errorCode, file, line);
}
};
const i32 audio_rendererInit(AudioRenderer *audioRenderer)
{
alGetError();
ALCdevice *deviceAL = alcOpenDevice(NULL);
if (!deviceAL)
{
printf("alcOpenDevice() failed: Failed to init OpenAL device.\n");
return -1;
}
ALCcontext *contextAL = alcCreateContext(deviceAL, NULL);
alcMakeContextCurrent(contextAL);
if (!contextAL)
{
printf("alcCreateContext() failed: Failed create AL context.\n");
return -1;
}
AL_CHECK_ERROR();
/* Generate number of concurrent audio file listeners */
alGenSources(ARRAY_COUNT(audioRenderer->sourceId), audioRenderer->sourceId);
AL_CHECK_ERROR();
/* Generate audio data buffers */
alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId), audioRenderer->bufferId);
AL_CHECK_ERROR();
return 0;
}
#define AUDIO_CHUNK_SIZE_ 65536
void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis)
{
/* Determine format */
audioRenderer->format = AL_FORMAT_MONO16;
if (vorbis->info.channels == 2)
audioRenderer->format = AL_FORMAT_STEREO16;
else if (vorbis->info.channels != 1)
DEBUG_LOG("audio_streamVorbis() warning: Unaccounted channel format");
audioRenderer->audio = vorbis;
AudioVorbis *audio = audioRenderer->audio;
/* Pre-load vorbis data into audio chunks */
for (i32 i = 0; i < ARRAY_COUNT(audioRenderer->bufferId); i++)
{
i16 audioChunk[AUDIO_CHUNK_SIZE_] = {0};
stb_vorbis_get_samples_short_interleaved(
audio->file, audio->info.channels, audioChunk, AUDIO_CHUNK_SIZE_);
alBufferData(audioRenderer->bufferId[i], audioRenderer->format,
audioChunk, AUDIO_CHUNK_SIZE_ * sizeof(i16),
audio->info.sample_rate);
}
/* Queue and play buffers */
alSourceQueueBuffers(audioRenderer->sourceId[0],
ARRAY_COUNT(audioRenderer->bufferId),
audioRenderer->bufferId);
alSourcePlay(audioRenderer->sourceId[0]);
}
void audio_updateAndPlay(AudioRenderer *audioRenderer)
{
AudioVorbis *audio = audioRenderer->audio;
ALint audioState;
alGetSourcei(audioRenderer->sourceId[0], AL_SOURCE_STATE, &audioState);
if (audioState == AL_STOPPED || audioState == AL_INITIAL)
{
// TODO(doyle): This fixes clicking when reusing old buffers
if (audioState == AL_STOPPED)
{
alDeleteBuffers(ARRAY_COUNT(audioRenderer->bufferId),
audioRenderer->bufferId);
alGenBuffers(ARRAY_COUNT(audioRenderer->bufferId),
audioRenderer->bufferId);
}
stb_vorbis_seek_start(audio->file);
for (i32 i = 0; i < ARRAY_COUNT(audioRenderer->bufferId); i++)
{
i16 audioChunk[AUDIO_CHUNK_SIZE_] = {0};
stb_vorbis_get_samples_short_interleaved(
audio->file, audio->info.channels, audioChunk,
AUDIO_CHUNK_SIZE_);
alBufferData(audioRenderer->bufferId[i], audioRenderer->format,
audioChunk, AUDIO_CHUNK_SIZE_ * sizeof(i16),
audio->info.sample_rate);
}
alSourceQueueBuffers(audioRenderer->sourceId[0],
ARRAY_COUNT(audioRenderer->bufferId),
audioRenderer->bufferId);
alSourcePlay(audioRenderer->sourceId[0]);
}
else if (audioState == AL_PLAYING)
{
ALint numProcessedBuffers;
alGetSourcei(audioRenderer->sourceId[0], AL_BUFFERS_PROCESSED,
&numProcessedBuffers);
if (numProcessedBuffers > 0)
{
ALint numBuffersToUnqueue = 1;
ALuint emptyBufferId;
alSourceUnqueueBuffers(audioRenderer->sourceId[0],
numBuffersToUnqueue, &emptyBufferId);
i16 audioChunk[AUDIO_CHUNK_SIZE_] = {0};
i32 sampleCount = stb_vorbis_get_samples_short_interleaved(
audio->file, audio->info.channels, audioChunk,
AUDIO_CHUNK_SIZE_);
/* There are still samples to play */
if (sampleCount > 0)
{
DEBUG_LOG("Buffering new audio data");
alBufferData(emptyBufferId, audioRenderer->format, audioChunk,
sampleCount * audio->info.channels * sizeof(i16),
audio->info.sample_rate);
alSourceQueueBuffers(audioRenderer->sourceId[0], 1,
&emptyBufferId);
}
}
}
}

View File

@ -1,11 +1,6 @@
#if 1 #if 1
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#define _CRT_SECURE_NO_WARNINGS
#include <STB/stb_vorbis.c>
#include "Dengine/AssetManager.h" #include "Dengine/AssetManager.h"
#include "Dengine/Audio.h"
#include "Dengine/Common.h" #include "Dengine/Common.h"
#include "Dengine/Debug.h" #include "Dengine/Debug.h"
#include "Dengine/Math.h" #include "Dengine/Math.h"
@ -14,38 +9,8 @@
#include "WorldTraveller/WorldTraveller.h" #include "WorldTraveller/WorldTraveller.h"
void alCheckError_(const char *file, int line) // TODO(doyle): Temporary
{ struct AudioRenderer;
ALenum errorCode;
while ((errorCode = alGetError()) != AL_NO_ERROR)
{
printf("OPENAL ");
switch(errorCode)
{
case AL_INVALID_NAME:
printf("INVALID_NAME | ");
break;
case AL_INVALID_ENUM:
printf("INVALID_ENUM | ");
break;
case AL_INVALID_VALUE:
printf("INVALID_VALUE | ");
break;
case AL_INVALID_OPERATION:
printf("INVALID_OPERATION | ");
break;
case AL_OUT_OF_MEMORY:
printf("OUT_OF_MEMORY | ");
break;
default:
printf("UNRECOGNISED ERROR CODE | ");
break;
}
printf("Error %08x, %s (%d)\n", errorCode, file, line);
}
};
#define AL_CHECK_ERROR() alCheckError_(__FILE__, __LINE__);
void key_callback(GLFWwindow *window, int key, int scancode, int action, int mode) void key_callback(GLFWwindow *window, int key, int scancode, int action, int mode)
{ {
@ -148,64 +113,20 @@ int main()
* INITIALISE AUDIO * INITIALISE AUDIO
******************* *******************
*/ */
alGetError(); AudioRenderer audioRenderer = {0};
// TODO(doyle): Read this audio_rendererInit(&audioRenderer);
// http://www.gamedev.net/page/resources/_/technical/game-programming/basic-openal-sound-manager-for-your-project-r3791
// https://gist.github.com/Oddity007/965399
// https://jogamp.org/joal-demos/www/devmaster/lesson8.html
// http://basic-converter.proboards.com/thread/818/play-files-using-vorbis-openal
ALCdevice *deviceAL = alcOpenDevice(NULL);
if (!deviceAL)
{
printf("alcOpenDevice() failed: Failed to init OpenAL device.\n");
return;
}
ALCcontext *contextAL = alcCreateContext(deviceAL, NULL); /* Load audio assets */
alcMakeContextCurrent(contextAL); char *audioPath = "data/audio/Nobuo Uematsu - Battle 1.ogg";
if (!contextAL) asset_loadVorbis(&worldTraveller.assetManager, &worldTraveller.arena,
{ audioPath, audiolist_battle);
printf("alcCreateContext() failed: Failed create AL context.\n"); audioPath = "data/audio/Yuki Kajiura - Swordland.ogg";
return; asset_loadVorbis(&worldTraveller.assetManager, &worldTraveller.arena,
} audioPath, audiolist_overworld);
AL_CHECK_ERROR(); AudioVorbis *audio =
asset_getVorbis(&worldTraveller.assetManager, audiolist_battle);
/* Open audio file */ audio_streamVorbis(&audioRenderer, audio);
PlatformFileRead fileRead = {0};
#if 0
platform_readFileToBuffer(&worldTraveller.arena,
"data/audio/Yuki Kajiura - Swordland.ogg",
&fileRead);
#else
platform_readFileToBuffer(&worldTraveller.arena,
"data/audio/Nobuo Uematsu - Battle 1.ogg",
&fileRead);
#endif
i32 error;
stb_vorbis *vorbisFile = stb_vorbis_open_memory(fileRead.buffer, fileRead.size,
&error, NULL);
stb_vorbis_info vorbisInfo = stb_vorbis_get_info(vorbisFile);
//platform_closeFileRead(&worldTraveller.arena, &fileRead);
/* Generate number of concurrent audio file listeners */
ALuint audioSourceId;
alGenSources(1, &audioSourceId);
AL_CHECK_ERROR();
/* Generate audio data buffers */
ALuint audioBufferId[4];
alGenBuffers(ARRAY_COUNT(audioBufferId), audioBufferId);
AL_CHECK_ERROR();
#if 0
ALuint audioFormat = AL_FORMAT_MONO16;
if (vorbisInfo.channels == 2) audioFormat = AL_FORMAT_STEREO16;
i32 audioState;
alGetSourcei(audioSourceIds[0], AL_SOURCE_STATE, &audioState);
#endif
/* /*
******************* *******************
@ -237,6 +158,7 @@ int main()
worldTraveller_gameUpdateAndRender(&worldTraveller, secondsElapsed); worldTraveller_gameUpdateAndRender(&worldTraveller, secondsElapsed);
GL_CHECK_ERROR(); GL_CHECK_ERROR();
audio_updateAndPlay(&audioRenderer);
/* Swap the buffers */ /* Swap the buffers */
glfwSwapBuffers(window); glfwSwapBuffers(window);
@ -244,64 +166,6 @@ int main()
f32 endTime = CAST(f32)glfwGetTime(); f32 endTime = CAST(f32)glfwGetTime();
secondsElapsed = endTime - startTime; secondsElapsed = endTime - startTime;
#define AUDIO_CHUNK_SIZE 65536
ALint audioState;
alGetSourcei(audioSourceId, AL_SOURCE_STATE, &audioState);
if (audioState == AL_STOPPED || audioState == AL_INITIAL)
{
// TODO(doyle): This fixes clicking when reusing old buffers
if (audioState == AL_STOPPED)
{
alDeleteBuffers(ARRAY_COUNT(audioBufferId), audioBufferId);
alGenBuffers(ARRAY_COUNT(audioBufferId), audioBufferId);
}
stb_vorbis_seek_start(vorbisFile);
for (i32 i = 0; i < ARRAY_COUNT(audioBufferId); i++)
{
i16 audioChunk[AUDIO_CHUNK_SIZE] = {0};
stb_vorbis_get_samples_short_interleaved(
vorbisFile, vorbisInfo.channels, audioChunk,
AUDIO_CHUNK_SIZE);
alBufferData(audioBufferId[i], AL_FORMAT_STEREO16, audioChunk,
AUDIO_CHUNK_SIZE * sizeof(i16),
vorbisInfo.sample_rate);
}
alSourceQueueBuffers(audioSourceId, ARRAY_COUNT(audioBufferId),
audioBufferId);
alSourcePlay(audioSourceId);
}
else if (audioState == AL_PLAYING)
{
ALint numProcessedBuffers;
alGetSourcei(audioSourceId, AL_BUFFERS_PROCESSED,
&numProcessedBuffers);
if (numProcessedBuffers > 0)
{
ALint numBuffersToUnqueue = 1;
ALuint emptyBufferId;
alSourceUnqueueBuffers(audioSourceId, numBuffersToUnqueue,
&emptyBufferId);
i16 audioChunk[AUDIO_CHUNK_SIZE] = {0};
i32 sampleCount = stb_vorbis_get_samples_short_interleaved(
vorbisFile, vorbisInfo.channels, audioChunk,
AUDIO_CHUNK_SIZE);
/* There are still samples to play */
if (sampleCount > 0)
{
DEBUG_LOG("Buffering new audio data");
alBufferData(emptyBufferId, AL_FORMAT_STEREO16, audioChunk,
sampleCount * vorbisInfo.channels *
sizeof(i16),
vorbisInfo.sample_rate);
alSourceQueueBuffers(audioSourceId, 1, &emptyBufferId);
}
}
}
#if 0 #if 0
// TODO(doyle): Busy waiting, should sleep // TODO(doyle): Busy waiting, should sleep
while (secondsElapsed < targetSecondsPerFrame) while (secondsElapsed < targetSecondsPerFrame)

View File

@ -2,30 +2,38 @@
#define DENGINE_ASSET_MANAGER_H #define DENGINE_ASSET_MANAGER_H
#include "Dengine/Assets.h" #include "Dengine/Assets.h"
#include "Dengine/MemoryArena.h"
#include "Dengine/Shader.h" #include "Dengine/Shader.h"
#include "Dengine/Texture.h" #include "Dengine/Texture.h"
/* Forward declaration */
typedef struct MemoryArena MemoryArena;
#define MAX_TEXTURE_SIZE 1024 #define MAX_TEXTURE_SIZE 1024
// TODO(doyle): Switch to hash based lookup // TODO(doyle): Switch to hash based lookup
// TODO(doyle): Use pointers, so we can forward declare all assets?
typedef struct AssetManager typedef struct AssetManager
{ {
Texture textures[32]; Texture textures[32];
TexAtlas texAtlas[32]; TexAtlas texAtlas[32];
Shader shaders[32]; Shader shaders[32];
Animation anims[32]; Animation anims[32];
AudioVorbis audio[32];
Font font; Font font;
} AssetManager; } AssetManager;
GLOBAL_VAR AssetManager assetManager; GLOBAL_VAR AssetManager assetManager;
AudioVorbis *asset_getVorbis(AssetManager *assetManager,
const enum AudioList type);
Texture *asset_getTexture(AssetManager *assetManager, const enum TexList type); Texture *asset_getTexture(AssetManager *assetManager, const enum TexList type);
Shader *asset_getShader(AssetManager *assetManager, const enum ShaderList type); Shader *asset_getShader(AssetManager *assetManager, const enum ShaderList type);
TexAtlas *asset_getTextureAtlas(AssetManager *assetManager, TexAtlas *asset_getTextureAtlas(AssetManager *assetManager,
const enum TexList type); const enum TexList type);
Animation *asset_getAnim(AssetManager *assetManager, i32 type); Animation *asset_getAnim(AssetManager *assetManager, i32 type);
const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena *arena,
const char *const path, const enum AudioList type);
const i32 asset_loadTextureImage(AssetManager *assetManager, const i32 asset_loadTextureImage(AssetManager *assetManager,
const char *const path, const char *const path,
const enum TexList type); const enum TexList type);

View File

@ -1,6 +1,9 @@
#ifndef DENGINE_ASSETS_H #ifndef DENGINE_ASSETS_H
#define DENGINE_ASSETS_H #define DENGINE_ASSETS_H
#define STB_VORBIS_HEADER_ONLY
#include <STB/stb_vorbis.c>
#include "Dengine/Math.h" #include "Dengine/Math.h"
#include "Dengine/Texture.h" #include "Dengine/Texture.h"
@ -52,6 +55,23 @@ enum AnimList
animlist_invalid, animlist_invalid,
}; };
enum AudioList
{
audiolist_battle,
audiolist_overworld,
audiolist_count,
audiolist_invalid,
};
typedef struct AudioVorbis
{
stb_vorbis *file;
stb_vorbis_info info;
u32 lengthInSamples;
f32 lengthInSeconds;
} AudioVorbis;
typedef struct TexAtlas typedef struct TexAtlas
{ {
// TODO(doyle): String hash based lookup // TODO(doyle): String hash based lookup

View File

@ -0,0 +1,18 @@
#ifndef DENGINE_AUDIO_H
#define DENGINE_AUDIO_H
#include <OpenAL/al.h>
typedef struct AudioRenderer
{
ALuint sourceId[1];
ALuint bufferId[4];
AudioVorbis *audio;
ALuint format;
} AudioRenderer;
const i32 audio_rendererInit(AudioRenderer *audioRenderer);
void audio_streamVorbis(AudioRenderer *audioRenderer, AudioVorbis *vorbis);
void audio_updateAndPlay(AudioRenderer *audioRenderer);
#endif

View File

@ -1,9 +1,11 @@
#ifndef DENGINE_MEMORY_ARENA_H #ifndef DENGINE_MEMORY_ARENA_H
#define DENGINE_MEMORY_ARENA_H #define DENGINE_MEMORY_ARENA_H
typedef struct MemoryArena #include "Dengine/Common.h"
struct MemoryArena
{ {
i32 bytesAllocated; i32 bytesAllocated;
} MemoryArena; };
#endif #endif

View File

@ -2,7 +2,9 @@
#define DENGINE_PLATFORM_H #define DENGINE_PLATFORM_H
#include "Dengine/Common.h" #include "Dengine/Common.h"
#include "Dengine/MemoryArena.h"
/* Forward Declaration */
typedef struct MemoryArena MemoryArena;
typedef struct PlatformFileRead typedef struct PlatformFileRead
{ {

View File

@ -8,6 +8,9 @@
#include "Dengine/MemoryArena.h" #include "Dengine/MemoryArena.h"
#include "Dengine/Renderer.h" #include "Dengine/Renderer.h"
/* Forward Declaration */
typedef struct MemoryArena MemoryArena;
#define NUM_KEYS 1024 #define NUM_KEYS 1024
#define METERS_TO_PIXEL 240 #define METERS_TO_PIXEL 240