Как сыграть ноты в браузере? Как сократить любое длиннейшее произведение до 107 отдельных нот (которые можно еще и закэшировать) и килобайта-другого текста? Немного музыкальной теории, js-библиотеки и MIDI под катом.

Предисловие. На глубокие познания в нотной грамоте я не претендую. Но это не должно помешать понять читателю нотное письмо и сделать нечто, как минимум, похожее на звукоряд, записанный композитором в нотах. Многие термины и понятия нарочно упрощены. Указания на грамматические, синтаксические и прочие ошибки в тексте приветствуются в личных сообщениях.

Для нетерпеливых — сразу можно ознакомиться с результатом (7 тактов лунной сонаты). Работает в последних Chrome и Firefox на Ubuntu 14.04. На мобильных устройствах работать, скорее всего, не будет.

Как же воспроизвести нотную запись в браузере? Первое, что приходит в голову — найти решение, реализующее основные функции. Поиск по гитхабу выдает midi.js. Решение удобное. Лицензия MIT. Примеры — работают. Берем!

$ git clone https://github.com/mudcube/MIDI.js.git

Получили копию на локальном окружении. В каталоге examples видим Basic.html. Скопируем туда же как Betchoven.html и будем изменять содержимое. Интересующие нас строки:

    var delay = 0; // play one note every quarter second
    var note = 50; // the MIDI note
    var velocity = 127; // how hard the note hits
    // play the note
    MIDI.setVolume(0, 127);
    MIDI.noteOn(0, note, velocity, delay);
    MIDI.noteOff(0, note, delay + 0.75);

Написать надо что-то относительно простое и медленное. Например, первую часть Лунной сонаты Бетховена (Соната для фортепиано № 14 до-диез минор, оп. 27, № 2, как подсказывает вики). Найдем ноты.

Пусть будут такие:

Распишу минимально необходимое для понимания этой нотной записи.

Небольшой экскурс в теорию


Ноты на пианино. На первой картинке нарисована раскладка пианино.


Разметка октав не совсем верная — первые две ноты слева — октава 0 в упрощенной нотации. С первой ноты До (обозначена как C) начинается октава 1.

Нажатие на клавишу пианино с такой раскладкой приведет к проигрыванию соответствующего звука:
от контроктавных Ля — Ля-диез (он же Си-бемоль) — Си
до До пятой октавы. Где целые ноты (До, Ре,..) — белые клавиши, полутона (с диезами и бемолями) — черные.
В зарубежной литературе часто применяется другая, упрощенная нотация, которой мы в итоге и будем пользоваться:
ноты обозначаются латинскими буквами С (До), D (Ре), E (Ми), F (Фа), G (Соль), A (Ля), B (Си). Октавы просто пронумерованы от 0 до 8. Соответственно, на пианино вы увидите обозначения нот
от A0 — A0? (B0?) — B0 — С1
до С8. Черные клавиши могут быть не обозначены, как на картинке выше. На черных находятся промежуточные звуки (полутона) — ноты с диезами и бемолями.
Для каждой ноты и каждого полутона между ними, изображенных на данной раскладке есть соответствующий номер ноты в MIDI (от 21 до 108). Соотношение будет видно далее.
Над пианино видно два нотных стана (два раза по пять линий). Верхний — скрипичный, обозначает более высокие октавы, Нижний — басовый, более низкие. Загогулины в начале строк — знаки скрипичного и басового ключей, соответственно. Обратите внимание, что первая линия скрипичного ключа обозначает ноту Ми(E) первой(4) октавы, а первая басового — ноту Соль(G) большой(2).

Тональности

Следом за ключом на нотной записи видим 4 диеза. Так обозначается тональность. В данном случае она называется до-диез минор.

Для правильной игры в этой тональности нужно вместо нот, на линиях которых нарисованы диезы, играть диезы (следующая справа клавиша) этих нот. Диезы нарисованы на нотах C, D, F, G. Принципы построения тональностей расписывать не буду — сейчас они не так важны и информации в сети много. Желающие да загуглят.

Следовательно, если мы видим ноты C1, D1,..G7, мысленно меняем их на ближайшие справа C1?, D1?,… G7? и уже после ищем соответствующий номер в MIDI нумерации.

Знаки альтерации (?, ?, ?)

Если эти знаки стоят не в начале строки, а где-то в «случайном» месте, то они временно, до конца текущего такта (такты разделяются вертикальной чертой) изменяют ноту следующим образом:
— Отменяется альтерация этой ноты в этой октаве, заданная тональностью. Например, нота С3? и все следующие за ней C3 до конца такта (ближайшей вертикальной черты) будут играться как С3;
-? повышает ноту на полтона. A3? и все следующие A3 до конца такта играются как A3?;
-? понижает ноту на полтона. D3? и все следующие D3 будут играться как C3? (он же D3?).

Вдумчивый читатель уже заметил, что в некоторых случаях «временные диез и бемоль» не имеют смысла. Например, в тональности лунной сонаты при нотах С, D, F, G можно ставить и не ставить диез. Ничего от этого не измениться. Да, для таких случаев есть дубль-диезы, но они — за пределами рассмотрения данной статьи.

Длительность

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



Еще один нюанс — точки. Точка сразу после ноты значит, что эта нота звучит полторы указанный длительности. Например, 1/8 с точкой звучит на протяжении 1/8 + 1/16 = 3/16 доли такта. Сразу оговорюсь для знатоков, Adagio в beats per minute я не переводил, а если бы и перевел, то что делать с этим 60-80 — не очень понятно. Поэтому длительность такта подобрана на слух.

Используя полученные знания, посчитаем ноты и переведем их в MIDI по следующей картинке:



С2 -> 37 (на полтона выше, потому что про тональность — до-диез минор)
C3 -> 49
G3 -> 56 и так далее.

Получим несколько топорную, но рабочую реализацию первого такта:

window.onload = function () {
	MIDI.loadPlugin({
		soundfontUrl: "./soundfont/",
		instrument: "acoustic_grand_piano",
		onprogress: function(state, progress) {
			console.log(state, progress);
		},
		onsuccess: function() {
  			play();
		}
	});
};

function play() {
	var delay = 0; // play one note every quarter second
	var velocity = 127; // how hard the note hits
	var gap = 0.6;
	var duration = 0.4;
	MIDI.setVolume(0, 80);
	// первый такт
	delay += gap;
	MIDI.noteOn(0, 49, velocity, delay);
	MIDI.noteOff(0, 49, delay + 4 * gap);
	MIDI.noteOn(0, 37, velocity, delay);
	MIDI.noteOff(0, 37, delay + 4 * gap);

	MIDI.noteOn(0, 56, velocity, delay);
	MIDI.noteOff(0, 56, delay + duration);
	delay += gap;
	MIDI.noteOn(0, 61, velocity, delay);
	MIDI.noteOff(0, 61, delay + duration);
	delay += gap;
	MIDI.noteOn(0, 64, velocity, delay);
	MIDI.noteOff(0, 64, delay + duration);

	delay += gap;
	MIDI.noteOn(0, 56, velocity, delay);
	MIDI.noteOff(0, 56, delay + duration);
	delay += gap;
	MIDI.noteOn(0, 61, velocity, delay);
	MIDI.noteOff(0, 61, delay + duration);
	delay += gap;
	MIDI.noteOn(0, 64, velocity, delay);
	MIDI.noteOff(0, 64, delay + duration);

	delay += gap;
	MIDI.noteOn(0, 56, velocity, delay);
	MIDI.noteOff(0, 56, delay + duration);
	delay += gap;
	MIDI.noteOn(0, 61, velocity, delay);
	MIDI.noteOff(0, 61, delay + duration);
	delay += gap;
	MIDI.noteOn(0, 64, velocity, delay);
	MIDI.noteOff(0, 64, delay + duration);

	delay += gap;
	MIDI.noteOn(0, 56, velocity, delay);
	MIDI.noteOff(0, 56, delay + duration);
	delay += gap;
	MIDI.noteOn(0, 61, velocity, delay);
	MIDI.noteOff(0, 61, delay + duration);
	delay += gap;
	MIDI.noteOn(0, 64, velocity, delay);
	MIDI.noteOff(0, 64, delay + duration);
}

Это только начало, но нетренированный человек уже может притомится считать тональности и временные альтерации. Да и простыни кода получаются длинные. А это всего лишь первый такт. Конечно, нацеленный на ООП глаз сразу найдет мишени для рефакторинга. Вместе с тем, задачу высчитывания звука можно смело переложить на javascript.

Опишем тональность. Тональность — термин расплывчатый. Но в нашем конкретном примере тональностью будет просто необходимая альтерация нот в этой тональности. Как вы помните, в тональности до-диез минор мы видим ноты C1, D1,..G7, а подставляем на их место C1?, D1?,… G7?. Я просто обозначил сдвиг для каждой из этих нот (+1 или просто 1). 4 — количество диезов. Бемоли бы обозначались как -4. Знатоки поймут, что в данном узком случае разницы между параллельными тональностями до-диез минор и ми-мажор для нашей задачи — нет. Одинаковые диезы при тех же нотах.

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

var keys = {
    4 : {
        C : 1,
        D : 1,
        F : 1,
        G : 1
    }
};

Теперь создадим объект для проигрывания:

var player = {
    // длительность такта
    barDuration : 8,
    // шкала времени
    timeline : 0,
    // не очень важный параметр
    velocity : 127,
    // укажем тональность
    key : keys[4],
    // временные изменения тональности по тексту
    tempAlts : {},
    // параметры - нота как строка, длительность, надо ли сдвигать координату времени
    play : function(noteString, duration, moveTime) {
        // подсчет ноты будет ниже
        var noteInt = this.calcNote(noteString);

        MIDI.noteOn(0, noteInt, this.velocity, this.timeline);
        // звучание ноты заканчивается через длительность такта * длительность ноты в такте
        MIDI.noteOff(0, noteInt, this.velocity, this.timeline + this.barDuration * duration);

        if (typeof moveTime !== 'undefined' && moveTime === true) {
            this.move(duration);
        }
    },
    move : function(duration) {
        this.timeline += this.barDuration * duration;
        // в конце каждого такта временные диезы, бемоли и бекары отменяются.
        if (this.timeline % this.barDuration === 0) {
            this.tempAlts = {};}
    },
};

Теперь релизуем подсчет ноты и еще немного улучшим код:

var player = {
    barDuration : 8,
    timeline : 0,
    velocity : 127,
    key : keys[4],
    tempAlts : {},
    play : function(noteString, duration, moveTime) {
        var noteInt = this.calcNote(noteString);

        MIDI.noteOn(0, noteInt, this.velocity, this.timeline);
        MIDI.noteOff(0, noteInt, this.velocity, this.timeline + this.barDuration * duration);

        if (typeof moveTime !== 'undefined' && moveTime === true) {
            this.move(duration);
        }
    },
    move : function(duration) {
        this.timeline += this.barDuration * duration;
        if (this.isEndOfBar()) {
            this.tempAlts = {};}
    },
    calcNote : function(noteString) {
        var note = noteString[0];
        var noteWithOctave = noteString.substring(0,2);
        // есть ли временные знаки альтерации при ноте
        var altering = this.getAltering(noteString);
        // установим временные диезы, бемоли, бекары
        if (altering) {
            this.setTempAltering(noteWithOctave, altering);
        }
        // если временных альтераций нет - возвращаем номер ноты в MIDI + сдвиг по тональности
        if (this.tempAlts[noteWithOctave] !== undefined) {
            return MIDI.keyToNote[noteWithOctave] + this.tempAlts[noteWithOctave];
        }
        // если временные альтерации есть - возвращаем номер ноты в MIDI + сдвиг по временной альтерации
        // тональность здесь не учавствует
        return MIDI.keyToNote[noteWithOctave] +
                (this.key[note] !== undefined ? this.key[note] : 0);
    },
    isEndOfBar : function() {
        return !!(this.timeline % this.barDuration === 0)
    },
    // получить знак альтерации при ноте или false
    getAltering : function(noteString) {
        var altering = noteString[2];
        return altering !== undefined ? altering : false;
    },
    setTempAltering : function(noteWithOctave, altering) {
        switch (altering) {
            // знак бемоля при ноте временно понижает ноту на 1 полутон и так далее 
            case 'b': this.tempAlts[noteWithOctave] = -1; break;
            // бекар обозначил как "%"
            case '%': this.tempAlts[noteWithOctave] = 0;  break;
            case '#': this.tempAlts[noteWithOctave] = 1;  break;
        }
    }
}

Ну и сама нотная запись:

    player.play('C2', 1);
    player.play('C1', 1);
    player.play('G3', 1/12, true);
    player.play('C4', 1/12, true);
    player.play('E4', 1/12, true);
    player.play('G3', 1/12, true);
    player.play('C4', 1/12, true);
    player.play('E4', 1/12, true);
    player.play('G3', 1/12, true);
    player.play('C4', 1/12, true);
    player.play('E4', 1/12, true);
    player.play('G3', 1/12, true);
    player.play('C4', 1/12, true);
    player.play('E4', 1/12, true);
    ...

Получившийся результат можно услышать тут и увидеть здесь. Сделал для примера 7 тактов из 19.

UPD Поправил про октавы, нотацию и длительность согласно комментарию lair

Комментарии (17)


  1. lair
    13.05.2015 11:38
    +6

    Разметка октав не совсем верная — первые две ноты слева — нулевая октава. С первой ноты До (обозначена как C) начинается первая октава.

    На самом деле, нет. Вы путаете midi-октавы и октавы из музыкальной теории. В русскоязычной музыкальной теории октава всегда начинается с «до» (здесь вы правы), но «первая» октава — это не первая полная слева на фортепиано, а та, в которой ля — 440Гц (предполагая, что мы используем стандартную современную темперацию). Соответственно, октавы вверх («вправо») от нее нумеруются по возрастанию (вторая, третья...), октавы вниз («влево») нумеруются отдельно: малая, большая, контроктава, субконтроктава. Что характерно, вы дальше эти названия упоминаете, но не объясняете.

    В зарубежной литературе применяется другая, научная нотация, которой мы в итоге и будем пользоваться:
    ноты обозначаются латинскими буквами С (До), D (Ре), E (Ми), F (Фа), G (Соль), A (Ля), B (Си).

    Это не научная нотация. В научной (академической) нотации буквой B обозначается си-бемоль, а си обозначается буквой H. То, о чем вы говорите — это упрощенная нотация, появившаяся в записи популярной музыки (и тема продолжительных дискуссий «B ли H» в ru.guitar).

    Нота в нотной записи может иметь полый или закрашенный кружок (головку), иметь вертикальную палку (штиль) и флажок. Это определяет, какую часть такта по времени занимает нота — весь такт, полтакта и так далее. [...] Например, 1/8 с точкой звучит на протяжении 1/8 + 1/16 = 3/16 доли такта.

    А это просто неверно (точнее, это было верно во времена мензуральной нотации). В современной академической нотации длительности относительны сами себя: целая равна х, половина в два раза короче, четверть в четыре раза короче — ну, вы поняли. Размер такта задается… размером — это специальная помета в первой строчке (и дальше, если он меняется). В данном случае размер ? — это абревиатура для 2/2, что означает, что длина такта равна целой ноте (при этом он двухдольный, это важно для метрической структуры). Если кому интересно, то собственно абсолютная длина x определяется по указанию темпа (Adagio Sostenuto), метрической доле (половина), принятым соглашениям, ну и собственно личному вкусу и настроению исполнителя.


    1. Secessus Автор
      13.05.2015 13:09

      Благодарю за подробное и понятное объяснение.
      Поправил про нотацию, октавы и длительности.

      С размерностями и длительностями было непросто, так как нот 12, и каждая — 1/8. Но на самом деле размер — не 12/8, а 2/2. Кроме того, в пятом и шестом тактах после двенадцати восьмых нот (четырех триолей) еще идет 1/16, «не влезающая» в такт. Что несколько затрудняло стройную математическую модель, выстроившуюся у меня в голове. В итоге и вылетело совершенно из головы про сложные размеры, где такт != целая нота.

      Если кому интересно, то собственно абсолютная длина x определяется по указанию темпа (Adagio Sostenuto), метрической доле (половина), принятым соглашениям, ну и собственно личному вкусу и настроению исполнителя.


      В интернете мнения об абсолютной длительности расходятся от «нельзя определить, но стоит ориентироваться на темп» до «считается через BPM/метроном по Мальтеру». И хотя я понимаю, что на заре развития нотной грамоты сложно было задать метроном ровно на m ударов в минуту и сыграть ноту длительностью n/m, сегодня в музыкальном ПО совсем не хочется ссылаться на «вкус и настроение исполнителя».
      Тот же guitar pro ориентируется на BPM, насколько помню.


      1. lair
        13.05.2015 13:35
        +1

        С размерностями и длительностями было непросто, так как нот 12, и каждая — 1/8.

        Потому что каждая нота — не 1/8. Видите, там группировка «крышами» по три, и еще и троечка снизу пририсована на двух первых группах? Это триоли. А триоли (и вообще дробные длительности) — это весьма специфическая конструкция. Так, триоль восьмыми — это три равные ноты суммарной длительностью в 1/4, т.е… те самые 1/12.

        Кроме того, в пятом и шестом тактах после двенадцати восьмых нот (четырех триолей) еще идет 1/16, «не влезающая» в такт.

        Она не «после», она одновременно. Это двухголосие (в рамках правой руки), где нижний голос ровно играет триоли, а верхний — играет в двоичном делении пунктирный ритм: три четверти паузы, затем «нормальную» восьмую с точкой (3/16), затем шестнадцатую (1/16). Если вы будете пробовать изложить это все строго линейно, вы немножечко сойдете с ума.

        сегодня в музыкальном ПО совсем не хочется ссылаться на «вкус и настроение исполнителя».

        Это зависит только от задач «музыкального ПО».

        (метроном Мельцеля, а не Мальтера)


  1. WaveCut
    13.05.2015 13:09
    +1

    На хроме 44.0.2398.0 dev-m (64-bit) ваш пример падает с:

    plugin.webmidi.js:69

    Uncaught (in promise) TypeError: plugin.outputs is not a function
    at navigator.requestMIDIAccess.then.opts.api


    1. Secessus Автор
      13.05.2015 13:17

      Только что открыл с Chrome 42.0.2311.135 m в Win7 — работает…
      Попробуйте стабильные версии браузера FF/Chrome.


    1. pav5000
      14.05.2015 00:12

      Насколько знаю, в dev версии хрома сейчас включено экспериментальное midi api, которое умеет выполнять ввод с миди-устройств и вывод на миди-синтезатор ОС или внешний. Чтобы не валилось, надо или отключить это в chrome://flags или автору в коде указать явно, чтобы MIDI.js не использовало этот апи.


  1. EminH
    13.05.2015 16:03
    +1

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


  1. EminH
    13.05.2015 16:17

    А объясните пожалуйста, в примере на гитхабе

    // first bar
    100 player.play('C2', 1);
    


    и
    // second bar
    116 player.play('B1', 1);
    


    Это два разных инструмента звучащих одновременно? Потому что я слышу в демо две клавиши нажатых одновременно, а где это в коде задается понять не могу (разъясните с учетом нулевого уровня как в музыке так и в javascript)


    1. Secessus Автор
      13.05.2015 17:04
      +1

      Инструмент один и канал — тоже один, что бы это ни значило.
      В самом начале одновременно должно звучать по три ноты(клавиши). Может быть небольшое наложение вначале из-за тормозов и загрузки.

      Алгоритм виден в первой реализации. Он, упрощенно, такой:
      1 время = 0; задаем координату времени
      2 нота = номерНоты; номер ноты, начинающий играть в момент «время» (0)
      3 начатьИгратьНоту(нота, время);
      4 закончитьИгратьНоту(нота, время + длительностьНоты) звукоизвлечение происходит «в отдельном потоке», то есть не влияет на возможность обрабатывать алгоритм далее.
      5 если есть другие непроигранные ноты, начинающие звучать в момент «время» — вернуться к шагу 2 с одной из этой нот.
      6 время = время + расстояние до начала ближайшей по времени следующей ноты
      7 перейти к шагу 2

          // линия времени
          var delay = 0; 
          ...
          // длительность ноты
          var gap = 0.6;
          MIDI.setVolume(0, 80);
          // первый такт
          // начинаем играть ноту 49 в момент времени 0
          MIDI.noteOn(0, 49, velocity, delay);
          // перестаем играть ноту 49 в момент времени (0 + 4 длительности ноты)
          MIDI.noteOff(0, 49, delay + 4 * gap);
          // так как процесс происходит в "отдельном потоке", сразу пишем другую ноту
          // начинаем играть ноту 37 так же в момент времени 0
          MIDI.noteOn(0, 37, velocity, delay);
          // перестаем играть ноту 37 в момент времени (0 + 4 длительности ноты)
          MIDI.noteOff(0, 37, delay + 4 * gap);
      
          // опять же, т.к. поток другой, запускаем уже третью ноту №56 в момент времени 0
          MIDI.noteOn(0, 56, velocity, delay);
          // перестаем играть ее в момент времени (0 + 1 длительность ноты)
          MIDI.noteOff(0, 56, delay +gap);
          // сдвигаем координату времени до начала звучания следующей по времени ноты
          // время = 0 + 1 длительность ноты
          delay += gap;
          
          // нота 56 перестала звучать, ноты  37 и 49 продолжают играть
      
          // начинаем играть ноту 61 в момент времени (1 длительность ноты)
          MIDI.noteOn(0, 61, velocity, delay);
          // престаем играть ноту в момент времени (1 длительность ноты + 1 длительность ноты)
          MIDI.noteOff(0, 61, delay + duration);
          // сдвигаем координату времени до начала звучания следующей по времени ноты
          // время = 1 длительность ноты + 1 длительность ноты
          delay += gap;
      
          // нота 61 тоже перестала звучать, а ноты 37 и 49 будут играть еще два такта
      

          // first bar
          // тоже самое, только время является полем player'а
          //начинаем играть C2 в время=0 и прекращаем в время=1
          player.play('C2', 1);
          // начинаем играть C1 в время=0 и прекращаем время=1
          player.play('C1', 1);
          // начинаем играть G3 в время=0 и прекращаем время=1/12
          // true здесь приведет к время = время + 1/12
          player.play('G3', 1/12, true);
          // начинаем играть C4 в время=1/12 и прекращаем время=2/12
          // true здесь тоже подвинет временную координату внутри player'а
          player.play('C4', 1/12, true);
      


  1. EminH
    13.05.2015 17:11

    начинаем играть в время=0 и прекращаем в время=1

    ОК, а как дальше не перемещая время? player.play принимает 3 аргумента (нота, начало, конец)?


    1. Secessus Автор
      13.05.2015 17:25
      +1

      player.play принимает 3 аргумента (нота, длительность, надоЛиСдвигатьВремя).
      Если время сдвигать надо — время = время + длительность (та самая, которая второй аргумент).
      Можно также сдвинуть в ручном, так сказать, режиме.

          player.play('G3', 1/12, true);
          player.play('C4', 1/12, true);
      

      аналогично
          player.play('G3', 1/12);
          player.move(1/12);
          player.play('C4', 1/12);
          player.move(1/12);
      


      1. EminH
        13.05.2015 17:37

        понял, мерси


  1. nemilya
    13.05.2015 18:15

    Спасибо за статью! Всё работает :)

    Для визализации интересно прикрутить вот этот проект: www.vexflow.com

    MIT лицензия, open-source: github.com/0xfe/vexflow

    Можно использовать текстовую нотацию: www.vexflow.com/vextab

    Пример: www.vexflow.com/vextab/playground/?foo

    Кстати, здесь пример online проигрывания my.vexflow.com/articles/82?source=enabled
    И осталось дело за малым — перевести все public domain партиты с этого ресурса imslp.org :)


    1. nemilya
      13.05.2015 18:19

      Здесь, кстати, пример online проигрывания my.vexflow.com/articles/82?source=enabled


  1. stepik777
    13.05.2015 20:04

    Я думаю, было бы удобнее скачивать с интернета или самому создавать/редактировать в предназначенных для этого редакторах MIDI файлы, а потом их проигрывать в браузере, чем писать ноты на javascript.


  1. PaulZi
    14.05.2015 12:13

    Я использовал MIDI.js и VexTab/VexFlow в своём проекте, и могу сказать, что сейчас в MIDI.js очень не хватает нормального сэмлера.
    В частности в нашем проекте был инструмент гобой, и в midi были ноты которые проигрывались по 6 секунд и более… Чтобы MIDI.js нормально его воспроизводил, нужно либо делать очень длинные сэмплы, что сожрёт память, либо делать Loop, но Loop правильный.
    Правильный сэмпл, по-хорошему, должен иметь три участка — attack, loop и fade. То есть при noteOn должно сначала воспроизводиться участок атаки, потом зацикливать воспроизведение loop, причём он должен быть бесшовным, а при noteOff воспроизводить fade.
    Это конечно существенно потребует доработать библиотеку, но без этого нормального воспроизведения духовых и струнных (скрипка) инструментов не получится.


    1. WaveCut
      14.05.2015 17:42

      Не хватает конроля ADSR и лупатых точек:)