VS1003とVS1053のMIDI音源を試してみる

Last Updated on 2026年8月9日 by kabekin

ラズパイ用に安価なDACを調べていたところ、秋月などで安価で売っていたVS1003がSDIでそのままデータを転送するだけでMIDIファイルが再生できることが分かりました。
MP3とWAVのエンコーダが内蔵されていることは知っていましたが、MIDIデータも垂れ流すだけで再生できるようです。

秋月電子で購入していたVS1003VS1053

10年以上前に、MP3をPICマイコンで再生できると話題になりICは入手済みだったものの
SOPなので完全放置状態でした。

いろいろ調べていたところMP3やWAVなどのPCM再生のみではく、SMF0のMIDIファイルのもそのまま送れば再生できるとなっています。
SMF0仕様のみですがパースの解析なしにMIDIデータを簡単に再生できるようです。

これならS1やFM-7などの8ビットマイコンでも簡単にMIDIを再生できそうなのでPIAでSPI通信を駆動するための方法を調べるためラズパイ5のGPIOを使ってビットバンキング版でVS1003を駆動してみました。

SOP-DIPの変換基板は入手していましたが、モジュール化されたユニットが安く販売されているのでこちらを購入して試してみました。

VS1053bVS1003の2種類を入手

どちらもコマンドやデータの転送方法は同じようです
VS1053は機能拡張されているようでMIDI音源も強化され音色も増えているようです

簡単に動作確認するためラズパイ5のGPIO + Pythonで実験してみます。
将来的にはレベル3やFM-7でPIA経由で説測するための実験なのでビットバンキングで制御します。

SPIの信号のタイミングは初めてなので安価で売っている中華の8CHロジアナユニットを試してみました。
ロジアナは岩通のSL-4122やHPの54622Dを持っていますが、机の上に置くには邪魔になって活用できません・・・(^^;;;

こんな感じでラズパイ5に接続して実験開始(^^)


こちらの安価な8CHロジアナユニットですがソフトは付属していませんでした。
届いた品物にも取説は入っていません(^^;;;

ネットで調べるとsigrok PulseViewというソフトをダウンロードして使うようです。
比較的多機能のアプリのようですが、リアルタイムで波形は見られない仕様でした。
この中華製8CHロジアナユニットは Saleae Logic8の互換品のようで Saleae Logic というソフトも使えました。
Saleae Logicを使うとリアルタイムで観測できまがすがこちらを使うのはライセンス違反のようですので使えません。

VS1003をSCIのコマンドで初期化して、その後はMIDIファイルをそのままSPIで送るだけで再生できました(^^)


VS1003でMIDIデータを再生するPythonスクリプトのサンプルはネット上では見つかりませんでしたが、SCIでICを初期化してその後はSDIでデータを転送するだけのようなので初期化部分とSDIの転送部分をAIに作ってもらいました(^^)

ビットバンキング版なので、このロジックをそのままPIAに置き換えればFM-7やレベル3でもMIDIが再生できるのではと思います(^^)

このスクリプトは ラズパイ5のGPIO + python3でmidiファイルが再生できます。

#!/usr/bin/env python3
"""
VS1003B SMF0プレーヤー - ビットバンギング版
使い方: python3 vs1003player.py music.mid
"""
import lgpio
import time
import sys

# 信号名 = BCM  Raspi5Pin
PIN_MISO = 9    #21
PIN_MOSI = 10   #19
PIN_SCK  = 11   #23
PIN_XCS  = 8    #24
PIN_XRST = 25   #22
PIN_XDCS = 7    #26
PIN_DREQ = 24   #18

CHUNK = 32   # 一度に送る最大バイト数

h = None

def gpio_setup():
    global h
    h = lgpio.gpiochip_open(0)
    for pin in [PIN_MOSI, PIN_MISO, PIN_SCK,
                PIN_XCS, PIN_XDCS, PIN_DREQ, PIN_XRST]:
        try: lgpio.gpio_free(h, pin)
        except: pass
    lgpio.gpio_claim_output(h, PIN_MOSI, 0)
    lgpio.gpio_claim_output(h, PIN_SCK,  0)
    lgpio.gpio_claim_output(h, PIN_XCS,  1)
    lgpio.gpio_claim_output(h, PIN_XDCS, 1)
    lgpio.gpio_claim_output(h, PIN_XRST, 1)
    lgpio.gpio_claim_input(h, PIN_MISO)
    lgpio.gpio_claim_input(h, PIN_DREQ)

def wait_dreq():
    while lgpio.gpio_read(h, PIN_DREQ) == 0:
        pass

def spi_byte(b: int) -> int:
    result = 0
    for i in range(7, -1, -1):
        lgpio.gpio_write(h, PIN_MOSI, (b >> i) & 1)
        lgpio.gpio_write(h, PIN_SCK, 1)
        result = (result << 1) | lgpio.gpio_read(h, PIN_MISO)
        lgpio.gpio_write(h, PIN_SCK, 0)
    return result

def sci_write(reg, value):
    wait_dreq()
    lgpio.gpio_write(h, PIN_XCS, 0)
    spi_byte(0x02)
    spi_byte(reg)
    spi_byte((value >> 8) & 0xFF)
    spi_byte(value & 0xFF)
    lgpio.gpio_write(h, PIN_XCS, 1)

def sci_read(reg) -> int:
    wait_dreq()
    lgpio.gpio_write(h, PIN_XCS, 0)
    spi_byte(0x03)
    spi_byte(reg)
    hi = spi_byte(0x00)
    lo = spi_byte(0x00)
    lgpio.gpio_write(h, PIN_XCS, 1)
    return (hi << 8) | lo

def sdi_send(data: bytes):
    """32バイト単位でDREQ確認しながら送信"""
    for i in range(0, len(data), CHUNK):
        lgpio.gpio_write(h, PIN_XDCS, 1)
        wait_dreq()
        lgpio.gpio_write(h, PIN_XDCS, 0)
        for b in data[i:i+CHUNK]:
            spi_byte(b)
    lgpio.gpio_write(h, PIN_XDCS, 1)

def vs_reset():
    lgpio.gpio_write(h, PIN_XRST, 0)
    time.sleep(0.2)
    lgpio.gpio_write(h, PIN_XRST, 1)
    time.sleep(0.2)
    wait_dreq()
    print("Reset OK")

def vs_init():
    vs_reset()
    sci_write(0x00, 0x0800)
    time.sleep(0.01)
    sci_write(0x03, 0x9800)
    time.sleep(0.01)
    sci_write(0x0B, 0x0000)
    wait_dreq()
    mode = sci_read(0x00)
    vol  = sci_read(0x0B)
    print(f"Init OK  MODE=0x{mode:04X} VOL=0x{vol:04X}")

def play_midi(filename: str):
    print(f"Loading: {filename}")
    with open(filename, 'rb') as f:
        midi_data = f.read()
    print(f"Size: {len(midi_data)} bytes")

    # VS1003BのデコーダーはSMFファイルをそのまま受け付ける
    # ファイル全体をSDIに流すだけでOK(デルタタイム処理はチップ側)
    print("再生中... (Ctrl+C で停止)")
    start = time.time()
    sdi_send(midi_data)

    # パディングを送ってバッファをフラッシュ
    sdi_send(bytes(2048))

    elapsed = time.time() - start
    print(f"転送完了 ({elapsed:.1f}秒)")

    # 再生が終わるまで待機
    # VS1003BのSCI_DECODE_TIMEレジスタで再生位置を監視
    print("再生終了を待機中...")
    prev_time = -1
    no_change = 0
    while True:
        decode_time = sci_read(0x04)   # SCI_DECODE_TIME
        if decode_time != prev_time:
            print(f"  再生位置: {decode_time}秒", end='\r')
            prev_time = decode_time
            no_change = 0
        else:
            no_change += 1
            if no_change > 10:
                break
        time.sleep(0.5)

    print(f"\n再生完了")

def vs_stop():
    """音量を即座にゼロにして停止"""
    sci_write(0x0B, 0xFEFE)   # VOL最小(ほぼ無音)
    time.sleep(0.1)
    # さらに確実に止めたい場合はリセットも追加
    lgpio.gpio_write(h, PIN_XRST, 0)
    time.sleep(0.1)
    lgpio.gpio_write(h, PIN_XRST, 1)

def main():
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <file.mid>")
        sys.exit(1)

    gpio_setup()
    try:
        vs_init()
        play_midi(sys.argv[1])
    except KeyboardInterrupt:
        print("\n停止処理中...(連打禁止)")
        try:
            vs_stop()
        except KeyboardInterrupt:
            # 停止処理中に2回目のCtrl+Cが来ても無視して先に進める
            print("(停止処理中に割り込みを検出、無視して続行)")
        print("停止しました")
    finally:
        try:
            lgpio.gpiochip_close(h)
        except Exception as e:
            print(f"gpiochip_close失敗: {e}")
        print("Cleanup done")

if __name__ == "__main__":
    main()

 

私はVS1053bよりVS1003の再生音の方が好きな音色でした。
皆さんはVS1053bとVS1003のどちらが好きですか?

そんなVS1003/1053でのMIDI再生の記録・・・

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

Maximum file size: 2 MB.
Allowed formats: JPG, JPEG, PNG.
Drop image here

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください