C4Dでの音合わせが少し楽になるかもしれないAEスクリプト紹介

これは[AfterEffects Advent Calendar 2016](http://qiita.com/advent-calendar/2016/aftereffects)の5日目の記事です。
昨日の記事は、 @tetsuoh さんによる [アニメ撮影でErgoDox使ってみた](http://qiita.com/tetsuoh/items/d6e00ea06a31449d5e33) です。

# はじめに
映像作ってます。Shutoです。
BGAやボーカロイドMVなどのモーショングラフィックス作品の制作でよく使われる**Cinema 4D**（通称C4D）は、ドイツに本社を置くMAXONが開発した3DCG制作ソフトです。
最近のAfterEffects CCには「Cineware」といった機能がついていてC4D単体製品を買わなくても「Cinema 4D Lite」が使えるためにその名前をご存知の方も多いでしょう。

さて、本題に入りますが、今回はC4Dを用いて音楽に合ったCGシーンを制作する際に、**音合わせ**が少し楽になるかもしれない、そんな気休め程度のスクリプトをご紹介します。

お気づきの方も多いでしょうが、本記事はC4DかつAEのユーザを想定しています。C4Dユーザで無い方はごめんなさい。一応AEのスクリプトなので許してください<del>なんでもry</del>

ちなみに環境ですが、こちらはWindows 10のCS6（11.0.4.2）を使用しています。

# C4Dでの音合わせの問題
C4Dではシーンが重くなってくると、作業画面のプレビューの再生がままならなくなります。音声再生がガタガタになったり、プレビューのフレームレートが1fps以下にもなったりします。
ここでAEユーザーなら「じゃあRAMプレビューだ！」と思うわけですが、C4DにはRAMプレビューがありません。代わりにある簡易的にレンダリングしてから再生させるプレビューが「プレビュ作成」の「ハードウェア（ソフトウェア）プレビュ」にあたるわけですが、シーンが複雑になってくると、この「プレビュ作成」も非常に重くなってくることが多いです。
<img width="400px" alt="qiita_advent2016_1_1.png" src="https://qiita-image-store.s3.amazonaws.com/0/40264/b3ad6248-cd38-418d-f16d-719c57cd74df.png">

![qiita_advent2016_1_2.png](https://qiita-image-store.s3.amazonaws.com/0/40264/6838d4cd-ad03-6f29-f2f5-1853f92c6002.png)

映像制作の音合わせでは基本的には何度も試行錯誤を行って気持ちよく当てはまる動きを探すことでしょう。
ところがこの重い「プレビュ作成」を何度もしていたら試行錯誤をするのに疲弊してしまいます。
効率よく制作するには1度のプレビュ作成でどれだけ理想の音ハメのタイミングに近づけるかが重要になってきます。

# 解決策とその手順
最初からいきなりC4Dで音合わせをするのでは、タイミングを当てるのが難しいです。
AEには`.c4d`ファイルを書き出す機能があるので、それをうまく活用して、AEで作成した**音合わせの目印**をC4Dに持っていくことにしましょう。
手順を示すとこんな感じです。**1～4はすべてAEでの作業になります。**
なお、題名にもあるスクリプトについては最後に記します。

### 1. AE側で音楽ファイルを読み込み、RAMプレビューを用いてタイミングを確認する
普通にAEにmp3やら音楽ファイルを読み込んでRAMプレビューをさせます。ここである程度タイミングを確認しておきます。

### 2. 音ハメのキーポイントとなる特徴的な音のタイミングで、**AEのマーカー**を設置する
![qiita_advent2016_1_3.png](https://qiita-image-store.s3.amazonaws.com/0/40264/54fe6e70-f491-b851-6884-36c83f9ba5e1.png)
このように音楽ファイルのレイヤーなどにマーカーを打っていきます。
一例ですが、2秒間にこんなに細かい動きを作りたいとしたら、C4Dだと音合わせが大変ですよね。
### 3. ライトレイヤーを作成し、2.で設置したマーカーと同じタイミングで座標などにキーフレームを打つ
![qiita_advent2016_1_5.png](https://qiita-image-store.s3.amazonaws.com/0/40264/c8026097-a130-3c2f-2759-ffa4e1b49dea.png)
2で打ったマーカーをライトレイヤーの位置プロパティのキーフレームに変換します。手打ちでも構いませんが、大変なのでここは**スクリプトで自動化**します。
なお、「ライトレイヤー」は「カメラレイヤー」でもかまいません。C4Dにキーフレームを持っていければなんでもいいのです。
※キーフレーム補間法が「停止」になっている点については次項で説明します。
### 4. コンポジションを `.c4d` 形式にエクスポートする
![qiita_advent2016_1_6.png](https://qiita-image-store.s3.amazonaws.com/0/40264/6a017739-6041-9629-5017-5ea2598c24e7.png)
打たれたキーフレームの情報も含めてAEのライトをC4Dに持っていけます。これも**スクリプトで自動化**してしまいましょう。
メニューの**「ファイル＞書き出し＞CINEMA 4D Exporter」**から手動でもエクスポートができます。
ただし、一点だけ注意が必要で、3のライトレイヤーに打つ**キーフレームの補完法は「停止」**にします。他の補完法でもエクスポートは出来るのですが、C4Dに対応していない補完法は何でもかんでも勝手にベイクされてしまうようで、どこがマーカーのタイミングなのか分からなくなってしまいます。（補間法リニアでエクスポートした図）
![qiita_advent2016_1_7.png](https://qiita-image-store.s3.amazonaws.com/0/40264/3026ef2d-fb03-7012-f4c3-3c579e9a6566.png)

### 5. エクスポートされた `.c4d` ファイルをC4Dで読み込み、ライトレイヤーのキーフレームのタイミングを参考に音ハメをする
キーフレーム補完法を「停止」にして`.c4d`ファイルとしてエクスポートすると、C4Dで読み込むときちんとこのように必要なタイミングでのみキーフレームが打たれた状態でライトを持っていくことが出来ます。
![qiita_advent2016_1_8.png](https://qiita-image-store.s3.amazonaws.com/0/40264/4101bafe-cc09-9d03-ba5f-f76b97e2cbe0.png)
制作にあたっては、当然何度かはハードウェア（ソフトウェア）プレビュを作成してきちんと動きとタイミングが合っているかを確認する必要はありますが、この方法で音の目印を持っていくことで、だいたいのタイミングが合った状態で動きをつけていくことが出来ます。

余談ですが、最近制作した[kanone](https://twitter.com/kanone_alcoholi)さんの[CO5M1C R4ILR0AD](https://vimeo.com/184196910)のBGAでもこのように活用しました。
![qiita_advent2016_1_13.PNG](https://qiita-image-store.s3.amazonaws.com/0/40264/385c494f-4b6c-f287-7cd5-8d96cd6ab299.png)


# スクリプトについて
ようやくスクリプトの紹介ですが、このように**「AEで作成したマーカー情報を元に、C4Dへ持っていけるライトレイヤーにキーフレームを打ち、C4Dファイルにエクスポートしてくれるスクリプト」**を書きました。どのようなスクリプトを書いたとか、なんでそんなスクリプト書いたのとかっていう説明の上手い手順が思い浮かばなかったので回りくどい書き方をしてしまいました。
なお、最近は実装されたそうですが、コンポマーカーにアクセスするメソッドが用意されていなかったので、noriaki iwataniさんの[コンポジションマーカーを取得するスクリプト](http://nariakiiwatani.net/?p=351)をお借りしています。


以下作成したスクリプト。
コピペ保存は面倒くさいと思うので、[こちら](https://raw.githubusercontent.com/shutosg/AEScripts/master/markerToC4DasLight/markerToC4DasLight.jsx)をCtrl+Sもしくは⌘+Sで保存してお使いください。
**保存形式が`.jsx`になるように注意してください。**

```markerToC4DasLight.js

//  コンポかレイヤーを選択した状態で使うと、新たにライトレイヤーを生成して、コンポかレイヤーのマーカーと同じ時間にキーフレームを打つ。
//  レイヤーが1枚選択されていればそのレイヤーのマーカーを使う。
//  コンポが選択されていれば(レイヤーが選択されていなければ)、そのコンポのマーカーを使う。
//  CS6ではコンポのマーカーを取得するメソッドが用意されていないのでnariakiwataniさんのスクリプトをそのままお借りした。


var activeComp = app.project.activeItem;  //  プロジェクトパネルでアクティブなアイテムを取得

function main() {
    var markers = null;
    
    
    if (activeComp instanceof CompItem == false) {  //  アクティブなアイテムがコンポ以外ならはじく
        alert("コンポが選択されていません。");
        return;
    }
    if (activeComp.selectedLayers.length > 1) {  //  activeComp内で選択されているレイヤーが2枚以上ならはじく
        alert ("レイヤーを1つだけ選択するか、コンポのみ選択してください。\nレイヤーを選択していない場合はアクティブなコンポのマーカーが使われます。");
        return;
    } else if (activeComp.selectedLayers.length == 1) {  //  選択されているレイヤーが1枚ならそのレイヤーのマーカーを取得
        markers = activeComp.selectedLayers[0].property("Marker");
    } else if (activeComp.selectedLayers.length == 0) {  //  レイヤーが選択されていないならばそのコンポのマーカーを取得
        markers = getCompMarker(activeComp);
    }
    if (markers.numKeys < 1) {  //  マーカーが0ならはじく
        alert ("選択されているレイヤーかコンポにマーカーがありません。");
        return;
    }


    var newLight = activeComp.layers.addLight("Marker_Light", [0, 0]);  //  ライトレイヤーを生成して、newLightに格納
    newLight.property("position").setValue([0, 0, 0]);  //  ライトレイヤーの座標が初期だとキモいので変える
    newLight.property("pointOfInterest").setValue([0, 0, 100]);
    for (var i=0; i<=markers.numKeys; i++) {  //  取得したマーカーの分だけキーフレームを打つ
        var keyIdx = newLight.property("position").addKey((i==0)?0:markers.keyTime(i));  // キーフレームを打って、そのキーフレームのインデックスをkeyIdxに格納
        newLight.property("position").setValueAtKey(keyIdx, [0, 0, i*-10]);  //  格納したインデックスを元に、そのキーの座標値を変更
        //  同様に補間方法を変更
        newLight.property("position").setInterpolationTypeAtKey(keyIdx, KeyframeInterpolationType.HOLD, KeyframeInterpolationType.HOLD);
    }


    var version = parseInt(app.version.split(".")[0]);
    var msg_export = "Marker_Light を生成しました。\n続けて.c4dファイルに出力しますか？";
    var code_export = (version>11)?5022:5006;
    var execute = confirm(msg_export);  //  確認ダイアログを表示
    if (execute) app.executeCommand(code_export);  //  yesならexporter起動
    
}

app.beginUndoGroup("markerToC4DasLight");  //  ここから
main();
app.endUndoGroup(); // ここまでの処理がUndo1回で戻せる


/*
AfterEffectsでコンポジションマーカーを取得するスクリプト
2013.10.31 by nariakiiwatani
MIT License - http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license
*/
 
// マーカーの情報を取得したいコンポジションを渡す
function getCompMarker(comp) {
 
// プロジェクトに含まれるコンポジションのリストを取得
function getCompAll(proj) {
    var ret = [];
    for(var i = 1; i <= proj.numItems; ++i) {
        if(proj.item(i) instanceof CompItem) {
            ret.push(proj.item(i));
        }
    }
    return ret;
}
// 配列の差分を取得
function getArrayDiff(a, b) {
    var ret = [];
    for(var _a in a) {
        var found = false;
        for(var _b in b) {
            if(a[_a] === b[_b]) {
                found = true;
                break;
            }
        }
        if(!found) {
            ret.push(a[_a]);
        }
    }
    var tmp = a;
    a = b;
    b = tmp;
    for(var _a in a) {
        var found = false;
        for(var _b in b) {
            if(a[_a] === b[_b]) {
                found = true;
                break;
            }
        }
        if(!found) {
            ret.push(a[_a]);
        }
    }
    return ret;
}
 
// プロジェクトウィンドウの選択情報を操作するので、操作前の状態を保存しておく
var selected = app.project.selection;
var selection = [];
for(var i = 0; i < selected.length; ++i) {
    selection.push(selected[i].selected);
    selected[i].selected = false;
}
 
// ここから処理の本体
comp.selected = true;   // 書き出し対象のコンポジションを選択状態にする
// 「複数アイテムから新規コンポジション」はプロジェクトウィンドウにフォーカスしていないと使えないので強制的にアクティブにする
app.project.showWindow(false);
app.project.showWindow(true);
 
var allComps = getCompAll(app.project); // コマンド実行前のコンポジションのリスト
app.executeCommand(2796);   // 「複数アイテムから新規コンポジション」を実行
var added = getArrayDiff(getCompAll(app.project), allComps)[0]; // コマンド実行によって追加されたコンポジションを探す
var marker = added.layer(1).marker; // これが欲しかったマーカー！
 
// ここから後片付け
added.remove(); // 増やしたコンポジションを削除
comp.selected = false;  // 選択状態を解除
for(var i = 0; i < selected.length; ++i) {   // スクリプト実行前の選択状態に戻す
    selected[i].selected = selection[i];
}
// マーカー情報を返す
return marker;
}
```

# オマケのC4Dスクリプト
C4Dで音楽を読み込む方法はググって貰えれば出てくる通り、オブジェクト（ヌルなどなんでもよい）に**「サウンド」**という特殊トラックを追加すればよいのですが、これがまたUIからのアクセス性が悪く、「サウンド」のパラメータを変える場合は毎回タイムラインを開いて「サウンド」トラックを選択して属性マネージャに表示してやる必要があります。

![qiita_advent2016_1_14.PNG](https://qiita-image-store.s3.amazonaws.com/0/40264/85edc3fb-e0a7-51bb-d15f-104f13cb553d.png)
↑追加した特殊トラック「サウンド」を選択してアクティブにしないと↓このプロパティを表示できない
![qiita_advent2016_1_11.PNG](https://qiita-image-store.s3.amazonaws.com/0/40264/bacad162-a088-1eb7-507b-dd8f04ceae93.png)


座標やその他のタグなど、他のパラメータは**基本的にオブジェクトマネージャ（ツリー）からそのオブジェクトやタグ自身を選択すれば属性マネージャに表示されます**が、「サウンド」の属性を表示させるには**タイムラインの「サウンド」を選択する**必要があります。
![qiita_advent2016_1_10.PNG](https://qiita-image-store.s3.amazonaws.com/0/40264/650a26bf-51a1-74b1-f87b-7b6a4581ea06.png)
↑オブジェクトマネージャの「null」オブジェクトを選択しても「サウンド」のプロパティは表示されない。


そこで、新規ヌルオブジェクト（**Music**という名前）に「サウンド」トラックを追加（実際この手順も結構面倒）して、さらにそのMusicオブジェクトを選択すればこのように「サウンド」のプロパティに簡単にアクセスできるスクリプトを書きました。
![qiita_advent2016_1_12.PNG](https://qiita-image-store.s3.amazonaws.com/0/40264/0b0ed2ea-8469-915c-4f30-99bdd521a711.png)
仕組みとしてはPythonタグを使って常に属性マネージャの↑の「Sound」タブの値を見て、「サウンド」の特殊トラックの値に入れています。つまり↑の「Use Sound」や「Start Time」の値をいじれば自動的にそれが特殊トラックの方にも反映されるわけです。

**<font color="red">※使い方にご注意ください！Pythonスクリプトタグにコピペせずに、必ずスクリプトファイルとして保存して、メニューの「スクリプト＞ユーザスクリプト＞スクリプトを実行」から実行してください。</font>**
![qiita_advent2016_1_15.png](https://qiita-image-store.s3.amazonaws.com/0/40264/18ecabf3-c103-49fa-d17b-b111283a8c8f.png)


スクリプトの保存はAEスクリプト同様、[こちら](https://raw.githubusercontent.com/shutosg/C4DScripts/master/addMusic/addMusic.py)から保存してお使いください。
**jsx同様に保存形式が`.py`になるように注意してください。**

```addMusic.py
import c4d
import time

def main():
    obj = c4d.BaseObject(c4d.Onull)
    obj.SetName("Music")
    doc.InsertObject(obj)
    doc.SetSelection(obj)

    tr = c4d.CTrack(obj,c4d.DescID(c4d.DescLevel(c4d.CTsound, c4d.CTsound, 0)))
    obj.InsertTrackSorted(tr)

    pyTag = obj.MakeTag(c4d.Tpython)
    pyTag.SetName("Music")

    rootGroup = c4d.GetCustomDataTypeDefault(c4d.DTYPE_GROUP)
    rootGroup[c4d.DESC_NAME] = "Sound Properties"
    rootGroup[c4d.DESC_SHORT_NAME] = "Sound"
    rootGroup[c4d.DESC_TITLEBAR] = 1
    rootGroup[c4d.DESC_GUIOPEN] = 1
    rootGroup[c4d.DESC_PARENTGROUP] = c4d.DescID()
    descId = obj.AddUserData(rootGroup)

    secondGroup = c4d.GetCustomDataTypeDefault(c4d.DTYPE_GROUP)
    secondGroup[c4d.DESC_NAME] = "Basic Properties"
    secondGroup[c4d.DESC_SHORT_NAME] = "Basic"
    secondGroup[c4d.DESC_TITLEBAR] = 1
    secondGroup[c4d.DESC_GUIOPEN] = 1
    secondGroup[c4d.DESC_PARENTGROUP] = descId
    descId = obj.AddUserData(secondGroup)

    label = ["Use Sound", "Start Time", "Sound"]
    default = [1, c4d.BaseTime(), ""]
    typ = [c4d.DTYPE_BOOL, c4d.DTYPE_TIME, c4d.DTYPE_FILENAME]

    for i in range(3):
        bc = c4d.GetCustomDataTypeDefault(typ[i])
        bc[c4d.DESC_NAME] = label[i]
        bc[c4d.DESC_SHORT_NAME] = label[i]
        bc[c4d.DESC_PARENTGROUP] = descId
        bc[c4d.DESC_DEFAULT] = default[i]
        bc[c4d.DESC_ANIMATE] = c4d.DESC_ANIMATE_OFF
        descId2 = obj.AddUserData(bc)
        obj[descId2] = default[i]

    pyTag[c4d.TPYTHON_CODE] = 'import c4d\n#Welcome to the world of Python\n\n\ndef main():\n    obj = op.GetObject()\n    tr = obj.GetCTracks()[0]\n    tr[c4d.CID_SOUND_ONOFF] = obj[(c4d.ID_USERDATA, 3)]\n    tr[c4d.CID_SOUND_START] = obj[(c4d.ID_USERDATA, 4)]\n    tr[c4d.CID_SOUND_NAME] = obj[(c4d.ID_USERDATA, 5)]\n'

if __name__=='__main__':
    main()
    c4d.EventAdd()

```
*※公開直後追記*
[やりたさん](https://twitter.com/yarita_cc/status/805434465812025348) 曰く、C4D標準UIから「サウンド」プロパティへのアクセスは、MoGraphの「サウンドエフェクタ」をシーンに追加するだけで簡単にできるそうです。
ただし、「サウンドエフェクタ」は**ハードウェアプレビュなど「プレビュ作成」においては音声が再生されない**ので、そのあたり使い分けが必要になりそうです。
プレビュ作成をしないのならばそちらだけでも十分楽になると思いますが、僕のように基本的に必ずプレビュ作成する場合はこのようなスクリプトが便利かと思います。
*追記ここまで*

# 最後に

この時点で既に公開予定の0時15分を過ぎています。ごめんなさい。
駆け足で書いたのでとっても読みにくかったと思いますが、何か誤りや分かりにくいところがあればコメントまたは[Twitter](https://twitter.com/shuto_fms)までご連絡ください！

*※公開直後追記*
なお、記事公開後に**そもそもC4Dのシーンを軽くする方法**をやりたさんに教えて頂きました。
つきましては [こちらのくだり](https://twitter.com/yarita_cc/status/805443509851340801) が参考になるかと思いますので、こちらも一読して貰えるとよいかと思います。
*追記ここまで*

それでは明日は @kickbase さんの [マルチバイトフォルダ名許さいないマンのためのTips](http://www.kickbase.net/entry/after-effects-exclude-multi-byte) です！
僕もマルチバイトフォルダ名許さないマンなのでとても楽しみです！ *※2016/12/6更新↑リンク追加しました*