본문

VMPK에서 Extra Controller를 RtMidi까지 보내는 과정 분석


[그림 1, 2] Extra Controller를 추가한 화면과 설정값이 저장된 레지스트리의 위치,

vpiano.cpp의 void VPiano::readSettings()의 일부인 693번째 줄부터 분석을 시작한다. 우선 constants.h에 const QString QSTR_EXTRACONTROLLERS("ExtraControllers");로 선언이 되었다는점을 생각하며 글을 읽으면 편하다.

settings.beginGroup(QSTR_EXTRACONTROLLERS);
m_extraControls.clear();
QStringList keys = settings.allKeys();
keys.sort();
foreach(const QString& key, keys) {
m_extraControls << settings.value(key, QString()).toString();
}
settings.endGroup();

위 코드에 대한 설명은 [그림 1, 2]와 이전 게시물(QSettings 클래스의 설명과 사용법, 설정 저장위치)을 참고하면 매우 쉽게 이해가 될 것이다. 실행된 후 void VPiano::initExtraControllers()에서 foreach를 통하여 각각의 항목을 decode한다. 디코딩 작업은 extracontrols.cpp의 274번째 줄에 아래와 같이 정의되어있다. 즉 각각의 QString값을 ,으로 분리한후 참조변수에 값을 저장하는것이다. 예를 들자면 "Filter o/off,75,0,0,127,0" 가 있는데 따로 설명을 하지 않아도 바로 눈으로 봐서 분리가 된다.

void ExtraControl::decodeString(const QString s, QString& label, int& control, int& type, int& minValue, int& maxValue, int& defValue, int& size, QString& fileName){
QStringList lst = s.split(",");
if (!lst.isEmpty()) label = lst.takeFirst();
if (!lst.isEmpty()) control = ExtraControl::mbrFromString(lst.takeFirst(), 0);
if (!lst.isEmpty()) type = ExtraControl::mbrFromString(lst.takeFirst(), 0);
if (!lst.isEmpty()) minValue = ExtraControl::mbrFromString(lst.takeFirst(), 0);
if (!lst.isEmpty()) maxValue = ExtraControl::mbrFromString(lst.takeFirst(), 127);
if (!lst.isEmpty()) defValue = ExtraControl::mbrFromString(lst.takeFirst(), 0);
if (!lst.isEmpty() && type == 3) size = ExtraControl::mbrFromString(lst.takeFirst(), 100);
if (!lst.isEmpty() && type == 5) fileName = lst.takeFirst();
}


그럼 다시 initExtraControllers()으로 돌아와서 보면, 각 GUI 요소의 이벤트 시그널이 slotControlClicked, slotExtraController 두개의 슬롯으로 보내진다는 사실을 발견할 수 있다. 그중에서 비교적 간단한 slotExtraController는 다음과 같이 정의가 된다..

void VPiano::slotExtraController(const int value)
{
QWidget *w = static_cast<QWidget *>(sender());
QVariant p = w->property(MIDICTLNUMBER);
if (p.isValid()) {
int controller = p.toInt();
sendController( controller, value );
updateController( controller, value );
setWidgetTip(w, value);
}
}


여기에서 중요한 사실은 controller와 value를 통하여 sendController/updateController에게 호출된다는 것이다. 그러면 각각에 대한 정의는 어떨까?

void VPiano::sendController(const int controller, const int value)
{
std::vector<unsigned char> message;
unsigned char chan = static_cast<unsigned char>(m_channel);
unsigned char ctl = static_cast<unsigned char>(controller);
unsigned char val = static_cast<unsigned char>(value);
// Controller: 0xB0 + channel, ctl, val
message.push_back(STATUS_CTLCHG + (chan & MASK_CHANNEL));
message.push_back(ctl & MASK_SAFETY);
message.push_back(val & MASK_SAFETY);
sendMessageWrapper( &message );
}


mididefs.h에서는 다음과 같이 지정해 놓았다는 것을 참고한다. 물론 괄호안의 숫자는 참고하기 편하라고 임의로 넣은것.

#define STATUS_CTLCHG 0xB0 (1011 0000)

#define MASK_CHANNEL 0x0f (0000 1111)
#define MASK_SAFETY 0x7f (0111 1111)


그리고 m_channel = settings.value(QSTR_CHANNEL, 0).toInt(); m_sboxChannel->setValue(m_channel + 1); 으로,  지정되어있다는 것을 확인하고 주석메시지를 보면 좋다.즉, 아무 채널 설정도 변경하지 않았다면, 위의 설정에서는 QSTR_CHANNEL을 기본 0으로 지정해 놓고, GUI상의 채널 선택 요소에서는 보기좋게 채널값에 1을 더한다는것. 이제 아래의 sendMessageWrapper()를 확인한다.

void VPiano::sendMessageWrapper(std::vector<unsigned char> *message) const
{
try {
m_midiout->sendMessage( message );
}
catch (RtError& err) {
ui.statusBar->showMessage(QString::fromStdString(err.getMessage()));
}
}


드디어 RtMidi.cpp, 즉 RtMidi 라이브러리까지 진입하는 방법에 대해 알게 된 것이다. 위에서 왜 Vector로 값을 보내는가 했더니 아래 정의가 아예 이래버려서 그랬던 것이다. (void RtMidiOut :: sendMessage( std::vector<unsigned char> *message ))

댓글

Holic Spirit :: Tistory Edition

design by tokiidesu. powerd by kakao.