Hyodot が 2025年12月01日21時17分46秒 に編集
コメント無し
本文の変更
# 外観  ## ADSR入力例の動画 Facebookに紹介動画があります。(Facebookへのログインが必要です) https://www.facebook.com/toshikazu.hyodo/videos/1216092900384973?idorvanity=247733226639836 # 用途(課題) この作品は、FM音源IC(YMF825)のパラメータをグラフィックに編集するための作品です。 FM音源のパラメータは、数値だけではイメージがつかみにくいものです。 ADSRパラメータの編集は、ADSRの折れ線グラフのようなものを表示して、グラフの偏移点をつまんで操作できれば全体のイメージがつかみやすくなります。 音源のパラメータの選択肢もパラメータの番号ではなく、HzやdBなどで表されるパラメータを表示して選択できれば分かりやすくなります。 ここでは、FM音源のパラメータ編集方法ですが、相互に関連するパラメータを設定する場面では他の用途にも応用できると思います。 # 編集画面の構成方法について FM音源IC YMF825 には、16種類の楽器に相当する音源の設定ができます。 各音源にはBO、LFO、ALGという3つのパラメータと、17種類のパラメータをもつ4オペレータがあります。 4オペレータには、それぞれ SR、LOF、KSR、RR、DR、AR、SL、TL、KSL、DAM、EAM、DVB、EVB、MUL、DT、WS、FBという17種類のパラメータがあります。 すなわち、1つの音源には、3 + 17 * 4 = 71のパラメータがあります。 パソコンであれば、大きな画面で、すべてのパラメータを表示して操作することが可能だと思いますが、今回利用したタッチパネル付きLCDは、画面が小さいので、4つのオペレータ別にパラメータを編集しています。 シンセサイザーの中核の部分は、いわゆるADSR(AR,DR,SR,RR)のパラメータですが、一つの画面で折れ線グラフを操作するようなイメージで編集します。YMF825には、SLというパラメータもありますので加えています。また、KSL、KSRというパラメータも含めました。 WS(Wave Shapes):音源の波形の選択では、29の波形のイメージ表示してそれをタッチ選択するようにしました。 ALG(Algorithm):アルゴリズムは、4つのオペレータをどのような組み合わせるかの選択ですが、これも7つのアルゴリズムのイメージを表示して、タッチ選択できるようにしました。 なお、素人の理解で作ったFM音源パラメータ編集プログラムです。間違いや勘違いがあると思います。ご利用される場合は自己責任でお願いします。 # 概要(作品全体の説明) 2つのPicoが連携して動作します。PicoMainとPicoSubと呼びます。 PicoMainは音源編集用、PicoSubはYMF825を接続してMIDI再生(発音)用です。 PicoMainには、パラメータをグラフィックに編集するためにタッチパネル付き液晶(Waveshare 3.5型静電容量式タッチスクリーンディスプレイ 320 x 480 SPI接続)を接続しました。Waveshare社の商品名は、Pico-ResTouch-LCD-3.5です。 PicoMainとPicoSubは、シリアル通信し、PicoMainで編集した音源のパラメータや発音のモードなどをPicoSubに送信します。 PicoSubは、PicoMainから受信がないときは、MIDI端子から受信してYMF825で演奏するとともに、MIDI-OUT端子にも出力します。 紹介動画では、MIDI-OUT端子にポケット・ミクを接続しています。ポケット・ミクは、USB端子ですので変換アダプタを挟んでいます。 ポケット・ミクは、チャンネル0にミクの音声が固定されています、チャンネル0を出力しないでポケット・ミクが持つGM音源を伴奏に利用できるように工夫しました。 # 主要部品 |デバイス名|用途|接続方法| |:-------------------------|:--------------|:-------------|:----------------| |Pimoroni Pico Plus2| PicoMain:音源編集用|PicoSubとシリアル接続| |Waveshare 3.5型静電容量式タッチパネルLCD|編集画面表示・タッチ入力用| PicoMain にSPI接続| |Raspberry Pi Pico | PicoSub:YMF825制御用| PicoMainとシリアル接続| |YMF825(YAMAHA FM音源)|設定された音源でMIDI再生| PicoSub とSPI接続| |MIDIインターフェース|PicoSubでMIDI再生|PicoSubにシリアル接続| ## 入手先など(ご参考) Waveshare 3.5型静電容量式タッチパネルLCD Waveshare Pico-ResTouch-LCD-3.5 https://www.waveshare.com/wiki/Pico-ResTouch-LCD-3.5 Pimoroni Pico Plus 2 https://www.switch-science.com/products/9906 Raspberry Pi Pico https://akizukidenshi.com/catalog/g/g116132/ YMF825(YAMAHA FM音源)・・・残念ながら販売終了です。 https://akizukidenshi.com/catalog/g/g112414/ MIDIインターフェイス基板キット https://akizukidenshi.com/catalog/g/g111112/ # 実体配線図  PicoMain側は、Pico-ResTouch-LCD-3.5の背面にある40PinのPico用のソケットにPimoroni Pico Plus2を装着するだけです。ソケットに差し込めるよう、予めPimoroni Pico Plus2にヘッダーピンを半田付けする必要があるのと、PicoSub側とのシリアル通信の配線はありますが、追加したハードウェアはありません。 PicoMainとPicoSubは、5Vの電源ラインも接続して、どちらかのUSB端子に電源供給されれば、PicoMainとPicoSub両方が動作します。 Pico-ResTouch-LCD-3.5には、SDカードインターフェースも付属しています。編集した音源のパラメータをSD-Cardに保存・読み出しに利用しました。提供されているサンプルプログラムを参考にすると簡単に繋げました。 ## PicoMain とLCD、タッチパネル、SDカードとのSPIデバイス接続 Pico-ResTouch-LCD-3.5の背面にある40PinのソケットにPicoMain(Pimoroni Pico Plus2)を装着すると、次の通りSPI接続されます。 このSPI接続では、同じMOSI、MISO、SCKを使って、CS端子の仕様を、LCDの場合はLCD_CS、タッチパネルの場合はTP_CS、SD-Cardの場合はSD_CSに切り替えて使用する仕組みなっています。 ### Pico(Pimoroni Pico Plus2)と Pico-ResTouch-LCD-3.5 との gpio接続 |信号名|Pico gpio番号| |:-------------------------|:--------------| |LCD_DC |Pin(8, Pin.OUT)| |LCD_CS |Pin(9, Pin.OUT)| |LCD_SCK |Pin(10)| |LCD_MOSI |Pin(11)| |LCD_MISO |Pin(12)| |LCD_BL |Pin(13, Pin.OUT)| |LCD_RST |Pin(15, Pin.OUT)| |TP_CS |Pin(16, Pin.OUT)| |TP_IRQ |Pin(17, Pin.IN)| |SD_CS |Pin(22, Pin.OUT)| ## PicoSub側 のYMF825 および MIDI端子との接続 PicoSub(Raspberry Pi Pico)側では、FM音源IC(YMF825)をSPI接続して、MIDI端子をシリアル接続しています。  ### FM音源IC(YMF825)とのSPI接続 |PicoSub gpio name| (pin) |配線色|(pin)| YMF825| |:--------------|:----:|:----:|:---:|:---:| | gpio-17 SPI CS | (22) | 白 | (1) | SS | | gpio-19 SPI MOSI | (25) | 青 | (2) | MOSI | | gpio-16 SPI MISO | (21) | 緑 | (3) | MISO | | gpio-18 SPI CLK | (24) | 黄 | (4) | CLK | | GND | | 白 | (5) | GND | | 5V | | 橙 | (6) | VCC | | gpio-22 RESET | (29) | 紫 | (7) | RST | | No Connect | | | (8) | NC | ### YMF825 Monitor LED-BLUE(Note Onの期間中点灯) |PicoSub gpio name| (pin) |接続部品| GND | |:--------------|:----:|:----:|:---:|:---:| |gpio-28 | (34) |BLUE-LED Anode | BLUE-LED Cathode| ### PicoSub とMIDI端子の接続 |PicoSub gpio name| (pin) |配線色|接続先| |:--------------|:----:|:----:|:---:|:---:| |gpio-1 UART0 TxD| (1) | 黄 |TX信号ドライブ回路の入力へ| |gpio-2 UART0 RxD| (2) | 緑 |カレント・ミラー回路の出力から| MIDI の受信(RXD)と送信(TXD)の信号波形改善のため、受信回路には2つのトランジスタでカレントミラー回路構成し、送信回路にはドライバーとして74HC04(インバータ)を入れました。   # PicoMainとPicoSub との シリアル接続 | PicoMain| (pin) |配線色|配線色| (pin)| PicoSub| |:---------------|:-----:|:----:|:-----:|:----:|:----------------:| |gpio-2 respT |(4)| 紫 | 黄 |(4)| respT gpio-2| | | |ここでクロス| | | | |gpio-3 respR |(5)| 黄 | 紫 |(5)| respR gpio-3| ||||||| |gpio-4 TX |(6)| 緑 | 青 |(6)| TX gpio-4| | | |ここでクロス| | | | |gpio-5 RX |(7)| 青 | 緑 |(7)| RX gpio-5|   ### PicoMainからPicoSubへの送信手順 PicoSubは通常、MIDI信号を受信して再生(音出し)処理をしています。突然、PicoMainからデータが送られてきても対応できませんので、次のような手順で通信するようにプログラムしてします。 ① PicoMainは、PicoSubへの送信データがあるなら、respTをLowにしてPicoSubに受信を要求します。 ② PicoSubは、処理の合間にrespRをポーリングして、Lowを検知したら、受信モードに移行してrespTをLowにして、受信体制が整ったことをPicoMainに知らせます。 ③ PicoMainは、PicoSubからrespRが返ってきたら、TXを使って115200Kbpsでデータ送信し、PicoSubは受信します。 ④ 送信が終わったらPicoMainは、respTをHighにし、PicoSubも、respTをHighにして通信は終了します。 # PicoMainに「Pimoroni Pico Plus2」を採用した理由(読み飛ばし可) Pico-ResTouch-LCD-3.5は、背面にある40PinのソケットにRaspberry Pi Pico2を装着するだけでMicroPythonのFramebufferを利用して、文字を描く、線を引く、点を打つ、四角を描くなどのプログラムが簡単に作れます。 しかし、LCD 320 x 480 1画面分で307200バイトのメモリを必要としますので、Pico2(PicoではなくPico2)のメモリ(520KBのSRAM)でも、残りのメモリでは小さなプログラムしか動かせません。 Waveshare社のサイトで提供されているサンプルプログラムでは、画面の半分のバッファを用意して、プログラム領域を確保していますが、画面・半分だけのバッファではプログラムが煩雑になります。 今回、PicoMainにPimoroni Pico Plus2を採用したのは、メモリを気にすることなくプログラムするためです。Pimoroni Pico Plus2は、8MBのPSRAMを搭載していますので、Pico2よりも約16倍のメモリが使えます。 ところがPimoroni Pico Plus2を利用すると嵌まり込む症状が出て解決するのに時間がかかりました。SPIの速度を、Pico2で動作していた40MHzから20MHzに変更したら症状は出なくなりました。SPIの動作を見ると、実際にはその60% 12MHzで動作しています。表示速度は落ちますが、大きなプログラムを作れるので良しとしました。 # プログラム開発環境(読み飛ばし可) Raspberry Pi 4B にRaspberry Pi OSを標準でインストールするとThonnyが入ります。そのThonnyだけを使って開発しました。 すべてのプログラム言語は、MicroPythonです。 Pimoroni Pico Plus2 または Raspberry Pi Pico2をUSBケーブルでRaspberry Pi 4BにUSB接続して、Thonnyを起動させれば準備完了ですが、初めて接続する場合には、Pimoroni Pico Plus2 または Raspberry Pi Picoに、MicroPythonファームウェア(UF2 ファイル)を書き込む必要があります。ご存じない方は、ネットで検索してください。ファームウェアはMicroPython用を導入してください。 Raspberry Pi Pico の場合はThonnyからインストールできますが、Pimoroni Pico Plus2は、専用のサイトからUF2ファイルをダウンロードする必要があります。 Thonnyのシェル画面に表示されるシリアル出力のみでデバッグしました。 # プログラムの紹介 ## PicoMain側のプログラム PicoMainプログラムは、次のプログラムで構成されています。 ### main.py
```
```MicroPython:main.py
######################################################################## # 2025/11/06 Programed by Hyodot # PicoM_YMF825Edit-152.py # PicoSub 側 # PicoS_YMF825Midi-023.py # YMF825用のパラメータ設定用 PicoMain用のプログラムである。 # LCDタッチパネル WAVE SHARE Pico-ResTouch-LCD-3.5 を利用して # グラフィックな編集を実現する。ADSRなどの操作はわかりやすくなる。 ######################################################################## # # 2025/08/02 : ADSRの表示と入力、WSの表示、ALGの表示まで完成させた # main_YMF825_PicoM-047_AdsrTest.py # をベースに開発している。実行には、上記から関数群を分離した # tone_edit.py # を必要とする。 # # tone_edit.pyは、LCD表示とタッチセンス部分をライブラリとして独立させた # touch_lcd.py # を必要とする。 # touch_lcd.pyは、 # WAVE SHARE Pico-ResTouch-LCD-3.5 で用意された main_3inch5.py を改良した # touch_lcd-20250814-No3TouchLCD.py # をベースにしている。 # # 2025/08/14:Pico2は、RAMが520KB FrameBufferを使った大きなプログラムが # 作れないので、Pimoroni Pico Plus 2 PSRAM 8MB を利用する。 # PimoroniPicoPlus2で、WAVE SHARE Pico-ResTouch-LCD-3.5 を動作させると # はまり込む症状が出たが、SPIの周波数を40MHzから20MHzに変更すれば解決した。 # 20MHzを指定しても、実際の動作は6割の12MHzでしか動作していないが良しとする。 # # 設定した音源データ(ToneData)を SD-CARDに書き込み・読み込みができるようにした。 # SD-CARD への Read Write は、sdcard.py を利用する。 # sdcard.py では、touch_lcd.py で使用している SPI-1 を共有しているが、 # 特に意識して切り替えをしなくても問題なく動作している。よくわからないが良しとする。 # # YMF825 を搭載した PicoSub とシリアル通信で設定した Tone Data を送信する # # # # ######################################################################## from machine import Pin, SPI, PWM from micropython import const import time import utime import os import vfs # 仮想ファイルシステム import gc # メモリ利用状況表示用 import ymf825py as ymf825 import mojipy as moji # edit_tone.py から必要な関数をインポートする # import した「関数名」で利用できる。 print(f'Function Program Loading Start ... ') from tone_edit import spi_change2_lcd from tone_edit import dspFill from tone_edit import dspShow from tone_edit import dspColorlist from tone_edit import dspFillUpper from tone_edit import dspFillLower from tone_edit import dsp_menu from tone_edit import edit_menu from tone_edit import select_MainMenu from tone_edit import select_SubMenu from tone_edit import select_SubCom_tab from tone_edit import select_SubOp_tab from tone_edit import select_ALG from tone_edit import dsp_text_bar from tone_edit import dsp_ALGup from tone_edit import dsp_ALG_TN from tone_edit import wave_form from tone_edit import select_WS from tone_edit import edit_mul from tone_edit import edit_adsr from tone_edit import edit_dam from tone_edit import edit_bo from tone_edit import select_ToneNumber from tone_edit import select_SwapCh from tone_edit import edit_ToneName from tone_edit import select_Item from tone_edit import select_CanExe from tone_edit import edit_text # SD-Card from sdcard import SDCard print(f'Library Program Loading End') ######################################################################## print(f'main MicroPython Start ... gc.mem_alloc() = {gc.mem_alloc()}') ######################################################################## # UART PicoSub uartRespT = Pin(2, Pin.OUT) # Low True uartRespT.value(1) uartRespR = Pin(3, Pin.IN) # Low True # SD-Card sdcardCS = Pin(22, Pin.OUT) # main-tab sub-tab のタブ番号 tabGET = const(1) tabCOM = const(2) tabOP = const(3) tabPUT = const(4) tabALG = const(1) tabBO = const(2) tabLOF = const(3) tabWS = const(1) tabMUL = const(2) tabADSR = const(3) tabDAM = const(4) tabMR = const(1) tabYMF = const(1) tabSD = const(2) tabMODE = const(4) OP_max = const(0) OP1 = const(1) OP2 = const(2) OP3 = const(3) OP4 = const(4) # YMF825 より引用 # カラー設定を取り込み、カラー定数設定 lcd_color_list = dspColorlist() BLACK = lcd_color_list[0] RED = lcd_color_list[1] GREEN = lcd_color_list[2] BLUE = lcd_color_list[3] YELLOW = lcd_color_list[4] PURPLE = lcd_color_list[5] BLUEGREEN = lcd_color_list[6] GRAY = lcd_color_list[7] WHITE = lcd_color_list[8] # VOICE COMMON PARAMETER ROW s Number COM = const(0) BO = const(1) LFO = const(2) ALG = const(3) VOICE_COM_COL_NUM = const(4) # VOICE COMMON PARAMETER COLUMON s Number CM0 = const(0) # Oparator Name CM1 = const(1) # Edit CM2 = const(2) # Orig CM3 = const(3) # Max VOICE_COM_ROW_NUM = const(4) # 20240326 Change # VOICE OPTIONAL PARAMETER ROW s Number OPT = const(0) SR = const(1) XOF = const(2) KSR = const(3) RR = const(4) DR = const(5) AR = const(6) SL = const(7) TL = const(8) KSL = const(9) DAM = const(10) EAM = const(11) DVB = const(12) EVB = const(13) MUL = const(14) DT = const(15) WS = const(16) FB = const(17) VOICE_OP_ROW_NUM = const(18) # ymf825の音源(音色)について # Common は、音源(音色)毎に1つである com_BO = ymf825.Voice_Com_Edit[BO][CM1] com_LFO = ymf825.Voice_Com_Edit[LFO][CM1] com_ALG = ymf825.Voice_Com_Edit[ALG][CM1] # Operator # オペレーション・パラメータは、音源(音色)毎に4つのオペレータがある OP_num = 1 ''' YMF825 Tone Data には、 Tone_Com_Orig + Tone_Op_Orig # YAMAHA Original Tone_Com_Edit + Tone_Op_Edit # 編集用 があり、Tone_Com_Edit + Tone_Op_Edit を要素毎に分解した Voice_Com_Edit + Voice_Op_Edit # 要素編集用 がある。 # Common BO, LFO, ALG の最大値 Voice_Com_Max = [3, 3, 7] # Common SR, XOF, KSR, RR, DR, AR, SL, TL, KSL, DAM, EAM, DVB, EVB, MUL, DT,WS, FB の最大値 Voice_Op_Max = [15, 1, 1, 15, 15, 15, 15, 63, 3, 3, 1, 3, 1, 15, 7, 31, 7] 以下は、ymf825.serialOutVoiceData(putTN) により Voice_Com_Edit + Voice_Op_Edit をシリアル出力(プリント)したものである。 Voice Number = 0 : GRAND_PIANO Common : Edt| Org| Max| BO : 0| 0| 3| LFO : 1| 1| 3| ALG : 3| 3| 7| Operator : e1| e2| e3| e4| o1| o2| o3| o4| Mx| SR : 0| 2| 1| 2| 0| 2| 1| 2| 15| XOF : 0| 0| 0| 0| 0| 0| 0| 0| 1| KSR : 0| 1| 0| 1| 0| 1| 0| 1| 1| RR : 6| 3| 4| 6| 6| 3| 4| 6| 15| DR : 7| 3| 1| 2| 7| 3| 1| 2| 15| AR : 15| 14| 13| 13| 15| 14| 13| 13| 15| SL : 15| 2| 3| 4| 15| 2| 3| 4| 15| TL : 39| 40| 34| 0| 39| 40| 34| 0| 63| KSL : 1| 3| 0| 2| 1| 3| 0| 2| 3| DAM : 0| 0| 0| 0| 0| 0| 0| 0| 3| EAM : 0| 0| 0| 0| 0| 0| 0| 0| 1| DVB : 0| 0| 0| 0| 0| 0| 0| 0| 3| EVB : 0| 0| 1| 1| 0| 0| 1| 1| 1| MUL : 1| 5| 1| 1| 1| 5| 1| 1| 15| DT : 0| 0| 0| 0| 0| 0| 0| 0| 7| WS : 8| 0| 0| 0| 8| 0| 0| 0| 31| FB : 0| 0| 0| 0| 0| 0| 0| 0| 7| ''' # PicoSub' YMF825 MIDI Play Mode # PicoSub 側では、'Ch0_Waon+4sec' 'Ch0_Waon+Normal' いずれのモードでも # MIDI のプログラムチェンジを受け付けると Ch0 和音対応を解除して、MIDI信号に対応した発音にする。 mode_list = [ 'Init_YMF825', # YMF825 を初期設定する 'All_Note_Off', # すべてのチャンネルのノートオフ 'Ch0_Waon+4sec', # Ch0 和音対応 4秒維持 'Ch0_Waon+Normal', # Ch0 和音対応 4秒維持はしない 'With_Out_Ch0', # Ch0 を MIDI出力しない 'Swapping_Ch0>', # Ch0 を 指定した MIDI Ch に出力する ] ############################################################### # HandShake Send RTS & Recive CTS Cross Connection # PicoMain から PicoSub に respT(RTS) を 0 とすることで通信を要求し、 # PicoSub から PicoMain に respR(CTS) を 0 とすることで通信を開始する # PicoMain と PicoSub との接続 # <<< PicoMain >>> <<< PicoSub >>> # gpio-2 respT (4) --紫-- --黄-- (4) respT gpio-2 # X # gpio-3 respR (5) --黄-- --紫-- (5) respR gpio-3 # # gpio-4 TX (6) --緑-- --青-- (6) TX gpio-4 # X # gpio-5 RX (7) --青-- --緑-- (7) RX gpio-5 ############################################################### def waitCTS(rts, time, msg, cnt): global uart_timer if rts == 0: # Send RTS On uartRespT.value(0) uart_timer = time # Wait CTS On while uartRespR.value() == 1: # On == Low になるのを待つ if uart_timer == 0: print(msg) uart_timer = time if cnt > 0: cnt -= 1 else: break # Pico Sub と UART 通信できない utime.sleep_ms(10) else: # Send RTS Off uartRespT.value(1) uart_timer = time # Wait CTS Off while uartRespR.value() == 0: # Off == High になるのを待つ if uart_timer == 0: print(msg) uart_timer = time if cnt > 0: cnt -= 1 else: break # Pico Sub と UART 通信できない utime.sleep_ms(10) return cnt ####################################################################### def setToneDataPicoSub(edit_num, save_num): print('setToneDataPicoSub(edit_num)') # Sub Pico へ送信要求を送出 print('RTS On to Sub_Pico') cnt = 3 cnt = waitCTS(0, 1000, 'Wait Sub_Pico CTS On', cnt) if cnt > 0: # print('CTS On(0) from Sub_Pico') # ToneVoice をシリアル出力 ymf825.serialOutVoiceData(edit_num) # Tone_Com_Edit, Tone_Op_Edit を ASCII2文字の send_buffに変換 # ymf825.serialOutToneData(edit_num) # 20240727 Change 保存するときに ToneNumber を指定できるようにした ymf825.takeOutToneData2(edit_num, save_num) # send_buff を UART で送信 ymf825.uartOutToneData() utime.sleep_ms(10) # Sub Pico へ送信要求の解除を送出 print('RTS Off to Sub_Pico') cnt = 3 cnt = waitCTS(1, 1000, 'Wait Sub_Pico CTS Off', cnt) if cnt > 0: print('CTS Off(1) from Sub_Pico') print('ymf825.uartOutToneData() was performed') else: print() print('CTS Off(1) signal not comming from Sub-Pico') print('Check the connection with Sub-Pico') print() else: print() print('CTS On(0) signal not comming from Sub-Pico') print('ymf825.uartOutToneData() was not performed !!!') print('Check the connection with Sub-Pico') print() # Send RTS Off uartRespT.value(1) ####################################################################### # PicoSub MIDI Play Mode Set # [All_Note_Off] [Normal] [Ch0_Waon] [Ch0_Waon+Delay4] def setPlayModePicoSub(command): print('setPlayModePicoSub(command)') # Sub Pico へ送信要求を送出 print('RTS On to Sub_Pico') cnt = 3 cnt = waitCTS(0, 1000, 'Wait Sub_Pico CTS On', cnt) if cnt > 0: # print('CTS On(0) from Sub_Pico') ####################################################### ymf825.uart_write_multi(command) ####################################################### utime.sleep_ms(10) # Sub Pico へ送信要求の解除を送出 print('RTS Off to Sub_Pico') cnt = 3 cnt = waitCTS(1, 1000, 'Wait Sub_Pico CTS Off', cnt) if cnt > 0: print('CTS Off(1) from Sub_Pico') print('setPlayModePicoSub(command) was performed') else: print() print('CTS Off(1) signal not comming from Sub-Pico') print('Check the connection with Sub-Pico') print() else: print() print('CTS On(0) signal not comming from Sub-Pico') print('!!! setPlayModePicoSub(command) was not performed !!!') print('Check the connection with Sub-Pico') print() # Send RTS Off uartRespT.value(1) ####################################################################### def setToneTablePicoSub(): print('setToneTablePicoSub()') # Sub Pico へ送信要求を送出 print('RTS On to Sub_Pico') cnt = 3 cnt = waitCTS(0, 1000, 'Wait Sub_Pico CTS On', cnt) if cnt > 0: # print('CTS On(0) from Sub_Pico') # ToneTable を ASCII2文字の send_buffに変換 ymf825.takeOutToneTable() # ToneTableリストを UART に転送 ymf825.uartOutToneTable() utime.sleep_ms(10) # Sub Pico へ送信要求の解除を送出 print('RTS Off to Sub_Pico') cnt = 3 cnt = waitCTS(1, 1000, 'Wait Sub_Pico CTS Off', cnt) if cnt > 0: print('CTS Off(1) from Sub_Pico') print('ymf825.uartOutToneTable() was performed') else: print() print('CTS Off(1) signal not comming from Sub-Pico') print('Check the connection with Sub-Pico') print() else: print() print('CTS On(0) signal not comming from Sub-Pico') print('ymf825.uartOutToneTable() was not performed !!!') print('Check the connection with Sub-Pico') print() # Send RTS Off uartRespT.value(1) # SD-CARD のファイルリスト # Waveshare sdcard_demo.py より引用 def print_directory(path, tabs = 0): for file in os.listdir(path): stats = os.stat(path+"/"+file) filesize = stats[6] isdir = stats[0] & 0x4000 if filesize < 1000: sizestr = str(filesize) + " by" elif filesize < 1000000: sizestr = "%0.1f KB" % (filesize/1000) else: sizestr = "%0.1f MB" % (filesize/1000000) prettyprintname = "" for i in range(tabs): prettyprintname += " " prettyprintname += file if isdir: prettyprintname += "/" print('{0:<40} Size: {1:>10}'.format(prettyprintname, sizestr)) # recursively print directory contents if isdir: print_directory(path+"/"+file, tabs+1) # SD-CARD 指定パス直下のファイルリスト。ソートした結果を返す。 def sd_file_list(path): file_list = list() for file in os.listdir(path): stats = os.stat(path+"/"+file) filesize = stats[6] isdir = stats[0] & 0x4000 if not isdir: file_list.append(file) print() print(f'file_list = {file_list}') # リスト形式を タプル形式に変換 tpl_list = tuple(file_list) # タプル形式で、ソートを実施して、リストに戻す # tpl_list = tuple(sorted(tpl_list)) # これは、タプルに戻す場合 file_list = sorted(tpl_list) # 拡張子 .txt を取り除く(この処理、ファイル名の本体に .txt が無いことが前提) for i in range(len(file_list)): file_list[i] = file_list[i].replace('.txt', '') return file_list # SD-card から Tone Dataを読み込み # Tone Data は、100バイト、1行のみである def readToneData_fromSD(file_path): # 指定されたファイルを読み込み、ymf825.rcv_tone に展開する # この時点では、Tone Data として取り込むか否かは決定していない。 with open(file_path, 'r') as f: ymf825.rcv_buff = f.readline() if len(ymf825.rcv_buff) == 100: # Tone Data として取り込むには、ymf825.takeInToneData() を実行すればよいが、 # tone_num tone_name を取得して確認する tone_num = moji.asc2int(ymf825.rcv_buff[ymf825.TONE_HEADER:ymf825.TONE_HEADER +2]) print(f'tone_num = {tone_num}') ymf825.rcv_tone = [] for i in range(ymf825.UART_TONE_NUM): #42 ymf825.rcv_tone.append(moji.asc2int(ymf825.rcv_buff[ymf825.TONE_HEADER +i *2 : \ ymf825.TONE_HEADER +i *2 +2])) print(f'ymf825.rcv_tone = {ymf825.rcv_tone}') # tone_name の取得と更新 ymf825 1141行あたりの処理 tone_name = ymf825.rcv_buff[1:ymf825.TONE_HEADER].strip() # 末尾のスペースを取り除く print(f'Recive Data Tone Name = {tone_name}') else: print(f'len(ymf825.rcv_buff) = {len(ymf825.rcv_buff)} does not 100 bytes') tone_num = 0 tone_name ='' return len(ymf825.rcv_buff), tone_num, tone_name ######################################################################## # MicroPython Start ######################################################################## # カラー表示テスト ・・・ 不要であるが for i in range(len(lcd_color_list)): dspFill(lcd_color_list[i]) dspShow() utime.sleep_ms(100) # ToneNo 0-15 getTN = 0 putTN = 0 # 何も指定しなくても編集を開始できるように 0:GrandPiano を読み込んでおく # YMF825 Grand Piano Tone Data Set ymf825.copyTone2_Voice(getTN) # main-menu sub-menu の初期設定 tabM_num = 0 # MainTab 毎に SubTab があるので、管理しやすくするために # tabM_num 0, 1, 2, 3, 4 tabL = [0, 0, 0, 0, 0] tabS_num = tabL[tabM_num] # 直前の処理状況を edit_menu(tabM_num, tabS_num, processMsg) により画面表示する processMsg = 'Program Start' print(processMsg) # SD-CARD モジュール起動 # LCD TouchPanel と SPI が共通なのだが、特に切り替えをしなくても動作している spi = SPI(1, baudrate=60_000_000, sck=Pin(10), mosi=Pin(11), miso=Pin(12), \ polarity=0, phase=0) # from sdcard import SDCard ############################################################################ sd = SDCard(spi, Pin(22, Pin.OUT), 30_000_000) processMsg = 'sd = SDCard(spi, Pin(22, Pin.OUT), 30_000_000)' print(processMsg) # PicoM_YMF825Edit-136.py 以前では、 # Thonny による停止ボタン、起動ボタンでは2回目の起動で必ずエラーになる(タイムアウト) # 次のようにすることで回避できた。 # SD-Card 接続:vfs.mount(sd, '/sd') でエラーならば、vfs.umountを入れる。 # SD-Card 切断:vfs.umount('/sd') ではなく vfs.umount('/') を実行してから # SD-Card 接続:vfs.mount(sd, '/sd') すればよい。より確実と思われる方法にしてみた。 # vfs.mount() により、現在の接続情報が得られるが、リスト内にタプル形式で収納されている。 # print(f'vfs.mount() = {vfs.mount()}') print('command: mount_value = vfs.mount()') mount_value = vfs.mount() # mount_value は、リスト print('command: vfs_obl, mount_point = mount_value') vfs_obj, mount_point = mount_value[0] print(f'result: mount_point = {mount_point}') try: print("command: vfs.mount(sd, '/sd')") vfs.mount(sd, '/sd') except: print(f"Error On : vfs.mount(sd, '/sd')") print(f"command: vfs.umount({mount_point})") # vfs.umount('/sd') は NG vfs.umount('/') なら OK vfs.umount(mount_point) print("command: vfs.mount(sd, '/sd')") vfs.mount(sd, '/sd') print(f'vfs.mount() = {vfs.mount()}') # ############################################################################ ######################################## # main loop ######################################## while True: dsp_menu(tabM_num, tabL[tabM_num]) if tabM_num == tabGET: print(f'tab[GET] was selected') if tabL[tabM_num] == tabMR: print(f'tab[MR]:memory was selected') # getTN(Tone Number)選択 guide_msg1 = 'Get Tone Data' guide_msg2 = 'Select Tone Number' getTN_old = getTN # (btn, getTN) = select_ToneNumber(guide_msg, getTN_old) tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], btn, getTN) = \ select_ToneNumber(tabM_num, tabL[tabM_num], guide_msg1, guide_msg2, \ getTN_old, ymf825.ToneName) if getTN_old != getTN: print(f'Tone Number has changed') if btn == 'Execute': print(f'Execution Get Tone Data') ########################################################## # getTN で指定された YAMAHA の Tone Data を編集用にロードする ymf825.copyTone2_Voice(getTN) ########################################################## processMsg = 'Succeed Memory RD' print(processMsg) # main Menu 選択に移行 tabM_num = 0 elif btn == 'Cancel': processMsg = 'Cansel Memory RD' print(processMsg) getTN = getTN_old # subMenu をリセット tabL[tabM_num] = 0 # main Menu 選択に移行 tabM_num = 0 # tabM_num or tabL[tabM_num] was Changed else: print('Tab was Changed') getTN = getTN_old dspFillUpper(WHITE) elif tabL[tabM_num] == tabSD: print(f'tab[SD]:card was selected') # SD-CARD を読み込み ファイル一覧を表示、1つを選択してデータを読み込む # file_list を作成する file_list = sd_file_list("/sd") print() print(f'file_list = {file_list}') guide1 = 'Get Tone Data' guide2 = 'Select File Name' list_name = 'file name' # list を表示、 ひとつを選択する # btn, file_name = select_File(file_list, guide1, guide2, list_name) tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], btn, file_name) = \ select_Item(tabM_num, tabL[tabM_num], file_list, guide1, guide2, list_name) print() print(f'file_name = {file_name}') if btn == 'Execute': # 指定された file_name の file_path 生成 file_name = file_name.strip()+'.txt' file_path = '/sd/'+file_name print(f'file_path = {file_path}') if file_name not in os.listdir('/sd'): # 指定されたファイルがなければ? print('#################################################################') print(f'The file name ({file_name}) does not exist on the SD card.') print('#################################################################') # ありえないはず・・・何もしない else: # 指定のファイルがあれば SD-card の指定ファイルからデータ読み込む rcv_buff_size, tone_num, tone_name = readToneData_fromSD(file_path) if rcv_buff_size == 100: print('Tone Data') print(f'tone_num = {tone_num}') print(f'tone_name = {tone_name}') print(f'tone_data = {ymf825.rcv_tone}') # tone_number を指定して取り込む # getTN(Tone Number)選択 guide_msg1 = 'Get Tone Data' guide_msg2 = 'Select Tone Number' tone_num_old = tone_num # getTN_old = getTN # (btn, tone_num) = select_ToneNumber(guide_msg, tone_num_old) tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], btn, tone_num) = \ select_ToneNumber(tabM_num, tabL[tabM_num], guide_msg1, guide_msg2, \ tone_num_old, ymf825.ToneName) if tone_num_old != tone_num: print(f'Tone Number has changed') # Tone Number 変更 # ymf825.takeInToneData() では、ymf825.rcv_tone[0] から取り込まれる ymf825.rcv_tone[0] = tone_num print(f'ymf825.rcv_tone[0] = {ymf825.rcv_tone[0]}') if btn == 'Execute': print(f'Execution Get Tone Data') # 読み込もうとする場所の ToneVoice をシリアル出力(プリント) ymf825.serialOutVoiceData(getTN) ################### getTN = tone_num ################### # Tone Name 更新 ymf825.ToneName[getTN] = tone_name.strip() # 末尾のスペースを取り除く # rcv_tone から Tone Data へデータ変換 ymf825.takeInToneData() ########################################################## # getTN で指定された YAMAHA の Tone Data を編集用にロードする ymf825.copyTone2_Voice(getTN) ########################################################## # ToneVoice をシリアル出力(プリント) ymf825.serialOutVoiceData(getTN) processMsg = 'Succeed SDcard RD' print(processMsg) # DEBUG 書き込み具合を見たいとき # SDカードが見られる状態にするにはここで break # break # main Menu 選択に移行 tabM_num = 0 elif btn == 'Cancel': # select_ToneNumber で btn == 'Cancel' processMsg = 'Cansel SDcard RD' print(processMsg) # subMenu をリセット tabL[tabM_num] = 0 # main Menu 選択に移行 tabM_num = 0 # tabM_num or tabL[tabM_num] was Changed else: print('Tab was Changed') else: print('rcv_buff_size is not 100') processMsg = 'Not Tone Data' print(processMsg) elif btn == 'Cancel': processMsg = 'Cansel SDcard RD' print(processMsg) # subMenu をリセット tabL[tabM_num] = 0 # main Menu 選択に移行 tabM_num = 0 # tabM_num or tabL[tabM_num] was Changed else: print('Tab was Changed') dspFillUpper(WHITE) else: dspFillUpper(WHITE) tabS_num = 0 tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old]) = select_SubMenu(tabM_num, tabS_num) processMsg = '' ######################################################################## print(f'main Loop tab[GET] end ... gc.mem_alloc() = {gc.mem_alloc()}') ######################################################################## elif tabM_num == tabCOM: print(f'tab[COM] was selected') if tabL[tabM_num] == tabALG: print(f'tab[ALG] was selected') # ALG 選択 tabM_num_old = tabM_num print(f'ymf825.Voice_Com_Edit[ALG][CM1] = {ymf825.Voice_Com_Edit[ALG][CM1]}') (tabM_num, tabL[tabM_num_old], ymf825.Voice_Com_Edit[ALG][CM1]) = \ select_ALG(tabM_num, tabL[tabM_num], ymf825.Voice_Com_Edit[ALG][CM1], \ getTN, ymf825.ToneName[getTN]) processMsg = '[ALG] Edit end' print(processMsg) elif tabL[tabM_num] == tabBO or tabL[tabM_num] == tabLOF: print(f'tab[BO] or tab[LFO] was selected') # edit BO LFO tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], OP_num, ymf825.Voice_Com_Edit[BO][CM1],\ ymf825.Voice_Com_Edit[LFO][CM1]) = \ edit_bo(tabM_num, tabL[tabM_num], OP_num, ymf825.Voice_Com_Edit[BO][CM1], \ ymf825.Voice_Com_Edit[LFO][CM1], getTN, ymf825.ToneName[getTN]) processMsg = '[BO][LFO] Edit end' print(processMsg) else: dspFillUpper(WHITE) tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old]) = select_SubMenu(tabM_num, tabL[tabM_num]) processMsg = '' ######################################################################## print(f'main Loop tab[COM] end ... gc.mem_alloc() = {gc.mem_alloc()}') ######################################################################## elif tabM_num == tabOP: print(f'tab[OP] was selected') if tabL[tabM_num] == tabWS: print(f'tab[WS] was selected') # tabOP が選択されたら、選択されている getTN tone_name ALG を上段に表示する dsp_ALG_TN(ymf825.Voice_Com_Edit[ALG][CM1], OP_num, getTN, ymf825.ToneName[getTN]) # Select WS # OP_num は、変更されて返ってくることを前提にする OP_num_old = OP_num tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], OP_num, ymf825.Voice_Op_Edit[WS][OP_num_old]) = \ select_WS(tabM_num, tabL[tabM_num], OP_num, ymf825.Voice_Op_Edit[WS][OP_num]) processMsg = '[WS] Edit end' print(processMsg) elif tabL[tabM_num] == tabMUL: print(f'tab[MUL] was selected') # tabOP が選択されたら、選択されている getTN tone_name ALG を上段に表示する dsp_ALG_TN(ymf825.Voice_Com_Edit[ALG][CM1], OP_num, getTN, ymf825.ToneName[getTN]) # edit MUL DT FB XOF # OP_num は、変更されて返ってくることを前提にする OP_num_old = OP_num tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], OP_num, ymf825.Voice_Op_Edit[MUL][OP_num_old], \ ymf825.Voice_Op_Edit[DT][OP_num_old], ymf825.Voice_Op_Edit[FB][OP_num_old], \ ymf825.Voice_Op_Edit[XOF][OP_num_old]) = \ edit_mul(tabM_num, tabL[tabM_num], OP_num, ymf825.Voice_Op_Edit[MUL][OP_num], \ ymf825.Voice_Op_Edit[DT][OP_num], ymf825.Voice_Op_Edit[FB][OP_num], \ ymf825.Voice_Op_Edit[XOF][OP_num]) processMsg = '[MUL] Edit end' print(processMsg) elif tabL[tabM_num] == tabADSR: print(f'tab[ADSR] was selected') # tabOP が選択されたら、選択されている getTN tone_name ALG を上段に表示する dsp_ALG_TN(ymf825.Voice_Com_Edit[ALG][CM1], OP_num, getTN, ymf825.ToneName[getTN]) # WS 波形を表示する 上段・右下 z = [250, 180] color_back = GREEN color_wave = PURPLE wave_form(ymf825.Voice_Op_Edit[WS][OP_num], color_back, color_wave, z) # WS 波形番号を表示する text = f'WS{ymf825.Voice_Op_Edit[WS][OP_num]:>2}' zs = [250, 180 +36] chr_num = len(text) color_text = PURPLE dsp_text_bar(text, zs, chr_num, color_back, color_text) # edit ADSR 編集 # OP_num は、変更されて返ってくることを前提にする # 2025/11/03 ymf825.Voice_Op_Edit[MUL][OP_num] を追加、表示のみ OP_num_old = OP_num tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], OP_num, ymf825.Voice_Op_Edit[TL][OP_num_old], \ ymf825.Voice_Op_Edit[SL][OP_num_old], ymf825.Voice_Op_Edit[AR][OP_num_old], \ ymf825.Voice_Op_Edit[DR][OP_num_old], ymf825.Voice_Op_Edit[SR][OP_num_old], \ ymf825.Voice_Op_Edit[RR][OP_num_old], ymf825.Voice_Op_Edit[KSL][OP_num_old], \ ymf825.Voice_Op_Edit[KSR][OP_num_old]) = \ edit_adsr(tabM_num, tabL[tabM_num], OP_num, ymf825.Voice_Op_Edit[TL][OP_num], \ ymf825.Voice_Op_Edit[SL][OP_num], ymf825.Voice_Op_Edit[AR][OP_num], \ ymf825.Voice_Op_Edit[DR][OP_num], ymf825.Voice_Op_Edit[SR][OP_num], \ ymf825.Voice_Op_Edit[RR][OP_num], ymf825.Voice_Op_Edit[KSL][OP_num], \ ymf825.Voice_Op_Edit[KSR][OP_num], ymf825.Voice_Op_Edit[MUL][OP_num]) processMsg = '[ADSR] Edit end' print(processMsg) elif tabL[tabM_num] == tabDAM: print(f'tab[DAM] was selected') # tabOP が選択されたら、選択されている getTN tone_name ALG を上段に表示する dsp_ALG_TN(ymf825.Voice_Com_Edit[ALG][CM1], OP_num, getTN, ymf825.ToneName[getTN]) # edit DAM EAM DVB EVB 編集 # OP_num は、変更されて返ってくることを前提にする OP_num_old = OP_num tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], OP_num, ymf825.Voice_Op_Edit[DAM][OP_num_old], \ ymf825.Voice_Op_Edit[EAM][OP_num_old],ymf825.Voice_Op_Edit[DVB][OP_num_old], \ ymf825.Voice_Op_Edit[EVB][OP_num_old]) = \ edit_dam(tabM_num, tabL[tabM_num], OP_num, ymf825.Voice_Op_Edit[DAM][OP_num], \ ymf825.Voice_Op_Edit[EAM][OP_num], ymf825.Voice_Op_Edit[DVB][OP_num], \ ymf825.Voice_Op_Edit[EVB][OP_num]) processMsg = '[DAM] Edit end' print(processMsg) else: tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old]) = select_SubMenu(tabM_num, tabL[tabM_num]) processMsg = '' ######################################################################## print(f'main Loop tab[OP] end ... gc.mem_alloc() = {gc.mem_alloc()}') ######################################################################## elif tabM_num == tabPUT: print(f'tab[PUT] was selected') if tabL[tabM_num] == tabYMF: print(f"tab[YMF]:PicoSub's device YMF825 was selected") # getTN(Tone Number)選択 guide_msg1 = 'Get Tone Data' guide_msg2 = 'Select Tone Number' putTN = getTN putTN_old = putTN # (btn, putTN) = select_ToneNumber(guide_msg, putTN_old) tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], btn, putTN) = \ select_ToneNumber(tabM_num, tabL[tabM_num], guide_msg1, guide_msg2, \ putTN_old, ymf825.ToneName) if putTN_old != putTN: print(f'Tone Number has changed') if btn == 'Execute': print(f'Execution Put Tone Data') # ToneVoice をシリアル出力(プリント) ymf825.serialOutVoiceData(putTN) # 編集した内容を Tone Data に保存 ymf825.copyVoice2_Tone(putTN) #################################################################### # getTN で指定された Tone_Com_Edit と Tone_Op_Edit を # PicoSub の putTN で指定された Tone_Com_Edit と Tone_Op_Edit に保存する setToneDataPicoSub(getTN, putTN) #################################################################### processMsg = 'Succeed [YMF] WR' print(processMsg) # main Menu 選択に移行 tabM_num = 0 elif btn == 'Cancel': print('Cansel was selected') # subMenu をリセット tabL[tabM_num] = 0 # main Menu 選択に移行 tabM_num = 0 # tabM_num or tabL[tabM_num] was Changed else: print('Tab was Changed') dspFillUpper(WHITE) elif tabL[tabM_num] == tabSD: print(f'tab[SD]:card was selected') # SD-CARD に書き込む ファイル名は ymf825.ToneName に登録されているものを編集する。 # 15文字以下で、末尾の余白を取り除いたものにファイルの拡張子は「.txt」を付加する。 # file name 入力 loop = 1 # 正常処理が終われば flag_loop = 0 として書き込み動作に移る while loop == 1: digit_max = 15 # tone_name の最大桁数 exeName = ' OK ' tone_name = ymf825.ToneName[getTN] print(f'tone_name = {tone_name}') tone_name_old = tone_name tabM_num_old = tabM_num # (btn, tone_name) = edit_ToneName(tone_name_old, digit_max, exeName) (tabM_num, tabL[tabM_num_old], btn, tone_name) = \ edit_ToneName(tabM_num, tabL[tabM_num], tone_name_old, digit_max, exeName) if tone_name_old != tone_name: print(f'Tone Name has changed') print(f'tone_name = {tone_name}') if btn == exeName or btn == 'Enter': print(f'Execution Put Tone Data') # Tone Name 更新 ymf825.ToneName[getTN] = tone_name.strip() # 末尾のスペースを取り除く # ToneVoice をシリアル出力(プリント) ymf825.serialOutVoiceData(putTN) # 編集した内容を Tone Data に保存 ymf825.copyVoice2_Tone(putTN) # Tone_Com_Edit, Tone_Op_Edit を ASCII2文字の send_buffに変換 # puTNの指定(=Tone Number)の変更はなしとするので # ymf825.takeOutToneData2(getTN, putTN) は使用しない。 # Tone Number は、読み込むときに指定すれば良い。 ymf825.takeOutToneData(getTN) # file_name 生成 file_name = tone_name.strip()+'.txt' file_path = '/sd/'+file_name print(f'file_path = {file_path}') if file_name in os.listdir('/sd'): # 同じファイル名が存在すれば上書きするか確認のステップを入れる print('########################################################') print(f'The file name ({file_name}) exist on the SD card.') print('########################################################') # 上書きするか否か title = 'File name exists' guide1 = 'Want to OverWrite?' guide2 = 'Execute or Cancel' btn = select_CanExe(title, guide1, guide2) if btn == 'Execute': # while loop を抜けて書き込み実行 loop = 0 else: # file name = ToneName の再入力をするなら、while loop continue else: # 同じファイル名でなければ、while loop を抜けて書き込み実行 loop = 0 elif btn == 'Cancel': # edit_ToneName() で Cancel 選択ならば processMsg = 'Cansel SDcard WR' print(processMsg) print('Cansel was selected') # subMenu をリセット tabL[tabM_num] = 0 # main Menu 選択に移行 tabM_num = 0 break # tabM_num or tabL[tabM_num] was Changed else: print('Tab was Changed') dspFillUpper(WHITE) break else: # while loop の外である。loop == 0 でここに来る 書き込み実行 with open(file_path, 'w') as f: f.write(ymf825.send_buff) processMsg = 'Succeed SDcard WR' print(processMsg) # main Menu 選択に移行 tabM_num = 0 # DEBUG print("Files on filesystem:") print("====================") print_directory("/sd") file_list = sd_file_list("/sd") print() print(f'file_list = {file_list}') # DEBUG 書き込み具合を見たいとき・・・SDカードが見られる状態にするにはここで break # break # break ならば ここに来る edit_ToneName() で Cancel 選択ならば elif tabL[tabM_num] == tabMODE: print(f'tab[MODE]:YMF825 Play Mode was selected') # PicoSub YMF825 のMIDI再生モードを指定する。 # 15文字以下で、再生コマンドを用意して、PicoSub と共有する。 # mode_list にモードリストを用意しておき、選択して PicoSub に送る。 guide1 = 'Set Play Mode' guide2 = 'Select Play Mode' list_name = 'Play Mode' # list を表示、 ひとつを選択する # btn, play_mode = select_File(mode_list, guide1, guide2, list_name) tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], btn, play_mode) = \ select_Item(tabM_num, tabL[tabM_num], mode_list, guide1, guide2, list_name) print() print(f'play_mode = {play_mode}') if btn == 'Execute': # plat_mode が 'Swapping_Ch0>' ならば、末尾にスワッピング・チャンネルを追加 if 'Swap'.lower() in play_mode.lower() and 'Ch0>'.lower() in play_mode.lower(): # tone_number を指定して取り込む # getTN(Tone Number)選択 guide_msg1 = 'Select Swapping Ch' guide_msg2 = 'Ch0 Swap to Ch?' default_ch = 14 tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old], btn, sw_ch) = \ select_SwapCh(tabM_num, tabL[tabM_num], guide_msg1, guide_msg2, \ default_ch) if btn == 'Execute': print(f'Selected Swap Ch0 to Ch{sw_ch}') # 指定された play_mode を PicoSub への送信データに構成する # 先頭の '2' は Play Mode であることを示す # 先頭に '2' に続けて play_mode を入れ、末尾にスペースを入れて16桁にする # play_mode = '2' + moji.strFixL_n(play_mode, 15) play_mode += str(sw_ch) print(f'play_mode = {play_mode}') play_mode = f'2{play_mode:<15}' print(f'setPlayModePicoSub play_mode = {play_mode}') ####################################################### setPlayModePicoSub(play_mode) ####################################################### # main Menu 選択に移行 tabM_num = 0 elif btn == 'Cancel': # select_ToneNumber で btn == 'Cancel' processMsg = 'Cansel Play Mode Set' print(processMsg) # subMenu をリセット tabL[tabM_num] = 0 # main Menu 選択に移行 tabM_num = 0 # tabM_num or tabL[tabM_num] was Changed else: print('Tab was Changed') else: # 指定された play_mode を PicoSub への送信データに構成する # 先頭の '2' は Play Mode であることを示す # 先頭に '2' に続けて play_mode を入れ、末尾にスペースを入れて16桁にする # play_mode = '2' + moji.strFixL_n(play_mode, 15) play_mode = f'2{play_mode:<15}' print(f'setPlayModePicoSub play_mode = {play_mode}') ####################################################### setPlayModePicoSub(play_mode) ####################################################### processMsg = 'SucceedSetPlayMode' print(processMsg) # main Menu 選択に移行 tabM_num = 0 elif btn == 'Cancel': processMsg = 'Cansel SetPlayMode' print(processMsg) # subMenu をリセット tabL[tabM_num] = 0 # main Menu 選択に移行 tabM_num = 0 # tabM_num or tabL[tabM_num] was Changed else: print('Tab was Changed') dspFillUpper(WHITE) else: dspFillUpper(WHITE) tabS_num = 0 tabM_num_old = tabM_num (tabM_num, tabL[tabM_num_old]) = select_SubMenu(tabM_num, tabS_num) processMsg = '' ######################################################################## print(f'main Loop tab[PUT] end ... gc.mem_alloc() = {gc.mem_alloc()}') ######################################################################## else: print(f'Nothing Selected main-tab') print(f'Before tabM_num = {tabM_num} tabL[tabM_num] = {tabL[tabM_num]}') # while tabM_num == 0 or tabL[tabM_num] == 0: # tabM_num_old = tabM_num # (tabM_num, tabL[tabM_num_old]) = edit_menu(tabM_num, tabL[tabM_num], processMsg) # 20251023 Change tabM_num = select_MainMenu(tabM_num, processMsg) print(f'After tabM_num = {tabM_num} tabL[tabM_num] = {tabL[tabM_num]}') ######################################################################## print(f'main MicroPython while ... gc.mem_alloc() = {gc.mem_alloc()}') ######################################################################## processMsg = '' ``` ### tone_edit.py ``` ``` ### touch_lcd.py ``` ``` ### tab_cdn.py ``` ``` ### sdcard.py ``` ``` ### ymf825.py ``` ``` ### mojipy.py ``` ``` プログラム全体の見通しを良くするため「main.py」はできるだけ小さくして、タッチパネルを利用した音源パラメータ編集プログラムは、「tone_edit.py」に関数を集めモジュール化しました。 LCD表示やタッチパネル座標取得は「touch_lcd.py」、タブ表示やタブ座標などを管理する「tab_cdn.py」、SD-Cardへの読み書きを担当する「sdcard.py」などもモジュールとして「lib」フォルダ下にします。「tone_edit.py」は200KBを超える大きさになってしまいました。 ## PicoSub側のプログラム PicoSubプログラムは、次のプログラムで構成されています。 ### main.py ``` ``` ### ymf825.py ``` ``` ### mojipy.py ``` ``` このプログラムの説明は本題と外れますので省きます。 ## モジュールの紹介 ### 「touch_lcd.py」の改良について 「touch_lcd.py」は、LCD表示やタッチパネルからの入力を行うプログラムで、Waveshare社のサイトで提供されているサンプルプログラムmain_3inch5.pyを改変して「touch_lcd.py」としました。主に、「tone_edit.py」が利用します。 LCDの表示は4方向に指定できますが、表示を回転した時にも、表示座標(X軸・Y軸)とタッチ座標(X軸・Y軸)の方向が合致するように変更しました。表示位置のPixelと同様に扱えるようにしました。 タッチパネルのタッチ位置を表示位置に合わせて調整できるよう用のプログラムを内蔵・・・4隅にポイントを表示したところをタッチして、表示した結果でプログラムの定数を変更します。変更は自動ではなく、Thonyyでプログラムを書き換えが必要です。 ### 「ymf825.py」について 「ymf825.py」は、FM音源IC YMF825を制御するための音源パラメータや制御コマンドを収納したモジュールです。 このモジュールは、Shunsuke Ohira様が作成されたプログラムやYAMAHAのC言語ライブラリなどを参考に作成しました。 ### 「moji.py」について 「moji.py」は、①1バイトの整数を2バイトのASCII文字に変換 ②2バイト以上の0-9 a-f or A-F表記のASCII文字を整数に変換 するためのモジュールです。シリアル通信するときのデータのパックやアンパックに利用していますが、bytearrayの使い方がよく分かっていなかった時に作ったモジュールです。bytearrayをうまく使えば不要にできると思いますがプログラムを書き換えるのが面倒なのでそのままにしています。 # プログラムの動かし方(読み飛ばし可) PicoでMicroPythonプログラムを動かす方法は、①Thonnyで開いているプログラムを実行させる。②Picoにmain.pyの名前で書き込んで実行する。の2つの方法があります。 どちらにしてもmain.pyプログラム以外のモジュールである「touch_lcd.py」や「tone_edit.py」などは、予めPicoに書き込んでおく必要があります。 # YMF825パラメータの紹介(読み飛ばし可) ご参考に編集対象である、YMF825のパラメータについて紹介します YMF825では、16種類の音源を設定できます。 各音源の音色は、ベースの高さ(BO)、音の揺れ(LFO)、アルゴリズム(ALG)の3つに、17種類のパラメータをもつ4オペレータ(68パラメータ)を加えた全71パラメータで決定します。 4オペレータには、SR、XOF、KSR、RR、DR、AR、SL、TL、KSL、DAM、EAM、DVB、EVB、MUL、DT、WS、FBの17要素があります。 例えば、YAMAHAが用意しているTone Number 11 NylonGuiter は、次のようなパラメータになっています。 Parameter,Max,Now BO, 3, 0 LFO, 3, 2 ARG, 7, 5 Parameter,Max,op1,op2,op3,op4 SR, 15, 1, 3, 5, 4 XOF, 1, 0, 0, 0, 0 KSR, 1, 1, 0, 1, 0 RR, 15, 4, 7, 5, 9 DR, 15, 1, 3, 5, 4 AR, 15, 14, 15, 11, 13 SL, 15, 8, 15, 4, 15 TL, 63, 21, 0, 14, 13 KSL, 3, 1, 0, 0, 2 DAM, 3, 0, 0, 0, 0 EAM, 1, 0, 0, 0, 0 DVB, 3, 0, 0, 0, 1 EVB, 1, 0, 1, 0, 1 MUL, 15, 1, 1, 3, 1 DT, 7, 0, 0, 0, 0 WS, 31, 0, 0, 0, 0 FB, 7, 6, 0, 0, 0 Maxは、項目別に設定できる最大値です。 # 編集画面の紹介 YMF825では、16の音源毎に共通項である3要素(BO、LFO、ALG)の編集画面と、4オペレータ17要素(SR、XOF、KSR、RR、DR、AR、SL、TL、KSL、DAM、EAM、DVB、EVB、MUL、DT、WS、FB)について編集する必要があります。 以下、編集画面を紹介します。 編集の操作状況は、Facebookに紹介動画があります。 Facebookに紹介動画があります。(Facebookへのログインが必要です) https://www.facebook.com/toshikazu.hyodo/videos/1216092900384973?idorvanity=247733226639836 ## ALG選択画面 Image CIMG9075.JPG ALG(Algorithm):アルゴリズムは、4つのオペレータをどのような組み合わせるかの選択ですが、YMF825の場合、7つのアルゴリズムがありますので、組み合わせのイメージを表示して、その番号部分をタッチして選択できるようにしました。 ## BO LFO選択画面 Image CIMG9076.JPG 音源のベースの高さ(BO)を設定します。通常は「0」ですが、低い方に1オクターブ、2オクターブ、3オクターブを選択できるようになっています。 音の揺れ(LFO)は、オシレータの周波数を選択できます。4つのオペレータのDAM、EAM、DVB、EVBと関係があると思います。 WS選択画面 Image CIMG9077.JPG WS(Wave Shapes):音源の波形の選択では、29の波形をイメージ表示してそれをタッチ選択するようにしました。 ## ADSR編集画面 Image CIMG9086.JPG ADSR画面では、AR、DR、SR、RR、TL、SL、KSL、KSRを、1つの画面で編集しようとするものです。 これらの要素は、4つのオペレータごとに設定する必要がありますので、画面上半分にある「OP1」~「OP4」で、どのオペレータに対する編集かを選択する必要があります。 WSとMULは関連が深いので参考に表示しています。 グラフの偏移点に紫色で四角のポイントが4つあります。ここにタッチペンを当てると大きなポイント表示となり、そのポイントを緑色の直線状、または、黄色の四角や緑色の四角の範囲内でスライドさせることで値を設定できます。 ARとTL、DRとSRについては相互に関連していますので、黄色の四角と緑色の四角の範囲内で両方を同時に設定できるようにしています。 KSL、KSRは、段階的な設定値を選ぶようになっています。設定値の場所をタッチするとその設定に切り替わり背景色と文字色が変わります。 ## MUL DT FB XOF編集画面 Image CIMG9081.JPG この画面では、MUL、DT、FB、XOFを設定する画面です。 これらの要素は、4つのオペレータごとに設定する必要がありますので、画面上半分にある「OP1」~「OP4」でオペレータを選択する必要があります。 WSとMULは関連が深いので参考に表示しています。 MULとDTは、緑の直線状に紫色で四角のポイントがあります。ここにタッチペンを当てると大きなポイント表示となり、そのポイントを緑色の直線状にスライドさせることで値を設定できます。 FB、XOFは、段階的な設定値を選ぶようになっています。設定値の場所をタッチするとその設定に切り替わり背景色と文字色が変わります。 ## DAM、EAM、DVB、EVB、編集画面 Image CIMG9089.JPG DAM、EAM、DVB、EVBの選択画面です。 LFOの設定と関係があるようです。 # おまけ 他人が作ったプログラムを見る(読む)には大変です。特に素人の私が作った長く絡まりまくったスパゲッティのようなプログラムを見るのは苦行だと思います。 そこで最後までご覧いた方に、おまけを用意しました。 MUL、DT、FB、XOFの設定部分だけをコンパクトにまとめたプログラムです。 TestEditMul_main.py このプログラムの実行には touch_lcd.py が必要です。touch_lcd.pyは既出です。 Pico-ResTouch-LCD-3.5の背面にある40PinのPico用のソケットにPimoroni Pico Plus2 または Raspberry Pi Pico2を装着するだけで、PicoSubとの接続は無しで編集画面だけの確認ができます。 何かの参考になれば幸いです。 CIMG9113_4-3コメント.jpg