Ejemplo n.º 1
0
unsigned long long dosamp_FAR dosamp_time_source_8254_poll(dosamp_time_source_t inst) {
    if (inst->open_flags == 0) return 0UL;

    if (ts_8254_pper == t8254_counter[T8254_TIMER_INTERRUPT_TICK]) {
        uint16_t ncnt = read_8254(T8254_TIMER_INTERRUPT_TICK);
        uint16_t delta;

        /* NTS: remember the timer chip counts DOWN to zero.
         *      so normally, ncnt < ts_8254_pcnt unless 16-bit rollover happened. */
        if (ncnt <= ts_8254_pcnt)
            delta = ts_8254_pcnt - ncnt;
        else
            delta = ts_8254_pcnt + (uint16_t)ts_8254_pper - ncnt;

        /* count */
        inst->counter += (unsigned long long)delta;

        /* store the prev counter */
        ts_8254_pcnt = ncnt;
    }
    else {
        ts_8254_pcnt = read_8254(T8254_TIMER_INTERRUPT_TICK);
        ts_8254_pper = t8254_counter[T8254_TIMER_INTERRUPT_TICK];
    }

    return inst->counter;
}
Ejemplo n.º 2
0
int dosamp_FAR dosamp_time_source_8254_open(dosamp_time_source_t inst) {
    inst->open_flags = (unsigned int)(-1);
    inst->clock_rate = T8254_REF_CLOCK_HZ;
    ts_8254_pcnt = read_8254(T8254_TIMER_INTERRUPT_TICK);
    ts_8254_pper = t8254_counter[T8254_TIMER_INTERRUPT_TICK];
#if defined(TARGET_PC98)
    assert(inst->clock_rate != 0ul); // blow up here if someone forgot to probe the 8254
#endif
    return 0;
}
Ejemplo n.º 3
0
void play_sfx(const pcspkt_ent_list *pl) {
    struct pcspk_ent *p;
    t8254_time_t pc,cc;
    t8254_time_t delta;
    int32_t tim = 0;

    p = *(pl++);
    cc = read_8254(0);
    while (p != NULL) {
        while (p->delay != 0) {
            if (p->freq != 0) {
#ifdef TARGET_PC98 /* Double the counter to convert IBM PC timer delay (1193182Hz) to PC-98 timer delay (2457600Hz) */
                write_8254_pc_speaker(p->freq << 9u);
#else
                write_8254_pc_speaker(p->freq << 8u);
#endif
                t8254_pc_speaker_set_gate(PC_SPEAKER_GATE_ON);
            }
            else {
                t8254_pc_speaker_set_gate(PC_SPEAKER_GATE_OFF);
            }

#ifdef TARGET_PC98 /* Double the delay to convert IBM PC timer delay (1193182Hz) to PC-98 timer delay (2457600Hz) */
            tim += (int32_t)((unsigned long)p->delay << 9u);
#else
            tim += (int32_t)((unsigned int)p->delay << 8u);
#endif
            while (tim >= 0l) {
                pc = cc;
                cc = read_8254(0);
                delta = (pc - cc) & 0xFFFFul; /* counts DOWN */
                tim -= delta;
            }

            p++;
        }

        p = *(pl++);
    }

    t8254_pc_speaker_set_gate(PC_SPEAKER_GATE_OFF);
}
Ejemplo n.º 4
0
void t8254_wait(unsigned long ticks) {
	t8254_time_t pr,cr;
	uint16_t dec;

	if (ticks <= 1) return;
	ticks--;

	cr = read_8254(0);
	do {
		/* NTS: remember the 8254 counts downward, not upward */
		pr = cr;
		cr = read_8254(0);
		if (cr > pr) /* when counter reaches zero it resets to the original counter value and begins counting again */
			dec = (pr + (uint16_t)t8254_counter[0] - cr);
		else
			dec = (pr - cr);

		ticks -= dec;
	} while ((signed long)ticks >= 0L);
}
Ejemplo n.º 5
0
void direct_dac_test(void) {
    unsigned long time,bytes;
    unsigned char FAR *ptr;
    unsigned int i,count;
    unsigned int pc,c;

    doubleprintf("Direct DAC playback test.\n");

    /* FIXME: Why is the final rate SLOWER in DOSBox-X in 386 protected mode? */

    sndsb_reset_dsp(sb_card);
    sndsb_write_dsp(sb_card,0xD1); /* speaker on */

    _cli();
    time = 0;
    bytes = 0;
    c = read_8254(T8254_TIMER_INTERRUPT_TICK);
    for (count=0;count < 3;count++) {
        ptr = sb_dma->lin;
        for (i=0;i < sb_dma->length;i++) {
            sndsb_write_dsp(sb_card,SNDSB_DSPCMD_DIRECT_DAC_OUT); /* 0x10 */
            sndsb_write_dsp(sb_card,*ptr++);

            pc = c;
            c = read_8254(T8254_TIMER_INTERRUPT_TICK);
            time += (unsigned long)((pc - c) & 0xFFFFU); /* remember: it counts DOWN. assumes full 16-bit count */
        }
        bytes += sb_dma->length;
    }
    _sti();

    if (time == 0UL) time = 1;

    {
        double t = (double)time / T8254_REF_CLOCK_HZ;
        double rate = (double)bytes / t;

        doubleprintf(" - %lu bytes played in %.3f seconds\n",(unsigned long)bytes,t);
        doubleprintf(" - Sample rate is %.3fHz\n",rate);
    }
}
Ejemplo n.º 6
0
void sb1_sc_play_adpcm2_test(void) {
    unsigned long time,bytes,expect,tlen,timeout;
    unsigned int count;
    unsigned int pc,c;
    unsigned long d;
    uint32_t irqc;

    doubleprintf("SB 1.x ADPCM 2-bit single cycle DSP playback test.\n");

    timeout = T8254_REF_CLOCK_HZ * 4UL;

    for (count=0;count < 256;count++) {
        expect = 1000000UL / (unsigned long)(256 - count);

        _cli();
        if (sb_card->irq >= 8) {
            p8259_OCW2(8,P8259_OCW2_SPECIFIC_EOI | (sb_card->irq & 7));
            p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | 2);
        }
        else if (sb_card->irq >= 0) {
            p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | sb_card->irq);
        }
        _sti();

        tlen = (expect / 4UL) + 1; // 1 sec
        if (tlen > sb_card->buffer_size) tlen = sb_card->buffer_size;

        sb_card->buffer_dma_started_length = tlen;
        sb_card->buffer_dma_started = 0;

        sndsb_reset_dsp(sb_card);
        sndsb_write_dsp(sb_card,0xD1); /* speaker on */
        sndsb_setup_dma(sb_card);
        irqc = sb_card->irq_counter;

        sndsb_write_dsp_timeconst(sb_card,count);

        _cli();
        c = read_8254(T8254_TIMER_INTERRUPT_TICK);
        bytes = tlen;
        time = 0;
        _sti();

        {
            unsigned int lv = (unsigned int)(tlen - 1UL);

            sndsb_write_dsp(sb_card,SNDSB_DSPCMD_DMA_DAC_OUT_ADPCM_2BIT_REF); /* 0x17 */
            sndsb_write_dsp(sb_card,lv);
            sndsb_write_dsp(sb_card,lv >> 8);
        }

        while (1) {
            if (irqc != sb_card->irq_counter)
                break;

            _cli();
            pc = c;
            c = read_8254(T8254_TIMER_INTERRUPT_TICK);
            time += (unsigned long)((pc - c) & 0xFFFFU); /* remember: it counts DOWN. assumes full 16-bit count */
            _sti();

            if (time >= timeout) goto x_timeout;
        }

x_complete:
        if (time == 0UL) time = 1;

        {
            double t = (double)time / T8254_REF_CLOCK_HZ;
            double rate = (double)(((bytes - 1UL) * 4UL) + 1UL) / t; /* 4 samples/byte + 1 reference */

            doubleprintf(" - TC 0x%02X: expecting %luHz, %lub/%.3fs @ %.3fHz\n",count,expect,(unsigned long)bytes,t,rate);
        }

        if (kbhit()) {
            if (getch() == 27)
                break;
        }

        continue;
x_timeout:
        d = d8237_read_count(sb_card->dma8); /* counts DOWNWARD */
        if (d > tlen) d = 0; /* terminal count */
        d = tlen - d;

        if (irqc == sb_card->irq_counter && d == 0) bytes = 0; /* nothing happened if no IRQ and counter never changed */
        else if (bytes > d) bytes = d;
        goto x_complete;
    }

    _cli();
    if (sb_card->irq >= 8) {
        p8259_OCW2(8,P8259_OCW2_SPECIFIC_EOI | (sb_card->irq & 7));
        p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | 2);
    }
    else if (sb_card->irq >= 0) {
        p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | sb_card->irq);
    }
    _sti();

    sndsb_write_dsp_timeconst(sb_card,0x83); /* 8000Hz */

    sndsb_reset_dsp(sb_card);
}
Ejemplo n.º 7
0
void sb16_sc_play_test(void) {
    unsigned char bytespersample = wav_16bit ? 2 : 1;
    unsigned long time,bytes,expect,tlen,timeout;
    unsigned long count;
    unsigned int pc,c;
    unsigned long d;
    uint32_t irqc;
    int dma;

    if (wav_16bit && sb_card->dma16 >= 4)
        dma = sb_card->dma16;
    else
        dma = sb_card->dma8;

    if (sb_card->dsp_vmaj >= 4) /* Sound Blaster 16 */
        { }
    else if (sb_card->is_gallant_sc6600) /* Reveal SC-4000 / Gallant SC-6600 */
        { }
    else 
        return;

    doubleprintf("SB16 4.x single cycle DSP playback test (%u bit).\n",wav_16bit?16:8);

    timeout = T8254_REF_CLOCK_HZ * 2UL;

    count = 0;
    do {
        expect = count;

        _cli();
        if (sb_card->irq >= 8) {
            p8259_OCW2(8,P8259_OCW2_SPECIFIC_EOI | (sb_card->irq & 7));
            p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | 2);
        }
        else if (sb_card->irq >= 0) {
            p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | sb_card->irq);
        }
        _sti();

        tlen = expect; // 1 sec
        if (tlen < 4000UL) tlen = 4000UL;
        if (tlen > (sb_card->buffer_size / (unsigned long)bytespersample)) tlen = sb_card->buffer_size / (unsigned long)bytespersample;

        sb_card->buffer_dma_started_length = tlen * (unsigned long)bytespersample;
        sb_card->buffer_dma_started = 0;

        sndsb_reset_dsp(sb_card);
        sndsb_write_dsp(sb_card,0xD1); /* speaker on */
        sndsb_setup_dma(sb_card);
        irqc = sb_card->irq_counter;

        sndsb_write_dsp_outrate(sb_card,count);

        _cli();
        c = read_8254(T8254_TIMER_INTERRUPT_TICK);
        bytes = tlen;
        time = 0;
        _sti();

        {
            unsigned int lv = (unsigned int)(tlen - 1UL);

            /* NTS: Reveal SC-4000 (Gallant 6600) cards DO support SB16 but only specific commands.
             *      Command 0xC0 is not recognized, but command 0xC6 works. */
            if (wav_16bit) {
                if (sb_card->is_gallant_sc6600)
                    sndsb_write_dsp(sb_card,SNDSB_DSPCMD_SB16_AUTOINIT_DMA_DAC_OUT_16BIT); /* 0xB6 */
                else
                    sndsb_write_dsp(sb_card,SNDSB_DSPCMD_SB16_DMA_DAC_OUT_16BIT); /* 0xB0 */

                sndsb_write_dsp(sb_card,0x10); /* mode (16-bit signed PCM) */
            }
            else {
                if (sb_card->is_gallant_sc6600)
                    sndsb_write_dsp(sb_card,SNDSB_DSPCMD_SB16_AUTOINIT_DMA_DAC_OUT_8BIT); /* 0xC6 */
                else
                    sndsb_write_dsp(sb_card,SNDSB_DSPCMD_SB16_DMA_DAC_OUT_8BIT); /* 0xC0 */

                sndsb_write_dsp(sb_card,0x00); /* mode (8-bit unsigned PCM) */
            }

            sndsb_write_dsp(sb_card,lv);
            sndsb_write_dsp(sb_card,lv >> 8);
        }

        while (1) {
            if (irqc != sb_card->irq_counter)
                break;

            _cli();
            pc = c;
            c = read_8254(T8254_TIMER_INTERRUPT_TICK);
            time += (unsigned long)((pc - c) & 0xFFFFU); /* remember: it counts DOWN. assumes full 16-bit count */
            _sti();

            if (time >= timeout) goto x_timeout;
        }

x_complete:
        if (time == 0UL) time = 1;

        {
            double t = (double)time / T8254_REF_CLOCK_HZ;
            double rate = (double)bytes / t;

            doubleprintf(" - Rate 0x%04lX: expecting %luHz, %lu%c/%.3fs @ %.3fHz\n",count,expect,(unsigned long)bytes,wav_16bit?'w':'b',t,rate);
        }

        if (kbhit()) {
            if (getch() == 27)
                break;
        }

        count += 0x80; /* count by 128 because enumerating all would take too long */
        if (count > 0xFFFFUL) break;

        continue;
x_timeout:
        d = d8237_read_count(dma) / (unsigned long)bytespersample; /* counts DOWNWARD */
        if (d > tlen) d = 0; /* terminal count */
        d = tlen - d;

        if (irqc == sb_card->irq_counter && d == 0) bytes = 0; /* nothing happened if no IRQ and counter never changed */
        else if (bytes > d) bytes = d;
        goto x_complete;
    } while (1);

    _cli();
    if (sb_card->irq >= 8) {
        p8259_OCW2(8,P8259_OCW2_SPECIFIC_EOI | (sb_card->irq & 7));
        p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | 2);
    }
    else if (sb_card->irq >= 0) {
        p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | sb_card->irq);
    }
    _sti();

    sndsb_reset_dsp(sb_card);

    sndsb_write_dsp_outrate(sb_card,8000U);
}
Ejemplo n.º 8
0
void ess_sc_play_test(void) {
    unsigned char bytespersample = wav_16bit ? 2 : 1;
    unsigned long time,bytes,expect,tlen,timeout;
    unsigned long count;
    unsigned int pc,c;
    unsigned long d;
    uint32_t irqc;

    if (!sb_card->ess_extensions || sb_card->ess_chipset == 0)
        return;

    doubleprintf("ESS688 single cycle DSP playback test (%u bit).\n",wav_16bit?16:8);

    timeout = T8254_REF_CLOCK_HZ * 2UL;

    for (count=0;count < 256;count++) {
        if (count >= 128)
            expect = 795500UL / (256 - count);
        else
            expect = 397700UL / (128 - count);

        _cli();
        if (sb_card->irq >= 8) {
            p8259_OCW2(8,P8259_OCW2_SPECIFIC_EOI | (sb_card->irq & 7));
            p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | 2);
        }
        else if (sb_card->irq >= 0) {
            p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | sb_card->irq);
        }
        _sti();

        tlen = expect; // 1 sec
        if (tlen > (sb_card->buffer_size / (unsigned long)bytespersample)) tlen = sb_card->buffer_size / (unsigned long)bytespersample;

        /* NTS: We ask the card to use demand ISA, 4 bytes at a time.
         *      Behavior observed on real hardware, is that if you set up
         *      DMA this way and then set up a non-auto-init DMA transfer
         *      that isn't a multiple of 4, the DMA will stop short of
         *      the full transfer and the IRQ will never fire. Therefore
         *      the transfer length must be a multiple of 4! */
        tlen -= tlen & 3;
        if (tlen == 0) tlen = 4;

        sb_card->buffer_dma_started_length = tlen * (unsigned long)bytespersample;
        sb_card->buffer_dma_started = 0;

        sndsb_reset_dsp(sb_card);
        sndsb_write_dsp(sb_card,0xD1); /* speaker on */
        sndsb_ess_set_extended_mode(sb_card,1/*enable*/);
        sndsb_setup_dma(sb_card);
        irqc = sb_card->irq_counter;

        {
            /* ESS 688/1869 chipset specific DSP playback.
               using this mode bypasses a lot of the Sound Blaster Pro emulation
               and restrictions and allows us to run up to 48KHz 16-bit stereo */
            unsigned short t16;
            int b;

            _cli();

            /* clear IRQ */
            sndsb_interrupt_ack(sb_card,3);

            b = 0x00; /* DMA disable */
            b |= 0x00; /* no auto-init */
            b |= 0x00; /* [3]=DMA converter in ADC mode [1]=DMA read for ADC playback mode */
            sndsb_ess_write_controller(sb_card,0xB8,b);

            b = sndsb_ess_read_controller(sb_card,0xA8);
            b &= ~0xB; /* clear mono/stereo and record monitor (bits 3, 1, and 0) */
            b |= 2;	/* mono 10=mono 01=stereo */
            sndsb_ess_write_controller(sb_card,0xA8,b);

            /* NTS: The meaning of bits 1:0 in register 0xB9
             *
             *      00 single DMA transfer mode
             *      01 demand DMA transfer mode, 2 bytes/request
             *      10 demand DMA transfer mode, 4 bytes/request
             *      11 reserved
             *
             * NOTES on what happens if you set bits 1:0 (DMA transfer type) to the "reserved" 11 value:
             *
             *      ESS 688 (Sharp laptop)          Nothing, apparently. Treated the same as 4 bytes/request
             *
             *      ESS 1887 (Compaq Presario)      Triggers a hardware bug where the chip appears to fetch
             *                                      3 bytes per demand transfer but then only handle 1 byte,
             *                                      which translates to audio playing at 3x the sample rate
             *                                      it should be. NOT because the DAC is running any faster,
             *                                      but because the chip is only playing back every 3rd sample!
             *                                      This play only 3rds behavior is consistent across 8/16-bit
             *                                      PCM and mono/stereo.
             */

            b = 2;  /* demand transfer DMA 4 bytes per request */
            sndsb_ess_write_controller(sb_card,0xB9,b);

            sndsb_ess_write_controller(sb_card,0xA1,count);

            /* effectively disable the lowpass filter (NTS: 0xFF mutes the audio, apparently) */
            sndsb_ess_write_controller(sb_card,0xA2,0xFE);

            t16 = -(tlen * (uint16_t)bytespersample); /* DMA transfer count reload register value is 2's complement of length */
            sndsb_ess_write_controller(sb_card,0xA4,t16); /* DMA transfer count low */
            sndsb_ess_write_controller(sb_card,0xA5,t16>>8); /* DMA transfer count high */

            b = sndsb_ess_read_controller(sb_card,0xB1);
            b &= ~0xA0; /* clear compat game IRQ, fifo half-empty IRQs */
            b |= 0x50; /* set overflow IRQ, and "no function" */
            sndsb_ess_write_controller(sb_card,0xB1,b);

            b = sndsb_ess_read_controller(sb_card,0xB2);
            b &= ~0xA0; /* clear compat */
            b |= 0x50; /* set DRQ/DACKB inputs for DMA */
            sndsb_ess_write_controller(sb_card,0xB2,b);

            b = 0x51; /* enable FIFO+DMA, reserved, load signal */
	        b |= wav_16bit ? 0x20 : 0x00; /* signed complement mode or not */
            sndsb_ess_write_controller(sb_card,0xB7,b);

            b = 0x90; /* enable FIFO+DMA, reserved, load signal */
	        b |= wav_16bit ? 0x20 : 0x00; /* signed complement mode or not */
            b |= 0x40; /* [3]=stereo [6]=!stereo */
            b |= wav_16bit ? 0x04 : 0x00; /* [2]=16bit */
            sndsb_ess_write_controller(sb_card,0xB7,b);

            b = sndsb_ess_read_controller(sb_card,0xB8);
            sndsb_ess_write_controller(sb_card,0xB8,b | 1);
        }

        _cli();
        c = read_8254(T8254_TIMER_INTERRUPT_TICK);
        bytes = tlen;
        time = 0;
        _sti();

        while (1) {
            if (irqc != sb_card->irq_counter)
                break;

            _cli();
            pc = c;
            c = read_8254(T8254_TIMER_INTERRUPT_TICK);
            time += (unsigned long)((pc - c) & 0xFFFFU); /* remember: it counts DOWN. assumes full 16-bit count */
            _sti();

            if (time >= timeout) goto x_timeout;
        }

x_complete:
        if (time == 0UL) time = 1;

        {
            int b;

            b = sndsb_ess_read_controller(sb_card,0xB8);
            if (b != -1) {
                b &= ~0x01; /* stop DMA */
                sndsb_ess_write_controller(sb_card,0xB8,b);
            }
        }

        {
            double t = (double)time / T8254_REF_CLOCK_HZ;
            double rate = (double)bytes / t;

            doubleprintf(" - TC 0x%02lX: expecting %luHz, %lu%c/%.3fs @ %.3fHz\n",count,expect,(unsigned long)bytes,wav_16bit?'w':'b',t,rate);
        }

        if (kbhit()) {
            if (getch() == 27)
                break;
        }

        continue;
x_timeout:
        d = d8237_read_count(sb_card->dma8) / (unsigned long)bytespersample; /* counts DOWNWARD */
        if (d > tlen) d = 0; /* terminal count */
        d = tlen - d;

        if (irqc == sb_card->irq_counter && d == 0) bytes = 0; /* nothing happened if no IRQ and counter never changed */
        else if (bytes > d) bytes = d;
        goto x_complete;
    }

    _cli();
    if (sb_card->irq >= 8) {
        p8259_OCW2(8,P8259_OCW2_SPECIFIC_EOI | (sb_card->irq & 7));
        p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | 2);
    }
    else if (sb_card->irq >= 0) {
        p8259_OCW2(0,P8259_OCW2_SPECIFIC_EOI | sb_card->irq);
    }
    _sti();

    sndsb_reset_dsp(sb_card);
}
Ejemplo n.º 9
0
int main() {
	struct t8254_readback_t readback;
	t8254_time_t tick[3];
	unsigned int i;

	printf("8254 library test program\n");
	if (!probe_8254()) {
		printf("Chip not present. Your computer might be 2010-era hardware that dropped support for it.\n");
		return 1;
	}
	if (!probe_8259()) {
		printf("8259 interrupt controller not present. Your computer might be 2010-era hardware that dropped support for it.\n");
		return 1;
	}

	printf("8254 base clock: %luHz\n",T8254_REF_CLOCK_HZ);

	speaker_rate = T8254_REF_CLOCK_HZ / 400UL;	/* 400Hz */

	prev_irq0 = _dos_getvect(T8254_IRQ+0x08);
	_dos_setvect(T8254_IRQ+0x8,irq0);

	_cli();
	write_8254_pc_speaker(speaker_rate);
	write_8254_system_timer(max);
	_sti();

#ifdef TARGET_PC98
	/* PC-98 does not have IRQ0 running by default */
	p8259_unmask(T8254_IRQ);
#endif

	while (1) {
		if (kbhit()) {
			int c = getch();
			if (c == 27)
				break;
			else if (c == '-') {
				max -= 80;
				if (max > (0xFFFF-80))
					max = 0xFFFF;

				_cli();
				write_8254_system_timer(max);
				_sti();
			}
			else if (c == '=') {
				max += 110;
				if (max < 110 || max > (0xFFFF-110))
					max = 0xFFFF;
	
				_cli();
				write_8254_system_timer(max);
				_sti();
			}
			/* play with timer 2 and the PC speaker gate */
			else if (c == 'p') {
				unsigned char on = (t8254_pc_speaker_read_gate() != 0) ? 1 : 0;
				if (on) t8254_pc_speaker_set_gate(0);
				else t8254_pc_speaker_set_gate(3);
			}
			else if (c == '1') {
#ifndef TARGET_PC98
				unsigned char v = t8254_pc_speaker_read_gate();
				t8254_pc_speaker_set_gate(v ^ PC_SPEAKER_OUTPUT_TTL_AND_GATE);
#endif
			}
			else if (c == '2') {
#ifndef TARGET_PC98
				unsigned char v = t8254_pc_speaker_read_gate();
				t8254_pc_speaker_set_gate(v ^ PC_SPEAKER_COUNTER_2_GATE);
#endif
			}
			else if (c == '[') {
				speaker_rate += 110;
				if (speaker_rate > (0xFFFF-110) || speaker_rate < 110)
					speaker_rate = 0xFFFF;

				write_8254_pc_speaker(speaker_rate);
			}
			else if (c == ']') {
				speaker_rate -= 110;
				if (speaker_rate > (0xFFFF-110))
					speaker_rate = 0;

				write_8254_pc_speaker(speaker_rate);
			}
			else if (c == 'w') {
				printf("\n");
				pulse_width_test();
				_cli();
				write_8254_system_timer(max);
				_sti();
				printf("\n");
			}
			else if (c == 'z') {
				/* sleep-wait loop test */
				unsigned long delay_ticks;
				unsigned long z;
				unsigned int c,cmax;

				printf("\nDelay interval in us? ");
				z = 1000000; scanf("%lu",&z);

				delay_ticks = t8254_us2ticks(z);
				printf("  %lu = %lu ticks\n",z,delay_ticks);

				if (delay_ticks == 0UL)	cmax = (unsigned int)(T8254_REF_CLOCK_HZ / 20UL);
				else			cmax = (unsigned int)(T8254_REF_CLOCK_HZ / 20UL / (unsigned long)delay_ticks);
				if (cmax == 0) cmax = 1;

				write_8254_pc_speaker(T8254_REF_CLOCK_HZ / 400UL); /* tick as fast as possible */
				while (1) {
					if (kbhit()) {
						if (getch() == 27) break;
					}

					for (c=0;c < cmax;c++) {
						t8254_pc_speaker_set_gate(3);
						t8254_wait(delay_ticks);
						t8254_pc_speaker_set_gate(0);
						t8254_wait(delay_ticks);
					}
				}
			}
			else if (c == 'd') {
				printf("\n                                    \nDetail mode, hit 'd' again to exit: [WARNING: 8254 only]\n");
				while (1) {
					if (kbhit()) {
						int c = getch();
						if (c == 'd') {
							break;
						}
					}

					_cli();
					readback_8254(T8254_READBACK_ALL,&readback);
					_sti();
					printf("\x0D");
					for (i=0;i <= 2;i++) {
						printf("[%u] stat=%02x count=%04x  ",i,
							readback.timer[i].status,
							readback.timer[i].count);
					}
					fflush(stdout);
				}
				printf("\n");
			}
		}

		for (i=0;i <= 2;i++) tick[i] = read_8254(i);

		/* BUG: DOSBox/DOSBox-X appear to have a bug where the PC speaker readback toggles
		 *      regardless of the GATE input to Counter 2. Bring GATE low (setting bit 1
		 *      of port 61h to 0) is supposed to cause Counter 2 to stop. The AND gate
		 *      after the output (bit 0 of port 61h) is not supposed to affect the readback. */
		printf("\x0D %04x %04x %04x max=%04x count=%04x SPKR=%u",tick[0],tick[1],tick[2],
			max,counter,read_8254_pc_speaker_output()!=0?1:0);
		fflush(stdout);
	}
	printf("\n");

#ifdef TARGET_PC98
	/* PC-98 does not have IRQ0 running by default */
	p8259_mask(T8254_IRQ);
#endif

	_cli();
	write_8254_pc_speaker(0);
	t8254_pc_speaker_set_gate(0);
	_dos_setvect(T8254_IRQ+0x8,prev_irq0);
	_sti();

	write_8254_system_timer(0xFFFF); /* restore normal function to prevent BIOS from going crazy */
	return 0;
}