Найти тему

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

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

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

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

Работа с KSP и скриптами

Давайте посмотрим на основные элементы пользовательского интерфейса KSP:

Изображение с мануала
Изображение с мануала

Script module: Эта область похожа на обычный модуль Kontakt. Вверху вы найдете пять вкладок для переключения с одного скрипта на другой (вы можете загрузить до пяти модулей скриптов на инструмент). Модуль сценария не обязательно должен содержать какие-либо элементы графического интерфейса пользователя; сценарий может иметь определенную функцию и пустой интерфейс. Позже вы научитесь создавать элементы пользовательского интерфейса.

Bypass: активирует/деактивирует скрипт.

Script: Используйте это всплывающее меню для загрузки и сохранения сценариев.

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

Script edit area: Эта область открывается, если вы нажмете на Edit в модуле сценария. Здесь вы можете писать, вставлять и просматривать реальный код скрипта.

  • Открывайте Script edit area только в том случае, если вам нужно просмотреть или отредактировать код, в противном случае держите ее закрытой для экономии ресурсов ЦП.

Lock with Password: Нажмите эту кнопку, чтобы ввести пароль, чтобы другие пользователи не могли просматривать или изменять ваши сценарии.

10

Title for this Script: В это поле можно ввести название сценария. После этого заголовок появится на вкладке. Обратите внимание, что сценарии могут иметь разные имена для заголовков и имен файлов.

Apply: Нажмите «Apply», чтобы активировать скрипт. KSP проверяет синтаксис кода и, если сообщений об ошибках нет, вы готовы к работе. Светодиод слева от кнопки «Apply» загорается оранжевым всякий раз, когда вы вносите изменения в сценарий, а сценарий еще не активирован.

Script status line: Если вы допустили ошибку при наборе скрипта, KSP выдаст сообщение об ошибке в этой строке и выделит неправильную строку в области редактирования.

Kontakt status line: В этой строке будут выводиться все сообщения скрипта, сгенерированные функцией message(), а также ошибки, возникающие при воспроизведении скрипта. Не знаете, что такое функция? Не волнуйся, ты увидишь.

11

Базовое руководство по написанию сценариев

Генерация нот MIDI: простой аккомпаниатор

Теперь мы готовы перейти непосредственно к программированию. Эта глава познакомит вас с самыми основными (но очень важными) процедурами программирования. Кроме того, вы получите представление о многих темах, которые будут рассмотрены далее в этом руководстве.

Мы начнем с простого сценария, демонстрирующего одну важную и мощную функцию скриптового движка: способность генерировать «artificial» MIDI-события. Итак, приступим!

Откройте Kontakt, загрузите инструмент по вашему выбору, откройте редактор скриптов и скопируйте следующий текст в редактор скриптов:

on note

play_note(60,120,0,-1)

end on

После нажатия "Apply" скрипт анализируется и (если вы не допустили ошибок при копировании...) готов к использованию. Сыграйте несколько нот на клавиатуре; каждая сыгранная нота будет сопровождаться нотой C3 со скоростью 120.

Отлично! Я ВСЕГДА хотел, чтобы каждая нота, которую я играю, сопровождалась C3…

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

Итак, как это работает?

Всякий раз, когда вы проигрываете ноту, KSP обрабатывает определенную часть сценария. Эти части называются callbacks (обратный вызов). То, что вы написали выше, это так называемый note callback.Обратный вызов ноты — это раздел сценария, который выполняется всякий раз, когда вы проигрываете ноту. «Executed» означает, что KSP интерпретирует каждую строку сверху вниз.

Итак, давайте проанализируем, что мы сделали построчно:

on note: это отмечает начало обратного вызова ноты, т. е. указывает KSP интерпретировать следующие строки кода при каждом воспроизведении ноты.

play_note (60,120,0,-1): это первая команда, которую выполняет KSP (в данном случае она также единственная). Мы будем называть такую команду function. Эта функция генерирует миди-ноты. В этом случае он генерирует C3 (номер ноты 60) со скоростью 120. См. ниже полное определение этой функции.

end on: это отмечает конец обратного вызова.

Опять же, имейте в виду, что этот обратный вызов запускается только примечаниями (поскольку это обратный вызов примечания), поэтому он не будет генерировать никаких примечаний, например, при перемещении колеса модуляции. Конечно, KSP распознает больше, чем этот тип обратного вызова. Вы узнаете еще об одном в этой главе, а об остальных в разделе «Callbacks» (обратные вызовы).

12

Вы, наверное, уже все поняли, кроме тех причудливых цифр в play_note(). Эти числа называются parameters и каждой функции нужны параметры для правильной работы. Функции могут иметь один, два, три и более параметров. В таком случае play_note() имеет четыре параметра и для работы всегда нужны четыре параметра.

Вот полное определение play_note() function (в этом руководстве вы встретите множество определений функций, поэтому мы также познакомим вас с общим форматом такого определения):

play_note(<номер ноты>,<скорость>,<смещение семпла>,<длительность>)

play_note(<note-number>,<velocity>,<sample-offset>,<duration>)

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

< note-number > номер создаваемой ноты (0-127)

< velocity > скорость сгенерированной ноты (1 -127)

<sample-offset> этот параметр указывает смещение в выборке в микросекундах

Пожалуйста, обрати внимание: этот параметр не работает в режиме DFD - только в

режиме сэмплера!

< duration > определяет длину сгенерированной ноты в микросекундах

этот параметр также принимает два специальных значения:

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

0: воспроизводится весь семпл

Теперь мы можем полностью понять, что делает наш скрипт:

• он играет ноту с номером ноты 60: play_note(60,120,0,-1)

• со скоростью 120: play_note(60,120,0,-1)

• сэмпл будет воспроизводиться с начала: play_note(60,120,0,-1)

• и сэмпл будет иметь ту же длительность, что и нота, вызвавшая обратный вызов: play_note(60,120,0,-1)

Возможно, сейчас самое время сделать перерыв и поиграть с тем, что вы уже узнали; исследовать play_note() функцию, введя другие значения или даже добавив больше play_note() функции для скрипта.

Не забывайте: полезно читать руководство, но, как и в случае с любым другим языком программирования… обучение на практике — это ключ к успеху!

Резюме

Заявление "on note" знаменует собой начало или note callback (обратный вызов ноты). Сallback — это раздел внутри скрипта, который "called back" (вызывается т.е. выполняется) в определенное время. В нашем примере обратный вызов выполняется всякий раз, когда программа получает сообщение о примечании, поскольку это обратный вызов примечания. Затем обработчик сценариев интерпретирует каждую строку сценария сверху вниз, пока не достигнет "end on" (закончить) оператор, который определяет конец обратного вызова. Функция play_note() генерирует искусственные ноты MIDI.

13

Встроенные переменные: создание простого октавера

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

Теперь это действительно должно измениться, верно?

Пожалуйста, введите следующий скрипт:

on note

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

end on

Сыграйте несколько нот на клавиатуре, каждая сыгранная вами нота будет сопровождаться октавой ниже с той же скоростью исходной ноты; Вы только что построили простой октавер!

Итак, как мы это сделали?

Этот небольшой сценарий знакомит вас с двумя новыми элементами: variables и operators.

Подробную информацию об этих двух концепциях вы найдете далее в этом руководстве, а пока давайте остановимся на следующем:

$EVENT_NOTE (СОБЫТИЕ_НОТА), он содержит номер исходной сыгранной ноты. Поэтому, когда вы играете C3 (60) на клавиатуре, $EVENT_NOTE будет 60.

$EVENT_VELOCITY, (СОБЫТИЕ_СКОРОСТЬ ) он несет номер скорости исходной сыгранной ноты. Итак, когда вы играете ноту со скоростью 110, $EVENT_VELOCITY будет 110.

Вы также найдете оператор «-». И да, он работает именно так, как вы ожидаете: он вычитает значение. Итак, первый параметр play_note function() –который обозначает номер сгенерированной заметки – это: $EVENT_NOTE – 12; поэтому, когда вы играете C3 (60), скрипт будет выводить C2 (48) (поскольку 60 - 12 = 48) с той же скоростью, что и нота, которую вы сыграли!

Опять же, пора экспериментировать: побаловаться с разными транспозициями (т.е. с другими числами вместо 12), изменить скорость сгенерированной ноты (например, $EVENT_VELOCITY – 20), использовать более одного play_note() function; вы получите идею.

Резюме

Встроенные переменные — очень важные и мощные вещи. Их нельзя объявить, но они «всегда есть». Здесь мы рассмотрели две важные встроенные переменные: $EVENT_NOTE а также $EVENT_VELOCITY. KSP предоставляет множество различных встроенных переменных. Полный список всех встроенных переменных можно найти в справочном разделе этого документа.

14

Переменные управления пользовательским интерфейсом и обратный вызов инициализации: создание простого Harmonizer (гармонизатора)

Теперь давайте расширим наш небольшой скрипт:

Было бы здорово, если бы у вас была какая-то ручка, с помощью которой вы могли бы указать расстояние между сгенерированной нотой и исходной нотой; может ручка с надписью "Interval" (Интервал)?

Пожалуйста, введите следующий скрипт:

on init

declare ui_knob $Interval (-12,12,1)

$Interval := 7

end on

on note

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

end on

Сыграйте несколько нот, каждая нота будет аккомпанировать на квинту выше, идеально подходит для озвучивания фильма с Беном Харлом…

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

Изображение с мануала
Изображение с мануала

Появилась симпатичная ручка с надписью «Interval». Теперь сыграйте несколько нот и установите ручку на разные значения: теперь вы можете сами указать величину транспонирования, так что вы превратили свой октавер в простой гармонизатор!

Но как мы это сделали?

Проще говоря, мы объявили элемент GUI (в данном случае ручку), мы сказали KSP, что эта ручка имеет диапазон от -12 до 12 и что ее начальное значение должно быть 7, и, наконец, мы сказали KSP, что он должен добавить этот номер к номеру ноты, которую мы играем, и сгенерировать новую ноту.

Этот скрипт знакомит вас с тремя новыми элементами: init callback (обратный вызов инициализации), declaration of UI control variables (добавление управляющих переменных пользовательского интерфейса) и assignment of values to variables (привязка значений к переменным).

Давайте углубимся в это:

on init ... end on

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

15

init cal lback, который выполняется, когда вы нажимаете «Apply». Он содержит все инициализации, объявления переменных, элементы пользовательского интерфейса и многое другое. init cal lback таким образом, интерпретируется только один раз, тогда как обратный вызов ноты интерпретируется всякий раз, когда вы играете ноту. Очевидно, что если вы хотите иметь элемент графического интерфейса, такой как ручка, в своем модуле, эту ручку нужно создать, но ее нужно создать только один раз. Вот почему, например, все элементы GUI могут быть объявлены ("created" (созданы)) только в init callback. Начало обратного вызова инициализации помечается при init и его конец отмечен end on (как обратный вызов заметки (note callback)).

А UI control variable, это user def ined var iable. Переменная определенная пользователем. Язык сценариев Kontakt различает встроенные переменные и пользовательские переменные.

В нашем примере мы создали ручку, которая в принципе является определяемой пользователем переменной, где вы можете изменить значение переменной, изменив ручку в области модуля сценария.

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

объявить ui_knob $<имя-переменной> (<min>,<max>,<отношение отображения>)

declare ui_knob $<variable-name> (<min>,<max>,<display-ratio>)

создать ручку пользовательского интерфейса

Пока не беспокойтесь о различных параметрах. Вы узнаете их позже.

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

$Interval := 7

знак равно означает задание. В нашем примере это говорит: «Присвойте значение 7 переменной с именем Interval».

Таким образом, когда выполняется обратный вызов инициализации (при нажатии "Apply" (Применить)), создается ручка, и ручка инициализируется на 7. Когда вы играете ноту, это значение затем добавляется к номеру сыгранной вами ноты, поэтому, когда вы устанавливаете Interval на -12 и играете C3 (60), сопровождающая нота будет C2 (48), так как $EVENT_NOTE + $Interval равно 60 + (-12) равно 48!

Мы заканчиваем тонкой вариацией. К настоящему времени у вас должно быть достаточно времени, чтобы понять, что он делает:

on init

declare ui_knob $Interval (-12,12,1)

$Interval := 7

declare ui_knob $Velocity (1,127,1)

$Velocity := 60

end on

on note

play_note($EVENT_NOTE + $Interval,$Velocity,0,-1)

end on

16

Резюме

init cal lback вызывается, как только сценарий успешно проанализирован. Это происходит именно тогда, когда вы нажимаете кнопку «Apply» в окне редактора скриптов.

Ручка (knob) — это переменная управления пользовательского интерфейса, которая является типом определяемой пользователем переменной. Элементы пользовательского интерфейса создаются в обратном вызове init.

":=" знак равно отмечает присваивание, значение справа присваивается переменной слева от этого знака.

17

Отладка и настройка: завершение сценария

В этой последней части главы мы начнем с небольшой вариации предыдущего скрипта:

on init

declare ui_knob $Interval (-12,12,1)

declare ui_knob $Velocity (-64,64,1)

$Interval := 7

$Velocity := 20

end on

on note

play_note($EVENT_NOTE+$Interval,$EVENT_VELOCITY+$VELOCITY,0,-1)

end on

С ручкой с надписью Velocity теперь вы можете изменить скорость сгенерированной ноты; например, если установлено значение -10, когда вы играете ноту со скоростью 100, сгенерированная нота будет иметь скорость 90.

Ничего страшного, почему бы нам просто не заняться чем-нибудь новым?

Теперь вы думаете, что мы закончили с нашим маленьким сценарием. Увы, нет …

Попробуйте это: установите Velocity до максимального значения 64 и сыграйте ноту на клавиатуре как можно громче. Затем взгляните на статус Контакта (в левом нижнем углу окна Контакта):

Изображение с мануала
Изображение с мануала

Ой, у нас проблема. Что случилось?

Ну, возможно, вы играли ноту со скоростью 100 или больше, Velocity. К этой скорости добавлено значение ручки 64, и в итоге вы получите значение скорости, намного превышающее 127, которого нет в MIDI. Так что KSP так дружелюбно напоминает вам, что было сказано играть ноту с такой скоростью, которая просто невозможна. В этом случае KSP будет обрабатывать play_note() функцию со скоростью 127 и вывести сообщение об ошибке.

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

18

Взгляните на следующее:

on init

declare ui_knob $Interval (-12,12,1)

declare ui_knob $Velocity (-64,64,1)

$Interval := 7

$Velocity := 20

message ("")

end on

on note

if ($EVENT_VELOCITY+$VELOCITY > 127)

play_note($EVENT_NOTE+$Interval,127,0,-1)

else

play_note($EVENT_NOTE+$Interval,$EVENT_VELOCITY+$VELOCITY,0,-1)

end if

end on

Снова установите Velocity установите ручку на 64 и сыграйте громкую ноту; сообщение об ошибке не будет выведено.

Итак, давайте пройдемся по этому сценарию. Он содержит два новых элемента: message()(сообщение()) функции if…else…end (если… иначе…конец), и контрольное заявление.

Функция сообщения записывает текст в статусную строку Контакта. Вот полное определение.

сообщение(<число,переменная или текст>)

message(<number,variable or text>)

отображать числа, значения переменных и текст в строке состояния в Контакте

< number > отображать отдельные числа: message (2), message (128)

< variable > отображать значение переменной: message ($EVENT_VELOCITY)

< text > отображать текст: message ("информация о скрипте") –вы должны поместить текстовую строку в кавычки

Примечания

вы также можете использовать комбинации вышеперечисленного:

message ("Velocity: " & $EVENT_VELOCITY)

вы должны использовать оператор & для объединения элементов

message() функция может быть чрезвычайно полезна в сценариях, когда вы сталкиваетесь с проблемами и вам нужно получить определенную информацию. Обратите внимание, что в статусной строке Контакта может отображаться только одно сообщение, поэтому вы всегда будете видеть последний вывод message().

Если вы хотите вывести сообщение пользователю, пожалуйста, не используйте message() функцию.

Существует метка GUI (объясняется позже), которая более полезна для этих целей.

Представьте, что у вас загружено 16 инструментов, каждый с 5 скриптами: если каждый скрипт выводит

сообщения (что-то вроде: этот скрипт создал xxx, мастер скриптинга!), это может ОЧЕНЬ раздражать.

В нашем примере мы написали: message("") (т.е. ничего) в обратном вызове инициализации; это полезно, если вы хотите очистить предыдущие сообщения об ошибках.

19

Сделайте привычкой писать message("") в init callback(обратном вызове инициализации). Тогда вы можете быть уверены, что все предыдущие сообщения (по сценарию или по системе) удалены и вы видите только новые сообщения.

Следующим новым элементом является if…else…end.

if (если) условие $EVENT_VELOCITY + $Velocity > 127 true (истина) (т.е. сумма больше 127), скрипт выведет:

play_note($EVENT_NOTE+$Interval,127,0,-1))

If not (если нет) (т.е. сумма равна или меньше 127), скрипт выведет:

play_note($EVENT_NOTE + $Interval, $EVENT_VELOCITY+$VELOCITY,0,-

1)

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

Итак, теперь мы можем быть уверены, что play_note() функция всегда будет обрабатывать правильные значения скорости (например, в диапазоне от 1 до 127).

Круто… достаточно умный разговор. Мы, наконец, закончили?

Нет.

Вы действительно уверены, что этот скрипт не выдаст никаких сообщений об ошибках? Мы говорили о скоростях, превышающих 127, но разве мы говорили о скоростях ниже 1, которые также невозможны?

Правильно, поверните Velocity установите ручку на -40 и сыграйте мягкую ноту, вы получите такое же сообщение об ошибке, так как скорость в функции play_note() была ниже 1.

20

Без дальнейших комментариев взгляните на следующий скрипт:

on init

declare ui_knob $Interval (-12,12,1) { сумма транспонирования}

declare ui_knob $Velocity (-64,64,1) { сумма Vel для новой заметки}

$Interval := 7 { инициализировать до идеальной квинты}

$Velocity := 20 { инициализировать до 20: все сгенерированные ноты
становятся громче}

message (" ") { очистить строку статуса контакта}

end on

on note

{ проверить, если Vel больше 127}

if ($EVENT_VELOCITY+$VELOCITY > 127)

play_note($EVENT_NOTE+$Interval,127,0,-1)

else

{ проверить, меньше ли Velocity 1}

if ($EVENT_VELOCITY+$VELOCITY < 1)

play_note($EVENT_NOTE+$Interval,1,0,-1)

else

play_note($EVENT_NOTE+$Interval,$EVENT_VELOCITY+$VELOCITY,0,-1)

end if

end if

end on

Теперь ваш скрипт должен работать как положено и не будет выдавать никаких сообщений об ошибках.

Кстати, вышеуказанное объявление «Без дальнейших комментариев» не совсем так. На самом деле, наш сценарий теперь действительно имеет много комментариев...

Взгляните на редактор скриптов:

Изображение с мануала
Изображение с мануала

Все, что вы пишете между фигурными скобками: {........} — это комментарий; не интерпретируется KSP

(в редакторе скриптов комментарии выделены). Цель комментариев — просто помочь вам структурировать код, чтобы в любой момент вы знали, что делаете.

21

Будьте умнее, используйте комментарии!

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

Кроме того, не ожидайте, что кто-то поможет вам с вашими сценариями, когда вы столкнетесь с проблемами, если ваш сценарий не имеет комментариев; просто слишком сложно смотреть на 500 строк чужого кода без каких-либо комментариев.

Мой скрипт запущен и работает. Что-нибудь еще?

Да, еще кое-что, и на сегодня мы закончили.

Загрузите скрипт и найдите красивую комбинацию Interval и Velocity вашего будущего музыкального шедевра. Сохраните патч (или ваш проект, если вы работаете в хост-секвенсоре) и снова откройте его: все ваши настройки в модуле сценария будут потеряны!

Почему?

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

Вот это просто здорово — совсем не вспоминать со сценариями?

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

22

Поэтому можно сделать переменные persistent. Попробуйте этот скрипт:

on init

{----- Элементы интерфейса -----}

declare ui_knob $Interval (-12,12,1) { Сумма транспонирования}

declare ui_knob $Velocity (-64,64,1) { Сумма Vel для новой заметки}

{----- Инизиализация -----}

$Interval := 7 { инициализировать до идеальной квинты}

$Velocity := 20 { инициализировать до 20: все сгенерированные ноты
становятся громче}

{----- Отзыв -----}

make_persistent ($Interval) { сохранить состояние ручки Interval}

make_persistent ($Velocity) { сохранить состояние ручки Velocity}

message("") { очистить строку статуса Kontakt}

end on

on note

{ проверить, если Vel больше 127}

if ($EVENT_VELOCITY+$VELOCITY > 127)

play_note($EVENT_NOTE+$Interval,127,0,-1)

else

{ проверить, меньше ли Velocity 1}

if ($EVENT_VELOCITY+$VELOCITY < 1)

play_note($EVENT_NOTE+$Interval,1,0,-1)

else

play_note($EVENT_NOTE+$Interval,$EVENT_VELOCITY+$VELOCITY,0,-1)

end if

end if

end on

Загрузите скрипт, внесите в него некоторые изменения, сохраните патч и перезагрузите патч: все на своем месте!

Это достигается использованием функции make_persistent(); эта функция должна быть записана в обратном вызове инициализации и должна содержать имя переменной.

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

make_persistent(<variable-name>)

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

Все типы переменных (например, массивы, переменные определяемые пользователем) можно сделать постоянными, а не только переменные управления пользовательского интерфейса.

Вот и все — сценарий наконец готов. Поздравляем!

Теперь дайте ему красивое название, сохраните его и… самое главное… создавайте с ним музыку!

Резюме

Вы можете выполнять команды при определенных условиях с помощью if…else…end. С помощью функции message(), вы можете отображать числа, значения переменных или текст в строке состояния Kontakt. Если вы хотите, чтобы все переменные (например, регуляторы) сохранялись вместе с патчем, используйте функцию make_persistent().

23

Комментарии пишутся в фигурных скобках: {это комментарий}. Они являются неизмеримой помощью для вас и других в понимании кода.

На этом заканчивается наша небольшая глава об основных сценариях (Basic Scripting). С этого момента мы будем двигаться немного быстрее с более полными определениями. Всегда играйте со всем, когда вы его изучаете. Это единственный способ по-настоящему осознать весь потенциал языка сценариев Kontakt. Не бойтесь совершать ошибки. Никаким скриптом Контакт не испортишь…

24

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