5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

明和電機のSUSHI BEATっぽい電子楽器をpythonで作ってみる

Last updated at Posted at 2023-07-02

できたもの

はじめに

みなさんはSUSHI BEATをご存知でしょうか?
オタマトーンで有名な明和電機が2020年に発売した寿司型電子楽器です。
1つ1つの寿司に異なる音楽が入っていて、押すとリピート再生されます。
4つの寿司は音程とテンポが合っているので、複数同時に押すと、アンサンブルできます。
なお、動画後ろのロボットが欲しいのですが、どうやら非売品のようです。

今回はMIDIとpythonを使って、キーボードでできるSUSHI BEATっぽい電子楽器を作っていきます。

実行環境

OS:Ubuntu 22.04 LTS
Python: 3.10.6

下準備(作曲)

そのままSUSHI BEATの音を鳴らすわけにはいかないので、自分で音楽を作るところから初めます。
pythonで楽器を鳴らす方法はいくらでもありますが、今回は自分で楽譜を打ち込む必要があったので、MIDIを使用することにしました。
まずは、MuseScoreをインストールします。
それっぽいメロディを打ち込んで行きます。
image.png
打ち込み終わったら、楽器毎別々のMIDIファイルに分けて保存します。
以下の4つのファイルを生成しました。

* key_beat-Bass_Synthesizer.mid
* key_beat-Drumset.mid
* key_beat-Effect_Synthesizer.mid
* key_beat-Brightness_Synthesizer.mid

これをpythonで読み込んで、再生させます。
以下のリンクにMIDIファイルを格納したので、お試ししたい方はダウンロードしてください。
Google Drive

コード

外部ライブラリとして、
画面描画・キー入力:pygame
MIDI再生:mido
を使用しています。

main.py
from pygame.locals import *
import pygame
import mido
import threading

#pygameの処理
pygame.init()
screen = pygame.display.set_mode((420, 200))
pygame.display.set_caption("key beat")
font = pygame.font.Font(None, 72)
key_pos = {#各キーのテキストの表示位置
    "a": (50,100),
    "s": (150,100),
    "d": (250,100),
    "f": (350,100),
}

#midoの処理
ports = mido.get_output_names()
port = mido.open_output(ports[2])#数字は環境にあわせる
key_dict = ["a", "s", "d", "f"]# 使用するキー
midi_dict = {# 読み込むMIDIファイルを表記
    "a": mido.MidiFile('key_beat-Drumset.mid'),
    "s": mido.MidiFile('key_beat-Brightness_Synthesizer.mid'),
    "d": mido.MidiFile('key_beat-Effect_Synthesizer.mid'),
    "f": mido.MidiFile('key_beat-Bass_Synthesizer.mid')
}
channel_dict = {# MIDIが使用するチャンネル
    "a": 9,
    "s": 11,
    "d": 2,
    "f": 1
}
key_state = {#各キーのステータス。True:押されている、False:離れている
    "a": False,
    "s": False,
    "d": False,
    "f": False,
}
#MIDIファイルの再生。キーを押したときに呼ぶ関数。非同期にするため、threadingを使用
def play(key):
    mid = midi_dict[key]
    while True:#押されている間は繰り返し
        for msg in mid.play():
            if not key_state[key]:
                return
            print(msg)
            port.send(msg)
thread_dict = {
    "a": threading.Thread(target=play, args="a"),
    "s": threading.Thread(target=play, args="s"),
    "d": threading.Thread(target=play, args="d"),
    "f": threading.Thread(target=play, args="f"),
}

#MIDIファイルの停止時の処理
def stop(channel):
    stop_msg = mido.Message('control_change', control=7, value=0, channel=channel)# チャンネルボリュームを0にする。
    port.send(stop_msg)

#画面描画
def screet_text():
    while True:
        screen.fill((0, 0, 0)) #背景色は黒
        for key in key_dict:
            if key_state[key]:
                if screet_text.light:# 赤LEDを表現するために、チカチカさせている。
                    color = (255, 0, 0)
                else:
                    color = (255, 255, 255)
            else:
                color = (100, 100, 100)
            screen.blit(font.render(key.upper(), True, color), key_pos[key])
        screet_text.light = not screet_text.light
        pygame.display.update()
screet_text.light = True
screen_thread = threading.Thread(target=screet_text)
screen_thread.start()

if __name__ == "__main__":
    while True:
        for event in pygame.event.get():
            if not(event.type == KEYDOWN or event.type == KEYUP):#キー入力以外は無視
                continue
            if event.key == K_ESCAPE:# ESCキーでプログラム終了
                    pygame.quit()
                    exit()
            key = pygame.key.name(event.key)
            if not pygame.key.name(event.key) in key_dict:# 使用していないキーは無視
                continue
            if event.type == KEYDOWN:# キーを押すと、再生させるスレッドを起動する。
                key_state[key] = True
                thread_dict[key].start()
            if event.type == KEYUP:# キーを離すと、再生させるスレッドを停止する。
                key_state[key] = False
                stop(channel_dict[key])
                thread_dict[key].join()
                thread_dict[key] = threading.Thread(target=play, args=key)# 新しいスレッドを作らないと、RuntimeError: threads can only be started onceが発生する。

できたもの

(再掲)

まとめ

明和電機のSUSHI BEATっぽい電子楽器をpythonで作ってみました。
コードを書くより、MIDIを打ち込む方が時間かかりました。
より、楽曲を磨いていけば、SUSHI BEATみたいにノリノリの音楽が奏でられると思います。

参考サイト

大変感謝しております。

追記

MIDIだと音源の入れ替えが難しいかと思い、mp3バージョンも作成しました。
こちらの方が再生遅延が少ないです。

mp3.py
from pygame.locals import *
import pygame
import threading
import vlc
import time

#pygameの処理
pygame.init()
screen = pygame.display.set_mode((420, 200))
pygame.display.set_caption("key beat")
font = pygame.font.Font(None, 72)
key_pos = {#各キーのテキストの表示位置
    "a": (50,100),
    "s": (150,100),
    "d": (250,100),
    "f": (350,100),
}

key_dict = ["a", "s", "d", "f"]# 使用するキー

mp3_dict = {# 読み込むMIDIファイルを表記
    "a": vlc.MediaPlayer(),
    "s": vlc.MediaPlayer(),
    "d": vlc.MediaPlayer(),
    "f": vlc.MediaPlayer()
}
mp3_dict["a"].set_mrl("key_beat-Drumset.mp3")
mp3_dict["s"].set_mrl("key_beat-Brightness_Synthesizer.mp3")
mp3_dict["d"].set_mrl("key_beat-Effect_Synthesizer.mp3")
mp3_dict["f"].set_mrl("key_beat-Bass_Synthesizer.mp3")

channel_dict = {# MIDIが使用するチャンネル
    "a": 9,
    "s": 11,
    "d": 2,
    "f": 1
}
key_state = {#各キーのステータス。True:押されている、False:離れている
    "a": False,
    "s": False,
    "d": False,
    "f": False,
}
#MIDIファイルの再生。キーを押したときに呼ぶ関数。非同期にするため、threadingを使用
def play(key):
    mp3 = mp3_dict[key]
    mp3.play()
    while True:
        if not key_state[key]:
            mp3.stop()
            return
        time.sleep(0.1)
thread_dict = {
    "a": threading.Thread(target=play, args="a"),
    "s": threading.Thread(target=play, args="s"),
    "d": threading.Thread(target=play, args="d"),
    "f": threading.Thread(target=play, args="f"),
}

#画面描画
def screet_text():
    while True:
        screen.fill((0, 0, 0)) #背景色は黒
        for key in key_dict:
            if key_state[key]:
                if screet_text.light:# 赤LEDを表現するために、チカチカさせている。
                    color = (255, 0, 0)
                else:
                    color = (255, 255, 255)
            else:
                color = (100, 100, 100)
            screen.blit(font.render(key.upper(), True, color), key_pos[key])
        screet_text.light = not screet_text.light
        pygame.display.update()
screet_text.light = True
screen_thread = threading.Thread(target=screet_text)
screen_thread.start()

if __name__ == "__main__":
    while True:
        for event in pygame.event.get():
            if not(event.type == KEYDOWN or event.type == KEYUP):#キー入力以外は無視
                continue
            if event.key == K_ESCAPE:# ESCキーでプログラム終了
                    pygame.quit()
                    exit()
            key = pygame.key.name(event.key)
            if not pygame.key.name(event.key) in key_dict:# 使用していないキーは無視
                continue
            if event.type == KEYDOWN:# キーを押すと、再生させるスレッドを起動する。
                key_state[key] = True
                thread_dict[key].start()
            if event.type == KEYUP:# キーを離すと、再生させるスレッドを停止する。
                key_state[key] = False
                thread_dict[key].join()
                thread_dict[key] = threading.Thread(target=play, args=key)# 新しいスレッドを作らないと、RuntimeError: threads can only be started onceが発生する。
5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?