PythonからWindows APIを使ってサウンド再生

#はじめに
ctypesパッケージを使ってWindows APIを呼び出すサンプルとしてサウンドを再生してみた。

#ソース
##短縮版
###winsound版

```beep3.py
import struct
import winsound

data = b"\x90\x90\x70\x70" * 500 + b"\x90\x90\x90\x90\x70\x70\x70\x70" * 250

buf = struct.pack("<4sL4s" "4sLHHLLHH" "4sL",
    b"RIFF", 36 + len(data), b"WAVE",
    b"fmt ", 16, 1, 1, 8000, 8000, 1, 8,
    b"data", len(data)) + data
winsound.PlaySound(buf, winsound.SND_MEMORY)
```

###winmm.sndPlaySound版

```beep4.py
import struct
import ctypes
winmm = ctypes.windll.winmm

SND_SYNC = 0x0000
SND_MEMORY = 0x0004

data = b"\x90\x90\x70\x70" * 500 + b"\x90\x90\x90\x90\x70\x70\x70\x70" * 250

buf = struct.pack("<4sL4s" "4sLHHLLHH" "4sL",
    b"RIFF", 36 + len(data), b"WAVE",
    b"fmt ", 16, 1, 1, 8000, 8000, 1, 8,
    b"data", len(data)) + data
winmm.sndPlaySoundA(buf, SND_MEMORY | SND_SYNC)
```

##winmm方式

```beep.py
from ctypes import *
from ctypes.wintypes import *
import time

winmm = windll.winmm

WAVE_FORMAT_PCM = 1
WAVE_MAPPER = UINT(-1)
CALLBACK_NULL = 0x0000_0000

class WAVEFORMATEX(Structure):
    _fields_ = [
        ("wFormatTag", WORD),
        ("nChannels", WORD),
        ("nSamplesPerSec", DWORD),
        ("nAvgBytesPerSec", DWORD),
        ("nBlockAlign", WORD),
        ("wBitsPerSample", WORD),
        ("cbSize", WORD),
    ]

class WAVEHDR(Structure):
    _fields_ = [
        ("lpData", LPSTR),
        ("dwBufferLength", DWORD),
        ("dwBytesRecorded", DWORD),
        ("dwUser", PDWORD),
        ("dwFlags", DWORD),
        ("dwLoops", DWORD),
        ("lpNext", LPVOID),
        ("reserved", PDWORD),
    ]

def main():
    data = b"\x90\x90\x70\x70" * 500 + b"\x90\x90\x90\x90\x70\x70\x70\x70" * 250

    hwo = HANDLE()
    wfx = WAVEFORMATEX()
    wfx.wFormatTag = WAVE_FORMAT_PCM
    wfx.nChannels = 1
    wfx.nSamplesPerSec = 8000
    wfx.nAvgBytesPerSec = 8000
    wfx.nBlockAlign = 1
    wfx.wBitsPerSample = 8
    wfx.cbSize = 0
    winmm.waveOutOpen(byref(hwo), WAVE_MAPPER, wfx, 0, 0, CALLBACK_NULL)

    wh = WAVEHDR()
    wh.lpData = data
    wh.dwBufferLength = len(data)
    winmm.waveOutPrepareHeader(hwo, byref(wh), sizeof(wh))
    winmm.waveOutWrite(hwo, byref(wh), sizeof(wh))
    time.sleep(0.6)

    winmm.waveOutReset(hwo)
    winmm.waveOutUnprepareHeader(hwo, byref(wh), sizeof(wh))
    winmm.waveOutClose(hwo)

main()
```

##winsound方式

```beep2.py
import io
import winsound
from ctypes import LittleEndianStructure
from ctypes.wintypes import BYTE, WORD, DWORD
FOURCC = BYTE * 4

WAVE_FORMAT_PCM = 0x0001

class WAVEFORMATEX(LittleEndianStructure):
    _fields_ = [
        ("wFormatTag", WORD),
        ("nChannels", WORD),
        ("nSamplesPerSec", DWORD),
        ("nAvgBytesPerSec", DWORD),
        ("nBlockAlign", WORD),
        ("wBitsPerSample", WORD),
    ]

class WAVEFILEHEADER(LittleEndianStructure):
    _fields_ = [
        ("RIFF_ckid", FOURCC),
        ("RIFF_cksize", DWORD),
        ("fccType", FOURCC),
        ("fmt_ckid", FOURCC),
        ("fmt_cksize", DWORD),
        ("wfx", WAVEFORMATEX),
        ("data_ckid", FOURCC),
        ("data_cksize", DWORD),
    ]

def main():
    data = b"\x90\x90\x70\x70" * 500 + b"\x90\x90\x90\x90\x70\x70\x70\x70" * 250
    data_len = len(data)

    wfx = WAVEFORMATEX()
    wfx.wFormatTag = WAVE_FORMAT_PCM
    wfx.nChannels = 1
    wfx.nSamplesPerSec = 8000
    wfx.nAvgBytesPerSec = 8000
    wfx.nBlockAlign = 1
    wfx.wBitsPerSample = 8

    wfh = WAVEFILEHEADER()
    wfh.RIFF_ckid = FOURCC(*b"RIFF")
    wfh.RIFF_cksize = 36 + data_len
    wfh.fccType = FOURCC(*b"WAVE")
    wfh.fmt_ckid = FOURCC(*b"fmt ")
    wfh.fmt_cksize = 16
    wfh.wfx = wfx
    wfh.data_ckid = FOURCC(*b"data")
    wfh.data_cksize = data_len

    buf = io.BytesIO()
    buf.write(wfh)
    buf.write(data)

    winsound.PlaySound(buf.getvalue(), winsound.SND_MEMORY)

main()
```

#おまけ

```coin.py
import struct
import winsound

SAMPLE_RATE = 48000
theta = 0

def main():
    data = bytearray()
    tone(data, 83, .1)
    tone(data, 88, 1)

    wav = struct.pack("<4sL4s" "4sLHHLLHH" "4sL",
        b"RIFF", 36 + len(data), b"WAVE",
        b"fmt ", 16, 1, 1, SAMPLE_RATE, SAMPLE_RATE, 1, 8,
        b"data", len(data)) + data
    winsound.PlaySound(wav, winsound.SND_MEMORY)

def tone(data, note, sec):
    global theta
    freq = 440 * 2**((note - 69) / 12)
    len = int(SAMPLE_RATE * sec)
    for i in range(len):
        h = int(16 * (1 - i / SAMPLE_RATE))
        t = theta / SAMPLE_RATE
        d = 0x80 + (h if t < .5 else -h)
        data.append(d)
        theta = (theta + freq) % SAMPLE_RATE

main()
```