music_mod.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 05 Oct 2009 07:15:36 +0000
changeset 427 47cf8b3f2fb0
parent 420 2594a266fc8b
child 470 5cebd6c5be2d
permissions -rw-r--r--
Made this a _little_ bit more 64-bit aware
     1 /*
     2     SDL_mixer:  An audio mixer library based on the SDL library
     3     Copyright (C) 1997-2009 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Library General Public
     7     License as published by the Free Software Foundation; either
     8     version 2 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Library General Public License for more details.
    14 
    15     You should have received a copy of the GNU Library General Public
    16     License along with this library; if not, write to the Free
    17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 
    23 /* $Id: music_mod.c 4211 2008-12-08 00:27:32Z slouken $ */
    24 
    25 #ifdef MOD_MUSIC
    26 
    27 /* This file supports MOD tracker music streams */
    28 
    29 #include "SDL_mixer.h"
    30 #include "dynamic_mod.h"
    31 #include "music_mod.h"
    32 
    33 #include "mikmod.h"
    34 
    35 #define SDL_SURROUND
    36 #ifdef SDL_SURROUND
    37 #define MAX_OUTPUT_CHANNELS 6
    38 #else
    39 #define MAX_OUTPUT_CHANNELS 2
    40 #endif
    41 
    42 /* Reference for converting mikmod output to 4/6 channels */
    43 static int current_output_channels;
    44 static Uint16 current_output_format;
    45 
    46 static int music_swap8;
    47 static int music_swap16;
    48 
    49 /* Initialize the MOD player, with the given mixer settings
    50    This function returns 0, or -1 if there was an error.
    51  */
    52 int MOD_init(SDL_AudioSpec *mixerfmt)
    53 {
    54 	CHAR *list;
    55 
    56 	if ( Mix_InitMOD() < 0 ) {
    57 		return -1;
    58 	}
    59 
    60 	/* Set the MikMod music format */
    61 	music_swap8 = 0;
    62 	music_swap16 = 0;
    63 	switch (mixerfmt->format) {
    64 
    65 		case AUDIO_U8:
    66 		case AUDIO_S8: {
    67 			if ( mixerfmt->format == AUDIO_S8 ) {
    68 				music_swap8 = 1;
    69 			}
    70 			*mikmod.md_mode = 0;
    71 		}
    72 		break;
    73 
    74 		case AUDIO_S16LSB:
    75 		case AUDIO_S16MSB: {
    76 			/* See if we need to correct MikMod mixing */
    77 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
    78 			if ( mixerfmt->format == AUDIO_S16MSB ) {
    79 #else
    80 			if ( mixerfmt->format == AUDIO_S16LSB ) {
    81 #endif
    82 				music_swap16 = 1;
    83 			}
    84 			*mikmod.md_mode = DMODE_16BITS;
    85 		}
    86 		break;
    87 
    88 		default: {
    89 			Mix_SetError("Unknown hardware audio format");
    90 			return -1;
    91 		}
    92 	}
    93 	current_output_channels = mixerfmt->channels;
    94 	current_output_format = mixerfmt->format;
    95 	if ( mixerfmt->channels > 1 ) {
    96 		if ( mixerfmt->channels > MAX_OUTPUT_CHANNELS ) {
    97 			Mix_SetError("Hardware uses more channels than mixerfmt");
    98 			return -1;
    99 		}
   100 		*mikmod.md_mode |= DMODE_STEREO;
   101 	}
   102 	*mikmod.md_mixfreq = mixerfmt->freq;
   103 	*mikmod.md_device  = 0;
   104 	*mikmod.md_volume  = 96;
   105 	*mikmod.md_musicvolume = 128;
   106 	*mikmod.md_sndfxvolume = 128;
   107 	*mikmod.md_pansep  = 128;
   108 	*mikmod.md_reverb  = 0;
   109 	*mikmod.md_mode    |= DMODE_HQMIXER|DMODE_SOFT_MUSIC|DMODE_SURROUND;
   110 
   111 	list = mikmod.MikMod_InfoDriver();
   112 	if ( list )
   113 	  free(list);
   114 	else
   115 	  mikmod.MikMod_RegisterDriver(mikmod.drv_nos);
   116 
   117 	list = mikmod.MikMod_InfoLoader();
   118 	if ( list )
   119 	  free(list);
   120 	else
   121 	  mikmod.MikMod_RegisterAllLoaders();
   122 
   123 	if ( mikmod.MikMod_Init(NULL) ) {
   124 		Mix_SetError("%s", mikmod.MikMod_strerror(*mikmod.MikMod_errno));
   125 		return -1;
   126 	}
   127 
   128 	return 0;
   129 }
   130 
   131 /* Uninitialize the music players */
   132 void MOD_exit(void)
   133 {
   134 	mikmod.MikMod_Exit();
   135 	Mix_QuitMOD();
   136 }
   137 
   138 /* Set the volume for a MOD stream */
   139 void MOD_setvolume(MODULE *music, int volume)
   140 {
   141 	mikmod.Player_SetVolume((SWORD)volume);
   142 }
   143 
   144 /* Load a MOD stream from the given file */
   145 MODULE *MOD_new(const char *file)
   146 {
   147 	SDL_RWops *rw;
   148 
   149 	rw = SDL_RWFromFile(file, "rb");
   150 	if ( rw == NULL ) {
   151 		/* FIXME: Free rw, need to free on delete */
   152 		SDL_SetError("Couldn't open %s", file);
   153 		return NULL;
   154 	}
   155 	return MOD_new_RW(rw);
   156 }
   157 
   158 
   159 typedef struct
   160 {
   161 	MREADER mr;
   162 	long offset;
   163 	long eof;
   164 	SDL_RWops *rw;
   165 } LMM_MREADER;
   166 
   167 BOOL LMM_Seek(struct MREADER *mr,long to,int dir)
   168 {
   169 	LMM_MREADER* lmmmr = (LMM_MREADER*)mr;
   170 	if ( dir == SEEK_SET ) {
   171 		to += lmmmr->offset;
   172 	}
   173 	return (SDL_RWseek(lmmmr->rw, to, dir) < lmmmr->offset);
   174 }
   175 long LMM_Tell(struct MREADER *mr)
   176 {
   177 	LMM_MREADER* lmmmr = (LMM_MREADER*)mr;
   178 	return SDL_RWtell(lmmmr->rw) - lmmmr->offset;
   179 }
   180 BOOL LMM_Read(struct MREADER *mr,void *buf,size_t sz)
   181 {
   182 	LMM_MREADER* lmmmr = (LMM_MREADER*)mr;
   183 	return SDL_RWread(lmmmr->rw, buf, sz, 1);
   184 }
   185 int LMM_Get(struct MREADER *mr)
   186 {
   187 	unsigned char c;
   188 	LMM_MREADER* lmmmr = (LMM_MREADER*)mr;
   189 	if ( SDL_RWread(lmmmr->rw, &c, 1, 1) ) {
   190 		return c;
   191 	}
   192 	return EOF;
   193 }
   194 BOOL LMM_Eof(struct MREADER *mr)
   195 {
   196 	long offset;
   197 	LMM_MREADER* lmmmr = (LMM_MREADER*)mr;
   198 	offset = LMM_Tell(mr);
   199 	return offset >= lmmmr->eof;
   200 }
   201 MODULE *MikMod_LoadSongRW(SDL_RWops *rw, int maxchan)
   202 {
   203 	LMM_MREADER lmmmr = {
   204 		{ LMM_Seek, LMM_Tell, LMM_Read, LMM_Get, LMM_Eof },
   205 		0,
   206 		0,
   207 		0
   208 	};
   209 	lmmmr.offset = SDL_RWtell(rw);
   210 	SDL_RWseek(rw, 0, SEEK_END);
   211 	lmmmr.eof = SDL_RWtell(rw);
   212 	SDL_RWseek(rw, lmmmr.offset, SEEK_SET);
   213         lmmmr.rw = rw;
   214 	return mikmod.Player_LoadGeneric((MREADER*)&lmmmr, maxchan, 0);
   215 }
   216 
   217 /* Load a MOD stream from an SDL_RWops object */
   218 MODULE *MOD_new_RW(SDL_RWops *rw)
   219 {
   220 	MODULE *module;
   221 
   222 	module = MikMod_LoadSongRW(rw,64);
   223 	if (!module) {
   224 		Mix_SetError("%s", mikmod.MikMod_strerror(*mikmod.MikMod_errno));
   225 		return NULL;
   226 	}
   227 
   228 	/* Stop implicit looping, fade out and other flags. */
   229 	module->extspd  = 1;
   230 	module->panflag = 1;
   231 	module->wrap    = 0;
   232 	module->loop    = 0;
   233 #if 0 /* Don't set fade out by default - unfortunately there's no real way
   234 to query the status of the song or set trigger actions.  Hum. */
   235 	module->fadeout = 1;
   236 #endif
   237 	return module;
   238 }
   239 
   240 /* Start playback of a given MOD stream */
   241 void MOD_play(MODULE *music)
   242 {
   243 	mikmod.Player_Start(music);
   244 }
   245 
   246 /* Return non-zero if a stream is currently playing */
   247 int MOD_playing(MODULE *music)
   248 {
   249 	return mikmod.Player_Active();
   250 }
   251 
   252 /* Play some of a stream previously started with MOD_play() */
   253 int MOD_playAudio(MODULE *music, Uint8 *stream, int len)
   254 {
   255 	if (current_output_channels > 2) {
   256 		int small_len = 2 * len / current_output_channels;
   257 		int i;
   258 		Uint8 *src, *dst;
   259 
   260 		mikmod.VC_WriteBytes((SBYTE *)stream, small_len);
   261 		/* and extend to len by copying channels */
   262 		src = stream + small_len;
   263 		dst = stream + len;
   264 
   265 		switch (current_output_format & 0xFF) {
   266 			case 8:
   267 				for ( i=small_len/2; i; --i ) {
   268 					src -= 2;
   269 					dst -= current_output_channels;
   270 					dst[0] = src[0];
   271 					dst[1] = src[1];
   272 					dst[2] = src[0];
   273 					dst[3] = src[1];
   274 					if (current_output_channels == 6) {
   275 						dst[4] = src[0];
   276 						dst[5] = src[1];
   277 					}
   278 				}
   279 				break;
   280 			case 16:
   281 				for ( i=small_len/4; i; --i ) {
   282 					src -= 4;
   283 					dst -= 2 * current_output_channels;
   284 					dst[0] = src[0];
   285 					dst[1] = src[1];
   286 					dst[2] = src[2];
   287 					dst[3] = src[3];
   288 					dst[4] = src[0];
   289 					dst[5] = src[1];
   290 					dst[6] = src[2];
   291 					dst[7] = src[3];
   292 					if (current_output_channels == 6) {
   293 						dst[8] = src[0];
   294 						dst[9] = src[1];
   295 						dst[10] = src[2];
   296 						dst[11] = src[3];
   297 					}
   298 				}
   299 				break;
   300 		}
   301 	} else {
   302 		mikmod.VC_WriteBytes((SBYTE *)stream, len);
   303 	}
   304 	if ( music_swap8 ) {
   305 		Uint8 *dst;
   306 		int i;
   307 
   308 		dst = stream;
   309 		for ( i=len; i; --i ) {
   310 			*dst++ ^= 0x80;
   311 		}
   312 	} else
   313 	if ( music_swap16 ) {
   314 		Uint8 *dst, tmp;
   315 		int i;
   316 
   317 		dst = stream;
   318 		for ( i=(len/2); i; --i ) {
   319 			tmp = dst[0];
   320 			dst[0] = dst[1];
   321 			dst[1] = tmp;
   322 			dst += 2;
   323 		}
   324 	}
   325 	return 0;
   326 }
   327 
   328 /* Stop playback of a stream previously started with MOD_play() */
   329 void MOD_stop(MODULE *music)
   330 {
   331 	mikmod.Player_Stop();
   332 }
   333 
   334 /* Close the given MOD stream */
   335 void MOD_delete(MODULE *music)
   336 {
   337 	mikmod.Player_Free(music);
   338 }
   339 
   340 /* Jump (seek) to a given position (time is in seconds) */
   341 void MOD_jump_to_time(MODULE *music, double time)
   342 {
   343 	mikmod.Player_SetPosition((UWORD)time);
   344 }
   345 
   346 #endif /* MOD_MUSIC */