Вот пробой самой внутренней петли, которая проходит через x
, чтобы нарисовать форму волны. Я подробно расскажу о своем объяснении в надежде, что некоторые дополнительные сведения могут быть полезны другим.
for x in Swift.stride(from:0, to:self.bounds.width + self.density, by:self.density)
{
В итерации цикла через ширину UIView путем приращения density
. Это позволяет контролировать два свойства: (1) «разрешение» формы волны и (2) сколько времени он проводит, генерируя UIBezierPath
, который нарисован. Просто установка density
на 2
(в ViewController.swift
) сократит количество вычислений пополам, а также создаст путь с половиной количества элементов для рисования. Увеличение density
на полный порядок (10
) может показаться чересчур большим, но вам будет трудно заметить визуальную разницу. Попробуйте установить значение 100
, если вы хотите увидеть треугольную волну.
Примечание стороны: за счет использования stride(from:to:by:)
, если ширина точка зрения не является равномерно делится на density
, сигнал может не доходить на правой стороне точки зрения, так + self.density
был добавлен.
// Parabolic scaling
let scaling = -pow(1/mid * (x - mid), 2) + 1
Вы заметили, как форма волны, кажется, прикреплен к точке крепления на обеих сторонах экрана? Вот что делает это параболическое масштабирование. Чтобы увидеть это более ясно, вы можете plug this formula в функциональности графиков Google, чтобы получить это:
В этом диапазоне, y
повторяет кривую, да, но обратите внимание, как y
начинается с 0, поднимается ровно 1,0 в центр, затем падает до 0. Более конкретно, он делает это в диапазоне x
от 0 до 1. Это ключ, потому что мы будем сопоставлять эту кривую с шириной вида, где левый край экрана карты до x=0
, а правый край экрана соответствует x=1
.
Если мы сопоставим эту кривую с нашей экранной формой волны и используем ее для масштабирования амплитуды (амплитуда: размер формы волны относительно ее центральной линии), вы увидите, что левая и правая конечные точки формы волны будет иметь амплитуду 0 (наши опорные точки) с размером формы волны, постепенно увеличивающейся до полноразмерного (1.0) в центре.
Чтобы увидеть полный эффект этого масштабирования, попробуйте изменить эту строку на let scaling = CGFloat(1.0)
.
На этом этапе мы готовы нарисовать форму волны. Вот исходная строка кода, о которой спрашивал ОП:
let y = scaling * maxAmplitude * normedAmplitude *
sin(CGFloat(2 * M_PI) * self.frequency * (x/self.bounds.width) + self.phase)
+ self.bounds.height/2.0
Это много, чтобы взять все сразу. Этот код делает то же точную вещь, но я разбил его на части во временные переменные с соответствующими именами, чтобы помочь в понимании того, что происходит:
let unitWidth = x/self.bounds.width
var wave = CGFloat(2 * M_PI)
wave *= unitWidth
wave *= self.frequency
let wavePosition = wave + self.phase
let waveUnitValue = sin(wavePosition)
var amplitude = waveUnitValue * maxAmplitude
amplitude *= scaling
amplitude *= normedAmplitude
let y = amplitude + self.bounds.height/2.0
Хорошо, давайте решать этот один бит за один раз. Начнем с unitWidth
. Помните, когда я упоминал, что мы собираемся отображать кривую на ширину экрана? Это то, что делает это unitWidth
расчет: в x
диапазоне от 0 до self.bounds.width
, unitWidth
будет находиться в диапазоне от 0 до 1.
Далее идет wave
. Важно отметить, что это значение предназначено для расчета синусоидальной волны. Обратите внимание, что функция sin
работает в Radians, что означает, что полный период синусоидальной волны будет находиться в диапазоне от 0 до 2π, поэтому мы начнем там (CGFloat(2 * M_PI)
).
Затем мы применяем наш unitWidth
к wave
, который определяет, где, в пределах синусоиды, мы хотим быть для данной позиции x
на вид. Подумайте об этом так: по левой стороне обзора unitWidth
равно 0, поэтому это умножение приводит к 0 (начало синусоиды). В правой части окна unitWidth
равно 1.0 (дает нам полную значение 2π - конец синусоидальной волны.) Если мы находимся в середине обзора, то unitWidth
будет 0,5, что даст нам полпути через весь период синусоиды. И все между ними. Это называется интерполяцией. Важно понимать, что мы не двигаем синусоидальную волну, мы ее преодолеваем.
Далее мы применяем self.frequency
к wave
. Это масштабирует синусоидальную волну, так что более высокие значения имеют больше холмов и долин. Частота 1 не будет делать ничего, и мы будем следовать естественной синусоидальной волне. Но это скучно, поэтому частота увеличивается немного (1,5), чтобы улучшить внешний вид. Как соль, приспосабливайтесь к вкусу. Здесь на 3x частоте:
До сих пор мы определили, как наша синусоида будет выглядеть по сравнению с точки зрения, что мы рисуем его. Наша следующая задача - дать ему движение. С этой целью мы добавим self.phase
в wave
. Это называется «фазой», потому что фаза представляет собой отчетливый период внутри формы волны. Постоянно изменяя self.phase
для каждого кадра анимации, рисунок будет начинаться в другом положении в пределах формы волны, заставляя его перемещаться по экрану.
Для расчета фактического значения синусоидальной волны (let waveUnitValue = sin(wavePosition)
) используется wavePosition
. Я назвал это waveUnitValue
, потому что результат sin() - это значение, которое варьируется от -1 до +1.Если мы рисовали его как есть, наша волна будет довольно скучной, напоминающей почти плоская линия:
«У меня есть потребность ... потребность в амплитуду»
- Nobody
Наши amplitude
начинается с применения maxAmplitude
к waveUnitValue
, растягивая его по вертикали. Зачем начинать с максимума? Если мы вернемся к этому вычислению переменной scaling
, нам следует напомнить, что это единичное значение - значение, которое находится в диапазоне от 0 до 1, что означает, что оно может уменьшить амплитуду (или оставить ее неизменной), но не увеличьте его.
И это именно то, что мы будем делать дальше, примените наше значение scaling
. Это приводит к тому, что наш сигнал имеет амплитуду 0 на концах, постепенно увеличиваясь до полной амплитуды в центре. Без этого мы бы что-то, что выглядит следующим образом:
Наконец, мы имеем normedAmplitude
. Если вы выполните код, вы увидите, что функция drawWave
вызывается в цикле для того, чтобы нарисовать несколько волн в виде (в этом случае появляются те вторичные или «тени»). normedAmplitude
используется для выбора различной амплитуды для каждой из осциллограмм, нарисованных как часть общего эффекта.
Интересно отметить, что нормированная амплитуда может быть отрицательной, что позволяет отражать формы тени в вертикальном направлении, заполняя пустые пространства формы сигнала. Попробуйте изменить использование normedAmplitude
в исходном коде abs(normedAmplitude)
и вы увидите что-то вроде этого (в сочетании с примером частоты 3x, чтобы подчеркнуть разницу):
Последний шаг заключается в центровки сигнала на вид (amplitude + self.bounds.height/2.0
), который становится окончательным значением y
, которое мы будем использовать для рисования формы волны.
Итак, um. Вот и все.
Я не уверен, что вопрос здесь ... – Abizern
Я хотел короткое объяснение кода внутри метода 'draw (rect)', но вопрос неясен. Поэтому я спрашиваю об этом .. почему «let y = масштабирование * maxAmplitude * normedAmplitude * sin (CGFloat (2 * M_PI) * self.frequency * (x/self.bounds.width) + self.phase) + self.bounds. высота/2.0' –