Создавая синтезатор, разработчики видели в нем не только качественный, конкурентно-способный продукт. Главной целью было создание мощного инструментария, который одновременно был бы «гибким» и простым. Как бы лестно не звучало, но эта работа в очередной раз подтверждает высочайшее мастерство команды NI . Основными особенностями данного инструмента являются простота в обращении и высокая скорость работы с ним. Massive имеет трехстраничный, наглядный интерфейс, и на первый взгляд может показаться интуитивно понятным… но метод научного тыка здесь не даст нужного результата, и надеюсь, что по прочтении мануала ваши познания о Massive по-крайней мере дополнятся.
По большей части это руководство является переведенным, переработанным вариантом оригинального мануала. Возможно, что какие-то моменты я упустил, но основные вещи попытался осветить. В руководстве я не затронул разделы Attributes и Browser…
1.0.0. Интерфейс. Massive имеет трехстраничный пользовательский интерфейс. Browser, Attributes, Synth. В данном разделе описаны основные функции страницы Synth, и разъяснено, как работать с теми или иными средствами управления.
1.0.1. Заголовок. Каждая секция в интерфейсе снабжена заголовком, описывающим его. Например, OSC1 означает, что данная секция непосредственно связана с Осциллятором номер один. Здесь же находится кнопка активации ( светло-голубая = активен / серая = неактивен) секции. В народе известная как «байпасс». Полезной будет в ходе создания тембра. Например, выключив фильтр или блоки эффектов и т.п.…
1.0.2. Всплывающее меню. Большинство секций имеет всплывающее меню, в котором перечислен список соответствующих атрибутов (например, выбор типа фильтра, или эффекта в соответствующих секциях)…
1.0.3. Регуляторы. Основным средством управления параметрами инструмента являются, конечно же ручки (вращающиеся регуляторы/крутилки) и фейдеры (слайдеры/ползунки J). Удерживая клавишу Shift можно осуществлять более точную регулировку, даже колесико мыши нашло применение здесь. Немаловажным фактом является отсутствие каких-либо числовых отображений у большинства параметров. Этим разработчики подчеркнули «строго аналоговый подход». Доверяйте своим ушам.
1.0.4. Контроль модуляций. «Модуляция сигнала — процесс изменения одного сигнала в соответствии с формой, амплитудой другого сигнала». Модулирующая волна – волна (колебание/параметр) воздействующая на несущую (модулируемую) волну (колебание/параметр).
Одним из новшеств в интерфейсе является альтернативный подход к управлению параметров какими-либо источниками модуляций.
Вспомните таких знаменитостей, как FM7 или Albino, z3ta, Minimoog … была отдельная таблица (матрица модуляции), в которой мы имели управляющий и управляемый параметры, а также значение (Amount), величина которого определяла диапазон воздействия управляющего параметра (модулятора) на управляемый параметр…
В Massive такой матрицы модуляции нет. Вернее мы сами её формируем. Теперь давайте разберемся, как это работает. Начнем с очевидного. У нас имеется восемь источников модуляции. Расположены они в центральной секции a.k.a. «Центральное Окно» (стр. Synth). Это четыре огибающие (Env. от Envelopes) и четыре с настраиваемым режимом: LFO, Stepper, Performer. (по умолчанию эта четверка в режиме LFO)
- Для того, чтобы назначить модулятор на управляемый объект (параметр) сперва выделите модулятор (щелчок на крестике справа от названия модулятора ) а затем указателем мыши щелкните в свободном поле (квадратике) под регулятором нужного управляемого параметра. В этом поле отобразится номер источника модуляции (модулятора) соответствующего цвета.
- Далее следует указать «значение». Делается это также просто. Зажимаем нужный «квадратик» и тянем его либо вниз, либо вверх, тем самым, задавая диапазон воздействия модулятора на параметр.
- Все вышеописанные операции можно проделать с помощью всплывающего меню, щелкнув пр. кн. мыши в поле параметра. Убрать или «выключить» модуляцию данного параметра можно с помощью off / mute соответственно.
Небольшой «перелом» на границе регулятора — индикатор, который сообщает о том, что диапазон модуляции выше границы регулятора. Следует отметить, что выше этой границы диапазон модуляции не распространяется, и следовательно, «модуляция не модулирует». Наверняка вы заметили, что у некоторых параметров имеется поле подписанное SC. Аббревиатура SC расшифровывается как Side Chain. В данном случае это никак не связано с компрессорами J. Это поле может использоваться для применения SC модуляции. Тип модуляции, при которой один модулятор воздействует на значение другого. Другими словами, с помощью SC мы сможем управлять «количеством» выбранной модуляции.
Разберемся, как это работает.
- На Wt-Position осциллятора вешаем LFO (в данном случае модулятор #6) и устанавливаем
нужный диапазон. - Затем из секции Macro Control (та, что в правом нижнем углу) берем свободный регулятор
(#2) и вешаем его в поле SC (WT-Position) - Щелчок мыши по небольшой горизонтальной полоске, находящейся под нужным полем
(#6) превратит её в треугольник. Теперь попробуйте регулировать Macro Control (#2). О
чудо. Попробуйте переключить направление треугольника, чтобы понять, как оно
работает. - В качестве SC модулятора можно использовать любой из доступных модуляторов.
Все операции, применимые к вращательным регуляторам применимы к Фейдерам, рядом с которыми находятся соответствующие поля.
1.0.5. «Центральное окно».
Данное окно позволяет переключаться между шестью секциями общих параметров – General Pages: OSC, KTR OSC, KTR FLT, VOICING, ROUTING, GLOBAL. (первый верхний ряд — закладки), и восьмью источниками модуляции – Modulation Pages.
Скорее всего, вы уже знакомы с ними… На первых четырех страницах находятся секции управления огибающими, остальные страницы назначаемые. Для каждой страницы (5-8) можно выбрать три режима: LFO, STEPPER, PERFORMER.
Меню выбора режима выделено розовым.
Ближе познакомимся с функциями этого окна немного позже.
1.0.6. «Макроконтроль».
Данная секция содержит два блока, два типа контроллеров.
Слева находится блок из четырех параметров. Созданы они для работы с MIDI сообщениями:
· KTr – Keytracking. Чем выше играемая нота (слева направо) тем больше амплитуда (и наоборот) модулируемого KTr параметра.
· Vel – Velocity. Чемболее сильный удар тем больше амплитуда модулируемого Vel. параметра.
· AT – Aftertouch. В зависимости от значения послекасания меняется амплитуда модулируемого параметра. (Если ваша MIDI клава поддерживает сию функцию).
· TrR – Trigger Random. За все говорит название. Эта штука каждый раз при нажатии клавиши выбирает случайные величины (амплитуды), в рамках указанного диапазона.
Справа находятся восемь вращающихся регуляторов, которые могут быть использованы для управления какими-либо параметрами. Область применения их достаточно широка. К примеру, нужно заставить изменяться несколько параметров одновременно. Вешаете один из контролеров данной секции на нужные параметры, устанавливаете диапазоны и получается, что одной ручкой вы регулируете несколько параметров… Наряду со всеми вспомогательными функциями, эти контроллеры могут быть полезны в целях упорядочивания ваших часто используемых/автоматизируемых параметров. Следует отметить, что практически любым параметром в Massive можно манипулировать MIDI-Контроллером с помощью MIDI CC…
1.0.7. «Секция осцилляторов».
Здесь зарождается Звук. J Секция включает в себя 3 блока таблично-волновых (Wavetable) осцилляторов, блок модулирующего осциллятора (Mod.Osc.), генератор шума и блок обратной связи (Feedback).
… В отличие от многих других синтезаторов, где звукообразование происходит с помощью генерации базовых волновых форм типа пила, пульс, треугольник, синус и т.д., в Massive реализован таблично-волновой синтез (Wavetable). Этот вид синтеза предполагает использование засемплированных и оцифрованных волновых форм реальных или синтетических инструментов.
Звуковые формы сохранены в памяти и проигрываются с различными скоростями в зависимости от взятой ноты. С помощью таблично-волнового синтеза могут быть довольно просто получены сложные и оригинальные звуки. Особенностью реализации этого метода в Massive является то, что в одной Wavetable может находиться несколько волноформ, плавный переход между которыми осуществляется с помощью регулятора Wt-Position. Если поиграть этим регулятором, можно заметить изменения тембра.Wt-position регулирует так называемый morphing – плавное преобразование семпла.
1.0.8. Управление осциллятором.
В верхней части блока осциллятора, во всплывающем меню можно выбрать одну из 82 «волновых таблиц».
Регулируя Wt-Position, вы плавно проходитесь по всем волноформам, которые содержатся в выбранной таблице. Количество волноформ в одной таблице варьируется от 2 до 128, а то и больше.
Числовое поле «Pitch» позволяет изменять частоту в полутонах и центах (сотых долях полутона). Питч также может быть управляем одним из модуляторов. К примеру эффект вибрато достигается с помощью LFO (частотная модуляция), а арпеджио с помощью STEPPER’а.…
Экспериментируйте с этим параметром, результат может быть непредсказуемым…
Регулятор Intensity, наряду с Wt-position является мощным инструментом изменения тембра. В зависимости от выбранного режима этот параметр ведет себя по-разному. Стоящий по умолчанию режим Spectrum при регулировке Intensity дает схожий с LP (Low Pass) фильтром эффект. Суть все же отличается. При работе фильтром, частоты выше Cutoff «срезаются» с опр. спадом, здесь же происходит «спектральное истощение», другими словами обертона (вышележащие гармоники) постепенно уходят по мере уменьшения параметра Intensity, (говорят: «Падает интенсивность обертонов». В данном случае Intensity в роли Cutoff).
Режимы bend + / bend-/+ /bend- также работают со спектром, но уже в «другом ключе». Для упрощения объяснения ниже приведены изображения волноформы под влиянием различных вариаций Intensity.
1) Исходная волноформа, Intensity по центру.
2) Intensity сдвинут влево.
3) Intensity сдвинут вправо.
Некоторые волны сильно изменяются в данном режиме, а некоторые не особо.
Режим Formant реализует сдвиг формант обертонов. Объясню наглядно. Сверху спектр, снизу
волноформа полученного сигнала.
1.0.9. Amplification and Routing.
Регуляторы Amp управляют выходным уровнем осцилляторов. Каждый осциллятор имеет вертикальный, так называемый “Routing Fader”. Сигнал, после формирования в осцилляторе поступает в секцию фильтров, этот фейдер регулирует количество сигнала идущего на тот или иной фильтр. В крайнем положении весь сигнал осциллятора идет на соответствующий фильтр (F1 сверху либо F2 снизу), в ином случае обрабатывается одновременно двумя фильтрами, в заданном соотношении. (О работе фильтров читайте дальше).
1.1.0. Модулирующий осциллятор.
Этот девайсъ звука не создает. Но может воздействовать на него. Он генерирует синусоиду в пределах определенного диапазона, которая служит модулирующим сигналом четырех видов: Кольцевая модуляция (Ring Mod), Фазовая модуляция (Phase), Позиционная модуляция (Position) и фильтр-частотная модуляция (Filter FM).
Ring Mod. Давайте разберемся, что это такое. В кольцевом модуляторе используется амплитудная модуляция, которая достигается простым перемножением двух сигналов. В данном случае у нас имеется один входной сигнал (с выбранного осциллятора), а второй сигнал вырабатывается внутренним генератором – модулирующим осциллятором (Emo.Osc.). Результат перемножения дает сумму и разность частот этих двух сигналов.
Для пущей наглядности приведен рисунок:
(a)Синус 400 Гц. (входной)
(b)Синус 600 Гц. (генерируемый Mod.Osc.)
(c)Произведение двух сигналов (200 Гц. и 1000 Гц. сигналы сложенные вместе).
В результирующем сигнале мы можем обнаружить компонент высокой частоты (1000 Гц.) наложенный на низкочастотный компонент.(200 Гц). Частоту генератора можно регулировать в поле pitch.
Попробуем создать тембр с биениями, которые будут учащаться/уменьшаться пропорционально высоте ноты. Для начала File > New Sound, далее выбираем таблицу в первом осцилляторе. Пусть это будет самая первая Square-Saw. Затем активируем Mod.Osc., и там же, в матрице указываем Ring Mod > Osc.1, выкручиваем регулятор RM вправо, а Pitch (Mod.Osc.) опускаем до (– 64).
Теперь осталось покрутить Wt-Position на 1 осцилляторе для изменения окраски Phase. По характеристикам фазовая модуляция близка к частотной модуляции. Ежели модулирующий сигнал является синусоидальным, как в данном случае, результаты частотной и фазовой модуляции совпадают. Mod. Osc. Генерирует модулирующее колебание, а сигнал осциллятора является несущим.
Position. Новый уникальный тип модуляции, который был успешно реализован благодаря инновационному таблично-волновому движку Massive.
Filter FM. Фильтр частотная модуляция. Позволяет модулировать частоту среза (Cutoff) одного из фильтров.
Разберем работу Filter FM на примере создания FM-подобного баса:
- В матрице Mod.Osc. Установите Filter FM > 1 (модулирует 1-й фильтр).
- Выберите тип фильтра: Daft.
- Установите значение Pitch (Mod.Osc.) = -12.
- Выберите источник модуляции. В данном случае одну из огибающих (Env), и установите в поле параметра FM of Filter. Сам регулятор поместите в крайнее левое положение, а диапазон установите на «11 часов». Огибающую (Env) настройте следующим образом:
Этим мы добились эффекта «затухающих колебаний» баса. Временем этого затухания управляет Decay в нужной Envelope. Регулируя параметры фильтра, поиграйте в низких октавах…
1.1.1. Генератор Шума a.k.a.Noise.
Шума он не генерирует, а использует волновую таблицу с семплами. Всего их 9. Параметр Color управляет «окраской» шума, перемещая весь его спектр вниз или вверх соответственно (меняет скорость воспроизведения семпла шума). Попробуем найти ему применение. Сделаем «питерскую тарелку». Для этого:
- Установим темп 170 bpm.
- Один из LFO переключим в режим Performer.
- В появившемся внутреннем секвенсоре Performer’а выполним следующие манипуляции: количество шагов сократим с 16 до 4 (Верхняя строка от 1 до 16). Зажимаем 16 и тянем влево до 4; затем в верхнем левом углу окна, в Load Curve выбираем вид кривой; далее, фейдер XFade Seq перемещаем вверх, а в самом левом блоке жмем Sync 1/16 (для синхронизации с внешним секвенсором), а также Restart.
- В секции шума выбираем тип: “metal” и вешаем на Amp. Указываем диапазон: от начала до конца.
1.1.2. Feedback Section. Блок «обратной связи». Направляет сигнал из выбранной точки (FB на странице Routing (схема) центрального окна) на вход указанного фильтра (вертикальный “Routing Fader”справа).
Надпись слева от регулятора Amp. показывает положение, откуда сигнал посылается на вход фильтра.
Разумно использовать данную вещь для получения Distortion-подобных эффектов. Попробуйте размещать FB на странице Routing в разных местах.
1.1.3. Фильтры. Massive имеет 2 отдельные шины фильтров, которые можно «соединить» последовательно либо параллельно (Serial/Parallel). Имеется 11 типов фильтров. Количество сигнала, поступающего с осциллятора на тот или иной фильтр регулируется вертикальным фейдером справа в блоке осциллятора. При параллельном соединении сигналы, поступающие на соответствующие фильтры обрабатывается независимо. При последовательном соединении, сигнал с выхода первого фильтра поступает на вход второго. Уровень выходного сигнала фильтра регулируется фейдером справа в соответствующем блоке. В правой части секции фильтров имеется фейдер “Mix”. Он управляет балансом выходного сигнала всей секции фильтров.
Последовательное соединение будет правильным при следующих настройках: фейдер Ser/Par в верхнем положении, а фейдер Mix в нижнем. Тогда сигнал будет проходить In>F1>F2>Out. Для параллельного соединения переместите фейдер Ser/Par вниз, а Mix по центру (двойной щелчок).
1.1.4. Эффекты разрыва или Insert Effects. В Massive реализовано 2 типа эффектов. Insert и Master эффекты. Первые могут быть расположены в разных, определенных точках цепи посредством разрыва (Insert), а Master эффекты находятся в «конце маршрута» сигнала. Пока остановимся на первой категории.
Разрывы могут располагаться в местах, обозначенных на схеме (Routing) как ins.
Эффекты Разрыва:
· Delay – эффект задержки. По сути, копирует сигнал и накладывает его на «оригинал» с установленным интервалом (Time). Его следует использовать в связке с feedback (Ins > feedback) для получения эхо-подобного эффекта, при этом попробуйте управлять Time с помощью LFO J.
· Sample & Hold – эффект понижения частоты семплирования (дискретизации). Ninendocore J
· Bitcrusher – Эффект понижения разрядности сигнала.
· Frequency Shifter – как гласит название, «сдвигатель частот»… -/+ PitchJ
· HPLP – HI PASS, LOW PASS в сокращении. 2 фильтра в одном (800 pioneer like) фильтры с низким коэффициентом спада, без отсутствия резонансного усиления.
· Sine/Parabolic Shaper – Distortion-подобные эффекты с различными передаточными характеристиками (зависимость выходного сигнала от входного).
1.1.5. Выходной сигнал. В конце «маршрута», сигнал проходит через несколько блоков.
Как видим, на данной схеме, после фильтр-секции сигнал поступает на блок Amp/Pan, затем на блок Master-эффектов (Fx1,Fx2,Eq) и наконец на Master Out.
1.1.6. Секция Amp. Отвечает за панорамирование и уровень выходного сигнала. По умолчанию 4-й источник модуляции (огибающая #4) контролирует амплитуду.
1.1.7.Секция Bypass. Данная секция управляет уровнем Bypass. Bypass направляет «копию сигнала» одного из выбранных осцилляторов (или генератора шума) в обход на выход цепи, минуя фильтры и пр.эффекты, и смешивает его с остальным сигналом. Имеется всего один фейдер, регулирующий уровень bypass сигнала. В окне Routing указываются точки входа и выхода bypass сигнала.
1.1.8. Секция Master Effects. Состоит из последовательно соединенных двух блоков эффектов и эквалайзера. Эффекты классические, типа Delay/Sync.Delay, Reverb, Chorus, Flange, Tube…имеются моно эффекты.
1.1.9. Master Volume. Управляет уровнем выходного сигнала синтезатора. Справа расположен вертикальный индикатор, показывающий текущий уровень сигнала. Следует отметить, что перегруз может привести к нелинейным цифровым искажениям. Следите за уровнем, чтобы он не превышал пиковой отметки. В случае необходимости эффекта перегруза, воспользуйтесь Feedback или внешним плагином…
1.2.0. General Pages a.k.a. Верхний ряд закладок.
В разделе 1.0.5. мы бегло рассмотрели данные страницы, пришло время узнать побольше.
1.2.1. OSC.
Данная страница содержит общие настройки, связанные с Pitch и фазой осцилляторов.
Блок Glide. Glide подразумевает плавный тональный переход от одной ноты к другой. Параметр Time регулирует время перехода. Имеется 2 режима:
В режиме Equal время перехода постоянно, и задается регулятором Time.
В режиме Rate время перехода зависит от того, насколько ноты удалены друг от друга. Чем ближе друг к другу ноты, тем быстрее переход, и наоборот. Следует отметить, что глайд работает лишь в монофоническом режиме. Настраивается это во вкладке Voicing в одноименном блоке. Об этом чуть дальше…(1.2.4.)
Блок Vibrato управляет эффектом вибрато. Попросту говоря, вибрато это колебания питча играемой ноты. Параметр Rate управляет частотой колебаний, а Depth «глубиной» (диапазоном). Над блоком находится кнопка “mono”. Чтобы понять суть, попробуйте зажать две клавиши с небольшим временным интервалом, и, включая/выключая «mono» слушать изменения, при этом установите Depth побольше…
Блок Pitchbend. В верхнем и нижем полях указан диапазон значений (в полутонах), в пределах которых работает соответствующий регулятор (Pitch) на midi клавиатуре/контроллере. В обоих полях можно указывать как отрицательные, так и положительные значения…
Блок Phase управляет положением фазы сигнала осцилляторов и генератора модуляций, другими словами контролирует стартовую позицию волноформы. По горизонтали шкала в градусах. Например поворот фазы одного осциллятора на 180 градусов означает, что волноформа начнет воспроизводиться на половину своей длины позже. Кнопка Restart via Gate позволяет «сбрасывать» положение фазы в соответствии с заданными параметрами каждый раз, когда вы нажимаете клавишу. В ином случае при нажатии клавиши за начальное положение фазы берется текущее положение.
В самой правой части страницы находится интересная штука. Internal Envelope является внутренней огибающей. Эта огибающая может назначаться в рамках текущего окна. Например, для плавного появления вибрато вешаете её на Depth и устанавливаете диапазон.
1.2.2. KTR OSC a.k.a. Key Tracking Osc.
Данная страница позволяет установить зависимость частоты (высоты) осциллятора от играемой ноты. Диагональная линия отображает данную зависимость. По-умолчанию зависимость линейная (режим Linear). В правой части окна находится таблица сопоставления того или иного режима разным осцилляторам и Insert FX. В режиме Off все ноты имеют высоту ноты C5. Режим User позволяет изменить зависимость. Имеется 5 контрольных точек, значения которых могут быть изменены. Пользуйтесь клавишами Shift для более точной настройки, и alt для перемещения точки вдоль вертикальной оси. Двойной щелчок по одной из точек выровняет остальные по уровню этой точки.
1.2.3. KTR FLT a.k.a. Key Tracking Filter.
Key Tracking Filter позволяет установить зависимость параметров фильтра от играемой ноты. В отличие от KTR OSC здесь имеются две линии, отображающие зависимость. Светлая для Cutoff, а темная для второго параметра фильтра. Например для Bandpass фильтра темная линия отображает зависимость параметра Bandwidth… Для каждого фильтра можно установить свою зависимость в соответствующей таблице справа.
Попрактикуемся с применением белого шума J Как известно, белый шум это хаотичный сигнал спектральные составляющие которого равномерно распределены по всему диапазону частот. Это значит, что он не имеет тональности. С помощью Ktr F. Сделаем из шума тональный сигнал. Для этого:
· Выключаем все осцилляторы, и включаем Noise.
· Полностью посылаем шум на первый фильтр.
· Выбираем тип LP2 (Low Pass 12 dB/Oct), резонанс выкручиваем на 15 часов.
· Настраиваем главную огибающую (Env 4) с небольшим по времени, полным затуханием (decay, vel.= 0).
· По вкусу настраиваем Reverb в FX для создания пространства.
В данном случае Cutoff фильтра по умолчанию настроен на частоту ноты До.
1.2.4. Voicing.
Окно Voicing содержит все, что касается работы с голосами и полифонией. Блок «Voicing» в левой части окна позволяет выбрать режим: монофонический/полифонический, указать максимальное количество голосов, количество унисон-голосов.
Поле Unisono показывает количество голосов одной ноты. Поле Max отображает максимально возможное количество одновременно играемых голосов, при этом, если их количество будет превышать Max, то самые ранние голоса уйдут взамен на новые. Важно не путать количество нот, и количество голосов. Например, если Max = 4, а Unisono = 2, то взяв две ноты вы услышите четыре голоса (по 2 на каждую), а по взятии третьей ноты первые две станут одноголосными, в то время как третья будет иметь два голоса. Количество нот изменилось, а количество голосов осталось неизменным … полностью понять суть этих манипуляций вы сможете чуть дальше…
Ниже располагаются три кнопки, позволяющие выбирать между полифоническим, монофоническим и Monorotate режимами.
Полифонический режим подразумевает возможность взятия нескольких нот одновременно, количество нот ограничено полем Max.
Монофонический режим позволяет брать не более одной ноты одновременно. При нажатии последующей клавиши, предыдущая нота «выключится».
Режим Monorotate отличается от монофонического чем-то не принципиальным. В самом низу располагается блок Trigger. Он управляет режимами работы Glide. Always означает, что glide будет срабатывать всегда когда нажимается клавиша. Legato означает, что glide будет срабатывать тогда, когда последующая нота взята во время игры предыдущей.
Legato Triller по сути тот же Legato, отличие только в том, что при отпускании последующей ноты зазвучит предыдущая (если она все еще зажата). Хорош для игры трелью…
Справа от секции Voicing находится секция Unisono Spread , которая управляет расхождением голосов указанных в поле Unisono. Другими словами, здесь определяется, как все голоса одной ноты будут расходиться между собой.
Имеется три основных параметра управления:
1) Pitch /Cutoff
2) Wavetable Position
3) Panorama Position
У каждого из параметров есть слайдер в дополнение к двум числовым дисплеям…
1) Pitch/Cutoff . Слайдер в этом блоке управляет «шириной» расхождения голосов. В числовых полях указывается максимальный диапазон расхождения. Голоса, как нестранно распространяются равномерно. Таким образом, чем большее количество голосов имеем, тем ближе они друг к другу. Диапазон расхождения задается в полутонах и центах (сотых долях полутона) и может принимать значения от -12 до +12 полутонов (12 полутонов образуют полную октаву). Правее можно выбрать один из двух методов «расхождения» (расстройки). Режим Centered подразумевает симметричное распространение голосов в обе стороны относительно играемой ноты. Другими словами при увеличении «ширины» расхождения все голоса расходятся симметрично относительно играемой ноты, при этом сама играемая нота отсутствует. Поясним на примере:
С данными настройками 4 голоса будут принимать значения высоты согласно приведенному ниже списку:
Голос 1: -75 cents
Голос 2: -25 cents
Голос 3: +25 cents
Голос 4: +75 cents
Как видим, играемая нота не наблюдается, лишь голоса распределились вокруг него с шагом 50 центов.
Режим Chord. Также осуществляет расхождение голосов, но в отличие от Centered оставляет первым голосом играемую ноту.
С данными настройками 4 голоса будут принимать значения высоты согласно приведенному ниже списку:
Голос 1: 0 (original pitch)
Голос 2: +12 semitones
Голос 3: -12 semitones
Голос 4: +24 semitones
Играемая нота и распределенные голоса с шагом в 1 октаву. Ниже Расположен блок Wavetable Position. Диапазон выражен в процентах, слайдер регулирует распределение WT-Position для разных голосов.
Третий блок Pan Position служит для распределения голосов в пространстве…
Поэкспериментируйте с Voicing, сделайте Rukus-подобный Reece
1.2.5. Routing и Global. Страница Routing (1.1.5.) отображает схему пути сигнала, места расположения Insert эффектов и Feedback секции, а также выбор Bypass осциллятора. Указываются точки входа (Osc1/2/3/Noise) и выхода Bypass сигнала (после Amp/Pan, после FX1 и после FX2). Страница Global содержит некоторые глобальные настройки, вроде Global Tune, Global bpm, а также «рандомизатор» настроек осцилляторов, фильтров, Insert и мастер эффектов.
1.2.6. Страницы модуляций.a.k.a. Цветной ряд
На данных страницах содержатся источники модуляций. Их два типа: Огибающие (1-4) и Назначаемые (5-8). Для каждой назначаемой страницы (5-8) можно выбрать три режима: LFO, STEPPER, PERFORMER.
1.2.7. Огибающие.
Огибающая — это кривая, которая используется для модулирования амплитуды какого-либо параметра. Сигнал этот проходит 5 стадий : Delay — задержка, Attack- нарастание (атака), Decay — спад , Sustain – поддержка, Release — затухание.
Для управления этими стадиями имеются соответствующие регуляторы в нижней части окна.
В верхнем ряду имеется пять кнопок:
· Trg Zero Reset. При включении данного параметра, огибающая сбрасывается каждый раз, как нажимается клавиша. В ином случае огибающая идет непрерывно. // Так же как и Glide, работает лишь в монофоническом режиме (Voicing).
· Linear переключает форму decay между линейной и логарифмической.
· Gate. Когда вы нажимаете клавишу и держите её, огибающая проходит все стадии, задерживаясь на Sustain, в случае отпускания клавиши, огибающая переходит на стадию Release.
· One Shoot. Независимо от того держите ли вы клавишу или нет, огибающая пройдет все стадии от начала до конца, включая стадию затухания.
· Hold. Тот же One Shoot, только до стадии Release дело не доходит. Вернее доходит, но только тогда, когда клавиша будет повторно нажата. Удобно при лайв выступлениях. Взял аккорд и куришь.
В правой части окна находятся два фейдера:
· Vel. a.k.a. Velocity Sensitivity Fader. Чем выше фейдер, тем амплитуда огибающей больше подвергнута силе удара (скорости нажатия клавиши). Сильнее(быстрее) – громче, и наоборот.
· KTR a.k.a. Keytracking Fader. Попросту говоря, чем выше фейдер, тем тише верхние ноты.
Пройдитесь по клавишам слева направо, чтобы понять.
Операции со стадиями огибающей (нижняя часть окна) стандартны, стоит лишь отметить пару особенностей. Стадия Sustain имеет две дополнительные функции:
· Loop. Проигрывает соответствующую часть указанное количество раз назад и вперед (от 1 до 32 полуциклов и до бесконечности).
· Morph. Позволяет осуществлять плавный морфинг между двумя выбранными в соответствующих полях) формами, в соответствии с которыми и проходит Loop. Следует отметить, что выделяющаяся белая точка при выборе количества полуциклов Loop определяет, откуда произойдет переход на стадию затухания.
1.2.8. Настраиваемые модуляторы: Общие параметры.
Самый левый блок позволяет регулировать Ratio – частоту / скорость модуляции / длительность шага, и Amp – амплитуду. Sync позволяет синхронизировать Ratio с темпом внешнего секвенсора, либо указанным в окне Global. Restart означает, что модуляция будет сбрасываться, и начинаться сначала при нажатии клавиши. Кнопка Mono синхронизирует модуляцию для всех взятых нот.
1.2.9. LFO a.k.a.Low Frequency Oscillator.
Этот модулятор генерирует периодическое колебание низкой частоты. Под «низкой» частоты колебаниями подразумеваются колебания, частоты которых могут быть различимы «ухом». В Massive реализовано «Двойное» LFO. Это значит, что на каждой странице модулятора LFO имеется два LFO генератора.
Как видим, на изображении выше имеется два «экрана» с формой колебания, а слева от них фейдер XFade Curve, позволяющий «морфить» одно колебание в другое, в указанном соотношении. Двигая форму колебания влево/вправо можно регулировать её стартовую позицию.
В блоке Curve Select можно выбрать форму колебания. Четыре базовые формы и множество дополнительных во всплывающем меню, чуть выше базовых. Блок Internal Envelope также, как и в окне OSC, отвечает за параметры внутренней огибающей. Её можно назначить на доступные параметры внутри окна.
1.3.0. Stepper.
Stepper представляет из себя пошаговый секвенсор, работающий в режиме Loop, каждый шаг которого имеет настраиваемую амплитуду. Чтобы задать точное значение амплитуды сначала нажмите и удерживайте Shift, только затем двигайте полоску амплитуды шага. Верхний ряд отображает номера шагов. Желтый треугольник в левой части этого ряда показывает начальную позицию loop’а; длину этого самого loop’а можно регулировать передвигая крайние шаги (влево / вправо), также можно передвигать весь выбранный фрагмент. Скорость loop’а регулируется в левом блоке, с помощью Ratio. Слева от главного окна секвенсора имеется два фейдера:
· Amp. Mod. Регулирует максимальную амплитуду выбранных шагов (выбор в нижнем ряду кнопок нижней части экрана).
· Glide Mod. Регулирует время glide выбранных шагов (верхний ряд кнопок нижней части экрана).
1.3.1. Performer.
Performer это по-сути тот же Stepper – пошаговый секвенсор. Но в отличие от Stepper’а, здесь мы можем выбрать «форму шага» (Load Curve в верхнем левом углу). Удерживая клавишу Shift можно применить одну форму к нескольким шагам. В Performer’е имеется два «генератора» (верхний и нижний секвенсоры), между которыми можно осуществлять морфинг (фейдер XFade Seq). Хорош для создания «синтетической» перкуссии, особенно в minimal, tech…
За последние 24 часа нас посетили 11017 программистов и 899 роботов. Сейчас ищут 435 программистов …
in_array
(PHP 4, PHP 5, PHP 7)
in_array — Проверяет, присутствует ли в массиве значение
Описание
bool in_array
( mixed $needle
, array $haystack
[, bool $strict
= FALSE
] )
Список параметров
-
needle
-
Искомое значение.
Замечание:
Если
needle
— строка, сравнение
будет произведено с учетом регистра. -
haystack
-
Массив.
-
strict
-
Если третий параметр
strict
установлен в
TRUE
тогда функция in_array()
также проверит соответствие типов
параметраneedle
и соответствующего
значения массиваhaystack
.
Возвращаемые значения
Возвращает TRUE
, если needle
был найден
в массиве, и FALSE
в обратном случае.
Примеры
Пример #1 Пример использования in_array()
<?php
$os = array("Mac", "NT", "Irix", "Linux");
if (in_array("Irix", $os)) {
echo "Нашел Irix";
}
if (in_array("mac", $os)) {
echo "Нашел mac";
}
?>
Второго совпадения не будет, потому что in_array()
регистрозависима, таким образом, программа выведет:
Пример #2 Пример использования in_array() с параметром strict
<?php
$a = array('1.10', 12.4, 1.13);
if (
in_array('12.4', $a, true)) {
echo "'12.4' найдено со строгой проверкойn";
}
if (
in_array(1.13, $a, true)) {
echo "1.13 найдено со строгой проверкойn";
}
?>
Результат выполнения данного примера:
1.13 найдено со строгой проверкой
Пример #3 Пример использования in_array() с массивом в качестве параметра needle
<?php
$a = array(array('p', 'h'), array('p', 'r'), 'o');
if (
in_array(array('p', 'h'), $a)) {
echo "'ph' найденоn";
}
if (
in_array(array('f', 'i'), $a)) {
echo "'fi' найденоn";
}
if (
in_array('o', $a)) {
echo "'o' найденоn";
}
?>
Результат выполнения данного примера:
Смотрите также
- array_search() — Осуществляет поиск данного значения в массиве и возвращает
соответствующий ключ в случае удачи - isset() — Определяет, была ли установлена переменная значением отличным от NULL
- array_key_exists() — Проверяет, присутствует ли в массиве указанный ключ или индекс
Вернуться к: Функции для работы с массивами
Сегодня, в пятой части перевода курса по JavaScript, мы поговорим о массивах и циклах. Массивы используются в ходе решения множества задач. Часто с массивами работают, используя циклы.
→ Часть 1: первая программа, особенности языка, стандарты
→ Часть 2: стиль кода и структура программ
→ Часть 3: переменные, типы данных, выражения, объекты
→ Часть 4: функции
→ Часть 5: массивы и циклы
→ Часть 6: исключения, точка с запятой, шаблонные литералы
→ Часть 7: строгий режим, ключевое слово this, события, модули, математические вычисления
→ Часть 8: обзор возможностей стандарта ES6
→ Часть 9: обзор возможностей стандартов ES7, ES8 и ES9
Массивы
Массивы, объекты типа Array
, развиваются вместе с остальными механизмами языка. Они представляют собой списки пронумерованных значений.
Первый элемент массива имеет индекс (ключ) 0, такой подход используется во многих языках программирования.
В этом разделе мы рассмотрим современные методы работы с массивами.
▍Инициализация массивов
Вот несколько способов инициализации массивов.
const a = []
const a = [1, 2, 3]
const a = Array.of(1, 2, 3)
const a = Array(6).fill(1) //инициализация каждого элемента массива, состоящего из 6 элементов, числом 1
Для того чтобы получить доступ к отдельному элементу массива, используют конструкцию, состоящую из квадратных скобок, в которых содержится индекс элемента массива. Элементы массивов можно как считывать, так и записывать.
const a = [1, 2, 3]
console.log(a) //[ 1, 2, 3 ]
const first = a[0]
console.log(first) //1
a[0] = 4
console.log(a) //[ 4, 2, 3 ]
Конструктор Array
для объявления массивов использовать не рекомендуется.
const a = new Array() //не рекомендуется
const a = new Array(1, 2, 3) //не рекомендуется
Этот способ следует использовать лишь при объявлении типизированных массивов.
▍Получение длины массива
Для того чтобы узнать длину массива, нужно обратиться к его свойству length
.
const l = a.length
▍Проверка массива с использованием метода every()
Метод массивов every()
можно использовать для организации проверки всех их элементов с использованием некоего условия. Если все элементы массива соответствуют условию, функция возвратит true
, в противном случае она возвратит false
.
Этому методу передаётся функция, принимающая аргументы currentValue
(текущий элемент массива), index
(индекс текущего элемента массива) и array
(сам массив). Он может принимать и необязательное значение, используемое в качестве this
при выполнении переданной ему функции.
Например, проверим, превышают ли значения всех элементов массива число 10.
const a = [11, 12, 13]
const b = [5, 6, 25]
const test = el => el > 10
console.log(a.every(test)) //true
console.log(b.every(test)) //false
Здесь нас, в функции test()
, интересует лишь первый передаваемый ей аргумент, поэтому мы объявляем её, указывая лишь параметр el
, в который и попадёт соответствующее значение.
▍Проверка массива с использованием метода some()
Этот метод очень похож на метод every()
, но он возвращает true
, если хотя бы один из элементов массива удовлетворяет условию, заданному переданной ему функцией.
▍Создание массива на основе существующего массива с использованием метода map()
Метод массивов map()
позволяет перебирать массивы, применяя к каждому их элементу, переданную этому методу, функцию, преобразующую элемент, и создавать из полученных значений новые массивы. Вот, например, как получить новый массив, являющийся результатом умножения всех элементов исходного массива на 2.
const a = [1, 2, 3]
const double = el => el * 2
const doubleA = a.map(double)
console.log(a) //[ 1, 2, 3 ]
console.log(doubleA) //[ 2, 4, 6 ]
▍Фильтрация массива с помощью метода filter()
Метод filter()
похож на метод map()
, но он позволяет создавать новые массивы, содержащие лишь те элементы исходных массивов, которые удовлетворяют условию, задаваемому передаваемой методу filter()
функцией.
▍Метод reduce()
Метод reduce()
позволяет применить заданную функцию к аккумулятору и к каждому значению массива, сведя массив к единственному значению (это значение может иметь как примитивный, так и объектный тип). Этот метод принимает функцию, выполняющую преобразования, и необязательное начальное значение аккумулятора. Рассмотрим пример.
const a = [1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
return accumulator * currentValue
}, 1)
console.log(a) //24
//итерация 1: 1 * 1 = 1
//итерация 2: 1 * 2 = 2
//итерация 3: 2 * 3 = 6
//итерация 4: 6 * 4 = 24
Здесь мы ищем произведение всех элементов массива, описанного с помощью литерала, задавая в качестве начального значения аккумулятора 1.
▍Перебор массива с помощью метода forEach()
Метод массивов forEach()
можно использовать для перебора значений массивов и для выполнения над ними неких действий, задаваемых передаваемой методу функцией. Например, выведем, по одному, элементы массива в консоль.
const a = [1, 2, 3]
a.forEach(el => console.log(el))
//1
//2
//3
Если при переборе массива нужно остановить или прервать цикл, то при использовании forEach()
придётся выбрасывать исключение. Поэтому если в ходе решения некоей задачи может понадобиться прерывание цикла, лучше всего выбрать какой-нибудь другой способ перебора элементов массива.
▍Перебор массива с использованием оператора for…of
Оператор for...of
появился в стандарте ES6. Он позволяет перебирать итерируемые объекты (в том числе — массивы). Вот как им пользоваться.
const a = [1, 2, 3]
for (let v of a) {
console.log(v)
}
//1
//2
//3
На каждой итерации цикла в переменную v
попадает очередной элемент массива a
.
▍Перебор массива с использованием оператора for
Оператор for
позволяет организовывать циклы, которые, в частности, можно использовать и для перебора (или инициализации) массивов, обращаясь к их элементам по индексам. Обычно индекс очередного элемента получают, пользуясь счётчиком цикла.
const a = [1, 2, 3]
for (let i = 0; i < a.length; i += 1) {
console.log(a[i])
}
//1
//2
//3
Если, в ходе выполнения цикла, нужно пропустить его итерацию, можно воспользоваться командой continue
. Для досрочного завершения цикла можно воспользоваться командой break
. Если в цикле, например, расположенном в некоей функции, использовать команду return
, выполнение цикла и функции завершится, а возвращённое с помощью return
значение попадёт туда, откуда была вызвана функция.
▍Метод @@iterator
Этот метод появился в стандарте ES6. Он позволяет получать так называемый «итератор объекта» — объект, который в данном случае позволяет организовывать перебор элементов массива. Итератор массива можно получить, воспользовавшись символом (такие символы называют «известными символами») Symbol.iterator
. После получения итератора можно обращаться к его методу next()
, который, при каждом его вызове, возвращает структуру данных, содержащую очередной элемент массива.
const a = [1, 2, 3]
let it = a[Symbol.iterator]()
console.log(it.next().value) //1
console.log(it.next().value) //2
console.log(it.next().value) //3
Если вызвать метод next()
после того, как будет достигнут последний элемент массива, он возвратит, в качестве значения элемента, undefined
. Объект, возвращаемый методом next()
, содержит свойства value
и done
. Свойство done
принимает значение false
до тех пор, пока не будет достигнут последний элемент массива. В нашем случае, если вызвать it.next()
в четвёртый раз, он возвратит объект { value: undefined, done: true }
, в то время как при трёх предыдущих вызовах этот объект имел вид { value: значение, done: false }
.
Метод массивов entries()
возвращает итератор, который позволяет перебирать пары ключ-значение массива.
const a = [1, 2, 3]
let it = a.entries()
console.log(it.next().value) //[0, 1]
console.log(it.next().value) //[1, 2]
console.log(it.next().value) //[2, 3]
Метод keys()
позволяет перебирать ключи массива.
const a = [1, 2, 3]
let it = a.keys()
console.log(it.next().value) //0
console.log(it.next().value) //1
console.log(it.next().value) //2
▍Добавление элементов в конец массива
Для добавления элементов в конец массива используют метод push()
.
a.push(4)
▍Добавление элементов в начало массива
Для добавления элементов в начало массива используют метод unshift()
.
a.unshift(0)
a.unshift(-2, -1)
▍Удаление элементов массива
Удалить элемент из конца массива, одновременно возвратив этот элемент, можно с помощью метода pop()
.
a.pop()
Аналогичным образом, с помощью метода shift()
, можно удалить элемент из начала массива.
a.shift()
То же самое, но уже с указанием позиции удаления элементов и их количества, делается с помощью метода splice()
.
a.splice(0, 2) // удаляет и возвращает 2 элемента из начала массива
a.splice(3, 2) // удаляет и возвращает 2 элемента, начиная с индекса 3
▍Удаление элементов массива и вставка вместо них других элементов
Для того чтобы, воспользовавшись одной операцией, удалить некие элементы массива и вставить вместо них другие элементы, используется уже знакомый вам метод splice()
.
Например, здесь мы удаляем 3 элемента массива начиная с индекса 2, после чего в то же место добавляем два других элемента:
const a = [1, 2, 3, 4, 5, 6]
a.splice(2, 3, 'a', 'b')
console.log(a) //[ 1, 2, 'a', 'b', 6 ]
▍Объединение нескольких массивов
Для объединения нескольких массивов можно воспользоваться методом concat()
, возвращающим новый массив.
const a = [1, 2]
const b = [3, 4]
const c = a.concat(b)
console.log(c) //[ 1, 2, 3, 4 ]
▍Поиск элементов в массиве
В стандарте ES5 появился метод indexOf()
, который возвращает индекс первого вхождения искомого элемента массива. Если элемент в массиве найти не удаётся — возвращается -1
.
const a = [1, 2, 3, 4, 5, 6, 7, 5, 8]
console.log(a.indexOf(5)) //4
console.log(a.indexOf(23)) //-1
Метод lastIndexOf()
возвращает индекс последнего вхождения элемента в массив, или, если элемент не найден, -1
.
const a = [1, 2, 3, 4, 5, 6, 7, 5, 8]
console.log(a.lastIndexOf(5)) //7
console.log(a.lastIndexOf(23)) //-1
В ES6 появился метод массивов find()
, который выполняет поиск по массиву с использованием передаваемой ему функции. Если функция возвращает true
, метод возвращает значение первого найденного элемента. Если элемент найти не удаётся, функция возвратит undefined
.
Выглядеть его использование может следующим образом.
a.find(x => x.id === my_id)
Здесь в массиве, содержащем объекты, осуществляется поиск элемента, свойство id
которого равняется заданному.
Метод findIndex()
похож на find()
, но он возвращает индекс найденного элемента или undefined
.
В ES7 появился метод includes()
, который позволяет проверить наличие некоего элемента в массиве. Он возвращает true
или false
, найдя или не найдя интересующий программиста элемент.
a.includes(value)
С помощью этого метода можно проверять на наличие некоего элемента не весь массив, а лишь некоторую его часть, начинающуюся с заданного при вызове этого метода индекса. Индекс задаётся с помощью второго, необязательного, параметра этого метода.
a.includes(value, i)
▍Получение фрагмента массива
Для того чтобы получить копию некоего фрагмента массива в виде нового массива, можно воспользоваться методом slice()
. Если этот метод вызывается без аргументов, то возвращённый массив окажется полной копией исходного. Он принимает два необязательных параметра. Первый задаёт начальный индекс фрагмента, второй — конечный. Если конечный индекс не задан, то массив копируется от заданного начального индекса до конца.
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(a.slice(4)) //[ 5, 6, 7, 8, 9 ]
console.log(a.slice(3,7)) //[ 4, 5, 6, 7 ]
▍Сортировка массива
Для организации сортировки элементов массива в алфавитном порядке (0-9A-Za-z
) используется метод sort()
без передачи ему аргументов.
const a = [1, 2, 3, 10, 11]
a.sort()
console.log(a) //[ 1, 10, 11, 2, 3 ]
const b = [1, 'a', 'Z', 3, 2, 11]
b.sort()
console.log(b) //[ 1, 11, 2, 3, 'Z', 'a' ]
Этому методу можно передать функцию, задающую порядок сортировки. Функция принимает, для сравнения двух элементов, параметры a
и b
. Она возвращает отрицательное число в том случае, если a
меньше b
по какому-либо критерию, 0 — если они равны, и положительное число — если a
больше b
. При написании подобной функции для сортировки числовых массивов она может возвратить результат вычитания a
и b
. Так, возврат результата вычисления выражения a - b
означает сортировку массива по возрастанию, возврат результата вычисления выражения b - a
даст сортировку массива по убыванию.
const a = [1, 10, 3, 2, 11]
console.log(a.sort((a, b) => a - b)) //[ 1, 2, 3, 10, 11 ]
console.log(a.sort((a, b) => b - a)) //[ 11, 10, 3, 2, 1 ]
Для того чтобы обратить порядок следования элементов массива можно воспользоваться методом reverse()
. Он, так же, как и sort()
, модифицирует массив для которого вызывается.
▍Получение строкового представления массива
Для получения строкового представления массива можно воспользоваться его методом toString()
.
a.toString()
Похожий результат даёт метод join()
, вызванный без аргументов.
a.join()
Ему, в качестве аргумента, можно передать разделитель элементов.
const a = [1, 10, 3, 2, 11]
console.log(a.toString()) //1,10,3,2,11
console.log(a.join()) //1,10,3,2,11
console.log(a.join(', ')) //1, 10, 3, 2, 11
▍Создание копий массивов
Для создания копии массива путём копирования в новый массив значений исходного массива можно воспользоваться методом Array.from()
. Он подходит и для создания массивов из массивоподобных объектов (из строк, например).
const a = 'a string'
const b = Array.from(a)
console.log(b) //[ 'a', ' ', 's', 't', 'r', 'i', 'n', 'g' ]
Метод Array.of()
тоже можно использовать для копирования массивов, а также для «сборки» массивов из различных элементов. Например, для копирования элементов одного массива в другой можно воспользоваться следующей конструкцией.
const a = [1, 10, 3, 2, 11]
const b = Array.of(...a)
console.log(b) // [ 1, 10, 3, 2, 11 ]
Для копирования элементов массива в некое место самого этого массива используется метод copyWithin()
. Его первый аргумент задаёт начальный индекс целевой позиции, второй — начальный индекс позиции источника элементов, а третий параметр, необязательный, указывает конечный индекс позиции источника элементов. Если его не указать, в указанное место массива будет скопировано всё, начиная от начального индекса позиции источника до конца массива.
const a = [1, 2, 3, 4, 5]
a.copyWithin(0, 2)
console.log(a) //[ 3, 4, 5, 4, 5 ]
Циклы
Выше, говоря о массивах, мы уже сталкивались с некоторыми способами организации циклов. Однако циклы в JavaScript используются не только для работы с массивами, да и рассмотрели мы далеко не все их виды. Поэтому сейчас мы уделим некоторое время рассмотрению разных способов организации циклов в JavaScript и поговорим об их особенностях.
▍Цикл for
Рассмотрим пример применения этого цикла.
const list = ['a', 'b', 'c']
for (let i = 0; i < list.length; i++) {
console.log(list[i]) //значения, хранящиеся в элементах циклов
console.log(i) //индексы
}
Как уже было сказано, прерывать выполнение такого цикла можно, используя команду break
, а пропускать текущую итерацию и переходить сразу к следующей можно с помощью команды continue
.
▍Цикл forEach
Этот цикл мы тоже обсуждали. Приведём пример перебора массива с его помощью.
const list = ['a', 'b', 'c']
list.forEach((item, index) => {
console.log(item) //значение
console.log(index) //индекс
})
//если индексы элементов нас не интересуют, можно обойтись и без них
list.forEach(item => console.log(item))
Напомним, что для прерывания такого цикла надо выбрасывать исключение, то есть, если при использовании цикла может понадобиться прервать его, лучше выбрать какой-нибудь другой цикл.
▍Цикл do…while
Это — так называемый «цикл с постусловием». Такой цикл будет выполнен как минимум один раз до проверки условия завершения цикла.
const list = ['a', 'b', 'c']
let i = 0
do {
console.log(list[i]) //значение
console.log(i) //индекс
i = i + 1
} while (i < list.length)
Его можно прерывать с использованием команды break
, можно переходить на его следующую итерацию командой continue
.
▍Цикл while
Это — так называемый «цикл с предусловием». Если, на входе в цикл, условие продолжения цикла ложно, он не будет выполнен ни одного раза.
const list = ['a', 'b', 'c']
let i = 0
while (i < list.length) {
console.log(list[i]) //значение
console.log(i) //индекс
i = i + 1
}
▍Цикл for…in
Этот цикл позволяет перебирать все перечислимые свойства объекта по их именам.
let object = {a: 1, b: 2, c: 'three'}
for (let property in object) {
console.log(property) //имя свойства
console.log(object[property]) //значение свойства
}
▍Цикл for…of
Цикл for...of
совмещает в себе удобство цикла forEach
и возможность прерывать его работу штатными средствами.
//перебор значений
for (const value of ['a', 'b', 'c']) {
console.log(value) //значение
}
//перебор значений и получение индексов с помощью `entries()`
for (const [index, value] of ['a', 'b', 'c'].entries()) {
console.log(index) //индекс
console.log(value) //значение
}
Обратите внимание на то, что здесь, в заголовке цикла, используется ключевое слово const
, а не, как можно было бы ожидать, let
. Если внутри блока цикла переменные не нужно переназначать, то const
нам вполне подходит.
Если сравнить циклы for...in
и for...of
, то окажется, что for...in
перебирает имена свойств, а for...of
— значения свойств.
Циклы и области видимости
С циклами и с областями видимости переменных связана одна особенность JavaScript, которая может доставить разработчику некоторые проблемы. Для того чтобы с этими проблемами разобраться, поговорим о циклах, об областях видимости, и о ключевых словах var
и let
.
Рассмотрим пример.
const operations = []
for (var i = 0; i < 5; i++) {
operations.push(() => {
console.log(i)
})
}
for (const operation of operations) {
operation()
}
В цикле производится 5 итераций, на каждой из которых в массив operations
добавляется новая функция. Эта функция выводит в консоль значение счётчика цикла — i
. После того, как функции добавлены в массив, мы этот массив перебираем и вызываем функции, являющиеся его элементами.
Выполняя подобный код можно ожидать результата, показанного ниже.
0
1
2
3
4
Но на самом деле он выводит следующее.
5
5
5
5
5
Почему это так? Всё дело в том, что в качестве счётчика цикла мы используем переменную, объявленную с использованием ключевого слова var
.
Так как объявления подобных переменных поднимаются в верхнюю часть области видимости, вышеприведённый код аналогичен следующему.
var i;
const operations = []
for (i = 0; i < 5; i++) {
operations.push(() => {
console.log(i)
})
}
for (const operation of operations) {
operation()
}
В результате оказывается, что в цикле for...of
, в котором мы перебираем массив, переменная i
всё ещё видна, она равна 5, в результате, ссылаясь на i
во всех функциях, мы выводим число 5.
Как изменить поведение программы таким образом, чтобы она делала бы то, что от неё ожидается?
Самое простое решение этой проблемы заключается в использовании ключевого слова let
. Оно, как мы уже говорили, появилось в ES6, его использование позволяет избавиться от некоторых странностей, характерных для var
.
В частности, в вышеприведённом примере достаточно изменить var
на let
и всё заработает так, как нужно.
const operations = []
for (let i = 0; i < 5; i++) {
operations.push(() => {
console.log(i)
})
}
for (const operation of operations) {
operation()
}
Теперь на каждой итерации цикла каждая функция, добавленная в массив operations
, получает собственную копию i
. Помните о том, что в данной ситуации нельзя использовать ключевое слово const
, так как значение i
в цикле меняется.
Ещё один способ решения этой проблемы, который часто применялся до появления стандарта ES6, когда ключевого слова let
ещё не было, заключается в использовании IIFE.
При таком подходе значение i
сохраняется в замыкании, а в массив попадает функция, возвращаемая IIFE и имеющая доступ к замыканию. Эту функцию можно выполнить тогда, когда в ней возникнет необходимость. Вот как это выглядит.
const operations = []
for (var i = 0; i < 5; i++) {
operations.push(((j) => {
return () => console.log(j)
})(i))
}
for (const operation of operations) {
operation()
}
Итоги
Сегодня мы поговорили о массивах и о циклах в JavaScript. Тема нашего следующего материала — обработка исключений, особенности использования точки с запятой и шаблонные литералы.
Уважаемые читатели! Какими методами для работы с массивами в JavaScript вы пользуетесь чаще всего?
- →
Ранее в одной из статей мы с вами подробно изучили одномерные массивы. Наверняка в процессе чтения материала вы обратили внимание, что массив не предоставляет никаких методов, облегчающих с ним работу — все приходится реализовывать самостоятельно. Это связано с тем, что массив не имеет как такового класса своей реализации. Вызвать какой-либо собственный метод у него мы не можем (методы Object не в счет) — их просто нет.
Подобные «издевательства» над разработчиками продолжалось до тех пор, пока в JDK 1.2 не появился специальный утилитный класс Arrays, призванный упростить работу с массивами.
Утилитный класс — это класс, имеющий набор статических методов, никак не связанных между собой и не обладающий состоянием. Характерной особенностью таких классов является использование множественного числа в их имени, например, Collections, Objects, а также слов Util и Helper, например, ArrayUtils.
Для использования Arrays в своих классах необходимо прописать импорт:
Класс Arrays содержит методы, упрощающие выполнение стереотипных (часто повторяющихся) операций с массивами: отображение, копирование, сортировка, поиск элементов массива и т. д.
В данной статье мы разберем наиболее часто используемые методы при работе с одномерными массивами.
Как мы выяснили ранее, вывести элементы массива, просто передав его в метод println(), не выйдет — отобразится значение вида [I@2f490758, получаемое с помощью неявного вызова метода toString() класса Object, что не очень полезно.
Необходимо самостоятельно переопределить toString(), реализовав в нем корректное преобразование в строку, или воспользоваться методом Arrays.toString(), который предназначен для строкового представления одномерных массивов.
int[] nums = {2, 4, 6, 8, 10};
System.out.println(Arrays.toString(nums));
Результатом работы Arrays.toString() является строка из элементов массива, перечисленных через запятую, внутри квадратных скобок.
Если по какой-то причине вас не устраивает такой формат вывода, то либо придется писать свой вариант преобразования, например, через обычный цикл, либо использовать регулярные выражения (1, 2).
int[] nums = {2, 4, 6, 8, 10};
String str = Arrays.toString(nums);
System.out.println(str.replaceAll("\[|\]|\,", ""));
Благодаря применению метода replaceAll() удалось заменить запятые и [ ] на пустые строки.
String[] letters = {"A", "B", "C"};
System.out.println(Arrays.toString(letters));
В качестве следующего примера создадим два класса, поместив экземпляры Example в массив:
public class Main {
public static void main(String[] args) {
Example[] arr = {new Example(), new Example(), new Example()};
System.out.println(Arrays.toString(arr));
}
}
class Example {
int num = 3;
}
[Example@3b22cdd0, Example@1e81f4dc, Example@4d591d15]
Arrays.toString() не отобразил значение поля num для элементов массива. Это связано с тем, что для каждого элемента в одной из строк реализации метода Arrays.toString() вызывается метод valueOf().
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
Как видно из примера кода, если элемент массива (в нашем случае Object obj) не равен null, то у него вызывается toString(). Но т. к. у Example не реализован toString(), то используется реализация из класса Object, доставшаяся ему по наследству.
Java не берет на себя ответственность отображать то, что ей не поручали. Реализация toString() по умолчанию ограничивается выводом вида Example@3b22cdd0. Его можно воспринимать как сигнал того, что в пользовательском классе toString() не реализован.
Для решения проблемы в Example необходимо переопределить метод toString():
class Example {
int num = 3;
public String toString() {
return "" + num;
}
}
Возможно, кто-то из вас может задаться вопросом: «Зачем вызывать toString() у Arrays, если все равно пришлось писать свою реализацию? Разве смысл использования Arrays.toString() не в том, чтобы не иметь своей реализации?»
Запустим программу, вызвав toString() у массива:
System.out.println(arr.toString());
Отобразилась информация о типе данных массива и типе хранимой в нем информации. В этом и состоит проблема, что toString() вызывается у массива, а не у его элементов. В то время как Arrays.toString() вызывает toString() у каждого элемента массива. А чтобы ему было, что вызывать (и отображать нужные нам данные), метод toString() необходимо переопределить.
Из приведенных выше примеров можно сделать вывод, что Arrays.toString() может выводить как значения примитивных типов, так и объектов (при условии, что у них реализован toString()), хранящихся в одномерных массивах. Удобство в использовании Arrays.toString() заключается в том, что не нужно использовать цикл для отображения значений массива.
Частой бывает ситуация, когда требуется из одного массива скопировать в другой все элементы (или часть из них). Рассмотрим на примерах разные способы копирования.
Копировать элементы из одного массива в другой можно по самым разным причинам: когда в одном массиве необходимо найти все подходящие данные, а затем скопировать их в другой массив; если исходный массив заполнен, то можно создать новый с бо́льшим размером, а затем скопировать в него все элементы из исходного и т. д.
Для начала реализуем стереотипный способ копирования, чтобы увидеть все его недостатки.
Воспользуемся циклом for, в теле которого будем поэлементно копировать значения из исходного массива srcArr в destArr (src — сокращение от source; dest — от destination):
int[] srcArr = {1, 2, 3, 4, 5};
int[] destArr = new int[srcArr.length];
// процесс копирования
for(int i = 0; i < destArr.length; i++) {
destArr[i] = srcArr[i];
}
В этом коде создаются два массива типа int. Исходный массив инициализируется значениями от 1 до 5. В итоге в for каждый элемент исходного массива копируется во второй массив.
Недостатком данного подхода является том, что приходится вручную писать цикл. Хотелось бы скрыть эти подробности реализации, чтобы избавиться от многословности языка.
Недостаток вышеизложенного способа можно устранить с помощью метода Arrays.copyOf() (появился в Java 1.6), что позволяет сократить код и упростить копирование.
Данный метод применяется тогда, когда необходимо скопировать элементы массива, начиная с нулевого индекса.
int[] srcArr = {1, 2, 3, 4, 5};
int[] destArr = Arrays.copyOf(srcArr, srcArr.length);
Метод Arrays.copyOf() принимает 2 параметра: копируемый массив и количество копируемых значений, которое определяет также длину нового массива. Т. к. мы хотим скопировать массив целиком, то указываем в качестве количества длину исходного массива.
Если вам требуется скопировать не весь массив, а только какую-то его часть, то вместо длины следует указать необходимое значение: это может быть как целочисленный литерал, так и значение переменной.
Следует иметь ввиду небольшой нюанс работы метода, связанный с тем, что если количество копируемых элементов указано больше, чем их содержит массив, то copyOf() дополнит итоговый массив значениями по умолчанию.
Пусть имеется заполненный массив чисел, в который необходимо добавить еще одно число. Т. к. размер уже созданного массива менять нельзя, то создадим новый с увеличенным размером:
int[] srcArr = {1, 2, 3, 4, 5};
int[] destArr = Arrays.copyOf(srcArr, srcArr.length + 3);
System.out.println(Arrays.toString(destArr));
destArr[srcArr.length] = 6;
destArr[srcArr.length + 1] = 7;
System.out.println(Arrays.toString(destArr));
[1, 2, 3, 4, 5, 0, 0, 0]
[1, 2, 3, 4, 5, 6, 7, 0]
Рассмотрим чуть более сложный пример:
String[] letters = {"A", "B", "C"};
int len = letters.length;
int newLen = len + (int) (len * 0.75);
System.out.println("Длина нового массива = " + newLen);
String[] destArr = Arrays.copyOf(letters, newLen);
System.out.println(Arrays.toString(destArr));
destArr[len] = "D";
System.out.println(Arrays.toString(destArr));
Длина нового массива = 5
[A, B, C, null, null]
[A, B, C, D, null]
В данном коде создаются два массива типа String. Длина второго массива (newLen) вычисляется по формуле, которая позволяет увеличивать его размер на 75%. Также видно, что copyOf заполняет пустые ячейки null’ом.
2.3. Arrays.copyOfRange()
Если требуется скопировать значения не с начала массива (или не до конца), то необходимо использовать Arrays.copyOfRange() (появился в Java 1.6). Данный метод позволяет копировать определенный диапазон значений массива, начиная с указанного индекса:
int[] srcArr = {1, 2, 3, 4, 5};
int[] destArr = Arrays.copyOfRange(srcArr, 1, 3);
System.out.println(Arrays.toString(destArr));
Метод copyOfRange() принимает 3 параметра: исходный (копируемый) массив; индекс в исходном массиве, с которого будет начинаться копирование; третий параметр — конечный индекс до которого будут копироваться элементы (значение под этим индексом не включается в копирование).
Еще пример. Необходимо найти максимальное число, затем скопировать в новый массив все значения, которые находятся справа от этого числа, включая само число:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
double[] srcArr = {100.1, 120.2, -4.8, 3.5, 70.2};
double max = srcArr[0];
int maxNumIndex = 0;
for (int i = 1; i < srcArr.length; i++) {
if (srcArr[i] > max) {
maxNumIndex = i;
max = srcArr[i];
}
}
double[] destArr = Arrays.copyOfRange(srcArr, maxNumIndex, srcArr.length);
System.out.println(Arrays.toString(destArr));
}
}
Метод copyOfRange(), так же как и copyOf(), если количество копируемых элементов указано больше, чем их содержит массив, дополняет итоговый массив значениями по умолчанию.
В Java есть еще один способ скопировать массив — с помощью нативного (native) метода arraycopy(), расположенного в классе System.
Его работа имеет принципиальное отличие от копирования элементов массива в цикле: он копирует не поэлементно, а целыми блоками памяти, что влияет положительно на производительность.
Данный класс находится в пакете java.lang, и его не нужно импортировать — это делается автоматически.
Нативными называются методы, реализация которых написана на каком-то другом языке, отличном от Java (например, С/C++), под конкретную ОС, что делает их код платформозависимым, т. е. «родным» для конкретной системы.
Методы, помеченные как native, не могут иметь тела и должны заканчиваться точкой с запятой.
Получается, что сигнатуру метода arraycopy() с ключевым словом native мы можем наблюдать в классе System, а вот реализация, написанная на языках C/C++, находится совсем в другом месте. Она является частью скомпилированной в виде машинного (бинарного) кода библиотеки внутри JVM.
Сигнатура обсуждаемого метода выглядит сложноватой, но к ней можно привыкнуть:
@IntrinsicCandidate
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length)
Запомнить последовательность аргументов, которые принимает метод, не сложно. Рассуждать можно следующим образом: из исходного массива (src), начиная с начальной позиции (srcPos), нужно скопировать данные в другой массив (dest), в такую-то позицию (destPos), в таком-то количестве (length).
Стоит отметить, что если массив назначения уже содержит данные, то arraycopy() их перезапишет.
Обратите внимание, что оба массива имеют тип Object. Это сделано для универсальности, чтобы данный метод мог работать с массивами любого типа.
Над сигнатурой метода мы также можем наблюдать аннотацию @IntrinsicCandidate (до Java 16 она называлась @HotSpotIntrinsicCandidate), сообщающую о том, что JVM вместо вызова кода, написанного на другом языке (а такие вызовы могут негативно влиять на производительность и делают невозможным выполнять оптимизации кода), может использовать внутреннюю (встроенную) реализацию данного метода, которая есть у JIT (Just-in-Time — технология увеличения производительности программ за счет компиляции часто используемых частей кода).
В режиме интерпретатора (когда JVM выполняет вашу программу байт-код за байт-кодом) виртуальной машиной будет запускаться native-версия arraycopy(), но проработав какое-то время и собрав статистику, при определенных условиях она будет рассматривать методы с подобными аннотациями как кандидаты на замену native-кода внутренним машинным кодом.
Разберем пример работы с arraycopy(). Пусть требуется удалить значение по индексу 0, а затем сдвинуть влево все числа, стоящие от него справа:
import java.util.Arrays;
class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
System.arraycopy(arr, 1, arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
arr[arr.length - 1] = 0;
System.out.println(Arrays.toString(arr));
}
}
[2, 3, 4, 5, 5]
[2, 3, 4, 5, 0]
После сдвига последнее значение стало дублироваться. По этому нам приходится выполнять вручную обнуление последней ячейки, чтобы она содержала 0.
System.arraycopy(), как и методы копирования из Arrays, делают неглубокую копию (shallow copy) объектов, копируя только ссылки. После копирования новый массив будет указывать на тот же самый набор объектов! Это плохо тем, что изменение элементов в исходном массиве приведет к изменениям в массиве-копии, т. к. они оба хранят ссылки на одни и те же элементы.
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Player[] srcArr = {new Player("Max"), new Player("Fox"), new Player("LoL")};
Player[] destArr = new Player[srcArr.length];
System.out.println("До копирования:n" +
Arrays.toString(srcArr) + "n" +
Arrays.toString(destArr));
System.arraycopy(srcArr, 0, destArr, 0, srcArr.length);
srcArr[0].setName(null);
System.out.println("nПосле копирования:n" +
Arrays.toString(srcArr) + "n" +
Arrays.toString(destArr));
}
}
class Player {
private String name;
Player(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
Поскольку результатом является поверхностная копия, изменение имени игрока элемента исходного массива вызвало изменение массива копии.
Если для вас неглубокое копирование не создает каких-либо сложностей при работе программы или нет требований к глубокому копированию (когда при копировании создаются новые объекты), то не рассматривайте копирование только ссылок как проблему. Но знать об этих нюансах все равно необходимо.
Самое время затронуть тему производительности: если методы из класса Arrays делают тоже самое, что и метод arraycopy из класс System, то какой из них использовать? Можно предположить, что arraycopy() должен работать быстрее, т. к. является методом из библиотеки, написанной специально под конкретную ОС, а также у JIT есть его оптимизированная реализация. Попробуем в этом разобраться.
Если заглянуть в код метода copyOf(), то выяснится, что внутри себя он вызывает метод arraycopy():
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
Поэтому оба метода работают примерно одинаково, что подтверждают результаты тестов производительности.
Отличие между этими методами в том, что результатом работы методов копирования из Arrays является массив, который они сами и создают, в то время как для arraycopy() массив должен быть уже создан. Из-за этого нюанса код с использованием copyOf() получается на пару строк короче. При этом copyOf() контролирует количество копируемых элементов: если их больше, чем в массиве, то новый массив будет дополнен ячейками со значением по умолчанию; если их меньше, то он их просто обрежет.
Класс Arrays имеет набор методов, называемых fill(), которые заполняют весь массив одним и тем же значением. Это бывает полезно, когда нужно очистить массив или проинициализировать все его ячейки определенными значениями.
Применение fill() заменяет использование цикла, скрывая все подробности внутри себя. В итоге остается одна лаконичная строка.
int[] nums = new int[4];
Arrays.fill(nums, 36);
System.out.println(Arrays.toString(nums));
В этом примере все ячейки массива заполняются числом 36, а затем отображаются в консоль.
Рассмотренный метод fill() принимает два аргумента: сам массив и заполняемое его ячейки значение, число 36.
Иногда бывает нужно заполнить массив, начиная с определенного адреса. Для этих целей подойдет другая версия метода, которая принимает уже 4 параметра. Два новых из них отвечают за индекс начала и окончания (значение под этим индексом не включается в заполнение) заполнения.
String[] arr = new String[10];
Arrays.fill(arr, 3, 7, "Java");
System.out.println(Arrays.toString(arr));
[null, null, null, Java, Java, Java, Java, null, null, null]
В этом примере заполняются только элементы с индексами от 3 до 7. При этом 7 не включается (exclusive), о чем частенько забывают. Т. е. последней ячейкой, которая будет заполнена, является ячейка под индексом 6, т. к. индексация в массиве начинается с 0.
Еще пример. Необходимо обнулить ячейки массива, которые хранят экземпляры класса Example:
Example[] arr = {new Example(), new Example(), new Example(), null, null};
Arrays.fill(arr, 0, 3, null);
System.out.println(Arrays.toString(arr));
[null, null, null, null, null]
Элементы массивов можно сравнивать друг с другом в цикле, проверяя значение каждой ячейки на равенство.
char[] chars1 = {'M', 'A', 'X'};
char[] chars2 = {'M', 'A', 'N'};
boolean equals = true;
for (int i = 0; i < chars2.length; i++) {
if (chars1[i] != chars2[i]) {
equals = false;
break;
}
}
System.out.println("Массивы " + (equals ? "равны" : "не равны"));
Данный способ выглядит громоздко из-за цикла и логики проверок элементов массива.
В качестве альтернативы можно использовать метод equals(), наследуемый любым массивом от суперкласса Object. Но его реализация по умолчанию ориентирована на сравнение ссылок массивов, а не значений их элементов.
Так выглядит реализация метода equals() в классе Object:
public boolean equals(Object obj) {
return (this == obj);
}
double[] nums = {.5, .7, .3};
double[] copyNums = Arrays.copyOf(nums, nums.length);
System.out.println(Arrays.toString(nums));
System.out.println(Arrays.toString(copyNums));
System.out.println("Массивы " + (copyNums.equals(nums) ? "равны" : "не равны"));
[0.5, 0.7, 0.3]
[0.5, 0.7, 0.3]
Массивы не равны
Хотя массивы и имеют одни и те же значения в одинаковых по индексу ячейках, но программа все равно выдает, что они не равны., т. к. по факту сравниваются ссылки массивов, а не их элементы.
Равенство ссылок нас обычно не устраивает, т. к. каждый новый массив будет иметь свою ссылку.
Два массива считаются равными, если имеют одинаковую длину, а их элементы равны друг другу в том порядке, в котором они находятся в массиве.
Сравнивать ссылки приходится не часто. А вот сравнение элементов массивов — это наиболее востребованная операция. За этой возможностью обратимся к классу Arrays, используя его реализацию equals().
Рассмотрим пример, создав для разнообразия массивы для хранения boolean-значений:
boolean[] states1 = {false, true, false};
boolean[] states2 = {false, true, false};
boolean[] states3 = {false, false, true};
System.out.println("Массивы " + (Arrays.equals(states1, states2) ? "равны" : "не равны"));
System.out.println("Массивы " + (Arrays.equals(states1, states3) ? "равны" : "не равны"));
Массивы равны
Массивы не равны
В этом примере создаются три массива типа boolean. Массив states1 сравнивается с двумя другими. Результат первого сравнения будет true, т. к. states1 и states2 содержат одни и те же значения в одинаковом порядке.
Второе сравнение приведет к false, т. к. states3 хоть и содержит те же элементы, что и в states1, но их порядок отличается.
От сравнения примитивных типов давайте перейдем к объектам.
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Example[] arr1 = {new Example(), new Example()};
Example[] arr2 = {new Example(), new Example()};
System.out.println(Arrays.equals(arr1, arr2));
}
}
class Example {
int num = 3;
public String toString() {
return "" + num;
}
}
Программа выведет false, т. к. мы наступаем на те же самые грабли, что и ранее: внутри Arrays.equals() для сравнения элементов массива вызывается equals() с реализацией по умолчанию (сравнение ссылок). У нас в примере одно поле, но обычно их больше, и необходимо явно указывать, что для автора данного класса является равенством объектов. Для этого нужна своя реализация метода equals(). Я воспользуюсь средой разработки Intellij IDEA и сгенерирую в ней данный метод. В итоге класc Example будет выглядеть следующим образом:
class Example {
int num = 3;
public String toString() {
return "" + num;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Example example = (Example) o;
return num == example.num;
}
}
Если пропустить в реализации метода разного рода предварительные проверки, то в сухом остатке останется строка с непосредственным сравнением двух значений: поля экземпляра и переданного в метод значения. Результат проверки в виде boolean-значения возвращается методом в место его вызова:
return num == example.num;
Если запустить класс Main с использованием нашей реализации equals(), то предсказуемо два массива окажутся равными.
Оцените статью, если она вам понравилась!
SAMESOUND в Телеграме
Подписывайтесь на SAMESOUND в Телеграме
Звукорежиссёр Илья Евсюков на странице в социальной сети «ВКонтакте» рассказал о своих впечатлениях от нового синтезатора Native Instruments Massive X. Обзор Massive X получился коротким, очень субъективным, но не лишённым смысла. По мнению Ильи, новый инструмент — не тот Massive, которого все ждали (хотя разработчики не раз говорили, что Massive X — совершенно другой инструмент).
Начну с главного: я очень долго ждал релиза Massive. Так долго, что дело закончилось вот чем: покрутил 30 минут демку, переслушал множество пресетов, закрыл и удалил синтезатор.
При всем уважении к Native Instruments, первый Massive был типичной рабочей лошадкой. Для этой категории вещей самое главное — удобство использования. Скорость работы. Интуитивность. Понятность. В общем, называть удобство интерфейса можно как угодно. Massive X — тот самый случай, когда разработчики почему-то решили, что музыкантам нужен совершенно иной продукт. В этом они ошиблись.
Ладно, давайте я буду говорить за себя. Чего я хотел и ждал от Massive X:
1. Обратной совместимости патчей. Её нет. Уже резонная причина не ставить новую версию, когда необходимо периодически возвращаться к проектам столетней давности, в которых использовал синтезатор.
2. Простого улучшения алгоритмов генерации звука. Не кардинального изменения, а доработки. Теперь звук синтезатора ВООБЩЕ другой и кажется, будто Massive X сильно косит под аналог. Но зачем мне ещё один «типа аналоговый» синтезатор? У меня есть Diva, Legend и куча других плагинов, которые звучат на порядок лучше!
3. Удобства использования. Я ошарашен новым интерфейсом. Он плох и катастрофически усложняет жизнь. Такой же звук на другом синтезаторе я накручу за три минуты. В Massive X уйдет целая жизнь на привыкание!
4. Визуального сопровождения. Его нет — мол, рулите звук на слух. Если бы не дизайн, то я бы с радостью рулил на слух, но новая внешность попросту не позволяет этого сделать.
После тестирования у меня осталась только одна мысль — существует Xfer Serum. Нет, серьёзно: зачем покупать новый Massive X, когда есть Serum? Он же лучше во всём! В общем, новый Massive — странный инструмент. Он вроде бы как совершенно новый синтезатор по своей сути, создан не для олдфагов, но при этом и не рабочая лошадка.