Sound Blaster improvements :

- Implemented audio recording on SB16 & AWE32 (MIDI only)
- Mixer improvements
- SB 2.0 CD mixer can be emulated
- DSP tweaks, mainly for SB Pro. Implemented speaker on/off and limited high speed DMA to SB 2.0 & Pro
- A few other things
Patch from JosepMa.
This commit is contained in:
SarahW 2017-09-09 14:58:07 +01:00
commit 4ca6d67f87
5 changed files with 1188 additions and 297 deletions

View file

@ -1396,8 +1396,8 @@ void emu8k_outw(uint16_t addr, uint16_t val, void *p)
if ((val&0xFF) == 0 && the_voice->cvcf_curr_volume == 0 && the_voice->vtft_vol_target == 0
&& the_voice->dcysusv == 0x80 && the_voice->ip == 0)
{
// Patch to avoid some clicking noises with Impulse tracker or other software that sets different values to 0
// to set noteoff, but here, 0 means no attenuation = full volume.
// Patch to avoid some clicking noises with Impulse tracker or other software that sets
// different values to 0 to set noteoff, but here, 0 means no attenuation = full volume.
return;
}
the_voice->ifatn = val;
@ -2100,7 +2100,7 @@ I've recopilated these sentences to get an idea of how to loop
emu8k->pos = new_pos;
}
/* onboard_ram in kilobytes */
void emu8k_init(emu8k_t *emu8k, int onboard_ram)
void emu8k_init(emu8k_t *emu8k, uint16_t emu_addr, int onboard_ram)
{
uint32_t const BLOCK_SIZE_WORDS = 0x10000;
FILE *f;
@ -2160,9 +2160,9 @@ void emu8k_init(emu8k_t *emu8k, int onboard_ram)
}
io_sethandler(0x0620, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
io_sethandler(0x0a20, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
io_sethandler(0x0e20, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
io_sethandler(emu_addr, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
io_sethandler(emu_addr+0x400, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
io_sethandler(emu_addr+0x800, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
/*Create frequency table. (Convert initial pitch register value to a linear speed change)
* The input is encoded such as 0xe000 is center note (no pitch shift)

View file

@ -357,8 +357,6 @@ typedef struct emu8k_t
uint16_t wc;
uint16_t c02_read;
uint16_t id;
/* The empty block is used to act as an unallocated memory returning zero. */
@ -370,8 +368,6 @@ typedef struct emu8k_t
int cur_reg, cur_voice;
int timer_count;
int16_t out_l, out_r;
emu8k_chorus_eng_t chorus_engine;
@ -385,7 +381,7 @@ typedef struct emu8k_t
void emu8k_init(emu8k_t *emu8k, int onboard_ram);
void emu8k_init(emu8k_t *emu8k, uint16_t emu_addr, int onboard_ram);
void emu8k_close(emu8k_t *emu8k);
void emu8k_update(emu8k_t *emu8k);

File diff suppressed because it is too large Load diff

View file

@ -16,10 +16,22 @@
#include "sound_sb_dsp.h"
#include "timer.h"
/*The recording safety margin is intended for uneven "len" calls to the get_buffer mixer calls on sound_sb*/
#define SB_DSP_REC_SAFEFTY_MARGIN 4096
void pollsb(void *p);
void sb_poll_i(void *p);
//#define SB_DSP_RECORD_DEBUG
//#define SB_TEST_RECORDING_SAW
#ifdef SB_DSP_RECORD_DEBUG
FILE* soundf = 0/*NULL*/;
#endif
#ifdef SB_TEST_RECORDING_SAW
int counttest;
#endif
static int sbe2dat[4][9] = {
{ 0x01, -0x02, -0x04, 0x08, -0x10, 0x20, 0x40, -0x80, -106 },
@ -133,9 +145,20 @@ void sb_dsp_reset(sb_dsp_t *dsp)
dsp->sbreset = 0;
dsp->sbenable = dsp->sb_enable_i = dsp->sb_count_i = 0;
dsp->record_pos_read=0;
dsp->record_pos_write=SB_DSP_REC_SAFEFTY_MARGIN;
picintc(1 << dsp->sb_irqnum);
dsp->asp_data_len = 0;
#ifdef SB_DSP_RECORD_DEBUG
if (soundf != 0)
{
fclose(soundf);
soundf=0;
}
#endif
}
void sb_doreset(sb_dsp_t *dsp)
@ -205,8 +228,8 @@ void sb_start_dma(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len
if (dsp->sb_8_enable && dsp->sb_8_output) dsp->sb_8_enable = 0;
dsp->sb_16_output = 1;
timer_process();
dsp->sbenable = dsp->sb_16_enable;
timer_update_outstanding();
dsp->sbenable = dsp->sb_16_enable;
timer_update_outstanding();
// pclog("Start 16-bit DMA addr %06X len %04X\n",dma16.ac[1]+(dma16.page[1]<<16),len);
}
}
@ -215,6 +238,13 @@ void sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int l
{
if (dma8)
{
#ifdef SB_TEST_RECORDING_SAW
switch (dsp->sb_8_format)
{
case 00:case 20:counttest=0x80;break;
case 10:case 30:counttest=0;break;
}
#endif
dsp->sb_8_length = len;
dsp->sb_8_format = format;
dsp->sb_8_autoinit = autoinit;
@ -229,6 +259,13 @@ void sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int l
}
else
{
#ifdef SB_TEST_RECORDING_SAW
switch (dsp->sb_16_format)
{
case 00:case 20:counttest=0x8000;break;
case 10:case 30:counttest=0;break;
}
#endif
dsp->sb_16_length = len;
dsp->sb_16_format = format;
dsp->sb_16_autoinit = autoinit;
@ -241,6 +278,16 @@ void sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int l
timer_update_outstanding();
// pclog("Start 16-bit input DMA addr %06X len %04X\n",dma.ac[1]+(dma.page[1]<<16),len);
}
memset(dsp->record_buffer,0,sizeof(dsp->record_buffer));
#ifdef SB_DSP_RECORD_DEBUG
if (soundf != 0)
{
fclose(soundf);
soundf=0;
}
#endif
}
int sb_8_read_dma(sb_dsp_t *dsp)
@ -250,14 +297,23 @@ int sb_8_read_dma(sb_dsp_t *dsp)
void sb_8_write_dma(sb_dsp_t *dsp, uint8_t val)
{
dma_channel_write(dsp->sb_8_dmanum, val);
#ifdef SB_DSP_RECORD_DEBUG
if (!soundf) soundf=fopen("sound_dsp.pcm","wb");
fwrite(&val,1,1,soundf);
#endif
}
uint16_t sb_16_read_dma(sb_dsp_t *dsp)
{
return dma_channel_read(5);
return dma_channel_read(dsp->sb_16_dmanum);
}
void sb_16_write_dma(sb_dsp_t *dsp, uint16_t val)
int sb_16_write_dma(sb_dsp_t *dsp, uint16_t val)
{
dma_channel_write(5, val);
int ret = dma_channel_write(dsp->sb_16_dmanum, val);
#ifdef SB_DSP_RECORD_DEBUG
if (!soundf) soundf=fopen("sound_dsp.pcm","wb");
fwrite(&val,2,1,soundf);
#endif
return (ret == DMA_NODATA);
}
void sb_dsp_setirq(sb_dsp_t *dsp, int irq)
@ -270,6 +326,10 @@ void sb_dsp_setdma8(sb_dsp_t *dsp, int dma)
dsp->sb_8_dmanum = dma;
}
void sb_dsp_setdma16(sb_dsp_t *dsp, int dma)
{
dsp->sb_16_dmanum = dma;
}
void sb_exec_command(sb_dsp_t *dsp)
{
int temp,c;
@ -310,7 +370,19 @@ void sb_exec_command(sb_dsp_t *dsp)
dsp->sb_8_length--;
break;
case 0x20: /*8-bit direct input*/
sb_add_data(dsp, 0);
sb_add_data(dsp, (dsp->record_buffer[dsp->record_pos_read]>>8) ^0x80);
/*Due to the current implementation, I need to emulate a samplerate, even if this
* mode does not imply such samplerate. Position is increased in sb_poll_i*/
if (dsp->sb_enable_i==0)
{
dsp->sb_timei = 256 - 22;
dsp->sblatchi = TIMER_USEC * 22;
temp = 1000000 / 22;
dsp->sb_freq = temp;
timer_process();
dsp->sb_enable_i = 1;
timer_update_outstanding();
}
break;
case 0x24: /*8-bit single cycle DMA input*/
sb_start_dma_i(dsp, 1, 0, 0, dsp->sb_data[0] + (dsp->sb_data[1] << 8));
@ -373,28 +445,30 @@ void sb_exec_command(sb_dsp_t *dsp)
case 0x80: /*Pause DAC*/
dsp->sb_pausetime = dsp->sb_data[0] + (dsp->sb_data[1] << 8);
// pclog("SB pause %04X\n",sb_pausetime);
timer_process();
timer_process();
dsp->sbenable = 1;
timer_update_outstanding();
break;
case 0x90: /*High speed 8-bit autoinit DMA output*/
if (dsp->sb_type < SB2) break;
if (dsp->sb_type < SB2 || dsp->sb_type > SBPRO2) break;
sb_start_dma(dsp, 1, 1, 0, dsp->sb_8_autolen);
break;
case 0x91: /*High speed 8-bit single cycle DMA output*/
if (dsp->sb_type < SB2) break;
if (dsp->sb_type < SB2 || dsp->sb_type > SBPRO2) break;
sb_start_dma(dsp, 1, 0, 0, dsp->sb_8_autolen);
break;
case 0x98: /*High speed 8-bit autoinit DMA input*/
if (dsp->sb_type < SB2) break;
if (dsp->sb_type < SB2 || dsp->sb_type > SBPRO2) break;
sb_start_dma_i(dsp, 1, 1, 0, dsp->sb_8_autolen);
break;
case 0x99: /*High speed 8-bit single cycle DMA input*/
if (dsp->sb_type < SB2) break;
if (dsp->sb_type < SB2 || dsp->sb_type > SBPRO2) break;
sb_start_dma_i(dsp, 1, 0, 0, dsp->sb_8_autolen);
break;
case 0xA0: /*Set input mode to mono*/
case 0xA8: /*Set input mode to stereo*/
if (dsp->sb_type < SB2 || dsp->sb_type > SBPRO2) break;
//TODO: Implement. 3.xx-only command.
break;
case 0xB0: case 0xB1: case 0xB2: case 0xB3:
case 0xB4: case 0xB5: case 0xB6: case 0xB7: /*16-bit DMA output*/
@ -424,9 +498,17 @@ void sb_exec_command(sb_dsp_t *dsp)
dsp->sb_8_pause = 1;
break;
case 0xD1: /*Speaker on*/
if (dsp->sb_type < SB15 )
dsp->sb_8_pause = 1;
else if ( dsp->sb_type < SB16 )
dsp->muted = 0;
dsp->sb_speaker = 1;
break;
case 0xD3: /*Speaker off*/
if (dsp->sb_type < SB15 )
dsp->sb_8_pause = 1;
else if ( dsp->sb_type < SB16 )
dsp->muted = 1;
dsp->sb_speaker = 0;
break;
case 0xD4: /*Continue 8-bit DMA*/
@ -481,6 +563,10 @@ void sb_exec_command(sb_dsp_t *dsp)
// pclog("Trigger IRQ\n");
sb_irq(dsp, 1);
break;
case 0xF3: /*Trigger 16-bit IRQ*/
// pclog("Trigger IRQ\n");
sb_irq(dsp, 0);
break;
case 0xE7: /*???*/
case 0xFA: /*???*/
break;
@ -517,6 +603,22 @@ void sb_exec_command(sb_dsp_t *dsp)
break;
// default:
// fatal("Exec bad SB command %02X\n",sb_command);
/*TODO: Some more data about the DSP registeres
* http://the.earth.li/~tfm/oldpage/sb_dsp.html
* http://www.synchrondata.com/pheaven/www/area19.htm
* http://www.dcee.net/Files/Programm/Sound/
0E3h DSP Copyright SBPro2???
0F0h Sine Generator SB
0F1h DSP Auxiliary Status (Obsolete) SB-Pro2
0F2h IRQ Request, 8-bit SB
0F3h IRQ Request, 16-bit SB16
0FBh DSP Status SB16
0FCh DSP Auxiliary Status SB16
0FDh DSP Command Status SB16
*/
}
}
@ -570,7 +672,6 @@ void sb_write(uint16_t a, uint8_t v, void *priv)
uint8_t sb_read(uint16_t a, void *priv)
{
sb_dsp_t *dsp = (sb_dsp_t *)priv;
// if (a==0x224) output=1;
// pclog("sb_read : Read soundblaster %04X %04X:%04X\n",a,CS,pc);
switch (a & 0xf)
{
@ -613,8 +714,10 @@ void sb_dsp_init(sb_dsp_t *dsp, int type)
{
dsp->sb_type = type;
// Default values. Use sb_dsp_setxxx() methods to change.
dsp->sb_irqnum = 7;
dsp->sb_8_dmanum = 1;
dsp->sb_16_dmanum = 5;
sb_doreset(dsp);
@ -670,7 +773,7 @@ void pollsb(void *p)
dsp->sbleftright = !dsp->sbleftright;
}
else
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
dsp->sb_8_length--;
break;
case 0x10: /*Mono signed*/
@ -685,7 +788,7 @@ void pollsb(void *p)
dsp->sbleftright = !dsp->sbleftright;
}
else
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
dsp->sb_8_length--;
break;
case 0x20: /*Stereo unsigned*/
@ -737,7 +840,7 @@ void pollsb(void *p)
dsp->sbleftright = !dsp->sbleftright;
}
else
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
break;
case ADPCM_26:
@ -771,7 +874,7 @@ void pollsb(void *p)
dsp->sbleftright = !dsp->sbleftright;
}
else
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
break;
case ADPCM_2:
@ -801,7 +904,7 @@ void pollsb(void *p)
dsp->sbleftright = !dsp->sbleftright;
}
else
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
dsp->sbdatl = dsp->sbdatr = dsp->sbdat;
break;
// default:
@ -839,7 +942,6 @@ void pollsb(void *p)
dsp->sbdatr = sb_16_read_dma(dsp);
dsp->sb_16_length -= 2;
break;
// default:
// fatal("Unrecognised SB 16-bit format %02X\n",sb_16_format);
}
@ -867,32 +969,57 @@ void pollsb(void *p)
void sb_poll_i(void *p)
{
sb_dsp_t *dsp = (sb_dsp_t *)p;
int processed=0;
dsp->sb_count_i += dsp->sblatchi;
// pclog("PollSBi %i %i %i %i\n",sb_8_enable,sb_8_pause,sb_pausetime,sb_8_output);
if (dsp->sb_8_enable && !dsp->sb_8_pause && dsp->sb_pausetime < 0 && !dsp->sb_8_output)
{
switch (dsp->sb_8_format)
{
case 0x00: /*Mono unsigned*/
sb_8_write_dma(dsp, 0x80);
#ifdef SB_TEST_RECORDING_SAW
case 0x00: /*Unsigned mono. As the manual says, only the left channel is recorded*/
case 0x10: /*Signed mono. As the manual says, only the left channel is recorded*/
sb_8_write_dma(dsp, counttest);
counttest+=0x10;
counttest&=0xFF;
dsp->sb_8_length--;
break;
case 0x10: /*Mono signed*/
sb_8_write_dma(dsp, 0x00);
case 0x20: /*Unsigned stereo*/
case 0x30: /*Signed stereo*/
sb_8_write_dma(dsp, counttest);
sb_8_write_dma(dsp, counttest);
counttest+=0x10;
counttest&=0xFF;
dsp->sb_8_length -= 2;
break;
#else
case 0x00: /*Mono unsigned As the manual says, only the left channel is recorded*/
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read]>>8) ^0x80);
dsp->sb_8_length--;
dsp->record_pos_read+=2;
dsp->record_pos_read&=0xFFFF;
break;
case 0x10: /*Mono signed As the manual says, only the left channel is recorded*/
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read]>>8));
dsp->sb_8_length--;
dsp->record_pos_read+=2;
dsp->record_pos_read&=0xFFFF;
break;
case 0x20: /*Stereo unsigned*/
sb_8_write_dma(dsp, 0x80);
sb_8_write_dma(dsp, 0x80);
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read]>>8)^0x80);
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read+1]>>8)^0x80);
dsp->sb_8_length -= 2;
dsp->record_pos_read+=2;
dsp->record_pos_read&=0xFFFF;
break;
case 0x30: /*Stereo signed*/
sb_8_write_dma(dsp, 0x00);
sb_8_write_dma(dsp, 0x00);
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read]>>8));
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read+1]>>8));
dsp->sb_8_length -= 2;
dsp->record_pos_read+=2;
dsp->record_pos_read&=0xFFFF;
break;
#endif
// default:
// fatal("Unrecognised SB 8-bit input format %02X\n",sb_8_format);
}
@ -901,33 +1028,65 @@ void sb_poll_i(void *p)
{
// pclog("Input DMA over %i\n",sb_8_autoinit);
if (dsp->sb_8_autoinit) dsp->sb_8_length = dsp->sb_8_autolen;
else dsp->sb_8_enable = dsp->sbenable = 0;
else dsp->sb_8_enable = dsp->sb_enable_i = 0;
sb_irq(dsp, 1);
}
processed=1;
}
if (dsp->sb_16_enable && !dsp->sb_16_pause && dsp->sb_pausetime < 0 && !dsp->sb_16_output)
{
switch (dsp->sb_16_format)
{
case 0x00: /*Unsigned mono*/
sb_16_write_dma(dsp, 0x8000);
dsp->sb_16_length--;
break;
case 0x10: /*Signed mono*/
sb_16_write_dma(dsp, 0);
#ifdef SB_TEST_RECORDING_SAW
case 0x00: /*Unsigned mono. As the manual says, only the left channel is recorded*/
case 0x10: /*Signed mono. As the manual says, only the left channel is recorded*/
if (sb_16_write_dma(dsp, counttest))
return;
counttest+=0x1000;
counttest&=0xFFFF;
dsp->sb_16_length--;
break;
case 0x20: /*Unsigned stereo*/
sb_16_write_dma(dsp, 0x8000);
sb_16_write_dma(dsp, 0x8000);
case 0x30: /*Signed stereo*/
if (sb_16_write_dma(dsp, counttest))
return;
sb_16_write_dma(dsp, counttest);
counttest+=0x1000;
counttest&=0xFFFF;
dsp->sb_16_length -= 2;
break;
#else
case 0x00: /*Unsigned mono. As the manual says, only the left channel is recorded*/
if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read]^0x8000))
return;
dsp->sb_16_length--;
dsp->record_pos_read+=2;
dsp->record_pos_read&=0xFFFF;
break;
case 0x10: /*Signed mono. As the manual says, only the left channel is recorded*/
if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read]))
return;
dsp->sb_16_length--;
dsp->record_pos_read+=2;
dsp->record_pos_read&=0xFFFF;
break;
case 0x20: /*Unsigned stereo*/
if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read]^0x8000))
return;
sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read+1]^0x8000);
dsp->sb_16_length -= 2;
dsp->record_pos_read+=2;
dsp->record_pos_read&=0xFFFF;
break;
case 0x30: /*Signed stereo*/
sb_16_write_dma(dsp, 0);
sb_16_write_dma(dsp, 0);
if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read]))
return;
sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read+1]);
dsp->sb_16_length -= 2;
dsp->record_pos_read+=2;
dsp->record_pos_read&=0xFFFF;
break;
#endif
// default:
// fatal("Unrecognised SB 16-bit input format %02X\n",sb_16_format);
}
@ -936,20 +1095,43 @@ void sb_poll_i(void *p)
{
// pclog("16iDMA over %i\n",sb_16_autoinit);
if (dsp->sb_16_autoinit) dsp->sb_16_length = dsp->sb_16_autolen;
else dsp->sb_16_enable = dsp->sbenable = 0;
else dsp->sb_16_enable = dsp->sb_enable_i = 0;
sb_irq(dsp, 0);
}
processed=1;
}
//Assume this is direct mode
if (!processed)
{
dsp->record_pos_read+=2;
dsp->record_pos_read&=0xFFFF;
}
}
void sb_dsp_update(sb_dsp_t *dsp)
{
/*this "if" implements two things: speaker on/off and buffer cleanup after stopping audio. */
if (!dsp->sbenable || dsp->muted)
{
dsp->sbdatl=0;
dsp->sbdatr=0;
}
for (; dsp->pos < sound_pos_global; dsp->pos++)
{
dsp->buffer[dsp->pos*2] = dsp->sbdatl;
dsp->buffer[dsp->pos*2 + 1] = dsp->sbdatr;
}
}
void sb_dsp_close(sb_dsp_t *dsp)
{
#ifdef SB_DSP_RECORD_DEBUG
if (soundf != 0)
{
fclose(soundf);
soundf=0;
}
#endif
}
void sb_dsp_add_status_info(char *s, int max_len, sb_dsp_t *dsp)
{

View file

@ -5,11 +5,13 @@ typedef struct sb_dsp_t
int sb_8_length, sb_8_format, sb_8_autoinit, sb_8_pause, sb_8_enable, sb_8_autolen, sb_8_output;
int sb_8_dmanum;
int sb_16_length, sb_16_format, sb_16_autoinit, sb_16_pause, sb_16_enable, sb_16_autolen, sb_16_output;
int sb_16_dmanum;
int sb_pausetime;
uint8_t sb_read_data[256];
int sb_read_wp, sb_read_rp;
int sb_speaker;
int muted;
int sb_data_stat;
@ -56,15 +58,20 @@ typedef struct sb_dsp_t
int asp_data_len;
int wb_time, wb_full;
int record_pos_read;
int record_pos_write;
int16_t record_buffer[0xFFFF];
int16_t buffer[MAXSOUNDBUFLEN * 2];
int pos;
} sb_dsp_t;
void sb_dsp_init(sb_dsp_t *dsp, int type);
void sb_dsp_close(sb_dsp_t *dsp);
void sb_dsp_setirq(sb_dsp_t *dsp, int irq);
void sb_dsp_setdma8(sb_dsp_t *dsp, int dma);
void sb_dsp_setdma16(sb_dsp_t *dsp, int dma);
void sb_dsp_setaddr(sb_dsp_t *dsp, uint16_t addr);
void sb_dsp_speed_changed(sb_dsp_t *dsp);