/* * OpenTyrian Classic: A modern cross-platform port of Tyrian * Copyright (C) 2007-2009 The OpenTyrian Development Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "file.h" #include "fm_synth.h" #include "lds_play.h" #include "loudness.h" #include "nortsong.h" #include "opentyr.h" #include "params.h" float music_volume = 0, sample_volume = 0; bool music_stopped = true; unsigned int song_playing = 0; bool audio_disabled = false, music_disabled = false, samples_disabled = false; /* SYN: These shouldn't be used outside this file. Hands off! */ FILE *music_file = NULL; Uint32 *song_offset; Uint16 song_count = 0; SAMPLE_TYPE *channel_buffer[SFX_CHANNELS] = { NULL }; SAMPLE_TYPE *channel_pos[SFX_CHANNELS] = { NULL }; Uint32 channel_len[SFX_CHANNELS] = { 0 }; Uint8 channel_vol[SFX_CHANNELS]; int sound_init_state = false; int freq = 11025 * OUTPUT_QUALITY; static SDL_AudioCVT audio_cvt; // used for format conversion void audio_cb( void *userdata, unsigned char *feedme, int howmuch ); void load_song( unsigned int song_num ); bool init_audio( void ) { if (audio_disabled) return false; SDL_AudioSpec ask, got; ask.freq = freq; ask.format = (BYTES_PER_SAMPLE == 2) ? AUDIO_S16SYS : AUDIO_S8; ask.channels = 1; ask.samples = 512; ask.callback = audio_cb; printf("\trequested %d Hz, %d channels, %d samples\n", ask.freq, ask.channels, ask.samples); if (SDL_OpenAudio(&ask, &got) == -1) { fprintf(stderr, "error: failed to initialize SDL audio: %s\n", SDL_GetError()); audio_disabled = true; return false; } printf("\tobtained %d Hz, %d channels, %d samples\n", got.freq, got.channels, got.samples); SDL_BuildAudioCVT(&audio_cvt, ask.format, ask.channels, ask.freq, got.format, got.channels, got.freq); opl_init(); SDL_PauseAudio(0); // unpause return true; } void audio_cb( void *user_data, unsigned char *sdl_buffer, int howmuch ) { (void)user_data; // prepare for conversion howmuch /= audio_cvt.len_mult; audio_cvt.buf = sdl_buffer; audio_cvt.len = howmuch; static long ct = 0; SAMPLE_TYPE *feedme = (SAMPLE_TYPE *)sdl_buffer; if (!music_disabled && !music_stopped) { /* SYN: Simulate the fm synth chip */ SAMPLE_TYPE *music_pos = feedme; long remaining = howmuch / BYTES_PER_SAMPLE; while (remaining > 0) { while (ct < 0) { ct += freq; lds_update(); /* SYN: Do I need to use the return value for anything here? */ } /* SYN: Okay, about the calculations below. I still don't 100% get what's going on, but... - freq is samples/time as output by SDL. - REFRESH is how often the play proc would have been called in Tyrian. Standard speed is 70Hz, which is the default value of 70.0f - ct represents the margin between play time (representing # of samples) and tick speed of the songs (70Hz by default). It keeps track of which one is ahead, because they don't synch perfectly. */ /* set i to smaller of data requested by SDL and a value calculated from the refresh rate */ long i = (long)((ct / REFRESH) + 4) & ~3; i = (i > remaining) ? remaining : i; /* i should now equal the number of samples we get */ opl_update((SAMPLE_TYPE *)music_pos, i); music_pos += i; remaining -= i; ct -= (long)(REFRESH * i); } /* Reduce the music volume. */ int qu = howmuch / BYTES_PER_SAMPLE; for (int smp = 0; smp < qu; smp++) { feedme[smp] *= music_volume; } } if (!samples_disabled) { /* SYN: Mix sound channels and shove into audio buffer */ for (int ch = 0; ch < SFX_CHANNELS; ch++) { float volume = sample_volume * (channel_vol[ch] / (float)SFX_CHANNELS); /* SYN: Don't copy more data than is in the channel! */ unsigned int qu = ((unsigned)howmuch > channel_len[ch] ? channel_len[ch] : (unsigned)howmuch) / BYTES_PER_SAMPLE; for (unsigned int smp = 0; smp < qu; smp++) { #if (BYTES_PER_SAMPLE == 2) Sint32 clip = (Sint32)feedme[smp] + (Sint32)(channel_pos[ch][smp] * volume); feedme[smp] = (clip > 0x7fff) ? 0x7fff : (clip <= -0x8000) ? -0x8000 : (Sint16)clip; #else /* BYTES_PER_SAMPLE */ Sint16 clip = (Sint16)feedme[smp] + (Sint16)(channel_pos[ch][smp] * volume); feedme[smp] = (clip > 0x7f) ? 0x7f : (clip <= -0x80) ? -0x80 : (Sint8)clip; #endif /* BYTES_PER_SAMPLE */ } channel_pos[ch] += qu; channel_len[ch] -= qu * BYTES_PER_SAMPLE; /* SYN: If we've emptied a channel buffer, let's free the memory and clear the channel. */ if (channel_len[ch] == 0) { free(channel_buffer[ch]); channel_buffer[ch] = channel_pos[ch] = NULL; } } } // do conversion SDL_ConvertAudio(&audio_cvt); } void deinit_audio( void ) { if (audio_disabled) return; SDL_PauseAudio(1); // pause SDL_CloseAudio(); for (unsigned int i = 0; i < SFX_CHANNELS; i++) { free(channel_buffer[i]); channel_buffer[i] = channel_pos[i] = NULL; channel_len[i] = 0; } opl_deinit(); lds_free(); } void load_music( void ) { if (music_file == NULL) { music_file = dir_fopen_die(data_dir(), "music.mus", "rb"); efread(&song_count, sizeof(song_count), 1, music_file); song_offset = (Uint32*)malloc((song_count + 1) * sizeof(song_offset)); efread(song_offset, 4, song_count, music_file); song_offset[song_count] = ftell_eof(music_file); } } void load_song( unsigned int song_num ) { if (audio_disabled) return; SDL_LockAudio(); if (song_num < song_count) { unsigned int song_size = song_offset[song_num + 1] - song_offset[song_num]; lds_load(music_file, song_offset[song_num], song_size); } else { fprintf(stderr, "warning: failed to load song %d\n", song_num + 1); } SDL_UnlockAudio(); } void play_song( unsigned int song_num ) { if (song_num != song_playing) { load_song(song_num); song_playing = song_num; } music_stopped = false; } void restart_song( void ) { unsigned int temp = song_playing; song_playing = -1; play_song(temp); } void stop_song( void ) { music_stopped = true; } void fade_song( void ) { printf("TODO: %s\n", __FUNCTION__); } void set_volume( unsigned int music, unsigned int sample ) { music_volume = music * (1.5f / 255.0f); sample_volume = sample * (1.0f / 255.0f); } void JE_multiSamplePlay(JE_byte *buffer, JE_word size, JE_byte chan, JE_byte vol) { if (audio_disabled || samples_disabled) return; SDL_LockAudio(); free(channel_buffer[chan]); channel_len[chan] = size * BYTES_PER_SAMPLE * SAMPLE_SCALING; channel_buffer[chan] = (OPLSAMPLE*)malloc(channel_len[chan]); channel_pos[chan] = channel_buffer[chan]; channel_vol[chan] = vol + 1; for (int i = 0; i < size; i++) { for (int ex = 0; ex < SAMPLE_SCALING; ex++) { #if (BYTES_PER_SAMPLE == 2) channel_buffer[chan][(i * SAMPLE_SCALING) + ex] = (Sint8)buffer[i] << 8; #else /* BYTES_PER_SAMPLE */ channel_buffer[chan][(i * SAMPLE_SCALING) + ex] = (Sint8)buffer[i]; #endif /* BYTES_PER_SAMPLE */ } } SDL_UnlockAudio(); } // kate: tab-width 4; vim: set noet: