/*
  Ruby/SDL   Ruby extension library for SDL

  Copyright (C) 2001-2007 Ohbayashi Ippei
  
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
#ifdef HAVE_SDL_MIXER

#include "rubysdl.h"
#define USE_RWOPS
#include <SDL_mixer.h>

static int mix_opened=0;
static int mix_closed=0;

static VALUE playing_wave = Qnil;
static VALUE playing_music=Qnil;

static void mix_FreeChunk(Mix_Chunk *chunk)
{
  if( ! mix_closed ){
    Mix_FreeChunk( chunk );
  }
}

static void mix_FreeMusic(Mix_Music *music)
{
  if( !mix_closed ){
    Mix_FreeMusic( music );
  }
}

static VALUE mix_audioDriverName(VALUE mod)
{
  char driver_name[512];
  if(SDL_AudioDriverName(driver_name, sizeof(driver_name)) == NULL)
    rb_raise(eSDLError, "No driver has been initialized: %s", SDL_GetError());
  return rb_str_new2(driver_name);
}

static VALUE mix_openAudio(VALUE mod,VALUE frequency,VALUE format,
			   VALUE channels,VALUE chunksize)
{
  if( mix_opened ){
    rb_raise(eSDLError,"already initialize SDL::Mixer");
  }
  if( Mix_OpenAudio( NUM2INT(frequency),NUM2UINT(format),NUM2INT(channels),
		     NUM2INT(chunksize) ) < 0 ){
    rb_raise(eSDLError,"Couldn't open audio: %s",SDL_GetError());
  }

  mix_opened = 1;
  return Qnil;
}

static VALUE mix_querySpec(VALUE mod)
{
  int rate;
  Uint16 format;
  int channels;

  if( !Mix_QuerySpec(&rate,&format,&channels) )
    rb_raise(eSDLError,"audio have not been opened yet: %s", Mix_GetError());
  return rb_ary_new3( 3,INT2NUM(rate),UINT2NUM(format),INT2NUM(channels) );
}

static VALUE mix_allocateChannels(VALUE mod,VALUE numchannels)
{
  return INT2FIX( Mix_AllocateChannels(NUM2INT(numchannels)) );  
}

/* Returns which channel was used to play the sound. */
static VALUE mix_playChannel(VALUE mod,VALUE channel,VALUE wave,VALUE loops)
{
  Mix_Chunk *chunk;
  int playing_channel;
  
  if( ! rb_obj_is_kind_of(wave,cWave) )
    rb_raise(rb_eArgError,"type mismatch: SDL::Mixer::Wave is expected");
  Data_Get_Struct(wave,Mix_Chunk,chunk);
  
  playing_channel = Mix_PlayChannel(NUM2INT(channel),chunk,NUM2INT(loops) );
  if( playing_channel == -1 ){
    rb_raise( eSDLError, "couldn't play wave: %s", Mix_GetError() );
  }

  rb_ary_store(playing_wave,playing_channel,wave);/* to avoid gc problem */
  return INT2FIX(playing_channel);
}

static VALUE mix_playChannelTimed(VALUE mod,VALUE channel,VALUE wave,VALUE loops,
                                  VALUE ticks)
{
  Mix_Chunk *chunk;
  int playing_channel;
  
  if( ! rb_obj_is_kind_of(wave,cWave) )
    rb_raise(rb_eArgError,"type mismatch: SDL::Mixer::Wave is expected");
  Data_Get_Struct(wave,Mix_Chunk,chunk);
  
  playing_channel = Mix_PlayChannelTimed(NUM2INT(channel),chunk,NUM2INT(loops),
                                         NUM2INT(ticks));
  if( playing_channel == -1 ){
    rb_raise( eSDLError, "couldn't play wave: %s", Mix_GetError() );
  }

  rb_ary_store(playing_wave,playing_channel,wave);/* to avoid gc problem */
  return INT2FIX(playing_channel);
}

static VALUE mix_fadeInChannel(VALUE mod,VALUE channel,VALUE wave,VALUE loops,
                               VALUE ms)
{
  Mix_Chunk *chunk;
  int playing_channel;
  
  if( ! rb_obj_is_kind_of(wave,cWave) )
    rb_raise(rb_eArgError,"type mismatch: SDL::Mixer::Wave is expected");
  Data_Get_Struct(wave,Mix_Chunk,chunk);
  
  playing_channel = Mix_FadeInChannel(NUM2INT(channel),chunk,NUM2INT(loops),
                                      NUM2INT(ms));
  if( playing_channel == -1 ){
    rb_raise( eSDLError, "couldn't play wave: %s", Mix_GetError() );
  }

  rb_ary_store(playing_wave,playing_channel,wave);/* to avoid gc problem */
  return INT2FIX(playing_channel);
}

static VALUE mix_fadeInChannelTimed(VALUE mod,VALUE channel,VALUE wave,VALUE loops,
                                    VALUE ms, VALUE ticks)
{
  Mix_Chunk *chunk;
  int playing_channel;
  
  if( ! rb_obj_is_kind_of(wave,cWave) )
    rb_raise(rb_eArgError,"type mismatch: SDL::Mixer::Wave is expected");
  Data_Get_Struct(wave,Mix_Chunk,chunk);
  
  playing_channel = Mix_FadeInChannelTimed(NUM2INT(channel),chunk,NUM2INT(loops),
                                           NUM2INT(ms), NUM2INT(ticks));
  if( playing_channel == -1 ){
    rb_raise( eSDLError, "couldn't play wave: %s", Mix_GetError() );
  }

  rb_ary_store(playing_wave,playing_channel,wave);/* to avoid gc problem */
  return INT2FIX(playing_channel);
}

static VALUE mix_playing(VALUE mod,VALUE channel)
{
  if( Mix_Playing( NUM2INT(channel) ) ){
    return Qtrue;
  }else{
    return Qfalse;
  }
}
  
static VALUE mix_loadWav(VALUE class,VALUE filename)
{
  Mix_Chunk *wave;
  Check_Type(filename,T_STRING);
  wave = Mix_LoadWAV( GETCSTR(filename) );
  if( wave == NULL ){
    rb_raise( eSDLError,"Couldn't load wave file %s: %s",
	      GETCSTR(filename), Mix_GetError() );
  }
  return Data_Wrap_Struct(class,0,mix_FreeChunk,wave);
}

static VALUE mix_loadWavFromIO(VALUE class, VALUE io)
{
  Mix_Chunk *wave;
  wave = Mix_LoadWAV_RW(rubysdl_RWops_from_ruby_obj(io), 1);
  if( wave == NULL ){
    rb_raise(eSDLError,"Couldn't load wave file from IO: %s",
             Mix_GetError());
  }
  return Data_Wrap_Struct(class,0,mix_FreeChunk,wave);
}
 
/* Volume setting functions and methods : volume in 0..128 */
static VALUE mix_volume(VALUE mod,VALUE channel,VALUE volume)
{
  return INT2FIX( Mix_Volume( NUM2INT(channel),NUM2INT(volume) ) );
}
static VALUE mix_wave_volume(VALUE obj,VALUE volume)
{
  Mix_Chunk *chunk;
  Data_Get_Struct(obj,Mix_Chunk,chunk);
  return INT2FIX( Mix_VolumeChunk( chunk,NUM2INT(volume) ) );
}

/* Halt,Pause function */
static VALUE mix_halt(VALUE mod,VALUE channel)
{
  Mix_HaltChannel(NUM2INT(channel));
  return Qnil;
}
static VALUE mix_pause(VALUE mod,VALUE channel)
{
  Mix_Pause(NUM2INT(channel));
  return Qnil;
}
static VALUE mix_resume(VALUE mod,VALUE channel)
{
  Mix_Resume(NUM2INT(channel));
  return Qnil;
}
static VALUE mix_paused(VALUE mod,VALUE channel)
{
  return INT2FIX(Mix_Paused(NUM2INT(channel)));
}
static VALUE mix_fadeOut(VALUE mod,VALUE channel,VALUE ms)
{
  return INT2FIX(Mix_FadeOutChannel(NUM2INT(channel), NUM2INT(ms)));
}
static VALUE mix_expire(VALUE mod, VALUE channel, VALUE ticks)
{
  return INT2FIX(Mix_ExpireChannel(NUM2INT(channel),NUM2INT(ticks)));
}
static VALUE mix_fading(VALUE mod, VALUE which)
{
  if( NUM2INT(which) < 0 || Mix_AllocateChannels(-1) <= NUM2INT(which))
    rb_raise(eSDLError, "channel %d out of range", NUM2INT(which));
  return INT2FIX(Mix_FadingChannel(which));
}
  
/* music functions */
#define MakeSimpleRubyFunc(rubyFunc,sdlFunc) \
static VALUE rubyFunc(VALUE mod) \
{ \
  sdlFunc(); \
  return Qnil; \
} \

static VALUE mix_playMusic(VALUE mod,VALUE music,VALUE loops)
{
  Mix_Music *mus;
  if( ! rb_obj_is_kind_of(music,cMusic) )
    rb_raise(rb_eArgError,"type mismatch: SDL::Mixer::Music is expected");
  Data_Get_Struct(music,Mix_Music,mus);
  playing_music=music; /* to avoid gc problem */
  Mix_PlayMusic(mus,NUM2INT(loops));
  return Qnil;
}

static VALUE mix_fadeInMusic(VALUE mod,VALUE music,VALUE loops,VALUE ms)
{
  Mix_Music *mus;
  if( ! rb_obj_is_kind_of(music,cMusic) )
    rb_raise(rb_eArgError,"type mismatch: SDL::Mixer::Music is expected");
  Data_Get_Struct(music,Mix_Music,mus);
  Mix_FadeInMusic(mus,NUM2INT(loops),NUM2INT(ms));
  return Qnil;
}

static VALUE mix_setVolumeMusic(VALUE mod,VALUE volume)
{
  Mix_VolumeMusic( NUM2INT(volume) );
  return Qnil;
}

static VALUE mix_fadeOutMusic(VALUE mod,VALUE ms)
{
  Mix_FadeOutMusic(NUM2INT(ms));
  return Qnil;
}

MakeSimpleRubyFunc(mix_haltMusic,Mix_HaltMusic)
MakeSimpleRubyFunc(mix_pauseMusic,Mix_PauseMusic)
MakeSimpleRubyFunc(mix_resumeMusic,Mix_ResumeMusic)
MakeSimpleRubyFunc(mix_rewindMusic,Mix_RewindMusic)

static VALUE mix_pausedMusic(VALUE mod)
{
  return BOOL(Mix_PausedMusic());
}

static VALUE mix_playingMusic(VALUE mod)
{
  return BOOL(Mix_PlayingMusic());
}

static VALUE mix_fadingMusic(VALUE mod)
{
  return INT2FIX(Mix_FadingMusic());
}

static VALUE mix_loadMus(VALUE class,VALUE filename)
{
  Mix_Music* music;
  music = Mix_LoadMUS(GETCSTR(filename));
  if( music == NULL )
    rb_raise(eSDLError,
	     "Couldn't load %s: %s",GETCSTR(filename),SDL_GetError());
  return Data_Wrap_Struct(class,0,mix_FreeMusic,music);
}

#ifdef HAVE_MIX_LOADMUS_RW
static VALUE mix_loadMusFromString(VALUE class,VALUE str)
{
  Mix_Music* music;
  volatile VALUE result;
  volatile VALUE buf;
  
  StringValue(str);
  buf = rb_str_dup(str);
  music = Mix_LoadMUS_RW(SDL_RWFromConstMem(RSTRING(buf)->ptr,
                                            RSTRING(buf)->len));
  
  if( music == NULL )
    rb_raise(eSDLError,
	     "Couldn't load from String: %s",Mix_GetError());
  
  result = Data_Wrap_Struct(class,0,mix_FreeMusic,music);
  rb_iv_set(result, "buf", buf);
  
  return result;
}
#endif

static void defineConstForAudio()
{
  rb_define_const(mMixer,"FORMAT_U8",UINT2NUM(AUDIO_U8));
  rb_define_const(mMixer,"FORMAT_S8",UINT2NUM(AUDIO_S8));
  rb_define_const(mMixer,"FORMAT_U16LSB",UINT2NUM(AUDIO_U16LSB));
  rb_define_const(mMixer,"FORMAT_S16LSB",UINT2NUM(AUDIO_S16LSB));
  rb_define_const(mMixer,"FORMAT_U16MSB",UINT2NUM(AUDIO_U16MSB));
  rb_define_const(mMixer,"FORMAT_S16MSB",UINT2NUM(AUDIO_S16MSB));
  rb_define_const(mMixer,"FORMAT_U16",UINT2NUM(AUDIO_U16));
  rb_define_const(mMixer,"FORMAT_S16",UINT2NUM(AUDIO_S16));

  rb_define_const(mMixer,"FORMAT_U16SYS",UINT2NUM(AUDIO_U16SYS));
  rb_define_const(mMixer,"FORMAT_S16SYS",UINT2NUM(AUDIO_S16SYS));

  rb_define_const(mMixer,"CHANNELS",INT2NUM(MIX_CHANNELS));
  rb_define_const(mMixer,"DEFAULT_FREQUENCY",INT2NUM(MIX_DEFAULT_FREQUENCY));
  rb_define_const(mMixer,"DEFAULT_FORMAT",UINT2NUM(MIX_DEFAULT_FORMAT));
  rb_define_const(mMixer,"DEFAULT_CHANNELS",UINT2NUM(MIX_DEFAULT_CHANNELS));
  rb_define_const(mMixer,"MAX_VOLUME",INT2NUM(MIX_MAX_VOLUME));

  rb_define_const(mMixer,"NO_FADING", INT2NUM(MIX_NO_FADING));
  rb_define_const(mMixer,"FADING_OUT", INT2NUM(MIX_FADING_OUT));
  rb_define_const(mMixer,"FADING_IN", INT2NUM(MIX_FADING_IN));
}

void init_mixer()
{
  mMixer = rb_define_module_under(mSDL,"Mixer");
  rb_define_module_function(mMixer,"open",mix_openAudio,4);
  rb_define_module_function(mMixer,"spec",mix_querySpec,0);
  rb_define_module_function(mMixer,"driverName", mix_audioDriverName, 0); 
  rb_define_module_function(mMixer,"playChannel",mix_playChannel,3);
  rb_define_module_function(mMixer,"playChannelTimed", mix_playChannelTimed, 4);
  rb_define_module_function(mMixer,"fadeInChannel", mix_fadeInChannel, 4);
  rb_define_module_function(mMixer,"fadeInChannelTimed", mix_fadeInChannelTimed, 5);
  rb_define_module_function(mMixer,"play?",mix_playing,1);
  rb_define_module_function(mMixer,"setVolume",mix_volume,2);
  rb_define_module_function(mMixer,"allocateChannels",mix_allocateChannels,1);
  rb_define_module_function(mMixer,"halt",mix_halt,1);
  rb_define_module_function(mMixer,"pause",mix_pause,1);
  rb_define_module_function(mMixer,"resume",mix_resume,1);
  rb_define_module_function(mMixer,"pause?",mix_paused,1);
  rb_define_module_function(mMixer,"expire",mix_expire,2);
  rb_define_module_function(mMixer,"fading",mix_fading,1);
  rb_define_module_function(mMixer,"fadeOut",mix_fadeOut,2);
  
  rb_define_module_function(mMixer,"playMusic",mix_playMusic,2);
  rb_define_module_function(mMixer,"fadeInMusic",mix_fadeInMusic,3);
  rb_define_module_function(mMixer,"setVolumeMusic",mix_setVolumeMusic,1);
  rb_define_module_function(mMixer,"haltMusic",mix_haltMusic,0);
  rb_define_module_function(mMixer,"fadeOutMusic",mix_fadeOutMusic,1);
  rb_define_module_function(mMixer,"pauseMusic",mix_pauseMusic,0);
  rb_define_module_function(mMixer,"resumeMusic",mix_resumeMusic,0);
  rb_define_module_function(mMixer,"rewindMusic",mix_rewindMusic,0);
  rb_define_module_function(mMixer,"pauseMusic?",mix_pausedMusic,0);
  rb_define_module_function(mMixer,"playMusic?",mix_playingMusic,0);
  rb_define_module_function(mMixer,"fadingMusic",mix_fadingMusic,0);
  
  cWave = rb_define_class_under(mMixer,"Wave",rb_cObject);
  rb_define_singleton_method(cWave,"load",mix_loadWav,1);
  rb_define_singleton_method(cWave,"loadFromIO",mix_loadWavFromIO,1);
  rb_define_method(cWave,"setVolume",mix_wave_volume,1);

  cMusic = rb_define_class_under(mMixer,"Music",rb_cObject);
  rb_define_singleton_method(cMusic,"load",mix_loadMus,1);
#ifdef HAVE_MIX_LOADMUS_RW
  rb_define_singleton_method(cMusic,"loadFromString",mix_loadMusFromString,1);
#endif
  
  /* to avoid to do garbage collect when playing */
  playing_wave = rb_ary_new();
  rb_global_variable( &playing_wave );
  rb_global_variable( &playing_music );

  defineConstForAudio();
  return ;
}

void quit_mixer()
{
  if( mix_opened ){
    Mix_CloseAudio();
    mix_closed = 1;
  }
}
#endif  /* HAVE_SDL_MIXER */


syntax highlighted by Code2HTML, v. 0.9.1