Сообщество - Web-технологии

Web-технологии

534 поста 5 786 подписчиков

Популярные теги в сообществе:

2

Пишем NFT модульный синтезатор на javascript. Часть 2

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

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


А тут кстати мы делимся полезными приколюхами для фронт-ендов. Если вы новичок, то это для вас.



Хватит пиздеть, пора к делу


Порт — это просто именованное хранилище числа с геттером и сеттером, имеющее своё визуальное представление (например в виде кружочка с подписанным названием). Задача порта — хранить число, переданное ему кабелем в момент вызова метода process() (подробнее о том что это за метод — чуть ниже) и отдавать это число владеющему портом модулю в момент востребования. Ещё порт должен уметь отдавать свои экранные координаты, чтобы движок мог правильно нарисовать кабель из одного порта в другой


Класс Port

class Port {
constructor(x, y, name) {
this.x = x; 
this.y = y;
this.name = name;
}
set(value) {
this.value = value;
}
get() {
return this.value; 
}
get_position() {
return [this.x, this.y];
}
draw() {
// тут надо нарисовать кружочек с координатами this.x
// и this.y и написать рядом с ним this.name
}
}

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


Класс Wire

class Wire {
constructor(porta, portb, scale=1, offset=0) {
this.porta = porta; 
this.portb = portb;
this.scale = scale;
this.offset = offset; 
}
process() {
this.portb.set(this.porta.get() * this.scale + this.offset);
}
draw() {
let p1 = this.porta.get_position(); // координаты первого порта
let p2 = this.portb.get_position(); // координаты второго порта
// рисуем сплайн из точки p1 в точку p2
}
}

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


Класс Module


class Module {
constructor(x, y, w, h, name) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.name = name;
this.i = []; // входные порты
this.o = []; // выходные порты
}
add_input(port) {
this.i[port.name] = port;
}
add_output(port) {
this.o[port.name] = port;
}
process() {
// здесь будет происходить весь процессинг аудио
}
draw() {
for(var name in this.i) this.i[name].draw(); 
for(var name in this.o) this.o[name].draw(); 
}
}

Наконец, движок. Движок модульного синтезатора должен уметь делать три главных вещи:


1. Рендерить звук, вызывая некоторый метод process() каждого модуля/кабеля для каждого аудиосемпла (т.е 44100 раз в секунду).

2. Отрисовывать модули, вызывая метод draw() каждого из них с некоторым FPS.

3. Уметь соединять модули проводами и пропагировать по ним сигнал.


С учётом этих требований, он будет выглядеть как-то так:


Класс Engine

class Engine {
constructor() {
this.modules = []; 
this.wires = []; 
this.OUT = new InvisiblePort(); 
}
add_module(m) {
this.modules.push(m); 
}
add_wire(oport, iport) {
this.wires.push(new Wire(oport, iport))
}
draw() {
for (const m of this.modules) m.draw(); 
for (const w of this.wires) w.draw(); 
}
process() {
for (const w of this.wires) w.process(); 
for (const m of this.modules) m.process(); 
return this.OUT.get(); 
}
}

Осталось только прикрутить движок к аудиопроцессору webaudio и p5js. И если с p5js всё более-менее понятно (нужно просто вызвать из глобальной функции draw() метод draw() движка), то с аудио есть один нюанс. API onaudioprocess, которое я использовал в своём проекте, уже давно маркируется как deprecated, но так как оно показалось мне удобнее чем audioworklet, я использовал его. В теории этот способ может не работать в каких-то браузерах.


Взаимодействие движка и webaudio API

audioContext = new AudioContext(); 
let scriptNode = audioContext.createScriptProcessor(2048, 0, 1); 

scriptNode.onaudioprocess = function(audioProcessingEvent) {
let outputBuffer = audioProcessingEvent.outputBuffer; 

for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
let outputData = outputBuffer.getChannelData(channel); 
for (let sample = 0; sample < outputData.length; sample++) {
outputData[sample] = engine.process() / 10.0; 
}
}
}

scriptNode.connect(audioContext.destination);

Модули


Для коммуникации модулей друг с другом в реальных системах как правило используется интерфейс CV/GATE. CV это control voltage, управляющее напряжение от -10 до 10 вольт, а GATE — прямоугольный импульс +10В, триггер.


VCO


Напишем в качестве примера один, но самый главный модуль, без которого не обходится ни один патч — VCO. VCO расшифровывается как Voltage Controlled Oscillator — это модуль, осциллирующий на высоте тона, пропорциональной напряжениям на входах CV (control voltage) и FM (frequency modulation). Здесь есть важная деталь — высота тона осциллятора пропорциональна напряжениям на входах, если измерять её в октавах а не в герцах. Эта конвенция называется 1V/OCT, что значит что прирост напряжения на 1 вольт даёт прирост частоты на октаву, то есть в 2 раза. Зная это, напишем модуль VCO с двумя управляющими частотой входами CV и FM.


Класс VCO

class VCO extends Module {
constructor(name, freq, x=-1, y=-1) {
super(name, x, y, 30, 70); 
this.freq = freq; 
this.delta = Math.PI * 2 / (sample_rate / freq); 
this.phase = 0; 
this.add_input(new Port(x=8, y=38, r=7, 'CV')); 
this.add_input(new Port(x=22, y=38, r=7, 'FM')); 
this.add_output(new Port(x=22, y=62, r=7, 'OUT')); 
this.value = 0; 
this.mod = 0; 
}
set_frequency(f) {
this.freq = f; this.delta = Math.PI * 2 / (sample_rate / f); 
}
draw() {
super.draw(); // здесь можно например нарисовать осциллограф
}
process() {
this.value = Math.sin(this.phase); 
this.o['OUT'].set( this.value ); 
this.mod = this.i['CV'].get() + this.i['FM'].get(); 
this.phase += this.delta * Math.pow(2, this.mod); 
if (this.phase > Math.PI * 2) this.phase -= Math.PI * 2; 
}
}

Как работают другие типы модулей и как их написать можно подсмотреть например в исходниках VCV Rack. Вместо разбора их реализации расскажу, что делает и зачем нужен каждый из них.


1. Sample&Hold — модуль который запоминает значение напряжения на входе по триггеру и выдаёт это значение в выход пока не произойдёт новый триггер.


2. Delay — модуль задержки сигнала. Имеет параметры time, устанавливающий время задержки, dry/wet, отвечающий за смешивание исходного сигнала с задержанным и feedback, регулирующий величину обратной связи.


3. Reverb — ревербератор, имитирующий естественное эхо большого помещения. Довольно сложная штука, реализованная на дилеях и фильтрах, как именно — можно посмотреть здесь и здесь


4. Scale — он же quantizer, штука притягивающая напряжение на входе к ближайшему напряжению, соответствующему ноте находящейся в заданной тональности.


5. Filter — частотный фильтр, low pass или high pass.

Итого


В итоге мы разобрались с вами как создавать NFT проекты и сделали генеративные горы)

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


Черкайте комменты, работали ли вы раньше с NFT? Если да, то расскажите про свой опыт :3

Показать полностью 2 1

Продвинутая реализация каррирования

Продвинутая реализация каррирования

Выше представлен "продвинутый" вариант функции каррирования.

Когда мы запускаем её, есть две ветви выполнения if:
1. Вызвать сейчас: если количество переданных аргументов args совпадает с количеством аргументов при объявлении функции (func.length) или больше, тогда вызов просто переходит к ней.
2. Частичное применение: в противном случае func не вызывается сразу. Вместо этого, возвращается другая обёртка pass, которая снова применит curried, передав предыдущие аргументы вместе с новыми. Затем при новом вызове мы опять получим либо новое частичное применение (если аргументов недостаточно) либо, наконец, результат.

На картинке выше показано, что произойдёт в случае sum(a, b, c). У неё три аргумента, так что sum.length = 3.


Telegram

Продвинутая реализация каррирования
Показать полностью 1

Объект события доступен и в HTML

Объект события доступен и в HTML

При назначении обработчика в HTML, тоже можно использовать объект event.

Это возможно потому, что когда браузер из атрибута создаёт функцию-обработчик, то она выглядит так: f
unction(event) { alert(event.type) }.

То есть, её первый аргумент называется "event", а тело взято из атрибута.

#браузер #документ #события

Объект события доступен и в HTML
24

4 крутые функции JavaScript, о которых не знает большинство junior разработчиков

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


Канал с вкусностями для новичков во front-end

1. Object.entries

Большинство разработчиков используют метод Object.keys для итерации по объекту. Этот метод возвращает только массив ключей объекта, а не значения. Можно использовать Object.entries для получения как ключа, так и значения.

Чтобы выполнить итерацию по объекту, можем сделать следующее:

Оба подхода, описанные выше, возвращают один и тот же результат, но Object.entries позволяет легко получить пару ключ-значение.



2. Метод replaceAll

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

Но в ES12 в String.prototype добавлен новый метод replaceAll, который заменяет все вхождения строки другим строковым значением:

3. Числовой разделитель


Можно использовать символ подчеркивания «_» в качестве числового разделителя, для упрощения подсчета количества нулей в числе.

Разделитель также можно использовать с числами BigInt, как в следующем примере:

Это делает число более читабельным.



4. document.designMode


Связанный с интерфейсным JavaScript, designMode позволяет редактировать любой контент на странице. Просто откройте консоль браузера и введите следующее:

Это полезно для дизайнеров, так как им не нужно каждый раз менять что-то в коде в соответствии с изменениями на экране.



Заключение


Рассмотренные трюки и фичи способны ускорить работу разработчика, а их использование не только необходимо, но и полезно. Продолжайте познавать скрытые возможности языка, изучать всевозможные трюки и повышать свой скилл, а мы поможем новым чтивом. Удачи!

Показать полностью 7

Декомпозиция процесса обработки событий

Декомпозиция процесса обработки событий

Метод handleEvent не обязательно должен выполнять всю работу сам. Он может вызывать другие методы, которые заточены под обработку конкретных типов событий (см. пример на картинке).

Теперь обработка событий разделена по методам, что упрощает поддержку кода.

#браузер #документ #события

YouTube Channel | LinkedIn | Instagram | Telegram

Декомпозиция процесса обработки событий

HTML-атрибуты

HTML-атрибуты

У HTML тегов могут быть атрибуты. Когда браузер парсит HTML, чтобы создать DOM-объекты для тегов, он распознаёт стандартные атрибуты и создаёт DOM-свойства для них.

Таким образом, когда у элемента есть id или другой стандартный атрибут, создаётся соответствующее свойство. Но этого не происходит,
если атрибут нестандартный.

#html #браузер #dom #bom

YouTube Channel | LinkedIn | Instagram | Telegram

HTML-атрибуты
3

Редкие теги в HTML: details, dialog, menu

Привет, Привет, в рамках этого урока мы познакомимся в очень редкими тегами в HTML которые практически невозможно встретить, а именно: details, dialog, menu. Текстовая версия урок в полной версии статьи.

Details


<details> - создает интерактивный элемент при нажатии на который пользователю будет показана дополнительная информация. Обычно используется в паре с тегом <summary>. Этот элемент практически не используется так как является стандартным и тяжел для стилизации через CSS. Обычно если нужен элемент с подобным функционалом его создают из базовых тегов используя HTML + CSS + JavaScript.

<details> 
<summary>Описание</summary> 
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut, saepe?</p> 
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut, saepe?</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut, saepe?</p> 
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut, saepe?</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut, saepe?</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut, saepe?</p>
</details>

Имеет атрибут open при наличии которого браузер сразу будет показывать скрытый контент

<details open>
<summary>Описание</summary>
<p>content</p>
</details>

Как вы могли заметить текст из тега <summary> стал заголовком <details>

Dialog

<dialog> - создает интерактивный элемент при нажатии на который пользователю будет показано всплывающее окно с контентом. Этот элемент практически не используется так как является стандартным и тяжел для стилизации через CSS.

Обычно если нужен элемент с подобным функционалом его создают из базовых тегов используя HTML + CSS + JavaScript.

В данном случае мы ничего не увидим так как по-умолчанию диалог скрыт

<dialog>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias consequatur cupiditate eos, excepturi inventore iste libero perferendis quibusdam reiciendis soluta vel veritatis voluptate voluptates! Blanditiis laborum quos repellat vero voluptatem?
</dialog>

Для того, чтобы открыть диалог, нам нужно добавить ему атрибут open

<dialog id="favDialog" open>  dialog content  </dialog>

Menu

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

<menu type="context" id="popup-menu">
<menuitem>Action</menuitem>
<menuitem>Another action</menuitem>
<hr/>
<menuitem>Separated action</menuitem>
</menu>

<menu type="toolbar">
<li>
<button type="menu" menu="file-menu">File</button>
<menu type="context" id="file-menu">
<menuitem label="New..." onclick="newFile()"></menuitem>
<menuitem label="Save..." onclick="saveFile()"></menuitem>
</menu>
</li>

<li>
<button type="menu" menu="edit-menu">Edit</button>
<menu type="context" id="edit-menu">
<menuitem label="Cut..." onclick="cutEdit()"></menuitem>
<menuitem label="Copy..." onclick="copyEdit()"></menuitem>
<menuitem label="Paste..." onclick="pasteEdit()"></menuitem>
</menu>
</li>
</menu>

В спецификации HTML4 <menu> работал по другом, но с выходом стандарта HTML5 логика тега <menu> была изменена, но на данный момент его практически не поддерживают никакие браузеры.

Более подробная информация о данных тегах рассказана в видео

Файлы с урока

Мой Телеграм канал

Показать полностью
Отличная работа, все прочитано!