Pygame で MIDI

してみるテスト。

examples にある midi.py が複雑すぎる上に動作が怪しい*1ので似たようなのを自分で 1から作ってみました。

miditest.py

(Python 3.1 & Pygame 1.9)

'''MIDI keyboard.

マウス左ボタンで発音、右ボタンで完全5度を同時に発音。
スペースバーを押しながらだとサステイン。
鍵盤の下の方を押すほど大音量。
'''
import pygame
import pygame.midi
from pygame.locals import *

INSTRUMENT = 0  # 楽器の種類 (0-127)
                #   0:Piano, 19:Organ, 56:Trumpet, 91:Voice 等
KEY_WIDTH = 20  # 鍵盤の幅
WIDTH, HEIGHT = 800, 128  # 画面の大きさ

FPS = 60
NOTE_CENTER = 60  # 中央の音。C(ド)の音を指定
COLOR = 0, 255, 200  # 色
WHITE_COLOR = 255, 255, 255  # 白鍵の色
BLACK_COLOR = 0, 0, 50  # 黒鍵の色
BG_COLOR = 100, 0, 50  # 背景色

KEY_COLOR = 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0  # 0=白鍵, 1=黒鍵
NOTE_NAME = ('C', 'C#', 'D', 'D#', 'E',
             'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')

def main():
    pygame.init()
    pygame.midi.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    midiout = pygame.midi.Output(pygame.midi.get_default_output_id())
    midiout.set_instrument(INSTRUMENT)
    clock = pygame.time.Clock()
    clock.tick(FPS)
    keys = WIDTH // KEY_WIDTH + 1
    keylist = [False] * (keys + 7)
    note_start = NOTE_CENTER - keys // 2
    note_no = None
    vel = 0
    sustain = False
    while True:
        for e in pygame.event.get():
            if e.type is QUIT:
                return
            elif e.type is KEYDOWN and e.key is K_ESCAPE:
                return
            elif e.type is KEYDOWN and e.key is K_SPACE:
                sustain = True
            elif e.type is KEYUP and e.key is K_SPACE:
                sustain = False
                note_no = None
                for key, b in enumerate(keylist):
                    if b:
                        midiout.note_off(note_start + key, 0)
                        keylist[key] = False
            elif e.type is MOUSEBUTTONDOWN and (
                e.button == 1 or e.button == 3):
                x, y = e.pos
                vel = 128 * y // HEIGHT
                key = x // KEY_WIDTH
                keylist[key] = True
                note_no = note_start + key
                midiout.note_on(note_no, vel)
                if e.button == 3:
                    keylist[key + 7] = True
                    midiout.note_on(note_no + 7, vel)
            elif e.type is MOUSEBUTTONUP and (
                e.button == 1 or e.button == 3):
                if not sustain:
                    note_no = None
                    for key, b in enumerate(keylist):
                        if b:
                            midiout.note_off(note_start + key, 0)
                            keylist[key] = False
        screen.fill(BG_COLOR)
        for key in range(keys):
            x = key * KEY_WIDTH
            pygame.draw.rect(
                screen,
                (WHITE_COLOR, BLACK_COLOR)[
                    KEY_COLOR[(note_start + key) % 12]],
                (x + 1 , 0, KEY_WIDTH - 2, HEIGHT))
            if keylist[key]:
                pygame.draw.rect(
                    screen, COLOR, (x, 0, KEY_WIDTH, HEIGHT), 3)
        clock.tick(FPS)
        pygame.display.flip()
        notes = []
        for key, b in enumerate(keylist):
            if b:
                nn = note_start + key
                notes.append('{0}:{1}'.format(NOTE_NAME[nn % 12], nn))
        pygame.display.set_caption(', '.join(notes))
        

if  __name__ == '__main__':
    try:
        main()
    finally:
        pygame.quit()

# Public Domain. 好きに流用してください。

*1:マウスボタン同時押しすると音が止まらなくなる