kobodl/sound.cpp
Ville Lindholm dbc223eb84
Initial commit
Import existing source tree; original VCS history is no longer available.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-28 16:35:31 +03:00

709 lines
15 KiB
C++

/*(GPL)
------------------------------------------------------------
Kobo Deluxe - Wrapper for Sound Control
------------------------------------------------------------
* Copyright (C) 2007 David Olofson
*
* 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.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "sound.h"
#include "kobo.h"
#include "kobolog.h"
#include "random.h"
#include "audio.h"
int KOBO_sound::sounds_loaded = 0;
int KOBO_sound::music_loaded = 0;
int KOBO_sound::time = 0;
int KOBO_sound::_period = 30;
int KOBO_sound::sfx2d_tag = 0;
int KOBO_sound::listener_x = 0;
int KOBO_sound::listener_y = 0;
int KOBO_sound::wrap_x = 0;
int KOBO_sound::wrap_y = 0;
int KOBO_sound::scale = 65536 / 1000;
int KOBO_sound::panscale = 65536 / 700;
int KOBO_sound::firing = 0;
int KOBO_sound::overheat = 0;
unsigned KOBO_sound::rumble = 0;
KOBO_sound::KOBO_sound()
{
}
KOBO_sound::~KOBO_sound()
{
close();
}
/*--------------------------------------------------
Open/close
--------------------------------------------------*/
int KOBO_sound::load(int (*prog)(const char *msg), int force)
{
int res;
int save_to_disk = 0;
const char *ap = fmap->get("SFX>>", FM_DIR);
if(!ap)
{
log_printf(ELOG, "Couldn't find audio data directory!\n");
return -1;
}
audio_set_path(ap);
if(!sounds_loaded || force)
{
if(prog("Loading sound effects"))
return -999;
res = -1;
if(prefs->cached_sounds && !force)
{
res = audio_wave_load(0, "sfx_c.agw", 0);
if(res < 0)
save_to_disk = 1;
}
if(res < 0)
res = audio_wave_load(0, "sfx.agw", 0);
if(res >= 0)
sounds_loaded = 1;
else
log_printf(ELOG, "Could not load sound effects!\n");
prog(NULL);
}
if(prefs->use_music && !music_loaded || force)
{
if(prog("Loading music"))
return -999;
res = -1;
if(prefs->cached_sounds && !force)
{
res = audio_wave_load(0, "music_c.agw", 0);
if(res < 0)
save_to_disk = 1;
}
if(res < 0)
res = audio_wave_load(0, "music.agw", 0);
if(res >= 0)
music_loaded = 1;
else
log_printf(ELOG, "Could not load music!\n");
prog(NULL);
}
if(save_to_disk)
if(prog("Preparing audio engine"))
return -999;
audio_wave_prepare(-1);
if(save_to_disk)
{
if(prog("Writing sounds to disk"))
return -999;
if(audio_wave_load(0, "save_waves.agw", 0) < 0)
log_printf(ELOG, "Could not save sounds to disk!\n");
}
audio_wave_info(-1);
return prog(NULL);
}
void KOBO_sound::prefschange()
{
/* Levels */
// audio_master_volume((float)prefs->volume/100.0);
audio_group_controlf(SOUND_GROUP_UI, ACC_VOLUME,
(float)prefs->intro_vol/100.0);
audio_group_controlf(SOUND_GROUP_UIMUSIC, ACC_VOLUME,
(float)prefs->intro_vol/100.0);
audio_group_controlf(SOUND_GROUP_SFX, ACC_VOLUME,
(float)prefs->sfx_vol/100.0);
audio_group_controlf(SOUND_GROUP_BGMUSIC, ACC_VOLUME,
(float)prefs->music_vol/100.0);
set_boost(prefs->vol_boost);
audio_quality((audio_quality_t)prefs->mixquality);
// Bus 7: Our "Master Reverb Bus"
master_reverb((float)prefs->reverb/100.0);
}
int KOBO_sound::open()
{
if(!prefs->use_sound)
{
log_printf(WLOG, "Sound disabled!\n");
return 0;
}
if(audio_start(prefs->samplerate, prefs->latency, prefs->use_oss, prefs->cmd_midi,
prefs->cmd_pollaudio) < 0)
{
log_printf(ELOG, "Couldn't initialize audio;"
" disabling sound effects.\n");
return -1;
}
// Channel grouping. We use only one chanel per group here, so we
// just assign the first channels to the available groups.
for(int i = 0; i < AUDIO_MAX_GROUPS; ++i)
audio_channel_control(i, -1, ACC_GROUP, i);
// Dirty hack for the music; the sequencer uses channels 16..31.
for(int i = 16; i < 32; ++i)
audio_channel_control(i, -1, ACC_GROUP, SOUND_GROUP_BGMUSIC);
// Higher priority for music and UI effects
audio_channel_control(SOUND_GROUP_BGMUSIC, AVT_ALL, ACC_PRIORITY, 1);
audio_channel_control(SOUND_GROUP_UIMUSIC, AVT_ALL, ACC_PRIORITY, 2);
audio_channel_control(SOUND_GROUP_UI, AVT_ALL, ACC_PRIORITY, 3);
audio_channel_control(SOUND_GROUP_SFX, AVT_ALL, ACC_PRIORITY, 4);
g_wrap(MAP_SIZEX*CHIP_SIZEX, MAP_SIZEY*CHIP_SIZEY);
g_scale(VIEWLIMIT * 3 / 2, VIEWLIMIT);
// For the noise "bzzzt" effect :-)
audio_channel_control(3, -1, ACC_PRIM_BUS, 1);
// Bus 0: Sound effects
audio_bus_control(0, 1, ABC_FX_TYPE, AFX_NONE);
audio_bus_controlf(0, 0, ABC_SEND_MASTER, 1.0);
audio_bus_controlf(0, 0, ABC_SEND_BUS_7, 0.5); // Always a little rvb!
// Bus 1: Sound effects with less reverb
audio_bus_control(1, 1, ABC_FX_TYPE, AFX_NONE);
audio_bus_controlf(1, 0, ABC_SEND_MASTER, 1.0);
audio_bus_controlf(1, 0, ABC_SEND_BUS_7, 0.1);
prefschange();
time = SDL_GetTicks();
return 0;
}
void KOBO_sound::stop()
{
audio_stop();
}
void KOBO_sound::close()
{
audio_close();
sounds_loaded = 0;
music_loaded = 0;
}
/*--------------------------------------------------
Main controls
--------------------------------------------------*/
void KOBO_sound::master_reverb(float rvb)
{
int master_rvb = (int)(rvb * 65536.0);
audio_bus_controlf(7, 0, ABC_SEND_MASTER, 0.0);
audio_bus_control(7, 1, ABC_SEND_MASTER, master_rvb);
if(master_rvb)
audio_bus_control(7, 1, ABC_FX_TYPE, AFX_REVERB);
else
audio_bus_control(7, 1, ABC_FX_TYPE, AFX_NONE);
}
void KOBO_sound::set_boost(int boost)
{
switch(boost)
{
case 0:
audio_set_limiter(1.0, 5.0);
break;
case 1:
audio_set_limiter(.9, 4.5);
break;
case 2:
default:
audio_set_limiter(.75, 4.0);
break;
case 3:
audio_set_limiter(.5, 3.5);
break;
case 4:
audio_set_limiter(.25, 3.0);
break;
}
}
void KOBO_sound::sfx_volume(float vol)
{
audio_group_controlf(SOUND_GROUP_SFX, ACC_VOLUME,
(float)prefs->sfx_vol * vol / 100.0f);
}
void KOBO_sound::intro_volume(float vol)
{
audio_group_controlf(SOUND_GROUP_UIMUSIC, ACC_VOLUME,
(float)prefs->intro_vol * vol / 100.0f);
}
void KOBO_sound::music_volume(float vol)
{
audio_group_controlf(SOUND_GROUP_BGMUSIC, ACC_VOLUME,
(float)prefs->music_vol * vol / 100.0f);
}
void KOBO_sound::period(int ms)
{
if(ms > 0)
_period = ms;
time = 0;
}
void KOBO_sound::frame()
{
#if 0
// Positional audio test
static int aaa = 0;
if(++aaa > 20)
aaa = 0;
if(aaa == 10)
g_play(SOUND_OVERHEAT, listener_x + 128, listener_y, 32768, 55<<16);
else if (aaa == 20)
g_play(SOUND_OVERHEAT, listener_x - 128, listener_y, 32768, 67<<16);
#endif
// Advance to next game logic frame
int nc = audio_next_callback();
if(time < nc)
time = nc;
else
time -= 1 + (labs(time - nc) >> 5);
audio_bump(time);
time += _period;
// Various sound control logic
++rumble; // Rumble timer
}
void KOBO_sound::run()
{
audio_run();
}
/*--------------------------------------------------
In-game sound
--------------------------------------------------*/
void KOBO_sound::g_music(int wid)
{
audio_channel_control(SOUND_GROUP_BGMUSIC, AVT_FUTURE, ACC_PATCH, wid);
audio_channel_control(SOUND_GROUP_UI, AVT_FUTURE, ACC_PAN, 0);
audio_channel_play(SOUND_GROUP_BGMUSIC, 0, 60<<16, 65536);
}
void KOBO_sound::play(int wid, int vol, int pitch, int pan)
{
audio_channel_control(SOUND_GROUP_UI, AVT_FUTURE, ACC_PATCH, wid);
audio_channel_control(SOUND_GROUP_UI, AVT_FUTURE, ACC_PAN, pan);
audio_channel_play(SOUND_GROUP_UI, 0, pitch, vol);
}
void KOBO_sound::g_position(int x, int y)
{
listener_x = x;
listener_y = y;
}
void KOBO_sound::g_wrap(int w, int h)
{
wrap_x = w;
wrap_y = h;
}
void KOBO_sound::g_scale(int maxrange, int pan_maxrange)
{
scale = 65536 / maxrange;
panscale = 65536 / pan_maxrange;
}
void KOBO_sound::g_play(int wid, int x, int y, int vol, int pitch)
{
int volume, vx, vy, pan;
/* Calculate volume */
x -= listener_x;
y -= listener_y;
if(wrap_x)
{
x += wrap_x / 2;
while(x < 0)
x += wrap_x;
x %= wrap_x;
x -= wrap_x / 2;
}
if(wrap_y)
{
y += wrap_y / 2;
while(y < 0)
y += wrap_y;
y %= wrap_y;
y -= wrap_y / 2;
}
/* Approximation of distance attenuation */
vx = abs(x * scale);
vy = abs(y * scale);
if((vx | vy) & 0xffff0000)
return;
vx = (65536 - vx) >> 1;
vy = (65536 - vy) >> 1;
volume = vx * vy >> 14;
volume = (volume>>1) * (volume>>1) >> 14;
pan = x * panscale;
if(pan < -65536)
pan = -65536;
else if(pan > 65536)
pan = 65536;
audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_PATCH, wid);
audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_PAN, pan);
audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_VOLUME, volume);
audio_channel_play(SOUND_GROUP_SFX, sfx2d_tag, pitch, vol);
sfx2d_tag = (sfx2d_tag + 1) & 0xffff;
}
void KOBO_sound::g_play0(int wid, int vol, int pitch)
{
audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_PATCH, wid);
audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_PAN, 0);
audio_channel_control(SOUND_GROUP_SFX, AVT_FUTURE, ACC_VOLUME, 65536);
audio_channel_play(SOUND_GROUP_SFX, sfx2d_tag, 60<<16, vol);
sfx2d_tag = (sfx2d_tag + 1) & 0xffff;
}
void KOBO_sound::g_player_fire()
{
if(firing)
g_play0(SOUND_SHOT, (prefs->cannonloud << 13) / 100);
else
{
firing = 1;
g_play0(SOUND_SHOT_START, 20000);
}
overheat = 0;
}
void KOBO_sound::g_player_fire_off()
{
if(firing)
{
firing = 0;
g_play0(SOUND_SHOT_END, 20000);
}
}
void KOBO_sound::g_player_overheat(int classic)
{
if(overheat)
return;
overheat = 1;
g_play0(SOUND_SHOT_OVERHEAT, (prefs->cannonloud << 13) / 75);
if(!classic)
g_play(SOUND_OVERHEAT, listener_x + 100, listener_y,
(prefs->overheatloud << 14) / 100);
}
void KOBO_sound::g_player_damage()
{
int p0 = (60<<16) + (pubrand.get(9) << 8);
g_play(SOUND_DAMAGE, listener_x - 100, listener_y,
65536, p0 + 3000);
g_play(SOUND_DAMAGE, listener_x + 100, listener_y,
65536, p0 - 3000);
}
void KOBO_sound::g_player_explo_start()
{
g_player_damage();
g_play(SOUND_EXPLO_PLAYER, listener_x - 100, listener_y,
65536, (55<<16) + 4000);
g_play(SOUND_EXPLO_PLAYER, listener_x + 100, listener_y,
65536, (55<<16) - 4000);
}
void KOBO_sound::g_bolt_hit(int x, int y)
{
g_play(SOUND_METALLIC1 + (pubrand.get() % 3),
CS2PIXEL(x), CS2PIXEL(y),
12000);
}
void KOBO_sound::g_bolt_hit_rock(int x, int y)
{
g_play(SOUND_HIT1 + (pubrand.get() % 3),
CS2PIXEL(x), CS2PIXEL(y),
20000);
}
void KOBO_sound::g_base_node_explo(int x, int y)
{
g_play(pubrand.get(8) > 128 ? SOUND_EXPLO_NODE1 : SOUND_EXPLO_NODE2,
CS2PIXEL(x), CS2PIXEL(y));
rumble = 0;
}
void KOBO_sound::g_base_core_explo(int x, int y)
{
x = CS2PIXEL(x);
y = CS2PIXEL(y);
g_play(pubrand.get(8) > 128 ? SOUND_EXPLO_NODE1 : SOUND_EXPLO_NODE2,
x, y, 100000, 62<<16);
g_play(SOUND_FINE, x + 30, y, 65536, (60<<16) + 4000);
g_play(SOUND_FINE, x - 30, y, 65536, (60<<16) - 4000);
rumble = 0;
}
void KOBO_sound::g_pipe_rumble(int x, int y)
{
if(rumble < 4 + pubrand.get(3))
return;
g_play(pubrand.get(8) > 128 ? SOUND_RUMBLE_NODE1 : SOUND_RUMBLE_NODE2,
CS2PIXEL(x), CS2PIXEL(y));
rumble = 0;
}
void KOBO_sound::g_enemy_explo(int x, int y)
{
g_play(SOUND_EXPLO_ENEMY1 + (pubrand.get() % 2),
CS2PIXEL(x), CS2PIXEL(y), 40000);
}
void KOBO_sound::g_rock_explo(int x, int y)
{
g_play(SOUND_EXPLO_ROCK, CS2PIXEL(x), CS2PIXEL(y), 40000);
}
void KOBO_sound::g_ring_explo(int x, int y)
{
g_play(SOUND_EXPLO_RING1 + (pubrand.get() % 3),
CS2PIXEL(x), CS2PIXEL(y), 32768);
}
void KOBO_sound::g_bomb_deto(int x, int y)
{
g_play(SOUND_BOMB_DETO, CS2PIXEL(x), CS2PIXEL(y), 40000);
}
void KOBO_sound::g_launch_ring(int x, int y)
{
g_play(SOUND_RING, CS2PIXEL(x), CS2PIXEL(y));
}
void KOBO_sound::g_launch_beam(int x, int y)
{
g_play(SOUND_BEAM, CS2PIXEL(x), CS2PIXEL(y));
}
void KOBO_sound::g_launch_bomb(int x, int y)
{
g_play(SOUND_LAUNCH2, CS2PIXEL(x), CS2PIXEL(y));
}
void KOBO_sound::g_launch(int x, int y)
{
g_play(SOUND_LAUNCH, CS2PIXEL(x), CS2PIXEL(y));
}
void KOBO_sound::g_m_launch_ring(int x, int y)
{
g_play(SOUND_RING, CS2PIXEL(x), CS2PIXEL(y));
g_play(SOUND_ENEMYM, CS2PIXEL(x), CS2PIXEL(y));
}
void KOBO_sound::g_m_launch_beam(int x, int y)
{
g_play(SOUND_BEAM, CS2PIXEL(x), CS2PIXEL(y));
g_play(SOUND_ENEMYM, CS2PIXEL(x), CS2PIXEL(y));
}
void KOBO_sound::g_m_launch_bomb(int x, int y)
{
g_play(SOUND_LAUNCH2, CS2PIXEL(x), CS2PIXEL(y));
g_play(SOUND_ENEMYM, CS2PIXEL(x), CS2PIXEL(y));
}
void KOBO_sound::g_m_launch(int x, int y)
{
g_play(SOUND_LAUNCH, CS2PIXEL(x), CS2PIXEL(y));
g_play(SOUND_ENEMYM, CS2PIXEL(x), CS2PIXEL(y));
}
/*--------------------------------------------------
UI sound effects
--------------------------------------------------*/
void KOBO_sound::ui_music(int wid)
{
// KLUDGE UNTIL MIDI SONGS AND FX CONTROL IS FIXED!
audio_bus_control(0, 1, ABC_FX_TYPE, AFX_NONE);
audio_bus_controlf(0, 0, ABC_SEND_MASTER, 1.0);
audio_bus_controlf(0, 0, ABC_SEND_BUS_7, 0.5);
// KLUDGE UNTIL MIDI SONGS AND FX CONTROL IS FIXED!
audio_channel_control(SOUND_GROUP_UIMUSIC, AVT_FUTURE, ACC_PATCH, wid);
audio_channel_play(SOUND_GROUP_UIMUSIC, 0, 60<<16, 65536);
}
void KOBO_sound::ui_noise(int n)
{
if(n < 0)
{
sound.sfx_volume(0.0f);
audio_channel_control(SOUND_GROUP_UI,
AVT_FUTURE, ACC_PATCH, SOUND_BZZZT);
audio_channel_play(SOUND_GROUP_UI, 0, 60<<16,
10000 + pubrand.get(14));
return;
}
if(n && pubrand.get(1))
{
sound.sfx_volume(0.0f);
audio_channel_control(SOUND_GROUP_UI,
AVT_FUTURE, ACC_PATCH, SOUND_BZZZT);
audio_channel_play(SOUND_GROUP_UI, 0, 60<<16,
20000 + pubrand.get(14));
}
else
sound.sfx_volume(1.0f);
}
void KOBO_sound::ui_ok()
{
play(SOUND_EXPLO_NODE1, 32768);
}
void KOBO_sound::ui_cancel()
{
play(SOUND_CANCEL, 20000);
}
void KOBO_sound::ui_move()
{
play(SOUND_MOVE, 20000);
}
void KOBO_sound::ui_tick()
{
play(SOUND_METALLIC1, 15000);
}
void KOBO_sound::ui_error()
{
play(SOUND_OVERHEAT, 20000);
}
void KOBO_sound::ui_play()
{
play(SOUND_PLAY, 32768, (50<<16) + 2000, -40000);
play(SOUND_PLAY, 32768, (50<<16) - 2000, 40000);
}
void KOBO_sound::ui_pause()
{
play(SOUND_PAUSE, 32768, (56<<16) + 2000, -40000);
play(SOUND_PAUSE, 32768, (56<<16) - 2000, 40000);
}
void KOBO_sound::ui_ready()
{
play(SOUND_READY, 15000, (60<<16) + 3000, -40000);
play(SOUND_READY, 15000, (60<<16) - 3000, 40000);
}
void KOBO_sound::ui_countdown(int remain)
{
play(SOUND_TICK, 20000, (65 - remain)<<16);
}
void KOBO_sound::ui_oneup()
{
play(SOUND_ONEUP, 32768, (60<<16) + 5000, -40000);
play(SOUND_ONEUP, 32768, (60<<16) - 5000, 40000);
}
void KOBO_sound::ui_gameover()
{
play(SOUND_GAMEOVER);
}