diff --git a/Sources/VLDocument.h b/Sources/VLDocument.h index 8a8522a..4ebbced 100644 --- a/Sources/VLDocument.h +++ b/Sources/VLDocument.h @@ -41,7 +41,7 @@ enum { NSString * songComposer; NSString * songArranger; NSString * songGroove; - NSNumber * songTempo; + float songTempo; float chordSize; float lyricSize; float staffSize; @@ -65,6 +65,7 @@ enum { float baseTempo; } +@property (nonatomic) float songTempo; @property (nonatomic) int playElements; - (VLSong *) song; @@ -83,12 +84,8 @@ enum { - (void) setRepeatVolta:(int)repeatVolta; -- (IBAction) play:(id)sender; -- (IBAction) stop:(id)sender; -- (IBAction) playStop:(id)sender; -- (IBAction) playMusic:(id)sender; -- (IBAction) adjustTempo:(id)sender; - +- (void) playSong; + - (NSURL *) tmpURL; - (NSURL *) workURL; - (NSString *) baseName; diff --git a/Sources/VLDocument.mm b/Sources/VLDocument.mm index 26efbfe..b76c5e7 100644 --- a/Sources/VLDocument.mm +++ b/Sources/VLDocument.mm @@ -68,7 +68,7 @@ @implementation VLDocument -@synthesize playElements; +@synthesize songTempo, playElements; + (BOOL)autosavesInPlace { @@ -87,7 +87,7 @@ songArranger = @""; songGroove = @"Swing"; playElements = kVLPlayAccompaniment|kVLPlayMelody|kVLPlayCountIn; - songTempo = [[NSNumber alloc] initWithInt:120]; + songTempo = 120.0f; baseTempo = 120.0f; chordSize = 6.0f; lyricSize = 0.0f; @@ -142,7 +142,7 @@ - (void) dealloc { - VLSoundOut::Instance()->Stop(); + VLSoundOut::Instance()->Stop(false); delete song; @@ -237,13 +237,12 @@ [self didChangeSong]; } -- (void) setSongTempo:(int)tempo +- (void) setSongTempo:(float)tempo { - if (tempo == [songTempo intValue]) + if (tempo == songTempo) return; - [songTempo autorelease]; - songTempo = [[NSNumber numberWithInt:tempo] retain]; + songTempo = tempo; if (VLSoundOut::Instance()->Playing()) VLSoundOut::Instance()->SetPlayRate(playRate*tempo/baseTempo); } @@ -466,7 +465,7 @@ return task; } -- (IBAction) play:(id)sender +- (void) playSong { if (hasMusicSequence) { VLSoundOut::Instance()->PlaySequence(NULL); @@ -492,7 +491,7 @@ hasMusicSequence = true; [sheetWin willPlaySequence:music]; - baseTempo = [songTempo floatValue]; + baseTempo = songTempo; VLSoundOut::Instance()->SetPlayRate(playRate); VLSoundOut::Instance()->PlaySequence(music); } @@ -507,7 +506,7 @@ songGroove = groove; previewRange = sections; playElements |= kVLPlayGroovePreview; - [self play:groove]; + [self playSong]; playElements &= ~kVLPlayGroovePreview; songGroove = savedGroove; [validTmpFiles removeObjectForKey:@"mma"]; @@ -515,49 +514,6 @@ hasMusicSequence = false; } -- (IBAction) stop:(id)sender -{ - VLSoundOut::Instance()->Stop(); -} - -- (IBAction) playStop:(id)sender -{ - if (VLSoundOut::Instance()->Playing()) { - [self stop:sender]; - if ([sender isKindOfClass:[NSMenuItem class]]) { - [sender setTitle:@"Play"]; - } else { - [sender setLabel:@"Play"]; - [sender setImage:[NSImage imageNamed:@"play.icns"]]; - } - } else { - [self play:sender]; - if ([sender isKindOfClass:[NSMenuItem class]]) { - [sender setTitle:@"Stop"]; - } else { - [sender setLabel:@"Stop"]; - [sender setImage:[NSImage imageNamed:@"stop.icns"]]; - } - } -} - -- (IBAction) playMusic:(id)sender -{ - switch ([sender tag]) { - case 1: // Fwd - VLSoundOut::Instance()->Fwd(); - break; - case -1: // Rew - VLSoundOut::Instance()->Bck(); - break; - } -} - -- (IBAction) adjustTempo:(id)sender -{ - [self setSongTempo:[songTempo intValue]+[sender tag]]; -} - - (NSPrintOperation *)printOperationWithSettings:(NSDictionary *)printSettings error:(NSError **)outError { diff --git a/Sources/VLGrooveController.h b/Sources/VLGrooveController.h index 985b351..5eb8b1c 100644 --- a/Sources/VLGrooveController.h +++ b/Sources/VLGrooveController.h @@ -11,7 +11,7 @@ #import @class VLSheetView; -@class VLDocument; +@class VLSheetWindow; @interface VLGrooveController : NSWindowController { NSDictionary * fGrooves; @@ -24,7 +24,7 @@ NSArray * fSubStyleList; NSPredicate * fSubStyleFilter; VLSheetView * fView; - VLDocument * fDocument; + VLSheetWindow * fSheetWin; } - (id) initWithSheetView:(VLSheetView *)view; diff --git a/Sources/VLGrooveController.mm b/Sources/VLGrooveController.mm index 36e73dc..a55f683 100644 --- a/Sources/VLGrooveController.mm +++ b/Sources/VLGrooveController.mm @@ -5,12 +5,12 @@ // // (MN) Matthias Neeracher // -// Copyright © 2007 Matthias Neeracher +// Copyright © 2007-2011 Matthias Neeracher // #import "VLGrooveController.h" #import "VLSheetView.h" -#import "VLDocument.h" +#import "VLSheetWindow.h" @implementation VLGrooveController @@ -24,7 +24,7 @@ @"!(SELF matches[c] '.*(Intro|End)\\\\d*$')"] retain]; fView = view; - fDocument = [view document]; + fSheetWin = (VLSheetWindow *)[view window]; [NSApp beginSheet: [self window] modalForWindow: [view window] @@ -46,7 +46,7 @@ if ([sender state]) [fView playWithGroove:[[fBrowser selectedCellInColumn:1] stringValue]]; else - [fDocument stop:sender]; + [fSheetWin stop:self]; } - (IBAction)endSheet:(id)sender @@ -56,7 +56,7 @@ - (void)didEndSheet:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { - [fDocument stop:self]; + [fSheetWin stop:self]; if (returnCode == NSAlertFirstButtonReturn) [fView setGroove:[[fBrowser selectedCellInColumn:1] stringValue]]; @@ -119,7 +119,7 @@ [fSubStyles objectForKey:@".DESC"], [fSubStyles objectForKey: [[fBrowser selectedCellInColumn:1] stringValue]]]]; - [fDocument stop:self]; + [fSheetWin stop:self]; [self togglePlay:fPlayButton]; } else [fDescription setStringValue:[fSubStyles objectForKey:@".DESC"]]; diff --git a/Sources/VLMMADocument.mm b/Sources/VLMMADocument.mm index 3114aff..b004f1d 100644 --- a/Sources/VLMMADocument.mm +++ b/Sources/VLMMADocument.mm @@ -26,7 +26,7 @@ + (const char *)[[bndl objectForInfoDictionaryKey:@"CFBundleVersion"] UTF8String] + "\n\n"; - sprintf(buf, "Tempo %d\n", [songTempo intValue]); + sprintf(buf, "Tempo %ld\n", lround(songTempo)); mmaFile += buf; if (playElements & kVLPlayGroovePreview) { // diff --git a/Sources/VLPListDocument.mm b/Sources/VLPListDocument.mm index 2f81974..0cdc0a7 100644 --- a/Sources/VLPListDocument.mm +++ b/Sources/VLPListDocument.mm @@ -243,7 +243,7 @@ void VLPlistVisitor::VisitChord(VLChord & c) { NSMutableDictionary * plist = [NSMutableDictionary dictionaryWithObjectsAndKeys: - songTitle, @"title", songTempo, @"tempo", + songTitle, @"title", [NSNumber numberWithLong:lround(songTempo)], @"tempo", [NSString stringWithUTF8String:song->PrimaryGroove().c_str()], @"groove", songComposer, @"composer", songLyricist, @"lyricist", [NSDate date], @"saved", diff --git a/Sources/VLSheetView.h b/Sources/VLSheetView.h index e0235c7..d512a66 100644 --- a/Sources/VLSheetView.h +++ b/Sources/VLSheetView.h @@ -120,6 +120,7 @@ enum VLCursorVisual { - (IBAction) editDisplayOptions:(id)sender; - (IBAction) transposeOctave:(id)sender; + - (VLDocument *) document; - (VLSong *) song; - (NSImage *) musicElement:(VLMusicElement)elt; diff --git a/Sources/VLSheetWindow.h b/Sources/VLSheetWindow.h index 7a9c754..92f6b89 100644 --- a/Sources/VLSheetWindow.h +++ b/Sources/VLSheetWindow.h @@ -33,6 +33,8 @@ IBOutlet NSProgressIndicator * progressIndicator; IBOutlet VLLogWindow * logWin; IBOutlet VLPDFWindow * pdfWin; + id soundStartObserver; + id soundStopObserver; VLEditable * editTarget; @@ -46,6 +48,10 @@ - (IBAction) togglePlayElements:(id)sender; - (IBAction) showOutput:(id)sender; +- (IBAction) stop:(id)sender; +- (IBAction) playStop:(id)sender; +- (IBAction) playMusic:(id)sender; +- (IBAction) adjustTempo:(id)sender; - (VLEditable *) editTarget; - (void) setEditTarget:(VLEditable *)editable; diff --git a/Sources/VLSheetWindow.mm b/Sources/VLSheetWindow.mm index 6619f90..53f5774 100644 --- a/Sources/VLSheetWindow.mm +++ b/Sources/VLSheetWindow.mm @@ -12,6 +12,7 @@ #import "VLDocument.h" #import "VLPDFWindow.h" #import "VLLogWindow.h" +#import "VLSoundOut.h" @implementation VLEditable @@ -51,10 +52,27 @@ { if (self = [super initWithWindow:window]) { editTarget = nil; + NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; + NSOperationQueue * oq = [NSOperationQueue mainQueue]; + soundStartObserver = [nc addObserverForName:(NSString*)kVLSoundStartedNotification + object:nil queue:oq usingBlock:^(NSNotification *note) { + [[[self window] contentView] setNeedsDisplay:YES]; + }]; + soundStopObserver = [nc addObserverForName:(NSString*)kVLSoundStoppedNotification + object:nil queue:oq usingBlock:^(NSNotification *note) { + [[[self window] contentView] setNeedsDisplay:YES]; + }]; } return self; } +- (void) dealloc +{ + NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; + [nc removeObserver:soundStartObserver]; + [nc removeObserver:soundStopObserver]; +} + - (VLEditable *)editTarget { return editTarget; @@ -100,11 +118,25 @@ [[self document] setPlayElements:[[self document] playElements] ^ [sender tag]]; } -- (BOOL) validateMenuItem:(NSMenuItem *)menuItem +- (BOOL) validateUserInterfaceItem:(id )item { - if ([menuItem action] == @selector(togglePlayElements:)) - if (int tag = [menuItem tag]) + if ([item action] == @selector(togglePlayElements:)) { + NSMenuItem * menuItem = (NSMenuItem *)item; + if (int tag = [item tag]) [menuItem setState:([[self document] playElements] & tag) != 0]; + } else if ([item action] == @selector(playStop:)) { + NSMenuItem * menuItem = [(NSObject *)item isKindOfClass:[NSMenuItem class]] ? (NSMenuItem *)item : nil; + NSToolbarItem * toolItem = menuItem ? nil : (NSToolbarItem *)item; + if (VLSoundOut::Instance()->Playing()) { + [menuItem setTitle:@"Stop"]; + [toolItem setLabel:@"Stop"]; + [toolItem setImage:[NSImage imageNamed:@"stop.icns"]]; + } else { + [menuItem setTitle:@"Play"]; + [toolItem setLabel:@"Play"]; + [toolItem setImage:[NSImage imageNamed:@"play.icns"]]; + } + } return YES; } @@ -127,4 +159,35 @@ [pdfWin reloadPDF]; } +- (IBAction) stop:(id)sender +{ + VLSoundOut::Instance()->Stop(); +} + +- (IBAction) playStop:(id)sender +{ + if (VLSoundOut::Instance()->Playing()) + [self stop:sender]; + else + [[self document] playSong]; +} + +- (IBAction) playMusic:(id)sender +{ + switch ([sender tag]) { + case 1: // Fwd + VLSoundOut::Instance()->Fwd(); + break; + case -1: // Rew + VLSoundOut::Instance()->Bck(); + break; + } +} + +- (IBAction) adjustTempo:(id)sender +{ + [[self document] setSongTempo:[[self document] songTempo]+[sender tag]]; +} + + @end diff --git a/Sources/VLSoundOut.cpp b/Sources/VLSoundOut.cpp index f18ef17..18f508e 100644 --- a/Sources/VLSoundOut.cpp +++ b/Sources/VLSoundOut.cpp @@ -5,7 +5,7 @@ // // (MN) Matthias Neeracher // -// Copyright © 2005-2007 Matthias Neeracher +// Copyright © 2005-2011 Matthias Neeracher // #include "VLSoundOut.h" @@ -21,6 +21,9 @@ #define R(x) if (OSStatus r = (x)) fprintf(stderr, "%s -> %d\n", #x, r); +CFStringRef kVLSoundStartedNotification = CFSTR("VLSoundStarted"); +CFStringRef kVLSoundStoppedNotification = CFSTR("VLSoundStopped"); + class VLAUSoundOut : public VLSoundOut { public: VLAUSoundOut(); @@ -36,23 +39,25 @@ public: virtual void Bck(); virtual ~VLAUSoundOut(); + void PollMusic(); protected: VLAUSoundOut(bool fileOutput); void InitSoundOutput(bool fileOutput); virtual void SetupOutput(AUNode outputNode); - MusicTimeStamp SequenceLength(MusicSequence music); + MusicTimeStamp SequenceLength(MusicSequence music); void SkipTimeInterval(); - AUGraph fGraph; - MusicPlayer fPlayer; + AUGraph fGraph; + MusicPlayer fPlayer; private: - MusicSequence fMusic; - MusicTimeStamp fMusicLength; - bool fRunning; - bool fForward; + MusicSequence fMusic; + MusicTimeStamp fMusicLength; + bool fRunning; + bool fForward; + dispatch_source_t fMusicPoll; - void Play(const int8_t * note, size_t numNotes = 1); + void Play(const int8_t * note, size_t numNotes = 1); }; class VLAUFileSoundOut : public VLAUSoundOut { @@ -119,6 +124,12 @@ VLAUSoundOut::VLAUSoundOut() : fMusic(0), fRunning(false), fForward(true) { InitSoundOutput(false); + fMusicPoll = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); + dispatch_source_set_event_handler(fMusicPoll, ^{ + this->PollMusic(); + }); + dispatch_source_set_timer(fMusicPoll, DISPATCH_TIME_FOREVER, INT64_MAX, 1000*NSEC_PER_USEC); + dispatch_resume(fMusicPoll); } VLAUSoundOut::VLAUSoundOut(bool) @@ -128,8 +139,9 @@ VLAUSoundOut::VLAUSoundOut(bool) VLAUSoundOut::~VLAUSoundOut() { - DisposeMusicPlayer(fPlayer); Stop(false); + dispatch_release(fMusicPoll); + DisposeMusicPlayer(fPlayer); DisposeAUGraph(fGraph); } @@ -205,6 +217,9 @@ void VLAUSoundOut::PlaySequence(MusicSequence music) R(MusicPlayerStart(fPlayer)); fRunning = true; + CFNotificationCenterPostNotification(CFNotificationCenterGetLocalCenter(), kVLSoundStartedNotification, + NULL, NULL, false); + dispatch_source_set_timer(fMusicPoll, DISPATCH_TIME_NOW, 500*NSEC_PER_MSEC, 200*NSEC_PER_MSEC); } void VLAUSoundOut::SetPlayRate(float rate) @@ -266,6 +281,9 @@ void VLAUSoundOut::Stop(bool pause) DisposeMusicSequence(fMusic); fMusic = 0; } + CFNotificationCenterPostNotification(CFNotificationCenterGetLocalCenter(), kVLSoundStoppedNotification, + NULL, NULL, false); + dispatch_source_set_timer(fMusicPoll, DISPATCH_TIME_FOREVER, INT64_MAX, 200*NSEC_PER_MSEC); } bool VLAUSoundOut::Playing() @@ -280,6 +298,14 @@ bool VLAUSoundOut::AtEnd() return !MusicPlayerGetTime(fPlayer, &time) && time >= fMusicLength; } +void VLAUSoundOut::PollMusic() +{ + if (fRunning && AtEnd()) { + MusicPlayerSetTime(fPlayer, 0); + Stop(true); + } +} + void VLAUSoundOut::PlayNote(const VLNote & note) { Play(¬e.fPitch); diff --git a/Sources/VLSoundOut.h b/Sources/VLSoundOut.h index e683cbc..45f1a0a 100644 --- a/Sources/VLSoundOut.h +++ b/Sources/VLSoundOut.h @@ -5,13 +5,16 @@ // // (MN) Matthias Neeracher // -// Copyright © 2005-2008 Matthias Neeracher +// Copyright © 2005-2011 Matthias Neeracher // #include "VLModel.h" #import #include +extern CFStringRef kVLSoundStartedNotification; +extern CFStringRef kVLSoundStoppedNotification; + class VLSoundEvent { protected: VLSoundEvent() {}