Пример #1
0
void Audio::preloadControllers()/*{{{*/
{
	midiBusy = true;

	MidiTrackList* tracks = song->midis();
	for (iMidiTrack it = tracks->begin(); it != tracks->end(); ++it)
	{
		MidiTrack* track = *it;
		//activePorts[track->outPort()] = true;
		QList<ProcessList*> pcevents;

		int port = track->outPort();
		int channel = track->outChannel();
		int defaultPort = port;

		MidiDevice* md = midiPorts[port].device();
		if (!md)
		{
			continue;
		}
		MPEventList* playEvents = md->playEvents();
		playEvents->erase(playEvents->begin(), playEvents->end());

		PartList* pl = track->parts();
		for (iPart p = pl->begin(); p != pl->end(); ++p)
		{
			MidiPart* part = (MidiPart*) (p->second);
			EventList* events = part->events();
			unsigned partTick = part->tick();
			//unsigned partLen = part->lenTick();
			int delay = track->delay;

			unsigned offset = delay + partTick;

			for (iEvent ie = events->begin(); ie != events->end(); ++ie)
			{
				Event ev = ie->second;
				port = defaultPort;
				unsigned tick = ev.tick() + offset;
				//unsigned frame = tempomap.tick2frame(tick) + frameOffset;
				switch (ev.dataA())
				{
					case CTRL_PROGRAM:
					{
						ProcessList *pl = new ProcessList;
						pl->port = port;
						pl->channel = channel;
						pl->dataB = ev.dataB();
						bool addEvent = true;
						for(int i = 0; i < pcevents.size(); ++i)
						{
							ProcessList* ipl = pcevents.at(i);
							if(ipl->port == pl->port && ipl->channel == pl->channel && ipl->dataB == pl->dataB)
							{
								addEvent = false;
								break;
							}
						}
						if(addEvent)
						{
							printf("Audio::preloadControllers() Loading event @ tick: %d - on channel: %d - on port: %d - dataA: %d - dataB: %d\n",
								tick, channel, port, ev.dataA(), ev.dataB());
							pcevents.append(pl);
							playEvents->add(MidiPlayEvent(tick, port, channel, ev));
						}
					}
						break;
					default:
						break;
				}
			}
		}
		md->setNextPlayEvent(playEvents->begin());
	}
	midiBusy = false;
}/*}}}*/
Пример #2
0
void Audio::collectEvents(MidiTrack* track, unsigned int cts, unsigned int nts)
{
	int port = track->outPort();
	int channel = track->outChannel();
	int defaultPort = port;

	MidiDevice* md = midiPorts[port].device();
	MPEventList* playEvents = md->playEvents();
	MPEventList* stuckNotes = md->stuckNotes();

	PartList* pl = track->parts();
	for (iPart p = pl->begin(); p != pl->end(); ++p)
	{
		MidiPart* part = (MidiPart*) (p->second);
		// dont play muted parts
		if (part->mute())
			continue;
		EventList* events = part->events();
		unsigned partTick = part->tick();
		unsigned partLen = part->lenTick();
		int delay = track->delay;

		if (cts > nts)
		{
			printf("processMidi: FATAL: cur > next %d > %d\n",
					cts, nts);
			return;
		}
		unsigned offset = delay + partTick;
		if (offset > nts)
			continue;
		unsigned stick = (offset > cts) ? 0 : cts - offset;
		unsigned etick = nts - offset;
		// By T356. Do not play events which are past the end of this part.
		if (etick > partLen)
			continue;

		iEvent ie = events->lower_bound(stick);
		iEvent iend = events->lower_bound(etick);

		for (; ie != iend; ++ie)
		{
			Event ev = ie->second;
			port = defaultPort; //Reset each loop
			//
			//  dont play any meta events
			//
			if (ev.type() == Meta)
				continue;
			if (track->type() == Track::DRUM)
			{
				int instr = ev.pitch();
				// ignore muted drums
				if (ev.isNote() && drumMap[instr].mute)
					continue;
			}
			unsigned tick = ev.tick() + offset;
			unsigned frame = tempomap.tick2frame(tick) + frameOffset;
			switch (ev.type())
			{
				case Note:
				{
					int len = ev.lenTick();
					int pitch = ev.pitch();
					int velo = ev.velo();
					if (track->type() == Track::DRUM)
					{
						//
						// Map drum-notes to the drum-map values
						//
						int instr = ev.pitch();
						pitch = drumMap[instr].anote;
						port = drumMap[instr].port; //This changes to non-default port
						channel = drumMap[instr].channel;
						velo = int(double(velo) * (double(drumMap[instr].vol) / 100.0));
					}
					else
					{
						//
						// transpose non drum notes
						//
						pitch += (track->transposition + song->globalPitchShift());
					}

					if (pitch > 127)
						pitch = 127;
					if (pitch < 0)
						pitch = 0;
					velo += track->velocity;
					velo = (velo * track->compression) / 100;
					if (velo > 127)
						velo = 127;
					if (velo < 1) // no off event
						velo = 1;
					len = (len * track->len) / 100;
					if (len <= 0) // dont allow zero length
						len = 1;
					int veloOff = ev.veloOff();

					if (port == defaultPort)
					{
						//printf("Adding event normally: frame=%d port=%d channel=%d pitch=%d velo=%d\n",frame, port, channel, pitch, velo);

						// p3.3.25
						// 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 (extSyncFlag.value())
							playEvents->add(MidiPlayEvent(tick, port, channel, 0x90, pitch, velo));
						else

							playEvents->add(MidiPlayEvent(frame, port, channel, 0x90, pitch, velo));

						stuckNotes->add(MidiPlayEvent(tick + len, port, channel,
								veloOff ? 0x80 : 0x90, pitch, veloOff));
					}
					else
					{ //Handle events to different port than standard.
						MidiDevice* mdAlt = midiPorts[port].device();
						if (mdAlt)
						{

							// p3.3.25
							if (extSyncFlag.value())
								mdAlt->playEvents()->add(MidiPlayEvent(tick, port, channel, 0x90, pitch, velo));
							else

								mdAlt->playEvents()->add(MidiPlayEvent(frame, port, channel, 0x90, pitch, velo));

							mdAlt->stuckNotes()->add(MidiPlayEvent(tick + len, port, channel,
									veloOff ? 0x80 : 0x90, pitch, veloOff));
						}
					}

					if (velo > track->activity())
						track->setActivity(velo);
				}
					break;

					// Added by T356.
				case Controller:
				{
					//int len   = ev.lenTick();
					//int pitch = ev.pitch();
					if (track->type() == Track::DRUM)
					{
						int ctl = ev.dataA();
						// Is it a drum controller event, according to the track port's instrument?
						MidiController *mc = midiPorts[defaultPort].drumController(ctl);
						if (mc)
						{
							int instr = ctl & 0x7f;
							ctl &= ~0xff;
							int pitch = drumMap[instr].anote & 0x7f;
							port = drumMap[instr].port; //This changes to non-default port
							channel = drumMap[instr].channel;
							MidiDevice* mdAlt = midiPorts[port].device();
							if (mdAlt)
							{
								// p3.3.25
								// 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 (extSyncFlag.value())
									mdAlt->playEvents()->add(MidiPlayEvent(tick, port, channel,
										ME_CONTROLLER, ctl | pitch, ev.dataB()));
								else

									//playEvents->add(MidiPlayEvent(frame, port, channel, ev));
									mdAlt->playEvents()->add(MidiPlayEvent(frame, port, channel,
										ME_CONTROLLER, ctl | pitch, ev.dataB()));

							}
							break;
						}
					}
					// p3.3.25
					if (extSyncFlag.value())
						playEvents->add(MidiPlayEvent(tick, port, channel, ev));
					else

						playEvents->add(MidiPlayEvent(frame, port, channel, ev));
				}
					break;


				default:
					// p3.3.25
					if (extSyncFlag.value())
						playEvents->add(MidiPlayEvent(tick, port, channel, ev));
					else

						playEvents->add(MidiPlayEvent(frame, port, channel, ev));

					break;
			}
		}
	}
}
Пример #3
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;
}