среда, 6 июня 2018 г.

MIDI музыка на C#. Часть 4-я.

  1. Пауза и воспроизведение с любого момента
  2. Фильтр по каналам
  3. Транспозиция

Пауза и воспроизведение с любого момента



Остановить воспроизведение несложно, надо только иметь в виду, что на момент остановки, какие-то звуки уже звучат и их неплохо было бы остановить, а стало быть простого уничтожения таймера будет недостаточно. Из возможных вариантов я пришел к выводу, что лучше всего просто закрыть инструмент. Другой вопрос, что при восстановлении звучания с того момента, на котором воспроизведение было остановлено или с любого другого момента, это обстоятельство тоже надо иметь в виду, то есть нужно вычислить, какие ноты звучали к тому моменту, с которого начинается воспроизведение. По всей видимости вычислять это придется пройдя все сообщения с самого начала, а стало быть и при остановке надо "убить" таймер, остановить инструмент и удалить все пакеты сообщений из очереди.
Код csharp Выделить
        public void Pause()
        {
            timer.Dispose();
            pkgqueue = null;
            Close();
        }
Для восстановления звучания можно просто обновить список пакетов и запустить воспроизведение, оставив значение таймлайна на том же месте, где оно было остановлено. Учитывая, что при каждом тике отправляются сообщения из всех пакетов, значение таймлана которых меньше или равно текущему значению таймлайна плеера, они и будут все отправлены немедленно, то есть ноты будут "включаться и тут же "отключаться" пока не будет отправлен последний на текущий момент пакет. Если такой вариант не устраивает, то можно написать что-то вроде такого
Код csharp Выделить
        void SendCurrentMessages()
        {
            var messages = new List<int>();
            var tline = 0;
            Action<Func<int, bool>> excludeMsgs = predicate =>
             {
                 var notesForExclude = messages.Where(predicate);
                 messages.RemoveAll(x => notesForExclude.Contains(x));
             };
            for (int i = 0; i < MsgList.Length; i++)
            {
                if (tline > TimeLine) return;
                if (MsgList[i] == 0)
                {
                    tline += MsgList[i + 1];
                    i += 2;
                }
                else
                {
                    var command = MsgList[i] >> 4 & 15;
                    switch (command)
                    {
                        case 8:
                            excludeMsgs(m => ((MsgList[i] & 15) == (m & 15)) && ((MsgList[i] >> 16 | 255) == (m >> 16 | 255)));
                            break;
                        case 9:
                            messages.Add(MsgList[i]);
                            break;
                        case 0xc:
                            excludeMsgs(m => (MsgList[i] & 15) == (m & 15));
                            messages.Add(MsgList[i]);
                            break;
                        default:
                            break;
                    }
                }
            }
            messages.ForEach(m => midiOutShortMsg(handle, m));
        }
Я этот метод не тестировал, так что правильную работу не гарантирую, но принцип его работы таков: обходятся сообщения, если это 9X (NoteOn), то оно просто добавляется в коллекцию, если 8X(NoteOff), то в коллекции находится 9X для того же канала и той же ноты и все такие сообщения извлекаются из коллекции. То же для CX: если в коллекции уже есть такая команда для того же канала, все они извлекаются из коллекции и добавляется новая. В конце, все что останется в коллекции messages отправляется с помощью midiOutShortMsg. До сего момента мы пользовались только тремя типами команд, но вообще этот метод придется сильно расширить, если будут использоваться и другие команды. В проекте из вложения этот метод есть, но я его там не использую.

Воспроизведение с любой позиции ничем не отличается от восстановления после паузы, для этого надо всего лишь установить нужное значение свойства TimeLine плеера.
Код csharp Выделить
        public void GoOn()
        {
            CreatePackageQueue();
            Open();
            timer = new Timer(TimerTick, null, 0, TimerInterval);
        }
 
        public override void Play()
        {
            TimeLine = 0;
            GoOn();
        }

Фильтр по каналам



Иногда бывает нужно воспроизвести партию отдельного инструмента или отдельного голоса. Если то, что нужно воспроизвести записано в отдельный канал (а обычно так и бывает), то выделить из всего потока сообщений только те, которые относятся к этому каналу совсем несложно: канал указан в последних четырех битах сообщения, поэтому мы легко можем получить его значение и отфильтровать сообщения. Единственная вещь о которой надо при этом помнить - в массиве сообщений мы размещаем не только сообщения, но и паузы. Следующий метод размещаем в классе MessageProcessor, он принимает массив сообщений и номер канала, возвращает отфильтрованный массив сообщений.
Код csharp Выделить
        public static int[] GetChannel(int[] sourceMessages, int channel)
        {
            var result = new List<int>();
            for (int i = 0; i < sourceMessages.Length; i++)
            {
                if(sourceMessages[i] == 0)
                {
                    i++;
                    result.Add(0);
                    result.Add(sourceMessages[i]);
                }
                else
                {
                    if ((sourceMessages[i] & 15) == channel) result.Add(sourceMessages[i]);
                }
            }
            return result.ToArray();
        }
В проекте во вложении можно прослушать отдельные партии нашей мелодии, то есть сначала мы их объединили, а теперь, с помощью этого метода, можно разделить обратно.

Транспозиция



Обычно процесс транспозиции (переноса в другую тональность) имеет некоторые сложности в силу того, что она является переносом всех нот на некоторое фиксированное количество хроматических интервалов, в то время как запись ведется в диатонической "системе координат". У нас же, для того, чтобы изменить тон ноты на определенное количество хроматических полутонов - достаточно просто прибавить к ее номеру количество полутонов, на которые надо ее повысить (или вычесть, если надо понизить). Соответственно весь процесс смещения мелодии будет похож на фильтрацию, только в результирующий массив мы будем добавлять все сообщения, предварительно изменив те, которые содержат информацию о ноте.
Код csharp Выделить
        public static int[] Traspose(int[] sourceMessages, int distance)
        {
            var result = new List<int>();
            for (int i = 0; i < sourceMessages.Length; i++)
            {
                var msg = sourceMessages[i];
                if (msg == 0)
                {
                    i++;
                    result.Add(0);
                    result.Add(msg);
                }
                else
                {
                    var command = (msg >> 4) & 15;
                    if (command == 8 || command == 9)
                    {
                        var note = (msg >> 8) & 255;
                        result.Add((msg >> 16) << 16 | (note + distance) << 8 | (msg & 255));
                    }
 
                }
            }
            return result.ToArray();
        }
В проекте во вложении можно прослушать пример, в котором наша мелодия исполняется на один тон выше, что достигнуто с помощью данной функции.
Вложения

Комментариев нет :

Отправить комментарий