Search code examples
assemblyaudiox86operating-system

Playing sound with the PC Speaker in x86 Assembly


So I'm trying to play a single tone using the pc speaker in x86 assembly, It play a sound, but when I try to turn it off again... the tone begins do what I can only describe as shaking.

Also I'm making this for a 16 bit OS if that means anything.

Here's my sound.asm file

; ------------------------------------------------------------------
; os_play_sound -- Play a single tone using the pc speaker
; IN: CX = tone, BX = duration

os_play_sound:
    mov     al, 182
    out     0x43, al
    mov     ax, cx

    out     0x42, al
    mov     al, ah
    out     0x42, al
    in      al, 0x61

    or      al, 00000011b
    out     0x61, al

    .pause1:
        mov cx, 65535

    .pause2:
        dec cx
        jne .pause2
        dec bx
        jne .pause1

        in  al, 0x61
        and al, 11111100b
        out 0x61, al

        ret

And here's the part in main.asm where I'm calling the sound.asm label from

mov cx, 9121
mov bx, 25
call os_play_sound

Solution

  • I'm kind of late to the party and probably you may have figured it out, but here's my answer for the future Stack Overflow lurking generations ;).

    You can test the following snippet (based on your code; slightly cleaned up) that will in fact issue the tone, but wait using INT15H function AX=86H. It's usually a better practice than busy wait, but slightly worse than reprogramming the PIT for your needs. As you're using MikeOS though as your main codebase, I wanted to keep the code as simple as possible. Comments are included for further understanding of the code.

    ; ---------------------------------------------
    ; Generate tone with frequency specified in AX.
    ; The tone will last for CX:DX microseconds.
    ; For instance, CX=000Fh, DX=4240h will play the
    ; specified tone for one second (CX:DX = 1000000).
    ; Registers preserved.
    
    tone:
        PUSHA               ; Prolog: Preserve all registers
        MOV BX, AX          ; 1) Preserve the note value by storing it in BX.
        MOV AL, 182         ; 2) Set up the write to the control word register.
        OUT 43h, AL         ; 2) Perform the write.
        MOV AX, BX          ; 2) Pull back the frequency from BX.
        OUT 42h, AL         ; 2) Send lower byte of the frequency.
        MOV AL, AH          ; 2) Load higher byte of the frequency.
        OUT 42h, AL         ; 2) Send the higher byte.
        IN AL, 61h          ; 3) Read the current keyboard controller status.
        OR AL, 03h          ; 3) Turn on 0 and 1 bit, enabling the PC speaker gate and the data transfer.
        OUT 61h, AL         ; 3) Save the new keyboard controller status.
        MOV AH, 86h         ; 4) Load the BIOS WAIT, int15h function AH=86h.
        INT 15h             ; 4) Immediately interrupt. The delay is already in CX:DX.
        IN AL, 61h          ; 5) Read the current keyboard controller status.
        AND AL, 0FCh        ; 5) Turn off 0 and 1 bit, simply disabling the gate.
        OUT 61h, AL         ; 5) Write the new keyboard controller status.
        POPA                ; Epilog: Pop off all the registers pushed
        RET                 ; Epilog: Return.