Develop and Download Open Source Software

Browse Subversion Repository

Contents of /trunk/Classes/MyDocument.m

Parent Directory Parent Directory | Revision Log Revision Log


Revision 203 - (show annotations) (download)
Sat Jul 23 11:26:39 2022 UTC (20 months, 2 weeks ago) by toshinagata1964
File size: 127986 byte(s)
Selection is restored after undoing insert/delete
1 //
2 // MyDocument.m
3 //
4 // Created by Toshi Nagata.
5 /*
6 Copyright (c) 2000-2017 Toshi Nagata. All rights reserved.
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation version 2 of the License.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16 */
17
18 #import "MyDocument.h"
19 //#import "TrackWindowController.h"
20 #import "NSWindowControllerAdditions.h"
21 #import "MyMIDISequence.h"
22 #import "LoadingPanelController.h"
23 #import "RemapDevicePanelController.h"
24 //#import "PlayingPanelController.h"
25 #import "MDObjects.h"
26 #import "GraphicWindowController.h"
27 #import "PlayingViewController.h"
28 #import "MyAppController.h"
29 #import "QuantizePanelController.h"
30 #import "SaveErrorPanelController.h"
31
32 #include "MDRubyExtern.h"
33
34 NSString *MyDocumentTrackModifiedNotification = @"My Track Modified Notification";
35 NSString *MyDocumentTrackInsertedNotification = @"My Track Inserted Notification";
36 NSString *MyDocumentTrackDeletedNotification = @"My Track Deleted Notification";
37 NSString *MyDocumentPlayPositionNotification = @"My Track Play Position Notification";
38 //NSString *MyDocumentStopPlayingNotification = @"My Track Stop Playing Notification";
39 NSString *MyDocumentSelectionDidChangeNotification = @"My Selection Did Change Notification";
40 NSString *MyDocumentEditingRangeDidChangeNotification = @"My Editing Range Did Change Notification";
41
42 // Pasteboard types
43 NSString *MySequencePBoardType = @"Alchemusica MIDI sequence";
44 NSString *MySeqCatalogPBoardType = @"Alchemusica MIDI sequence info";
45
46 /* The following notification is used only within MyDocument */
47 static NSString *sSelectionWillChangeNotification = @"My Selection Will Change Notification";
48 static NSString *sPostTrackModifiedNotification = @"TrackModifiedNotification needs posted later";
49
50 /* Do runtime sanity check after every edit operations (slow) */
51 #if defined(DEBUG)
52 #define DEFAULT_SANITY_CHECK 1
53 #else
54 #define DEFAULT_SANITY_CHECK 0
55 #endif
56 int gMyDocumentSanityCheck = DEFAULT_SANITY_CHECK;
57
58 @implementation MyDocument
59
60 #pragma mark ====== Keeping the document/track correspondence ======
61
62 static MyDocumentTrackInfo **sTrackInfos;
63 static int sNumTrackInfo, sMaxTrackInfo;
64
65 + (void)registerDocumentTrackInfo: (MyDocumentTrackInfo *)info
66 {
67 if (sTrackInfos == NULL) {
68 sMaxTrackInfo = 8;
69 sNumTrackInfo = 0;
70 sTrackInfos = (MyDocumentTrackInfo **)malloc(sizeof(MyDocumentTrackInfo *) * sMaxTrackInfo);
71 } else if (sNumTrackInfo >= sMaxTrackInfo) {
72 sMaxTrackInfo += 8;
73 sTrackInfos = (MyDocumentTrackInfo **)realloc(sTrackInfos, sizeof(MyDocumentTrackInfo *) * sMaxTrackInfo);
74 }
75 /* TODO: check sTrackInfo != NULL */
76 sTrackInfos[sNumTrackInfo++] = info;
77 }
78
79 + (void)unregisterDocumentTrackInfo: (MyDocumentTrackInfo *)info
80 {
81 int i;
82 for (i = 0; i < sNumTrackInfo; i++) {
83 if (sTrackInfos[i] == info) {
84 /* Remove this entry */
85 if (i < sNumTrackInfo - 1)
86 memmove(&sTrackInfos[i], &sTrackInfos[i + 1], sizeof(MyDocumentTrackInfo *) * (sNumTrackInfo - i - 1));
87 sNumTrackInfo--;
88 return;
89 }
90 }
91 }
92
93 #pragma mark ====== init/dealloc ======
94
95 - (id)init {
96 self = [super init];
97 if (self != nil) {
98 int i;
99 myMIDISequence = [[MyMIDISequence allocWithZone:[self zone]] initWithDocument:self];
100 mainWindowController = nil;
101 selections = [[NSMutableArray allocWithZone: [self zone]] init];
102 /* Set empty selection for all tracks */
103 for (i = 0; i < [myMIDISequence trackCount]; i++) {
104 [selections addObject: [[[MDSelectionObject allocWithZone: [self zone]] init] autorelease]];
105 }
106 [[NSNotificationCenter defaultCenter]
107 addObserver: self
108 selector: @selector(selectionWillChange:)
109 name: sSelectionWillChangeNotification
110 object: self];
111 [[NSNotificationCenter defaultCenter]
112 addObserver: self
113 selector: @selector(postTrackModifiedNotification:)
114 name: sPostTrackModifiedNotification
115 object: self];
116 [[NSNotificationCenter defaultCenter]
117 addObserver:self
118 selector:@selector(trackModified:)
119 name:MyDocumentTrackModifiedNotification
120 object:self];
121
122 // Create a Ruby object for this document
123 MRSequenceRegister(self);
124
125 startEditingRange = endEditingRange = kMDNegativeTick;
126 }
127 return self;
128 }
129
130 - (void)dealloc {
131 // NSLog(@"document deallocated: %@", self);
132 int i;
133 for (i = 0; i < sNumTrackInfo; i++) {
134 if (sTrackInfos[i]->doc == self)
135 sTrackInfos[i]->doc = NULL; // No need to release (actually, this cannot happen)
136 }
137 MRSequenceUnregister(self);
138 [[NSNotificationCenter defaultCenter]
139 removeObserver: self];
140 [[self myMIDISequence] release];
141 [selections release];
142 [super dealloc];
143 }
144
145 #pragma mark ====== File I/O ======
146
147 static int
148 docTypeToDocCode(NSString *docType)
149 {
150 if ([docType isEqualToString: @"Alchemusica Project File"])
151 return 1;
152 else if ([docType isEqualToString: @"Standard MIDI File"])
153 return 2;
154 else return 0;
155 }
156
157 - (BOOL)encodeDocumentAttributesToFile: (NSString *)fileName
158 {
159 NSString *arcName = [NSString stringWithFormat: @"%@/attributes", fileName];
160 NSMutableArray *array;
161 NSMutableDictionary *dict;
162 NSEnumerator *en;
163 NSWindowController *cont;
164 int32_t i, n;
165 dict = [NSMutableDictionary dictionary];
166
167 // Track attributes
168 n = [[self myMIDISequence] trackCount];
169 array = [NSMutableArray arrayWithCapacity: n];
170 for (i = 0; i < n; i++) {
171 MDTrack *track = [[self myMIDISequence] getTrackAtIndex: i];
172 [array addObject: [NSNumber numberWithInt: (int)MDTrackGetAttribute(track)]];
173 }
174 [dict setObject: array forKey: @"track attributes"];
175
176 // Windows
177 en = [[self windowControllers] objectEnumerator];
178 array = [NSMutableArray array];
179 while ((cont = (NSWindowController *)[en nextObject]) != nil) {
180 [array addObject: [cont encodeWindow]];
181 }
182 [dict setObject: array forKey: @"windows"];
183
184 return [NSKeyedArchiver archiveRootObject: dict toFile: arcName];
185 }
186
187 - (BOOL)decodeDocumentAttributesFromFile: (NSString *)fileName
188 {
189 // NSString *arcName = [NSString stringWithFormat: @"%@/attributes", fileName];
190 // NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithFile: arcName];
191 // NSLog(@"archive = %@", dict);
192 return YES;
193 }
194
195 static int
196 callback(float progress, void *data)
197 {
198 LoadingPanelController *controller = (LoadingPanelController *)data;
199 [controller setProgressAmount: (double)progress];
200 if (![controller runSession] || [controller canceled])
201 return 0;
202 else return 1;
203 }
204
205 /* ������������������������������������������ Remap device ��������������������������������������������������� */
206 - (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)docType
207 {
208 int i, n;
209 MDStatus result;
210 LoadingPanelController *controller;
211 RemapDevicePanelController *remapController;
212 NSString *smfName;
213 NSString *title = NSLocalizedString(@"Alchemusica: Loading...", @"");
214 NSString *caption = [NSString stringWithFormat: NSLocalizedString(@"Loading %@...", @""), [fileName lastPathComponent]];
215 int docCode = docTypeToDocCode(docType);
216
217 // Create progress panel
218 controller = [[LoadingPanelController allocWithZone: [self zone]] initWithTitle: title andCaption: caption];
219
220 // Begin a modal session
221 [controller beginSession];
222
223 // Read SMF, periodically invoking callback
224 if (docCode == 1)
225 smfName = [NSString stringWithFormat: @"%@/Sequence.mid", fileName];
226 else smfName = fileName;
227 result = [myMIDISequence readSMFFromFile: smfName withCallback: callback andData: controller];
228
229 // End modal session (without closing the window)
230 [controller endSession];
231
232 if (result == kMDNoError) {
233
234 NSMutableArray *array = [NSMutableArray array];
235
236 // Set destination numbers for each track
237 n = [myMIDISequence trackCount];
238 for (i = 0; i < n; i++) {
239 char name[256];
240 MDTrack *track = [myMIDISequence getTrackAtIndex: i];
241 MDTrackGetDeviceName(track, name, sizeof name);
242 if (name[0] != 0) {
243 int32_t dev = MDPlayerGetDestinationNumberFromName(name);
244 if (dev >= 0)
245 MDTrackSetDevice(track, dev);
246 else
247 [array addObject: [NSNumber numberWithInt: i]];
248 }
249 }
250 if ([array count] > 0) {
251 // Some tracks needs remapping
252 // Restart a modal session with the same window
253 [controller beginSession];
254
255 // Create a sheet to remap the device
256 remapController = [[[RemapDevicePanelController allocWithZone: [self zone]]
257 initWithDocument: self trackSelection: array] autorelease];
258
259 // Display and handle the "remap device" sheet. stopModalWithCode: is invoked when done.
260 [remapController beginSheetForWindow: [controller window] invokeStopModalWhenDone: YES];
261
262 // Wait until stopModalWithCode: is invoked
263 while ([controller runSession])
264 ;
265
266 // End modal session
267 [controller endSession];
268 }
269 }
270
271 // Close progress panel
272 [controller close];
273
274 // Initialize selections
275 if (result == kMDNoError) {
276 [selections removeAllObjects];
277 for (i = [myMIDISequence trackCount] - 1; i >= 0; i--) {
278 id obj = [[MDSelectionObject allocWithZone: [self zone]] init];
279 if (obj == nil) {
280 result = kMDErrorOutOfMemory;
281 break;
282 }
283 [selections addObject: obj];
284 [obj release];
285 }
286 }
287
288 // Initialize colors
289 if (result == kMDNoError) {
290 n = [myMIDISequence trackCount];
291 for (i = 0; i < n; i++) {
292 NSColor *color = [self colorForTrack:i enabled:YES];
293 [self changeColor:color forTrack:i enableUndo:NO];
294 }
295 }
296
297 return (result == kMDNoError);
298 }
299
300 - (BOOL)writeToURL:(NSURL *)url ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation originalContentsURL:(NSURL *)absoluteOriginalContentsURL error:(NSError * _Nullable *)outError
301 {
302 NSString *fileName = [url path];
303 NSString *originalFile = [absoluteOriginalContentsURL path];
304 MDStatus result;
305 LoadingPanelController *controller;
306 NSString *title = NSLocalizedString(@"Alchemusica: Saving...", @"");
307 NSString *caption = [NSString stringWithFormat: NSLocalizedString(@"Saving %@...", @""), [fileName lastPathComponent]];
308 NSString *smfName;
309 int docCode = docTypeToDocCode(typeName);
310 char *errorMessage;
311
312 if (docCode == 0)
313 return NO;
314
315 // Create a new directory if necessary
316 if (docCode == 1) {
317 if (![[NSFileManager defaultManager] createDirectoryAtPath: fileName withIntermediateDirectories: YES attributes: nil error:NULL])
318 return NO;
319 /* TODO: Make it package <-- not necessary! (Info.plist description is enough) */
320 }
321
322 // Create progress panel
323 controller = [[LoadingPanelController allocWithZone: [self zone]] initWithTitle: title andCaption: caption];
324
325 // Begin a modal session
326 [controller beginSession];
327
328 // Write SMF, periodically invoking callback
329 if (docCode == 1)
330 smfName = [NSString stringWithFormat: @"%@/Sequence.mid", fileName];
331 else smfName = fileName;
332 result = [myMIDISequence writeSMFToFile: smfName withCallback: callback andData: controller errorMessage:&errorMessage];
333 if (errorMessage != NULL) {
334 // Show warning message
335 NSString *mes = [NSString stringWithUTF8String:errorMessage];
336 if (![SaveErrorPanelController showSaveErrorPanelWithMessage:mes]) {
337 result = kMDErrorUserInterrupt;
338 }
339 }
340 // Archive window informations
341 if (result == kMDNoError && docCode == 1) {
342 result = ([self encodeDocumentAttributesToFile: fileName] ? kMDNoError : kMDErrorCannotWriteToStream);
343 }
344
345 // End modal session and close the panel
346 [[controller endSession] close];
347
348 return (result == kMDNoError);
349 }
350
351
352 #if 0
353 - (BOOL)writeToFile:(NSString *)fileName ofType:(NSString *)docType
354 {
355 MDStatus result;
356 LoadingPanelController *controller;
357 NSString *title = NSLocalizedString(@"Alchemusica: Saving...", @"");
358 NSString *caption = [NSString stringWithFormat: NSLocalizedString(@"Saving %@...", @""), [fileName lastPathComponent]];
359 NSString *smfName;
360 int docCode = docTypeToDocCode(docType);
361
362 if (docCode == 0)
363 return NO;
364
365 // Create a new directory if necessary
366 if (docCode == 1) {
367 if (![[NSFileManager defaultManager] createDirectoryAtPath: fileName withIntermediateDirectories: YES attributes: nil error:NULL])
368 return NO;
369 /* TODO: Make it package <-- not necessary! (Info.plist description is enough) */
370 }
371
372 // Create progress panel
373 controller = [[LoadingPanelController allocWithZone: [self zone]] initWithTitle: title andCaption: caption];
374
375 // Begin a modal session
376 [controller beginSession];
377
378 // Write SMF, periodically invoking callback
379 if (docCode == 1)
380 smfName = [NSString stringWithFormat: @"%@/Sequence.mid", fileName];
381 else smfName = fileName;
382 result = [myMIDISequence writeSMFToFile: smfName withCallback: callback andData: controller];
383
384 // Archive window informations
385 if (result == kMDNoError && docCode == 1) {
386 result = ([self encodeDocumentAttributesToFile: fileName] ? kMDNoError : kMDErrorCannotWriteToStream);
387 }
388
389 // End modal session and close the panel
390 [[controller endSession] close];
391
392 return (result == kMDNoError);
393 }
394 #endif
395
396 #pragma mark ====== Handling windows ======
397
398 - (void)makeWindowControllers
399 {
400 int docCode = docTypeToDocCode([self fileType]);
401 mainWindowController = [[[GraphicWindowController allocWithZone:[self zone]] init] autorelease];
402 [self addWindowController: mainWindowController];
403 if (docCode == 1)
404 [self decodeDocumentAttributesFromFile: [[self fileURL] path]];
405 [[mainWindowController window] makeKeyAndOrderFront: self];
406 [[mainWindowController window] makeFirstResponder: [[mainWindowController window] initialFirstResponder]];
407 }
408
409 - (GraphicWindowController *)mainWindowController
410 {
411 return mainWindowController;
412 }
413 - (void)createWindowForTracks: (NSArray *)tracks ofType: (NSString *)windowType
414 {
415 NSEnumerator *en;
416 id obj;
417 int track;
418 Class class;
419 NSWindowController *cont;
420
421 class = [NSWindowController classForWindowType: windowType];
422 if (class == nil)
423 return;
424 if (![class canContainMultipleTracks]) {
425 // Open window for each track
426 en = [tracks objectEnumerator];
427 while ((obj = [en nextObject]) != nil) {
428 NSEnumerator *enWin;
429 if (![obj isKindOfClass: [NSNumber class]])
430 continue;
431 track = [obj intValue];
432 // Examine whether the requested track is already open
433 enWin = [[self windowControllers] objectEnumerator];
434 while ((cont = (NSWindowController *)[enWin nextObject]) != nil) {
435 if ([cont isKindOfClass: class] && [cont containsTrack: track])
436 break;
437 }
438 if (cont == nil) {
439 // Create a new window controller
440 cont = [[[class allocWithZone:[self zone]] init] autorelease];
441 if (cont != nil) {
442 [self addWindowController: cont];
443 [cont window]; /* Load window */
444 [cont addTrack: track];
445 }
446 }
447 if (cont != nil) {
448 [[cont window] makeKeyAndOrderFront: self];
449 [cont reloadSelection];
450 [[cont window] makeFirstResponder: [[cont window] initialFirstResponder]];
451 }
452 }
453 } else {
454 // Open one window with all tracks
455 cont = [[[class allocWithZone: [self zone]] init] autorelease];
456 if (cont != nil) {
457 int lastTrack;
458 [self addWindowController: cont];
459 en = [tracks objectEnumerator];
460 while ((obj = [en nextObject]) != nil) {
461 lastTrack = [obj intValue];
462 [cont addTrack: lastTrack];
463 [cont setFocusFlag: YES onTrack: lastTrack extending: YES];
464 }
465 [[cont window] makeKeyAndOrderFront: self];
466 [cont reloadSelection];
467 [[cont window] makeFirstResponder: [[cont window] initialFirstResponder]];
468 }
469 }
470 }
471
472 #pragma mark ====== Handling MyMIDISequence ======
473
474 - (MyMIDISequence *)myMIDISequence {
475 return myMIDISequence;
476 }
477
478 - (NSString *)tuneName {
479 return [self displayName];
480 // NSString *name = [self fileName];
481 // if (name != nil)
482 // return [name lastPathComponent];
483 // else {
484 // name = [[mainWindowController window] title];
485 // if (name != nil)
486 // return name;
487 // else return @"";
488 // }
489 }
490
491 - (float)timebase
492 {
493 MDSequence *sequence = [[self myMIDISequence] mySequence];
494 if (sequence == NULL)
495 return 480.0f;
496 else return (float)MDSequenceGetTimebase(sequence);
497 }
498
499 - (void)setTimebase:(float)timebase
500 {
501 MDSequence *sequence = [[self myMIDISequence] mySequence];
502 if (sequence == NULL)
503 return;
504
505 // Register undo action
506 [[[self undoManager] prepareWithInvocationTarget: self]
507 setTimebase: (float)MDSequenceGetTimebase(sequence)];
508 MDSequenceSetTimebase(sequence, (int32_t)timebase);
509 }
510
511 - (void)lockMIDISequence
512 {
513 MDSequence *sequence = [[self myMIDISequence] mySequence];
514 MDSequenceLock(sequence);
515 }
516
517 - (void)unlockMIDISequence
518 {
519 MDSequence *sequence = [[self myMIDISequence] mySequence];
520 MDSequenceUnlock(sequence);
521 }
522
523 #pragma mark ====== Color management ======
524
525 - (NSColor *)defaultColorForTrack: (int)track enabled: (BOOL)flag
526 {
527 NSColor *color;
528 color = [NSColor colorWithDeviceHue: (float)((track * 5 + 7) % 17) / 17.0f saturation: 1.0f brightness: 1.0f - 0.2f * (track % 34 >= 17) alpha: (flag ? 1.0 : 0.5)];
529 return color;
530 }
531
532 - (NSColor *)colorForTrack: (int)track enabled: (BOOL)flag
533 {
534 const char *value;
535 unsigned int r, g, b;
536 MDTrack *tp = [[self myMIDISequence] getTrackAtIndex:track];
537 if (MDTrackGetExtraInfo(tp, "color", &value) >= 0 && sscanf(value, "%2x%2x%2x", &r, &g, &b) == 3) {
538 return [NSColor colorWithDeviceRed: r/255.0f green: g/255.0f blue: b/255.0f alpha: (flag ? 1.0 : 0.5)];
539 } else {
540 return [self defaultColorForTrack: track enabled: flag];
541 }
542 }
543
544 - (void)changeColor: (NSColor *)color forTrack: (int)track enableUndo: (BOOL)enableUndo
545 {
546 NSColor *oldColor = nil;
547 MDTrack *tp = [[self myMIDISequence] getTrackAtIndex:track];
548 if (MDTrackGetExtraInfo(tp, "color", NULL) >= 0) {
549 oldColor = [self colorForTrack: track enabled: YES];
550 }
551 if (enableUndo) {
552 [[[self undoManager] prepareWithInvocationTarget: self]
553 changeColor:oldColor forTrack:track enableUndo:YES];
554 }
555 if (color == nil) {
556 /* Unregister the color */
557 MDTrackSetExtraInfo(tp, "color", NULL);
558 } else {
559 unsigned int r, g, b;
560 CGFloat fr, fg, fb, fa;
561 char *newValue;
562 [color getRed:&fr green:&fg blue:&fb alpha:&fa];
563 r = floor(fr * 255.0);
564 g = floor(fg * 255.0);
565 b = floor(fb * 255.0);
566 asprintf(&newValue, "%02x%02x%02x", r, g, b);
567 MDTrackSetExtraInfo(tp, "color", newValue);
568 free(newValue);
569 }
570 [self enqueueTrackAttributeChangedNotification: track];
571 }
572
573 + (NSColor *)colorForEditingRange
574 {
575 static NSColor *sColorEditingRange;
576 if (sColorEditingRange == nil) {
577 sColorEditingRange = [[NSColor colorWithDeviceRed: 1.0f green: 0.9f blue: 1.0f alpha: 1.0f] retain];
578 }
579 return sColorEditingRange;
580 }
581
582 + (NSColor *)colorForSelectingRange
583 {
584 static NSColor *sColorSelectingRange;
585 if (sColorSelectingRange == nil) {
586 sColorSelectingRange = [[NSColor colorWithDeviceRed: 0.5f green: 0.5f blue: 1.0f alpha: 1.0f] retain];
587 }
588 return sColorSelectingRange;
589 }
590
591 #pragma mark ====== Selection undo/redo ======
592
593 static NSString *sEditingRangeKey = @"editing_range";
594 static NSString *sStackShouldBeCleared = @"stack_should_be_cleared";
595
596 - (void)getSelectionStartTick: (MDTickType *)startTickPtr endTick: (MDTickType *)endTickPtr editableTracksOnly: (BOOL)flag
597 {
598 int i;
599 int ntracks = (int)[selections count];
600 MDTickType startTick, endTick;
601 MyMIDISequence *seq = [self myMIDISequence];
602
603 startTick = kMDMaxTick;
604 endTick = kMDNegativeTick;
605 for (i = 0; i < ntracks; i++) {
606 MDTickType startTick1, endTick1;
607 MDSelectionObject *selection;
608 if (flag && ([self trackAttributeForTrack: i] & kMDTrackAttributeEditable) == 0)
609 continue;
610 selection = (MDSelectionObject *)[selections objectAtIndex: i];
611 if ([selection getStartTick: &startTick1 andEndTick: &endTick1 withMDTrack: [seq getTrackAtIndex: i]] && startTick1 >= 0 && endTick1 >= 0) {
612 if (startTick1 < startTick)
613 startTick = startTick1;
614 if (endTick1 > endTick)
615 endTick = endTick1;
616 }
617 }
618 if (startTick < kMDMaxTick) {
619 *startTickPtr = startTick;
620 *endTickPtr = endTick;
621 } else {
622 *startTickPtr = *endTickPtr = kMDNegativeTick;
623 }
624 }
625
626 /* Recalculate the editing range from the current selections:
627 This is called from the notification handler for sSelectionWillChangeNotification */
628 - (void)updateEditingRange
629 {
630 // Calculate the editing range from the selections
631 [self getSelectionStartTick: &startEditingRange endTick: &endEditingRange editableTracksOnly: NO];
632 }
633
634 /* Enqueue undo information for selection change */
635 /* At idle time, a "coalesced" notification is sent to self and selectionWillChange: is called */
636 - (void)enqueueSelectionUndoerWithKey: (id)key value: (id)value
637 {
638 if (selectionQueue == nil)
639 selectionQueue = [[NSMutableDictionary dictionary] retain];
640 if ([selectionQueue objectForKey: key] == nil)
641 [selectionQueue setObject: value forKey: key];
642 [[NSNotificationQueue defaultQueue]
643 enqueueNotification:
644 [NSNotification notificationWithName: sSelectionWillChangeNotification object: self]
645 postingStyle: NSPostWhenIdle
646 coalesceMask: (NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender)
647 forModes: nil];
648 }
649
650 /* Notification handler for (internal) sSelectionWillChangeNotification */
651 - (void)selectionWillChange: (NSNotification *)notification
652 {
653 id keys;
654 int i;
655 BOOL isEditingRangeModified = NO;
656
657 if (selectionQueue == nil)
658 return;
659
660 /* This will be later used in notification */
661 keys = [[[selectionQueue allKeys] retain] autorelease];
662
663 if ([selectionQueue objectForKey: sStackShouldBeCleared] != nil) {
664 /* If any track is modified then the selection undo stack is cleared.
665 No recalc of the editing range occurs, and selectionQueue is discarded. */
666 [selectionStack release];
667 selectionStack = nil;
668 selectionStackPointer = 0;
669 isEditingRangeModified = NO;
670 } else {
671 /* Otherwise, selectionQueue is inserted at selectionStack[selectionStackPointer].
672 If selectionStackPointer < [count selectionStack], then the elements at indices
673 no less than selectionStackPointer are discarded. */
674 if (selectionStack == nil)
675 selectionStack = [[NSMutableArray array] retain];
676 if (selectionStackPointer < [selectionStack count]) {
677 [selectionStack removeObjectsInRange: NSMakeRange(selectionStackPointer, [selectionStack count] - selectionStackPointer)];
678 }
679 [selectionStack addObject: selectionQueue];
680 isEditingRangeModified = [keys containsObject: sEditingRangeKey];
681 /* If selection is modified and editing range is not touched, then new editing range
682 is calculated from the new selection. */
683 for (i = (int)[keys count] - 1; i >= 0; i--) {
684 if ([[keys objectAtIndex: i] isKindOfClass: [NSNumber class]])
685 break;
686 }
687 if (i >= 0 && !isEditingRangeModified) {
688 [self updateEditingRange];
689 isEditingRangeModified = YES;
690 }
691 }
692
693 /* SelectionDidChange notification is sent with [selectionQueue allKeys] as the object. */
694 [[NSNotificationCenter defaultCenter]
695 postNotificationName: MyDocumentSelectionDidChangeNotification
696 object: self
697 userInfo: [NSDictionary
698 dictionaryWithObjectsAndKeys: keys, @"keys", nil]];
699
700 /* EditingRangeDidChange notification is sent as the object. */
701 [[NSNotificationCenter defaultCenter]
702 postNotificationName: MyDocumentEditingRangeDidChangeNotification
703 object: self userInfo: nil];
704
705 [selectionQueue release];
706 selectionQueue = nil;
707 }
708
709 /* Register undo for restoring current selection */
710 - (void)registerUndoForRestoringCurrentSelectionInTrack: (int32_t)trackNo
711 {
712 MDSelectionObject *oldSel = (MDSelectionObject *)[[[selections objectAtIndex: trackNo] retain] autorelease];
713 [[[self undoManager] prepareWithInvocationTarget: self]
714 setSelection: oldSel inTrack: trackNo sender: self];
715 }
716
717 #pragma mark ====== Posting notifications ======
718
719 - (void)postTrackModifiedNotification: (NSNotification *)notification
720 {
721 int i;
722
723 /* Post track modified notification for all modified tracks */
724 for (i = (int)[modifiedTracks count] - 1; i >= 0; i--) {
725 int32_t trackNo = [[modifiedTracks objectAtIndex: i] intValue];
726 [[NSNotificationCenter defaultCenter]
727 postNotificationName:MyDocumentTrackModifiedNotification
728 object:self
729 userInfo:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithLong: trackNo], @"track", nil]];
730 }
731 [modifiedTracks release];
732 modifiedTracks = nil;
733
734 if (notification == nil) {
735 // Dequeue "sPostTrackModifiedNotification" notifications
736 [[NSNotificationQueue defaultQueue]
737 dequeueNotificationsMatching:
738 [NSNotification notificationWithName: sPostTrackModifiedNotification object: self]
739 coalesceMask: (NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender)];
740 }
741 }
742
743 - (void)enqueueTrackModifiedNotification: (int32_t)trackNo withEventEdited: (BOOL)eventEdited
744 {
745 int i;
746
747 /* Calibrators should be reset */
748 if (eventEdited) {
749 MDSequenceResetCalibrators([myMIDISequence mySequence]);
750 }
751
752 /* Add a track to the modifiedTracks array (if not already present) */
753 for (i = (int)[modifiedTracks count] - 1; i >= 0; i--) {
754 if ([[modifiedTracks objectAtIndex: i] intValue] == trackNo)
755 break;
756 }
757 if (i < 0) {
758 if (modifiedTracks == nil)
759 modifiedTracks = [[NSMutableArray array] retain];
760 [modifiedTracks addObject: [NSNumber numberWithLong: trackNo]];
761 }
762
763 /* Post an internal notification that requests sending a "real" notification
764 at the end of the runloop */
765 [[NSNotificationQueue defaultQueue]
766 enqueueNotification:
767 [NSNotification notificationWithName: sPostTrackModifiedNotification object: self]
768 postingStyle: NSPostWhenIdle
769 coalesceMask: (NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender)
770 forModes: nil];
771
772 /* Update editing range */
773 if (eventEdited) {
774 /* Clear the cached tick range in the selection */
775 MDSelectionObject *selection = (MDSelectionObject *)[selections objectAtIndex: trackNo];
776 selection->startTick = kMDNegativeTick;
777 selection->endTick = kMDNegativeTick;
778 [self updateEditingRange];
779 }
780
781 /* Any track modification should clear the selection undo stack */
782 [self enqueueSelectionUndoerWithKey: sStackShouldBeCleared value: sStackShouldBeCleared];
783 }
784
785 - (void)enqueueTrackModifiedNotification: (int32_t)trackNo
786 {
787 [self enqueueTrackModifiedNotification:trackNo withEventEdited:YES];
788 }
789
790 - (void)enqueueTrackAttributeChangedNotification: (int32_t)trackNo
791 {
792 [self enqueueTrackModifiedNotification:trackNo withEventEdited:NO];
793 }
794
795 - (void)postPlayPositionNotification: (MDTickType)tick
796 {
797 float beat;
798 // if ([[self myMIDISequence] isPlaying])
799 // beat = [[self myMIDISequence] playingBeat];
800 // else beat = -1.0;
801 beat = tick / [self timebase];
802 [[NSNotificationCenter defaultCenter]
803 postNotificationName: MyDocumentPlayPositionNotification
804 object: self
805 userInfo: [NSDictionary
806 dictionaryWithObjectsAndKeys: [NSNumber numberWithFloat: beat], @"position", nil]];
807 }
808
809 - (void)trackModified: (NSNotification *)notification {
810 if (gMyDocumentSanityCheck) {
811 int32_t trackNo = [[[notification userInfo] objectForKey: @"track"] intValue];
812 MDTrack *track = [[self myMIDISequence] getTrackAtIndex:trackNo];
813 if (track != NULL && MDTrackRecache(track, 1) > 0) {
814 MyAppCallback_messageBox("Track data has some inconsistency", "Internal Error", 0, 0);
815 }
816 }
817 }
818
819 - (void)trackInserted: (NSNotification *)notification {
820 }
821
822 - (void)trackDeleted: (NSNotification *)notification {
823 }
824
825 - (void)documentSelectionDidChange: (NSNotification *)notification {
826 }
827
828 //- (void)postSelectionDidChangeNotification: (int32_t)trackNo selectionChange: (IntGroupObject *)set sender: (id)sender
829 //{
830 // [[NSNotificationCenter defaultCenter]
831 // postNotificationName:MyDocumentSelectionDidChangeNotification
832 // object:self
833 // userInfo:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithLong: trackNo], @"track", set, @"selectionChange", sender, @"sender", nil]];
834 //}
835
836 //- (void)postEditingRangeDidChangeNotification
837 //{
838 // [[NSNotificationCenter defaultCenter]
839 // postNotificationName:MyDocumentEditingRangeDidChangeNotification
840 // object: self
841 // userInfo: nil];
842 //}
843
844 /*
845 - (void)postStopPlayingNotification
846 {
847 [[NSNotificationCenter defaultCenter]
848 postNotificationName:MyDocumentStopPlayingNotification
849 object:self
850 userInfo: nil];
851 }
852 */
853
854 /*- (void)midiSetupDidChange: (NSNotification *)aNotification
855 {
856 }
857 */
858
859 #pragma mark ====== Editing track lists ======
860
861 - (BOOL)insertTrack: (MDTrackObject *)trackObj atIndex: (int32_t)trackNo
862 {
863 MDTrack *track;
864 MDSequence *sequence;
865 NSData *attr;
866 int32_t index;
867 if (trackObj == nil) {
868 trackObj = [[[MDTrackObject allocWithZone:[self zone]] init] autorelease];
869 }
870 track = trackObj->track;
871 sequence = [[self myMIDISequence] mySequence];
872 attr = [self getTrackAttributes];
873 index = MDSequenceInsertTrack(sequence, trackNo, track);
874 if (index >= 0) {
875 /* Update selections */
876 [selections insertObject: [[[MDSelectionObject allocWithZone: [self zone]] init] autorelease] atIndex: trackNo];
877 /* Register undo action (delete and restore track attributes) */
878 [[[self undoManager] prepareWithInvocationTarget: self]
879 setTrackAttributes: attr];
880 [[[self undoManager] prepareWithInvocationTarget: self]
881 deleteTrackAt: index];
882 /* Post pending notifications for track modification */
883 [self postTrackModifiedNotification: nil];
884 /* Post a notification that a track has been inserted */
885 [[NSNotificationCenter defaultCenter]
886 postNotificationName:MyDocumentTrackInsertedNotification
887 object:self
888 userInfo:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithLong: index], @"track", nil]];
889 /* Set track color */
890
891 /* Update registered document/track info */
892 {
893 int i;
894 for (i = 0; i < sNumTrackInfo; i++) {
895 if (sTrackInfos[i]->doc == self) {
896 if (sTrackInfos[i]->num >= index) {
897 sTrackInfos[i]->num++;
898 }
899 }
900 if (sTrackInfos[i]->track == track) {
901 if (sTrackInfos[i]->doc != self)
902 sTrackInfos[i]->doc = self;
903 sTrackInfos[i]->num = index;
904 }
905 }
906 }
907
908 return YES;
909 } else return NO;
910 }
911
912 - (BOOL)deleteTrackAt: (int32_t)trackNo
913 {
914 MDTrack *track;
915 MDSequence *sequence;
916 NSData *attr;
917 int32_t index;
918 sequence = [[self myMIDISequence] mySequence];
919 track = MDSequenceGetTrack(sequence, trackNo);
920 if (track != NULL) {
921 MDSelectionObject *psetObj = [self selectionOfTrack: trackNo];
922 MDTrackObject *trackObj;
923
924 /* Update registered document/track info */
925 {
926 int i, j;
927 j = -1;
928 for (i = 0; i < sNumTrackInfo; i++) {
929 if (sTrackInfos[i]->track == track) {
930 j = i; /* track is used elsewhere */
931 if (sTrackInfos[i]->doc == self)
932 sTrackInfos[i]->doc = NULL;
933 }
934 if (sTrackInfos[i]->doc == self) {
935 if (sTrackInfos[i]->num > trackNo)
936 sTrackInfos[i]->num--;
937 }
938 }
939 if (j >= 0) {
940 /* Duplicate track for undo */
941 MDTrack *track2 = MDTrackNewFromTrack(track);
942 if (track2 != NULL)
943 track = track2;
944 }
945 }
946
947 trackObj = [[[MDTrackObject allocWithZone: [self zone]] initWithMDTrack: track] autorelease];
948 attr = [self getTrackAttributes];
949 index = MDSequenceDeleteTrack(sequence, trackNo);
950 /* Register undo action (insert, restore track attributes and selection) */
951 [[[self undoManager] prepareWithInvocationTarget: self]
952 setSelection: psetObj inTrack: trackNo sender: self];
953 [[[self undoManager] prepareWithInvocationTarget: self]
954 setTrackAttributes: attr];
955 [[[self undoManager] prepareWithInvocationTarget: self]
956 insertTrack: trackObj atIndex: trackNo];
957 /* Update selections */
958 [selections removeObjectAtIndex: trackNo];
959 /* Post pending notifications for track modification */
960 [self postTrackModifiedNotification: nil];
961 /* Post a notification that a track has been deleted */
962 [[NSNotificationCenter defaultCenter]
963 postNotificationName:MyDocumentTrackDeletedNotification
964 object:self
965 userInfo:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithLong: index], @"track", nil]];
966
967 return YES;
968 } else return NO;
969 }
970
971 #pragma mark ====== Editing track informations ======
972
973 - (BOOL)updateTrackDestinations
974 {
975 MDPlayer *player = [[self myMIDISequence] myPlayer];
976 MDPlayerStatus status = MDPlayerGetStatus(player);
977 if (status == kMDPlayer_playing || status == kMDPlayer_suspended) {
978 if (MDPlayerRefreshTrackDestinations(player) != kMDNoError) {
979 /* Something wrong: stop playing and returns NO */
980 /* [[PlayingPanelController sharedPlayingPanelController] pressStopButton: self]; */
981 return NO;
982 }
983 return YES;
984 }
985 return NO;
986 }
987
988 // Get names of all destinations and the currently used device names.
989 // Duplicates are removed. Existing destinations (regardless they are used or not) are stored first.
990 - (NSArray *)getDestinationNames
991 {
992 int i, n;
993 char name[256];
994 MDTrack *track;
995 NSMutableArray *array = [NSMutableArray array];
996 id aname;
997 n = MDPlayerGetNumberOfDestinations();
998 [array addObject:@""]; // The first object is always an empty string
999 for (i = 0; i < n; i++) {
1000 MDPlayerGetDestinationName(i, name, sizeof name);
1001 aname = [NSString localizedStringWithFormat:@"%s", name];
1002 if (![array containsObject:aname])
1003 [array addObject:aname];
1004 }
1005 n = [myMIDISequence trackCount];
1006 for (i = 0; i < n; i++) {
1007 track = [myMIDISequence getTrackAtIndex: i];
1008 MDTrackGetDeviceName(track, name, sizeof name);
1009 aname = [NSString localizedStringWithFormat:@"%s", name];
1010 if (![array containsObject:aname])
1011 [array addObject:aname];
1012 }
1013 if (destinationNames != nil) {
1014 n = (int)[destinationNames count];
1015 for (i = 0; i < n; i++) {
1016 aname = [destinationNames objectAtIndex:i];
1017 if (![array containsObject:aname])
1018 [array addObject:aname];
1019 }
1020 }
1021 destinationNames = [array retain];
1022 return array;
1023 }
1024
1025 - (NSData *)getTrackAttributes
1026 {
1027 MDTrack *track;
1028 MDSequence *sequence;
1029 int32_t n, i;
1030 NSMutableData *data;
1031 MDTrackAttribute *ap;
1032 sequence = [[self myMIDISequence] mySequence];
1033 if (sequence == NULL)
1034 return nil;
1035 n = MDSequenceGetNumberOfTracks(sequence);
1036 data = [NSMutableData dataWithLength: sizeof(MDTrackAttribute) * n];
1037 ap = (MDTrackAttribute *)[data mutableBytes];
1038 for (i = 0; i < n; i++) {
1039 track = MDSequenceGetTrack(sequence, i);
1040 *ap++ = (track != NULL ? MDTrackGetAttribute(track) : 0);
1041 }
1042 return data;
1043 }
1044
1045 - (void)setTrackAttributes: (NSData *)data
1046 {
1047 MDTrack *track;
1048 MDSequence *sequence;
1049 int32_t n, i;
1050 const MDTrackAttribute *ap;
1051 sequence = [[self myMIDISequence] mySequence];
1052 if (sequence == NULL)
1053 return;
1054 n = MDSequenceGetNumberOfTracks(sequence);
1055 i = (int)([data length] / sizeof(MDTrackAttribute));
1056 if (i < n)
1057 n = i;
1058 ap = (const MDTrackAttribute *)[data bytes];
1059 for (i = 0; i < n; i++) {
1060 track = MDSequenceGetTrack(sequence, i);
1061 if (track != NULL)
1062 MDTrackSetAttribute(track, ap[i]);
1063 }
1064 }
1065
1066 - (MDTrackAttribute)trackAttributeForTrack: (int32_t)trackNo
1067 {
1068 MDSequence *seq;
1069 MDTrack *track;
1070 seq = [[self myMIDISequence] mySequence];
1071 if (seq != NULL && (track = MDSequenceGetTrack(seq, trackNo)) != NULL)
1072 return MDTrackGetAttribute(track);
1073 else return 0;
1074 }
1075
1076 - (void)setTrackAttribute: (MDTrackAttribute)attr forTrack: (int32_t)trackNo
1077 {
1078 MDSequence *seq;
1079 MDTrack *track;
1080 seq = [[self myMIDISequence] mySequence];
1081 if (seq != NULL && (track = MDSequenceGetTrack(seq, trackNo)) != NULL) {
1082 MDTrackAttribute oldAttr = MDTrackGetAttribute(track);
1083 if (oldAttr != attr) {
1084 MDTrackSetAttribute(track, attr);
1085 // Probably we do not want undo registration
1086 // [[[self undoManager] prepareWithInvocationTarget: self]
1087 // setTrackAttribute: oldAttr forTrack: trackNo];
1088 [self enqueueTrackAttributeChangedNotification: trackNo];
1089 }
1090 }
1091 }
1092
1093 - (BOOL)isTrackSelected: (int32_t)trackNo
1094 {
1095 return [mainWindowController isTrackSelected: trackNo];
1096 }
1097
1098 - (void)setIsTrackSelected: (int32_t)trackNo flag: (BOOL)flag
1099 {
1100 [mainWindowController setIsTrackSelected: trackNo flag: flag];
1101 }
1102
1103 - (BOOL)setRecordFlagOnTrack: (int32_t)trackNo flag: (int)flag
1104 {
1105 MDSequence *sequence = [[self myMIDISequence] mySequence];
1106 if (sequence != NULL) {
1107 if (MDSequenceSetRecordFlagOnTrack(sequence, trackNo, flag)) {
1108 return YES;
1109 }
1110 }
1111 return NO;
1112 }
1113
1114 - (BOOL)setMuteFlagOnTrack: (int32_t)trackNo flag: (int)flag
1115 {
1116 MDSequence *sequence = [[self myMIDISequence] mySequence];
1117 if (sequence != NULL) {
1118 // NSData *attr = [self getTrackAttributes];
1119 if (MDSequenceSetMuteFlagOnTrack(sequence, trackNo, flag)) {
1120 // [[[self undoManager] prepareWithInvocationTarget: self]
1121 // setTrackAttributes: attr];
1122 // [self enqueueTrackModifiedNotification: trackNo];
1123 // [self updateTrackDestinations];
1124 return YES;
1125 }
1126 }
1127 return NO;
1128 }
1129
1130 - (BOOL)setSoloFlagOnTrack: (int32_t)trackNo flag: (int)flag
1131 {
1132 MDSequence *sequence = [[self myMIDISequence] mySequence];
1133 if (sequence != NULL) {
1134 // NSData *attr = [self getTrackAttributes];
1135 if (MDSequenceSetSoloFlagOnTrack(sequence, trackNo, flag)) {
1136 // [[[self undoManager] prepareWithInvocationTarget: self]
1137 // setTrackAttributes: attr];
1138 // [self enqueueTrackModifiedNotification: trackNo];
1139 // [self updateTrackDestinations];
1140 return YES;
1141 }
1142 }
1143 return NO;
1144 }
1145
1146 - (void)registerUndoChangeTrackDuration: (int32_t)oldDuration ofTrack: (int32_t)trackNo
1147 {
1148 MDTrack *track;
1149 MDTickType duration;
1150 track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1151 duration = MDTrackGetDuration(track);
1152 if (oldDuration != duration)
1153 [[[self undoManager] prepareWithInvocationTarget: self]
1154 changeTrackDuration: oldDuration ofTrack: trackNo];
1155 }
1156
1157 - (void)updateDeviceNumberForTrack: (int32_t)trackNo
1158 {
1159 MDTrack *track = [myMIDISequence getTrackAtIndex: trackNo];
1160 if (track != NULL) {
1161 char name[256];
1162 MDTrackGetDeviceName(track, name, sizeof name);
1163 int32_t deviceNumber = MDPlayerGetDestinationNumberFromName(name);
1164 MDTrackSetDevice(track, deviceNumber);
1165 }
1166 }
1167
1168 - (BOOL)changeDevice: (NSString *)deviceName forTrack: (int32_t)trackNo
1169 {
1170 char name[256], oldname[256];
1171 MDTrack *track = [myMIDISequence getTrackAtIndex: trackNo];
1172 if (track != NULL && deviceName != nil) {
1173 MDTrackGetDeviceName(track, oldname, sizeof oldname);
1174 strncpy(name, [deviceName UTF8String], 255);
1175 name[255] = 0;
1176 if (strcmp(name, oldname) == 0)
1177 return NO; /* Do nothing */
1178 MDTrackSetDeviceName(track, name);
1179 [[[self undoManager] prepareWithInvocationTarget: self]
1180 changeDevice: [NSString stringWithUTF8String: oldname] forTrack: trackNo];
1181 [self updateDeviceNumberForTrack: trackNo];
1182 [self enqueueTrackAttributeChangedNotification: trackNo];
1183 return YES;
1184 } else return NO;
1185 }
1186
1187 /*
1188 - (BOOL)changeDevice: (NSString *)deviceName deviceNumber: (int32_t)deviceNumber forTrack: (int32_t)trackNo
1189 {
1190 char name[256], oldname[256];
1191 int32_t oldnumber;
1192 MDTrack *track = [myMIDISequence getTrackAtIndex: trackNo];
1193 if (track != NULL) {
1194 MDTrackGetDeviceName(track, oldname, sizeof oldname);
1195 oldnumber = MDTrackGetDevice(track);
1196 if (deviceName != nil) {
1197 strncpy(name, [deviceName cString], 255);
1198 name[255] = 0;
1199 } else if (deviceNumber >= 0) {
1200 if (MDPlayerGetDestinationName(deviceNumber, name, sizeof name) != kMDNoError)
1201 return NO;
1202 }
1203 if (deviceNumber == -2)
1204 deviceNumber = MDPlayerGetDestinationNumberFromName(name);
1205 if (oldnumber == deviceNumber)
1206 return NO; // No need to change
1207 if (deviceNumber >= 0)
1208 MDTrackSetDevice(track, deviceNumber);
1209 MDTrackSetDeviceName(track, name);
1210 [[[self undoManager] prepareWithInvocationTarget: self]
1211 changeDevice: [NSString stringWithCString: oldname] deviceNumber: oldnumber forTrack: trackNo];
1212 [self enqueueTrackModifiedNotification: trackNo];
1213 [self updateTrackDestinations];
1214 return YES;
1215 }
1216 return NO;
1217 }
1218 */
1219
1220 - (BOOL)changeTrackChannel: (int)channel forTrack: (int32_t)trackNo
1221 {
1222 int oldchannel;
1223 MDTrack *track;
1224 if (channel >= 0 && channel < 16 && (track = [myMIDISequence getTrackAtIndex: trackNo]) != NULL) {
1225 oldchannel = MDTrackGetTrackChannel(track);
1226 if (channel == oldchannel)
1227 return NO;
1228 MDTrackSetTrackChannel(track, channel);
1229 [[[self undoManager] prepareWithInvocationTarget: self]
1230 changeTrackChannel: oldchannel forTrack: trackNo];
1231 [self enqueueTrackAttributeChangedNotification: trackNo];
1232 [self updateTrackDestinations];
1233 return YES;
1234 }
1235 return NO;
1236 }
1237
1238 - (BOOL)changeTrackName: (NSString *)trackName forTrack: (int32_t)trackNo
1239 {
1240 char name[256], oldname[256];
1241 MDTrack *track;
1242 if (trackName != nil && (track = [myMIDISequence getTrackAtIndex: trackNo]) != NULL) {
1243 MDTrackGetName(track, oldname, sizeof oldname);
1244 strncpy(name, [trackName UTF8String], 255);
1245 name[255] = 0;
1246 if (strcmp(name, oldname) == 0)
1247 return NO;
1248 MDTrackSetName(track, name);
1249 [[[self undoManager] prepareWithInvocationTarget: self]
1250 changeTrackName: [NSString stringWithUTF8String: oldname] forTrack: trackNo];
1251 [self enqueueTrackAttributeChangedNotification: trackNo];
1252 return YES;
1253 }
1254 return NO;
1255 }
1256
1257 - (BOOL)changeTrackDuration: (int32_t)duration ofTrack: (int32_t)trackNo
1258 {
1259 MDTrack *track = MDSequenceGetTrack([[self myMIDISequence] mySequence], trackNo);
1260 if (track != NULL) {
1261 int32_t tduration = MDTrackGetLargestTick(track);
1262 int32_t oduration = MDTrackGetDuration(track);
1263 if (tduration >= duration)
1264 duration = tduration + 1;
1265 if (oduration != duration) {
1266 MDTrackSetDuration(track, duration);
1267 /* Register undo action with current value */
1268 [[[self undoManager] prepareWithInvocationTarget: self]
1269 changeTrackDuration: oduration ofTrack: (int32_t)trackNo];
1270 /* Post the notification that any track has been modified */
1271 [self enqueueTrackModifiedNotification: trackNo];
1272 return YES;
1273 }
1274 }
1275 return NO;
1276 }
1277
1278 #pragma mark ====== Editing events ======
1279
1280 - (BOOL)insertEvent: (MDEventObject *)eventObj toTrack: (int32_t)trackNo
1281 {
1282 MDTrack *track;
1283 MDPointer *ptr;
1284 MDStatus sts;
1285 int32_t position;
1286 track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1287 if (track != NULL) {
1288 ptr = MDPointerNew(track);
1289 if (ptr != NULL) {
1290 MDTickType oduration;
1291 [self lockMIDISequence];
1292 MDPointerSetPosition(ptr, eventObj->position);
1293 oduration = MDTrackGetDuration(track);
1294 sts = MDPointerInsertAnEvent(ptr, &eventObj->event);
1295 position = MDPointerGetPosition(ptr);
1296 [self unlockMIDISequence];
1297 MDPointerRelease(ptr);
1298 if (sts == kMDNoError) {
1299
1300 /* Update selection */
1301 IntGroup *temp1 = IntGroupNew();
1302 IntGroup *temp2 = IntGroupNew();
1303 if (temp1 == NULL || temp2 == NULL)
1304 return NO;
1305 sts = IntGroupAdd(temp1, position, 1);
1306 if (sts == kMDNoError)
1307 sts = IntGroupNegate(temp1, temp2);
1308 if (sts == kMDNoError) {
1309 IntGroupClear(temp1);
1310 sts = IntGroupConvolute([(MDSelectionObject *)[selections objectAtIndex: trackNo] pointSet], temp2, temp1);
1311 }
1312 if (sts == kMDNoError) {
1313 [self registerUndoForRestoringCurrentSelectionInTrack: trackNo];
1314 [self setSelection: [[[MDSelectionObject allocWithZone: [self zone]] initWithMDPointSet: temp1] autorelease] inTrack: trackNo sender: self];
1315 }
1316 IntGroupRelease(temp1);
1317 IntGroupRelease(temp2);
1318
1319 /* Register undo action for change of track duration (if necessary) */
1320 [self registerUndoChangeTrackDuration: oduration ofTrack: trackNo];
1321 /* Register undo action (delete) */
1322 [[[self undoManager] prepareWithInvocationTarget: self]
1323 deleteEventAt: position fromTrack: trackNo];
1324 /* Post the notification that any track has been modified */
1325 [self enqueueTrackModifiedNotification: trackNo];
1326 return YES;
1327 }
1328 }
1329 }
1330 return NO;
1331 }
1332
1333 - (BOOL)deleteEventAt: (int32_t)position fromTrack: (int32_t)trackNo
1334 {
1335 MDTrack *track;
1336 MDPointer *ptr;
1337 MDStatus sts;
1338 track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1339 if (track != NULL) {
1340 ptr = MDPointerNew(track);
1341 if (ptr != NULL) {
1342 MDEventObject *eventObj = [[[MDEventObject allocWithZone:[self zone]] init] autorelease];
1343 [self lockMIDISequence];
1344 MDPointerSetPosition(ptr, position);
1345 eventObj->position = position;
1346 sts = MDPointerDeleteAnEvent(ptr, &eventObj->event);
1347 [self unlockMIDISequence];
1348 MDPointerRelease(ptr);
1349 if (sts == kMDNoError) {
1350
1351 /* Update selection */
1352 IntGroup *temp1 = IntGroupNew();
1353 IntGroup *temp2 = IntGroupNew();
1354 if (temp1 == NULL || temp2 == NULL)
1355 return NO;
1356 sts = IntGroupAdd(temp1, position, 1);
1357 if (sts == kMDNoError)
1358 sts = IntGroupNegate(temp1, temp2);
1359 if (sts == kMDNoError) {
1360 IntGroupClear(temp1);
1361 sts = IntGroupDeconvolute([(MDSelectionObject *)[selections objectAtIndex: trackNo] pointSet], temp2, temp1);
1362 }
1363 if (sts == kMDNoError) {
1364 [self registerUndoForRestoringCurrentSelectionInTrack: trackNo];
1365 [self setSelection: [[[MDSelectionObject allocWithZone: [self zone]] initWithMDPointSet: temp1] autorelease] inTrack: trackNo sender: self];
1366 }
1367 IntGroupRelease(temp1);
1368 IntGroupRelease(temp2);
1369
1370 /* Register undo action */
1371 [[[self undoManager] prepareWithInvocationTarget: self]
1372 insertEvent: eventObj toTrack: trackNo];
1373 /* Post the notification that any track has been modified */
1374 [self enqueueTrackModifiedNotification: trackNo];
1375 return YES;
1376 }
1377 }
1378 }
1379 return NO;
1380 }
1381
1382 - (BOOL)replaceEvent: (MDEventObject *)eventObj inTrack: (int32_t)trackNo
1383 {
1384 MDTrack *track;
1385 MDPointer *ptr;
1386 MDStatus sts;
1387 track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1388 if (track != NULL) {
1389 ptr = MDPointerNew(track);
1390 if (ptr != NULL) {
1391 MDTickType oduration;
1392 MDEventObject *orgEventObj = [[[MDEventObject allocWithZone:[self zone]] init] autorelease];
1393 [self lockMIDISequence];
1394 MDPointerSetPosition(ptr, eventObj->position);
1395 oduration = MDTrackGetDuration(track);
1396 sts = MDPointerReplaceAnEvent(ptr, &eventObj->event, &orgEventObj->event);
1397 orgEventObj->position = MDPointerGetPosition(ptr);
1398 [self unlockMIDISequence];
1399 MDPointerRelease(ptr);
1400 if (sts == kMDNoError) {
1401
1402 if (eventObj->position != orgEventObj->position) {
1403 /* Update selection */
1404 IntGroup *temp1 = IntGroupNew();
1405 IntGroup *temp2 = IntGroupNew();
1406 IntGroup *temp3 = IntGroupNew();
1407 if (temp1 == NULL || temp2 == NULL || temp3 == NULL)
1408 return NO;
1409 sts = IntGroupAdd(temp1, eventObj->position, 1);
1410 if (sts == kMDNoError)
1411 sts = IntGroupNegate(temp1, temp2);
1412 if (sts == kMDNoError) {
1413 IntGroupClear(temp1);
1414 sts = IntGroupDeconvolute([(MDSelectionObject *)[selections objectAtIndex: trackNo] pointSet], temp2, temp1);
1415 }
1416 IntGroupClear(temp2);
1417 sts = IntGroupAdd(temp2, orgEventObj->position, 1);
1418 if (sts == kMDNoError)
1419 sts = IntGroupNegate(temp2, temp3);
1420 if (sts == kMDNoError) {
1421 IntGroupClear(temp2);
1422 sts = IntGroupConvolute(temp1, temp3, temp2);
1423 }
1424 if (sts == kMDNoError) {
1425 [self registerUndoForRestoringCurrentSelectionInTrack: trackNo];
1426 [self setSelection: [[[MDSelectionObject allocWithZone: [self zone]] initWithMDPointSet: temp2] autorelease] inTrack: trackNo sender: self];
1427 }
1428 IntGroupRelease(temp1);
1429 IntGroupRelease(temp2);
1430 IntGroupRelease(temp3);
1431 }
1432
1433 /* Register undo action for change of track duration (if necessary) */
1434 [self registerUndoChangeTrackDuration: oduration ofTrack: trackNo];
1435 /* Register undo action */
1436 [[[self undoManager] prepareWithInvocationTarget: self]
1437 replaceEvent: orgEventObj inTrack: trackNo];
1438 /* Post the notification that any track has been modified */
1439 [self enqueueTrackModifiedNotification: trackNo];
1440 return YES;
1441 }
1442 }
1443 }
1444 return NO;
1445 }
1446
1447 - (BOOL)insertMultipleEvents: (MDTrackObject *)trackObj at: (IntGroupObject *)pointSet toTrack: (int32_t)trackNo selectInsertedEvents: (BOOL)flag insertedPositions: (IntGroup **)outPtr
1448 {
1449 MDTrack *track;
1450 IntGroup *pset;
1451 MDStatus sts;
1452 MDTickType oduration;
1453 track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1454 if (track == NULL || trackObj == nil || trackObj->track == NULL)
1455 return NO;
1456 oduration = MDTrackGetDuration(track);
1457 if (pointSet != nil)
1458 pset = [pointSet pointSet];
1459 else pset = NULL;
1460 [self lockMIDISequence];
1461 sts = MDTrackMerge(track, trackObj->track, &pset);
1462 [self unlockMIDISequence];
1463 if (sts == kMDErrorNoEvents)
1464 return NO;
1465 if (sts == kMDNoError) {
1466 if (outPtr != NULL)
1467 *outPtr = pset;
1468 /* Update selection */
1469 [self registerUndoForRestoringCurrentSelectionInTrack: trackNo];
1470 if (flag) {
1471 [self setSelection: [[[MDSelectionObject allocWithZone: [self zone]] initWithMDPointSet: pset] autorelease] inTrack: trackNo sender: self];
1472 } else {
1473 IntGroup *temp = IntGroupNew();
1474 IntGroup *newSelection = IntGroupNew();
1475 if (temp == NULL || newSelection == NULL)
1476 return NO;
1477 sts = IntGroupNegate(pset, temp);
1478 if (sts == kMDNoError)
1479 sts = IntGroupConvolute([(MDSelectionObject *)[selections objectAtIndex: trackNo] pointSet], temp, newSelection);
1480 if (sts == kMDNoError)
1481 [self setSelection: [[[MDSelectionObject allocWithZone: [self zone]] initWithMDPointSet: newSelection] autorelease] inTrack: trackNo sender: self];
1482 }
1483
1484 /* Register undo action for change of track duration (if necessary) */
1485 pointSet = [[[IntGroupObject allocWithZone: [self zone]] initWithMDPointSet: pset] autorelease];
1486 [self registerUndoChangeTrackDuration: oduration ofTrack: trackNo];
1487 /* Register undo action */
1488 [[[self undoManager] prepareWithInvocationTarget: self]
1489 deleteMultipleEventsAt: pointSet fromTrack: trackNo deletedEvents: NULL];
1490 /* Post the notification that any track has been modified */
1491 [self enqueueTrackModifiedNotification: trackNo];
1492
1493 return YES;
1494 } else {
1495 if (outPtr != NULL)
1496 *outPtr = NULL;
1497 return NO;
1498 }
1499 }
1500
1501 - (BOOL)deleteMultipleEventsAt: (IntGroupObject *)pointSet fromTrack: (int32_t)trackNo deletedEvents: (MDTrack **)outPtr
1502 {
1503 MDTrack *track, *newTrack;
1504 MDTrackObject *trackObj;
1505 IntGroup *pset;
1506 MDStatus sts;
1507 MDTickType oduration;
1508 track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1509 pset = [pointSet pointSet];
1510 if (track == NULL || pointSet == nil || pset == NULL)
1511 return NO;
1512 oduration = MDTrackGetDuration(track);
1513 [self lockMIDISequence];
1514 sts = MDTrackUnmerge(track, &newTrack, pset);
1515 [self unlockMIDISequence];
1516 if (sts == kMDErrorNoEvents)
1517 return NO;
1518 if (sts == kMDNoError) {
1519 /* Update selection */
1520 IntGroup *newSelection = IntGroupNew();
1521 /* if (0) {
1522 IntGroup *temp = IntGroupNew();
1523 if (temp == NULL || newSelection == NULL)
1524 return NO;
1525 sts = IntGroupNegate(pset, temp);
1526 if (sts == kMDNoError)
1527 sts = IntGroupDeconvolute([(MDSelectionObject *)[selections objectAtIndex: trackNo] pointSet], temp, newSelection);
1528 } */
1529 if (sts == kMDNoError) {
1530 [self registerUndoForRestoringCurrentSelectionInTrack: trackNo];
1531 [self setSelection: [[[MDSelectionObject allocWithZone: [self zone]] initWithMDPointSet: newSelection] autorelease] inTrack: trackNo sender: self];
1532 }
1533 trackObj = [[[MDTrackObject allocWithZone: [self zone]] initWithMDTrack: newTrack] autorelease];
1534 /* Register undo action for change of track duration (if necessary) */
1535 [self registerUndoChangeTrackDuration: oduration ofTrack: trackNo];
1536 /* Register undo action */
1537 [[[self undoManager] prepareWithInvocationTarget: self]
1538 insertMultipleEvents: trackObj at: pointSet toTrack: trackNo selectInsertedEvents: NO insertedPositions: NULL];
1539 /* Post the notification that any track has been modified */
1540 [self enqueueTrackModifiedNotification: trackNo];
1541
1542 if (outPtr != NULL) {
1543 /* Duplicate newTrack */
1544 MDTrack *newTrack2 = MDTrackNewFromTrack(newTrack);
1545 *outPtr = newTrack2;
1546 }
1547
1548 MDTrackRelease(newTrack);
1549 return YES;
1550 }
1551 return NO;
1552 }
1553
1554 - (BOOL)duplicateMultipleEventsAt: (IntGroupObject *)pointSet ofTrack: (int32_t)trackNo selectInsertedEvents: (BOOL)flag
1555 {
1556 MDTrack *track, *newTrack;
1557 MDTrackObject *newTrackObj;
1558 MDPointer *pt;
1559 IntGroup *pset;
1560 MDEvent *ep;
1561 // MDStatus sts;
1562 track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1563 pset = [pointSet pointSet];
1564 if (track == NULL || pointSet == nil || pset == NULL)
1565 return NO;
1566 newTrack = MDTrackNew();
1567 if (newTrack == NULL)
1568 return NO;
1569 pt = MDPointerNew(track);
1570 if (pt == NULL)
1571 return NO;
1572 [self lockMIDISequence];
1573 while ((ep = MDPointerForwardWithPointSet(pt, pset, NULL)) != NULL) {
1574 if (MDTrackAppendEvents(newTrack, ep, 1) != 1)
1575 return NO;
1576 }
1577 [self unlockMIDISequence];
1578 MDPointerRelease(pt);
1579 newTrackObj = [[[MDTrackObject allocWithZone: [self zone]] initWithMDTrack: newTrack] autorelease];
1580 return [self insertMultipleEvents: newTrackObj at: nil toTrack: trackNo selectInsertedEvents: flag insertedPositions: NULL];
1581 }
1582
1583 static int
1584 sInternalComparatorByTick(void *t, const void *a, const void *b)
1585 {
1586 MDTickType ta = ((MDTickType *)t)[*((int32_t *)a)];
1587 MDTickType tb = ((MDTickType *)t)[*((int32_t *)b)];
1588 if (ta < tb)
1589 return -1;
1590 else if (ta == tb)
1591 return 0;
1592 else return 1;
1593 }
1594
1595 static int
1596 sInternalComparatorByPosition(void *t, const void *a, const void *b)
1597 {
1598 int32_t ta = ((int32_t *)t)[*((int32_t *)a)];
1599 int32_t tb = ((int32_t *)t)[*((int32_t *)b)];
1600 if (ta < tb)
1601 return -1;
1602 else if (ta == tb)
1603 return 0;
1604 else return 1;
1605 }
1606
1607 - (BOOL)modifyTick: (id)theData ofMultipleEventsAt: (IntGroupObject *)pointSet inTrack: (int32_t)trackNo mode: (MyDocumentModifyMode)mode destinationPositions: (id)destPositions setSelection: (BOOL)setSelection
1608 {
1609 MDTrack *track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1610 if (track == NULL)
1611 return NO;
1612 /* Call the class method version */
1613 return [MyDocument modifyTick: theData ofMultipleEventsAt: pointSet forMDTrack: track inDocument: self mode: mode destinationPositions: destPositions setSelection: setSelection];
1614 }
1615
1616 /* Implemented as a class method, because in some cases it is necessary to perform this operation for non-document tracks. */
1617 + (BOOL)modifyTick: (id)theData ofMultipleEventsAt: (IntGroupObject *)pointSet forMDTrack: (MDTrack *)track inDocument: (id)doc mode: (MyDocumentModifyMode)mode destinationPositions: (id)destPositions setSelection: (BOOL)setSelection
1618 {
1619 MDTrack *tempTrack;
1620 int32_t trackNo;
1621 MDEvent *ep;
1622 IntGroup *pset, *destPset;
1623 MDSelectionObject *newDestPointSet;
1624 int32_t index, length;
1625 MDTickType dataValue;
1626 const MDTickType *dataPtr;
1627 float floatDataValue;
1628 const float *floatDataPtr;
1629 MDStatus status;
1630 unsigned int dataMode;
1631 MyDocumentModifyMode undoMode;
1632 NSMutableData *tempData, *undoData, *undoPositions;
1633 MDTickType *tempDataPtr, *undoDataPtr;
1634 int32_t *undoPositionsPtr;
1635 const int32_t *destPositionsPtr;
1636 MDTickType oldDuration;
1637 MDPointer *tempTrackPtr;
1638
1639 if (doc != nil)
1640 trackNo = [[doc myMIDISequence] lookUpTrack: track];
1641 else trackNo = -1;
1642
1643 oldDuration = MDTrackGetDuration(track);
1644 pset = [pointSet pointSet];
1645 if (pset == NULL)
1646 return NO;
1647 length = IntGroupGetCount(pset);
1648 if (destPositions == nil)
1649 destPositionsPtr = NULL;
1650 else destPositionsPtr = (const int32_t *)[destPositions bytes];
1651
1652 /* Prepare tick data */
1653 dataPtr = NULL;
1654 if ([theData isKindOfClass: [NSNumber class]]) {
1655 dataMode = 0;
1656 if (mode == MyDocumentModifyMultiply) {
1657 floatDataValue = [theData floatValue];
1658 } else {
1659 dataValue = [theData intValue];
1660 }
1661 } else if ([theData isKindOfClass: [NSData class]]) {
1662 dataMode = 1;
1663 if (mode == MyDocumentModifyMultiply) {
1664 floatDataPtr = (const float *)[theData bytes];
1665 } else {
1666 dataPtr = (const MDTickType *)[theData bytes];
1667 }
1668 } else {
1669 dataMode = 2;
1670 }
1671
1672 /* Allocate temporary arrays */
1673 tempData = [NSMutableData dataWithLength: sizeof(MDTickType) * length];
1674 tempDataPtr = (MDTickType *)[tempData mutableBytes];
1675 undoData = [NSMutableData dataWithLength: sizeof(MDTickType) * length];
1676 undoDataPtr = (MDTickType *)[undoData mutableBytes];
1677 undoMode = (mode == MyDocumentModifyAdd ? mode : MyDocumentModifySet);
1678 undoPositions = [NSMutableData dataWithLength: sizeof(int32_t) * length];
1679 undoPositionsPtr = (int32_t *)[undoPositions mutableBytes];
1680
1681 /* Move the target events to a separate track */
1682 status = MDTrackUnmerge(track, &tempTrack, pset);
1683 if (status != kMDNoError)
1684 return NO;
1685 tempTrackPtr = MDPointerNew(tempTrack);
1686 if (tempTrackPtr == NULL)
1687 return NO;
1688
1689 /* Get new/old tick values to tempDataPtr[] and undoDataPtr[] */
1690 {
1691 MDTickType prevValue, newValue, oldValue;
1692 prevValue = 0;
1693 index = 0;
1694 while ((ep = MDPointerForward(tempTrackPtr)) != NULL) {
1695 oldValue = MDGetTick(ep);
1696 if (mode == MyDocumentModifySet || mode == MyDocumentModifyAdd) {
1697 if (dataMode == 0)
1698 newValue = dataValue;
1699 else if (dataMode == 1)
1700 newValue = dataPtr[index];
1701 else newValue = [[theData objectAtIndex: index] intValue];
1702 if (mode == MyDocumentModifyAdd)
1703 newValue += oldValue;
1704 } else if (mode == MyDocumentModifyMultiply) {
1705 if (dataMode == 0)
1706 newValue = oldValue * floatDataValue;
1707 else if (dataMode == 1)
1708 newValue = oldValue * floatDataPtr[index];
1709 else newValue = oldValue * [[theData objectAtIndex: index] floatValue];
1710 }
1711 if (newValue < prevValue) {
1712 undoMode = MyDocumentModifySet; // Undo can be no longer accomplished by simply adding negative of theData
1713 newValue = prevValue;
1714 }
1715 tempDataPtr[index] = newValue;
1716 undoDataPtr[index] = oldValue;
1717 index++;
1718 }
1719 }
1720
1721 /* Get old positions (for undo) to undoPositionsPtr[] */
1722 {
1723 int32_t i, pt, endPt;
1724 index = 0;
1725 for (i = 0; (pt = IntGroupGetStartPoint(pset, i)) >= 0; i++) {
1726 endPt = IntGroupGetEndPoint(pset, i);
1727 while (pt < endPt) {
1728 undoPositionsPtr[index++] = pt++;
1729 }
1730 }
1731 }
1732
1733 /* Sort events, tempDataPtr, undoDataPtr, undoPositionsPtr */
1734 {
1735 int32_t *new2old;
1736 void *tempBuffer;
1737
1738 /* Allocate temporary storage */
1739 new2old = (int32_t *)malloc(sizeof(int32_t) * length);
1740 if (new2old == NULL)
1741 return NO;
1742 tempBuffer = malloc(sizeof(MDEvent) * length);
1743 if (tempBuffer == NULL)
1744 return NO;
1745 memset(tempBuffer, 0, sizeof(MDEvent) * length);
1746
1747 /* Get sorted index */
1748 for (index = 0; index < length; index++)
1749 new2old[index] = index;
1750 if (destPositionsPtr != NULL)
1751 qsort_r(new2old, length, sizeof(new2old[0]), (void *)destPositionsPtr, sInternalComparatorByPosition);
1752 else
1753 qsort_r(new2old, length, sizeof(new2old[0]), tempDataPtr, sInternalComparatorByTick);
1754
1755 /* Sort events */
1756 MDPointerSetPosition(tempTrackPtr, -1);
1757 index = 0;
1758 while ((ep = MDPointerForward(tempTrackPtr)) != NULL) {
1759 MDSetTick(ep, tempDataPtr[index]);
1760 MDEventMove((MDEvent *)tempBuffer + index, ep, 1);
1761 index++;
1762 }
1763 MDPointerSetPosition(tempTrackPtr, -1);
1764 index = 0;
1765 while ((ep = MDPointerForward(tempTrackPtr)) != NULL) {
1766 MDEventMove(ep, (MDEvent *)tempBuffer + new2old[index], 1);
1767 index++;
1768 }
1769 MDTrackRecache(tempTrack, 0);
1770
1771 /* Sort undoDataPtr */
1772 memmove(tempBuffer, undoDataPtr, sizeof(MDTickType) * length);
1773 for (index = 0; index < length; index++)
1774 undoDataPtr[index] = *((MDTickType *)tempBuffer + new2old[index]);
1775
1776 /* Sort undoPositionsPtr */
1777 memmove(tempBuffer, undoPositionsPtr, sizeof(int32_t) * length);
1778 for (index = 0; index < length; index++)
1779 undoPositionsPtr[index] = *((int32_t *)tempBuffer + new2old[index]);
1780
1781 free(new2old);
1782 free(tempBuffer);
1783 }
1784
1785 /* Prepare destPset */
1786 if (destPositionsPtr == NULL)
1787 destPset = NULL;
1788 else {
1789 destPset = IntGroupNew();
1790 for (index = 0; index < length; index++)
1791 IntGroupAdd(destPset, destPositionsPtr[index], 1);
1792 }
1793
1794 /* Merge the modified events back to the target track */
1795 if (doc != nil)
1796 [doc lockMIDISequence];
1797 status = MDTrackMerge(track, tempTrack, &destPset);
1798 if (doc != nil)
1799 [doc unlockMIDISequence];
1800 if (status != kMDNoError)
1801 return NO;
1802
1803 MDPointerRelease(tempTrackPtr);
1804 MDTrackRelease(tempTrack);
1805
1806 if (doc != nil) {
1807 newDestPointSet = [[[MDSelectionObject allocWithZone: [doc zone]] initWithMDPointSet: destPset] autorelease];
1808
1809 /* Set selection */
1810 if (setSelection) {
1811 [doc registerUndoForRestoringCurrentSelectionInTrack: trackNo];
1812 [doc setSelection: newDestPointSet inTrack: trackNo sender: doc];
1813 }
1814
1815 /* Register undo action */
1816 if (oldDuration != MDTrackGetDuration(track)) {
1817 [[[doc undoManager] prepareWithInvocationTarget: doc]
1818 changeTrackDuration: oldDuration ofTrack: trackNo];
1819 }
1820 [[[doc undoManager] prepareWithInvocationTarget: doc]
1821 modifyTick:
1822 (undoMode == MyDocumentModifyAdd
1823 ? (id)[NSNumber numberWithLong: -dataValue]
1824 : (id)undoData)
1825 ofMultipleEventsAt: newDestPointSet inTrack: trackNo mode: undoMode
1826 destinationPositions: undoPositions setSelection: setSelection];
1827
1828 /* Post the notification that this track has been modified */
1829 [doc enqueueTrackModifiedNotification: trackNo];
1830 }
1831
1832 IntGroupRelease(destPset);
1833
1834 return YES;
1835 }
1836
1837 - (BOOL)modifyCodes: (id)theData ofMultipleEventsAt: (IntGroupObject *)pointSet inTrack: (int32_t)trackNo mode: (MyDocumentModifyMode)mode
1838 {
1839 MDTrack *track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1840 if (track == NULL)
1841 return NO;
1842 /* Call the class method version */
1843 return [MyDocument modifyCodes: theData ofMultipleEventsAt: pointSet forMDTrack: track inDocument: self mode: mode];
1844 }
1845
1846 + (BOOL)modifyCodes: (id)theData ofMultipleEventsAt: (IntGroupObject *)pointSet forMDTrack: (MDTrack *)track inDocument: (MyDocument *)doc mode: (MyDocumentModifyMode)mode
1847 {
1848 int32_t trackNo;
1849 MDPointer *ptr;
1850 MDEvent *ep;
1851 IntGroup *pset;
1852 int32_t index, length;
1853 int psetIndex;
1854 short dataValue, oldValue, newValue;
1855 short *dataPtr;
1856 float floatDataValue;
1857 float *floatDataPtr;
1858 unsigned int dataMode;
1859 MyDocumentModifyMode undoMode;
1860 NSMutableData *undoData;
1861 short *undoDataPtr;
1862 if (doc != nil)
1863 trackNo = [[doc myMIDISequence] lookUpTrack: track];
1864 else trackNo = -1;
1865 ptr = MDPointerNew(track);
1866 if (ptr == NULL)
1867 return NO;
1868 pset = [pointSet pointSet];
1869 if (pset == NULL)
1870 return NO;
1871 length = IntGroupGetCount(pset);
1872 if ([theData isKindOfClass: [NSNumber class]]) {
1873 dataMode = 0;
1874 if (mode == MyDocumentModifyMultiply) {
1875 floatDataValue = [theData floatValue];
1876 } else {
1877 dataValue = [theData intValue];
1878 }
1879 } else if ([theData isKindOfClass: [NSData class]]) {
1880 dataMode = 1;
1881 if (mode == MyDocumentModifyMultiply) {
1882 floatDataPtr = (float *)[theData bytes];
1883 } else {
1884 dataPtr = (short *)[theData bytes];
1885 }
1886 } else {
1887 dataMode = 2;
1888 }
1889 undoData = [NSMutableData dataWithLength: sizeof(short) * length];
1890 undoDataPtr = (short *)[undoData mutableBytes];
1891 undoMode = (mode == MyDocumentModifyAdd ? mode : MyDocumentModifySet);
1892 index = 0;
1893 MDPointerSetPositionWithPointSet(ptr, pset, -1, &psetIndex);
1894 if (doc != nil)
1895 [doc lockMIDISequence];
1896 while ((ep = MDPointerForwardWithPointSet(ptr, pset, &psetIndex)) != NULL) {
1897 // if (MDGetKind(ep) != kMDEventNote)
1898 // continue;
1899 oldValue = MDGetCode(ep);
1900 if (mode == MyDocumentModifySet || mode == MyDocumentModifyAdd) {
1901 if (dataMode == 0)
1902 newValue = dataValue;
1903 else if (dataMode == 1)
1904 newValue = dataPtr[index];
1905 else newValue = [[theData objectAtIndex: index] intValue];
1906 if (mode == MyDocumentModifyAdd)
1907 newValue += oldValue;
1908 } else if (mode == MyDocumentModifyMultiply) {
1909 if (dataMode == 0)
1910 newValue = oldValue * floatDataValue;
1911 else if (dataMode == 1)
1912 newValue = oldValue * floatDataPtr[index];
1913 else newValue = oldValue * [[theData objectAtIndex: index] floatValue];
1914 }
1915 if (newValue < 0 || newValue > 127) {
1916 undoMode = MyDocumentModifySet; // Undo can be no longer accomplished by simply adding negative of theData
1917 if (newValue < 0)
1918 newValue = 0;
1919 else newValue = 127;
1920 }
1921 MDSetCode(ep, newValue);
1922 undoDataPtr[index] = oldValue;
1923 index++;
1924 }
1925 if (doc != nil)
1926 [doc unlockMIDISequence];
1927 // fprintf(stderr, "modifyCodes: undoMode=%d undoData=(", (int)undoMode);
1928 // { int i; for (i = 0; i < [undoData length] / 2; i++) fprintf(stderr, "%s%d", (i!=0?",":""), ((short *)[undoData bytes])[i]); }
1929 // fprintf(stderr, ")\n");
1930 if (doc != nil) {
1931 /* Register undo action */
1932 [[[doc undoManager] prepareWithInvocationTarget: doc]
1933 modifyCodes:
1934 (undoMode == MyDocumentModifyAdd
1935 ? (id)[NSNumber numberWithInt: -dataValue]
1936 : (id)undoData)
1937 ofMultipleEventsAt: pointSet inTrack: trackNo mode: undoMode];
1938 /* Post the notification that this track has been modified */
1939 [doc enqueueTrackModifiedNotification: trackNo];
1940 }
1941 return YES;
1942 }
1943
1944 - (BOOL)modifyDurations: (id)theData ofMultipleEventsAt: (IntGroupObject *)pointSet inTrack: (int32_t)trackNo mode: (MyDocumentModifyMode)mode
1945 {
1946 MDTrack *track = [[self myMIDISequence] getTrackAtIndex: trackNo];
1947 if (track == NULL)
1948 return NO;
1949 /* Call the class method version */
1950 return [MyDocument modifyDurations: theData ofMultipleEventsAt: pointSet forMDTrack: track inDocument: self mode: mode];
1951 }
1952
1953 + (BOOL)modifyDurations: (id)theData ofMultipleEventsAt: (IntGroupObject *)pointSet forMDTrack: (MDTrack *)track inDocument: (MyDocument *)doc mode: (MyDocumentModifyMode)mode
1954 {
1955 int32_t trackNo;
1956 MDPointer *ptr;
1957 MDEvent *ep;
1958 IntGroup *pset;
1959 int32_t index, length;
1960 int psetIndex;
1961 MDTickType dataValue, oldValue, newValue;
1962 MDTickType *dataPtr;
1963 MDTickType maxTick;
1964 float floatDataValue;
1965 float *floatDataPtr;
1966 unsigned int dataMode;
1967 MyDocumentModifyMode undoMode;
1968 NSMutableData *undoData;
1969 MDTickType *undoDataPtr;
1970 MDTickType oldDuration;
1971
1972 if (doc != nil)
1973 trackNo = [[doc myMIDISequence] lookUpTrack: track];
1974 else trackNo = -1;
1975 oldDuration = MDTrackGetDuration(track);
1976 ptr = MDPointerNew(track);
1977 if (ptr == NULL)
1978 return NO;
1979 pset = [pointSet pointSet];
1980 if (pset == NULL)
1981 return NO;
1982 length = IntGroupGetCount(pset);
1983 if ([theData isKindOfClass: [NSNumber class]]) {
1984 dataMode = 0;
1985 if (mode == MyDocumentModifyMultiply) {
1986 floatDataValue = [theData floatValue];
1987 } else {
1988 dataValue = [theData intValue];
1989 }
1990 } else if ([theData isKindOfClass: [NSData class]]) {
1991 dataMode = 1;
1992 if (mode == MyDocumentModifyMultiply) {
1993 floatDataPtr = (float *)[theData bytes];
1994 } else {
1995 dataPtr = (MDTickType *)[theData bytes];
1996 }
1997 } else {
1998 dataMode = 2;
1999 }
2000 undoData = [NSMutableData dataWithLength: sizeof(MDTickType) * length];
2001 undoDataPtr = (MDTickType *)[undoData mutableBytes];
2002 undoMode = (mode == MyDocumentModifyAdd ? mode : MyDocumentModifySet);
2003 index = 0;
2004 MDPointerSetPositionWithPointSet(ptr, pset, -1, &psetIndex);
2005 maxTick = -1;
2006 if (doc != nil)
2007 [doc lockMIDISequence];
2008 while ((ep = MDPointerForwardWithPointSet(ptr, pset, &psetIndex)) != NULL) {
2009 if (MDGetKind(ep) != kMDEventNote)
2010 continue;
2011 oldValue = MDGetDuration(ep);
2012 if (mode == MyDocumentModifySet || mode == MyDocumentModifyAdd) {
2013 if (dataMode == 0)
2014 newValue = dataValue;
2015 else if (dataMode == 1)
2016 newValue = dataPtr[index];
2017 else newValue = [[theData objectAtIndex: index] intValue];
2018 if (mode == MyDocumentModifyAdd)
2019 newValue += oldValue;
2020 } else if (mode == MyDocumentModifyMultiply) {
2021 if (dataMode == 0)
2022 newValue = oldValue * floatDataValue;
2023 else if (dataMode == 1)
2024 newValue = oldValue * floatDataPtr[index];
2025 else newValue = oldValue * [[theData objectAtIndex: index] floatValue];
2026 }
2027 if (newValue <= 0 || newValue > kMDMaxTick / 2) {
2028 undoMode = MyDocumentModifySet; // Undo can be no longer accomplished by simply adding negative of theData
2029 if (newValue <= 0)
2030 newValue = 1;
2031 else newValue = kMDMaxTick / 2;
2032 }
2033 MDPointerSetDuration(ptr, newValue);
2034 if (MDGetTick(ep) + newValue > maxTick)
2035 maxTick = MDGetTick(ep) + newValue;
2036 undoDataPtr[index] = oldValue;
2037 index++;
2038 }
2039 if (maxTick >= MDTrackGetDuration(track)) {
2040 if (doc != nil)
2041 [doc changeTrackDuration: maxTick + 1 ofTrack: trackNo];
2042 else
2043 MDTrackSetDuration(track, maxTick + 1);
2044 }
2045 if (doc != nil)
2046 [doc unlockMIDISequence];
2047 // fprintf(stderr, "modifyCodes: undoMode=%d undoData=(", (int)undoMode);
2048 // { int i; for (i = 0; i < [undoData length] / 2; i++) fprintf(stderr, "%s%d", (i!=0?",":""), ((short *)[undoData bytes])[i]); }
2049 // fprintf(stderr, ")\n");
2050 /* Register undo action */
2051 if (doc != nil) {
2052 if (oldDuration != MDTrackGetDuration(track)) {
2053 [[[doc undoManager] prepareWithInvocationTarget: doc]
2054 changeTrackDuration: oldDuration ofTrack: trackNo];
2055 }
2056 [[[doc undoManager] prepareWithInvocationTarget: doc]
2057 modifyDurations:
2058 (undoMode == MyDocumentModifyAdd
2059 ? (id)[NSNumber numberWithLong: -dataValue]
2060 : (id)undoData)
2061 ofMultipleEventsAt: pointSet inTrack: trackNo mode: undoMode];
2062 /* Post the notification that this track has been modified */
2063 [doc enqueueTrackModifiedNotification: trackNo];
2064 }
2065 return YES;
2066 }
2067
2068 - (BOOL)modifyData: (id)theData forEventKind: (unsigned char)eventKind ofMultipleEventsAt: (IntGroupObject *)pointSet inTrack: (int32_t)trackNo mode: (MyDocumentModifyMode)mode
2069 {
2070 MDTrack *track = [[self myMIDISequence] getTrackAtIndex: trackNo];
2071 if (track == NULL)
2072 return NO;
2073 /* Call the class method version */
2074 return [MyDocument modifyData: theData forEventKind: eventKind ofMultipleEventsAt: pointSet forMDTrack: track inDocument: self mode: mode];
2075 }
2076
2077 + (BOOL)modifyData: (id)theData forEventKind: (unsigned char)eventKind ofMultipleEventsAt: (IntGroupObject *)pointSet forMDTrack: (MDTrack *)track inDocument: (MyDocument *)doc mode: (MyDocumentModifyMode)mode
2078 {
2079 int32_t trackNo;
2080 MDPointer *ptr;
2081 MDEvent *ep;
2082 IntGroup *pset;
2083 int32_t index, length;
2084 int psetIndex;
2085 float dataValue, oldValue, newValue;
2086 float dataMin, dataMax;
2087 short *dataPtr;
2088 float *floatDataPtr;
2089 unsigned int dataMode;
2090 MyDocumentModifyMode undoMode;
2091 NSMutableData *undoData;
2092 short *undoDataPtr;
2093 float *floatUndoDataPtr;
2094 BOOL floatFlag;
2095 if (doc != nil)
2096 trackNo = [[doc myMIDISequence] lookUpTrack: track];
2097 else trackNo = -1;
2098 ptr = MDPointerNew(track);
2099 if (ptr == NULL)
2100 return NO;
2101 pset = [pointSet pointSet];
2102 if (pset == NULL)
2103 return NO;
2104 length = IntGroupGetCount(pset);
2105 if ([theData isKindOfClass: [NSNumber class]]) {
2106 dataMode = 0;
2107 dataValue = [theData floatValue];
2108 } else if ([theData isKindOfClass: [NSData class]]) {
2109 dataMode = 1;
2110 if ([theData length] >= length * sizeof(float)) {
2111 floatDataPtr = (float *)[theData bytes];
2112 floatFlag = YES;
2113 } else {
2114 dataPtr = (short *)[theData bytes];
2115 floatFlag = NO;
2116 }
2117 } else {
2118 dataMode = 2;
2119 }
2120 if (eventKind == kMDEventPitchBend) {
2121 dataMax = 8191;
2122 dataMin = -8192;
2123 } else if (eventKind == kMDEventTempo) {
2124 dataMax = kMDMaxTempo;
2125 dataMin = kMDMinTempo;
2126 } else {
2127 dataMax = 127;
2128 dataMin = 0;
2129 }
2130 if (eventKind == kMDEventTempo) {
2131 undoData = [NSMutableData dataWithLength: sizeof(float) * length];
2132 floatUndoDataPtr = (float *)[undoData mutableBytes];
2133 } else {
2134 undoData = [NSMutableData dataWithLength: sizeof(short) * length];
2135 undoDataPtr = (short *)[undoData mutableBytes];
2136 }
2137 if (mode == MyDocumentModifyAdd && eventKind != kMDEventTempo)
2138 undoMode = MyDocumentModifyAdd;
2139 else undoMode = MyDocumentModifySet;
2140 index = 0;
2141 MDPointerSetPositionWithPointSet(ptr, pset, -1, &psetIndex);
2142 if (doc != nil)
2143 [doc lockMIDISequence];
2144 while ((ep = MDPointerForwardWithPointSet(ptr, pset, &psetIndex)) != NULL) {
2145 if (MDGetKind(ep) != eventKind
2146 && (eventKind != kMDEventInternalNoteOff || MDGetKind(ep) != kMDEventNote))
2147 continue;
2148 if (eventKind == kMDEventNote)
2149 oldValue = MDGetNoteOnVelocity(ep);
2150 else if (eventKind == kMDEventInternalNoteOff)
2151 oldValue = MDGetNoteOffVelocity(ep);
2152 else if (eventKind == kMDEventTempo)
2153 oldValue = MDGetTempo(ep);
2154 else oldValue = MDGetData1(ep);
2155 if (mode == MyDocumentModifySet || mode == MyDocumentModifyAdd) {
2156 if (dataMode == 0)
2157 newValue = dataValue;
2158 else if (dataMode == 1)
2159 newValue = (floatFlag ? floatDataPtr[index] : (float)dataPtr[index]);
2160 else newValue = [[theData objectAtIndex: index] floatValue];
2161 if (mode == MyDocumentModifyAdd) {
2162 newValue += oldValue;
2163 }
2164 } else if (mode == MyDocumentModifyMultiply) {
2165 float multiple;
2166 if (dataMode == 0)
2167 multiple = dataValue;
2168 else if (dataMode == 1)
2169 multiple = (floatFlag ? floatDataPtr[index] : (float)dataPtr[index]);
2170 else multiple = [[theData objectAtIndex: index] floatValue];
2171 newValue = oldValue * multiple;
2172 }
2173 if (newValue < dataMin || newValue > dataMax) {
2174 undoMode = MyDocumentModifySet; // Undo can be no longer accomplished by simply adding negative of theData
2175 if (newValue < dataMin)
2176 newValue = dataMin;
2177 else newValue = dataMax;
2178 }
2179 if (eventKind == kMDEventNote)
2180 MDSetNoteOnVelocity(ep, newValue);
2181 else if (eventKind == kMDEventInternalNoteOff)
2182 MDSetNoteOffVelocity(ep, newValue);
2183 else if (eventKind == kMDEventTempo)
2184 MDSetTempo(ep, newValue);
2185 else MDSetData1(ep, newValue);
2186 if (eventKind == kMDEventTempo)
2187 floatUndoDataPtr[index] = oldValue;
2188 else undoDataPtr[index] = oldValue;
2189 index++;
2190 }
2191 if (doc != nil)
2192 [doc unlockMIDISequence];
2193 // fprintf(stderr, "modifyCodes: undoMode=%d undoData=(", (int)undoMode);
2194 // { int i; for (i = 0; i < [undoData length] / 2; i++) fprintf(stderr, "%s%d", (i!=0?",":""), ((short *)[undoData bytes])[i]); }
2195 // fprintf(stderr, ")\n");
2196 if (doc != nil) {
2197 /* Register undo action */
2198 [[[doc undoManager] prepareWithInvocationTarget: doc]
2199 modifyData:
2200 (undoMode == MyDocumentModifyAdd
2201 ? (id)[NSNumber numberWithFloat: -dataValue]
2202 : (id)undoData)
2203 forEventKind: eventKind
2204 ofMultipleEventsAt: pointSet inTrack: trackNo mode: undoMode];
2205 /* Post the notification that this track has been modified */
2206 [doc enqueueTrackModifiedNotification: trackNo];
2207 }
2208 return YES;
2209 }
2210
2211 - (const MDEvent *)eventAtPosition: (int32_t)position inTrack: (int32_t)trackNo
2212 {
2213 MDTrack *track;
2214 MDEvent *ep1;
2215 MDPointer *pt1;
2216 track = MDSequenceGetTrack([[self myMIDISequence] mySequence], trackNo);
2217 pt1 = MDPointerNew(track);
2218 if (pt1 != NULL && MDPointerSetPosition(pt1, position)) {
2219 ep1 = MDPointerCurrent(pt1);
2220 } else ep1 = NULL;
2221 MDPointerRelease(pt1);
2222 return ep1;
2223 }
2224
2225 - (int32_t)changeTick: (int32_t)tick atPosition: (int32_t)position inTrack: (int32_t)trackNo originalPosition: (int32_t)pos1
2226 {
2227 MDTrack *track;
2228 MDEvent *ep1;
2229 MDPointer *pt1;
2230 int32_t opos1, npos;
2231 MDTickType otick, oduration, duration;
2232 MDStatus sts = kMDNoError;
2233 track = MDSequenceGetTrack([[self myMIDISequence] mySequence], trackNo);
2234 pt1 = MDPointerNew(track);
2235 if (pt1 != NULL && MDPointerSetPosition(pt1, position) && (ep1 = MDPointerCurrent(pt1)) != NULL) {
2236 otick = MDGetTick(ep1);
2237 if (otick == tick)
2238 return -1; /* Do nothing */
2239 opos1 = MDPointerGetPosition(pt1);
2240 oduration = MDTrackGetDuration(track);
2241 [self lockMIDISequence];
2242 if (otick < tick) {
2243 /* Move pt2 first, then ep1 */
2244 if (sts == kMDNoError)
2245 sts = MDPointerChangeTick(pt1, tick, pos1);
2246 } else {
2247 /* Move pt1 first, then ep2 */
2248 sts = MDPointerChangeTick(pt1, tick, pos1);
2249 }
2250 [self unlockMIDISequence];
2251 if (sts == kMDNoError) {
2252 /* The position of the event after moving */
2253 npos = MDPointerGetPosition(pt1);
2254 /* The selection after moving the event */
2255 if (npos != position) {
2256 MDSelectionObject *newSet = [[[MDSelectionObject allocWithZone: [self zone]] init] autorelease];
2257 MDSelectionObject *oldSet = (MDSelectionObject *)[[[selections objectAtIndex: trackNo] retain] autorelease];
2258 IntGroup *newpset = [newSet pointSet];
2259 IntGroup *oldpset = [oldSet pointSet];
2260 IntGroupIterator iter;
2261 IntGroupIteratorInit(oldpset, &iter);
2262 int32_t pos;
2263 /* For each point in oldSet, add the new position */
2264 while ((pos = IntGroupIteratorNext(&iter)) >= 0) {
2265 if (pos == position)
2266 IntGroupAdd(newpset, npos, 1); /* Add the new position */
2267 else {
2268 if (position < npos) {
2269 if (pos > position && pos <= npos)
2270 pos--; /* Move one event backward */
2271 } else {
2272 if (pos >= npos && pos < position)
2273 pos++; /* Move one event forward */
2274 }
2275 IntGroupAdd(newpset, pos, 1);
2276 }
2277 }
2278 [[[self undoManager] prepareWithInvocationTarget: self]
2279 setSelection: oldSet inTrack: trackNo sender: self];
2280 [self setSelection: newSet inTrack: trackNo sender: self];
2281 }
2282 /* Register undo action with the current values */
2283 duration = MDTrackGetDuration(track);
2284 if (oduration != duration)
2285 [[[self undoManager] prepareWithInvocationTarget: self]
2286 changeTrackDuration: oduration ofTrack: trackNo];
2287 [[[self undoManager] prepareWithInvocationTarget: self]
2288 changeTick: otick atPosition: npos inTrack: trackNo originalPosition: opos1];
2289 /* Post the notification that any track has been modified */
2290 [self enqueueTrackModifiedNotification: trackNo];
2291 } else npos = -1;
2292 MDPointerRelease(pt1);
2293 return npos;
2294 }
2295 if (pt1 != NULL)
2296 MDPointerRelease(pt1);
2297 return -1;
2298 }
2299
2300 - (BOOL)changeChannel: (int)channel atPosition: (int32_t)position inTrack: (int32_t)trackNo
2301 {
2302 MDEvent *ep;
2303 int ch;
2304 MDPointer *pointer = MDPointerNew(MDSequenceGetTrack([[self myMIDISequence] mySequence], trackNo));
2305 if (pointer != NULL && MDPointerSetPosition(pointer, position) && (ep = MDPointerCurrent(pointer)) != NULL) {
2306 if (MDIsChannelEvent(ep)) {
2307 ch = MDGetChannel(ep);
2308 MDSetChannel(ep, (channel & 15));
2309 if (ch != channel) {
2310 /* Register undo action with current value */
2311 [[[self undoManager] prepareWithInvocationTarget: self]
2312 changeChannel: ch atPosition: position inTrack: trackNo];
2313 /* Post the notification that any track has been modified */
2314 [self enqueueTrackModifiedNotification: trackNo];
2315 MDPointerRelease(pointer);
2316 return YES;
2317 }
2318 }
2319 }
2320 if (pointer != NULL)
2321 MDPointerRelease(pointer);
2322 return NO;
2323 }
2324
2325 - (BOOL)changeDuration: (int32_t)duration atPosition: (int32_t)position inTrack: (int32_t)trackNo
2326 {
2327 MDTrack *track;
2328 MDEvent *ep1;
2329 MDPointer *pt1;
2330 int32_t oduration, oldTrackDuration;
2331 BOOL modified = NO;
2332 track = MDSequenceGetTrack([[self myMIDISequence] mySequence], trackNo);
2333 pt1 = MDPointerNew(track);
2334 oldTrackDuration = MDTrackGetDuration(track);
2335 if (pt1 != NULL && MDPointerSetPosition(pt1, position) && (ep1 = MDPointerCurrent(pt1)) != NULL) {
2336 if (MDGetKind(ep1) == kMDEventNote) {
2337 oduration = MDGetDuration(ep1);
2338 if (oduration != duration) {
2339 [self lockMIDISequence];
2340 if (MDGetTick(ep1) + duration >= oldTrackDuration) {
2341 MDTickType newTrackDuration = MDGetTick(ep1) + duration + 1;
2342 [self changeTrackDuration: newTrackDuration ofTrack: trackNo];
2343 }
2344 MDPointerSetDuration(pt1, duration);
2345 [self unlockMIDISequence];
2346 /* Register undo action for change of track duration (if necessary) */
2347 [self registerUndoChangeTrackDuration: oldTrackDuration ofTrack: trackNo];
2348 /* Register undo action with current value */
2349 [[[self undoManager] prepareWithInvocationTarget: self]
2350 changeDuration: oduration atPosition: position inTrack: trackNo];
2351 /* Post the notification that any track has been modified */
2352 [self enqueueTrackModifiedNotification: trackNo];
2353 modified = YES;
2354 }
2355 }
2356 }
2357 return modified;
2358 }
2359
2360 - (BOOL)changeValue: (MDEventFieldDataWhole)wholeValue ofType: (int)code atPosition: (int32_t)position inTrack: (int32_t)trackNo
2361 {
2362 MDEventFieldData ed1, ed2, value;
2363 MDEvent *ep;
2364 int d1, d2;
2365 MDPointer *pointer = MDPointerNew(MDSequenceGetTrack([[self myMIDISequence] mySequence], trackNo));
2366
2367 value.whole = wholeValue;
2368 ed1 = ed2 = value;
2369
2370 if (pointer != NULL && MDPointerSetPosition(pointer, position) && (ep = MDPointerCurrent(pointer)) != NULL) {
2371 [self lockMIDISequence];
2372 switch (code) {
2373 case kMDEventFieldKindAndCode:
2374 /* Kind should be the same with the original */
2375 if (MDGetKind(ep) == ed1.ucValue[0]) {
2376 ed2.ucValue[1] = MDGetCode(ep);
2377 MDSetCode(ep, ed1.ucValue[1]);
2378 } else if (ed1.ucValue[0] == kMDEventNote && (MDGetKind(ep) == kMDEventNote)) {
2379 ed2.ucValue[1] = MDGetCode(ep);
2380 MDSetCode(ep, ed1.ucValue[1]);
2381 }
2382 break;
2383 case kMDEventFieldVelocities:
2384 {
2385 d1 = ed1.ucValue[0];
2386 d2 = ed1.ucValue[1];
2387 if (d1 == 0)
2388 d1 = 1;
2389 switch (MDGetKind(ep)) {
2390 case kMDEventNote:
2391 /* ed2.ucValue[0] = ((MDGetData1(ep) >> 8) & 0xff);
2392 ed2.ucValue[1] = (MDGetData1(ep) & 0xff);
2393 MDSetData1(ep, ((d1 & 0xff) << 8) + (d2 & 0xff)); */
2394 ed2.ucValue[0] = MDGetNoteOnVelocity(ep);
2395 ed2.ucValue[1] = MDGetNoteOffVelocity(ep);
2396 MDSetNoteOnVelocity(ep, d1);
2397 MDSetNoteOffVelocity(ep, d2);
2398 break;
2399 }
2400 break;
2401 }
2402 case kMDEventFieldData:
2403 ed2.intValue = MDGetData1(ep);
2404 MDSetData1(ep, ed1.intValue);
2405 break;
2406 case kMDEventFieldSMPTE:
2407 {
2408 MDSMPTERecord *smp;
2409 smp = MDGetSMPTERecordPtr(ep);
2410 ed2.smpte = *smp;
2411 *smp = ed1.smpte;
2412 break;
2413 }
2414 case kMDEventFieldMetaData:
2415 {
2416 unsigned char *ptr;
2417 ptr = MDGetMetaDataPtr(ep);
2418 memcpy(ed2.ucValue, ptr, 4);
2419 memcpy(ptr, ed1.ucValue, 4);
2420 break;
2421 }
2422 case kMDEventFieldTempo:
2423 {
2424 float tempo;
2425 ed2.floatValue = MDGetTempo(ep);
2426 tempo = ed1.floatValue;
2427 if (tempo < kMDMinTempo)
2428 tempo = kMDMinTempo;
2429 else if (tempo > kMDMaxTempo)
2430 tempo = kMDMaxTempo;
2431 MDSetTempo(ep, ed1.floatValue);
2432 break;
2433 }
2434 }
2435 [self unlockMIDISequence];
2436 }
2437 if (pointer != NULL)
2438 MDPointerRelease(pointer);
2439
2440 if (ed1.whole != ed2.whole) {
2441 /* Register undo action with current value */
2442 [[[self undoManager] prepareWithInvocationTarget: self]
2443 changeValue: ed2.whole ofType: code atPosition: position inTrack: trackNo];
2444 /* Post the notification that any track has been modified */
2445 [self enqueueTrackModifiedNotification: trackNo];
2446 return YES;
2447 } else return NO;
2448 }
2449
2450 - (BOOL)changeMessage: (NSData *)data atPosition: (int32_t)position inTrack: (int32_t)trackNo
2451 {
2452 MDEvent *ep;
2453 MDPointer *pointer = MDPointerNew(MDSequenceGetTrack([[self myMIDISequence] mySequence], trackNo));
2454 NSData *data2 = nil;
2455 BOOL modify = NO;
2456
2457 if (pointer != NULL && MDPointerSetPosition(pointer, position) && (ep = MDPointerCurrent(pointer)) != NULL) {
2458 if (MDHasEventMessage(ep)) {
2459 const unsigned char *ptr;
2460 int32_t length;
2461 [self lockMIDISequence];
2462 ptr = MDGetMessageConstPtr(ep, &length);
2463 data2 = [NSData dataWithBytes: ptr length: length];
2464 /* Will the data really need modification? */
2465 if (MDIsTextMetaEvent(ep)) {
2466 if (length != [data length] || strncmp((char *)[data bytes], (char *)[data2 bytes], length) != 0)
2467 modify = YES;
2468 } else {
2469 if (![data isEqualToData: data2])
2470 modify = YES;
2471 }
2472 if (modify) {
2473 length = (int)[data length];
2474 if (MDSetMessageLength(ep, length) == length)
2475 MDSetMessage(ep, [data bytes]);
2476 }
2477 [self unlockMIDISequence];
2478 }
2479 }
2480 if (pointer != NULL)
2481 MDPointerRelease(pointer);
2482
2483 if (modify) {
2484 /* Register undo action with current value */
2485 [[[self undoManager] prepareWithInvocationTarget: self]
2486 changeMessage: data2 atPosition: position inTrack: trackNo];
2487 /* Post the notification that any track has been modified */
2488 [self enqueueTrackModifiedNotification: trackNo];
2489 return YES;
2490 } else return NO;
2491 }
2492
2493 /*
2494 - (BOOL)changeDeviceNumber: (int32_t)deviceNumber forTrack: (int32_t)trackNo;
2495 {
2496 int32_t oldNumber;
2497 MDTrack *track = [myMIDISequence getTrackAtIndex: trackNo];
2498 if (track != NULL) {
2499 oldNumber = MDTrackGetDevice(track);
2500 if (oldNumber == deviceNumber)
2501 return NO; // No need to change
2502 MDTrackSetDevice(track, deviceNumber);
2503 [[[self undoManager] prepareWithInvocationTarget: self]
2504 changeDeviceNumber: oldNumber forTrack: trackNo];
2505 [self enqueueTrackModifiedNotification: trackNo];
2506 [self updateTrackDestinations];
2507 return YES;
2508 }
2509 return NO;
2510 }
2511 */
2512
2513 #pragma mark ====== Editing range ======
2514
2515 - (void)getEditingRangeStart: (MDTickType *)startTick end: (MDTickType *)endTick
2516 {
2517 *startTick = startEditingRange;
2518 *endTick = endEditingRange;
2519 }
2520
2521 - (void)setEditingRangeStart: (MDTickType)startTick end: (MDTickType)endTick
2522 {
2523 if (startTick < 0 && endTick < 0) {
2524 startTick = endTick = kMDNegativeTick;
2525 } else if (startTick >= 0 && endTick >= startTick) {
2526 /* MDTickType maxTick = [[self myMIDISequence] sequenceDuration];
2527 if (endTick >= maxTick)
2528 endTick = maxTick;
2529 if (startTick >= maxTick)
2530 startTick = maxTick; */
2531 } else return;
2532 [self enqueueSelectionUndoerWithKey: sEditingRangeKey value: [[[MDTickRangeObject alloc] initWithStartTick: startEditingRange endTick: endEditingRange] autorelease]];
2533 startEditingRange = startTick;
2534 endEditingRange = endTick;
2535 }
2536
2537 #pragma mark ====== Selection ======
2538
2539 /* NOTE: setSelection and toggleSelection are the main methods to modify track selections.
2540 Other methods calls these main methods with appropriate parameters. */
2541
2542 - (BOOL)setSelection: (MDSelectionObject *)set inTrack: (int32_t)trackNo sender: (id)sender
2543 {
2544 MDSelectionObject *oldSet = (MDSelectionObject *)[[[selections objectAtIndex: trackNo] retain] autorelease];
2545
2546 [selections replaceObjectAtIndex: trackNo withObject: set];
2547 [self enqueueSelectionUndoerWithKey: [NSNumber numberWithInt: (int)trackNo] value: oldSet];
2548 return YES;
2549 }
2550
2551 - (BOOL)toggleSelection: (MDSelectionObject *)pointSet inTrack: (int32_t)trackNo sender: (id)sender
2552 {
2553 MDSelectionObject *newSet = [[[MDSelectionObject allocWithZone: [self zone]] init] autorelease];
2554 MDSelectionObject *oldSet = (MDSelectionObject *)[[[selections objectAtIndex: trackNo] retain] autorelease];
2555 MDStatus sts = IntGroupXor([oldSet pointSet], [pointSet pointSet], [newSet pointSet]);
2556 if (sts == kMDNoError) {
2557 [selections replaceObjectAtIndex: trackNo withObject: newSet];
2558 /* For debug */
2559 #if DEBUG
2560 if (gMDVerbose > 0)
2561 IntGroupDump([newSet pointSet]);
2562 #endif
2563 [self enqueueSelectionUndoerWithKey: [NSNumber numberWithInt: (int)trackNo] value: oldSet];
2564 return YES;
2565 } else return NO;
2566 }
2567
2568 - (BOOL)selectEventAtPosition: (int32_t)position inTrack: (int32_t)trackNo sender: (id)sender
2569 {
2570 if (!IntGroupLookup([(MDSelectionObject *)[selections objectAtIndex: trackNo] pointSet], position, NULL)) {
2571 MDSelectionObject *pointSet = [[[MDSelectionObject allocWithZone: [self zone]] init] autorelease];
2572 IntGroupAdd([pointSet pointSet], position, 1);
2573 [self toggleSelection: pointSet inTrack: trackNo sender: sender];
2574 return YES;
2575 } else return NO;
2576 }
2577
2578 - (BOOL)unselectEventAtPosition: (int32_t)position inTrack: (int32_t)trackNo sender: (id)sender
2579 {
2580 if (IntGroupLookup([(MDSelectionObject *)[selections objectAtIndex: trackNo] pointSet], position, NULL)) {
2581 MDSelectionObject *pointSet = [[[MDSelectionObject allocWithZone: [self zone]] init] autorelease];
2582 IntGroupAdd([pointSet pointSet], position, 1);
2583 [self toggleSelection: pointSet inTrack: trackNo sender: sender];
2584 return YES;
2585 } else return NO;
2586 }
2587
2588 - (BOOL)selectAllEventsInTrack: (int32_t)trackNo sender: (id)sender
2589 {
2590 MDStatus sts;
2591 MDSelectionObject *pointSet = [[[MDSelectionObject allocWithZone: [self zone]] init] autorelease];
2592 sts = IntGroupAdd([pointSet pointSet], 0, MDTrackGetNumberOfEvents([[self myMIDISequence] getTrackAtIndex: trackNo]));
2593 if (sts == kMDNoError) {
2594 return [self setSelection: pointSet inTrack: trackNo sender: sender];
2595 } else return NO;
2596 }
2597
2598 - (BOOL)unselectAllEventsInTrack: (int32_t)trackNo sender: (id)sender
2599 {
2600 MDSelectionObject *sel;
2601 sel = [self selectionOfTrack: trackNo];
2602 if (sel != nil && (IntGroupGetIntervalCount([sel pointSet]) > 0 || sel->isEndOfTrackSelected)) {
2603 sel = [[[MDSelectionObject allocWithZone: [self zone]] init] autorelease];
2604 return [self setSelection: sel inTrack: trackNo sender: sender];
2605 } else return NO; /* No need to change */
2606 }
2607
2608 - (BOOL)unselectAllEventsInAllTracks: (id)sender
2609 {
2610 int i;
2611 for (i = [[self myMIDISequence] trackCount] - 1; i >= 0; i--) {
2612 [self unselectAllEventsInTrack: i sender: sender];
2613 }
2614 return YES;
2615 }
2616
2617 - (BOOL)addSelection: (IntGroupObject *)set inTrack: (int32_t)trackNo sender: (id)sender
2618 {
2619 MDSelectionObject *pointSet = [[[MDSelectionObject allocWithZone: [self zone]] init] autorelease];
2620 MDSelectionObject *oldSet = (MDSelectionObject *)[selections objectAtIndex: trackNo];
2621 MDStatus sts = IntGroupUnion([oldSet pointSet], [set pointSet], [pointSet pointSet]);
2622 if (sts == kMDNoError)
2623 return [self setSelection: pointSet inTrack: trackNo sender: sender];
2624 else return NO;
2625 }
2626
2627 - (BOOL)isSelectedAtPosition: (int32_t)position inTrack: (int32_t)trackNo
2628 {
2629 IntGroup *selection;
2630 selection = [(MDSelectionObject *)[selections objectAtIndex: trackNo] pointSet];
2631 if (selection != NULL) {
2632 return (IntGroupLookup(selection, position, NULL) != 0);
2633 } else return NO;
2634 }
2635
2636 - (MDSelectionObject *)selectionOfTrack: (int32_t)trackNo
2637 {
2638 return [[(MDSelectionObject *)[selections objectAtIndex: trackNo] retain] autorelease];
2639 }
2640
2641 - (MDSelectionObject *)eventSetInTrack: (int32_t)trackNo eventKind: (int)eventKind eventCode: (int)eventCode fromTick: (MDTickType)fromTick toTick: (MDTickType)toTick fromData: (float)fromData toData: (float)toData inPointSet: (IntGroupObject *)pointSet
2642 {
2643 MDEvent *ep;
2644 MDPointer *pointer = MDPointerNew(MDSequenceGetTrack([[self myMIDISequence] mySequence], trackNo));
2645 IntGroup *pset;
2646 IntGroup *resultSet;
2647 MDSelectionObject *retObj;
2648 int psetIndex;
2649 int32_t pos;
2650 int i;
2651
2652 if (pointer == NULL)
2653 return nil;
2654
2655 // Jump to the start tick
2656 if (fromTick >= 0)
2657 MDPointerJumpToTick(pointer, fromTick);
2658 pos = MDPointerGetPosition(pointer);
2659
2660 if (pointSet != nil)
2661 pset = [pointSet pointSet];
2662 else pset = NULL;
2663 if (pset != NULL) {
2664 if (!IntGroupLookup(pset, pos, &psetIndex)) {
2665 // Move forward until the position is included in pset
2666 int32_t pos1;
2667 for (i = 0; (pos1 = IntGroupGetStartPoint(pset, i)) >= 0; i++) {
2668 if (pos1 >= pos)
2669 break;
2670 }
2671 if (pos1 < 0) {
2672 MDPointerRelease(pointer);
2673 return nil; // No such events
2674 }
2675 psetIndex = i;
2676 MDPointerSetPosition(pointer, pos1);
2677 pos = MDPointerGetPosition(pointer);
2678 }
2679 }
2680
2681 // Create an empty set
2682 resultSet = IntGroupNew();
2683 if (resultSet == NULL) {
2684 MDPointerRelease(pointer);
2685 return nil;
2686 }
2687
2688 // Loop until the tick exceeds toTick or the pointSet exhausts
2689 ep = MDPointerCurrent(pointer);
2690 while (ep != NULL && MDGetTick(ep) <= toTick) {
2691 BOOL ok = NO;
2692 if (eventKind == -1 || eventKind == MDGetKind(ep)) {
2693 if (eventKind == kMDEventControl || eventKind == kMDEventKeyPres) {
2694 // Check the code
2695 if (eventCode == -1 || eventCode == MDGetCode(ep)) {
2696 // Check the data range
2697 if (MDGetData1(ep) >= fromData && MDGetData1(ep) <= toData)
2698 ok = YES;
2699 }
2700 } else if (eventKind == kMDEventNote) {
2701 // The data range is key code
2702 if (MDGetCode(ep) >= fromData && MDGetCode(ep) <= toData)
2703 ok = YES;
2704 } else {
2705 if (MDGetData1(ep) >= fromData && MDGetData1(ep) <= toData)
2706 ok = YES;
2707 }
2708 }
2709 if (ok)
2710 IntGroupAdd(resultSet, MDPointerGetPosition(pointer), 1);
2711 if (pset != NULL)
2712 ep = MDPointerForwardWithPointSet(pointer, pset, &psetIndex);
2713 else
2714 ep = MDPointerForward(pointer);
2715 }
2716
2717 retObj = [[[MDSelectionObject allocWithZone: [self zone]] initWithMDPointSet: resultSet] autorelease];
2718 MDPointerRelease(pointer);
2719 IntGroupRelease(resultSet);
2720 return retObj;
2721 }
2722
2723 - (int32_t)countMIDIEventsForTrack: (int32_t)index inSelection: (MDSelectionObject *)sel
2724 {
2725 MDEvent *ep;
2726 MDTrack *track = [[self myMIDISequence] getTrackAtIndex: index];
2727 MDPointer *pt = MDPointerNew(track);
2728 IntGroup *pset = [sel pointSet];
2729 int32_t count = 0;
2730 int n = -1;
2731 while ((ep = MDPointerForwardWithPointSet(pt, pset, &n)) != NULL) {
2732 if (!MDIsMetaEvent(ep))
2733 count++;
2734 }
2735 MDPointerRelease(pt);
2736 return count;
2737 }
2738
2739 - (BOOL)isSelectionEmptyInEditableTracks:(BOOL)editableOnly
2740 {
2741 int i;
2742 int ntracks = (int)[selections count];
2743 for (i = 0; i < ntracks; i++) {
2744 MDSelectionObject *selection;
2745 if (editableOnly && ([self trackAttributeForTrack: i] & kMDTrackAttributeEditable) == 0)
2746 continue;
2747 selection = (MDSelectionObject *)[selections objectAtIndex: i];
2748 if (IntGroupGetCount([selection pointSet]) > 0)
2749 return NO;
2750 }
2751 return YES;
2752 }
2753
2754 #pragma mark ==== Menu Commands ====
2755
2756 - (IBAction)performStartPlay: (id)sender
2757 {
2758 if ([[self myMIDISequence] isPlaying]) {
2759 [self performPausePlay:sender];
2760 return;
2761 }
2762 [[(GraphicWindowController *)mainWindowController playingViewController] pressPlayButton: sender];
2763 }
2764
2765 - (IBAction)performStopPlay: (id)sender
2766 {
2767 [[(GraphicWindowController *)mainWindowController playingViewController] pressStopButton: sender];
2768 }
2769
2770 - (IBAction)performPausePlay: (id)sender
2771 {
2772 [[(GraphicWindowController *)mainWindowController playingViewController] pressPauseButton: sender];
2773 }
2774
2775 - (IBAction)performStartMIDIRecording: (id)sender
2776 {
2777 [[(GraphicWindowController *)mainWindowController playingViewController] recordButtonPressed: sender audioFlag: NO];
2778 }
2779
2780 - (IBAction)performStartAudioRecording: (id)sender
2781 {
2782 [[(GraphicWindowController *)mainWindowController playingViewController] recordButtonPressed: sender audioFlag: YES];
2783 }
2784
2785 - (IBAction)insertBlankTime:(id)sender
2786 {
2787 int32_t trackNo;
2788 MDTickType deltaTick;
2789 MDTickType startTick, endTick;
2790 NSWindowController *cont = [[NSApp mainWindow] windowController];
2791
2792 if (startEditingRange < 0 || startEditingRange >= endEditingRange)
2793 return; /* Do nothing */
2794
2795 startTick = startEditingRange;
2796 endTick = endEditingRange;
2797
2798 /* Register undo for editing range */
2799 [[[self undoManager] prepareWithInvocationTarget:self]
2800 setEditingRangeStart:startTick end:endTick];
2801
2802 deltaTick = endTick - startTick;
2803 for (trackNo = [[self myMIDISequence] trackCount] - 1; trackNo >= 0; trackNo--) {
2804 MDTrack *track = [[self myMIDISequence] getTrackAtIndex:trackNo];
2805 MDPointer *pt;
2806 id psobj;
2807 int32_t n1, n2;
2808 if (![cont isFocusTrack:trackNo])
2809 continue;
2810
2811 /* Register undo for selection change */
2812 psobj = [self selectionOfTrack:trackNo];
2813 [[[self undoManager] prepareWithInvocationTarget: self]
2814 setSelection:psobj inTrack:trackNo sender:self];
2815
2816 /* Change track duration */
2817 [self changeTrackDuration:MDTrackGetDuration(track) + deltaTick ofTrack:trackNo];
2818
2819 /* Shift events */
2820 pt = MDPointerNew(track);
2821 MDPointerJumpToTick(pt, startTick);
2822 n1 = MDPointerGetPosition(pt);
2823 n2 = MDTrackGetNumberOfEvents(track) - n1;
2824 if (n2 > 0) {
2825 psobj = [[IntGroupObject allocWithZone:[self zone]] init];
2826 IntGroupAdd([psobj pointSet], n1, n2);
2827 [self modifyTick:[NSNumber numberWithLong:deltaTick] ofMultipleEventsAt:psobj inTrack:trackNo mode:MyDocumentModifyAdd destinationPositions:nil setSelection:NO];
2828 [psobj release];
2829 }
2830 MDPointerRelease(pt);
2831 }
2832
2833 /* Clear selection and select inserted blank time */
2834 [self unselectAllEventsInAllTracks:self];
2835 [self setEditingRangeStart:startTick end:endTick];
2836 }
2837
2838 - (IBAction)deleteSelectedTime:(id)sender
2839 {
2840 int32_t trackNo;
2841 MDTickType deltaTick;
2842 MDTickType startTick, endTick;
2843 NSWindowController *cont = [[NSApp mainWindow] windowController];
2844
2845 if (startEditingRange < 0 || startEditingRange >= endEditingRange)
2846 return; /* Do nothing */
2847
2848 startTick = startEditingRange;
2849 endTick = endEditingRange;
2850
2851 /* Register undo for editing range */
2852 [[[self undoManager] prepareWithInvocationTarget:self]
2853 setEditingRangeStart:startTick end:endTick];
2854
2855 deltaTick = endTick - startTick;
2856 for (trackNo = [[self myMIDISequence] trackCount] - 1; trackNo >= 0; trackNo--) {
2857 MDTrack *track = [[self myMIDISequence] getTrackAtIndex:trackNo];
2858 MDPointer *pt;
2859 id psobj;
2860 int32_t n1, n2;
2861 if (![cont isFocusTrack:trackNo])
2862 continue;
2863
2864 /* Register undo for selection change */
2865 psobj = [self selectionOfTrack:trackNo];
2866 [[[self undoManager] prepareWithInvocationTarget: self]
2867 setSelection:psobj inTrack:trackNo sender:self];
2868
2869 /* Remove events between startTick and endTick */
2870 pt = MDPointerNew(track);
2871 if (MDPointerJumpToTick(pt, startTick) && (n1 = MDPointerGetPosition(pt)) >= 0) {
2872 MDPointerJumpToTick(pt, endTick);
2873 n2 = MDPointerGetPosition(pt) - n1;
2874 psobj = [[IntGroupObject allocWithZone:[self zone]] init];
2875 if (n2 > 0) {
2876 IntGroupAdd([psobj pointSet], n1, n2);
2877 [self deleteMultipleEventsAt:psobj fromTrack:trackNo deletedEvents:NULL];
2878 }
2879
2880 /* Shift events after endTick */
2881 n2 = MDTrackGetNumberOfEvents(track) - n1;
2882 if (n2 > 0) {
2883 IntGroupClear([psobj pointSet]);
2884 IntGroupAdd([psobj pointSet], n1, n2);
2885 [self modifyTick:[NSNumber numberWithLong:-deltaTick] ofMultipleEventsAt:psobj inTrack:trackNo mode:MyDocumentModifyAdd destinationPositions:nil setSelection:NO];
2886 }
2887 [psobj release];
2888 }
2889 MDPointerRelease(pt);
2890
2891 /* Change track duration */
2892 [self changeTrackDuration:MDTrackGetDuration(track) - deltaTick ofTrack:trackNo];
2893 }
2894
2895 /* Clear selection and select the start tick */
2896 [self unselectAllEventsInAllTracks:self];
2897 [self setEditingRangeStart:startTick end:startTick];
2898 }
2899
2900 - (BOOL)scaleTimeFrom:(MDTickType)startTick to:(MDTickType)endTick newDuration:(MDTickType)newDuration insertTempo:(BOOL)insertTempo setSelection:(BOOL)setSelection
2901 {
2902 int32_t trackNo;
2903 MDTickType deltaTick;
2904 MDTrack *track;
2905 MDPointer *pt;
2906 MDEvent *ep;
2907 id psobj, dt;
2908 NSWindowController *cont = [[NSApp mainWindow] windowController];
2909
2910 if (startTick < 0 || startTick >= endTick)
2911 return NO; /* Do nothing */
2912
2913 /* Register undo for editing range */
2914 [[[self undoManager] prepareWithInvocationTarget:self]
2915 setEditingRangeStart:startEditingRange end:endEditingRange];
2916
2917 deltaTick = endTick - startTick;
2918
2919 /* Modify tempo if specified */
2920 if (insertTempo) {
2921
2922 /* Insert new Tempo events if not present at the borders */
2923 MDEventObject *newEvent;
2924 MDCalibrator *calib = [[self myMIDISequence] sharedCalibrator];
2925 float tempo;
2926 MDCalibratorJumpToTick(calib, startTick);
2927 tempo = MDCalibratorGetTempo(calib);
2928 ep = MDCalibratorGetEvent(calib, NULL, kMDEventTempo, -1);
2929 if (ep == NULL || MDGetTick(ep) != startTick) {
2930 newEvent = [[MDEventObject allocWithZone: [self zone]] init];
2931 ep = &(newEvent->event);
2932 MDSetTick(ep, startTick);
2933 MDSetKind(ep, kMDEventTempo);
2934 MDSetTempo(ep, tempo);
2935 [self insertEvent: newEvent toTrack: 0];
2936 [newEvent release];
2937 }
2938 MDCalibratorJumpToTick(calib, endTick);
2939 tempo = MDCalibratorGetTempo(calib);
2940 ep = MDCalibratorGetEvent(calib, NULL, kMDEventTempo, -1);
2941 if (ep == NULL || MDGetTick(ep) != endTick) {
2942 newEvent = [[MDEventObject allocWithZone: [self zone]] init];
2943 ep = &(newEvent->event);
2944 MDSetTick(ep, endTick);
2945 MDSetKind(ep, kMDEventTempo);
2946 MDSetTempo(ep, tempo);
2947 [self insertEvent: newEvent toTrack: 0];
2948 [newEvent release];
2949 }
2950 /* All tempo should be multiplied by ((double)newDuration)/(deltaTick) */
2951 track = [[self myMIDISequence] getTrackAtIndex:0];
2952 pt = MDPointerNew(track);
2953 MDPointerJumpToTick(pt, startTick);
2954 psobj = [[IntGroupObject allocWithZone:[