/*
 *    Example program for the Allegro library, by Peter Wang.
 *
 *    Demonstrate the audio addons.
 */

#include <allegro5/allegro.h>
#include <allegro5/allegro_acodec.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_ttf.h>
#include <ctype.h>
#include <math.h>
#include <string>
#include <vector>

#include "common.c"

#define DISP_W 800
#define DISP_H 600

using namespace std;

class Element;
class Voice;
class Mixer;
class SampleInstance;
class Sample;
class Audiostream;

struct Context {
   ALLEGRO_FONT *font;
   ALLEGRO_COLOR bg;
   ALLEGRO_COLOR fg;
   ALLEGRO_COLOR fill;
   ALLEGRO_COLOR disabled;
   ALLEGRO_COLOR highlight;
};

class Element {
public:
   Element();
   virtual ~Element() {};
   void set_pos(int x, int y);
   void set_random_pos();
   bool attach(Element *elt);
   bool detach();
   bool is_attached_to(Element const *elt);
   virtual bool is_playing() const = 0;
   virtual bool toggle_playing() = 0;
   virtual float get_gain() const = 0;
   virtual bool adjust_gain(float d) = 0;
   void draw(Context const& ctx, bool highlight) const;
   void draw_arrow(Context const& ctx, float px, float py, bool highlight) const;
   void move_by(int dx, int dy);
   bool contains(int x, int y) const;
   void output_point(float & ox, float & oy) const;
   void input_point(float & ox, float & oy) const;
   virtual char const *get_label() const = 0;

   typedef vector<Element *>::iterator iter;

protected:
   int x, y, w, h;

private:
   virtual bool do_attach(Mixer & mixer);
   virtual bool do_attach(SampleInstance & spl);
   virtual bool do_attach(Audiostream & stream);
   virtual bool do_detach() = 0;
   void set_attached_to(Element const *elt);

   Element const *attached_to;
};

class Voice : public Element {
public:
   Voice();
   ~Voice();
   bool valid() const;
   bool is_playing() const;
   bool toggle_playing();
   float get_gain() const;
   bool adjust_gain(float d);
   char const *get_label() const;
private:
   bool do_attach(Mixer & mixer);
   bool do_attach(SampleInstance & spl);
   bool do_attach(Audiostream & stream);
   bool do_detach();

   ALLEGRO_VOICE *voice;
};

class Mixer : public Element {
public:
   Mixer();
   ~Mixer();
   operator ALLEGRO_MIXER *() const { return mixer; }
   bool is_playing() const;
   bool toggle_playing();
   float get_gain() const;
   bool adjust_gain(float d);
   char const *get_label() const;
private:
   bool do_attach(Mixer & mixer);
   bool do_attach(SampleInstance & spl);
   bool do_attach(Audiostream & stream);
   bool do_detach();

   ALLEGRO_MIXER *mixer;
};

class SampleInstance : public Element {
public:
   SampleInstance();
   ~SampleInstance();
   operator ALLEGRO_SAMPLE_INSTANCE *() const { return splinst; }
   bool is_playing() const;
   bool toggle_playing();
   float get_gain() const;
   bool adjust_gain(float d);
   char const *get_label() const;
   bool set_sample(Sample const & wav);
private:
   bool do_detach();

   ALLEGRO_SAMPLE_INSTANCE *splinst;
   Sample const *spl;
   unsigned pos;
};

class Sample {
public:
   Sample(char const *filename);
   ~Sample();
   operator ALLEGRO_SAMPLE *() const { return spl; }
   bool valid() const;
   string const& get_filename() const;
private:
   ALLEGRO_SAMPLE *spl;
   string filename;
};

class Audiostream : public Element {
public:
   Audiostream(string const& filename);
   ~Audiostream();
   operator ALLEGRO_AUDIO_STREAM *() const { return stream; }
   bool valid() const;
   bool is_playing() const;
   bool toggle_playing();
   float get_gain() const;
   bool adjust_gain(float d);
   char const *get_label() const;
private:
   bool do_detach();

   ALLEGRO_AUDIO_STREAM *stream;
   string filename;
};

/*---------------------------------------------------------------------------*/

static ALLEGRO_PATH *make_path(char const *str)
{
   static ALLEGRO_PATH *dir;
   ALLEGRO_PATH *path;

   if (!dir) {
      dir = al_get_standard_path(ALLEGRO_RESOURCES_PATH);
#ifdef ALLEGRO_MSVC
      {
         /* Hack to cope automatically with MSVC workspaces. */
         const char *last = al_get_path_component(dir, -1);
         if (0 == strcmp(last, "Debug")
            || 0 == strcmp(last, "RelWithDebInfo")
            || 0 == strcmp(last, "Release")
            || 0 == strcmp(last, "Profile")) {
            al_remove_path_component(dir, -1);
         }
      }
#endif
   }

   path = al_create_path(str);
   al_rebase_path(dir, path);
   return path;
}

static void basename(const string & filename, string & out)
{
   size_t pos = filename.find_last_of("/");
   if (pos != string::npos)
      out = filename.substr(pos + 1);
   else
      out = filename;
}

static float clamp(float lo, float mid, float hi)
{
   if (mid < lo)
      return lo;
   if (mid > hi)
      return hi;
   return mid;
}

/*---------------------------------------------------------------------------*/

Element::Element() : x(0), y(0), w(40), h(40), attached_to(0)
{
}

void Element::set_pos(int x, int y)
{
   this->x = x;
   this->y = y;
}

void Element::set_random_pos()
{
   /* Could be smarter. */
   x = rand() % (DISP_W - w);
   y = rand() % (DISP_H - h);
}

bool Element::attach(Element *elt)
{
   Mixer *mixer;
   SampleInstance *splinst;
   Audiostream *stream;
   bool rc = false;

   if ((mixer = dynamic_cast<Mixer *>(elt))) {
      rc = do_attach(*mixer);
   }
   else if ((splinst = dynamic_cast<SampleInstance *>(elt))) {
      rc = do_attach(*splinst);
   }
   else if ((stream = dynamic_cast<Audiostream *>(elt))) {
      rc = do_attach(*stream);
   }
   if (rc) {
      elt->set_attached_to(this);
   }
   return rc;
}

bool Element::do_attach(Mixer & other)
{
   (void)other;
   return false;
}

bool Element::do_attach(SampleInstance & other)
{
   (void)other;
   return false;
}

bool Element::do_attach(Audiostream & other)
{
   (void)other;
   return false;
}

bool Element::detach()
{
   bool rc = attached_to && do_detach();
   if (rc) {
      attached_to = NULL;
   }
   return rc;
}

void Element::set_attached_to(Element const *attached_to)
{
   this->attached_to = attached_to;
}

bool Element::is_attached_to(Element const *elt)
{
   return attached_to == elt;
}

void Element::draw(Context const& ctx, bool highlight) const
{
   if (attached_to) {
      float x2, y2;
      attached_to->input_point(x2, y2);
      draw_arrow(ctx, x2, y2, highlight);
   }

   ALLEGRO_COLOR textcol = ctx.fg;
   ALLEGRO_COLOR boxcol = ctx.fg;
   if (highlight)
      boxcol = ctx.highlight;
   if (!is_playing())
      textcol = ctx.disabled;

   float rad = 7.0;
   al_draw_filled_rounded_rectangle(x+0.5, y+0.5, x+w-0.5, y+h-0.5, rad, rad, ctx.fill);
   al_draw_rounded_rectangle(x+0.5, y+0.5, x+w-0.5, y+h-0.5, rad, rad, boxcol, 1.0);

   float th = al_get_font_line_height(ctx.font);
   al_draw_text(ctx.font, textcol, x + w/2, y + h/2 - th/2,
      ALLEGRO_ALIGN_CENTRE, get_label());

   float gain = get_gain();
   if (gain > 0.0) {
      float igain = 1.0 - clamp(0.0, gain, 2.0)/2.0;
      al_draw_rectangle(x+w+1.5, y+h*igain, x+w+3.5, y+h, ctx.fg, 1.0);
   }
}

void Element::draw_arrow(Context const& ctx, float x2, float y2, bool highlight) const
{
   float x1, y1;
   ALLEGRO_COLOR col;
   output_point(x1, y1);
   col = (highlight ? ctx.highlight : ctx.fg);
   al_draw_line(x1, y1, x2, y2, col, 1.0);

   float a = atan2(y1-y2, x1-x2);
   float a1 = a + 0.5;
   float a2 = a - 0.5;
   float len = 7.0;
   al_draw_line(x2, y2, x2 + len*cos(a1), y2 + len*sin(a1), col, 1.0);
   al_draw_line(x2, y2, x2 + len*cos(a2), y2 + len*sin(a2), col, 1.0);
}

void Element::move_by(int dx, int dy)
{
   x += dx;
   y += dy;

   if (x < 0) x = 0;
   if (y < 0) y = 0;
   if (x+w > DISP_W) x = DISP_W-w;
   if (y+h > DISP_H) y = DISP_H-h;
}

bool Element::contains(int px, int py) const
{
   return px >= x && px < x + w
      &&  py >= y && py < y + h;
}

void Element::output_point(float & ox, float & oy) const
{
   ox = x + w/2;
   oy = y;
}

void Element::input_point(float & ox, float & oy) const
{
   ox = x + w/2;
   oy = y + h;
}

/*---------------------------------------------------------------------------*/

Voice::Voice()
{
   set_random_pos();
   voice = al_create_voice(44100, ALLEGRO_AUDIO_DEPTH_INT16,
      ALLEGRO_CHANNEL_CONF_2);
}

Voice::~Voice()
{
   al_destroy_voice(voice);
}

bool Voice::valid() const
{
   return (voice != NULL);
}

bool Voice::do_attach(Mixer & mixer)
{
   return al_attach_mixer_to_voice(mixer, voice);
}

bool Voice::do_attach(SampleInstance & spl)
{
   return al_attach_sample_instance_to_voice(spl, voice);
}

bool Voice::do_attach(Audiostream & stream)
{
   return al_attach_audio_stream_to_voice(stream, voice);
}

bool Voice::do_detach()
{
   return false;
}

bool Voice::is_playing() const
{
   return al_get_voice_playing(voice);
}

bool Voice::toggle_playing()
{
   bool playing = al_get_voice_playing(voice);
   return al_set_voice_playing(voice, !playing);
}

float Voice::get_gain() const
{
   return 0.0;
}

bool Voice::adjust_gain(float d)
{
   (void)d;
   return false;
}

char const *Voice::get_label() const
{
   return "Voice";
}

/*---------------------------------------------------------------------------*/

Mixer::Mixer()
{
   set_random_pos();
   mixer = al_create_mixer(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32,
      ALLEGRO_CHANNEL_CONF_2);
}

Mixer::~Mixer()
{
   al_destroy_mixer(mixer);
}

bool Mixer::do_attach(Mixer & other)
{
   return al_attach_mixer_to_mixer(other, mixer);
}

bool Mixer::do_attach(SampleInstance & spl)
{
   return al_attach_sample_instance_to_mixer(spl, mixer);
}

bool Mixer::do_attach(Audiostream & stream)
{
   return al_attach_audio_stream_to_mixer(stream, mixer);
}

bool Mixer::do_detach()
{
   return al_detach_mixer(mixer);
}

bool Mixer::is_playing() const
{
   return al_get_mixer_playing(mixer);
}

bool Mixer::toggle_playing()
{
   bool playing = al_get_mixer_playing(mixer);
   return al_set_mixer_playing(mixer, !playing);
}

float Mixer::get_gain() const
{
   return al_get_mixer_gain(mixer);
}

bool Mixer::adjust_gain(float d)
{
   float gain = al_get_mixer_gain(mixer) + d;
   gain = clamp(0, gain, 2);
   return al_set_mixer_gain(mixer, gain);
}

char const *Mixer::get_label() const
{
   return "Mixer";
}

/*---------------------------------------------------------------------------*/

SampleInstance::SampleInstance() : spl(NULL), pos(0)
{
   w = 150;
   set_random_pos();
   splinst = al_create_sample_instance(NULL);
}

SampleInstance::~SampleInstance()
{
   al_destroy_sample_instance(splinst);
}

bool SampleInstance::do_detach()
{
   return al_detach_sample_instance(splinst);
}

bool SampleInstance::is_playing() const
{
   return al_get_sample_instance_playing(splinst);
}

bool SampleInstance::toggle_playing()
{
   bool playing = is_playing();
   if (playing)
      pos = al_get_sample_instance_position(splinst);
   else
      al_set_sample_instance_position(splinst, pos);
   return al_set_sample_instance_playing(splinst, !playing);
}

float SampleInstance::get_gain() const
{
   return al_get_sample_instance_gain(splinst);
}

bool SampleInstance::adjust_gain(float d)
{
   float gain = al_get_sample_instance_gain(splinst) + d;
   gain = clamp(0, gain, 2);
   return al_set_sample_instance_gain(splinst, gain);
}

bool SampleInstance::set_sample(Sample const & spl)
{
   bool playing = is_playing();
   bool rc = al_set_sample(splinst, spl);
   if (rc) {
      this->spl = &spl;
      al_set_sample_instance_playmode(splinst, ALLEGRO_PLAYMODE_LOOP);
      al_set_sample_instance_playing(splinst, playing);
   }
   return rc;
}

char const *SampleInstance::get_label() const
{
   if (spl) {
      return spl->get_filename().c_str();
   }
   return "No sample";
}

/*---------------------------------------------------------------------------*/

Sample::Sample(char const *filename)
{
   spl = al_load_sample(filename);
   basename(filename, this->filename);
}

Sample::~Sample()
{
   al_destroy_sample(spl);
}

bool Sample::valid() const
{
   return spl;
}

string const& Sample::get_filename() const
{
   return filename;
}

/*---------------------------------------------------------------------------*/

Audiostream::Audiostream(string const& filename)
{
   w = 150;
   set_random_pos();
   basename(filename, this->filename);

   stream = al_load_audio_stream(filename.c_str(), 4, 2048);
   if (stream) {
      al_set_audio_stream_playmode(stream, ALLEGRO_PLAYMODE_LOOP);
   }
}

Audiostream::~Audiostream()
{
   al_destroy_audio_stream(stream);
}

bool Audiostream::valid() const
{
   return stream;
}

bool Audiostream::do_detach()
{
   return al_detach_audio_stream(stream);
}

bool Audiostream::is_playing() const
{
   return al_get_audio_stream_playing(stream);
}

bool Audiostream::toggle_playing()
{
   bool playing = al_get_audio_stream_playing(stream);
   return al_set_audio_stream_playing(stream, !playing);
}

float Audiostream::get_gain() const
{
   return al_get_audio_stream_gain(stream);
}

bool Audiostream::adjust_gain(float d)
{
   float gain = al_get_audio_stream_gain(stream) + d;
   gain = clamp(0, gain, 2);
   return al_set_audio_stream_gain(stream, gain);
}

char const *Audiostream::get_label() const
{
   return filename.c_str();
}

/*---------------------------------------------------------------------------*/

class Prog {
public:
   Prog();
   void init();
   void add_sample(char const *filename);
   void add_stream_path(char const *filename);
   void initial_config();
   void run(void);

private:
   Voice *new_voice();
   Mixer *new_mixer();
   SampleInstance *new_sample_instance();
   Audiostream *new_audiostream();
   void process_mouse_button_down(int mb, int mx, int my);
   void process_mouse_button_up(int mb, int mx, int my);
   void process_mouse_axes(int mx, int my, int dx, int dy);
   void process_mouse_wheel(int dz);
   void process_key_char(int unichar);
   Element *find_element(int x, int y);
   void delete_element(Element *elt);
   void redraw();

   ALLEGRO_DISPLAY *dpy;
   ALLEGRO_EVENT_QUEUE *queue;
   Context ctx;
   vector<Sample const *> samples;
   vector<string> stream_paths;
   vector<Element *> elements;
   int cur_button;
   Element *cur_element;
   float connect_x;
   float connect_y;
};

Prog::Prog() : cur_button(0), cur_element(NULL)
{
}

void Prog::init()
{
   if (!al_init()) {
      abort_example("Could not initialise Allegro.\n");
   }
   if (!al_init_primitives_addon()) {
      abort_example("Could not initialise primitives.\n");
   }
   al_init_font_addon();
   if (!al_init_ttf_addon()) {
      abort_example("Could not initialise TTF fonts.\n");
   }
   if (!al_install_audio()) {
      abort_example("Could not initialise audio.\n");
   }
   if (!al_init_acodec_addon()) {
      abort_example("Could not initialise audio codecs.\n");
   }
   init_platform_specific();
   if (!(dpy = al_create_display(800, 600))) {
      abort_example("Could not create display.\n");
   }
   if (!al_install_keyboard()) {
      abort_example("Could not install keyboard.\n");
   }
   if (!al_install_mouse()) {
      abort_example("Could not install mouse.\n");
   }

   ctx.font = al_load_ttf_font("data/DejaVuSans.ttf", 10, 0);
   if (!ctx.font) {
      abort_example("Could not load font.\n");
   }
   ctx.bg = al_map_rgb_f(0.9, 0.9, 0.9);
   ctx.fg = al_map_rgb_f(0, 0, 0);
   ctx.fill = al_map_rgb_f(0.85, 0.85, 0.85);
   ctx.disabled = al_map_rgb_f(0.6, 0.6, 0.6);
   ctx.highlight = al_map_rgb_f(1, 0.1, 0.1);

   queue = al_create_event_queue();
   al_register_event_source(queue, al_get_mouse_event_source());
   al_register_event_source(queue, al_get_keyboard_event_source());
   al_register_event_source(queue, al_get_display_event_source(dpy));
}

void Prog::add_sample(char const *filename)
{
   ALLEGRO_PATH *path = make_path(filename);
   Sample *spl = new Sample(al_path_cstr(path, '/'));
   if (spl) {
      samples.push_back(spl);
   } else {
      delete spl;
   }
   al_destroy_path(path);
}

void Prog::add_stream_path(char const *filename)
{
   ALLEGRO_PATH *path = make_path(filename);
   stream_paths.push_back(al_path_cstr(path, '/'));
   al_destroy_path(path);
}

void Prog::initial_config()
{
   Voice *voice = new_voice();
   if (!voice) {
      abort_example("Could not create initial voice.\n");
   }
   voice->set_pos(300, 50);

   Mixer *mixer = new_mixer();
   mixer->set_pos(300, 150);
   voice->attach(mixer);

   SampleInstance *splinst = new_sample_instance();
   splinst->set_pos(220, 300);
   mixer->attach(splinst);
   splinst->toggle_playing();

   SampleInstance *splinst2 = new_sample_instance();
   splinst2->set_pos(120, 240);
   mixer->attach(splinst2);
   splinst2->toggle_playing();

   Mixer *mixer2 = new_mixer();
   mixer2->set_pos(500, 250);
   mixer->attach(mixer2);

   Audiostream *stream;
   if ((stream = new_audiostream())) {
      stream->set_pos(450, 350);
      mixer2->attach(stream);
   }
}

Voice *Prog::new_voice()
{
   Voice *voice = new Voice();
   if (voice->valid()) {
      elements.push_back(voice);
   }
   else {
      delete voice;
      voice = NULL;
   }
   return voice;
}

Mixer *Prog::new_mixer()
{
   Mixer *mixer = new Mixer();
   elements.push_back(mixer);
   return mixer;
}

SampleInstance *Prog::new_sample_instance()
{
   SampleInstance *splinst = new SampleInstance();
   if (samples.size() > 0) {
      static unsigned i = 0;
      unsigned n = (i++) % samples.size();
      splinst->set_sample(*samples[n]);
      i++;
   }
   elements.push_back(splinst);
   return splinst;
}

Audiostream *Prog::new_audiostream()
{
   if (stream_paths.size() > 0) {
      static unsigned i = 0;
      unsigned n = (i++) % stream_paths.size();
      Audiostream *stream = new Audiostream(stream_paths[n]);
      if (stream->valid()) {
         elements.push_back(stream);
         return stream;
      }
      delete stream;
   }
   return NULL;
}

void Prog::run()
{
   for (;;) {
      if (al_is_event_queue_empty(queue)) {
         redraw();
      }

      ALLEGRO_EVENT ev;
      al_wait_for_event(queue, &ev);
      switch (ev.type) {
         case ALLEGRO_EVENT_DISPLAY_CLOSE:
            return;
         case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
            process_mouse_button_down(ev.mouse.button, ev.mouse.x, ev.mouse.y);
            break;
         case ALLEGRO_EVENT_MOUSE_BUTTON_UP:
            process_mouse_button_up(ev.mouse.button, ev.mouse.x, ev.mouse.y);
            break;
         case ALLEGRO_EVENT_MOUSE_AXES:
            if (ev.mouse.dz) {
               process_mouse_wheel(ev.mouse.dz);
            }
            else {
               process_mouse_axes(ev.mouse.x, ev.mouse.y,
                  ev.mouse.dx, ev.mouse.dy);
            }
            break;
         case ALLEGRO_EVENT_KEY_CHAR:
            if (ev.keyboard.unichar == 27) {
               return;
            }
            process_key_char(ev.keyboard.unichar);
            break;
      }
   }
}

void Prog::process_mouse_button_down(int mb, int mx, int my)
{
   if (cur_button == 0) {
      cur_element = find_element(mx, my);
      if (cur_element) {
         cur_button = mb;
      }
      if (cur_button == 2) {
         connect_x = mx;
         connect_y = my;
      }
   }
}

void Prog::process_mouse_button_up(int mb, int mx, int my)
{
   if (mb != cur_button)
      return;

   if (cur_button == 2 && cur_element) {
      Element *sink = find_element(mx, my);
      cur_element->detach();
      if (sink && sink != cur_element) {
         sink->attach(cur_element);
      }
      cur_element = NULL;
   }

   cur_button = 0;
}

void Prog::process_mouse_axes(int mx, int my, int dx, int dy)
{
   if (cur_button == 1 && cur_element) {
      cur_element->move_by(dx, dy);
   }
   if (cur_button == 2 && cur_element) {
      connect_x = mx;
      connect_y = my;
   }
}

void Prog::process_mouse_wheel(int dz)
{
   if (cur_element) {
      cur_element->adjust_gain(dz * 0.1);
   }
}

void Prog::process_key_char(int unichar)
{
   int upper = toupper(unichar);
   if (upper == 'V') {
      new_voice();
   }
   else if (upper == 'M') {
      new_mixer();
   }
   else if (upper == 'S') {
      new_sample_instance();
   }
   else if (upper == 'A') {
      new_audiostream();
   }
   else if (upper == ' ') {
      if (cur_element) {
         cur_element->toggle_playing();
      }
   }
   else if (upper == 'X') {
      if (cur_element) {
         delete_element(cur_element);
         cur_element = NULL;
      }
   }
   else if (upper >= '1' && upper <= '9') {
      unsigned n = upper - '1';
      SampleInstance *splinst;
      if (n < samples.size() &&
         (splinst = dynamic_cast<SampleInstance *>(cur_element)))
      {
         splinst->set_sample(*samples.at(n));
      }
   }
}

Element *Prog::find_element(int x, int y)
{
   for (Element::iter it = elements.begin(); it != elements.end(); it++) {
      if ((*it)->contains(x, y))
         return *it;
   }
   return NULL;
}

void Prog::delete_element(Element *elt)
{
   for (Element::iter it = elements.begin(); it != elements.end(); it++) {
      if ((*it)->is_attached_to(elt)) {
         (*it)->detach();
      }
   }
   for (Element::iter it = elements.begin(); it != elements.end(); it++) {
      if (*it == elt) {
         (*it)->detach();
         delete *it;
         elements.erase(it);
         break;
      }
   }
}

void Prog::redraw()
{
   al_clear_to_color(ctx.bg);

   for (Element::iter it = elements.begin(); it != elements.end(); it++) {
      bool highlight = (*it == cur_element);
      (*it)->draw(ctx, highlight);

      if (highlight && cur_button == 2) {
         (*it)->draw_arrow(ctx, connect_x, connect_y, highlight);
      }
   }

   float y = al_get_display_height(dpy);
   float th = al_get_font_line_height(ctx.font);
   al_draw_textf(ctx.font, ctx.fg, 0, y-th*2, ALLEGRO_ALIGN_LEFT,
      "Create [v]oices, [m]ixers, [s]ample instances, [a]udiostreams.   "
      "[SPACE] pause playback.    "
      "[1]-[9] set sample.    "
      "[x] delete.");
   al_draw_textf(ctx.font, ctx.fg, 0, y-th*1, ALLEGRO_ALIGN_LEFT,
      "Mouse: [LMB] select element.   "
      "[RMB] attach sources to sinks "
      "(sample->mixer, mixer->mixer, mixer->voice, sample->voice)");

   al_flip_display();
}

int main(int argc, char **argv)
{
   Prog prog;
   prog.init();
   prog.add_sample("data/haiku/air_0.ogg");
   prog.add_sample("data/haiku/air_1.ogg");
   prog.add_sample("data/haiku/earth_0.ogg");
   prog.add_sample("data/haiku/earth_1.ogg");
   prog.add_sample("data/haiku/earth_2.ogg");
   prog.add_sample("data/haiku/fire_0.ogg");
   prog.add_sample("data/haiku/fire_1.ogg");
   prog.add_sample("data/haiku/water_0.ogg");
   prog.add_sample("data/haiku/water_1.ogg");
   prog.add_stream_path("../demos/cosmic_protector/data/sfx/game_music.ogg");
   prog.add_stream_path("../demos/cosmic_protector/data/sfx/title_music.ogg");
   prog.initial_config();
   prog.run();
   /* Let Allegro handle the cleanup. */
   return 0;

   (void)argc;
   (void)argv;
}

/* vim: set sts=3 sw=3 et: */