hamo39.py - ポケット・ミクでハモるよ! #大人の科学ポケミク

ポケミクのエフェクト使ってハモってみるテスト。
3半音くらいが限界?

Python 2.7.6 + Pygame 1.9.1 用

u'''hamo39.py - ポケット・ミクでハモるよ!

キーボードの数字キーで何半音分ずらすか指定。
「-」キーでプラスマイナスが逆転。
ハモりが黒鍵にかぶる場合は自動的に半音落とします。
'''
import pygame
import pygame.midi
from pygame.locals import *

FINE = 50 # デチューン

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

NUM_KEYS = K_0, K_1, K_2, K_3, K_4, K_5, K_6, K_7, K_8, K_9

POKEMIKU_NOTE = 78  # ポケミクから送出されるMIDIノートナンバー




ADJUST = (0, -1, 0, -1, 0, 0, -1, 0, -1, 0, -1, 0)
KEY_NAME = 'C C# D D# E F F# G G# A A# B'.split()

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 xg_ex(output, address, msb, lsb):
    output.write_sys_ex(
        0, [0xF0, 0x43, 0x10, 0x4C, 0x02, 0x01, address, msb, lsb, 0xF7])

def xg_system_on(output):
    output.write_sys_ex(
        0, [0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7])

def hamo_pitch(output, pitch):
    xg_ex(output, 0x42, 0x00, 0x40 + pitch)

def main():
    pygame.init()
    pygame.midi.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption('hamo39.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)
    note_no = POKEMIKU_NOTE
    now_note = -1
    pitchbend = 0
    pitch = 3
    pitch_sign = -1
    adjust = 0
    xg_system_on(midiout)
    xg_ex(midiout, 0x40, 0x50, 0x10)  # PITCH CHG1
    hamo_pitch(midiout, pitch)  # Pitch
    xg_ex(midiout, 0x44, 0x00, 0x08)  # Initial Delay
    xg_ex(midiout, 0x46, 0x00, 0x40 - FINE)  # Fine 1
    xg_ex(midiout, 0x48, 0x00, 0x40 + FINE)  # Fine 2
    xg_ex(midiout, 0x4a, 0x00, 0x40)  # Feedback Level
    xg_ex(midiout, 0x70, 0x01, 0x00)  # Pan 1
    xg_ex(midiout, 0x71, 0x7f, 0x00)  # Output Level 1
    xg_ex(midiout, 0x72, 0x7f, 0x00)  # Pan 2
    xg_ex(midiout, 0x73, 0x7f, 0x00)  # Output Level 2

    xg_ex(midiout, 0x5a, 0x01, 0x00)  # CONNECTION
    xg_ex(midiout, 0x56, 0x50, 0x00)  # RETURN
    xg_ex(midiout, 0x57, 0x40, 0x00)  # PAN
    xg_ex(midiout, 0x58, 0x40, 0x00)  # TO REVERB 
    xg_ex(midiout, 0x59, 0x00, 0x00)  # TO CHORUS
    midiout.write_short(0xB0, 0x5e, 127)  # Effect4 Depth

    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):
                # ここに終了処理
                return
            elif e.type is KEYDOWN:
                if e.key in NUM_KEYS:
                    pitch = NUM_KEYS.index(e.key)
                elif e.key is K_MINUS:
                    pitch_sign *= -1
        if midiin.poll():
            for e in midiin.read(1000):
                (status,data1,data2,data3), timestamp = e
                if (status & 0xF0) == 0x90:  # Note On
                    note_no = data1
                elif (status & 0xF0) == 0xE0:  # ピッチベンド
                    now_note = (data1 + data2 * 128 - 8192
                                ) / 512.0 + note_no
                    adjust = ADJUST[(int(round(now_note))
                                     + pitch * pitch_sign) % 12]
                    hamo_pitch(midiout, pitch * pitch_sign + adjust)
        screen.fill(BG_COLOR)
        put_text(screen, font, 'Pitch %+d, %s' % (
            pitch * pitch_sign + adjust,
            KEY_NAME[int(round(now_note) % 12)]), (10, HEIGHT // 5))
        clock.tick(FPS)
        pygame.display.flip()

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

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