Как стать автором
Обновить
114
0

Desktop software developer

Отправить сообщение

99.99% задач связанных с MIDI - это воспроизведение MIDI.

Слишком категоричное заявление. Из моего опыта обращений пользователей это не так. Хотя, подтверждаю, задача одна из популярных.

синтезом звука занимаются VSTi и прочие программные продукты

Совершенно верно, и VSTi это уже про генерацию аудио (собственно, звука). VSTi забирает данные из MIDI Input и генерирует звук. MIDI это только про команды для синтезатора (аппратного, программного, VSTi и иже с ними), MIDI абсолютно никак с генерацией звука не связан сам по себе.

Ну вот подключил я недавно синтезатор к компьютеру и захотел с него получить квадро-звук с реверберацией из органного зала. Но стандартный миди-девайс Microsoft GS Wavetable Synth не даёт такой возможности, поэтому никак тут без низкоуровневого звука не обойтись.

Думаю, ваша проблема была какой-то иной или я чего-то не понял. Что мешает озвучивать ваш синтезатор (аппаратный, как я понимаю, речь не про генерацию MIDI из программы) любым VSTi? Зачем тут какой-то "низкоуровневый звук"?

Если же речь про озвучивание генерируемого программой MIDI, то ситуация стандартная и решается не менее стандартно через виртуальные устройства. Как я и писал выше, virtual cables это называется. Из наиболее популярных и бесплатных, наверное, loopMIDI:

  1. создаёте в loopMIDI устройство с именем X;

  2. выводите MIDI плейбек на этот X;

  3. забираете данные из X в любом нужном вам VSTi (т.е. у инструмента указываете MIDI Input = X).

Хорошее замечание, спасибо.

Заодно и ошибку в низкоуровневых алгоритмах нашёл, которую вы не захотели принимать

Принимаю. Но для пользователей функция, от того алгоритма зависящаая, работает верно.

Не нашёл, например, никакого упоминания про VSTi и устройства вывода звука, ни на каком уровне абстракции. Что заодно решило бы проблему с таймером в 1мс.

Вы путаете MIDI и Audio. MIDI playback никогда не будет без таймера. Audio будет, звук выводится на аудио-устройства посредством буфера.

Любой VSTi имеет параметр MIDI input. Это MIDI-устройство, откуда принимать даные. Стандартная практика вывода программного MIDI в VST-инструмент — виртуальные устройства (aka virtual cables). DryWetMIDI предоставляет возможность выводить MIDI.

Значит, это нужно делать самому, и вникать в низкоуровневую реализацию тоже самому, а тогда зачем вот это вот всё.

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

Так я и есть тот самый "благодарный пользователь" Sanford и MidiSharp.

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

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

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

Да, потому что в стандарте MIDI есть понятие "событие".

Да, об этом сказано в статье:

В MIDI есть события. При этом у каждого задано смещение во времени (в неких тиках) относительно предыдущего. Но в музыке, разумеется, никто не оперирует MIDI-событиями

По поводу

Поэтому правильные (и логичные) нэймспейсы будут Midi.Event.NoteOn, Midi.Event.NoteOff, Midi.Event.AllNoteOff и т.д.

не согласен категорически. Мало того, что я вообще в первый раз вижу отдельные пространства имён под каждый класс (пользователи за это будут вам очень "благодарны"), так ещё и логичного в этом ничего не вижу (AllNoteOff это так-то событие Control Change с определёнными параметрами).

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

Опять же, не могу согласиться. Да, это протокол передачи данных. Но его активно используют музыканты, например, вставляя в DAW. К слову, DAW пары Note On/Off объединяют, внезапно, в ноты в пиано ролле, потому что, как я и говорил, музыканты не работают в терминах MIDI.

Да и большое число пользователей, вопросов и обращений подтверждают, что API библиотеки сделан более или менее правильно. Библиотека ведь не запрещает вам использовать и "сырые" MIDI-события. Архитектура DryWetMIDI слоистая, есть и слой с абстракциями в терминах MIDI, можно и ими пользоваться. А можно и более высокоуровневыми.

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

Ну так чтобы сделать импорт, понадобится вся та кухня, что есть сейчас по работе с MIDI. Мне кажется, вы полагаете, что библиотека предоставляет только абстракции высокого уровня. Это не так. Даже в README проекта я об этом явно говорю:

DryWetMIDI is the .NET library to work with MIDI data and MIDI devices. It allows:

...

  • Manage content of a MIDI file either with low-level objects, like event, or high-level ones, like note (read the High-level data managing section of the library docs).

Спасибо большое за полезный комментарий, добавил информацию в статью.

Да, соглашусь.

Вообще, в библиотеке касательно вашего подхода есть несколько методов, например:

public static Note Get(NoteName noteName, int octave)

или

public static SevenBitNumber GetNoteNumber(NoteName noteName, int octave)

Длиннее, чем enum, но идея та же, и она верная: по имени ноты и октаве получить ноту/её номер.

Номер ноты и скорость нажатия лишь примеры, в MIDI намного больше таких сущностей.

Ну и enum не спасёт в случае вычисления значения по какой-то формуле. Придётся результат приводить к этому enum'у. А что если результат есть число, не принадлежащее перечисление? C# всё равно выполнит приведение, есть у enum'ов такая особенность. И пойдёт гулять по программе невалидное значение.

Мне кажется, вы невнимательно прочли статью.

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

Всё верно, и такое преобразование у меня явное. Также, как long к int. Посмотрите на код ещё раз. explicit operator это оно. Вы же пытаетесь доказать, что у меня исключение в неявном преобразование, но это не так.

Спасибо, добавил в статью информацию.

Спасибо.

Внутри моей библиотеки у SevenBitNumber/FourBitNumber есть методы Parse/TryParse. Ну и в целом, исключение при приведении типа или исключение при валидации параметра сообщает нам об одном и том же — значение невалидно. В MS это не запрещается, просто преобразование должно быть явным:

If a custom conversion can throw an exception or lose information, define it as an explicit conversion.

Касательно этого утверждения

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

не соглашусь. Как раз про неявное знать необязательно. На то оно и неявное, что никаких рисков нет. Из SevenBitNumber преобразование в byte не сопряжено ни с какими последствиями, поэтому оно неявное. Вероятно, вы имели в виду явное всё же.

Да, библиотека поддерживает старые версии языка, но для полноты добавил информацию в статью, спасибо.

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

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

Спасибо.

  1. Статья не про локализацию, а про подход. Локализовывать здорово, но в библиотеке я этим не занимаюсь, считаю, что английского вполне хватит. Если честно, по пальцам пересчитать библиотеки, которые выводят локализованные сообщения об ошибках.

  2. Да, это минус. С другой стороны, если вы получите сообщение с именем параметра, это всё равно ничего вам не скажет о том, почему в него попало невалидное значение.

  3. Если речь про отладку и сложные выражения, да, есть такой момент, дописал в статью.

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

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

Спасибо, вы навели меня на мысль, дополнил раздел Минусы. Но, кажется, вы говорите о чём-то другом. Можете, пожалуйста, показать пример, где возникают сложности? Это я про ваш пункт 2.

Спасибо, добавил в статью. Правда, работать это будет только до тех пор, пока мы не захотим использовать слева var.

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

Покажите, пожалуйста, ваш код, как вы проверяли. Если вы говорите про приближение действительного числа рациональным, речь же про метод FromDouble? Если так, то там невозможно получить зависание, как я уже упомянул выше. Более того, вам не нужно ничего делать с количеством итераций, просто не указывайте и всё будет работать с настройками по умолчанию :-)

Я к багам отношусь всегда серьёзно, поэтому, если ошибка есть, буду рад исправить. Но для этого мне нужно знать, как её поймать.

Видимо, дело вкуса.

Пусть будет дело вкуса.

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

var musicalTimeSpan = MusicalTimeSpan.FromDouble(
    0.77777777,
    new DoubleToMusicalTimeSpanSettings
    {
        FractionalPartEpsilon = 0.0000001
    });

За пару миллисекунд получил ответ 7/9. Никакого бесконечного цикла быть тут не может. Если вы посмотрите в настройки, там, помимо эпсилона на дробную часть и ограничения по точности приближения, есть ещё ограничение по количеству итераций. Это тот рубильник, который и призван спасти от каких-либо циклов и зависаний. Всё же лучше сперва проверять перед тем, как "находить ошибку" ;-)

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

Также замечу, что существует сайт Code Review Stack Exchange, созданный специально с целью помогать ревьювить код и помогать с кодом в плане архитетуры, стиля и всего такого. Я подозреваю, что автору нужно было написать туда.

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Software Developer, Test Automation Engineer
Lead
C#
.NET
Git
Selenium
Test Automation