Программирование звука в Windows (C++)

Программирование звука в Windows (C++) — MIDI

Советую поискать достаточно подробные переводы на русский язык документации по МИДИ, которые сделал Евгений Музыченко. Конечно же, при малейшей неясности приходится использовать англоязычную документацию из MSDN (надеюсь, что в новых русских версиях MSDN однажды переведут на русский язык документацию и по МИДИ.

Исходники некоторых функций для работы с MIDI из моей программы "Музыкальный Эказаменатор"

Скачать это в виде cpp-файла

// Члены класса CChildView (извините за жуткий дизайн, это я писал давно)
// В настоящее время я придерживаюсь принципа "Один объект - одна задача"
// Некоторые переменные мог не описать в комментах, пишите письма поправлю :)
int errCode;
DWORD midiMsg;
HMIDIOUT hMidiOut;
HMIDIIN hMidiIn;
CString tempString;
bool isMidiInError; // нужно, чтобы стартовое значение было false

// КРОМЕ ТОГО:
// ERRM - это функция для упрощения вывода сообщения об ошибке
// (чтобы не писать долго MessageBox с параметрами)
// L10N - это объект класса для Localization (L10N берёт строку текущего языка интерфейса пользователя
// из ини-файла, для каждого языка - свой ини-файл)
// options - это объект структуры, где храним все опции, выбранные пользователем
// m_hWnd - это член класса CChildWindow (в котором и происходит у меня сыр-бор, извините за жуткий дизайн)
// Следующие функции предоставляет Windows:
// midiInGetNumDevs, midiInStart, midiOutShortMsg, midiOutOpen, midiOutClose, midiInStop, midiInStop
// кстати midiOutReset(hMidiOut) - сброс всех звучащих или зависших нот

// Далее описаны функции-члены класса, обслуживающие миди-синтезатор звуковой карты

// Функция инициализации миди-синтезатора звуковой карты
void CChildView::MidiInit(void)
{    
    try {
        hMidiIn = 0;
        if (options.isHardwareMidiKbdEnabled)
        {
            // ---- test for MIDI input devices
            if (midiInGetNumDevs() == 0) // No MIDI input devices
            {
                isMidiInError = true;
                ERRM(L10N.GetStr(L"MSGBOX", L"MSGBOX_MIDIINERROR").c_str());
            }
            else
            {
                errCode = midiInOpen(&hMidiIn, 0, (DWORD_PTR)m_hWnd, 0, 
                        CALLBACK_WINDOW);
                if (errCode != MMSYSERR_NOERROR)
                {
                    isMidiInError = true;
                    ERRM(L10N.GetStr(L"MSGBOX", L"MSGBOX_MIDIINOPENERROR").c_str());
                }
                else
                {
                    midiInStart(hMidiIn);
                }
            }
        }
        if (midiOutGetNumDevs() == 0) // No MIDI output devices
        {
            tempString.Format(_T("%s %s"),
                L10N.GetStr(L"MSGBOX", L"MSGBOX_MIDIOUTERROR").c_str(),
                L10N.GetStr(L"MSGBOX", L"MSGBOX_PROGRAMWILLBECLOSED").c_str());
            ERRM((LPCTSTR)tempString);
            exit(1);
        }
        else
        {
            errCode = midiOutOpen(&hMidiOut, 0, 0, 0, 0);
            // errCode = midiOutOpen(&hMidiOut, MIDI_MAPPER, 0, 0, 0); // можно и так!
            if (errCode != MMSYSERR_NOERROR)
            {
                tempString.Format(_T("%s %s"),
                    L10N.GetStr(L"MSGBOX", L"MSGBOX_MIDIOUTOPENERROR").c_str(),
                    L10N.GetStr(L"MSGBOX", L"MSGBOX_PROGRAMWILLBECLOSED").c_str());
                ERRM((LPCTSTR)tempString);
                exit(1);
            }
            else
            {
                midiMsg = 0x0000c0; // 0xc0 - установка инструмента channel=0
                midiOutShortMsg(hMidiOut,midiMsg);
                // установка громкости, меняем громкость сразу на всех миди-каналах
                for (int i = 0; i < 16; i++)
                {
                    midiMsg = (DWORD)(volume << 16) | 0x0007b0 | (DWORD)i;
                    midiOutShortMsg(hMidiOut, midiMsg); 
                }
            }
        }        
    }

    catch (...)
    {
        tempString.Format(_T("%s %s"),
            L10N.GetStr(L"MSGBOX", L"MSGBOX_MIDIOUTERROR").c_str(),
            L10N.GetStr(L"MSGBOX", L"MSGBOX_PROGRAMWILLBECLOSED").c_str());
        ERRM((LPCTSTR)tempString);
        exit(1);
    }
}

void CChildView::OnDestroy()
{
    CWnd::OnDestroy();

    // сохраняем шрифтовые настройки и не только:
    SaveSettingsBank(datFileName_.c_str());
    midiOutClose(hMidiOut);
    if (!isMidiInError && options.isHardwareMidiKbdEnabled) // если миди-вход был открыт
    {
        midiInStop(hMidiIn);
        midiInClose(hMidiIn);
    }
}

// Воспроизводим один тон во всех одноголосных тестах одинаково
// пока для 37 клавиш (можно добавить потом ещё до 61)
void CChildView::PlaySingleTone(int ToneNumber, int midiChannel)
{
    // предыдущий миди-тон помнится индивидуально для каждого миди-канала
        // это необходимо, чтобы перед запуском нужной ноты заглушить предыдущую
    static DWORD preMidiTone[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};    

    if (ToneNumber < 37 && ToneNumber >= 0)
    {
        midiMsg = 0x000080 /*| (DWORD)(velocity[midiChannel] << 16)*/ | (DWORD)midiChannel | preMidiTone[midiChannel];
        midiOutShortMsg(hMidiOut, midiMsg); // гасим предыдущую ноту (метроном не гасит!)
        // 0x80 - выключение той же 0x30-ой ноты(8) + channel=0 (по умолчанию)
        // с той же самой скоростью, что и нажатие?...
        DWORD midiTone = (DWORD)ToneNumber + 36;
        midiTone <<= 8; // на один байт смещаемся влево
        preMidiTone[midiChannel] = midiTone;
        midiMsg = 0x000090 | (DWORD)(velocity[midiChannel] << 16) | (DWORD)midiChannel | midiTone; // ox90 - это нажатие ноты(9)
                // и channel = 0 (по умолчанию), 
                // по серёдке будет номер ноты
                // 0x7f - скорость нажатия (максимальная!)
        midiOutShortMsg(hMidiOut, midiMsg);
    }
    else
    {
        DWORD midiDrumTone;
        DWORD midiVelocity;
        switch (ToneNumber)
        {
        case 37:
            midiDrumTone = MIDICLICK_KEYNUMBER;
            midiVelocity = 0x3f; // иначе болезненно громко...
            break;
        case 38:
            midiDrumTone = midiMetronomeKey;
            midiVelocity = 0x4f;
            break;
        case 39:
            midiDrumTone = midiMetronomeKey;
            midiVelocity = 0x7f;
            break;
        default:
            return;
        }
        midiDrumTone <<= 8;
        midiVelocity <<= 16;
        midiMsg = 0x000099 | midiDrumTone | midiVelocity; // ox99 - это нажатие ноты(9) и channel = 9, 
            // средняя тетрада (2 байта) будет номер ноты
            // 0x7f - скорость нажатия (максимальная!)
        midiOutShortMsg(hMidiOut, midiMsg);
        midiMsg = 0x0089 | midiDrumTone; // ox89 - это убирание ноты(8) и channel = 9 , 
            // по серёдке будет номер ноты
        midiOutShortMsg(hMidiOut, midiMsg);
    }
}

void CChildView::StartMidiTone(int ToneNumber)
{
    DWORD midiTone = (DWORD)ToneNumber + 36; // смещаем тон на 3 октавы (от хрипящего суб-баса)
    midiTone <<= 8; // на один байт смещаемся влево
    midiMsg = 0x7f0090 | midiTone; // ox90 - это нажатие ноты(9) и channel = 0, 
                // средняя тетрада (2 байта) будет номер ноты
                // 0x7f - скорость нажатия (максимальная!)
    midiOutShortMsg(hMidiOut, midiMsg);
}

void CChildView::StopMidiTone(int ToneNumber)
{
    DWORD midiTone = (DWORD)ToneNumber + 36; // смещаем тон на 3 октавы (от хрипящего суб-баса)
    midiTone <<= 8; // на один байт смещаемся влево
    midiMsg = 0x7f0080 | midiTone; // ox80 - это отпускание ноты(8) и channel = 0, 
                // средняя тетрада (2 байта) будет номер ноты
                // 0x7f - скорость нажатия (максимальная!)
    midiOutShortMsg(hMidiOut, midiMsg);
}

Программирование звука в Windows (C++)