Pimoroni でセールをしていたので、思い切って購入。

4x4 のキーボードで、RP2040 を内蔵している。 キー毎にRGB LEDがついていて、FingerDrumにぴったり。

透明なキーキャップで準備万端。

仕様

音の雰囲気で色を決めて、キーを押すと光る。

音(note)

  • BD = 36 # バスドラム
  • SN = 38 # スネア1
  • RM = 40 # リムタム=スネア2
  • CH = 42 # クローズドハイハット
  • OH = 46 # オープンハイハット
  • HT = 50 # ハイタム
  • LT = 45 # ロータム
  • FT = 43 # フロアタム
  • HC = 49 # クラッシュド・シンバル(ハイ)
  • LC = 49 # クラッシュド・シンバル(ロウ)
  • RC = 51 # ライドシンバル

BD(バスドラム)、RC(ライドシンバル)、CH(クローズハイハット)は、左右2個づつ。

LIGHT

キーボードの RGB LED は基本的に、消灯している。 押したときだけ点灯。 しかし、全部点灯しているところも見たいので、 LIGHT キーで全てのキーを点灯させる。

ROTATE

USB-Cコネクタは、LT と FT の間あたりにある。 USBケーブルの取り回しによっては、 CH と RC の間辺りになって欲しいこともある。その場合は、ROTATE キーでレイアウトを変更。

DISCO

長押しで、LEDが虹色にうねうね。

program

finger_drum.py · Gistpython
import time
import math
import board
from keybow2040 import Keybow2040, number_to_xy, hsv_to_rgb

import usb_midi
import adafruit_midi
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn

#
# KEY TYPE
#
BD = 36  # バスドラム
SN = 38  # スネア1
RM = 40  # リムタム=スネア2
CH = 42  # クローズドハイハット
OH = 46  # オープンハイハット
HT = 50  # ハイタム
LT = 45  # ロータム
FT = 43  # フロアタム
HC = 49  # クラッシュド・シンバル(ハイ)
LC = 49  # クラッシュド・シンバル(ロウ)
RC = 51  # ライドシンバ

ROTATE=100
ALL=200

#
# COLOR DEFINITON
#
red = (255, 0, 50)
brown = (55, 0, 0)
green = (10, 155, 10)
purple = (80, 5, 80)
blue = (0, 0, 255)
yellow = (50, 50, 0)
hyellow = (80, 80, 10)
black = (5, 5, 5)
white = (109, 100, 100)

class FingerDrum():
    def __init__(self):
        # keymap
        self.default_notes_ = [
            [LC, LT, FT, HC],
            [CH, HT, OH, CH],
            [RC, SN, RM, RC],
            [ROTATE, BD, BD, ALL],
        ]

        # keymap and note table
        self.notes_ = []

        # for rotate
        self.current_layout_ = 0
        self.rotate_keylayout(default=True)

        # disco mode counter
        self.disco_step_ = 0

        # drum rooling mode
        self.roll_on_ = 1
        self.prev_time_ = 0

        # FingerDrum Mode
        self.is_disco_mode_ = 0
        self.disco_key_no_ = 0
        self.is_hold_key_ = None    # for rolling
        self.next_disco_led_time_ = 0

        self.colors_ = {
            BD : brown,   # バスドラム
            SN : blue,    # スネア1
            RM : blue,    # リムタム=スネア2
            CH : yellow,  # クローズドハイハット
            OH : yellow,  # オープンハイハット
            HT : purple,  # ハイタム
            LT : purple,  # ロータム
            FT : purple,  # フロアタム
            HC : hyellow, # クラッシュド・シンバル(ハイ)
            LC : hyellow, # クラッシュド・シンバル(ロウ)
            RC : yellow,  # ライドシンバ
            ROTATE: black,
            ALL: red,
        }
        # Set USB MIDI up on channel 0.
        self.midi_ = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)

    def update(self, keys):
        now = time.monotonic_ns()
        if self.is_hold_key_ is not None:
            self.roll()

        if self.is_disco_mode_:
            if now > self.next_disco_led_time_:
                self.disco(keys)
                self.next_disco_led_time_ = now + 99_000_000

    def disco(self, keys):
        # key_no for current color changing
        self.disco_key_no_ += 1

        if self.disco_key_no_ > 15:
            self.disco_step_ += 10
            self.disco_key_no_ = 0

        x, y = number_to_xy(self.disco_key_no_)

        hue = (x + y + (self.disco_step_ / 20)) / 8
        hue = hue - int(hue)
        hue = hue - math.floor(hue)
        r, g, b = hsv_to_rgb(hue, 1, 1)

        keys[self.disco_key_no_].set_led(r, g, b)
                    

    def get_note(self, key):
        note = self.notes_[key.number//4][key.number%4]
        color = self.colors_[note]
        return [note, color]

    def send_midi(self, note, velocity=100):
        if note < 100:
            if velocity > 0:
                self.midi_.send(NoteOn(note, velocity))
            else:
                self.midi_.send(NoteOff(note, 0))

    def rotate_keylayout(self, default=False):
        #
        #  0  1  2  3     12  8  4  0
        #  4  5  6  7     13  9  5  1
        #  8  9 10 11  -> 14 10  6  2
        # 12 13 14 15     15 11  7  3
        #
        self.notes_ = []
        for row in range(4):
            cols = []
            for col in range(4):
                cols.append(self.default_notes_[row][col])
            self.notes_.append(cols)

        if default:
            self.current_layout_ = 0
            return

        if self.current_layout_ == 1:
            for n in range(4):
                self.notes_[n][3] = self.default_notes_[0][n]
                self.notes_[n][2] = self.default_notes_[1][n]
                self.notes_[n][1] = self.default_notes_[2][n]
                self.notes_[n][0] = self.default_notes_[3][n]

        self.current_layout_ = 1 - self.current_layout_

        return

    def roll(self):
        # Drum rolling!
        [note, color] = drum.get_note(drum.is_hold_key_)

        now = time.monotonic_ns()
        past_time = now - self.prev_time_
        if past_time > 33_333_333:
            if self.roll_on_:
                drum.send_midi(note)
                drum.is_hold_key_.set_led(*white)
            else:
                drum.send_midi(note, 0)
                drum.is_hold_key_.set_led(*black)

            self.roll_on_ = 1 - self.roll_on_
            self.prev_time_ = now

    def switch_disco_mode(self):
        self.disco_step_ = 0

        self.is_disco_mode_ = 1 - drum.is_disco_mode_

    def switch_rolling_mode(self, key):
        self.is_hold_key_ = key

if __name__ == "__main__":
    # Set up Keybow
    i2c = board.I2C()
    keybow = Keybow2040(i2c)
    keys = keybow.keys

    drum = FingerDrum()
    start = time.monotonic_ns()

    # Loop through keys and attach decorators.
    for key in keys:
        # If pressed, send a MIDI note on command and light key.
        @keybow.on_press(key)
        def press_handler(key):
            [note, color] = drum.get_note(key)
            if note == ROTATE:
                # ROTATE KEY LAYOUT
                drum.rotate_keylayout()

            if note in [ ROTATE, ALL ]:
                # ALL LIGHT
                for k in keys:
                    [note, color] = drum.get_note(k)
                    k.set_led(*color)
            else:
                drum.send_midi(note)
                key.set_led(*color)

        @keybow.on_release(key)
        def release_handler(key):
            [note, color] = drum.get_note(key)
            drum.send_midi(note, 0)
            if not drum.is_disco_mode_:
                key.set_led(*black)
            drum.switch_rolling_mode(None)

        @keybow.on_hold(key)
        def on_hold_handler(key):
            [note, color] = drum.get_note(key)
            if note == ALL:
                # DISCO MODE SWITCH
                drum.switch_disco_mode()
            elif note != ROTATE:
                drum.switch_rolling_mode(key)

    while True:
        # Always remember to call keybow.update()!
        keybow.update()

        drum.update(keys)

install

keybow2040 は、はじめから circuitpython がinstall済みで、MIDI driverもある。 上記のプログラムを自動起動用のファイルとして、keybow2040にcopyするだけ。

Macならこんな感じ。

bash
cp 上記のファイル /Volumes/CIRCUITPY/code.py

勝手にresetされるので、これでおしまい。 GarageBand を起動して、ドラムに設定すればすぐ使える。 iPad Proなら、USB-Cケーブルで繋いで、GarageBandを起動し、ドラムセットを設定する。

demo

おまけ

キーキャップを白に変更。こっちのほうがいいかも。