ショップ

取扱説明書

  • 準備中

製品概要

PICOLAB-MUSICは、PICO用オーディオ実験用拡張ボードです。

PICOLABシリーズの拡張ボード群は、CircuitPythonやMicroPython を使用したプログラミングでの利用を主眼として、Raspberry Pi PICOの拡張ボードとして開発されました。PICOLAB-MUSICは、タッチスイッチによる鍵盤や、D級アンプ・スピーカーを備えており、CircuitPythonとsynthioライブラリを使用して楽器の作成や実験を行うことができます。

  • Raspberry Pi PICO/PICO W用の多機能拡張ボードです。PICO 2では利用できません。
  • CircuitPythonでのプログラミングに重点を置いて開発されています。
  • PICOのプログラム実験で利用される主要な入出力を搭載しており、PICOLAB-MUSICだけで様々な実験を行うことができます。また、拡張端子を使用して、様々なセンサーを追加で利用できます。
  • 出力として、TFTディスプレイ、カラーLEDx13、D級アンプ・スピーカーを搭載しています。
  • TFTディスプレイは320x170ピクセルです。
  • 入力として、タクトスイッチx2、タッチスイッチx13を搭載しています。
  • ストレージとして、マイクロSDカードソケットを搭載しています。
  • 基板上部に各種のセンサー等を接続できるI2C端子を装備しています。
  • Raspberry Pi PICO/PICO W等のMCUボードは付属しませんので別途お買い求めください。

入出力とセンサー

  • 出力
    • TFT 320x170 (SPI)
    • カラーLED x 13
    • D級アンプとスピーカー(PWMサウンド)
  • 入力
    • タクトスイッチ x2
    • タッチスイッチ x13
  • ストレージ (SPI)
    • マイクロSDカード ソケット
  • 拡張コネクタ
    • スライドボリューム
  • I2C拡張コネクタ (接続機器はオプション)
    • 温度、湿度センサー
    • 照度センサー
    • 加速度、ジャイロセンサー
    • レーザー距離センサー
    • 人感センサー


CircuitPythonでのプログラム例

CircuitPythonのシンセサイザー機能 synthio を使用した電子ピアノのプログラム例です。

  • タッチキーになっている鍵盤を押すとその音が鳴ります。
  • ポリフォニー機能を持ち、ドミソなどの複数の鍵盤を押すと複数の音が出ます。
  • 左のSW1を押しながら鍵盤を押すと1オクターブ高い、SW2を押しながら鍵盤を押すと1オクターブ低い音が鳴ります。
  • 鍵盤を押すとその上部のLEDが点灯します。
  • すみません。TFT表示は使っていません。。。

恐ろしい時代になりました。私は1行もプログラムを書いていません。

指示をしたらGemini君が書いてくれました。といっても、一筋縄ではいきませんでしたが。

synthioを使用したプログラム例は少なく、また、バージョンの違いで機能がかなり変わってきているので、現在のバージョンで適切に動くプログラムを書かせるのに少々苦労しました。細かい指示を繰り返しプログラムを作成させる必要がありました。とは言っても、多くの場合には、ここでエラーが出るなどを文句(状況報告)をすると勝手に再調査して書き直してくれたんですけどね。ただ、なかなか音が出るようにならないので、何度もそろそろ自分で書こうと思いもしました。

それでも、くじけずにGemini君に頑張ってもらって音が出た時は、やった!という感じでしたね。

import board
import audiopwmio
import audiomixer
import synthio
import re
import touchio
import time
import neopixel
import sys
import digitalio

# --- タッチセンサーとノート、LEDのマッピング ---
# GP_ピン番号: {"note": "ノート名", "led_idx": 対応するWS2812Bのインデックス, "is_sharp": シャープ音ならTrue} の形式で定義します。
TOUCH_SENSOR_MAPPING = {
    board.GP0: {"note": "C4", "led_idx": 0, "is_sharp": False},
    board.GP1: {"note": "C#4", "led_idx": 1, "is_sharp": True},
    board.GP2: {"note": "D4", "led_idx": 2, "is_sharp": False},
    board.GP3: {"note": "D#4", "led_idx": 3, "is_sharp": True},
    board.GP6: {"note": "E4", "led_idx": 4, "is_sharp": False},
    board.GP7: {"note": "F4", "led_idx": 5, "is_sharp": False},
    board.GP8: {"note": "F#4", "led_idx": 6, "is_sharp": True},
    board.GP9: {"note": "G4", "led_idx": 7, "is_sharp": False},
    board.GP10: {"note": "G#4", "led_idx": 8, "is_sharp": True},
    board.GP11: {"note": "A4", "led_idx": 9, "is_sharp": False},
    board.GP12: {"note": "A#4", "led_idx": 10, "is_sharp": True},
    board.GP13: {"note": "B4", "led_idx": 11, "is_sharp": False},
    board.GP22: {"note": "C5", "led_idx": 12, "is_sharp": False},
}

# --- WS2812B LED設定 ---
NEOPIXEL_PIN = board.GP21
NUM_PIXELS = len(TOUCH_SENSOR_MAPPING)
PIXEL_BRIGHTNESS = 0.05

PIXEL_COLOR_NATURAL = (255, 255, 0) # 黄色
PIXEL_COLOR_SHARP = (0, 255, 0)     # 緑色

pixels = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=PIXEL_BRIGHTNESS, auto_write=False)
pixels.fill((0, 0, 0))
pixels.show()

if NUM_PIXELS != len(TOUCH_SENSOR_MAPPING):
    print("エラー: NUM_PIXELSとTOUCH_SENSOR_MAPPINGの要素数が一致しません!")
    sys.exit(1)

# --- オクターブシフトボタン設定 ---
OCTAVE_DOWN_PIN = board.GP27
OCTAVE_UP_PIN = board.GP26

octave_down_button = digitalio.DigitalInOut(OCTAVE_DOWN_PIN)
octave_down_button.direction = digitalio.Direction.INPUT

octave_up_button = digitalio.DigitalInOut(OCTAVE_UP_PIN)
octave_up_button.direction = digitalio.Direction.INPUT

current_octave_shift = 0

# --- オーディオ出力ピンの定義 ---
AUDIO_PIN = board.GP20

# --- オーディオ設定 ---
SAMPLE_RATE = 22050

audio = audiopwmio.PWMAudioOut(AUDIO_PIN)
mixer = audiomixer.Mixer(voice_count=len(TOUCH_SENSOR_MAPPING), sample_rate=SAMPLE_RATE, channel_count=1)
synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE, channel_count=1)

audio.play(mixer)
mixer.play(synth, voice=0)

# --- MIDIノート番号計算 ---
NOTE_PARSE_REGEX = re.compile(r"([A-G]#?)([0-9])")

def note_to_midi_number(note_name_str):
    match = NOTE_PARSE_REGEX.match(note_name_str)
    if not match:
        raise ValueError(f"不正なノート名フォーマット: '{note_name_str}'. 'C4' や 'C#4' のような形式を期待します。")

    octave = int(match.group(2))
    note = match.group(1)

    notes_midi_offset = {
        "C": 0, "C#": 1, "D": 2, "D#": 3, "E": 4, "F": 5,
        "F#": 6, "G": 7, "G#": 8, "A": 9, "A#": 10, "B": 11
    }
    
    if note not in notes_midi_offset:
        raise ValueError(f"認識できないノート部分: '{note}' in '{note_name_str}'.")

    midi_note_number = (octave + 1) * 12 + notes_midi_offset[note]
    return midi_note_number

try:
    midi_note_base_numbers = {}
    for pin_obj, mapping_data in TOUCH_SENSOR_MAPPING.items():
        note_name = mapping_data["note"]
        midi_note_base_numbers[note_name] = note_to_midi_number(note_name)
    
    print("\n--- 計算された基本MIDIノート番号 ---")
    for note_name, midi_num in midi_note_base_numbers.items():
        print(f"{note_name}: {midi_num}")
    print("---------------------------------")
except ValueError as e:
    print(f"MIDIノート番号計算エラー: {e}")
    sys.exit(1)

# --- タッチセンサー(Capacitive Touch)設定 ---
touch_sensors = {}
print("\n--- タッチセンサー設定 ---")
for pin_obj, mapping_data in TOUCH_SENSOR_MAPPING.items():
    note_name = mapping_data["note"]
    led_idx = mapping_data["led_idx"]
    is_sharp = mapping_data["is_sharp"]
    try:
        sensor = touchio.TouchIn(pin_obj)
        touch_sensors[pin_obj] = {
            "object": sensor,
            "note": note_name,
            "led_idx": led_idx,
            "is_sharp": is_sharp,
            "is_pressed": False
        }
        print(f"{pin_obj} ({note_name}, LED#{led_idx}, {'#' if is_sharp else ' '}) をタッチセンサーとして設定しました。")
    except ValueError as e:
        print(f"警告: {pin_obj} はタッチセンサーとして設定できませんでした ({e})。このピンはスキップされます。")
        continue
    
if not touch_sensors:
    print("エラー: 設定できたタッチセンサーが一つもありません。Picoが対応しているか確認してください。")
    sys.exit(1)

print("\n電子ピアノの準備ができました!")
print("設定されたGPピンをタッチして音を鳴らしてください。")

# --- メインループ ---
active_midi_notes = set()
active_led_states = {}

while True:
    # --- オクターブシフトボタンのチェック ---
    temp_octave_shift = 0
    if not octave_down_button.value: # GP27が押されている
        temp_octave_shift = -12
    elif not octave_up_button.value: # GP26が押されている
        temp_octave_shift = 12

    if temp_octave_shift != current_octave_shift:
        current_octave_shift = temp_octave_shift
        if active_midi_notes:
            synth.release_all()
            active_midi_notes.clear()
        print(f"オクターブシフト: {current_octave_shift/12:+.0f}オクターブ")

    current_pressed_midi_notes = set()
    next_led_states = {}

    # 全てのタッチセンサーの状態をチェック
    for pin_obj, sensor_data in touch_sensors.items():
        sensor = sensor_data["object"]
        note_name = sensor_data["note"]
        led_idx = sensor_data["led_idx"]
        is_sharp = sensor_data["is_sharp"]

        if sensor.value:
            midi_note = midi_note_base_numbers[note_name] + current_octave_shift
            midi_note = max(0, min(127, midi_note))

            current_pressed_midi_notes.add(midi_note)
            
            next_led_states[led_idx] = PIXEL_COLOR_SHARP if is_sharp else PIXEL_COLOR_NATURAL

            if not sensor_data["is_pressed"]:
                sensor_data["is_pressed"] = True
        else:
            if sensor_data["is_pressed"]:
                sensor_data["is_pressed"] = False

    # 音の処理
    newly_active_midi_notes = current_pressed_midi_notes - active_midi_notes
    # ここを修正: current_pressed_notes -> current_pressed_midi_notes
    newly_inactive_midi_notes = active_midi_notes - current_pressed_midi_notes

    for midi_note in newly_active_midi_notes:
        synth.press([midi_note])
        active_midi_notes.add(midi_note)
    
    for midi_note in newly_inactive_midi_notes:
        synth.release([midi_note])
        active_midi_notes.remove(midi_note)

    if not active_midi_notes: # active_midi_notes が空になったら
        synth.release_all() # 全ての音をリリース (念のため)

    # LEDの処理
    for idx in list(active_led_states.keys()):
        if idx not in next_led_states:
            pixels[idx] = (0, 0, 0)
            del active_led_states[idx]
    
    for idx, color in next_led_states.items():
        if idx not in active_led_states or active_led_states[idx] != color:
            pixels[idx] = color
            active_led_states[idx] = color

    pixels.show()

    time.sleep(0.01)


回路図


関連製品

PICOLAB-SENS

PICOLABシリーズのフラグシップとなるPICO拡張多機能実験ボードで、プログラミングで多用される多くの入出力やセンサーを装備しており、このボードだけで多様な実験を行うことができます。

PICOLAB-BASE

PICOLAB-SENSからセンサー群とD級アンプ・スピーカーが取り除かれた廉価版です。

PICOLAB-HDMI

HDMIコネクタを備えており、CircuitPythonやArduinoとpicodviライブラリを使用してモニタ表示を利用したゲームやデジタルサイネージなどの作成や実験を行うことができます。