Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Explanation of juce::MidiOutput methods
// 发送消息块。1参:MIDI缓冲(引用型对象)。2参:开始时间(毫秒)。3参:每秒的采样数
// 本函数用于将MIDI缓冲数据发送至MIDI设备,从而发出声音。是MidiOutput类的核心函数,也是实现播放MIDI文件的功能性函数
void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer,
const double millisecondCounterToStartAt,
double samplesPerSecondForBuffer)
{
// 执行此函数之前,必须启动后台线程。如果尚未启动,则首先执行startBackgroundThread()函数,而后再调用本函数. 此处使用了一个断言。如果尚未启动后台线程,则调试时程序代码将停留在此处。
// The thread must be started before calling this function.
jassert (isThreadRunning());
// 本函数的2参(开始时间)必须大于0,否则无法执行此函数
// The time to start cannot be negative.
jassert (millisecondCounterToStartAt > 0);
// 定义一个double常量(时间伸缩的比例值),初始化为1000/本函数的3参
//////////////////////////////////////////////////////////////////
// 该值应该是乐曲的速度和100拍/分钟的比例值
// NOTE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// this value should be scale factor of song's tempo and 100BMP ----- I guess-----
// but, how to get the MIDI file(song)'s tempo????????????????? it changeable...
/////////////////////////////////////////////////////////////////
// This scaling factor is used to convert timestamps to milliseconds.
const double timeScaleFactor = 1000.0 / samplesPerSecondForBuffer;
// 定义一个迭代器对象,用于迭代本函数的1参(MIDI缓冲对象)
// Used to step through messages in the buffer.
MidiBuffer::Iterator i (buffer);
// 定义一个uint8(无符号8位整型)常量,用普通型指针对象data指向该常量.此时,该常量尚未初始化.其内容(值)所位于的堆内存尚未分配.
const uint8* data;
// 定义两个整型变量
int len, time;
// 开始循环遍历
// getNextEvent()的作用:从缓冲中检索下一个事件. 1参data: MIDI消息块的指针, 2参len: MIDI消息块的字节数, 3参time: 事件的位置, 即音符的开始时间.
// Go through each event one by one.
while (i.getNextEvent (data, len, time))
{
// 定义一个double常量,初始化为本函数的3参(开始时间)+ 时间伸缩的比例值 * 音符的开始时间
// Convert the event timestamp to an absolute time in milliseconds.
const double eventTime = millisecondCounterToStartAt + timeScaleFactor * time;
// 定义一个PendingMessage(待处理的MIDI消息,其实是一个链表型的数据结构,有两个数据成员,一个是MIDI消息,一个是该类的指针型对象)类型的常指针m,初始化为当前所遍历的MIDI事件(用于初始化的3个参数依次为:MIDI消息块的首地址(指针),消息块的字节数,MIDI事件的位置(音符的开始时间))
// Create a message to pass to our sending thread.
PendingMessage* const m = new PendingMessage (data, len, eventTime);
// 定义一个作用域锁定对象,初始化为锁定临界对象lock
// lock是MidiOutput类(即本类)的数据成员
// The lock is necessary because the sending thread could be accessing the list.
const ScopedLock sl (lock);
// 判断,如果firstMessage为空指针,或者 firstMessage所包含的MIDI消息的时间戳大于刚刚定义的eventTime
// firstMessage是本类的数据成员,PendingMessage类型的指针型对象
if (firstMessage == nullptr || firstMessage->message.getTimeStamp() > eventTime)
{
// 下一个待处理的消息 = firstMessage
m->next = firstMessage;
// firstMessage = 当前的待处理消息
firstMessage = m;
}
// 否则(如果firstMessage不为空指针,或者 firstMessage所包含的MIDI消息的时间戳不大于刚刚定义的eventTime)
else
{
// 定义一个待处理消息的指针型对象,初始化为firstMessage
PendingMessage* mm = firstMessage;
// 嵌套循环,循环条件是:下一个待处理消息不是空指针,并且下一个消息的MIDI时间戳不大于eventTime
while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime)
// mm = 下一个待处理消息
mm = mm->next;
// 第40行定义的m的下一条消息 = mm的下一条消息
m->next = mm->next;
// mm的下一条消息 = m
mm->next = m;
}
// 作用域锁定sl至此解除锁定
} // 外层循环结束************
// 唤醒线程。如果线程调用了wait(),则此方法将唤醒它。该函数继承自Thread类。
notify();
}
//=========================================================================================
// 本函数继承并重写Thread类的同名函数
// 启动线程后,该线程锁执行的实际功能,在此函数中完成
void MidiOutput::run()
{
// 循环,条件是:线程未收到退出命令
while (! threadShouldExit())
{
// 定义3个uint32型变量,分别是:
// 当前时间
uint32 now = Time::getMillisecondCounter();
// 事件时间,初始化为0
uint32 eventTime = 0;
// 等待时间,初始化为500
uint32 timeToWait = 500;
// 定义一个待处理消息的指针型对象
PendingMessage* message;
{ // 作用域
// 作用域锁定开始
const ScopedLock sl (lock);
// message赋值为firstMessage
message = firstMessage;
// 判断,如果刚刚定义的message(待处理消息的指针型对象)不是空指针(有效)
if (message != nullptr)
{
// 事件时间 = MIDI消息的时间戳(绝对时间),四舍五入为整数
eventTime = (uint32) roundToInt (message->message.getTimeStamp());
// 如果事件时间大于当前时间+20毫秒
if (eventTime > now + 20)
{
// 等待时间 = 事件时间 - 当前时间 + 20毫秒
timeToWait = eventTime - (now + 20);
// 待处理消息指针置为空指针
message = nullptr;
}
// 如果事件时间不大于当前时间 + 20毫秒
else
{
// firstMessage赋值为下一条消息
firstMessage = message->next;
}
}
} // 作用域解锁
// 继续判断,如果此时message依然为有效指针
if (message != nullptr)
{
// 定义一个常量作用域指针,类型为待处理消息,初始化为message
const ScopedPointer<PendingMessage> messageDeleter (message);
// 如果事件时间大于当前时间
if (eventTime > now)
{
// 一直等待,等待时间为事件时间
Time::waitForMillisecondCounter (eventTime);
// 此处设置线程退出的时机,也就是说,如果发出线程退出命令,则此处可以退出
if (threadShouldExit())
break;
}
// 如果事件时间大于当前时间 - 200毫秒
if (eventTime > now - 200)
// 发送消息,该函数是MidiOutput类的另一个核心函数,用于发送单条MIDI消息
sendMessageNow (message->message); //***********!!!!!!!!!
}
else // 如果message是空指针
{
// 断言,等待时间小于30秒
jassert (timeToWait < 1000 * 30);
// 线程等待,等待时间为timeToWait
wait ((int) timeToWait);
}
}
// 清除所有待处理消息
clearAllPendingMessages();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.