From e4c6e3458357365a2951cef0715e8c89a61674f0 Mon Sep 17 00:00:00 2001 From: Martin Mlostek Date: Mon, 27 Feb 2017 18:05:09 +0100 Subject: [PATCH 1/9] Update MIKMIDICommand.m different messages within one packet parsed correctly --- Source/MIKMIDICommand.m | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index b1e32b59..4ebb2350 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -51,28 +51,18 @@ + (instancetype)commandWithMIDIPacket:(MIDIPacket *)packet; + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket { - NSInteger firstCommandType = inputPacket->data[0]; - NSInteger standardLength = MIKMIDIStandardLengthOfMessageForCommandType(firstCommandType); - if (standardLength <= 0 || inputPacket->length == standardLength) { - // Can't parse multiple message because we don't know the length of each one, - // or there's only one message there - MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:inputPacket]; - return command ? @[command] : @[]; - } - NSMutableArray *result = [NSMutableArray array]; - NSInteger packetCount = 0; + NSInteger packetIndex = 0; + NSInteger packetType = 0; + NSInteger packetLength = 0; while (1) { - - NSInteger dataOffset = packetCount * standardLength; - if (dataOffset > (inputPacket->length - standardLength)) break; + + packetType = inputPacket->data[packetIndex]; + packetLength = MIKMIDIStandardLengthOfMessageForCommandType(packetType); + NSInteger dataOffset = packetIndex; + if (dataOffset > (inputPacket->length - packetLength)) break; const Byte *packetData = inputPacket->data + dataOffset; - if (packetData[0] != firstCommandType && ((packetData[0] | 0x0F) != (firstCommandType | 0x0F))) { - // Doesn't look like multiple messages because they're not all the same type - MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:inputPacket]; - return command ? @[command] : @[]; - } - + // This is gross, but it's the only way I can find to reliably create a // single-message MIDIPacket. MIDIPacketList packetList; @@ -81,13 +71,13 @@ + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket sizeof(MIDIPacketList), midiPacket, inputPacket->timeStamp, - standardLength, + packetLength, packetData); MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:midiPacket]; if (command) [result addObject:command]; - packetCount++; + packetIndex += packetLength; } - + return result; } From 83ebc651db54f2002bd49add693a3235c388dad6 Mon Sep 17 00:00:00 2001 From: Martin 'Arkardiusz' Mlostek Date: Mon, 27 Mar 2017 02:33:26 +0200 Subject: [PATCH 2/9] advanced debugging tools --- Source/MIKMIDICommand.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index 4ebb2350..ed68c9cd 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -60,7 +60,7 @@ + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket packetType = inputPacket->data[packetIndex]; packetLength = MIKMIDIStandardLengthOfMessageForCommandType(packetType); NSInteger dataOffset = packetIndex; - if (dataOffset > (inputPacket->length - packetLength)) break; + if (packetLength == -1 || dataOffset > (inputPacket->length - packetLength)) break; const Byte *packetData = inputPacket->data + dataOffset; // This is gross, but it's the only way I can find to reliably create a From f18ebb31bf431710d032476c8a2a7eebe0d34008 Mon Sep 17 00:00:00 2001 From: Martin 'Arkardiusz' Mlostek Date: Tue, 16 May 2017 23:43:57 +0200 Subject: [PATCH 3/9] support for recording CC commands --- Source/MIKMIDISequencer.m | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index fea99926..f0b995f2 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -25,7 +25,8 @@ #import "MIKMIDISequence+MIKMIDIPrivate.h" #import "MIKMIDICommandScheduler.h" #import "MIKMIDIDestinationEndpoint.h" - +#import "MIKMIDIControlChangeCommand.h" +#import "MIKMIDIControlChangeEvent.h" #if !__has_feature(objc_arc) #error MIKMIDISequencer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDISequencer.m in the Build Phases for this target @@ -299,7 +300,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam NSMutableDictionary *tempoEventsByTimeStamp = [NSMutableDictionary dictionary]; Float64 overrideTempo = self.tempo; - if (!overrideTempo) { + if (!overrideTempo) { NSArray *sequenceTempoEvents = [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp, 0) toTimeStamp:toMusicTimeStamp]; for (MIKMIDITempoEvent *tempoEvent in sequenceTempoEvents) { NSNumber *timeStampKey = @(tempoEvent.timeStamp); @@ -340,14 +341,14 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam NSMutableArray *soloTracks = [[NSMutableArray alloc] init]; for (MIKMIDITrack *track in sequence.tracks) { if (track.isMuted) continue; - + [nonMutedTracks addObject:track]; if (track.solo) { [soloTracks addObject:track]; } } - + // Never play muted tracks. If any non-muted tracks are soloed, only play those. Matches MusicPlayer behavior NSArray *tracksToPlay = soloTracks.count != 0 ? soloTracks : nonMutedTracks; - + for (MIKMIDITrack *track in tracksToPlay) { MusicTimeStamp startTimeStamp = MAX(fromMusicTimeStamp - track.offset, 0); MusicTimeStamp endTimeStamp = toMusicTimeStamp - track.offset; @@ -362,7 +363,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } events = shiftedEvents; } - + id destination = events.count ? [self commandSchedulerForTrack:track] : nil; // only get the destination if there's events so we don't create a destination endpoint if not needed for (MIKMIDIEvent *event in events) { if ([event isKindOfClass:[MIKMIDINoteEvent class]] && [(MIKMIDINoteEvent *)event duration] <= 0) continue; @@ -387,7 +388,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (isLooping && (musicTimeStamp < loopStartTimeStamp || musicTimeStamp >= loopEndTimeStamp)) continue; MIDITimeStamp midiTimeStamp = [clock midiTimeStampForMusicTimeStamp:musicTimeStamp]; if (midiTimeStamp < MIKMIDIGetCurrentTimeStamp() && midiTimeStamp > fromMIDITimeStamp) continue; // prevents events that were just recorded from being scheduled - + MIKMIDITempoEvent *tempoEventAtTimeStamp = tempoEventsByTimeStamp[timeStampKey]; if (tempoEventAtTimeStamp) [self updateClockWithMusicTimeStamp:musicTimeStamp tempo:tempoEventAtTimeStamp.bpm atMIDITimeStamp:midiTimeStamp]; @@ -539,11 +540,11 @@ - (void)prepareForRecordingWithPreRoll:(BOOL)includePreRoll - (void)recordMIDICommand:(MIKMIDICommand *)command { if (!self.isRecording) return; - + MIDITimeStamp midiTimeStamp = command.midiTimestamp; MusicTimeStamp musicTimeStamp = [self.clock musicTimeStampForMIDITimeStamp:midiTimeStamp]; - - if (musicTimeStamp < 0) { return; } // Command is in pre-roll + + if (musicTimeStamp < 0) { return; } // Command is in pre-roll MIKMIDIEvent *event; if ([command isKindOfClass:[MIKMIDINoteOnCommand class]]) { // note On @@ -564,6 +565,15 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command } else if ([command isKindOfClass:[MIKMIDINoteOffCommand class]]) { // note Off MIKMIDINoteOffCommand *noteOffCommand = (MIKMIDINoteOffCommand *)command; event = [self pendingNoteEventWithNoteNumber:@(noteOffCommand.note) channel:noteOffCommand.channel releaseVelocity:noteOffCommand.velocity offTimeStamp:musicTimeStamp]; + } else if([command isKindOfClass:[MIKMIDIControlChangeCommand class]]) + { + MIKMIDIControlChangeCommand* ccCmd= (MIKMIDIControlChangeCommand*)command; + MIKMutableMIDIControlChangeEvent* ccEvent = [[MIKMutableMIDIControlChangeEvent alloc] init]; + ccEvent.controllerNumber = ccCmd.controllerNumber; + ccEvent.controllerValue = ccCmd.controllerValue; + ccEvent.channel = ccCmd.channel; + ccEvent.timeStamp = musicTimeStamp; + event = ccEvent; } if (event) [self.recordEnabledTracks makeObjectsPerformSelector:@selector(addEvent:) withObject:event]; From 8fc23ed94015a37ad42b0ff494f32ae9c5e33e75 Mon Sep 17 00:00:00 2001 From: Martin Mlostek Date: Sun, 7 Oct 2018 11:24:43 +0200 Subject: [PATCH 4/9] merged fix https://github.com/mixedinkey-opensource/MIKMIDI/commit/fe68908833f06f2fbe36ea970987b69ecac7982e --- Source/MIKMIDIClock.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index ac5c2cba..3ace9128 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -364,7 +364,7 @@ Float64 MIKMIDIClockSecondsPerMIDITimeStamp() dispatch_once(&onceToken, ^{ mach_timebase_info_data_t timeBaseInfoData; mach_timebase_info(&timeBaseInfoData); - secondsPerMIDITimeStamp = (timeBaseInfoData.numer / timeBaseInfoData.denom) / 1.0e9; + secondsPerMIDITimeStamp = ((Float64)timeBaseInfoData.numer / (Float64)timeBaseInfoData.denom) / 1.0e9; }); return secondsPerMIDITimeStamp; From c95190f0069db9cab215c89d73ef6df110991ce0 Mon Sep 17 00:00:00 2001 From: Martin 'Arkardiusz' Mlostek Date: Sat, 17 Nov 2018 19:25:18 +0100 Subject: [PATCH 5/9] Revert "merged fix" This reverts commit 8fc23ed94015a37ad42b0ff494f32ae9c5e33e75. --- Source/MIKMIDIClock.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 3ace9128..ac5c2cba 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -364,7 +364,7 @@ Float64 MIKMIDIClockSecondsPerMIDITimeStamp() dispatch_once(&onceToken, ^{ mach_timebase_info_data_t timeBaseInfoData; mach_timebase_info(&timeBaseInfoData); - secondsPerMIDITimeStamp = ((Float64)timeBaseInfoData.numer / (Float64)timeBaseInfoData.denom) / 1.0e9; + secondsPerMIDITimeStamp = (timeBaseInfoData.numer / timeBaseInfoData.denom) / 1.0e9; }); return secondsPerMIDITimeStamp; From e8c43270603c99d443ed691c7ffe5efc1c8bb592 Mon Sep 17 00:00:00 2001 From: Martin 'Arkardiusz' Mlostek Date: Sat, 17 Nov 2018 19:25:34 +0100 Subject: [PATCH 6/9] Revert "support for recording CC commands" This reverts commit f18ebb31bf431710d032476c8a2a7eebe0d34008. --- Source/MIKMIDISequencer.m | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index f0b995f2..fea99926 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -25,8 +25,7 @@ #import "MIKMIDISequence+MIKMIDIPrivate.h" #import "MIKMIDICommandScheduler.h" #import "MIKMIDIDestinationEndpoint.h" -#import "MIKMIDIControlChangeCommand.h" -#import "MIKMIDIControlChangeEvent.h" + #if !__has_feature(objc_arc) #error MIKMIDISequencer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDISequencer.m in the Build Phases for this target @@ -300,7 +299,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam NSMutableDictionary *tempoEventsByTimeStamp = [NSMutableDictionary dictionary]; Float64 overrideTempo = self.tempo; - if (!overrideTempo) { + if (!overrideTempo) { NSArray *sequenceTempoEvents = [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp, 0) toTimeStamp:toMusicTimeStamp]; for (MIKMIDITempoEvent *tempoEvent in sequenceTempoEvents) { NSNumber *timeStampKey = @(tempoEvent.timeStamp); @@ -341,14 +340,14 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam NSMutableArray *soloTracks = [[NSMutableArray alloc] init]; for (MIKMIDITrack *track in sequence.tracks) { if (track.isMuted) continue; - + [nonMutedTracks addObject:track]; if (track.solo) { [soloTracks addObject:track]; } } - + // Never play muted tracks. If any non-muted tracks are soloed, only play those. Matches MusicPlayer behavior NSArray *tracksToPlay = soloTracks.count != 0 ? soloTracks : nonMutedTracks; - + for (MIKMIDITrack *track in tracksToPlay) { MusicTimeStamp startTimeStamp = MAX(fromMusicTimeStamp - track.offset, 0); MusicTimeStamp endTimeStamp = toMusicTimeStamp - track.offset; @@ -363,7 +362,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } events = shiftedEvents; } - + id destination = events.count ? [self commandSchedulerForTrack:track] : nil; // only get the destination if there's events so we don't create a destination endpoint if not needed for (MIKMIDIEvent *event in events) { if ([event isKindOfClass:[MIKMIDINoteEvent class]] && [(MIKMIDINoteEvent *)event duration] <= 0) continue; @@ -388,7 +387,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (isLooping && (musicTimeStamp < loopStartTimeStamp || musicTimeStamp >= loopEndTimeStamp)) continue; MIDITimeStamp midiTimeStamp = [clock midiTimeStampForMusicTimeStamp:musicTimeStamp]; if (midiTimeStamp < MIKMIDIGetCurrentTimeStamp() && midiTimeStamp > fromMIDITimeStamp) continue; // prevents events that were just recorded from being scheduled - + MIKMIDITempoEvent *tempoEventAtTimeStamp = tempoEventsByTimeStamp[timeStampKey]; if (tempoEventAtTimeStamp) [self updateClockWithMusicTimeStamp:musicTimeStamp tempo:tempoEventAtTimeStamp.bpm atMIDITimeStamp:midiTimeStamp]; @@ -540,11 +539,11 @@ - (void)prepareForRecordingWithPreRoll:(BOOL)includePreRoll - (void)recordMIDICommand:(MIKMIDICommand *)command { if (!self.isRecording) return; - + MIDITimeStamp midiTimeStamp = command.midiTimestamp; MusicTimeStamp musicTimeStamp = [self.clock musicTimeStampForMIDITimeStamp:midiTimeStamp]; - - if (musicTimeStamp < 0) { return; } // Command is in pre-roll + + if (musicTimeStamp < 0) { return; } // Command is in pre-roll MIKMIDIEvent *event; if ([command isKindOfClass:[MIKMIDINoteOnCommand class]]) { // note On @@ -565,15 +564,6 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command } else if ([command isKindOfClass:[MIKMIDINoteOffCommand class]]) { // note Off MIKMIDINoteOffCommand *noteOffCommand = (MIKMIDINoteOffCommand *)command; event = [self pendingNoteEventWithNoteNumber:@(noteOffCommand.note) channel:noteOffCommand.channel releaseVelocity:noteOffCommand.velocity offTimeStamp:musicTimeStamp]; - } else if([command isKindOfClass:[MIKMIDIControlChangeCommand class]]) - { - MIKMIDIControlChangeCommand* ccCmd= (MIKMIDIControlChangeCommand*)command; - MIKMutableMIDIControlChangeEvent* ccEvent = [[MIKMutableMIDIControlChangeEvent alloc] init]; - ccEvent.controllerNumber = ccCmd.controllerNumber; - ccEvent.controllerValue = ccCmd.controllerValue; - ccEvent.channel = ccCmd.channel; - ccEvent.timeStamp = musicTimeStamp; - event = ccEvent; } if (event) [self.recordEnabledTracks makeObjectsPerformSelector:@selector(addEvent:) withObject:event]; From a19e8c9751e3336a5434c2132127c4a5d67243ee Mon Sep 17 00:00:00 2001 From: Martin 'Arkardiusz' Mlostek Date: Sat, 17 Nov 2018 19:25:40 +0100 Subject: [PATCH 7/9] Revert "advanced debugging tools" This reverts commit 83ebc651db54f2002bd49add693a3235c388dad6. --- Source/MIKMIDICommand.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index ed68c9cd..4ebb2350 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -60,7 +60,7 @@ + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket packetType = inputPacket->data[packetIndex]; packetLength = MIKMIDIStandardLengthOfMessageForCommandType(packetType); NSInteger dataOffset = packetIndex; - if (packetLength == -1 || dataOffset > (inputPacket->length - packetLength)) break; + if (dataOffset > (inputPacket->length - packetLength)) break; const Byte *packetData = inputPacket->data + dataOffset; // This is gross, but it's the only way I can find to reliably create a From a7c4c2ba1e228af70cbc32b886b98d6b58b59492 Mon Sep 17 00:00:00 2001 From: Martin 'Arkardiusz' Mlostek Date: Sat, 17 Nov 2018 19:25:44 +0100 Subject: [PATCH 8/9] Revert "Update MIKMIDICommand.m" This reverts commit e4c6e3458357365a2951cef0715e8c89a61674f0. --- Source/MIKMIDICommand.m | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index 4ebb2350..b1e32b59 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -51,18 +51,28 @@ + (instancetype)commandWithMIDIPacket:(MIDIPacket *)packet; + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket { + NSInteger firstCommandType = inputPacket->data[0]; + NSInteger standardLength = MIKMIDIStandardLengthOfMessageForCommandType(firstCommandType); + if (standardLength <= 0 || inputPacket->length == standardLength) { + // Can't parse multiple message because we don't know the length of each one, + // or there's only one message there + MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:inputPacket]; + return command ? @[command] : @[]; + } + NSMutableArray *result = [NSMutableArray array]; - NSInteger packetIndex = 0; - NSInteger packetType = 0; - NSInteger packetLength = 0; + NSInteger packetCount = 0; while (1) { - - packetType = inputPacket->data[packetIndex]; - packetLength = MIKMIDIStandardLengthOfMessageForCommandType(packetType); - NSInteger dataOffset = packetIndex; - if (dataOffset > (inputPacket->length - packetLength)) break; + + NSInteger dataOffset = packetCount * standardLength; + if (dataOffset > (inputPacket->length - standardLength)) break; const Byte *packetData = inputPacket->data + dataOffset; - + if (packetData[0] != firstCommandType && ((packetData[0] | 0x0F) != (firstCommandType | 0x0F))) { + // Doesn't look like multiple messages because they're not all the same type + MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:inputPacket]; + return command ? @[command] : @[]; + } + // This is gross, but it's the only way I can find to reliably create a // single-message MIDIPacket. MIDIPacketList packetList; @@ -71,13 +81,13 @@ + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket sizeof(MIDIPacketList), midiPacket, inputPacket->timeStamp, - packetLength, + standardLength, packetData); MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:midiPacket]; if (command) [result addObject:command]; - packetIndex += packetLength; + packetCount++; } - + return result; } From 6a03c1991b61e5f7e2733db93180ac6eca18be6a Mon Sep 17 00:00:00 2001 From: Martin 'Arkardiusz' Mlostek Date: Sat, 17 Nov 2018 19:38:43 +0100 Subject: [PATCH 9/9] added functionality to record CC commands --- Source/MIKMIDISequencer.m | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 55e88a2b..70f5c2a6 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -25,6 +25,8 @@ #import "MIKMIDISequence+MIKMIDIPrivate.h" #import "MIKMIDICommandScheduler.h" #import "MIKMIDIDestinationEndpoint.h" +#import "MIKMIDIControlChangeCommand.h" +#import "MIKMIDIControlChangeEvent.h" #if !__has_feature(objc_arc) @@ -566,6 +568,15 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command } else if ([command isKindOfClass:[MIKMIDINoteOffCommand class]]) { // note Off MIKMIDINoteOffCommand *noteOffCommand = (MIKMIDINoteOffCommand *)command; event = [self pendingNoteEventWithNoteNumber:@(noteOffCommand.note) channel:noteOffCommand.channel releaseVelocity:noteOffCommand.velocity offTimeStamp:musicTimeStamp]; + } else if ([command isKindOfClass:[MIKMIDIControlChangeCommand class]]) // cc command + { + MIKMIDIControlChangeCommand* ccCmd = (MIKMIDIControlChangeCommand*)command; + MIKMutableMIDIControlChangeEvent* ccEvent = [[MIKMutableMIDIControlChangeEvent alloc] init]; + ccEvent.controllerNumber = ccCmd.controllerNumber; + ccEvent.controllerValue = ccCmd.controllerValue; + ccEvent.channel = ccCmd.channel; + ccEvent.timeStamp = musicTimeStamp; + event = ccEvent; } if (event) [self.recordEnabledTracks makeObjectsPerformSelector:@selector(addEvent:) withObject:event];