Форум программистов, компьютерный форум, киберфорум
diadiavova
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  

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

Запись от diadiavova размещена 20.10.2016 в 13:18
Показов 2825 Комментарии 0
Метки c#, midi, music, winmm

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

Остановить воспроизведение несложно, надо только иметь в виду, что на момент остановки, какие-то звуки уже звучат и их неплохо было бы остановить, а стало быть простого уничтожения таймера будет недостаточно. Из возможных вариантов я пришел к выводу, что лучше всего просто закрыть инструмент. Другой вопрос, что при восстановлении звучания с того момента, на котором воспроизведение было остановлено или с любого другого момента, это обстоятельство тоже надо иметь в виду, то есть нужно вычислить, какие ноты звучали к тому моменту, с которого начинается воспроизведение. По всей видимости вычислять это придется пройдя все сообщения с самого начала, а стало быть и при остановке надо "убить" таймер, остановить инструмент и удалить все пакеты сообщений из очереди.
C#
1
2
3
4
5
6
        public void Pause()
        {
            timer.Dispose();
            pkgqueue = null;
            Close();
        }
Для восстановления звучания можно просто обновить список пакетов и запустить воспроизведение, оставив значение таймлайна на том же месте, где оно было остановлено. Учитывая, что при каждом тике отправляются сообщения из всех пакетов, значение таймлана которых меньше или равно текущему значению таймлайна плеера, они и будут все отправлены немедленно, то есть ноты будут "включаться и тут же "отключаться" пока не будет отправлен последний на текущий момент пакет. Если такой вариант не устраивает, то можно написать что-то вроде такого
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
        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 плеера.
C#
1
2
3
4
5
6
7
8
9
10
11
12
        public void GoOn()
        {
            CreatePackageQueue();
            Open();
            timer = new Timer(TimerTick, null, 0, TimerInterval);
        }
 
        public override void Play()
        {
            TimeLine = 0;
            GoOn();
        }
Фильтр по каналам

Иногда бывает нужно воспроизвести партию отдельного инструмента или отдельного голоса. Если то, что нужно воспроизвести записано в отдельный канал (а обычно так и бывает), то выделить из всего потока сообщений только те, которые относятся к этому каналу совсем несложно: канал указан в последних четырех битах сообщения, поэтому мы легко можем получить его значение и отфильтровать сообщения. Единственная вещь о которой надо при этом помнить - в массиве сообщений мы размещаем не только сообщения, но и паузы. Следующий метод размещаем в классе MessageProcessor, он принимает массив сообщений и номер канала, возвращает отфильтрованный массив сообщений.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        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();
        }
В проекте во вложении можно прослушать отдельные партии нашей мелодии, то есть сначала мы их объединили, а теперь, с помощью этого метода, можно разделить обратно.

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

Обычно процесс транспозиции (переноса в другую тональность) имеет некоторые сложности в силу того, что она является переносом всех нот на некоторое фиксированное количество хроматических интервалов, в то время как запись ведется в диатонической "системе координат". У нас же, для того, чтобы изменить тон ноты на определенное количество хроматических полутонов - достаточно просто прибавить к ее номеру количество полутонов, на которые надо ее повысить (или вычесть, если надо понизить). Соответственно весь процесс смещения мелодии будет похож на фильтрацию, только в результирующий массив мы будем добавлять все сообщения, предварительно изменив те, которые содержат информацию о ноте.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        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();
        }
В проекте во вложении можно прослушать пример, в котором наша мелодия исполняется на один тон выше, что достигнуто с помощью данной функции.
>>
Вложения
Тип файла: zip midi.zip (354.4 Кб, 435 просмотров)
Метки c#, midi, music, winmm
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Лето уходит
kumehtar 17.08.2026
Мысли в слух
kumehtar 17.08.2026
Забавно, насколько сейчас стала доступна информация. Например о магии, духовном развитии, медитациях, и других подобных направлениях, ранее зачастую тайных, передаваемых от учителя к ученику. Хотя. . .
Перемещение строк из ТЧ в другой документ с учетом текущего пробега
Maks 17.08.2026
Реализация из решения ниже выполнена на примере нетипового документа "Автозапчасти", с ТЧ "Шины". За основу взят алгоритм отсюда: https:/ / www. cyberforum. ru/ blogs/ 359708/ 10838. html Задача: . . .
Саморегулирующийся социальный контракт для сервера cross-section.
Hrethgir 14.08.2026
С кодом конечно таких глубоких размышлений пока не было, впрочем я уже привык к алгоритмизации. Суть предмета записи: снова в диалоге с нейросетью (я взял пока себе ник для учётки админа - Rector). . . .
Часы электронные
Uhbif79 12.08.2026
Выкладываю программу часов. Программа позволяет: 1. Использовать системное время и дату, 2. Есть возможность вводить время и дату вручную. 3. Реализованы 2 будильника: начало и конец рабочего дня. . . .
Часы с будильником на основе класса QLCDNumber
Uhbif79 12.08.2026
Всем добрый день, выкладываю программу часов с будильником на основе класса QLCDNumber. Здесь я пробовал самостоятельно создавал классы, впервые столкнулся с видимостью переменной одного класса из. . .
Установка MinGW GCC 16.2 и CMake
8Observer8 10.08.2026
VK Видео: https:/ / vkvideo. ru/ video-240781534_456239017 YouTube: eY5-5PyI9NM Текстовая версия
Неделя из жизни имитационной модели склада: мои кривые руки растут, откуда надо
anaschu 10.08.2026
Неделя из жизни имитационной модели склада: как я почти написал неправильную логику и что с этим делать Работаю сейчас над учебно-рабочим проектом: строю в AnyLogic имитационную модель процессов. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru