+
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KickMan", "KickMan.vcproj", "{084AA5FB-DC4E-4A84-B198-19448669AD25}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {084AA5FB-DC4E-4A84-B198-19448669AD25}.Debug|Win32.ActiveCfg = Debug|Win32
+ {084AA5FB-DC4E-4A84-B198-19448669AD25}.Debug|Win32.Build.0 = Debug|Win32
+ {084AA5FB-DC4E-4A84-B198-19448669AD25}.Release|Win32.ActiveCfg = Release|Win32
+ {084AA5FB-DC4E-4A84-B198-19448669AD25}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="KickMan"
+ ProjectGUID="{084AA5FB-DC4E-4A84-B198-19448669AD25}"
+ RootNamespace="swarm"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="131072"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""$(PalmPDK)\include";"$(PalmPDK)\include\SDL""
+ PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="SDLmain.lib SDL.lib SDL_mixer.lib SDL_image.lib dglesv2.lib libpdl.lib ws2_32.lib"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories="$(PalmPDK)\host\lib"
+ GenerateDebugInformation="true"
+ SubSystem="2"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ TargetMachine="1"
+ Profile="false"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""$(PalmPDK)\include";"$(PalmPDK)\include\SDL""
+ PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="SDLmain.lib SDL.lib SDL_mixer.lib dglesv2.lib libpdl.lib ws2_32.lib"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories="$(PalmPDK)\host\lib"
+ GenerateDebugInformation="true"
+ SubSystem="2"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\src\BeatChart.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\BPMDisplay.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Clock.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Component.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\DecalShader.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\KickMan.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\RButton.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\RShader.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Sequencer.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Shader.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Texture.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath=".\src\BeatChart.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\BPMDisplay.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Clock.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Component.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\DecalShader.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\misc.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\RButton.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\RShader.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Sequencer.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Shader.h"
+ >
+ </File>
+ <File
+ RelativePath=".\src\Texture.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
+@echo off
+@rem Set the device you want to build for to 1
+@rem Use Pixi to allow running on either device
+set PRE=0
+set PIXI=1
+set DEBUG=0
+
+@rem List your source files here
+set SRC=src/KickMan.cpp src/RButton.cpp src/RShader.cpp src/Clock.cpp src/BeatChart.cpp src/Sequencer.cpp src/Shader.cpp src/DecalShader.cpp src/Component.cpp src/BPMDisplay.cpp src/Texture.cpp
+
+@rem List the libraries needed
+set LIBS=-lSDL -lGLESv2 -lpdl -lSDL_mixer -lSDL_image
+
+@rem Name your output executable
+set OUTFILE=kickman
+
+if %PRE% equ 0 if %PIXI% equ 0 goto :END
+
+if %DEBUG% equ 1 (
+ set DEVICEOPTS=-g
+) else (
+ set DEVICEOPTS=-O2
+)
+
+if %PRE% equ 1 (
+ set DEVICEOPTS=%DEVICEOPTS% -mcpu=cortex-a8 -mfpu=neon -mfloat-abi=softfp
+)
+
+if %PIXI% equ 1 (
+ set DEVICEOPTS=%DEVICEOPTS% -mcpu=arm1136jf-s -mfpu=vfp -mfloat-abi=softfp
+)
+
+echo %DEVICEOPTS%
+
+arm-none-linux-gnueabi-gcc %DEVICEOPTS% -o %OUTFILE% %SRC% "-I%PALMPDK%\include" "-I%PALMPDK%\include\SDL" "-L%PALMPDK%\device\lib" -Wl,--allow-shlib-undefined %LIBS%
+
+goto :EOF
+
+:END
+echo Please select the target device by editing the PRE/PIXI variable in this file.
+exit /b 1
+{
+ "id": "com.dominionofawesome.kickman",
+ "version": "1.0.0",
+ "vendor": "The Dominion of Awesome",
+ "type": "pdk",
+ "main": "kickman",
+ "title": "KickMan",
+ "icon": "icon.png"
+}
+@echo off
+call buildit.cmd
+del /q /s package\kits
+del /q /s package\img
+xcopy /e /i kits package\kits
+xcopy /e /i img package\img
+copy kickman package\
+palm-package package
+#include "BPMDisplay.h"
+
+BPMDisplay::BPMDisplay(void) {
+ this->BPMtex = new Texture("img/bpm.png");
+ this->setSize(BPMtex->getWidth(), BPMtex->getHeight());
+
+ labelVertices[0] = 0;
+ labelVertices[1] = 0;
+ labelVertices[2] = 38;
+ labelVertices[3] = 0;
+ labelVertices[4] = 38;
+ labelVertices[5] = 14;
+ labelVertices[6] = 0;
+ labelVertices[7] = 14;
+
+ numberVertices[0] = 0;
+ numberVertices[1] = 0;
+ numberVertices[2] = 10;
+ numberVertices[3] = 0;
+ numberVertices[4] = 10;
+ numberVertices[5] = 14;
+ numberVertices[6] = 0;
+ numberVertices[7] = 14;
+}
+
+BPMDisplay::~BPMDisplay(void) {
+}
+
+void BPMDisplay::draw(int t, int bpm) {
+ shader->useProgram();
+ shader->setTexture(BPMtex);
+
+ shader->setTranslation(this->x, this->y);
+ shader->setPositions(4, this->labelVertices);
+ shader->setTextureOffset(0, 0);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ int digit;
+ shader->setPositions(4, this->numberVertices);
+
+ digit = (bpm / 100) % 10;
+ shader->setTranslation(this->x + 45, this->y);
+ shader->setTextureOffset(40 + 10 * digit, 0);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ digit = (bpm / 10) % 10;
+ shader->setTranslation(this->x + 57, this->y);
+ shader->setTextureOffset(40 + 10 * digit, 0);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ digit = bpm % 10;
+ shader->setTranslation(this->x + 69, this->y);
+ shader->setTextureOffset(40 + 10 * digit, 0);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+}
\ No newline at end of file
+#ifndef _BPMDISPLAY_H
+#define _BPMDISPLAY_H
+
+#include "Component.h"
+#include "DecalShader.h"
+#include "Texture.h"
+
+class BPMDisplay : public Component {
+public:
+ BPMDisplay();
+ ~BPMDisplay();
+
+ void draw(int t, int bpm);
+
+ DecalShader* shader;
+private:
+ Texture* BPMtex;
+
+ GLshort labelVertices[8];
+ GLshort numberVertices[8];
+};
+
+#endif //_BPMDISPLAY_H
\ No newline at end of file
+#include "BeatChart.h"
+
+#include <GLES2/gl2.h>
+#include <string.h>
+
+BeatChart::BeatChart(Clock* clock) : Component() {
+ this->clock = clock;
+ this->beats_per_measure = 4;
+ this->beat = 0;
+ this->beat_start = 0;
+ setSize(320, 10);
+}
+
+BeatChart::~BeatChart(void) {
+}
+
+void BeatChart::setSize(int w, int h) {
+ Component::setSize(w, h);
+
+ blip_w = (float)w / (float)this->beats_per_measure;
+ blipVertices[0] = 0;
+ blipVertices[1] = 0;
+ blipVertices[2] = (GLshort)blip_w;
+ blipVertices[3] = 0;
+ blipVertices[4] = (GLshort)blip_w;
+ blipVertices[5] = h;
+ blipVertices[6] = 0;
+ blipVertices[7] = h;
+}
+
+void BeatChart::draw(int t) {
+ shader->useProgram();
+ int beat = clock->getBeat();
+ if (beat != this->beat) {
+ this->beat = beat;
+ this->beat_start = t;
+ }
+ int delta = t - this->beat_start;
+ this->fade_time = 60000.0 / clock->getBPM();
+ if (delta < this->fade_time) {
+ shader->setFade(1.0 - (GLfloat)delta / (GLfloat)this->fade_time);
+ } else {
+ shader->setFade(0.0);
+ }
+ shader->setColor(0, 0, 0, 1);
+ shader->setFadeColor(0.75, 0.75, 0.75, 1.0);
+ shader->setPositions(4, this->blipVertices);
+ shader->setTranslation(this->x + blip_w * (beat % this->beats_per_measure), this->y);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ shader->resetTransform();
+}
\ No newline at end of file
+#ifndef _BEATCHART_H
+#define _BEATCHART_H
+
+#include <GLES2/gl2.h>
+#include "RShader.h"
+#include "Component.h"
+#include "Clock.h"
+
+class BeatChart : public Component {
+public:
+ BeatChart(Clock* clock);
+ ~BeatChart();
+
+ RShader* shader;
+
+ void setSize(int w, int h);
+ void draw(int t);
+private:
+ Clock* clock;
+ int beats_per_measure;
+ int beat, beat_start;
+ int fade_time;
+ float blip_w;
+ GLshort blipVertices[8];
+
+ void setTranslation(int x, int y);
+};
+
+#endif //_BEATCHART_H
\ No newline at end of file
+#include "Clock.h"
+#include <SDL.h>
+
+Clock::Clock() {
+ this->bpm = 128;
+ this->slices_per_beat = 4;
+ this->beats_per_measure = 4;
+ this->recalculate();
+ this->start();
+}
+
+Clock::Clock(float bpm) {
+ this->bpm = bpm;
+ this->slices_per_beat = 4;
+ this->beats_per_measure = 4;
+ this->recalculate();
+ this->start();
+}
+
+Clock::Clock(float bpm, int slices_per_beat) {
+ this->bpm = bpm;
+ this->slices_per_beat = slices_per_beat;
+ this->beats_per_measure = 4;
+ this->recalculate();
+ this->start();
+}
+
+Clock::Clock(float bpm, int slices_per_beat, int beats_per_measure) {
+ this->bpm = bpm;
+ this->slices_per_beat = slices_per_beat;
+ this->beats_per_measure = beats_per_measure;
+ this->recalculate();
+ this->start();
+}
+
+Clock::~Clock(void) {
+}
+
+void Clock::start() {
+ this->t_next = this->t_start = SDL_GetTicks();
+ this->slice = 0;
+ this->tick(this->t_start);
+}
+
+int Clock::tick(int t) {
+ int start_slice = this->slice;
+
+ while (t >= this->t_next) {
+ this->slice++;
+ this->t_next = this->t_start + this->ms_per_slice * this->slice;
+ }
+
+ return (this->slice - start_slice);
+}
+
+void Clock::setBPM(float bpm) {
+ if (bpm < 20)
+ return;
+ this->t_start -= this->slice * (((60000.0 / bpm) - (60000.0 / this->bpm)) / this->slices_per_beat);
+ this->bpm = bpm;
+ this->recalculate();
+}
+
+float Clock::getBPM() {
+ return this->bpm;
+}
+
+void Clock::setSlicesPerBeat(int slices_per_beat) {
+ this->slices_per_beat = slices_per_beat;
+ this->recalculate();
+}
+
+int Clock::getMeasure() {
+ return this->getBeat() / this->beats_per_measure;
+}
+
+int Clock::getBeat() {
+ return this->slice / this->slices_per_beat;
+}
+
+int Clock::getSlice() {
+ return this->slice;
+}
+
+void Clock::recalculate() {
+ this->ms_per_slice = 60000.0 / (this->bpm * this->slices_per_beat);
+}
\ No newline at end of file
+#ifndef _CLOCK_H
+#define _CLOCK_H
+
+class Clock
+{
+public:
+ Clock();
+ Clock(float bpm);
+ Clock(float bpm, int slices_per_beat);
+ Clock(float bpm, int slices_per_beat, int beats_per_measure);
+ ~Clock(void);
+
+ void start();
+ int tick(int t);
+ void setBPM(float bpm);
+ float getBPM();
+ void setSlicesPerBeat(int slices_per_beat);
+ int getMeasure();
+ int getBeat();
+ int getSlice();
+private:
+ void recalculate();
+
+ float bpm;
+ int beats_per_measure;
+ int slices_per_beat;
+ float ms_per_slice;
+ int slice;
+ int t_start;
+ int t_next;
+};
+
+#endif //_CLOCK_H
\ No newline at end of file
+#include "Component.h"
+
+Component::Component(void) {
+ this->setPosition(0, 0);
+ this->setSize(0, 0);
+}
+
+Component::Component(int x, int y, int w, int h) {
+ this->setPosition(x, y);
+ this->setSize(w, h);
+}
+
+Component::~Component(void) { }
+
+void Component::setPosition(int x, int y) {
+ this->x = x;
+ this->y = y;
+}
+
+void Component::setSize(int w, int h) {
+ this->w = w;
+ this->h = h;
+}
\ No newline at end of file
+#ifndef _COMPONENT_H
+#define _COMPONENT_H
+
+class Component {
+public:
+ Component();
+ Component(int x, int y, int w, int h);
+ ~Component();
+
+ void setPosition(int x, int y);
+ void setSize(int w, int h);
+protected:
+ int x, y, w, h;
+};
+
+#endif //_COMPONENT_H
\ No newline at end of file
+#include "DecalShader.h"
+#include <GLES2/gl2.h>
+#include "Texture.h"
+
+DecalShader::DecalShader(void) {
+ vertexShader = compileShader(
+ "uniform mat4 Projection;\n"
+ "uniform mat4 Transform;\n"
+ "uniform sampler2D Texture;\n"
+ "uniform ivec2 TextureSize;\n"
+ "uniform ivec2 TextureOffset;\n"
+ "attribute vec2 Position;\n"
+ "varying mediump vec2 TexturePosition;\n"
+ "void main() {\n"
+ " gl_Position = Projection * Transform * vec4(Position, 0.0, 1.0);\n"
+ " TexturePosition = Position + vec2(TextureOffset);\n"
+ " TexturePosition.x = TexturePosition.x / float(TextureSize.x);\n"
+ " TexturePosition.y = TexturePosition.y / float(TextureSize.y);\n"
+ "}",
+ GL_VERTEX_SHADER
+ );
+ fragmentShader = compileShader(
+ "uniform sampler2D Texture;\n"
+ "varying mediump vec2 TexturePosition;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(Texture, TexturePosition);\n"
+ "}",
+ GL_FRAGMENT_SHADER
+ );
+ linkProgram();
+
+ glUseProgram(shaderProgram);
+
+ basicInit();
+
+ uTexture = glGetUniformLocation(shaderProgram, "Texture");
+ uTextureSize = glGetUniformLocation(shaderProgram, "TextureSize");
+ uTextureOffset = glGetUniformLocation(shaderProgram, "TextureOffset");
+ setTextureOffset(0, 0);
+}
+
+DecalShader::~DecalShader(void) {
+}
+
+void DecalShader::setTexture(Texture* texture) {
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, texture->getTextureUnit());
+ glUniform1i(this->uTexture, 0);
+ glUniform2i(this->uTextureSize, texture->getWidth(), texture->getHeight());
+}
+
+void DecalShader::setTextureOffset(int x, int y) {
+ glUniform2i(uTextureOffset, x, y);
+}
\ No newline at end of file
+#ifndef _DECALSHADER_H
+#define _DECALSHADER_H
+
+#include <GLES2/gl2.h>
+#include "Shader.h"
+#include "Texture.h"
+
+class DecalShader : public Shader {
+public:
+ DecalShader();
+ ~DecalShader();
+
+ void setTexture(Texture* texture);
+ void setTextureOffset(int x, int y);
+private:
+ GLuint uTexture;
+ GLuint uTextureSize;
+ GLuint uTextureOffset;
+};
+
+#endif //_DECALSHADER_H
\ No newline at end of file
+/* KickMan - a simple pocket drum machine
+ * Copyright (C) 2011 The Dominion of Awesome
+ */
+#include <stdio.h>
+#include <math.h>
+
+#include <GLES2/gl2.h>
+#include "SDL.h"
+#include "PDL.h"
+#include "SDL_mixer.h"
+
+#include "RButton.h"
+#include "Clock.h"
+#include "BeatChart.h"
+#include "Sequencer.h"
+#include "BPMDisplay.h"
+
+SDL_Surface *screen;
+RShader* rshader;
+DecalShader* decalshader;
+RButton* button[9];
+Clock* clock;
+BeatChart* beatChart;
+Sequencer* sequencer;
+BPMDisplay* bpmDisplay;
+Mix_Chunk* samples[9];
+int last_press[9];
+GLfloat Projection[4][4];
+int BPM_change[2];
+
+
+#ifdef WIN32
+extern "C"
+#endif
+GL_API int GL_APIENTRY _dgles_load_library(void *, void *(*)(void *, const char *));
+
+static void *proc_loader(void *h, const char *name)
+{
+ (void) h;
+ return SDL_GL_GetProcAddress(name);
+}
+
+int event_filter(const SDL_Event* event) {
+ return (int)(event->type == SDL_MOUSEBUTTONDOWN || event->type == SDL_MOUSEBUTTONUP ||
+ event->type == SDL_KEYDOWN || event->type == SDL_KEYUP ||
+ event->type == SDL_ACTIVEEVENT || event->type == SDL_QUIT);
+}
+
+void InitProjection() {
+ // Create an orthographic projection with screen coordinates
+ /*
+ GLfloat Projection[] = {
+ 2.0 / (GLfloat)screen->w, 0.0, 0.0, -1.0,
+ 0.0, -2.0 / (GLfloat)screen->h, 0.0, 1.0,
+ 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0
+ };
+ */
+
+ // Lord knows why this works and the above fails.
+ memset(Projection, 0, sizeof(Projection));
+
+ Projection[0][0] = 2.0 / (GLfloat)screen->w;
+ Projection[3][0] = -1.0;
+ Projection[1][1] = -2.0 / (GLfloat)screen->h;
+ Projection[3][1] = 1.0f;
+ Projection[2][2] = 1.0f;
+ Projection[3][3] = 1.0f;
+}
+
+void press_button(int channel, int t) {
+ Mix_PlayChannel(-1, samples[channel], 0);
+ sequencer->set(clock->getSlice() % 16, channel);
+ last_press[channel] = t;
+}
+
+void draw(int t) {
+ glClear (GL_COLOR_BUFFER_BIT);
+
+ for (int i = 0; i < 9; i++)
+ button[i]->draw(t);
+ beatChart->draw(t);
+ bpmDisplay->draw(t, clock->getBPM());
+
+ SDL_GL_SwapBuffers();
+}
+
+int main(int argc, char** argv)
+{
+ // Initialize the SDL library with the Video subsystem
+ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
+ atexit(SDL_Quit);
+
+ // start the PDL library
+ PDL_Init(0);
+ atexit(PDL_Quit);
+
+ // Tell it to use OpenGL version 2.0
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+
+ // Set the video mode to full screen with OpenGL-ES support
+ screen = SDL_SetVideoMode(320, 480, 0, SDL_OPENGL);
+
+#if WIN32
+ // Load the desktop OpenGL-ES emulation library
+ _dgles_load_library(NULL, proc_loader);
+#endif
+
+ // Set up event filtering
+ //SDL_SetEventFilter(event_filter);
+
+ InitProjection();
+ rshader = new RShader();
+ rshader->setProjection((GLfloat*)Projection);
+ decalshader = new DecalShader();
+ decalshader->setProjection((GLfloat*)Projection);
+
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 3; y++) {
+ int i = y * 3 + x;
+ button[i] = new RButton(x * 100 + (x + 1) * 5, y * 100 + (y + 1) * 5, 100, 100);
+ button[i]->shader = rshader;
+ }
+ }
+
+ for (int i = 0; i < 9; i++) {
+ last_press[i] = 0;
+ }
+
+ if (Mix_OpenAudio(44100, AUDIO_S16SYS, 1, 1024) == -1) {
+ printf("Could not initialize audio\n");
+ exit(1);
+ }
+ int channels = Mix_AllocateChannels(9);
+ //printf("Allocated %d channels\n", channels);
+
+ samples[0] = Mix_LoadWAV("kits/Lunchbox-Detroit/HHt9.wav");
+ samples[1] = Mix_LoadWAV("kits/Lunchbox-Detroit/HHo029.wav");
+ samples[2] = Mix_LoadWAV("kits/Lunchbox-Detroit/TT019.wav");
+ samples[3] = Mix_LoadWAV("kits/Lunchbox-Detroit/TT139.wav");
+ samples[4] = Mix_LoadWAV("kits/Lunchbox-Detroit/SD299.wav");
+ samples[5] = Mix_LoadWAV("kits/Lunchbox-Detroit/RIM019.wav");
+ samples[6] = Mix_LoadWAV("kits/Lunchbox-Detroit/BD179.wav");
+ samples[7] = Mix_LoadWAV("kits/Lunchbox-Detroit/SD019.wav");
+ samples[8] = Mix_LoadWAV("kits/Lunchbox-Detroit/CLAP029.wav");
+
+ clock = new Clock(128, 4);
+ beatChart = new BeatChart(clock);
+ beatChart->shader = rshader;
+ beatChart->setPosition(0, screen->h - 10);
+ sequencer = new Sequencer(4, 4);
+ bpmDisplay = new BPMDisplay();
+ bpmDisplay->shader = decalshader;
+ bpmDisplay->setPosition(5, 325);
+
+
+ // Event descriptor
+ SDL_Event Event;
+ Uint32 lastEvent;
+ bool paused = false;
+ int t;
+ int last_t = 0;
+
+ while (1) {
+ bool gotEvent;
+ if (paused) {
+ SDL_WaitEvent(&Event);
+ gotEvent = true;
+ }
+ else {
+ gotEvent = SDL_PollEvent(&Event);
+ t = SDL_GetTicks();
+ }
+
+ while (gotEvent) {
+ switch (Event.type) {
+ // List of keys that have been pressed
+ case SDL_KEYDOWN:
+ switch (Event.key.keysym.sym) {
+ // Escape forces us to quit the app
+ // this is also sent when the user makes a back gesture
+ case SDLK_ESCAPE:
+ Event.type = SDL_QUIT;
+ break;
+ case SDLK_SPACE:
+ clock->start();
+ break;
+ case SDLK_q:
+ clock->setBPM(clock->getBPM() + 1);
+ BPM_change[1] = t;
+ break;
+ case SDLK_a:
+ clock->setBPM(clock->getBPM() - 1);
+ BPM_change[0] = t;
+ break;
+ case SDLK_e:
+ button[0]->press();
+ press_button(0, t);
+ break;
+ case SDLK_r:
+ button[1]->press();
+ press_button(1, t);
+ break;
+ case SDLK_t:
+ button[2]->press();
+ press_button(2, t);
+ break;
+ case SDLK_d:
+ button[3]->press();
+ press_button(3, t);
+ break;
+ case SDLK_f:
+ button[4]->press();
+ press_button(4, t);
+ break;
+ case SDLK_g:
+ button[5]->press();
+ press_button(5, t);
+ break;
+ case SDLK_x:
+ button[6]->press();
+ press_button(6, t);
+ break;
+ case SDLK_c:
+ button[7]->press();
+ press_button(7, t);
+ break;
+ case SDLK_v:
+ button[8]->press();
+ press_button(8, t);
+ break;
+ case SDLK_BACKSPACE:
+ sequencer->clearAll();
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case SDL_KEYUP:
+ switch (Event.key.keysym.sym) {
+ case SDLK_q:
+ BPM_change[1] = 0;
+ break;
+ case SDLK_a:
+ BPM_change[0] = 0;
+ break;
+ case SDLK_e:
+ last_press[0] = 0;
+ break;
+ case SDLK_r:
+ last_press[1] = 0;
+ break;
+ case SDLK_t:
+ last_press[2] = 0;
+ break;
+ case SDLK_d:
+ last_press[3] = 0;
+ break;
+ case SDLK_f:
+ last_press[4] = 0;
+ break;
+ case SDLK_g:
+ last_press[5] = 0;
+ break;
+ case SDLK_x:
+ last_press[6] = 0;
+ break;
+ case SDLK_c:
+ last_press[7] = 0;
+ break;
+ case SDLK_v:
+ last_press[8] = 0;
+ break;
+ }
+ break;
+
+ case SDL_ACTIVEEVENT:
+ if (Event.active.state == SDL_APPACTIVE) {
+ paused = !Event.active.gain;
+ }
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ for (int i = 0; i < 9; i++) {
+ if (button[i]->testHit(Event.button.x, Event.button.y))
+ press_button(i, t);
+ }
+ break;
+
+ case SDL_MOUSEBUTTONUP:
+ for (int i = 0; i < 9; i++)
+ last_press[i] = 0;
+ break;
+
+ case SDL_QUIT:
+ exit(0);
+ break;
+ }
+ gotEvent = SDL_PollEvent(&Event);
+ t = SDL_GetTicks();
+ }
+
+ int tick = clock->tick(t);
+ int slice = clock->getSlice();
+
+ for (int i = 0; i < 9; i++) {
+ if (last_press[i] != 0 && t - last_press[i] > 500) {
+ sequencer->clearChannel(i);
+ last_press[i] = 0;
+ }
+ }
+
+ if (tick > 0) {
+ unsigned int c = sequencer->get(slice % 16);
+ if (c != 0) {
+ for (int i = 0; i < 9; i++) {
+ if (c & 1) {
+ button[i]->press();
+ Mix_PlayChannel(-1, samples[i], 0);
+ }
+ c >>= 1;
+ }
+ }
+ }
+
+ // Skip drawing if we drew less than 50ms ago
+ if (t - last_t < 50)
+ continue;
+ last_t = t;
+
+ if (BPM_change[0] != 0 && t - BPM_change[0] > 250) {
+ clock->setBPM(clock->getBPM() - 1);
+ } else if (BPM_change[1] != 0 && t - BPM_change[1] > 250) {
+ clock->setBPM(clock->getBPM() + 1);
+ }
+
+ for (int i = 0; i < 9; i++)
+ button[i]->update(t);
+
+ draw(t);
+ }
+
+ return 0;
+}
\ No newline at end of file
+#include <GLES2/gl2.h>
+#include <SDL.h>
+
+#include "RButton.h"
+
+RButton::RButton(int x, int y, int w, int h) {
+ setRect(x, y, w, h);
+ this->setColor(0.0, 0.0, 1.0);
+ this->setFadeColor(1.0, 1.0, 1.0);
+ this->lastPress = 0;
+}
+
+RButton::~RButton(void) {
+}
+
+void RButton::setRect(int x, int y, int w, int h) {
+ vertices[0] = x;
+ vertices[1] = y;
+ vertices[2] = x + w;
+ vertices[3] = y;
+ vertices[4] = x + w;
+ vertices[5] = y + h;
+ vertices[6] = x;
+ vertices[7] = y + h;
+}
+
+bool RButton::testHit(int x, int y) {
+ bool r = (x > vertices[0] && x < vertices[2] && y > vertices[1] && y < vertices[5]);
+ if (r)
+ this->press();
+ return r;
+}
+
+void RButton::press() {
+ this->lastPress = SDL_GetTicks();
+}
+
+void RButton::update(int t) {
+}
+
+void RButton::draw(int t) {
+ shader->useProgram();
+ shader->setPositions(4, vertices);
+ shader->setColor(&color);
+ shader->setFadeColor(&fadeColor);
+
+ int delta = t - lastPress;
+ if (delta < PRESS_FADE)
+ shader->setFade(1.0 - (GLfloat)delta / (GLfloat)PRESS_FADE);
+ else
+ shader->setFade(0.0);
+
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+}
+
+void RButton::setColor(float r, float g, float b) {
+ this->color.r = r;
+ this->color.g = g;
+ this->color.b = b;
+ this->color.a = 1.0;
+}
+
+void RButton::setFadeColor(float r, float g, float b) {
+ this->fadeColor.r = r;
+ this->fadeColor.g = g;
+ this->fadeColor.b = b;
+ this->fadeColor.a = 1.0;
+}
\ No newline at end of file
+#ifndef _RBUTTON_H
+#define _RBUTTON_H
+
+#include "RShader.h"
+#include "misc.h"
+
+#define PRESS_FADE 500
+
+class RButton {
+public:
+ RShader* shader;
+
+ RButton(int x, int y, int w, int h);
+ ~RButton(void);
+
+ bool testHit(int x, int y);
+ void press();
+ void update(int t);
+ void draw(int t);
+ void setColor(float r, float g, float b);
+ void setFadeColor(float r, float g, float b);
+private:
+ GLshort vertices[8];
+ Color color;
+ Color fadeColor;
+ int lastPress;
+
+ void setRect(int x, int y, int w, int h);
+};
+
+#endif //_RBUTTON_H
\ No newline at end of file
+#include <GLES2/gl2.h>
+
+#include "RShader.h"
+
+RShader::RShader() {
+ vertexShader = compileShader(
+ "uniform mat4 Projection;\n"
+ "uniform mat4 Transform;\n"
+ "attribute vec2 Position;\n"
+ "void main() {\n"
+ " gl_Position = Projection * Transform * vec4(Position, 0.0, 1.0);\n"
+ "}",
+ GL_VERTEX_SHADER
+ );
+ fragmentShader = compileShader(
+ "uniform mediump vec4 Color;\n"
+ "uniform mediump vec4 FadeColor;\n"
+ "uniform mediump float Fade;\n"
+ "void main() {\n"
+ " gl_FragColor = (1.0 - Fade) * Color + Fade * FadeColor;\n"
+ "}",
+ GL_FRAGMENT_SHADER
+ );
+ linkProgram();
+
+ glUseProgram(shaderProgram);
+
+ basicInit();
+
+ uColor = glGetUniformLocation(shaderProgram, "Color");
+ uFadeColor = glGetUniformLocation(shaderProgram, "FadeColor");
+ uFade = glGetUniformLocation(shaderProgram, "Fade");
+
+ static Color defaultColor = {0.5, 0.5, 0.5, 1.0};
+ setColor(&defaultColor);
+ setFade(0.0);
+}
+
+RShader::~RShader() {
+}
+
+void RShader::setColor(Color* c) {
+ this->setColor(c->r, c->g, c->b, c->a);
+}
+
+void RShader::setColor(float r, float g, float b, float a) {
+ glUniform4f(uColor, r, g, b, a);
+}
+
+void RShader::setFadeColor(Color* c) {
+ this->setFadeColor(c->r, c->g, c->b, c->a);
+}
+
+void RShader::setFadeColor(float r, float g, float b, float a) {
+ glUniform4f(uFadeColor, r, g, b, a);
+}
+
+void RShader::setFade(GLfloat fade) {
+ glUniform1f(uFade, fade);
+}
\ No newline at end of file
+#ifndef _RSHADER_H
+#define _RSHADER_H
+
+#include "Shader.h"
+
+#include <GLES2/gl2.h>
+#include "misc.h"
+
+class RShader : public Shader {
+public:
+ RShader();
+ ~RShader();
+ void setColor(Color* c);
+ void setColor(float r, float g, float b, float a);
+ void setFadeColor(Color* c);
+ void setFadeColor(float r, float g, float b, float a);
+ void setFade(GLfloat fade);
+private:
+ GLuint uColor;
+ GLuint uFadeColor;
+ GLuint uFade;
+};
+
+#endif //_RSHADER_H
\ No newline at end of file
+#include <stdlib.h>
+#include <stdio.h>
+#include "Sequencer.h"
+
+Sequencer::Sequencer() {
+ this->slices = 0;
+ this->beats_per_measure = 4;
+ this->slices_per_beat = 4;
+ this->realloc();
+}
+
+Sequencer::Sequencer(int beats_per_measure) {
+ this->slices = 0;
+ this->beats_per_measure = beats_per_measure;
+ this->slices_per_beat = 4;
+ this->realloc();
+}
+
+Sequencer::Sequencer(int beats_per_measure, int slices_per_beat) {
+ this->slices = 0;
+ this->beats_per_measure = beats_per_measure;
+ this->slices_per_beat = slices_per_beat;
+ this->realloc();
+}
+
+Sequencer::~Sequencer(void) {
+ if (this->slices != 0)
+ free(this->slices);
+}
+
+void Sequencer::realloc() {
+ if (this->slices != 0)
+ free(this->slices);
+ this->slices = (unsigned int *) malloc(sizeof(unsigned int) * this->beats_per_measure * this->slices_per_beat);
+ if (slices == 0) {
+ perror("allocating slices");
+ exit(1);
+ }
+ this->clearAll();
+}
+
+void Sequencer::set(int slice, unsigned int channel) {
+ this->slices[slice] |= 1 << channel;
+}
+
+unsigned int Sequencer::get(int slice) {
+ return this->slices[slice];
+}
+
+void Sequencer::clear(int slice, unsigned int channel) {
+ this->slices[slice] &= ~(1 << channel);
+}
+
+void Sequencer::clearChannel(unsigned int channel) {
+ for (int i = 0; i < this->beats_per_measure * this->slices_per_beat; i++)
+ this->clear(i, channel);
+}
+
+void Sequencer::clearAll() {
+ for (int i = 0; i < this->beats_per_measure * this->slices_per_beat; i++)
+ this->slices[i] = 0;
+}
\ No newline at end of file
+#ifndef _SEQUENCER_H
+#define _SEQUENCER_H
+
+class Sequencer {
+public:
+ Sequencer();
+ Sequencer(int beats_per_measure);
+ Sequencer(int beats_per_measure, int slices_per_beat);
+ ~Sequencer(void);
+
+ void set(int slice, unsigned int channel);
+ unsigned int get(int slice);
+ void clear(int slice, unsigned int channel);
+ void clearChannel(unsigned int channel);
+ void clearAll();
+private:
+ int slices_per_beat;
+ int beats_per_measure;
+ unsigned int* slices;
+
+ void realloc();
+};
+
+#endif //_SEQUENCER_H
\ No newline at end of file
+#include "Shader.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+Shader::Shader(void) {
+}
+
+Shader::~Shader(void) {
+}
+
+GLuint Shader::compileShader(const char *code, GLenum type) {
+ int status;
+ GLuint shader;
+
+ shader = glCreateShader(type);
+ glShaderSource(shader, 1, &code, NULL);
+ glCompileShader(shader);
+
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+ if (status != GL_TRUE) {
+ char log[4096];
+ int len;
+
+ glGetShaderInfoLog(shader, 4096, &len, log);
+ printf("Shader compilation failed:\n%s\n", log);
+ exit(1);
+ }
+ return shader;
+}
+
+void Shader::linkProgram() {
+ int status;
+
+ shaderProgram = glCreateProgram();
+ glAttachShader(shaderProgram, vertexShader);
+ glAttachShader(shaderProgram, fragmentShader);
+ glLinkProgram(shaderProgram);
+ glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status);
+ if (status != GL_TRUE) {
+ char log[4096];
+ int len;
+
+ glGetProgramInfoLog(shaderProgram, 4096, &len, log);
+ printf("Shader program link failed:\n%s\n", log);
+ }
+}
+
+void Shader::basicInit() {
+ // uniforms
+ uProjection = glGetUniformLocation(shaderProgram, "Projection");
+ uTransform = glGetUniformLocation(shaderProgram, "Transform");
+
+ // attributes
+ aPosition = glGetAttribLocation(shaderProgram, "Position");
+ glEnableVertexAttribArray(aPosition);
+
+ this->resetTransform();
+}
+
+void Shader::useProgram() {
+ glUseProgram(shaderProgram);
+}
+
+void Shader::setProjection(GLfloat *matrix) {
+ glUniformMatrix4fv(uProjection, 1, GL_FALSE, matrix);
+}
+
+void Shader::setTransform(GLfloat *matrix) {
+ glUniformMatrix4fv(uTransform, 1, GL_FALSE, matrix);
+}
+
+void Shader::setTranslation(int x, int y) {
+ memset(transform, 0, sizeof(transform));
+ transform[0][0] = 1;
+ transform[1][1] = 1;
+ transform[2][2] = 1;
+ transform[3][3] = 1;
+ transform[3][0] = x;
+ transform[3][1] = y;
+ this->setTransform((GLfloat *)transform);
+}
+
+void Shader::resetTransform() {
+ static GLfloat identity[16] = {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ this->setTransform(identity);
+}
+
+void Shader::setPositions(int count, GLshort *positions) {
+ glVertexAttribPointer(aPosition, 2, GL_SHORT, GL_FALSE, 0, positions);
+}
\ No newline at end of file
+#ifndef _SHADER_H
+#define _SHADER_H
+
+#include <GLES2/gl2.h>
+
+class Shader
+{
+public:
+ Shader();
+ ~Shader();
+
+ void useProgram();
+ void setProjection(GLfloat *matrix);
+ void setTransform(GLfloat *matrix);
+ void setTranslation(int x, int y);
+ void resetTransform();
+ void setPositions(int count, GLshort *vertices);
+protected:
+ GLuint vertexShader;
+ GLuint fragmentShader;
+ GLuint shaderProgram;
+
+ GLfloat transform[4][4];
+
+ GLuint uProjection;
+ GLuint uTransform;
+ GLuint aPosition;
+
+ GLuint compileShader(const char *code, GLenum type);
+ void linkProgram();
+ void basicInit();
+};
+
+#endif //_SHADER_H
\ No newline at end of file
+#include "Texture.h"
+#include <GLES2/gl2.h>
+#include <SDL.h>
+#include <SDL_image.h>
+#include <stdio.h>
+
+Texture::Texture(const char* filename) {
+ SDL_Surface* img = IMG_Load(filename);
+ if (img == 0) {
+ fprintf(stderr, "Could not load %s: %s\n", filename, IMG_GetError());
+ abort();
+ }
+
+ this->w = img->w;
+ this->h = img->h;
+
+ SDL_PixelFormat pf;
+ pf.palette = NULL;
+ pf.BitsPerPixel = 32;
+ pf.BytesPerPixel = 4;
+ pf.Rmask = 0x000000FF;
+ pf.Gmask = 0x0000FF00;
+ pf.Bmask = 0x00FF0000;
+ pf.Amask = 0xFF000000;
+ pf.Rloss = pf.Gloss = pf.Bloss = pf.Aloss = 0;
+ pf.Rshift = 24;
+ pf.Gshift = 16;
+ pf.Bshift = 8;
+ pf.Ashift = 0;
+ pf.colorkey = 0;
+ pf.alpha = 255;
+ SDL_Surface *cimg = SDL_ConvertSurface(img, &pf, 0);
+ if (cimg == 0) {
+ fprintf(stderr, "Could not convert %s to RGBA: %s\n", filename, IMG_GetError());
+ abort();
+ }
+ SDL_FreeSurface(img);
+
+ glActiveTexture(GL_TEXTURE0);
+ glGenTextures(1, &this->textureUnit);
+ glBindTexture(GL_TEXTURE_2D, this->textureUnit);
+ SDL_LockSurface(cimg);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, this->w, this->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, cimg->pixels);
+ SDL_UnlockSurface(cimg);
+ GLenum err = glGetError();
+ if (err != GL_NO_ERROR) {
+ fprintf(stderr, "Error in glTexImage2D: %d\n", err);
+ exit(1);
+ }
+ SDL_FreeSurface(cimg);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+Texture::~Texture() {
+ glDeleteTextures(1, &this->textureUnit);
+}
+
+GLuint Texture::getTextureUnit() {
+ return this->textureUnit;
+}
+
+int Texture::getWidth() {
+ return this->w;
+}
+
+int Texture::getHeight() {
+ return this->h;
+}
\ No newline at end of file
+#ifndef _TEXTURE_H
+#define _TEXTURE_H
+
+#include <GLES2/gl2.h>
+
+class Texture {
+public:
+ Texture(const char* filename);
+ ~Texture();
+
+ GLuint getTextureUnit();
+ int getWidth();
+ int getHeight();
+private:
+ GLuint textureUnit;
+ int w, h;
+};
+
+#endif //_TEXTURE_H
\ No newline at end of file
+#ifndef _MISC_H
+#define _MISC_H
+
+typedef struct color {
+ float r;
+ float g;
+ float b;
+ float a;
+} Color;
+
+#endif //_MISC_H
\ No newline at end of file