Пример #1
0
QWidget* MenuList::createWidget(QWidget* parent)
{
	if(!_track)
		return 0;
	//if(!_track->isMidiTrack() || _track->type() != Track::AUDIO_SOFTSYNTH)
	//	return 0;
	MidiTrack* track = (MidiTrack*) _track;

	MidiDevice* md = 0;
	int port = -1;
	if (track->type() == Track::AUDIO_SOFTSYNTH)
	{
		md = dynamic_cast<MidiDevice*> (track);
		if (md)
			port = md->midiPort();
	}
	else
		port = track->outPort();
	list = new QListWidget(parent);
	list->setSelectionMode(QAbstractItemView::SingleSelection);
	list->setAlternatingRowColors(true);
	list->setEditTriggers(QAbstractItemView::NoEditTriggers);
	list->setFixedHeight(300);
	for (int i = 0; i < MIDI_PORTS; ++i)
	{
		QString name;
		name.sprintf("%d:%s", i + 1, midiPorts[i].portname().toLatin1().constData());
		list->insertItem(i, name);
		if (i == port)
			list->setCurrentRow(i);
	}
	connect(list, SIGNAL(itemPressed(QListWidgetItem*)), this, SLOT(updateData(QListWidgetItem*)));
	//connect(list, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(updateData(QListWidgetItem*)));
	//connect(list, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(updateData(QListWidgetItem*)));
	return list;
}
Пример #2
0
void Audio::processMidi()
{
	midiBusy = true;
	//
	// TODO: syntis should directly write into recordEventList
	//
	for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id)
	{
		MidiDevice* md = *id;

		MPEventList* playEvents = md->playEvents();
		//
		// erase already played events:
		//
		iMPEvent nextPlayEvent = md->nextPlayEvent();
		playEvents->erase(playEvents->begin(), nextPlayEvent);

		// klumsy hack for synti devices:
		if (md->isSynti())
		{
			SynthI* s = (SynthI*) md;
			while (s->eventsPending())
			{
				MidiRecordEvent ev = s->receiveEvent();
				md->recordEvent(ev);
			}
		}

		// Is it a Jack midi device?
		//MidiJackDevice* mjd = dynamic_cast<MidiJackDevice*>(md);
		//if(mjd)
		//  mjd->collectMidiEvents();
		md->collectMidiEvents();

		// Take snapshots of the current sizes of the recording fifos,
		//  because they may change while here in process, asynchronously.
		md->beforeProcess();
	}

	MPEventList* playEvents = metronome->playEvents();
	iMPEvent nextPlayEvent = metronome->nextPlayEvent();
	playEvents->erase(playEvents->begin(), nextPlayEvent);

	// p3.3.25
	bool extsync = extSyncFlag.value();

	for (iMidiTrack t = song->midis()->begin(); t != song->midis()->end(); ++t)
	{
		MidiTrack* track = *t;
		int port = track->outPort();
		MidiDevice* md = midiPorts[port].device();

		// Changed by Tim. p3.3.8
		//if(md == 0)
		//  continue;
		//MPEventList* playEvents = md->playEvents();
		//if (playEvents == 0)
		//    continue;
		//if (!track->isMute())
		MPEventList* playEvents = 0;
		if (md)
		{
			playEvents = md->playEvents();

			// only add track events if the track is unmuted
			if (!track->isMute())
			{
				if (isPlaying() && (curTickPos < nextTickPos))
					collectEvents(track, curTickPos, nextTickPos);
			}
		}

		//
		//----------midi recording
		//
		if (track->recordFlag())
		{
			//int portMask    = track->inPortMask();
			// p3.3.38 Removed
			//unsigned int portMask = track->inPortMask();
			//int channelMask = track->inChannelMask();

			MPEventList* rl = track->mpevents();
			MidiPort* tport = &midiPorts[port];

			// p3.3.38
			//for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id)
			//{
			RouteList* irl = track->inRoutes();
			for (ciRoute r = irl->begin(); r != irl->end(); ++r)
			{
				//if(!r->isValid() || (r->type != Route::ALSA_MIDI_ROUTE && r->type != Route::JACK_MIDI_ROUTE))
				//if(!r->isValid() || (r->type != Route::MIDI_DEVICE_ROUTE))
				if (!r->isValid() || (r->type != Route::MIDI_PORT_ROUTE)) // p3.3.49
					continue;

				int devport = r->midiPort; // p3.3.49
				if (devport == -1)
					continue;

				//MidiDevice* dev = *id;
				//MidiDevice* dev = r->device;
				MidiDevice* dev = midiPorts[devport].device(); // p3.3.49
				if (!dev)
					continue;


				// p3.3.50 Removed
				//int channel = r->channel;
				// NOTE: TODO: Special for input device sysex 'channel' marked as -1, ** IF we end up going with that method **.
				// This would mean having a separate 'System' channel listed in the routing popups.
				// The other alternative is to accept sysex from a device as long as ANY regular channel is routed from it,
				//  this does not require a 'System' channel listed in the routing popups.
				// But that requires more code below... Done.
				//if(channel == -1)
				//channel = MIDI_CHANNELS; // Special channel '17'
				//  continue;

				//int devport = dev->midiPort();

				// record only from ports marked in portMask:
				//if (devport == -1 || !(portMask & (1 << devport)))
				//if (devport == -1)
				//      continue;

				//MREventList* el = dev->recordEvents();
				//MidiFifo& rf = dev->recordEvents();


				int channelMask = r->channel; // p3.3.50
				if (channelMask == -1 || channelMask == 0)
					continue;
				for (int channel = 0; channel < MIDI_CHANNELS; ++channel) // p3.3.50
				{
					if (!(channelMask & (1 << channel)))
						continue;

					if (!dev->sysexFIFOProcessed())
					{
						// Set to the sysex fifo at first.
						MidiFifo& rf = dev->recordEvents(MIDI_CHANNELS);
						// Get the frozen snapshot of the size.
						int count = dev->tmpRecordCount(MIDI_CHANNELS);

						for (int i = 0; i < count; ++i)
						{
							MidiPlayEvent event(rf.peek(i));

							//unsigned time = event.time() + segmentSize*(segmentCount-1);
							//unsigned time = event.time() + (extsync ? config.division/24 : segmentSize*(segmentCount-1));
							//unsigned time = extsync ? curTickPos : (event.time() + segmentSize*(segmentCount-1));
							//event.setTime(time);
							//if(!extsync)
							//  event.setTime(event.time() + segmentSize*(segmentCount-1));

							event.setPort(port);

							// dont't echo controller changes back to software
							// synthesizer:
							if (!dev->isSynti() && md && track->recEcho())
								playEvents->add(event);

							// If syncing externally the event time is already in units of ticks, set above.
							if (!extsync)
							{
								//time = tempomap.frame2tick(event.time());
								//event.setTime(time);  // set tick time
								event.setTime(tempomap.frame2tick(event.time())); // set tick time
							}

							if (recording)
								rl->add(event);
						}

						dev->setSysexFIFOProcessed(true);
					}

					// Set to the sysex fifo at first.
					///MidiFifo& rf = dev->recordEvents(MIDI_CHANNELS);
					// Get the frozen snapshot of the size.
					///int count = dev->tmpRecordCount(MIDI_CHANNELS);

					// Iterate once for sysex fifo (if needed), once for channel fifos.
					///for(int sei = 0; sei < 2; ++sei)
					{
						// If on first pass, do sysex fifo.
						/*
						if(sei == 0)
						{
						  // Ignore any further channel routes on this device if already done here.
						  if(dev->sysexFIFOProcessed())
							continue;
						  // Go ahead and set this now.
						  dev->setSysexFIFOProcessed(true);
						  // Allow it to fall through with the sysex fifo and count...
						}
						else
						{
						  // We're on the second pass, do channel fifos.
						  rf = dev->recordEvents(channel);
						  // Get the frozen snapshot of the size.
						  count = dev->tmpRecordCount(channel);
						}
						 */

						MidiFifo& rf = dev->recordEvents(channel);
						int count = dev->tmpRecordCount(channel);

						//for (iMREvent ie = el->begin(); ie != el->end(); ++ie)
						for (int i = 0; i < count; ++i)
						{
							MidiPlayEvent event(rf.peek(i));

							//int channel = ie->channel();
							///int channel = event.channel();

							int defaultPort = devport;
							///if (!(channelMask & (1 << channel)))
							///{
							///      continue;
							///}

							//MidiPlayEvent event(*ie);
							int drumRecPitch = 0; //prevent compiler warning: variable used without initialization
							MidiController *mc = 0;
							int ctl = 0;

							//Hmmm, hehhh...
							// TODO: Clean up a bit around here when it comes to separate events for rec & for playback.
							// But not before 0.7 (ml)

							int prePitch = 0, preVelo = 0;

							event.setChannel(track->outChannel());

							if (event.isNote() || event.isNoteOff())
							{
								//
								// apply track values
								//

								//Apply drum inkey:
								if (track->type() == Track::DRUM)
								{
									int pitch = event.dataA();
									//Map note that is played according to drumInmap
									drumRecPitch = drumMap[(unsigned int) drumInmap[pitch]].enote;
									devport = drumMap[(unsigned int) drumInmap[pitch]].port;
									event.setPort(devport);
									channel = drumMap[(unsigned int) drumInmap[pitch]].channel;
									event.setA(drumMap[(unsigned int) drumInmap[pitch]].anote);
									event.setChannel(channel);
								}
								else
								{ //Track transpose if non-drum
									prePitch = event.dataA();
									int pitch = prePitch + track->transposition;
									if (pitch > 127)
										pitch = 127;
									if (pitch < 0)
										pitch = 0;
									event.setA(pitch);
								}

								if (!event.isNoteOff())
								{
									preVelo = event.dataB();
									int velo = preVelo + track->velocity;
									velo = (velo * track->compression) / 100;
									if (velo > 127)
										velo = 127;
									if (velo < 1)
										velo = 1;
									event.setB(velo);
								}
							}
								// Added by T356.
							else if (event.type() == ME_CONTROLLER)
							{
								//printf("11111111111111111111111111111111111111111111111111111\n");
								if (track->type() == Track::DRUM)
								{
									//printf("2222222222222222222222222222222222222222222222222222222222\n");
									ctl = event.dataA();
									// Regardless of what port the event came from, is it a drum controller event
									//  according to the track port's instrument?
									mc = tport->drumController(ctl);
									if (mc)
									{

										//printf("333333333333333333333333333333333333333333333333\n");
										int pitch = ctl & 0x7f;
										ctl &= ~0xff;
										int dmindex = drumInmap[pitch] & 0x7f;
										//Map note that is played according to drumInmap
										drumRecPitch = drumMap[dmindex].enote;
										devport = drumMap[dmindex].port;
										event.setPort(devport);
										channel = drumMap[dmindex].channel;
										event.setA(ctl | drumMap[dmindex].anote);
										event.setChannel(channel);
									}
								}
							}

							// p3.3.25
							// OOMidi uses a fixed clocks per quarternote of 24.
							// At standard 384 ticks per quarternote for example,
							// 384/24=16 for a division of 16 sub-frames (16 OOMidi 'ticks').
							// That is what we'll use if syncing externally.
							//unsigned time = event.time() + segmentSize*(segmentCount-1);
							//unsigned time = event.time() + (extsync ? config.division/24 : segmentSize*(segmentCount-1));
							// p3.3.34
							// Oops, use the current tick.
							//unsigned time = extsync ? curTickPos : (event.time() + segmentSize*(segmentCount-1));
							//event.setTime(time);
							// p3.3.35
							// If ext sync, events are now time-stamped with last tick in MidiDevice::recordEvent().
							// TODO: Tested, but record resolution not so good. Switch to wall clock based separate list in MidiDevice.
							// p3.3.36
							//if(!extsync)
							//  event.setTime(event.time() + segmentSize*(segmentCount-1));

							// dont't echo controller changes back to software
							// synthesizer:

							if (!dev->isSynti())
							{
								//printf("444444444444444444444444444444444444444444444444444444\n");
								//Check if we're outputting to another port than default:
								if (devport == defaultPort)
								{

									//printf("5555555555555555555555555555555555555555555\n");
									event.setPort(port);
									if (md && track->recEcho())
										playEvents->add(event);
								}
								else
								{
									//printf("66666666666666666666666666666666666666\n");
									// Hmm, this appears to work, but... Will this induce trouble with md->setNextPlayEvent??
									MidiDevice* mdAlt = midiPorts[devport].device();
									if (mdAlt && track->recEcho())
										mdAlt->playEvents()->add(event);
								}
								// Shall we activate meters even while rec echo is off? Sure, why not...
								if (event.isNote() && event.dataB() > track->activity())
									track->setActivity(event.dataB());
							}

							// p3.3.25
							// If syncing externally the event time is already in units of ticks, set above.
							if (!extsync)
							{
								//printf("7777777777777777777777777777777777777777777\n");
								// p3.3.35
								//time = tempomap.frame2tick(event.time());
								//event.setTime(time);  // set tick time
								event.setTime(tempomap.frame2tick(event.time())); // set tick time
							}

							// Special handling of events stored in rec-lists. a bit hACKish. TODO: Clean up (after 0.7)! :-/ (ml)
							if (recording)
							{
								//printf("888888888888888888888888888888888888888888888888\n");
								// In these next steps, it is essential to set the recorded event's port
								//  to the track port so buildMidiEventList will accept it. Even though
								//  the port may have no device "<none>".
								//
								if (track->type() == Track::DRUM)
								{
									//printf("99999999999999999999999999999999999999999999999999\n");
									// Is it a drum controller event?
									if (mc)
									{
										//printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n");
										MidiPlayEvent drumRecEvent = event;
										drumRecEvent.setA(ctl | drumRecPitch);
										// In this case, preVelo is simply the controller value.
										drumRecEvent.setB(preVelo);
										drumRecEvent.setPort(port); //rec-event to current port
										drumRecEvent.setChannel(track->outChannel()); //rec-event to current channel
										rl->add(drumRecEvent);
									}
									else
									{
										//printf("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n");

										MidiPlayEvent drumRecEvent = event;
										drumRecEvent.setA(drumRecPitch);
										drumRecEvent.setB(preVelo);
										// Changed by T356.
										// Tested: Events were not being recorded for a drum map entry pointing to a
										//  different port. This must have been wrong - buildMidiEventList would ignore this.
										//drumRecEvent.setPort(devport);
										drumRecEvent.setPort(port); //rec-event to current port

										drumRecEvent.setChannel(track->outChannel()); //rec-event to current channel
										rl->add(drumRecEvent);
									}
								}
								else
								{
									//printf("ccccccccccccccccccccccccccccccccccccccccccccc\n");
									// Restore record-pitch to non-transposed value since we don't want the note transposed twice next
									MidiPlayEvent recEvent = event;
									if (prePitch)
										recEvent.setA(prePitch);
									if (preVelo)
										recEvent.setB(preVelo);
									recEvent.setPort(port);
									recEvent.setChannel(track->outChannel());

									rl->add(recEvent);
								}
							}
						}
					}
				}
			}
		}
		// Added by Tim. p3.3.8
		if (md)
		{
			//printf("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\n");

			md->setNextPlayEvent(playEvents->begin());
		}
	}

	//
	// clear all recorded events in midiDevices
	// process stuck notes
	//
	for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id)
	{
		//printf("--------------------------aaaaaaaaaaaaaaaaaaaaaaaaaaaa\n");
		MidiDevice* md = *id;

		///md->recordEvents()->clear();
		// By T356. Done processing this rec buffer, now flip to the other one.
		///md->flipRecBuffer();
		// We are done with the 'frozen' recording fifos, remove the events.
		md->afterProcess();

		MPEventList* stuckNotes = md->stuckNotes();
		MPEventList* playEvents = md->playEvents();

		iMPEvent k;
		for (k = stuckNotes->begin(); k != stuckNotes->end(); ++k)
		{
			if (k->time() >= nextTickPos)
				break;
			MidiPlayEvent ev(*k);

			// p3.3.25
			//int frame = tempomap.tick2frame(k->time()) + frameOffset;
			if (extsync)
			{
				ev.setTime(k->time());
			}
			else
			{
				int frame = tempomap.tick2frame(k->time()) + frameOffset;
				ev.setTime(frame);
			}

			// p3.3.25
			//ev.setTime(frame);

			playEvents->add(ev);
		}
		stuckNotes->erase(stuckNotes->begin(), k);
		md->setNextPlayEvent(playEvents->begin());
	}

	//---------------------------------------------------
	//    insert metronome clicks
	//---------------------------------------------------

	MidiDevice* md = 0;
	if (midiClickFlag)
		md = midiPorts[clickPort].device();
	if (song->click() && (isPlaying() || state == PRECOUNT))
	{
		MPEventList* playEvents = 0;
		MPEventList* stuckNotes = 0;
		if (md)
		{
			playEvents = md->playEvents();
			stuckNotes = md->stuckNotes();
		}
		int bar, beat;
		unsigned tick;
		bool isMeasure = false;
		while (midiClick < nextTickPos)
		{
			if (isPlaying())
			{
				///sigmap.tickValues(midiClick, &bar, &beat, &tick);
				AL::sigmap.tickValues(midiClick, &bar, &beat, &tick);
				isMeasure = beat == 0;
			}
			else if (state == PRECOUNT)
			{
				isMeasure = (clickno % clicksMeasure) == 0;
			}
			// p3.3.25
			//int frame = tempomap.tick2frame(midiClick) + frameOffset;
			int evtime = extsync ? midiClick : tempomap.tick2frame(midiClick) + frameOffset;

			// p3.3.25
			//MidiPlayEvent ev(frame, clickPort, clickChan, ME_NOTEON,
			MidiPlayEvent ev(evtime, clickPort, clickChan, ME_NOTEON,
					beatClickNote, beatClickVelo);

			if (md)
			{
				// p3.3.25
				//MidiPlayEvent ev(frame, clickPort, clickChan, ME_NOTEON,
				MidiPlayEvent ev(evtime, clickPort, clickChan, ME_NOTEON,
						beatClickNote, beatClickVelo);

				if (isMeasure)
				{
					ev.setA(measureClickNote);
					ev.setB(measureClickVelo);
				}
				playEvents->add(ev);
			}
			if (audioClickFlag)
			{
				// p3.3.25
				//MidiPlayEvent ev1(frame, 0, 0, ME_NOTEON, 0, 0);
				MidiPlayEvent ev1(evtime, 0, 0, ME_NOTEON, 0, 0);

				ev1.setA(isMeasure ? 0 : 1);
				metronome->playEvents()->add(ev1);
			}
			if (md)
			{
				ev.setB(0);
				// p3.3.25
				// Removed. Why was this here?
				//frame = tempomap.tick2frame(midiClick+20) + frameOffset;
				//
				// Does it mean this should be changed too?
				// No, stuck notes are in units of ticks, not frames like (normal, non-external) play events...
				ev.setTime(midiClick + 10);

				if (md)
					stuckNotes->add(ev);
			}

			if (isPlaying())
				///midiClick = sigmap.bar2tick(bar, beat+1, 0);
				midiClick = AL::sigmap.bar2tick(bar, beat + 1, 0);
			else if (state == PRECOUNT)
			{
				midiClick += ticksBeat;
				if (clickno)
					--clickno;
				else
					state = START_PLAY;
			}
		}
		if (md)
			md->setNextPlayEvent(playEvents->begin());
		if (audioClickFlag)
			metronome->setNextPlayEvent(metronome->playEvents()->begin());
	}

	if (state == STOP)
	{
		//---------------------------------------------------
		//    end all notes
		//---------------------------------------------------

		for (iMidiDevice imd = midiDevices.begin(); imd != midiDevices.end(); ++imd)
		{
			MidiDevice* md = *imd;
			MPEventList* playEvents = md->playEvents();
			MPEventList* stuckNotes = md->stuckNotes();
			for (iMPEvent k = stuckNotes->begin(); k != stuckNotes->end(); ++k)
			{
				MidiPlayEvent ev(*k);
				ev.setTime(0); // play now
				playEvents->add(ev);
			}
			stuckNotes->clear();
		}
	}


	// p3.3.36
	//int tickpos = audio->tickPos();
	//bool extsync = extSyncFlag.value();
	//
	// Special for Jack midi devices: Play all Jack midi events up to curFrame.
	//
	for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id)
	{
		(*id)->processMidi();

		/*
		int port = md->midiPort();
		MidiPort* mp = port != -1 ? &midiPorts[port] : 0;
		MPEventList* el = md->playEvents();
		if (el->empty())
			  continue;
		iMPEvent i = md->nextPlayEvent();
		for(; i != el->end(); ++i)
		{
		  // If syncing to external midi sync, we cannot use the tempo map.
		  // Therefore we cannot get sub-tick resolution. Just use ticks instead of frames.
		  //if(i->time() > curFrame)
		  if(i->time() > (extsync ? tickpos : curFrame))
		  {
			//printf("  curT %d  frame %d\n", i->time(), curFrame);
			break; // skip this event
		  }

		  if(mp)
		  {
			if(mp->sendEvent(*i))
			  break;
		  }
		  else
		  {
			if(md->putEvent(*i))
			  break;
		  }
		}
		md->setNextPlayEvent(i);
		 */
	}

	midiBusy = false;
}
Пример #3
0
void removePortCtrlEvents(Part* part, bool doClones)
{
	// Traverse and process the clone chain ring until we arrive at the same part again.
	// The loop is a safety net.
	// Update: Due to the varying calls, and order of, incARefcount, (msg)ChangePart, replaceClone, and remove/addPortCtrlEvents,
	//  we can not rely on the reference count as a safety net in these routines. We will just have to trust the clone chain.
	Part* p = part;
	//int j = doClones ? p->cevents()->arefCount() : 1;
	//if(j > 0)
	{
		//for(int i = 0; i < j; ++i)
		while (1)
		{
			Track* t = p->track();
			if (t && t->isMidiTrack())
			{
				MidiTrack* mt = (MidiTrack*) t;
				int port = mt->outPort();
				const EventList* el = p->cevents();
				//unsigned len = p->lenTick();
				for (ciEvent ie = el->begin(); ie != el->end(); ++ie)
				{
					const Event& ev = ie->second;
					// Added by T356. Do not remove events which are past the end of the part.
					// No, actually, do remove ALL of them belonging to the part.
					// Just in case there are stray values left after the part end.
					//if(ev.tick() >= len)
					//  break;

					if (ev.type() == Controller)
					{
						int ch = mt->outChannel();
						int tck = ev.tick() + p->tick();
						int cntrl = ev.dataA();
						MidiPort* mp = &midiPorts[port];

						// Is it a drum controller event, according to the track port's instrument?
						if (mt->type() == Track::DRUM)
						{
							MidiController* mc = mp->drumController(cntrl);
							if (mc)
							{
								int note = cntrl & 0x7f;
								cntrl &= ~0xff;
								ch = drumMap[note].channel;
								mp = &midiPorts[drumMap[note].port];
								cntrl |= drumMap[note].anote;
							}
						}

						mp->deleteController(ch, tck, cntrl, p);
					}
				}
			}

			if (!doClones)
				break;
			// Get the next clone in the chain ring.
			p = p->nextClone();
			// Same as original part? Finished.
			if (p == part)
				break;
		}
	}
}
Пример #4
0
void addPortCtrlEvents(Event& event, Part* part, bool doClones)
{
	// Traverse and process the clone chain ring until we arrive at the same part again.
	// The loop is a safety net.
	// Update: Due to the varying calls, and order of, incARefcount, (msg)ChangePart, replaceClone, and remove/addPortCtrlEvents,
	//  we can not rely on the reference count as a safety net in these routines. We will just have to trust the clone chain.
	Part* p = part;
	//int j = doClones ? p->cevents()->arefCount() : 1;
	//if(j > 0)
	{
		//for(int i = 0; i < j; ++i)
		while (1)
		{
			// Added by Tim. p3.3.6
			//printf("addPortCtrlEvents i:%d %s %p events %p refs:%d arefs:%d\n", i, p->name().toLatin1().constData(), p, part->cevents(), part->cevents()->refCount(), j);

			Track* t = p->track();
			if (t && t->isMidiTrack())
			{
				MidiTrack* mt = (MidiTrack*) t;
				int port = mt->outPort();
				//const EventList* el = p->cevents();
				unsigned len = p->lenTick();
				//for(ciEvent ie = el->begin(); ie != el->end(); ++ie)
				//{
				//const Event& ev = ie->second;
				// Added by Tim. p3.3.6
				//printf("addPortCtrlEvents %s len:%d end:%d etick:%d\n", p->name().toLatin1().constData(), p->lenTick(), p->endTick(), event.tick());

				// Do not add events which are past the end of the part.
				if (event.tick() >= len)
					break;

				if (event.type() == Controller)
				{
					int ch = mt->outChannel();
					int tck = event.tick() + p->tick();
					int cntrl = event.dataA();
					int val = event.dataB();
					MidiPort* mp = &midiPorts[port];

					// Is it a drum controller event, according to the track port's instrument?
					if (mt->type() == Track::DRUM)
					{
						MidiController* mc = mp->drumController(cntrl);
						if (mc)
						{
							int note = cntrl & 0x7f;
							cntrl &= ~0xff;
							ch = drumMap[note].channel;
							mp = &midiPorts[drumMap[note].port];
							cntrl |= drumMap[note].anote;
						}
					}

					mp->setControllerVal(ch, tck, cntrl, val, p);
				}
				//}
			}

			if (!doClones)
				break;
			// Get the next clone in the chain ring.
			p = p->nextClone();
			// Same as original part? Finished.
			if (p == part)
				break;
		}
	}
}