Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,24 @@
# audio_driver = lptdss
# audio_config_lpt = 1

# The PC speaker pulse width modulation driver can play PCM sounds over the
# hardware PC speaker. Like LPT audio, it can be computationally expensive.
# Lower sample rates correspond to better sample quality, but rates under about
# 18kHz have audible high pitched carrier frequencies. Different combinations
# of audio_sample_rate and audio_buffer_samples can help reduce noise somewhat.
# Supported sample rates (approximate): 5000, 6000, 7000, 8000, 9321, 10000,
# 11000, 15500, 17000, 18643, 22500, 26500, 27500, 32000, 37286.

# audio_driver = pcs

# This driver can be used without a sound card to enable hardware PC speaker
# sound effects without playing PCM. It is automatically selected if the
# other audio drivers fail to initialize.

# audio_driver = nopcm

# To enable hardware PC speaker sound effects for any DOS audio driver,
# enable this option. This is disabled by default for most drivers, but
# To enable hardware PC speaker sound effects for most DOS audio drivers,
# enable this option. This is disabled by default except with nopcm, but
# may improve performance on slower machines.

# audio_pc_speaker_use_hardware = 1
Expand Down
1 change: 1 addition & 0 deletions src/audio/audio_drivers.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ static const struct audio_driver * const available_drivers[] =
&audio_driver_lpt_stereo1,
&audio_driver_lpt_stereo2,
&audio_driver_lpt_dss,
&audio_driver_pcs_pwm,
&audio_driver_no_pcm,
#endif
#ifdef CONFIG_NDS
Expand Down
1 change: 1 addition & 0 deletions src/audio/audio_drivers.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ extern const struct audio_driver audio_driver_lpt_mono;
extern const struct audio_driver audio_driver_lpt_stereo1;
extern const struct audio_driver audio_driver_lpt_stereo2;
extern const struct audio_driver audio_driver_lpt_dss;
extern const struct audio_driver audio_driver_pcs_pwm;
extern const struct audio_driver audio_driver_no_pcm;

extern const struct audio_driver audio_driver_nds;
Expand Down
82 changes: 80 additions & 2 deletions src/audio/djgpp/audio_lpt.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <dpmi.h>
Expand Down Expand Up @@ -177,8 +178,18 @@ static boolean init_audio_driver_lpt(unsigned mix_rate, unsigned irq_rate,
return false;
}

info("LPT audio initialized: %xh/%xh %uFr %uCh %zuHz (%uHz)", lpt_left_port,
lpt_right_port, audio.buffer_frames, channels, audio.output_frequency, irq_rate);
if(irq_handler == &pcs_pwm_handler)
{
info("PCS audio initialized: %.2f bit %uFr %uCh %zuHz (%uHz)\n",
log(current_div) / M_LN2, audio.buffer_frames, channels,
audio.output_frequency, irq_rate);
}
else
{
info("LPT audio initialized: %xh/%xh %uFr %uCh %zuHz (%uHz)\n",
lpt_left_port, lpt_right_port, audio.buffer_frames, channels,
audio.output_frequency, irq_rate);
}
return true;
}

Expand Down Expand Up @@ -233,6 +244,59 @@ static void quit_audio_driver_lpt(void)
lpt_free_buffer();
}

/* PC speaker rates are even more limited than LPT rates due to
* issues with carrier frequencies, harmonics, and bus noise. */
static uint16_t pcs_pwm_get_rate(int requested_rate)
{
static const uint16_t supported_rates[] =
{
LPT_5K_HZ, LPT_6K_HZ, LPT_7K_HZ, LPT_8K_HZ, PCS_9K_HZ,
LPT_10K_HZ, LPT_11K_HZ, LPT_15K_HZ, LPT_17K_HZ, PCS_18K_HZ,
LPT_22K_HZ, LPT_26K_HZ, LPT_28K_HZ, LPT_32K_HZ, PCS_37K_HZ
};
size_t i;

for(i = 0; i < ARRAY_SIZE(supported_rates) - 1; i++)
if(requested_rate < ((int)supported_rates[i] + supported_rates[i + 1]) >> 1)
break;

return supported_rates[i];
}

static void pcs_pwm_off(void)
{
/* Clear PC speaker gate */
outportb(0x61, inportb(0x61) & 0xfc);
/* Reset mode to 3 */
outportb(0x43, 0xb6);
}

/* This driver is so similar to the LPT drivers that it is included here
* and shares most of the same code. */
static boolean init_audio_driver_pcs_pwm(struct config_info *conf)
{
unsigned rate = pcs_pwm_get_rate(conf->audio_sample_rate);
unsigned frames = MIN(conf->audio_buffer_samples, 32768);

/* Channel 2 mode: low byte, mode 0 "interrupt on terminal count", binary */
outportb(0x43, 0x90);
/* Raise PC speaker gate */
outportb(0x61, inportb(0x61) | 0x03);

if(!init_audio_driver_lpt(rate, rate, frames, 1, &pcs_pwm_handler))
{
pcs_pwm_off();
return false;
}
return true;
}

static void quit_audio_driver_pcs_pwm(void)
{
quit_audio_driver_lpt();
pcs_pwm_off();
}

const struct audio_driver audio_driver_lpt_mono =
{
"LPT DAC (Mono)",
Expand Down Expand Up @@ -288,3 +352,17 @@ const struct audio_driver audio_driver_lpt_dss =
init_audio_driver_lpt_dss,
quit_audio_driver_lpt,
};

const struct audio_driver audio_driver_pcs_pwm =
{
"PC Speaker pulse width modulated PCM",
"pcs",
false, /* Must be explicitly requested */

NULL,
&audio_player_pcs,
NULL,

init_audio_driver_pcs_pwm,
quit_audio_driver_pcs_pwm,
};
26 changes: 25 additions & 1 deletion src/platform/djgpp/interrupt.S
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ _timer_handler_tick:
je 3f /* goto nosound; */
/* sound(freq) */
movb $1, _sfx_playing /* sfx_playing = true; */
movb $0xb6, %al /* Channel 2, lo + hi, mode 2, binary */
movb $0xb6, %al /* Channel 2, lo + hi, mode 3, binary */
outb %al, $0x43 /* PIT Mode/Command register */
movw SFX_DIVIDEND_DX, %dx
movw SFX_DIVIDEND_AX, %ax
Expand Down Expand Up @@ -230,6 +230,30 @@ _lpt_left_port:
_lpt_right_port:
.word 0

.balign 16
.global _pcs_pwm_handler
_pcs_pwm_handler:
pushl %eax
pushl %ds
pushl %esi
movw %cs:_int_ds, %ds
movl _dos_audio_buffer_pos, %esi
movb _current_div, %ah
movb (%esi), %al /* al = *pos++; */
incl %esi
mulb %ah /* ax = al * ah; */
movb %ah, %al /* ax >>= 8; */
outb %al, $0x42 /* write shifted sample to PCS reload */
cmpl _dos_audio_buffer_end, %esi
jae 2f /* if(pos >= end) goto 2; */
1:
movl %esi, _dos_audio_buffer_pos
popl %esi
jmp _timer_handler_var /* goto timer_handler_var; */
2: /* (early predictors assume no jump) */
movl _dos_audio_buffer, %esi /* pos = dos_audio_buffer; */
jmp 1b

.balign 16
.global _lpt_mono_handler
_lpt_mono_handler:
Expand Down
5 changes: 5 additions & 0 deletions src/platform/djgpp/interrupt.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ MEGAZEUX_BEGIN_DECLS
#define LPT_48K_HZ 47727 /* Divider 25 gets closest to 48000 */
#define LPT_DSS_HZ 7000 /* For mixer; DSS outputs a fixed rate */

#define PCS_9K_HZ 9321 /* Divider 128 -> sample >>= 1 */
#define PCS_18K_HZ 18643 /* Divider 64 -> sample >>= 2 */
#define PCS_37K_HZ 37286 /* Divider 32 -> sample >>= 3 */

struct audio_sfx_data; /* audio_struct.h */

extern const int int_lock_start;
Expand All @@ -81,6 +85,7 @@ extern uint16_t sfx_timer;
extern uint8_t sfx_playing;

/* Audio interrupt variables */
extern const int pcs_pwm_handler;
extern const int lpt_mono_handler;
extern const int lpt_stereo1_handler;
extern const int lpt_stereo2_handler;
Expand Down
Loading