Программирование звука в Windows (C++)
// Члены класса 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++)