Skip to content

Instantly share code, notes, and snippets.

@matsurai25
Last active May 3, 2016 23:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matsurai25/2a1d96a3c6ee7217cfc5 to your computer and use it in GitHub Desktop.
Save matsurai25/2a1d96a3c6ee7217cfc5 to your computer and use it in GitHub Desktop.
PutOnNote
###
■■ PutOnNote
MIDIの音(Note)情報にあわせて選択されたアイテムを置きまくります
code by matsurai25
http://matsurai25.info/put-on-note
■■ 使い方
1. 複製したいコンポジションを選択した状態で実行
2. MIDIを選択する
3. MIDIに合わせてコンポジションを置きまくったコンポジションが幾つか作成される
4. 位置など細かい変更を加えたい場合はapplyTargetOptionを変えると良い
■■ 使用上の注意
・全パート入のMIDI等を読み込むと非常に重くなることが予想されるので、DAWや何かでパート分けしてから読み込んだほうがイイです。
・適宜ソースコードを書き換えて使用して下さい。
・使用報告は不要です。あれば喜びはします。
・使用に関する責任は負いません。
■■ respected
■ 音のデータとAfter Effects その2
http://ae-users.com/jp/tutorials/2012/01/音のデータとafter-effects-その2/
■ AE: Hello again, & MIDI
http://omino.com/pixelblog/2011/12/26/ae-hello-again-midi/
■ JavaScriptでMIDIファイルを解析してみる シリーズ
http://qiita.com/PianoScoreJP/items/2f03ae61d91db0334d45
###
# データ保管用オブジェクト
MIDI = {
file:null # バイナリファイルを格納
length:null # バイナリ長さ
chunktype:null # SMF形式かを判定できる。通常4D546864
bytes:[] # 読みやすく1バイトごとに分けた配列
tracksize:null # トラック数
tracks:[] # 各トラックの情報を格納
timeunit:null # 時間単位。通常は01E0で、分解能が480で何小節何拍という形式であるということを表す
tempo:"" # テンポ。4分音符のミリ秒。BPM120で500000
tmp:{ #一時保存したいものとか
cur_deltatime:0
last_note_code:""
}
}
activeItem = app.project.activeItem #選択されてるアイテム
applyTargetOption = (target, note, CompObj) ->
###
ここで配置されるコンポジション:targetに対して位置などの値を適応しています
note.intime : 開始時間
note.pitch : 音の高さ。値は0~126
note.velocity : 音の強さ。値は0~126
###
target.name = "Note_"+note.pitch
centerX = CompObj.width / 2
centerY = CompObj.height / 2
x = centerX + ( note.pitch-64 ) * 50
y = centerY + ( note.velocity-64 ) * -7
s = note.velocity*1.3
target("position").setValue([x,y]);
target("scale").setValue([s,s]);
return
# 実際にアフターエフェクトのコンポに落とし込んでいく
applyToComp = ->
# 管理用コンプを追加
ex_time = print_time()
noteNum = 0
CompObj = app.project.items.addComp("MIDI #{ex_time} "+noteNum+"~", 1920, 1080, 1.0, 10*60, 29.97);
for track in MIDI.tracks
for note in track.notes
if noteNum%400 == 399
CompObj = app.project.items.addComp("MIDI #{ex_time} "+noteNum+"~", 1920, 1080, 1.00, 600, 29.97)
target = CompObj.layers.add(app.project.activeItem)
target.startTime = note.intime/parseInt(MIDI.timeunit,16)*MIDI.tempo*0.000001
applyTargetOption(target,note, CompObj)
noteNum++
# MIDIを取得してBytesの配列を作成
getMidiFile = ->
@FilePath = File.openDialog("MIDIファイルを選択してください","*.mid")
myFile = new File @FilePath;
# $.writeln(myFile.fsName)
myFile.encoding = "BINARY"
# $.writeln(myFile.encoding)
if !myFile.open("r")
alert "ファイルがオープンできませんでした"
return false
MIDI.length = myFile.length;
MIDI.file = myFile.read(MIDI.length);
myFile.close()
MIDI.bytes = []
for i in [0...MIDI.length]
n = convertBin2Byte(i)
MIDI.bytes.push(n)
if !MIDI.bytes?
alert "データ取得不可"
return false
# バイト位置指定でヘッダー情報類を読み出します
MIDI.chunktype = MIDI.bytes[0]+MIDI.bytes[1]+MIDI.bytes[2]+MIDI.bytes[3]
MIDI.tracksize = parseInt(MIDI.bytes[10],16)+parseInt(MIDI.bytes[11],16);
MIDI.timeunit = MIDI.bytes[12]+MIDI.bytes[13];
# $.writeln("■■■■■")
# $.writeln "MIDI.length : #{MIDI.length}"
# $.writeln "MIDI.chunktype : #{MIDI.chunktype}"
# $.writeln "MIDI.tracksize : #{MIDI.tracksize}"
# $.writeln "MIDI.timeunit : #{MIDI.timeunit}"
# $.writeln("■■■■■")
#SMFであることが確認できたら
if MIDI.chunktype == "4D546864"
mainloop()
# MIDIのバイナリを読み進める
mainloop = ->
i = 14
while i < MIDI.length
# トラック開始の判定
if MIDI.bytes[i]+MIDI.bytes[i+1]+MIDI.bytes[i+2]+MIDI.bytes[i+3] == "4D54726B"
# $.writeln "トラックの開始"
MIDI.tracks[MIDI.tracks.length] = {
name:""
size:parseInt(MIDI.bytes[i+4]+MIDI.bytes[i+5]+MIDI.bytes[i+6]+MIDI.bytes[i+7],16)
notes:[]
}
MIDI.tmp.cur_deltatime = 0
MIDI.tmp.last_note_code = ""
i+=8
continue
# まずは必ずデルタタイム
# 0x80以上なら次の値もデルタタイムと考える
b = 0
deltatimes = []
while b < MIDI.tracks[MIDI.tracks.length-1].size
deltatimes.push(MIDI.bytes[i+b])
# $.write "#{MIDI.bytes[i+b]}"
if parseInt(MIDI.bytes[i+b],16) < parseInt("80",16)
break
b++
i += b+1
# deltatimesの計算
deltatime_bit = ""
for dt in deltatimes
tmp_bit = parseInt(dt,16).toString(2)
# 空白を0で埋める
space_bit = ""
for bit in [0..(8-tmp_bit.length)]
space_bit += "0"
# 上位1ビットを削除して連結する
deltatime_bit = deltatime_bit+String(space_bit+tmp_bit).substr(-7)
cur_deltatime = parseInt(deltatime_bit,2)
MIDI.tmp.cur_deltatime = MIDI.tmp.cur_deltatime+cur_deltatime
# このループで読み進んだバイト数
read_byte = 0
## コントロールイベントの判定
if MIDI.bytes[i] == "FF"
## データ長を確認
l = parseInt(MIDI.bytes[i+2],16)
# トラック名を取得
if MIDI.bytes[i+1] == "03"
for k in [i+3...i+3+l]
MIDI.tracks[MIDI.tracks.length-1].name += "%"+MIDI.bytes[k]
MIDI.tracks[MIDI.tracks.length-1].name = decodeURI(MIDI.tracks[MIDI.tracks.length-1].name)
# $.writeln "トラック名:#{MIDI.tracks[MIDI.tracks.length-1].name}"
# $.writeln "トラックサイズ:#{MIDI.tracks[MIDI.tracks.length-1].size}"
# テンポを取得
else if MIDI.bytes[i+1] == "51"
for k in [i+3...i+3+l]
MIDI.tempo += MIDI.bytes[k]
MIDI.tempo = parseInt(MIDI.tempo,16)
# $.writeln "テンポ:#{MIDI.tracks[MIDI.tracks.length-1].tempo}"
# 拍子を取得
# 今のところ使う予定ないし放置
else if MIDI.bytes[i+1] == "58"
# $.writeln "拍子情報を発見"
# キーを取得
# 今のところ使う予定ないし放置
else if MIDI.bytes[i+1] == "59"
# $.writeln "キー情報を発見"
# トラック終了の判定
else if MIDI.bytes[i+1] == "2F"
# $.writeln "トラックの終了:#{MIDI.tracks[MIDI.tracks.length-1].name}"
else
# $.writeln "それ以外のイベント#{MIDI.bytes[i+1]}を発見"
read_byte += 2+l
# ノートオンイベントを取得
else if MIDI.bytes[i] == "9#{MIDI.tracks.length-2}"
# $.writeln "ノートオンイベントを発見:#{MIDI.tracks[MIDI.tracks.length-1].notes.length+1} ■ #{parseInt(MIDI.bytes[i+1],16)} ■#{parseInt(MIDI.bytes[i+2],16)}"
# $.write "."
note = {
intime:MIDI.tmp.cur_deltatime
# outtime:""
pitch:parseInt(MIDI.bytes[i+1],16)
velocity:parseInt(MIDI.bytes[i+2],16)
}
MIDI.tracks[MIDI.tracks.length-1].notes.push(note)
MIDI.tmp.last_note_code = "9#{MIDI.tracks.length-2}"
read_byte += 2
# ノートオフイベントを取得
# 今のところ使う予定ないし放置
else if MIDI.bytes[i] == "8#{MIDI.tracks.length-2}"
# $.writeln "ノートオフイベントを発見 ■ #{parseInt(MIDI.bytes[i+1],16)} ■#{parseInt(MIDI.bytes[i+2],16)}"
MIDI.tmp.last_note_code = "8#{MIDI.tracks.length-2}"
read_byte += 2
# コントロールイベントを取得
# 今のところ使う予定ないし放置
else if parseInt("A0",16) <= parseInt(MIDI.bytes[i],16) <= parseInt("EF",16)
# $.writeln "コントロールイベントを発見"
read_byte += 2
else # 8x/9xが省略されているため、前回のノートオン/ノートオフコードを引き継ぐ
# $.writeln "前回のノートコードの省略?"
# ノートオンイベントを取得
if MIDI.tmp.last_note_code == "9#{MIDI.tracks.length-2}"
# $.writeln "ノートオンイベントを発見:#{MIDI.tracks[MIDI.tracks.length-1].notes.length+1} ■ #{parseInt(MIDI.bytes[i],16)} ■#{parseInt(MIDI.bytes[i+1],16)}"
# $.write "."
note = {
intime:MIDI.tmp.cur_deltatime
# outtime:""
pitch:parseInt(MIDI.bytes[i],16)
velocity:parseInt(MIDI.bytes[i+1],16)
}
MIDI.tracks[MIDI.tracks.length-1].notes.push(note)
MIDI.tmp.last_note_code = "9#{MIDI.tracks.length-2}"
read_byte += 1
# ノートオフイベントを取得
# 今のところ使う予定ないし放置
else if MIDI.tmp.last_note_code == "8#{MIDI.tracks.length-2}"
# $.writeln "ノートオフイベントを発見 ■ #{parseInt(MIDI.bytes[i+1],16)} ■#{parseInt(MIDI.bytes[i+2],16)}"
MIDI.tmp.last_note_code = "8#{MIDI.tracks.length-2}"
read_byte += 1
i += read_byte+1
# Note位置取得処理が完了
applyToComp()
return true
# 現在時刻の文字列を取得
print_time = ->
date = new Date()
hour = date.getHours();
minute = date.getMinutes();
second = date.getSeconds();
return "#{hour}:#{minute}:#{second}"
# 実行可能な状態かどうか
checkCanExcute = ->
# 複製用コンプの存在確認
if (activeItem == null) or !(activeItem instanceof CompItem)
alert "複製配置したいコンポジションを選択した状態で実行して下さい"
return false
if confirm "現在選択されているコンポジションを複製配置します。"
return true
return false
# バイナリコードを人間的に判定しやすいByteに変換
convertBin2Byte = (offset) ->
if !MIDI.file?
# $.writeln "MIDI.fileがないのにconvertBin2Byte"
return false
c = MIDI.file.charCodeAt(offset).toString(16).toUpperCase()
if c<10
c = 0 + String c
return String c
# 開始
go = ->
return unless checkCanExcute()
getMidiFile()
go()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment