JS Live API: 4A Reading MIDI Clips / Ableton Live
Tweet- Original Auther: Adam Murray
- Original Text: JavaScript Live API Tutorials #4-A Reading MIDI Clips
- Translation: Nakanishi, Yusuke
- This article is licensed under the Creative Commons Attribution-Noncommercial-Share Alike license as original contentent is.
この記事は Ableton Live を JavaScript でハックする方法を紹介したシリーズの、三番目記事です。このシリーズでは読者は Ableton Live 9 suite を持っていて、JavaScript にある程度慣れ親しんでいることを前提としています。
This is the 2nd of a series of articles about hacking on Ableton Live with JavaScript. These articles assume you own Ableton Live 9 Suite and are comfortable coding JavaScript.
この記事では MIDI clip 内の note にアクセスする方法を学んでいきます。MIDI clip 操作の記事は二つあって、この記事はその最初のものです。後半では note を変更し、変更した内容を MIDI clip に反映させます。
In this article we'll learn how to access the notes inside a MIDI clip. This material continues in the second half of this article, where we'll modify the notes and write them back to the MIDI clip.
この記事は過去の記事を読んでいることを前提としていますので、わからないことがあれば過去の記事も参照するようにしてください。
This article builds upon the previous articles, so refer back to them if you're having trouble here.
Getting Started
まずは二番目の記事で作成した log() 関数をコピペしましょう。これを使って Live API を詳しく見ていきます。この記事以降は log() 関数がスクリプトに含まれているものとして進めていきます。このコードについてはこれ以降説明しませんので、常にコードの先頭にこの関数を配置してください。
First let's paste in our log() function that we built in article #2: Logging & Debugging. This will help us explore the Live API. The rest of this article assumes the log() function is in your script. I won't repeat this code again, so pretend it's at the top of all the code examples.
function log() {
for(var i=0,len=arguments.length; i<len; i++) {
var message = arguments[i];
if(message && message.toString) {
var s = message.toString();
if(s.indexOf("[object ") >= 0) {
s = JSON.stringify(message);
}
post(s);
}
else if(message === null) {
post("<null>");
}
else {
post(message);
}
}
post("\n");
}
log("___________________________________________________");
log("Reload:", new Date);
今ままでの記事で学習した技術を用いて Clip を扱う方法を理解していきましょう。まずは Live Object Model diagram をみて Clip への path を決定します。live_set
=> view
=> highlighted_clip_slot
と矢印を進んでいくと、現んざい選択されている clip slot にたどり着くことができます。そこまできたら clip の内部に入ることができます。つまり次のような path になります。
Now let's figure out how to work with clips using techniques we learned in the previous article. Take a look at the Live Object Model diagram to determine a path to a clip. Starting at liveset → view, we can follow the highlightedclip_slot arrow to the currently selected clip slot, and then to the clip inside. Like this:
var path = "live_set view highlighted_clip_slot clip";
var liveObject = new LiveAPI(path);
log("path:", liveObject.path);
log("id:", liveObject.id);
log("children:", liveObject.children);
log(liveObject.info);
上記コードを実行した際に、取得した id が 0 になり、Invalid syntax
error が出て、さらに "No object" というメッセージが表示される場合があります。これは、Live session view で clip slot が全く選択されていないか、もしくは slot に clip が一つもない状態のために起きる現象です。ですので clip を作成してその clip を session view 内でクリックし、clip slot が選択された状態にしましょう。そうしてから再度 JavaScript を実行すると Max window には以下のように出力されるはずです。
The first time you try this, you might get an id of 0, an Invalid syntax error, and a "No object" message. That's because you don't have a clip slot selected in Live's session view, or the slot is empty and doesn't contain a clip. So create a clip and click it in the session view grid to select the clip slot. Then re-run the JavaScript and the Max window should show something like:
path: "live_set tracks 0 clip_slots 0 clip"
id: 5
children: canonical_parent,view
id 5
type Clip
description This class represents a Clip in Live. It can be either an Audio Clip or a MIDI Clip\\, in an Arrangement or the Session\\, depending on the Track (Slot) it lives in.
child canonical_parent ClipSlot
child view View
property color int
property end_marker float
property has_envelopes bool
property is_arrangement_clip bool
property is_audio_clip bool
property is_midi_clip bool
property is_overdubbing bool
property is_playing bool
property is_recording bool
property is_triggered bool
property length float
property loop_end float
property loop_start float
property looping bool
property muted bool
property name unicode
property playing_position float
property signature_denominator int
property signature_numerator int
property start_marker float
property start_time float
property will_record_on_start bool
function clear_all_envelopes
function clear_envelope
function deselect_all_notes
function duplicate_loop
function fire
function get_notes
function get_selected_notes
function move_playing_pos
function quantize
function quantize_pitch
function remove_notes
function replace_selected_notes
function select_all_notes
function set_fire_button_state
function set_notes
function stop
Clip への "canonical"(正規)path について:"liveset tracks 0 clipslots 0 clip" が現在アクセスしている Clip への "canonical"(正規)path です。これは、最初のトラックの最初のクリップにアクセスしているということを意味しています。違う Clip を選択していれば異なる path が表示されるでしょう。例えば二つ目のトラックの三番目の Clip にアクセスしていれば "liveset tracks 1 clipslots 2 clip" が path になります。この記事では現在選択している Clip を扱っていきますが、どんな clip や slot にもこの方法を使ってアクセスすることができる、ということは覚えておいてください。
Note the "canonical" path to the clip: "liveset tracks 0 clipslots 0 clip". This means we accessed the first track's first clip. You'll see a different path if you've selected a different clip in Live. We could have, for example, accessed the second track's third clip with "liveset tracks 1 clipslots 2 clip". We'll be working with the currently selected clip in this article, but keep in mind you can go to any specific clip/slot this way.
MIDI Clip 内の Note にアクセスする
Accessing Notes in a MIDI Clip
現在 Session view で選択されている Clip にアクセスする方法はわかりましたので、次は Clip object に対して何ができるのかを見ていきましょう。object.info を先ほど表示した際に、get_notes
という関数がありました。これが使えそうです。MIDI Clip を作成し note を Clip に加えましょう。そしてその Clip が選択された状態で以下のコードを実行します。
Now that we know how to access the currently selected clip in session view, let's see what we can do with the clip object. In the info we logged above, there's a get_notes function that looks interesting. Create a MIDI clip and add some notes to it, and make sure it's selected. Then run this code:
var path = "live_set view highlighted_clip_slot clip";
var liveObject = new LiveAPI(path);
log("path:", liveObject.path);
log("notes:", liveObject.call("get_notes"));
うーん、全然うまく動きませんね。エラーと全く役に立たない値が notes に表示されるだけです。
Uh oh. That doesn't quite work. We get an error and some useless garbage value for the notes:
path: "live_set tracks 0 clip_slots 0 clip"
Invalid syntax: 'get_notes'
notes: 5e-324
残念なことに JavaScript Live API はこういう場合になんの手助けもしれくれませんので、ドキュメントを読むことにしましょう。Live Object Model diagram の "clip" と名前がつけられた四角をクリックしてください。すると Clip object のドキュメントに移動します。その中から get_notes
関数を見つけましょう。そこには次のように書かれています。
Unfortunately, the JavaScript Live API doesn't provide much assistance in this situation, so let's consult the documentation. In the Live Object Model diagram click on the box labeled "clip" to get to the clip object's documentation and look for the get_notes function. Here's what it says:
Parameter:
from_time [double]
from_pitch [double]
time_span [double]
pitch_span [double]
// 指定された area から始まる notes の list を返す
// 出力結果は get_selected_notes と同様である
Returns a list of notes that start in the given area.
The output is similar to get_selected_notes.
これら四つパラメーターは必須ですので、これを関数に対して提供しない場合には、エラーとなってしまいます。ですのでパラメーターを与えることにしましょう。
Those four parameters are required, and we aren't providing them, which is causing the error. So let's add the parameters:
var path = "live_set view highlighted_clip_slot clip";
var liveObject = new LiveAPI(path);
log("path:", liveObject.path);
log("notes:", liveObject.call("get_notes", 0, 0, 256, 128));
Clip 内の全ての note を取得するために、以下のパラメーターを使用しました。
To get all the notes in the clip, we use the following parameters:
- 0 を from_time に与える。これにより、範囲選択を Clip の冒頭から始めることとなる。
- 0 for from_pitch に与える。これにより、一番低い音程から全てを選択範囲とすることとなる。
- 256 を time_span に与える。これにより、Clip の冒頭から 256 拍を取得することとなる。
- 128 を pitch_span に与える。これにより、MIDI pitch の値の全範囲である 0-127 を対象範囲とする。
- 0 for from_time to start at the beginning of the clip
- 0 for from_pitch to start the lowest pitch
- 256 for time_span to get the first 256 beats of the clip
- 128 for pitch_span to cover the full range of MIDI pitch values 0-127
Clip が 256 拍よりも長いことはそうないと思うので 256 beats を指定しましたが、もちろんそれより長い拍を用いることもできます。ただすぐに JavaScript を使って Clip の長さを測定する方法を紹介します。これを使うことでなんとなく長さを指定するのではなく、的確な値を用いることができます。
I'm guessing a clip won't be more than 256 beats. This is just a guess and you could use a higher number if you want. Soon we'll see how to determine the clip length with JavaScript, so we won't have to guess.
このコードを実行しその出力結果をみる限り、うまくいっていそうです!notes に関するデータは、作成した Clip notes によって異なります。
When we run this code, the output is much more promising! You notes data will look different based on your clip's notes:
path: "live_set tracks 0 clip_slots 0 clip"
notes: notes,3,note,60,0,1,127,0,note,62,1,2,66,0,note,64,3,1,87,0,done
notes の部分は何を意味しているのでしょうか?もう一度ドキュメントの get_notes
の項目を見てみると「出力結果は get_selected_notes
と同内容になる」と書かれています。get_selected_notes
のドキュメントには次のような説明があります。
What does the notes data mean? Looking back at the documentation for getnotes, it says "The output is similar to getselectednotes". The getselected_notes documentation explains:
Output:
get_selected_notes notes count
get_selected_notes note pitch time duration velocity muted
...
get_selected_notes done
// count は note の数です
count [int] is the number of note lines to follow.
// pitch は MIDI note number の値です
pitch [int] is the MIDI note number, 0...127, 60 is C3.
// time は note の開始時間を、absolute clip time の拍数で表したものです
time [double] is the note start time in beats of absolute clip time.
// duration は note の長さを拍数で表したものです
duration [double] is the note length in beats.
// velocity は note の velocity です
velocity [int] is the note velocity, 1 ... 127.
// muted の値がもし 1 であれば、その note は inactive の状態です
muted [bool] is 1 if the note is deactivated.
この説明は live.object
Max object のためのものなので、我々が JS Live API で取得した JavaScript のデータとは異なります。しかし非常に近いものではあります。基本的に JS Live API はそれぞれが分離されたデータを返すことはありません。(全てのデータは単一の配列の中に全てが入った状態で返されます)また data の冒頭に get_selected_notes
と付くこともありません。
This doesn't exactly match our JavaScript data because this documentation is for the live.object Max object instead of the JS Live API. It's close though. Basically, the JS Live API doesn't use separate messages (it's all one big array of data), and the data is not prefixed with "getselectednotes".
JavaScript の出力結果を複数行に分けてみると、ドキュメントに書かれていた内容と一致します。冒頭に note がいくつあるかが示されていて、次に note,X,X,X,X,X というまとまりが続きます。この X の部分はそれぞれ pitch, (start) time, duration, velocity, muted state を表しています。
If we split up our JavaScript output into multiple lines, you can see how it matches the documentation. We get a notes count followed by groups of note,X,X,X,X,X where the Xs are numbers representing the pitch, (start) time, duration, velocity, and muted state of each note:
notes,3,
note,60,0,1,127,0,
note,62,1,2,66,0,
note,64,3,1,87,0,
done
このデータは確かに私の Clip の note の内容と一致しています。この数値で表されたデータが Clip の note にどのように対応するのか、時間をとって確認してみましょう。(Live における C3 の note は MIDI pitch 60 です。)
This data corresponds to the notes in my clip. Take a moment to understand how this numeric data corresponds to the notes in the clip. (A C3 note in Live is MIDI pitch 60.)
The Clip Class
では私たちが書いてきたコードをオブジェクト指向の interface にまとめてみましょう。そうすることでアプリケーションの構築がより簡単になります。もし JavaScript のオブジェクト指向プログラミングに慣れていない人は Mozilla の intro to object-oriented JavaScript をチェックしてみてください。
Let's organize our code into an object-oriented interface so we can more easily build some applications around it. Check out Mozilla's intro to object-oriented JavaScript if you are not familiar with this style of JavaScript programming.
function Clip() {
var path = "live_set view highlighted_clip_slot clip";
this.liveObject = new LiveAPI(path);
}
Clip.prototype.getNotes = function(startTime, startPitch, timeRange, pitchRange) {
return this.liveObject.call("get_notes", startTime, startPitch, timeRange, pitchRange);
}
var clip = new Clip();
var notes = clip.getNotes(0, 0, 256, 128);
log(notes);
新しい Clip オブジェクトを作成すると(その際に Class constructor のような動作をする Clip 関数を実行します)Live の session view で選択している Clip を参照する liveObject が作成されます。このオブジェクトの getNotes() メソッドを実行すると Live object の get_notes
を call します。
When we construct a new Clip object (which calls the Clip function that behaves like a class constructor), it creates a liveObject referencing the currently selected Clip in Live's session view. Then we call our getNotes() function, which simply wraps a call to that Live object's get_notes function.
さらに getNotes 関数を改良し、parameters を optional にして、parameters を渡さない場合にはデフォルト値が使われるようにしましょう。こうすることで getNotes() に parameters を何も与えない場合には、Clip の全ての notes を取得するようになります。
We can enhance our getNotes function to make the parameters optional and provide default values. The idea is that calling getNotes() with no parameters should get all the notes in the clip.
function Clip() {
var path = "live_set view highlighted_clip_slot clip";
this.liveObject = new LiveAPI(path);
}
Clip.prototype.getNotes = function(startTime, timeRange, startPitch, pitchRange) {
if(!startTime) startTime = 0;
if(!timeRange) timeRange = 256;
if(!startPitch) startPitch = 0;
if(!pitchRange) pitchRange = 128;
return this.liveObject.call("get_notes", startTime, startPitch, timeRange, pitchRange);
}
var clip = new Clip();
var notes = clip.getNotes();
log(notes);
Parameters のうち startPitch と timeRange の順番を変えている点に着目してください。こうすると getNotes() 関数が startTime と timeRange を最初に parameters として受け取ることになります。こうした理由は、全ての notes を取得したい場合には getNotes() に何も parameters を渡さなければ実現できるし、特定の timeRange の notes を取得したい場合には getNotes(startTime, timeRange) と渡せば実現できるようにしたかったからです。Live API と全く同じような構造にする必要はない、ということはとても重要です。自分たちが作業しやすいようにインターフェイスをデザインすればいいのです。
Note that I've switched the order of the startPitch and timeRange parameters. Our getNotes() function now takes startTime and timeRange first. I usually either want to get all the notes, which can be done by calling getNotes() with no parameters, or I want to get the notes in a particular time range, which can be done by calling getNotes(startTime, timeRange). The important point is we don't have to mirror the Live API. We can design our own interface that works the way we want.
基本的な class の構造ができたので、timeRange の default 値を当てずっぽうに 256 にするのではなく、Clip の実際の長さを適用することにしましょう。そのためには Clip の live object のプロパティである length を活用します。これを getLength() 関数として class に実装しましょう。
Now that we have a basic class structure in place, let's stop guessing about that default timeRange value of 256 and use the actual length of the clip. We can do that by getting the clip's live object's length property, and wrapping it in our own getLength() function:
function Clip() {
var path = "live_set view highlighted_clip_slot clip";
this.liveObject = new LiveAPI(path);
}
Clip.prototype.getLength = function() {
return this.liveObject.get('length');
}
Clip.prototype.getNotes = function(startTime, timeRange, startPitch, pitchRange) {
if(!startTime) startTime = 0;
if(!timeRange) timeRange = this.getLength();
if(!startPitch) startPitch = 0;
if(!pitchRange) pitchRange = 128;
var data = this.liveObject.call("get_notes", startTime, startPitch, timeRange, pitchRange);
return data;
}
var clip = new Clip();
log("clip length:", clip.getLength());
var notes = clip.getNotes();
log(notes);
Clip を扱う class の準備ができましたね。次は note 情報を扱うことにフォーカスし、note 情報そのままの array を直接扱わなくていいようにしていきましょう。
This is a good start to our Clip class. Let's focus on the note data next, so we don't have to deal directly with that raw array of note data.
The Note Class
Live API から取得できる note data は、それぞれの note ごとに 5 つのプロパティを持っています。これら 5 つのプロパティを class で扱う手法を探求していきましょう。
Since the note data coming from the Live API has 5 properties per note, we'll focus on modelling those 5 properties in our class:
function Note(pitch, start, duration, velocity, muted) {
this.pitch = pitch;
this.start = start;
this.duration = duration;
this.velocity = velocity;
this.muted = muted;
}
Note.prototype.toString = function() {
return '{pitch:' + this.pitch +
', start:' + this.start +
', duration:' + this.duration +
', velocity:' + this.velocity +
', muted:' + this.muted + '}';
}
toString() 関数を使って note object の現状がどうなっているかを簡単に確認するための機能を追加しています。さて次は data array を parse して Note object に変換する機能を作りましょう。data array は "notes", count と始まって、最後は "done" で終わる形式になっていることは既に説明しました。そして "notes", count の部分は必要がないので、それ以降の note の情報を loop して値を取得すればいいことになります。
We also added a toString() function to easily see what's going on when we log our Note objects. Now we can parse the raw note data array into Note objects. We know that the data array always starts with "notes", count, ... and ends with "done". Because of that, we don't actually need to look at the notes count value, we can construct a loop to pull out the individual notes' data directly:
// assuming we just did something like:
// var data = clip.getNotes();
var notes = [];
// data starts with "notes"/count and ends with "done" (which we ignore)
for(var i=2,len=data.length-1; i<len; i+=6) {
// each note starts with "note" (which we ignore) and is 6 items in the list
var note = new Note(data[i+1], data[i+2], data[i+3], data[i+4], data[i+5]);
notes.push(note);
}
コードの詳細:i=2 と指定することで最初の二つ("notes" と count)を飛ばして、最初の note を取得する部分からループを始めています。loop の内部では "note" という文字列が入った要素(data[i]
)は必要がないので飛ばして、data[i+1]
から data[i+5]
までの要素を取得しています。loop ごとに i を 6 進めて次の note を取得しています。("note" という文字列が入った要素と、残りの 5 つの要素を合わせて、合計 6 つの要素が各 note ごとにあるからです。)data.length-1
まで達したところで loop を終了させ、最後の "done" という文字列は無視します。
In detail: Starting the loop at i=2 skips over the first two ("notes",count) entries and starts on the first "note" entry. In the body of the loop we ignore the "note" string by skipping data[i] and grabbing data[i+1] through data[i+5] for the 5 note parameters. Then we increment the loop by 6 to get to the next note ("note" + 5 parameters is 6 entries in the array). We stop the loop when we reach data.length-1 to ignore the final "done" string.
Putting it All Together
では今までのコードを全てまとめて Note object を出力してみましょう。
Let's combine all this code to log a list of our Note objects instead of the raw note data array:
//--------------------------------------------------------------------
// Clip class
function Clip() {
var path = "live_set view highlighted_clip_slot clip";
this.liveObject = new LiveAPI(path);
}
Clip.prototype.getLength = function() {
return this.liveObject.get('length');
}
Clip.prototype.getNotes = function(startTime, timeRange, startPitch, pitchRange) {
if(!startTime) startTime = 0;
if(!timeRange) timeRange = this.getLength();
if(!startPitch) startPitch = 0;
if(!pitchRange) pitchRange = 128;
var data = this.liveObject.call("get_notes", startTime, startPitch, timeRange, pitchRange);
var notes = [];
// data starts with "notes"/count and ends with "done" (which we ignore)
for(var i=2,len=data.length-1; i<len; i+=6) {
// each note starts with "note" (which we ignore) and is 6 items in the list
var note = new Note(data[i+1], data[i+2], data[i+3], data[i+4], data[i+5]);
notes.push(note);
}
return notes;
}
//--------------------------------------------------------------------
// Note class
function Note(pitch, start, duration, velocity, muted) {
this.pitch = pitch;
this.start = start;
this.duration = duration;
this.velocity = velocity;
this.muted = muted;
}
Note.prototype.toString = function() {
return '{pitch:' + this.pitch +
', start:' + this.start +
', duration:' + this.duration +
', velocity:' + this.velocity +
', muted:' + this.muted + '}';
}
//--------------------------------------------------------------------
var clip = new Clip();
var notes = clip.getNotes();
notes.forEach(function(note){
log(note);
});
次のような結果が Max window に出力されるはずです。
You should see something like this in the Max window:
{pitch:60, start:0, duration:1, velocity:127, muted:0}
{pitch:62, start:1, duration:2, velocity:66, muted:0}
{pitch:64, start:3, duration:1, velocity:87, muted:0}
Accessing Selected Notes
もう一つ note data にアクセスする機能を追加しましょう。Live API documentation に get_selected_notes
という関数があります。これを使うことで user が選択している note を取得することができます。こうして取得したものは Clip の部分集合となります。
Let's add one more feature for accessing the note data. In the Live API documentation, we saw a getselectednotes function. This opens up some interesting possibilities where we can work with the notes selected by the user, which can be an arbitrary subest of the notes in the clip.
ドキュメントに書いてあるように get_selected_notes
は get_notes
関数と同じ形式の data を返します。ですので先ほど作ったループと同じものを適応すればいいことになります。ソフトウェア開発の伝統に乗っ取って、同じものをもう一度書かないことにしましょう。
As the documentation indicated, getselectednotes returns data in the same format as the get_notes function. This means we can use the same loop to parse the raw note data array into our Note objects. Let's use good software development practice and not repeat ourselves (the DRY principle):
//--------------------------------------------------------------------
// Clip class
function Clip() {
var path = "live_set view highlighted_clip_slot clip";
this.liveObject = new LiveAPI(path);
}
Clip.prototype.getLength = function() {
return this.liveObject.get('length');
}
Clip.prototype._parseNoteData = function(data) {
var notes = [];
// data starts with "notes"/count and ends with "done" (which we ignore)
for(var i=2,len=data.length-1; i<len; i+=6) {
// and each note starts with "note" (which we ignore) and is 6 items in the list
var note = new Note(data[i+1], data[i+2], data[i+3], data[i+4], data[i+5]);
notes.push(note);
}
return notes;
}
Clip.prototype.getSelectedNotes = function() {
var data = this.liveObject.call('get_selected_notes');
return this._parseNoteData(data);
}
Clip.prototype.getNotes = function(startTime, timeRange, startPitch, pitchRange) {
if(!startTime) startTime = 0;
if(!timeRange) timeRange = this.getLength();
if(!startPitch) startPitch = 0;
if(!pitchRange) pitchRange = 128;
var data = this.liveObject.call("get_notes", startTime, startPitch, timeRange, pitchRange);
return this._parseNoteData(data);
}
//--------------------------------------------------------------------
// Note class
// ...
// this class and the rest of the code is the same as before...
新しく作成した getSelectedNotes() 関数で raw data をパースするためのループを、以前の部分からコピペするのではなく、パースする部分を _parseNoteData()
というヘルパー関数に取り出して、これを複数箇所で使うことにしました。"_"
を冒頭に追加したのは、オブジェクト内部で使われる関数の名称においてよくある慣習に従うためです。
Rather than copy and paste the raw note data parsing loop into the new function getSelectedNotes(), we've moved it into a helper function called parseNoteData() where we can reuse the code. I'm using a convention that functions starting with a "" are for internal use in an object (Note: There are ways to enforce that a function be private to a class, but that's out of the scope of this article).
これで、選択されている notes を操作するには以下のように実行してあげればよくなりました。
Now we can do things like this at the bottom of our script, to operate on the selected notes:
var clip = new Clip();
var notes = clip.getSelectedNotes();
notes.forEach(function(note){
log(note);
});
何も出力されていない場合には MIDI Clip 内で note がしっかりと選択されているかどうかを確認してください。
If you don't see any notes logged, make sure you've actually selected some notes inside your MIDI clip.
Next Steps
この記事では MIDI Clip の note data の内容を読み取る方法を扱ってきました。作成したコードを使えば、Clip の note 全てを取得することもできますし、特定の範囲だけを取得することもできますし(getNotes()
関数に optional な値を渡すことで実現できる)、Clip 内で選択されている範囲のものだけを取り出すこともできます。これで MIDI clip の note data にアクセスしたい場合に必要となる機能が揃ったでしょう。
This article dealt with reading the note data our of a MIDI clip. Using the code we built, we can get the notes in the entire clip, a specific subset of the clip (by using the optional parameters to the getNotes() function), or the selected notes in a clip. That should cover our needs for accessing note data in MIDI clips.
次の記事では notes を操作し、その変更を実際に Clip に反映させる方法を取り上げます。
In the next article we will look at how to manipulate the notes and update the clip to actually change the notes.