2023/1/23
CCMapper改版:
code refined
2023/1/18+
CCMapper改版:
glide/portamento support
2023/1/15+
初版
CCMapper(RtMidi/python3)をFletライブラリを利用して、GUI付CCMapperに改良した。 Fletライブラリはクロスプラットフォーム対応なので、window/mac/linuxで動作する。
本機能は、[WIDI Bud Pro]経由でMIDIデータをリアルタイムでCC#2またはCC#11を受信して、CC#を任意のもの(複数、AT、PPを含む)に変更して音源に送信する。Pitch_Bendは,そのまま接続されている音源に送られる。
1.Windowsの場合、
仮想MIDIデバイスとして、loopMIDIがインストールされている必要がある。
参照:loopMIDI
MIDI信号の流れとしては以下のようになる:
[wind_controler(re.corder/Elfue etc)]→[WIDI Bud Pro]→(CCMapper)→[loopMIDI]→ [PC音源]
2.Macの場合
仮想MIDIデバイスとして、IACドライバを設定する。名前はWindowsに合わせて「loopMIDI」とする。
参照:MacのAudio MIDI設定でMIDI情報をアプリケーション間で送信する
MIDI信号の流れとしては以下のようになる:
[wind_controler(re.corder/Elfue etc)]→(CCMapper)→[loopMIDI(IAC)]→ [PC音源]
3.linuxの場合
仮想MIDIデバイスとして、既存の[Midi Through port]を利用する。
MIDI信号の流れとしては以下のようになる:
[wind_controler(re.corder/Elfue etc)]→[WIDI Bud Pro]→(CCMapper)→[Midi Through Port]→ [PC音源]
python-rtmidiのインストールから説明する:
1.windowsの場合
以下を実行してインストールする:
# scoopで必要なコマンドがインストール済みの前提
基本的には以下の方法であるが、エラーが出たので一部変更している:
https://spotlightkid.github.io/python-rtmidi/install-windows.html
python -m pip install -U virtualenv
python.exe -m pip install --upgrade pip
python -m virtualenv rtmidi
rtmidi\Scripts\activate
ここで、仮想環境rtmidiにはいる
pip install -U pip setuptools
pip download python-rtmidi
tar -xzf python-rtmidi-1.4.9.tar.gz
cd python-rtmidi-1.4.9
python setup.py install
# fletのインストール
pip install flet
仮想環境に該当ライブラリをインストールしたので、 rtmidi/fletライブラリを使用する場合は、 以下を実行して仮想環境に入ること。
.\rtmidi\Scripts\activate
2.Macの場合
以下を実行してインストールする:
# brewでpython3がインストール済みの前提
pip install python-rtmidi
pip install flet
実際の実行にあたり、python3は、コマンド名として、Macではpython3になっているので、 以下のように実行すること
(これに対して、linux/windowsの場合、python)
python3 flet_CCMapper.py
3.linuxの場合
以下を実行してライブラリをインストールする:
# Flet実行時にエラーなったので、それを回避するために、
# pythonの仮想環境を設定している
sudo apt update
sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgs
# GStreamerをインストールしている
# pythonの仮想環境fletを設定する
python -m venv flet
. ./flet/bin/activate
sudo apt install python3-rtmidi
# pythonでありがちなpipでないので注意のこと。
pip install flet
仮想環境に該当ライブラリをインストールしたので、 fletライブラリを使用する場合は、 以下を実行して仮想環境に入ること。
. ./flet/bin/activate
以下のコマンドラインで実行する:
windows/linuxの場合
# python仮想環境に入る
python flet_CCMapper.py
macの場合
python3 flet_CCMapper.py
以下、実行画面:
CCMapperのプログラムとしては以下を使用する:
Flet_CCMapper.py
#!/usr/bin/env python
#
# flet_CCMapper_RtMidi.py
#
# written by: xshige
# 2023/1/22: code refined
# 2023/1/20: compare order is changed to reduce overhead
# 2023/1/19: Hires Messages are ignored
# 2023/1/18: glide/portamento support
# 2023/1/16: added device name(USB-MIDI) for YDS-150
# 2023/1/15: 1st version
# related URLs
# https://github.com/flet-dev/flet
# https://github.com/SpotlightKid/python-rtmidi
# https://veliugurguney.com/blog/post/real_time_midi_in_python_using_sched_and_rtmidi
# https://courses.ideate.cmu.edu/16-375/f2021/text/code/midi-examples.html
import os
import sys
import flet as ft
from rtmidi.midiconstants import (ALL_NOTES_OFF, ALL_SOUND_OFF, BALANCE, BANK_SELECT_LSB,
BANK_SELECT_MSB, BREATH_CONTROLLER, CHANNEL_PRESSURE,
CHANNEL_VOLUME, CONTROL_CHANGE, DATA_ENTRY_LSB, DATA_ENTRY_MSB,
END_OF_EXCLUSIVE, EXPRESSION_CONTROLLER, FOOT_CONTROLLER,
LOCAL_CONTROL, MIDI_TIME_CODE, MODULATION, NOTE_OFF, NOTE_ON,
NRPN_LSB, NRPN_MSB, PAN, PITCH_BEND, POLY_PRESSURE,
PROGRAM_CHANGE, RESET_ALL_CONTROLLERS, RPN_LSB, RPN_MSB,
SONG_POSITION_POINTER, SONG_SELECT, TIMING_CLOCK)
from rtmidi import MidiIn
from rtmidi import MidiOut
#--------------------------------------------------------------------------
def midiin_callback(event, data=None):
global curNote
message, deltatime = event
if message[0] & 0xF0 == NOTE_OFF: # NoteOff is FIRST PRIORITY!
status, note, velocity = message
channel = (status & 0xF) + 1
print("NoteOff note:%d velocity:%d" % (note, velocity))
#message[1] = curNote # 2023/1/22 REMOVED
if (XFER.value): midiout.send_message(message)
"""
if (XFER.value):
# experimental code
# All Sound Off
msg = [CONTROL_CHANGE + channel - 1]
msg.append(120)
msg.append(0)
midiout.send_message(msg)
"""
return
elif message[0] & 0xF0 == NOTE_ON:
status, note, velocity = message
channel = (status & 0xF) + 1
print("NoteOn note:%d velocity:%d" % (note, velocity))
if (velocity == 0 and XFER.value):
# change NoteOn to NoteOff (patch 2023/1/15 for NuRad)
message[0] = NOTE_OFF + channel - 1
transpose = int(trans.value)
curNote = note+transpose
message[1] = curNote
midiout.send_message(message)
return
transpose = int(trans.value)
curNote = note+transpose
message[1] = curNote
if (VFIX.value): message[2] = int(vel.value)
if (XFER.value): midiout.send_message(message)
return
elif message[0] & 0xF0 == CONTROL_CHANGE:
status, control, value = message
channel = (status & 0xF) + 1
print("CONTROL_CHANGE control:%d value:%d" % (control, value))
if ((control == 2)|(control == 11)):
message[1] = 1
if (CC1.value): midiout.send_message(message)
message[1] = 2
if (CC2.value): midiout.send_message(message)
message[1] = 7
if (CC7.value): midiout.send_message(message)
message[1] = 11
if (CC11.value): midiout.send_message(message)
message[1] = 74
if (CC74.value): midiout.send_message(message)
message[1] = 26
if (CC26.value): midiout.send_message(message)
# AT
msg2 = [CHANNEL_PRESSURE + channel - 1]
msg2.append(value & 0x7F)
if (AT.value): midiout.send_message(msg2)
# PP
msg2 = [POLY_PRESSURE + channel - 1]
msg2.append(curNote & 0x7F)
msg2.append(value & 0x7F)
if (PP.value): midiout.send_message(msg2)
return
# the following CC ignore(not sent)
if (control == 102): return # EWI5000
if (control == 103): return # EWI5000
if (control == 123): return # EWI5000 (All Note Off)
if (control == 7): return # EWI5000
if (control == 68): return # EWI5000
if (control == 74): return # EWI5000
if (control == 34): return # CC#34(Hires/Breath Controler) ignored
if (control == 39): return # CC#39(Hires/Volume) ignored
if (control == 43): return # CC#43(Hires/Expression) ignored
if (control == 88): return # CC#88(Hires/NoteOn) ignored
# Incomming Modulation Wheel
if (control == 1):
if (not CC1.value): midiout.send_message(message)
return
# portamento/glide support
if (control == 65): # portamento on/off
if (PORTA.value): midiout.send_message(message)
return
if (control == 5): # portamento/glide time
if (PORTA.value): midiout.send_message(message)
return
if (control == 84): # portamento control
if (PORTA.value): midiout.send_message(message)
return
if (control == 104): # Legato Time (EWI5000)
if (PORTA.value): midiout.send_message(message)
return
#----------------------
else: # other CC
# other messsage
if (OTHER.value):
midiout.send_message(message)
print("********** unexpected CC **********")
print(hex(message[0]),message[1],message[2])
return
#--------------------------------------------------------
elif message[0] & 0xF0 == CHANNEL_PRESSURE:
if (not AT.value): midiout.send_message(message)
value = message[1] # bug fix 2023/1/12
channel = (status & 0xF) + 1
print("AT(CHANNEL_PRESSURE) value:%d" % (value))
elif message[0] & 0xF0 == POLY_PRESSURE:
if (not PP.value): midiout.send_message(message)
status, note, value = message
channel = (status & 0xF) + 1
print("PP(POLY_PRESSURE) note:%d value:%d" % (note, value))
elif message[0] & 0xF0 == PITCH_BEND:
if (PB.value): midiout.send_message(message)
int14 = message[2] # 2nd byte
int14 = (int14<<7)
int14 = (int14|(message[1]&0x7F))
print("PITCH_BEND value:%d" % (int14))
return
else:
# other messsage
if (OTHER.value):
midiout.send_message(message)
print("********** unexpected message **********")
print(hex(message[0]),message[1],message[2])
#---------------------------------------------------------
def panic(e):
channel = 1
# All Sound Off
msg = [CONTROL_CHANGE + channel - 1]
msg.append(120)
msg.append(0)
midiout.send_message(msg)
# All Note Off
msg = [CONTROL_CHANGE + channel - 1]
msg.append(123)
msg.append(0)
midiout.send_message(msg)
#---------------------------------------------------------
def main(page):
global midiout
ver = "2023/1/22" # version
# flet related variable
global CC1,CC2,CC7,CC11,CC74,CC26,AT,PP,PB,PORTA
global XFER,OTHER,VFIX
global trans,vel
print(ver)
print("Running at: "+page.platform)
print(ft.get_platform()) # you also can use it
# the following device will be opened
ID0 = "WIDI Bud Pro" # for Windows/Linux
ID1 = "Elefue" # for Mac
ID2 = "re.corder" # for Mac
ID3 = "Nu" # for Mac (with WIDI Master/NuRAD,NuEVI)
ID4 = "EWI" # for Mac (with WIDI Master/EWI5000,EWI4000,EVI3010 etc)
ID5 = "AE-" # for Mac (Roland)
ID6 = "YDS-" # for Mac (YAMAHA)
ID7 = "Saxophone" # for USB-MIDI (YAMAHA)
OD0 = "loopMIDI" # for Windows/Mac
OD1 = "Midi Throu" # for Linux
# OD = "SE-0" #
# search OUTPUT/INPUT device
outdev = -1
midiout = MidiOut()
for n, outport in enumerate(midiout.get_ports()):
if (OD0 in outport):
outdev = n
break
if (OD1 in outport):
outdev = n
break
#print(outdev)
indev = -1
midiin = MidiIn()
for n, inport in enumerate(midiin.get_ports()):
#print(n,inport)
if (ID0 in inport):
indev = n
break
if (ID1 in inport):
indev = n
break
if (ID2 in inport):
inndev = n
break
if (ID3 in inport):
indev = n
break
if (ID4 in inport):
indev = n
break
if (ID5 in inport):
indev = n
break
if (ID6 in inport):
indev = n
break
if (ID7 in inport):
indev = n
break
#print(indev)
# end of search OUTPUT/INPUT device
if (outdev == -1):
print("**** output device not found! *****")
sys.exit(-1)
midiout.open_port(outdev) # loopMIDI for windows/mac, Midi Through for linux
print("output port: '%s'" % outport)
if (indev == -1):
print("**** input device not found! *****")
sys.exit(-1)
midiin.open_port(indev) # WIDI Bud Pro for linux/windows, dev name for mac
print("input port: '%s'" % inport)
midiin.set_callback(midiin_callback)
#-----------------------------------------------------
page.appbar = ft.AppBar(
title=ft.Text("CCMapper(RtMidi/python3 with Flet)", color=ft.colors.WHITE), # title of the AppBar, with a white color
center_title=True, # we center the title
bgcolor=ft.colors.BLUE, # a color for the AppBar's background
)
page.title = "CCMapper(RtMidi/python3)"
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.bgcolor = "#f5f5f5"
page.padding = 15
page.scroll = "adaptive"
page.window_width = 600 # window's width is 200 px
page.window_height = 700 # window's height is 200 px
page.window_resizable = True # window is resizable
page.theme_mode = "light" # "system" #"light" #"dark"
def octupClicked(e):
trans.value = str(int(trans.value) +12)
page.update()
def octdwnClicked(e):
trans.value = str(int(trans.value) -12)
page.update()
def semiupClicked(e):
trans.value = str(int(trans.value) +1)
page.update()
def semidwnClicked(e):
trans.value = str(int(trans.value) -1)
page.update()
def velupClicked(e):
vel.value = str(int(vel.value) +1)
page.update()
def veldwnClicked(e):
vel.value = str(int(vel.value) -1)
page.update()
# buttons
octup = ft.ElevatedButton(
"Octave Up",
height=30,
bgcolor=ft.colors.BLUE,
color=ft.colors.WHITE,
on_click=octupClicked)
octdwn = ft.ElevatedButton(
"Octave Down",
height=30,
bgcolor=ft.colors.BLUE,
color=ft.colors.WHITE,
on_click=octdwnClicked)
semiup = ft.ElevatedButton(
"+",
height=30,
width=40,
bgcolor=ft.colors.BLUE,
color=ft.colors.WHITE,
on_click=semiupClicked)
semidwn = ft.ElevatedButton(
"-",
height=30,
width=40,
bgcolor=ft.colors.BLUE,
color=ft.colors.WHITE,
on_click=semidwnClicked)
velup = ft.ElevatedButton(
"+",
height=30,
width=40,
bgcolor=ft.colors.BLUE,
color=ft.colors.WHITE,
on_click=velupClicked)
veldwn = ft.ElevatedButton(
"-",
height=30,
width=40,
bgcolor=ft.colors.BLUE,
color=ft.colors.WHITE,
on_click=veldwnClicked)
#velup = ft.FilledTonalButton(text="+",on_click=velupClicked)
#veldwn = ft.FilledTonalButton(text="-",on_click=veldwnClicked)
#ttl = ft.Text("CCMapper(RtMidi/python3 with Flet)",weight="bold")
outdev = ft.Text("Output port: '"+outport+"'",weight="bold")
indev = ft.Text("Input port: '"+inport+"'",weight="bold")
htrans = ft.Text("trans: ",weight="bold")
endApp = ft.Text("end of CCMapper",weight="bold")
trans = ft.TextField(value="0", text_align="right", width=60, height=25) #15)
vel = ft.TextField(value="77", text_align="right", width=60, height=25) #15)
HLINE = ft.Text("-----------------------------------------------------------------------------------")
#-------------------------------------
PANIC = ft.ElevatedButton(height=30,width=80,bgcolor="RED",color=ft.colors.WHITE,
text="PANIC",on_click=panic)
#-------------------------------------
OTHER = ft.Switch(
#label="Other msg transfer",
#label_position=ft.LabelPosition.LEFT,
value = False,
active_color="GREEN"
)
OTHERL = ft.Text("Other msg transfer",weight="bold")
#-------------------------------------
XFER = ft.Switch(
#label="NoteOn/NoteOff transfer",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
XFERL = ft.Text("NoteOn/NoteOff transfer",weight="bold")
#-------------------------------------
CC1 = ft.Switch(
#label="CC1(Modulation Wheel)",
#label_position=ft.LabelPosition.LEFT,
value = False,
active_color="GREEN"
)
CC1L = ft.Text("CC1(Modulation Wheel)",weight="bold")
CC2 = ft.Switch(
#label="CC2(Breath Control)",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
CC2L = ft.Text("CC2(Breath Control)",weight="bold")
CC7 = ft.Switch(
#label="CC7(Volume)",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
CC7L = ft.Text("CC7(Volume)",weight="bold")
CC11 = ft.Switch(
#label="CC11(Expression)",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
CC11L = ft.Text("CC11(Expression)",weight="bold")
CC74 = ft.Switch(
#label="CC74(Cutoff Frequency)",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
CC74L = ft.Text("CC74(Cutoff Frequency)",weight="bold")
CC26 = ft.Switch(
#label="CC26(EqGain)",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
CC26L = ft.Text("CC26(EqGain)",weight="bold")
AT = ft.Switch(
#label="AT(Channel Pressure)",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
ATL = ft.Text("AT(Channel Pressure)",weight="bold")
PP = ft.Switch(
#label="PP(Polyphonic Key Pressure)",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
PPL = ft.Text("PP(Polyphonic Key Pressure)",weight="bold")
#-------------------------------------
VFIX = ft.Switch(
#label="Velocity Fixed",
#label_position=ft.LabelPosition.LEFT,
value = False,
active_color="GREEN"
)
VFIXL = ft.Text("Velocity Fixed",weight="bold")
#-------------------------------------
PB = ft.Switch(
#label="PitchBend Thru",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
PBL = ft.Text("PitchBend Thru",weight="bold")
PORTA = ft.Switch(
#label="Poartamento(Glid) Thru",
#label_position=ft.LabelPosition.LEFT,
value = True,
active_color="GREEN"
)
PORTAL = ft.Text("Portamento(Glide) Thru",weight="bold")
#-------------------------------------
page.add(
#ttl,
#ft.Row([outdev,indev]),
outdev,indev,
HLINE,
PANIC,
ft.Row([XFER,XFERL,OTHER,OTHERL]),
HLINE,
#ft.Row([htrans,trans,octup,octdwn,semiup,semidwn],alignment=ft.MainAxisAlignment.CENTER,),
ft.Row([htrans,trans,octup,octdwn,semiup,semidwn]),
HLINE,
ft.Row([CC1,CC1L]),
ft.Row([CC2,CC2L]),
ft.Row([CC7,CC7L]),
ft.Row([CC11,CC11L]),
ft.Row([CC74,CC74L]),
ft.Row([CC26,CC26L]),
HLINE,
ft.Row([AT,ATL,PP,PPL]),
HLINE,
ft.Row([VFIX,VFIXL,vel,velup,veldwn]),
HLINE,
ft.Row([PB,PBL,PORTA,PORTAL]),
endApp)
#ft.app(target=main, view=ft.WEB_BROWSER)
ft.app(target=main)
#=====================================-
CCMapperを起動したら、入出力MIDIデバイスは自動的に設定されるので
次に音源を立ち上げて、そのままwind_controlerで吹くと音が出る。
Macの場合、WIDI_Bud経由でないので、あらかじめ、bluetooth対応controlerを接続(ペアリング)しておく必要がある。
また、USB-MIDI対応controlerの場合、PC/Macに接続すると自動認識して、そのまま使用できる。
なお、色々な音源で必要と思われるCC#を有効にしているので、linuxでは以下の音源が、そのまま利用できる。
ファームウェアのバージョンで、異なる可能性があるが、現在、所有のNuRadの場合、(バグか仕様かは不明だが)NoteOffの代わりにベロシティがゼロのNoteOnを送っているようだ。今のところ実害はないがCCMapperでベロシティがゼロのNoteOnは、NoteOffに変換して転送している。
上のGUI有り版だとVitalのようにソフト音源の処理が重い場合、
GUIのオーバーヘッドが気になるときがあるので
以下にGUI無し版も用意した:
# GUI無しでもオーバーヘッドが気になる場合は、
# C#実装のCCMapperのほうがオーバーヘッドが小さいので、それを使用する、
ad_CCMapper_RtMidi.py
#!/usr/bin/env python
#
# ad_CCMapper_RtMidi.py
# (simple version: no flags)
#
# written by: xshige
# 2023/1 /22: added ad(Auto Device select), code refined
# 2022/12/19: 1st version
import sys
import time
from rtmidi.midiconstants import (ALL_NOTES_OFF, ALL_SOUND_OFF, BALANCE, BANK_SELECT_LSB,
BANK_SELECT_MSB, BREATH_CONTROLLER, CHANNEL_PRESSURE,
CHANNEL_VOLUME, CONTROL_CHANGE, DATA_ENTRY_LSB, DATA_ENTRY_MSB,
END_OF_EXCLUSIVE, EXPRESSION_CONTROLLER, FOOT_CONTROLLER,
LOCAL_CONTROL, MIDI_TIME_CODE, MODULATION, NOTE_OFF, NOTE_ON,
NRPN_LSB, NRPN_MSB, PAN, PITCH_BEND, POLY_PRESSURE,
PROGRAM_CHANGE, RESET_ALL_CONTROLLERS, RPN_LSB, RPN_MSB,
SONG_POSITION_POINTER, SONG_SELECT, TIMING_CLOCK)
#from rtmidi.midiutil import open_midioutput
#from rtmidi.midiutil import open_midiinput
from rtmidi import MidiIn
from rtmidi import MidiOut
def midiin_callback(event, data=None):
global curNote
message, deltatime = event
if message[0] & 0xF0 == NOTE_OFF: # NoteOff is FIRST PRIORITY!
status, note, velocity = message
channel = (status & 0xF) + 1
print("NoteOff note:%d velocity:%d" % (note, velocity))
#message[1] = curNote # 2023/1/22 REMOVED
midiout.send_message(message)
"""
# experimental code
# All Sound Off
msg = [CONTROL_CHANGE + channel - 1]
msg.append(120)
msg.append(0)
midiout.send_message(msg)
"""
elif message[0] & 0xF0 == NOTE_ON:
status, note, velocity = message
channel = (status & 0xF) + 1
print("NoteOn note:%d velocity:%d" % (note, velocity))
if (velocity == 0):
# change NoteOn to NoteOff (patch 2023/1/15 for NuRad)
message[0] = NOTE_OFF + channel - 1
#transpose = int(trans.value)
#curNote = note+transpose
#message[1] = curNote
midiout.send_message(message)
return
curNote = note
midiout.send_message(message)
elif message[0] & 0xF0 == CONTROL_CHANGE:
status, control, value = message
channel = (status & 0xF) + 1
print("CONTROL_CHANGE control:%d value:%d" % (control, value))
if ((control == 2)|(control == 11)):
#message[1] = 1
#midiout.send_message(message)
message[1] = 2
midiout.send_message(message)
message[1] = 7
midiout.send_message(message)
message[1] = 11
midiout.send_message(message)
message[1] = 74
midiout.send_message(message)
message[1] = 26
midiout.send_message(message)
# AT
msg2 = [CHANNEL_PRESSURE + channel - 1]
msg2.append(value & 0x7F)
midiout.send_message(msg2)
# PP
msg2 = [POLY_PRESSURE + channel - 1]
msg2.append(curNote & 0x7F)
msg2.append(value & 0x7F)
midiout.send_message(msg2)
"""
if (value == 0):
# PATCH All Sound Off (2023/1/21)
msg2 = [CONTROL_CHANGE + channel - 1]
msg2.append(120)
msg2.append(0)
midiout.send_message(msg2)
return
"""
return
# the following CC ignore(not sent)
if (control == 102): return # EWI5000
if (control == 103): return # EWI5000
if (control == 123): return # EWI5000 (All Note Off)
if (control == 7): return # EWI5000
if (control == 68): return # EWI5000
if (control == 74): return # EWI5000
if (control == 34): return # CC#34(Hires/Breath Controler) ignored
if (control == 39): return # CC#39(Hires/Volume) ignored
if (control == 43): return # CC#43(Hires/Expression) ignored
if (control == 88): return # CC#88(Hires/NoteOn) ignored
# Incomming Modulation Wheel
if (control == 1):
#if (not CC1.value): midiout.send_message(message)
midiout.send_message(message)
return
# portamento/glide support
if (control == 65): # portamento on/off
#if (PORTA.value): midiout.send_message(message)
midiout.send_message(message)
return
if (control == 5): # portamento/glide time
#if (PORTA.value): midiout.send_message(message)
midiout.send_message(message)
return
if (control == 84): # portamento control
#if (PORTA.value): midiout.send_message(message)
midiout.send_message(message)
return
if (control == 104): # Legato Time (EWI5000)
#if (PORTA.value): midiout.send_message(message)
midiout.send_message(message)
return
#----------------------
else:
# other CC messsage
midiout.send_message(message)
print("********** unexpected CC **********")
print(hex(message[0]),message[1],message[2])
return
#if (OTHER.value):
# midiout.send_message(message)
# print("********** unexpected CC **********")
# print(hex(message[0]),message[1],message[3])
# return
#--------------------------------------------------------
elif message[0] & 0xF0 == CHANNEL_PRESSURE:
value = message[0]
channel = (status & 0xF) + 1
print("AT(CHANNEL_PRESSURE) value:%d" % (value))
elif message[0] & 0xF0 == POLY_PRESSURE:
status, note, value = message
channel = (status & 0xF) + 1
print("PP(POLY_PRESSURE) note:%d value:%d" % (note, value))
elif message[0] & 0xF0 == PITCH_BEND:
value = message[0]
print("PITCH_BEND value:%d" % (value))
midiout.send_message(message)
return
else:
print("********** unexpected message **********")
print(hex(message[0]),message[1],message[2])
return
#---------------------------------------------------------
def panic(e):
channel = 1
# All Sound Off
msg = [CONTROL_CHANGE + channel - 1]
msg.append(120)
msg.append(0)
midiout.send_message(msg)
# All Note Off
msg = [CONTROL_CHANGE + channel - 1]
msg.append(123)
msg.append(0)
midiout.send_message(msg)
#---------------------------------------------------------
def main(args=None):
global midiout
# the following device will be opened
ID0 = "WIDI Bud Pro" # for Windows/Linux
ID1 = "Elefue" # for Mac
ID2 = "re.corder" # for Mac
ID3 = "Nu" # for Mac (with WIDI Master/NuRAD,NuEVI)
ID4 = "EWI" # for Mac (with WIDI Master/EWI5000,EWI4000,EVI3010 etc)
ID5 = "AE-" # for Mac (Roland)
ID6 = "YDS-" # for Mac (YAMAHA)
ID7 = "Saxophone" # for USB-MIDI (YAMAHA)
OD0 = "loopMIDI" # for Windows/Mac
OD1 = "Midi Throu" # for Linux
# search OUTPUT/INPUT device
outdev = -1
midiout = MidiOut()
for n, outport in enumerate(midiout.get_ports()):
if (OD0 in outport):
outdev = n
break
if (OD1 in outport):
outdev = n
break
#print(outdev)
indev = -1
midiin = MidiIn()
for n, inport in enumerate(midiin.get_ports()):
#print(n,inport)
if (ID0 in inport):
indev = n
break
if (ID1 in inport):
indev = n
break
if (ID2 in inport):
inndev = n
break
if (ID3 in inport):
indev = n
break
if (ID4 in inport):
indev = n
break
if (ID5 in inport):
indev = n
break
if (ID6 in inport):
indev = n
break
if (ID7 in inport):
indev = n
break
#print(indev)
# end of search OUTPUT/INPUT device
if (outdev == -1):
print("**** output device not found! *****")
sys.exit(-1)
midiout.open_port(outdev) # loopMIDI for windows/mac, Midi Through for linux
print("output port: '%s'" % outport)
if (indev == -1):
print("**** input device not found! *****")
sys.exit(-1)
midiin.open_port(indev) # WIDI Bud Pro for linux/windows, dev name for mac
print("input port: '%s'" % inport)
midiin.set_callback(midiin_callback)
#-----------------------------------------------------
"""
midiout, outport = open_midioutput(outdev) # loopMIDI for windows
#midiout, outport = open_midioutput(0) # Midi Through for linux
print("output port: '%s'" % outport)
#midiin, port_name = open_midiinput(args[0] if args else None)
midiin, inport = open_midiinput(indev) # WIDI Bud Pro for linux/windows
print("input port: '%s'" % inport)
midiin.set_callback(midiin_callback)
"""
#-----------------------------------------------------
print("PLEASE set MIDI CONTROL switch ON when AE-20 connected! (DON'T FOREGET IT)")
#-----------------------------------------------------
try:
while True:
time.sleep(0.01)
except KeyboardInterrupt:
print("Bye.")
pass
finally:
midiin.close_port()
del midiin
midiout.close_port()
del midiout
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv[1:]) or 0)
#=====================================-
MIDI tool関連:
Protokol - A responsive heavy duty console for troubleshooting control protocols
MIDIトラフィックをモニターできるツール。(お勧め)
MIDI関連:
ポルタメントで滑らかに音をつなぐ
Legato Pedal
MIDI CC List for Continuous Controllers
Flet関連:
Flet app framework
Creating Flet apps in Python
Packaging desktop apps with a custom icon
Pythonだけでクロスプラットフォームなアプリを作れるFletについて
Auto PY to EXE
python-rtmidi関連:
https://github.com/SpotlightKid/python-rtmidi
OS関連:
brewをインストールする/MIDI関連 - Macことはじめ
scoopをインストール - windows10にplatformioをインストールする(scoop版)
loopMIDI関連:
loopMIDI
loopMIDIでつなぐ
WIDI_Bud_Pro関連:
WIDI Bud Pro
WIDI Bud Pro 技術情報
PC音源関連:
[WS音源探訪02] Vital Free Patch for Windsynth つくってみました
wind_controler向けのパッチがあり役に立つ
[WS音源探訪01] Vital
【無料】VitalAudioのWaveTableシンセVitalの紹介
Macことはじめ/仮想MIDIデバイスの設定,Bluetooth_MIDIデバイスの接続
Respiro(VST3)を使ってみる
Lyrihorn 2(VST3)を使ってみる
EVI-NER(VST3)を使ってみる
re.corder/Elefueに外部音源(Aria/Windows)を接続する(WIDI_Bud_Pro経由)
EWI5000をソフト音源(IFW)と接続する
re.corder関連:
owner’s manual re.corder
re.corder Downloads
re.corder Frequently Asked Questions
MIDI関連:
現時点、最強のBluetooth MIDIかも!? 各種BLE-MIDI機器と自動でペアリングしてくれるWIDI Masterがスゴイ!
Midi View
ASIO関連:
asio4all - ASIOドライバーのないオーディオインターフェイスをASIO対応にできるソフト
Aria関連:
EWI MASTER BOOK CD付教則完全ガイド【改訂版】のp100-p119の音色の設定方法がある
以上