ADX2ロボット機能でMIDI4オクターブに挑戦

#はじめに
[MIDIで取り込んでみた](https://qiita.com/tatmos/items/738181ed9b346a8f32e4)けど、オクターブの範囲が広い場合、設定範囲の上限、下限にひっかかってしまう。

そこで、それを回避するようにしてみた。

まず、トラックを分け、トラック側でベースのピッチを変更したものを用意する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96808/1ecf1f6e-603e-b8f9-dcb7-f8aafa0bccf7.png)

それでも足りなかったので、高い方にオートメーションでさらにオクターブ上を追加。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96808/b11afd74-f3b7-8688-6557-6cf7c22a7c92.png)


<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/ADX2?src=hash&amp;ref_src=twsrc%5Etfw">#ADX2</a> ロボット機能で4オクターブMIDI取り込み中の動画 <a href="https://t.co/yygHE93Wjk">pic.twitter.com/yygHE93Wjk</a></p>&mdash; tatmos (@tatmos) <a href="https://twitter.com/tatmos/status/1357103217868349441?ref_src=twsrc%5Etfw">February 3, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


###ピッチ上げすぎ

このまま再生を試みる。
すると、再生ピッチが高すぎて再生時にエラーがでる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96808/c6d9835e-2aae-2550-388f-a1c9dcef9bbe.png)
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/ADX2?src=hash&amp;ref_src=twsrc%5Etfw">#ADX2</a> 音を高くしすぎると再生しきれなくなるところ<br>なるべく高い音を鳴らそうとしているけど正しくなくなる。（警告もでてる）<br>こういう場合は、元波形のサンプリングレートを下げてあげる必要がある。 <a href="https://t.co/JnxCM7MQo1">pic.twitter.com/JnxCM7MQo1</a></p>&mdash; tatmos (@tatmos) <a href="https://twitter.com/tatmos/status/1357104502894043136?ref_src=twsrc%5Etfw">February 3, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

サンプリングレートはオクターブが上がると倍になる。
もし44100の再生音をオクターブ上で再生しようとすると88200になる。
3オクターブ上だとさらに倍の176400になる。
4オクターブ上だとさらに倍の352800
と、かなり危険な値（流量的に）になるので、
元のレートを半分にすることで上がりすぎないようにした。
元の波形のレートを下げるのは簡単で、マテリアル＞エンコード＞リサンプリングレートを下げればよい。
音は若干劣化してしまう。（ちょっと古いサンプラーみたいなLoFi感）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96808/47c3bf3f-7ebf-8fb0-5676-58169d0f1b30.png)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/ADX2?src=hash&amp;ref_src=twsrc%5Etfw">#ADX2</a> サンプリングレートを下げることで、高い音も問題なくなる。　<br>でも、一つの波形で4オクターブを賄おうとすると、ちょっと不自然になりはじめる。　高い音は短く、低い音はより長く再生される。　また音量も変化する。　ある程度のオクターブごとに波形を割り当てる方が仕上がりはよくなる。 <a href="https://t.co/TE6GtMhPmB">pic.twitter.com/TE6GtMhPmB</a></p>&mdash; tatmos (@tatmos) <a href="https://twitter.com/tatmos/status/1357106166220771328?ref_src=twsrc%5Etfw">February 3, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

他にもいろいろ鳴っていれば問題ないかも。
音色にもよる。　例えば声とかだとフォルマントも変化してしまうので、いかにもサンプラーみたいになる。　木琴のようなものの場合は大きさも変わるから不自然にならないなど。
この動画のエレピの場合は、若干長さの変化が気になるところかもしれない。

# スクリプト（4オクターブ対応版）

``` readMidi4oct.py
# --Description:[tatmos]MIDIを読みこみwaveformを配置する
import sys
import cri.atomcraft.project
import cri.atomcraft.project as acproject
import cri.atomcraft.debug as acdebug
# --BeginUserVariable
"""
MIDI_FILE:
  type:string
  comment:MIDI File Path
MATERIAL:
  type:object
  comment:Material
"""
MIDI_FILE = "C:/Users/tanakat/Documents/Nuendo/20210128GGJ/20210128GGJ.mid"
MATERIAL = cri.atomcraft.project.get_object_from_path("/WorkUnits/WorkUnit_Block_IntaractiveMusic_MaterialInfo/MaterialRootFolder/Sozai20120527/epianoC.wav")["data"]
# --EndUserVariable

# 選択しているCueを得る
selected_Cue = acproject.get_selected_objects("Cue")["data"]
if not selected_Cue :
    acdebug.warning("MIDIを読み込むキューを選択してください。")
    sys.exit()

# オブジェクトパス表示
acdebug.log("Target Path:\"{0}\"".format(acproject.get_object_path(selected_Cue[0])["data"] ))

# Materialを得る
if not MATERIAL :
    acdebug.warning("Materialをセットしてください。")
    sys.exit()

#Octabe2のトラックを用意
track2 = acproject.create_object(selected_Cue[0],"Track","track2")["data"]
acproject.set_value(track2, "Pitch", 1200)
automation = acproject.create_automation(track2,"Pitch")["data"]
acproject.create_automation_point(automation,0,1200)
#Octabe1のトラックを用意
track1 = acproject.create_object(selected_Cue[0],"Track","Track1")["data"]
acproject.set_value(track1, "Pitch", 1200)	
#Octave0のトラックを用意
track0 = acproject.create_object(selected_Cue[0],"Track","Track0")["data"]
#Octave-1のトラックを用意
trackm1 = acproject.create_object(selected_Cue[0],"Track","trackm1")["data"]
acproject.set_value(trackm1, "Pitch", -1200)	

# MIDI読み込み
import mido 
from mido import MidiFile

mid = MidiFile(MIDI_FILE)

def setWaveformRegionParam(waveform_region,msg,absolute_time):
    acproject.set_value(waveform_region, "Volume", msg.velocity/127)
    acproject.set_value(waveform_region, "DelayTimeMS", absolute_time)
    acproject.set_value(waveform_region, "Comment", "note {0} vel {1} absTime {2}".format(msg.note,msg.velocity,absolute_time))

for i, track in enumerate(mid.tracks):
    print('Track {}: {}'.format(i, track.name))
    absolute_time = 0
    for msg in track:
        if msg.type == 'set_tempo':
            tempo = msg.tempo
            acdebug.warning("temo is {0}".format(tempo))
        if msg.time > 0:
            absolute_time = absolute_time + mido.tick2second(msg.time,480,tempo)*1000

	#note onのみ
        if msg.type == 'note_on':
	        print(absolute_time)
	       
	        # マテリアルを指定してウェーブフォーム リージョンを作成
	        if (msg.note-60) > 24:
                    #基準から1oct上の場合トラックを分ける
                    waveform_region = acproject.create_waveform_region(track2, MATERIAL)["data"]
                    acproject.set_value(waveform_region, "Pitch", ((msg.note-60-24)%12)*100)	
	        elif (msg.note-60) > 12 and (msg.note-60) <= 24:
                    #基準から1oct上の場合トラックを分ける
                    waveform_region = acproject.create_waveform_region(track1, MATERIAL)["data"]
                    acproject.set_value(waveform_region, "Pitch", ((msg.note-60-12))*100)
	        elif (msg.note-60) <= 12 and (msg.note-60) >= -12:
                    waveform_region = acproject.create_waveform_region(track0, MATERIAL)["data"]
                    acproject.set_value(waveform_region, "Pitch", (msg.note-60)*100)
	        elif (msg.note-60) >= -24 and (msg.note-60) < -12:
                    #基準から1oct下の場合トラックを分ける
                    waveform_region = acproject.create_waveform_region(trackm1, MATERIAL)["data"]
                    acproject.set_value(waveform_region, "Pitch", ((msg.note-60+12))*100)
	        else:
                    #基準から上下離れている時
                    waveform_region = acproject.create_waveform_region(trackm1, MATERIAL)["data"]
                    acproject.set_value(waveform_region, "Pitch", ((msg.note-60+12)%12)*100)	
	        setWaveformRegionParam(waveform_region,msg,absolute_time)

```