drum39.py - ポケット・ミクでドラム #大人の科学ポケミク

inst39.py - ポケット・ミクで楽器音を鳴らす。 #大人の科学ポケミク - つちのこ、のこのこ。(はてな番外地)」のドラム版。

Python 2.7.6 + Pygame 1.9.1 用

u'''drum39.py - ポケット・ミクでドラム

スタイラスでツンツンすると打楽器鳴るよ。
「A I U E」ボタンで楽器の割り当て。
「O」はシンバル、「SHIFT」はスネア、「VIBRATO」はバスドラで固定。
'''
import pygame
import pygame.midi
from pygame.locals import *

WIDTH = 600  # 画面の大きさ
HEIGHT = WIDTH // 4
COLOR = 0, 255, 200  # 文字色
BG_COLOR = 100, 0, 50  # 背景色
FONT_SIZE = HEIGHT // 2
FPS = 60

VELOCITY = 64  # ペロシティの基準

BUTTON_O_NOTE = 49  # Crash Cymbal 1
BUTTON_VIBRATO_NOTE = 35  # Bass Drum M
BUTTOU_SHIFT_NOTE = 38  # Snare M

# ボタンで設定される音域
BUTTON_A_NOTE_SHIFT = 24
BUTTON_I_NOTE_SHIFT = BUTTON_A_NOTE_SHIFT - 24
BUTTON_U_NOTE_SHIFT = BUTTON_I_NOTE_SHIFT - 24
BUTTON_E_NOTE_SHIFT = BUTTON_U_NOTE_SHIFT - 24

# ポケミクのボタン
BUTTON_A = 0x01
BUTTON_I = 0x02
BUTTON_U = 0x04
BUTTON_E = 0x08
BUTTON_O = 0x10
BUTTON_VIBRATO = 0x20
BUTTOU_SHIFT = 0x40

POKEMIKU_NOTE = 78  # ポケミクから送出されるMIDIノートナンバー
PORT_SHIFT = -16  # タポルタメント値との差分(data3 / 4 の値に対して)

GM_DRUM = { # XG相当
    13: 'Surdo Mute',
    14: 'Surdo Open',
    15: 'Hi Q',
    16: 'Whip Slap',
    17: 'Scratch Push',
    18: 'Scratch Pull',
    19: 'Finger Snap',
    20: 'Click Noise',
    21: 'Metronome Click',
    22: 'Metronome Bell',
    23: 'Seq Click L',
    24: 'Seq Click H',
    25: 'Brush Tap',
    26: 'Brush Swirl L',
    27: 'Brush Slap',
    28: 'Brush Swirl H',
    29: 'Snare Roll',
    30: 'Castanet',
    31: 'Snare L',
    32: 'Sticks',
    33: 'Bass Drum L',
    34: 'Open Rim Shot',
    35: 'Bass Drum M',
    36: 'Bass Drum H',
    37: 'Side Stick',
    38: 'Snare M',
    39: 'Hand Clap',
    40: 'Snare H',
    41: 'Floor Tom L',
    42: 'Hi-Hat Closed',
    43: 'Floor Tom H',
    44: 'Hi-Hat Pedal',
    45: 'Low Tom',
    46: 'Hi-Hat Open',
    47: 'Mid Tom L',
    48: 'Mid Tom H',
    49: 'Crash Cymbal 1',
    50: 'High Tom',
    51: 'Ride Cymbal 1',
    52: 'Chinese Cymbal',
    53: 'Ride Cymbal Cup',
    54: 'Tambourine',
    55: 'Splash Cymbal',
    56: 'Cowbell',
    57: 'Crash Cymbal 2',
    58: 'Vibraslap',
    59: 'Ride Cymbal 2',
    60: 'Bongo H',
    61: 'Bongo L',
    62: 'Conga H Mute',
    63: 'Conga H Open',
    64: 'Conga L',
    65: 'Timbale H',
    66: 'Timbale L',
    67: 'Agogo H',
    68: 'Agogo L',
    69: 'Cabasa',
    70: 'Maracas',
    71: 'Samba Whistle H',
    72: 'Samba Whistle L',
    73: 'Guiro Short',
    74: 'Guiro Long',
    75: 'Claves',
    76: 'Wood Block H',
    77: 'Wood Block L',
    78: 'Cuica Mute',
    79: 'Cuica Open',
    80: 'Triangle Mute',
    81: 'Triangle Open',
    82: 'Shaker',
    83: 'Jingle Bell',
    84: 'Bell Tree'
    }

def put_text(surface, font, text, dest, color=COLOR):
    x, y = dest
    for t in text.split('\n'):
        s = font.render(t, True, color)
        surface.blit(s, (x, y))
        y += font.get_linesize()
    
def main():
    pygame.init()
    pygame.midi.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption('drum39.py')
    font = pygame.font.SysFont(pygame.font.get_default_font(), FONT_SIZE)
    for i in range(pygame.midi.get_count()):
        interf, name, input, output, opened = pygame.midi.get_device_info(i)
        if output and name == 'NSX-39 ':
            midiout = pygame.midi.Output(i)
        if input and name == 'NSX-39 ':
            midiin = pygame.midi.Input(i)
    clock = pygame.time.Clock()
    clock.tick(FPS)
    midiout.write_short(0xB0, 7, 0)  # ミクさんの音量を0に
    base_note_shift = BUTTON_U_NOTE_SHIFT  # 中心になる音域(ミクとの差分
    moduration = 0  # モジュレーション ON/OFF
    note_no = 0
    button_o = False  # 各ボタン押下状態
    button_shift = False
    button_vibrato = False
    while True:
        sysex = False
        for e in pygame.event.get():
            if (e.type is QUIT) or (e.type is KEYDOWN and e.key is K_ESCAPE):
                if last_note is not None:
                    midiout.note_off(last_note, 64, 1)
                midiout.write_short(0xB0, 7, 64)  # ミクさんの音量を64に
                return
        if midiin.poll():
            for e in midiin.read(1000):
                (status,data1,data2,data3), timestamp = e
                if sysex and status == 0x11 and data1 == 0x20:
                    if not (data3 & BUTTON_O):
                        button_o = False
                    if not (data3 & BUTTOU_SHIFT):
                        button_shift = False
                    if not (data3 & BUTTON_VIBRATO):
                        button_vibrato = False
                    if data3 & BUTTON_A:
                        base_note_shift = BUTTON_A_NOTE_SHIFT
                    if data3 & BUTTON_I:
                        base_note_shift = BUTTON_I_NOTE_SHIFT
                    if data3 & BUTTON_U:
                        base_note_shift = BUTTON_U_NOTE_SHIFT
                    if data3 & BUTTON_E:
                        base_note_shift = BUTTON_E_NOTE_SHIFT
                    if data3 & BUTTON_O:
                        if not button_o:
                            midiout.note_on(BUTTON_O_NOTE, VELOCITY, 9)
                            button_o = True
                    if data3 & BUTTOU_SHIFT:
                        if not button_shift:
                            midiout.note_on(BUTTOU_SHIFT_NOTE, VELOCITY, 9)
                            button_shift = True
                    if data3 & BUTTON_VIBRATO:
                        if not button_vibrato:
                            midiout.note_on(BUTTON_VIBRATO_NOTE, VELOCITY, 9)
                            button_vibrato = True
                if status  == 0xF0:  # SysEx start
                    sysex = True
                if status == 0xF7:  # SysEx end
                    sysex = False
                if (status & 0xF0) == 0x90:  # Note On
                    note_no = now_note_shift + data1 + base_note_shift
                    midiout.note_on(note_no, VELOCITY, 9)
                    last_note = note_no
                if (status & 0xF0) == 0x80:  # Note Off
                    if last_note is not None:
                        midiout.note_off(last_note, VELOCITY, 9)
                    last_note = None
                if (status & 0xF0) == 0xE0:  # ピッチベンド
                    midiout.write_short(0xE1, data1, data2)
                    now_note_shift = PORT_SHIFT + data2 / 4
        screen.fill(BG_COLOR)
        try:
            note_name = GM_DRUM[note_no]
            if last_note is not None:
                note_name = note_name.upper()
        except KeyError:
            note_name = None
        x = y = 10
        put_text(screen, font, '%3d: %s' % (note_no, note_name), (x, 10))
        y = HEIGHT // 2
        x += FONT_SIZE
        put_text(screen, font, 'vV'[button_vibrato], (x, y))
        put_text(screen, font, 'sS'[button_shift], (x + FONT_SIZE, y))
        put_text(screen, font, 'oO'[button_o], (x + FONT_SIZE * 2, y))
        clock.tick(FPS)
        pygame.display.flip()

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

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