いらないモノ、ひつようなモノ

書籍、音楽、そして若干のテクノロジー

csoundとRosegarden、惜しい

csoundで作曲する時に面倒なのはやっぱりスコアを書くのが面倒。csoundのスコアファイルへの落し方は色々ある。MIDI経由などでスコアに落とせると、リアル楽器を弾いたものを取り込めるのでうれしい。
楽器で何のモジュレーションも加えずに淡々と弾くなんてありえないから、ノートON以外のイベントもスコアに取り込みたいと希うことになる。でやりたいことはこんな風になる。

  • csoundで発音するパラメータがグローバル変数になった楽器を作る(楽器1)
  • (楽器1)の音色などのグローバル変数にしたパラメータを外から制御する(楽器2)を作る
  • (楽器1)、(楽器2)のスコアをMIDIインタフェースを持つリアル楽器から打ち込み、かつ、エディットして形を整えたい。

これを実現するのはどうしたら良いのか!?

  • リアルタイムMIDI、しかもCC#に応じても反応するように楽器を作る。楽器は発音側、制御側の二つを作る(引いたときに音が出るように)
  • 引いたものを記録して、スコアに落とせる環境を作る

で、こうしたいときにどうするか?

  1. 外部のMIDIキーボードなどで演奏して、Rosegardenで記録してcsoundのスコアファイルにexport。この方法が最も扱いやすいし最も勧められる方法だと思う。でも惜しいところもある。MIDIのノートONはscoreに落ちるが、それ以外のイベント、例えばピッチベンドなど、すべてコメントでは出力されるもののscoreにはならない。これが楽器番号を変えてスコアに落ちるなら、楽器のパラメータを制御する楽器に例えばピッチベンドなどを割り当てcsoundの楽器も制御できるのに。。実に惜しい。。(これができると思って実験したらだめなんだもん、ショック)
  2. DAW環境で作った楽曲をMIDIに落としてmid2sco(http://www.csounds.com/istvan/html/scoreproc.html)でscoreファイルに変換。でもこれもノートONしか現在対応していないみたい。やはり惜しい。2番目に惜しい。
  3. AlgoScore(http://www.bitminds.net/kymatica/index.php/Software/AlgoScore)。こいつは名前どおりアルゴリズミックな作曲でGUIを使って直感的な作曲をサポートして、MIDIやOCSを出力するんだけども、どこにもリアル楽器からのMIDI入力を受け付ける動きをするとは書いていない。csound経由で音も作るとあるけど、csoundのスコアは吐き出すとは一応ドキュメントには書かれていない。うーむ残念。
  4. blue。これも同じくアルゴリズミックな作曲のためのツールと理解している(最近変わったかな?)だから同じく調査もせずに諦める。
  5. Bol Processor 2(http://bolprocessor.sourceforge.net/http://bolprocessor.sourceforge.net/bp2intro.htm)を利用してMIDIなどと絡める。これはBOLプロセッサの世界にハマらないと分からないのでちょっと身が引ける。真面目に調べていない。ちょっとドキュメントを齧ると

A convenient way of generating Csound scores is to convert them from MIDI files, but this limitates Csound parameters to the very few ones implemented in MIDI. BP2 handles any number of parameters (with arbitrary names) that may be assigned any position among Csound instrument arguments.

MIDIファイルからCsoundスコアを生成するのが良いんだけど、csoundで利用できるパラメータをMIDIのいくつかのパラメータに限定しなくちゃいけないんだよねー。でもBP2となら、いくつでもcsoundの楽器で使うパラメータを扱えるんだよねー

http://bolprocessor.sourceforge.net/docs/bp2-What-2.html

とある。MIDIも取りこめるみたいなのでやや希望がある。がなんか、とっつきにくい。

    • -

でどうしようか悩むなー。rosegardenはハックしずらそうな感じがする。mid2scoをパクるのが一番早いのかなあ。でも、いちいちMIDIに落としてまた変換してとかかなりいやだなぁ。リアルタイムにレンダリングするのはopcodeで本当にどこまでできるのかためしてないなぁ。
でもリアルタイムに発音できても、非リアルタイムにスコアとして落とせなかったらしょっくだしなぁ。

ちょっとなやんでます。

Rosegardenのソースを眺める

Rosegardenのソースをダウンロードして、ソースを見てみる。C++だ。src/document/io/CsoundExporter.cppが其の実体みたい。

        for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
            if ((*j)->isa(Note::EventType)) {

                long pitch = 0;
                (*j)->get
                <Int>(BaseProperties::PITCH, pitch);

                long velocity = 127;
                (*j)->get
                <Int>(BaseProperties::VELOCITY, velocity);

                str << "   i"
                << (*i)->getTrack() << "\t"
                << convertTime((*j)->getAbsoluteTime()) << "\t"
                << convertTime((*j)->getDuration()) << "\t"
                << 3 + (pitch / 12) << ((pitch % 12) < 10 ? ".0" : ".")
                << pitch % 12 << "\t"
                << velocity << "\t\n";

            } else {
                str << ";; Event type: " << (*j)->getType() << std::endl;
            }
        }

がさらに絞り込んだ書いているところ。「(*j)->isa(Note::EventType)」Note以外のイベントのelse Ifを入れればいいみたい。しかし何のイベントをどのように落とすか。

手持ちの楽器が何の信号を出すのか

Oxygen8を持っているので、そいつのノブなどが何の信号を出しているのか確認する。ノブを捻ってRosegardenに記録して何が出ているのかみると(もしかしたら、各ノブにCC#を設定出きるような気がするが)、いまのところ、pitchbendは高精度のLSB,MSBをそのまま、モジュレーションはモジュレーションMSBのみ。DataEntryというスライダーはMainVolumeMSB、各8つのノブは10〜16のCC#で分解能は128、となぜか最後のノブだけCC#が6のデータエントリMSBで分解能は128。

noteOn以外をどんなスコアに落とすか?!

思いつくのはCC#をそのまま楽器の番号にする的な発想だが、少し考えて以下のようなルールにする。

取り込むのは取り合えず

  • pitchbend*1
  • CC#(コントロールチェンジナンバー)*2

の二つだけとする。また楽器とそれらの関連は

  • CH#×150+200をNoteOnで発音する楽器とする
  • CH#×150には最大149個の制御用楽器が付属できる
  • 特に、CH#×150+1をpitchbendの信号を受ける楽器とする
  • 特に、CH#×150+2〜130を0〜127*3のCC#を受ける楽器とする
  • CH#×150+131〜149までは未定

つまり

  • 001-199:Reserved
  • 200 :MIDI-CH1用の発音楽器
  • 201-349:MIDI-CH1用のパラメータ制御用楽器(149個)
  • 350 :MIDI-CH2用の発音楽器
  • 351-499:MIDI-CH2用のパラメータ制御用楽器(149個)

となるようにする。この時に楽器番号の制約は想定しない*4

実際にはCH#はRosegardenの中ではTrack#として置き換えて実装する。この場合Track0番をMIDI-CH1番に流し込むと想定する。

本来であればこの様なルールをユーザが自由に決めるようにRosegardenにGUIをつけてどのMIDI信号の時に何の楽器番号を使うか決めたい。GUIがなくても初期化用のファイルにでも書きたい。

src/document/io/CsoundExporter.cppのCsoundExporter::write()を書き換える。

やったことは、

  • scoreを書き出すためのcsound_scoreのchar配列を定義
  • else ifを加えて、PitchBend::EventTypeと、Controller::EventTypeの時にスコアを書き出すようにする。それぞれはMidiTypes.hにある。
  • C++は最近書いていないのでわからなかったのでsprintfで出力文字列を作って出力。
  • おまけ、gtagsをインストールして使ってみた。
bool
CsoundExporter::write()
{
    char csound_score[70]; // added by a9a9qq@gmail.com
    std::ofstream str(m_fileName.c_str(), std::ios::out);
    if (!str) {
        //std::cerr << "CsoundExporter::write() - can't write file" << std::endl;
        return false;
    }

    str << ";; Csound score file written by Rosegarden\n\n";
    if (m_composition->getCopyrightNote() != "") {
        str << ";; Copyright note:\n;; "
        //!!! really need to remove newlines from copyright note
        << m_composition->getCopyrightNote() << "\n";
    }

    int trackNo = 0;
    for (Composition::iterator i = m_composition->begin();
            i != m_composition->end(); ++i) {

        emit setProgress(int(double(trackNo++) / double(m_composition->getNbTracks()) * 100.0));
        rgapp->refreshGUI(50);

        str << "\n;; Segment: \"" << (*i)->getLabel() << "\"\n";
        str << ";; on Track: \""
        << m_composition->getTrackById((*i)->getTrack())->getLabel()
        << "\"\n";
        str << ";;\n;; Inst\tTime\tDur\tPitch\tVely\n"
        << ";; ----\t----\t---\t-----\t----\n";

        for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {

            if ((*j)->isa(Note::EventType)) {

                long pitch = 0;
                (*j)->get
                <Int>(BaseProperties::PITCH, pitch);

                long velocity = 127;
                (*j)->get
                <Int>(BaseProperties::VELOCITY, velocity);

                str << "   i"
                    // modified by a9a9qq@gmail.com
                    << (*i)->getTrack()*150+200 << "\t"  
                    // end of modification
                << convertTime((*j)->getAbsoluteTime()) << "\t"
                << convertTime((*j)->getDuration()) << "\t"
                << 3 + (pitch / 12) << ((pitch % 12) < 10 ? ".0" : ".")
                << pitch % 12 << "\t"
                << velocity << "\t\n";
// from here - added by a9a9qq@gmail.com 
            } else if ((*j)->isa(PitchBend::EventType)) {
                MidiByte m_msb = (*j)->get<Int>(PitchBend::MSB);
                MidiByte m_lsb = (*j)->get<Int>(PitchBend::LSB);
                
                sprintf(csound_score,"   i%4d %5.2f 1 %5d\n",
                        ((*i)->getTrack())*150+201,
                        convertTime((*j)->getAbsoluteTime()),
                        (m_msb<<7)+m_lsb);
                str<<csound_score;
            } else if ((*j)->isa(Controller::EventType)) {
                MidiByte m_number = (*j)->get<Int>(Controller::NUMBER);
                MidiByte m_value  = (*j)->get<Int>(Controller::VALUE);
                sprintf(csound_score,"   i%4d %5.2f 1 %3d\n",
                        ((*i)->getTrack())*150+202+int(m_number),
                        convertTime((*j)->getAbsoluteTime()),
                        int(m_value));
                str<<csound_score;
            }
// end

            else {
                str << ";; Event type: " << (*j)->getType() << std::endl;
            }
        }
    }

    int tempoCount = m_composition->getTempoChangeCount();

    if (tempoCount > 0) {

        str << "\nt ";

        for (int i = 0; i < tempoCount - 1; ++i) {

            std::pair<timeT, tempoT> tempoChange =
                m_composition->getTempoChange(i);

            timeT myTime = tempoChange.first;
            timeT nextTime = myTime;
            if (i < m_composition->getTempoChangeCount() - 1) {
                nextTime = m_composition->getTempoChange(i + 1).first;
            }

            int tempo = int(Composition::getTempoQpm(tempoChange.second));

            str << convertTime( myTime) << " " << tempo << " "
            << convertTime(nextTime) << " " << tempo << " ";
        }

        str << convertTime(m_composition->getTempoChange(tempoCount - 1).first)
        << " "
        << int(Composition::getTempoQpm(m_composition->getTempoChange(tempoCount - 1).second))
        << std::endl;
    }

    str << "\ne" << std::endl;
    str.close();
    return true;
}

addedというコメントを加えたところが加えたコードである。このパッチを当てたRosegardenで出力すると、こんな風になる。ピッチベンド、CC#とNote-ON以外のMIDI信号はこれまでどおりコメントとして出力される。

;; Csound score file written by Rosegarden

;; Copyright note:
;; Unknown

;; Segment: "pitchbend"
;; on Track: ""
;;
;; Inst	Time	Dur	Pitch	Vely
;; ----	----	---	-----	----
;; Event type: clefchange
;; Event type: rest
   i 201  1.10 1  4491
   i 201  1.15 1  5020
   i 201  1.25 1  5548
   i 201  1.27 1  6077
   i 201  1.31 1  6605
   i 201  1.34 1  7134
   i 201  1.38 1  7662
   i 201  1.39 1  7926
   i 201  1.42 1  8192
   i 201  1.69 1  8456
   i 201  1.70 1  8984
   i 201  1.73 1  9513
   i 201  1.74 1 10041

;; Segment: "modulation(ModulationMSB)"
;; on Track: ""
;;
;; Inst	Time	Dur	Pitch	Vely
;; ----	----	---	-----	----
;; Event type: clefchange
;; Event type: rest
   i 353  1.43 1   4
   i 353  1.44 1   6
   i 353  1.46 1   8
   i 353  1.49 1  10
   i 353  1.50 1  12
   i 353  1.51 1  14
   i 353  1.54 1  16
   i 353  1.55 1  18
   i 353  1.58 1  20
   i 353  1.59 1  22
   i 353  1.60 1  24
   i 353  1.62 1  26
   i 353  1.63 1  28
   i 353  1.64 1  30
   i 353  1.66 1  32

;; Segment: "DataEntry(mainvolume MSB)"
;; on Track: ""
;;
;; Inst	Time	Dur	Pitch	Vely
;; ----	----	---	-----	----
;; Event type: clefchange
;; Event type: rest
   i 509  1.62 1 105
   i 509  1.65 1 104
   i 509  1.66 1 103
   i 509  1.68 1 102
   i 509  1.69 1 101
   i 509  1.70 1 100
   i 509  1.72 1  98
   i 509  1.73 1  96
   i 509  1.74 1  95
   i 509  1.74 1  93

;; Segment: "1(CC#=10 pan MSB)"
;; on Track: ""
;;
;; Inst	Time	Dur	Pitch	Vely
;; ----	----	---	-----	----
;; Event type: clefchange
;; Event type: rest
   i 662  1.12 1 125
   i 662  1.17 1 126
   i 662  1.22 1 127
   i 662  2.14 1 120
   i 662  2.19 1 105
   i 662  2.24 1  89
   i 662  2.29 1  77
   i 662  2.34 1  65
   i 662  2.40 1  55
   i 662  2.45 1  44
   i 662  2.50 1  33
   i 662  2.55 1  22
   i 662  2.60 1  13
   i 662  2.66 1   5
   i 662  2.71 1   3
   i 662  2.76 1   1
   i 662  3.22 1   0
;; Event type: rest
   i 662  4.24 1   3
   i 662  4.29 1   9
   i 662  4.34 1  17

t 0 120

e

*1:En(nは0〜15のチャネル)

*2:Bn+CC#(0-119)CC#が120以降はオールサウンドオフとか、モードを変更するもの

*3:0:バンクセレクトMSB、1:モジュレーションMSB、2:ブレスコントロールMSB、4:フットコントロールMSB、5:ポルタメントタイムMSB、6:データエントリMSB,7:メインボリュームMSB、8:バランスMSB、10:パンMSB、11:エクスプレッションMSB、12:エフェクトタイプセレクタ1、13:エフェクトタイプセレクタ2、16:汎用1MSB、17:汎用2MSB、18:汎用3MSB、19:汎用4MSB。20以降はMSBではなくLSBとなって同じ種類の信号。64〜119まで色々続く

*4:csoundの楽器の番号には最大値は200(The maximum instrument number used to be 200)とマニャーアルにある。しかし、楽器場号を10000にしてcsound 5.09で実験してみたが動いたのでその制約は忘れる