Найти тему

Язык скриптов Kontakt 2 Перевод часть 3

Изображение с официального сайта
Изображение с официального сайта

Двигаемся дальше! Прошу обратить внимание - дзен режет табуляцию, поэтому скрипты будут выглядеть немножко неприятно.

Не забудьте подписаться - чем больше подписчиков, тем больше переводов))

Основы

Общие правила, касающиеся синтаксиса

Давайте начнем с установления некоторых основных правил синтаксиса языка сценариев (например, как должны быть написаны сценарии, чтобы они выполнялись должным образом):

• каждая команда должна быть написана в одну строку

• между командными строками может быть бесконечное количество пробелов

• между отдельными словами может быть бесконечное количество пробелов

• язык сценариев чувствителен к регистру, поэтому команда play_note() не будет

распознан при написании Play_Note()

• если одна командная строка слишком длинная и поэтому ее трудно читать, вы можете прервать ее, набрав «...» в конце строки. Поскольку оператор сценария всегда должен содержаться в одной строке, нам нужен специальный механизм, чтобы сообщить процессору, что наша строка еще не закончена, если мы захотим ее прервать.

Таким образом, следующие два скрипта идентичны:

on note

if($EVENT_VELOCITY > 100)

message ("Script message: key struck HARD")

else

message("Script message: key struck SOFT")

end if

end on

такой же как

on note

if( $EVENT_VELOCITY > 100)

message(...

"Script message: key struck HARD")

else

message("Script message: key struck SOFT")

end...

if

end on

• синтаксические ошибки отображаются в строке состояния скрипта. Строка с ошибкой отмечена красным.

• сообщения об ошибках во время работы скрипта отображаются в строке состояния Kontakt под браузером Kontakt.

25

Callbacks (Обратные вызовы)

Обратные вызовы — это программы, которые выполняются в определенное время. Если вы нажимаете и удерживаете ноту, выполняется обратный вызов ноты, но отпускание ноты запускает обратный вызов освобождения и так далее.

Существует пять различных типов обратных вызовов:

on init ... end on (при инициализации ... закончить)

начальный обратный вызов, выполняемый при успешном анализе скрипта

on note ... end on (на заметку ... закончить)

обратный вызов примечания, выполняемый всякий раз, когда получено примечание к сообщению

on release ... end on (в выпуске ... закончить)

обратный вызов выпуска, выполняемый всякий раз, когда получено сообщение об отключении примечания

on ui_control (<variable-name>) ... end on

(управление пользовательским интерфейсом (<имя-переменной>) ... закончить)

обратный вызов ui, выполняемый всякий раз, когда пользователь изменяет соответствующий элемент пользовательского интерфейса

on controller ... end on (на контроллере ... закончить)

обратный вызов контроллера, выполняемый всякий раз, когда получено сообщение cc или изменение высоты тона (pitch bend)

(на самом деле есть еще два обратных вызова, используемых для сообщений rpn/nrpn, но эти два обратных вызова можно рассматривать как «специальные» обратные вызовы контроллера).

Вы можете остановить обратный вызов оператором выход:

exit (выход)

немедленно останавливает обратный вызов

Переменные

Вернемся к нашему первому скрипту:

on note

play_note(60,120,0,-1)

end on

Как мы уже говорили, если каждая нота сопровождается средней C3, это не очень эффектно. Итак, если мы хотим, чтобы каждая сыгранная нота сопровождалась новой сгенерированной нотой с такой же скоростью и звучащей на октаву выше, мы пишем:

on note

play_note($EVENT_NOTE + 12,$EVENT_VELOCITY,0,-1)

end on

26

Вместо конкретного номера заметки пишем $EVENT_NOTE + 12,вместо указания

velocity мы набираем $EVENT_VELOCITY. $EVENT_NOTE и $EVENT_VELOCITY так называемые встроенные переменные; они содержат номер ноты и скорость ноты, вызвавшей обратный вызов.

Переменные являются наиболее важной частью языка сценариев. Говоря компьютерными терминами, они называются местами для хранения чисел. У нас есть несколько типов переменных, которые мы обсудим чуть позже, но сейчас важно различать user -defined variables (пользовательские переменные) и built - in variables (встроенные переменные).

Как пользовательские, так и встроенные переменные могут иметь два состояния:

• нормальные переменные, отмеченные знаком доллара ($моя_переменная и $EVENT_VELOCITY) или знак "at" (@мой_текст)

• переменные массива, отмеченные знаком процента (%мой_массив[ ] или же %KEY_DOWN[<note-number>]) или восклицательный знак (!мой_текст_массив[ ])

Обычная переменная может хранить одно целочисленное значение или текстовую строку. Переменная-массив похожа на обычную, но может хранить сразу несколько значений/текстовых строк. Массив — это индексированный список чисел, похожий на таблицу, где каждый индекс x указывает на адрес y. Массив может иметь от 1 до 512 индексов.

Объявление файлов переменных

Все пользовательские переменные должны быть объявлены. Объявление переменной означает, что ее имя регистрируется для последующего использования, а ее значение инициализируется определенным значением. Давайте посмотрим на некоторые примеры объявлений переменных:

on init

declare $first_variable

declare $second_variable := 12

declare const $third_variable := 24

declare %first_array[4]

declare %second_array[3] := (3,7,2)

end on

Так что же делает этот скрипт?

• Первая строка отмечает начало обратного вызова инициализации (вы помните, что обратный вызов инициализации выполняется сразу после успешного анализа сценария).

• Во второй строке обычная переменная с именем first_variable объявляется с помощью оператора declare. Он не имеет значения, назначенного ему пользователем, поэтому он инициализируется нулем.

• Третья строка объявляет обычную переменную с именем second_variable и присваивает ей значение 12 с помощью оператора :=

• Четвертая строка объявляет специальную нормальную переменную: константу с именем третья_переменная. Константа — это почти то же самое, что и обычная переменная, за исключением того, что ее значение нельзя изменить, и она немного более эффективна (поскольку ее не нужно оценивать во время выполнения).

• Пятая строка объявляет массив с именем first_array из четырех элементов, инициализированных нулем.

• Шестая строка объявляет массив с именем second_array с тремя элементами, которые

инициализируются значениями 3, 7 и 2.

• Седьмая строка отмечает конец обратного вызова инициализации.

27

Мы видим, что переменные имеют определенные соглашения об именах: переменные, не являющиеся массивами, должны начинаться со знака доллара, тогда как массивы должны начинаться со знака процента. Синтаксис всегда такой же, как в примере; объявления констант всегда должны включать начальное значение, которое присваивается оператором :=.

Теперь возьмем скрипт сверху (который пока ничего не делает) и попробуем его расширить.

Работа с файлами переменных

Скопируйте следующий скрипт и сыграйте ноту на клавиатуре:

on init

declare $first_variable

declare $second_variable := 12

declare const $third_variable := 24

declare %first_array[4]

declare %second_array[3] := (3,7,2)

end on

on note

play_note ($second_variable + 48,$third_variable + 96,...

%first_array[2] + $first_variable,%second_array[0] - 4)

end on

Этот (довольно бесполезный) скрипт генерирует ту же ноту, что и наш самый первый скрипт: каждая сыгранная нота будет сопровождаться нотой C3 со скоростью 120…

Его цель — показать чрезвычайно базовую идею манипулирования переменными. Над функцией play_note() идентичной play_note (60, 120, 0, -1):

• $second_variable + 48 равно 60

• $third_variable + 96 равно 120

• %first_array[2] равно 0, так как номер индекса 2 указывает на значение 0

• %second_array[0] – 4 равно -1, так как значение индекса указывает на значение 3, из которого вычитается 4

Встроенные переменные — очень важные и мощные вещи. Они не могут быть объявлены, но «всегда есть». Здесь мы рассмотрим две важные встроенные переменные (полный список всех встроенных переменных можно найти в приложении к этому документу):

$EVENT_NOTE

отметить номер события, вызвавшего обратный вызов

$EVENT_VELOCITY

Скорость нажатия ноты, которая вызвала обратный вызов

$EVENT_NOTE содержит значение ноты MIDI-события, которое инициировало содержащий обратный вызов, и $EVENT_VELOCITY - соответствующая ему скорость. Совершенно очевидно, что обе переменные не могут использоваться в обратном вызове инициализации (поскольку обратный вызов инициализации не запускается клавишей и следовательно, не имеет значения ноты или скорости!).

28

Действительные значения нот находятся в диапазоне от 0 до 127, что, очевидно, соответствует C-2 — G8. $EVENT_VELOCITY может содержать значения от 1 до 127. Как видите, встроенные переменные пишутся с заглавной буквы, поэтому будет хорошей идеей, если вы будете использовать маленькие буквы для собственных переменных.

Давайте рассмотрим несколько примеров, которые содержат оба типа переменных.

on init

declare $new_note

end on

on note

$new_note := $EVENT_NOTE + 12

play_note($new_note,$EVENT_VELOCITY,0,-1)

end on

И опять же, этот скрипт сопровождает каждую ноту, которую вы играете с C3 и скоростью

120…

Переменная $new_noteобъявлена и имеет нулевое значение, так как ему не было присвоено значение. При нажатии на ноту обрабатывается обратный вызов ноты, где значение $new_note заменяется выражением $EVENT_NOTE +12. (помните: с помощью оператора := значение левой переменной заменяется значением правой переменной).

Можем ли мы, сделать сценарий, который не будет сопровождать каждую ноту, которую я играю в С?

Мы можем точно. Проверьте следующее:

on init

declare %addNote[12] := (4, 6, 3, 6, 3, 4, 6, 4, 6, 3, 6, 3)

declare $keyClass

end on

on note

$keyClass := $EVENT_NOTE mod 12

play_note($EVENT_NOTE + %addNote[$keyClass],$EVENT_VELOCITY,0,-1)

end on

Сыграйте несколько нот — волшебство, да? Каждая сыгранная вами нота будет сопровождаться нотой, которая действительно хорошо подходит для тональности до мажор…

Прежде чем мы приступим к анализу этого скрипта, давайте сначала обратим внимание на оператор по модулю. Оператор по модулю делит два числа и выводит остаток от деления, например, 14 по модулю 12 равно 2, 128 по модулю 10 равно 8 и так далее.

В нашем примере мы можем увидеть очень распространенное использование оператора по модулю: написав $EVENT_NOTE mod 12 мы можем получить класс высоты тона (т. е. высоту тона, независимую от октавы) сыгранной ноты. Таким образом, всякий раз, когда вы нажимаете любую клавишу D, $keyClassвсегда будет 2.

Допустим, вы играли в D3 (62): теперь эта переменная используется как индекс в массиве %addNote[12]что приводит к новому номеру ноты 62 + 3 = 65, что равно F3.

29

String Variables (Строковые переменные)

Существует два типа переменных, которые хранят текстовые строки вместо целых чисел:

• строковые переменные, объявленные с префиксом @.

• строковые массивы, объявленные с префиксом !

См. следующий скрипт для простого примера:

on init

declare @text

@text := "note played: "

declare !list[12]

!list[0] := "C"

!list[1] := "C#"

!list[2] := "D"

!list[3] := "D#"

!list[4] := "E"

!list[5] := "F"

!list[6] := "F#"

!list[7] := "G"

!list[8] := "G#"

!list[9] := "A"

!list[10] := "A#"

!list[11] := "B"

end on

on note

message(@text & !list[$EVENT_NOTE mod 12])

end on

Persistent variables (постоянные переменные)

В главе «Основные сценарии» команда make_persistent() уже было представлено:

make_persistent(<имя-переменной>)

сохранить значение переменной при загрузке патча

Всякий раз, когда вы загружаете патч или сценарий, обрабатывается обратный вызов инициализации, и постоянные переменные устанавливаются в их сохраненное состояние. Кроме того, значение постоянных переменных также сохраняется при нажатии кнопки «Apply». Поэтому, когда вы загружаете, например, арпеджиатор, вносите некоторые изменения в ритм в графическом интерфейсе, а затем решаете отредактировать код этого конкретного скрипта, вы не потеряете сделанные ранее изменения.

Обратите внимание: если вы загружаете скрипт из меню скриптов, значение постоянных переменных НЕ буферизуется. Это необходимо, поскольку, например, у вас может быть более одного сценария с одинаковым именем переменной.

Итак, если вы загружаете скрипт, который содержит имя переменной $Time (скажем, сценарий delay), внесите некоторые изменения, а затем загрузите другой сценарий, который содержит то же имя переменной (скажем, например, сценарий последовательности), вы не хотите, чтобы изменения первого сценария применялись ко второму сценарию.

30

Script Call Order (Порядок вызова скрипта)

Есть пять сценариев, которые вызываются в порядке возрастания. Первый сценарий получает MIDI-сигналы от клавиатуры или секвенсора, а другие сценарии получают MIDI-сигналы от предшествующего им сценария.

Если одно событие игнорируется в сценарии 1, оно не будет отображаться в следующих сценариях. Если, например, вы создаете ноты в сценарии 1, сценарий 2 будет получать эти ноты, как если бы они игрались на клавиатуре.

Переменные действительны только в сценарии, в котором они появляются, поэтому вы можете, например, использовать переменную $tune_amount независимо в разных сценариях. Поэтому смело можно вставлять скрипты друг за другом, например:

Midi Latch -> Harmonize -> Arpeggiator -> Microtuning

Взгляните на патч Ambient Harmonization.nki который является частью библиотеки Kontakt 2 Патч можно найти по адресу:

Kontakt 2 Library/02 - KSP Instruments/06 - Harmonizer/Ambient Harmonization.nki.

• Первый скрипт фиксирует входящие ноты MIDI.

• Второй сценарий перезапускает эти заметки с определенной скоростью.

• Третий сценарий создает аккорды из повторяющихся нот.

• Четвертый сценарий ограничивает эти аккорды определенной гаммой.

31

Tempo- and time-based scripting (Сценарии, основанные на темпе и времени)

The wait() function (Функция ожидания)

До сих пор все искусственно сгенерированные ноты игрались вместе с сыгранной нотой. Но что, если мы хотим задержать сгенерированную ноту, например, в ситуации с delay или арпеджиатором?

Пришло время поздороваться с очень важным оператором в скриптовом движке: the wait().

wait(<wait-time>) (ждать(<время ожидания>))

приостанавливает обратный вызов на указанное время

< wait-time >

время ожидания в микросекундах

wait() останавливает обратный вызов в позиции скрипта на указанное время. Другими словами, он замораживает обратный вызов (хотя другие обратные вызовы могут быть доступны или обработаны). По истечении указанного периода времени обратный вызов продолжается.

Вся информация о времени (кроме $ENGINE_UPTIME) в Kontakt Script Language измеряется в микросекундах (μsec), поэтому 1000000 μsec равны 1 секунде.

Посмотрим wait() в действии:

on init

declare %addNote[12] := (4, 6, 3, 6, 3, 4, 6, 4, 6, 3, 6, 3)

declare $keyClass

end on

on note

$keyClass := $EVENT_NOTE mod 12

wait(500000)

play_note($EVENT_NOTE + %addNote[$keyClass],$EVENT_VELOCITY,0,-1)

wait(500000)

play_note($EVENT_NOTE + 12,$EVENT_VELOCITY,0,-1)

end on

Сыграйте C3 и удерживайте ноту. Через 0,5 секунды вы услышите E3, а через 0,5 секунды C4. Из-за функции wait(), обратный вызов обрабатывается не сразу, а в течение одной секунды (двух функций wait(), каждая длится 0,5 секунды).

Теперь отпустите C3 и сыграйте и удерживайте E3. Вы услышите аналогичный результат, воспроизводятся G3 и E4. Пока все в порядке.

А теперь нажмите обе клавиши, C3 и E3 вместе. В результате… ну, это не то же самое, что раньше, когда мы играли ноты одну за другой.

32

Что случилось?

Прежде всего, нам нужно знать, что, поскольку каждый обратный вызов длится одну секунду, вторая нота, сыгранная вскоре после (< 1 секунды), вызовет второй обратный вызов, который выполняется параллельно первому обратному вызову! В этом случае два обратных вызова выполняются одновременно, один за другим.

Второй обратный вызов (запущенный E3) присвоит новое значение $keyClass, но эта переменная

также необходима для первого обратного вызова (запущенного C3), что приводит к неправильному

$keyClass - переменная для первого обратного вызова. Итак, нам нужна переменная для каждой ноты, что

приводит нас к новому типу переменных: polyphonic variable(полифоническая переменная).

Polyphonic Variables (Полифонические переменные)

Попробуйте следующее:

on init

declare %addNote[12] := (4, 6, 3, 6, 3, 4, 6, 4, 6, 3, 6, 3)

declare polyphonic $keyClass

end on

on note

$keyClass := $EVENT_NOTE mod 12

wait(500000)

play_note($EVENT_NOTE + %addNote[$keyClass],$EVENT_VELOCITY,0,-1)

wait(500000)

play_note($EVENT_NOTE + 12,$EVENT_VELOCITY,0,-1)

end on

Сейчас $keyClass является полифонической переменной. Если вы хотите объявить полифоническую переменную, вы должны написать полифонический между объявить и имя переменной.

declare polyphonic $<variable-name> (объявить полифоническую $<имя-переменной>)

объявить определяемую пользователем полифоническую переменную для хранения одного целочисленного значения

Сыграйте вместе в C3 и E3. Вы должны услышать более «правильное» арпеджио. Это потому, что каждый обратный вызов имеет свой собственный $keyClass переменной. Обратите внимание, что для этих переменных требуется больше памяти (4 КБ на экземпляр) и их можно использовать только в обратных вызовах примечания и выпуска.

Полифоническая переменная сохраняет свое значение в обратном вызове освобождения соответствующей ноты.

33

Control Statements (Операторы управления)

Часто нам (или, лучше сказать, KSP) приходится принимать решения на основе определенных условий.

KSP знает три типа операторов управления: if, select и while. (если, выберите и пока).

if…else…end (если… иначе… конец)

if…else…end control в следующем примере довольно легко понять:

on note

if($EVENT_VELOCITY > 80)

play_note($EVENT_NOTE+12,$EVENT_VELOCITY,0,-1)

message("loud " & $EVENT_VELOCITY)

else

message("soft " & $EVENT_VELOCITY)

end if

end on

Если (if)условие $EVENT_VELOCITY > 80 верно, скрипт обрабатывает сценарий if; если условие ложное, обрабатывается ветвь else . Еще ветвь необязательна - ее также можно опустить.

Заявление if закрывается термином end if.

select() (Выбрать)

Выбрать является проработанной версией if..else..end:

on note

select($EVENT_VELOCITY)

case 1 to 40

message("Сообщение скрипта: нажата клавиша SOFT")

case 41 to 100

message("Сообщение скрипта: нажата клавиша MEDIUM")

case 101 to 126

message("Сообщение скрипта: нажата клавиша HARD")

case 127

message("Сообщение скрипта: нажата клавиша BRUTAL")

end select

end on

select заявление похоже на else оператора, за исключением того, что он имеет произвольное количество ветвей. Выражение после select ключевое слово оценивается и сопоставляется с единственным case(кейс) ветви. Первый кейс соответствующей ветви выполняется. Кейс ветви могут состоять либо из одного постоянного числа, либо из диапазона чисел (выраженного термином "от х до у").

select() заявление закрывается термином end select.

34

while() (пока)

принципе, while – это непрерывный оператор if . Поэтому его можно назвать while loop (while

цикл или петля).

следующем примере мы используем оператор while для имитации тремоло мандолины.

on note

wait(70000)

while($NOTE_HELD = 1)

play_note($EVENT_NOTE,$EVENT_VELOCITY,0,70000)

wait(70000)

end while

end on

Встроенная переменная $NOTE_HELD равно 1, если клавиша, вызвавшая обратный вызов, все еще

нажата. В противном случае это 0. Пока вы держите ноту, KSP будет проходить через while loop, это означает, что он будет играть ноту, ждать, играть ноту и так далее.

Когда вы отпустите клавишу, while - условие больше не верно, так как $NOTE_HELD выведет 0, так что все внутри while будет проигнорировано, и обратный вызов завершится.

Заявление while () закрывается термином end while.

35

Operators (Операторы)

Boolean Operators (Логические операторы)

Логические операторы используются в операторах if и while, поскольку они возвращают, если условие является истинным или ложным. Ниже приведен список всех логических операторов. x, y и z обозначают цифры, a и b обозначают логические значения.

Логические операторы

больше чем

х > у больше чем

х < у меньше чем

х >= у больше или равно

х <= у меньше или равно

х = у равный

х#у не равный

in_range (х, у, г) истина, если x находится между y и z

не истинно, если a ложно, и наоборот

а и б верно, если верно a и верно b

а или б истина, если верно a или верно b

Arithmetic Operators (Арифметические операторы)

В языке сценариев могут использоваться следующие арифметические операторы:

Арифметические операторы

х + у сложение

х - у вычитание

х * у умножение

х/у деление

х мод у по модулю

- х отрицательное значение

абс(<выражение>) возвращает абсолютное значение выражения (expression)

inc(>выражение>) увеличивает выражение на 1

dec(<выражение>) уменьшает выражение на 1

36

Bit Operators (Битовые операторы)

Можно использовать следующие битовые операторы:

Битовые операторы

х .и. у побитовое и

х .или. у побитовое или

. нет. х побитовое отрицание

sh_left(<выражение>,<биты сдвига>) сдвигает биты в <выражении> на

количество <shift-bits> влево

sh_right(<выражение>,<биты сдвига>) сдвигает биты в <выражении> на

количество <shift-bits> вправо

Функции массива

Для облегчения использования массивов можно использовать следующие функции:

sort(<переменная-массив>,<направление>)

отсортировать массив по возрастанию или по убыванию:

При направлении = 0 эта функция сортирует по возрастанию, при значении, отличном от 0, сортирует по убыванию.

num_elements(<переменная-массив>)

возвращает количество элементов в массиве

Спомощью этой функции вы можете, например, проверить, сколько групп затронуто текущим событием,

используя num_elements(%GROUPS_AFFECTED) .

search (<переменная-массив>,<значение>)

ищет в указанном массиве указанное значение и возвращает индекс

Пример: $а := search (%array, 10)ищет в массиве значение 10 и возвращает первую позицию этого значения в $а. Если значение не найдено, функция возвращает -1.

array_equal(<переменная-массив1>,<переменная-массив2>)

проверить значения двух массивов, true если значения равны

Random Generator (Генератор случайных чисел)

Наконец, есть функция random(), которая генерирует случайные числа между <min-value> и <max-value> ( <минимальное значение>и <максимальное значение>):

random(<min>,<max>) (случайный(<мин>,<макс>))

генерировать случайное число

37

Group Management (Управление группой)

Если в одном инструменте несколько групп, доступ к ним можно получить в скриптовом движке с помощью следующих четырех функций:

disallow_group(<group-index>)

отключить указанную группу, т.е. сделать ее недоступной для воспроизведения

allow_group(<group-index>)

включить указанную группу, т.е. сделать ее доступной для воспроизведения

find_group(<group-name >)

возвращает индекс группы для указанной группы

group_name(<group-index>)

возвращает имя группы для указанной группы

Каждой группе присвоен порядковый номер. Индексы нумеруются от 0 до количества групп минус 1. Если вы не знаете индекс группы, вы можете вспомнить его, используя find_group()(найти_группу).Просто введите название группы в кавычках, и индекс будет возвращен. Функция group_name() работает наоборот.

По умолчанию разрешены все группы (воспроизводятся все). Если вы хотите разрешить только одну группу, лучше запретить все группы с помощью disallow_group($ALL_GROUPS). $ALL_GROUPS это встроенная переменная, которая обращается ко всем группам. Как только все группы будут запрещены, вы можете, например, разрешить первую группу, набрав разрешить_группу (0).

$ALL_GROUPS

обращается ко всем группам в функциях disallow_group() и allow_group()

on note

disallow_group($ALL_GROUPS)

allow_group(find_group("piano_1"))

end on

Группы можно изменить только в том случае, если голос не запущен.

Еще один полезный встроенный массив — %GROUPS_AFFECTED:

%GROUPS_AFFECTED

массив с групповыми индексами тех групп, на которые влияют текущие события Note On или Note Off

С помощью этой функции вы можете, например, проверить, сколько групп затронуто текущим событием,

используя num_elements (%GROUPS_AFFECTED).

38

И еще одна полезная встроенная переменная — $NUM_GROUPS:

$NUM_GROUPS

общее количество групп в инструменте

Эта встроенная переменная возвращает количество групп в инструменте. Это очень полезно при объявлении общего выпадающего меню со всеми именами групп:

on init

declare $count

declare ui_menu $group_menu

while ($count < $NUM_GROUPS)

add_menu_item ($group_menu,group_name($count),$count)

inc($count)

end while

end on

Конечно, если вы сделаете какие-либо изменения в количестве групп в вашем инструменте или если вы измените названия групп в вашем инструменте, изменения в меню отразятся не сразу. Либо нажмите «Apply» еще раз, либо сохраните и перезагрузите инструмент (помните, что обратный вызов инициализации обрабатывается, когда вы нажимаете «Apply», или открываете скрипт из меню «Script», или когда вы загружаете инструмент).

39

На сегодня все, подписываемся и ждем следующую часть!