mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 19:23:59 +00:00
461 lines
10 KiB
Plaintext
461 lines
10 KiB
Plaintext
//
|
|
// VLDocument.mm
|
|
// Vocalese
|
|
//
|
|
// Created by Matthias Neeracher on 12/17/05.
|
|
// Copyright __MyCompanyName__ 2005 . All rights reserved.
|
|
//
|
|
|
|
#import "VLDocument.h"
|
|
#import "VLXMLDocument.h"
|
|
#import "VLLilypondDocument.h"
|
|
#import "VLMMADocument.h"
|
|
#import "VLMIDIDocument.h"
|
|
#import "VLPDFWindow.h"
|
|
#import "VLLogWindow.h"
|
|
#import "VLSheetWindow.h"
|
|
#import "VLSoundOut.h"
|
|
|
|
@interface VLSongWrapper : NSObject {
|
|
VLSong * wrappedSong;
|
|
}
|
|
|
|
+ (VLSongWrapper *)wrapperWithSong:(VLSong *)song;
|
|
- (VLSong *)song;
|
|
|
|
@end
|
|
|
|
@implementation VLSongWrapper
|
|
|
|
- (id)initWithSong:(VLSong *)song
|
|
{
|
|
if (self = [super init])
|
|
wrappedSong = new VLSong(*song);
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
delete wrappedSong;
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
+ (VLSongWrapper *)wrapperWithSong:(VLSong *)song
|
|
{
|
|
return [[[VLSongWrapper alloc] initWithSong:song] autorelease];
|
|
}
|
|
|
|
- (VLSong *)song
|
|
{
|
|
return wrappedSong;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation VLDocument
|
|
|
|
- (id)init
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
song = new VLSong;
|
|
lilypondTemplate = @"default";
|
|
songTitle = @"";
|
|
songLyricist = @"";
|
|
songComposer = @"";
|
|
songArranger = @"";
|
|
songGroove = @"Swing";
|
|
songTempo = [[NSNumber numberWithInt:120] retain];
|
|
sheetWin = nil;
|
|
pdfWin = nil;
|
|
logWin = nil;
|
|
tmpPath = nil;
|
|
vcsWrapper = nil;
|
|
repeatVolta = 2;
|
|
brandNew = true;
|
|
[self setHasUndoManager:YES];
|
|
undo =
|
|
[[VLKeyValueUndo alloc] initWithOwner:self
|
|
keysAndNames: [NSDictionary dictionaryWithObjectsAndKeys:
|
|
@"", @"songTitle",
|
|
@"", @"songLyricist",
|
|
@"", @"songComposer",
|
|
@"", @"songArranger",
|
|
@"", @"songGroove",
|
|
@"", @"songTempo",
|
|
nil]];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
delete song;
|
|
|
|
[lilypondTemplate release];
|
|
[songTitle release];
|
|
[songLyricist release];
|
|
[songComposer release];
|
|
[songArranger release];
|
|
[vcsWrapper release];
|
|
[undo release];
|
|
|
|
if (tmpPath) {
|
|
[[NSFileManager defaultManager] removeFileAtPath:tmpPath handler:nil];
|
|
[tmpPath release];
|
|
}
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void)removeWindowController:(NSWindowController *)win
|
|
{
|
|
if (win == logWin)
|
|
logWin = nil;
|
|
else if (win == pdfWin)
|
|
pdfWin = nil;
|
|
else if (win == sheetWin)
|
|
sheetWin = nil;
|
|
|
|
[super removeWindowController:win];
|
|
}
|
|
|
|
- (VLLogWindow *)logWin
|
|
{
|
|
if (!logWin) {
|
|
logWin = [[VLLogWindow alloc] initWithWindowNibName: @"VLLogWindow"];
|
|
[self addWindowController: logWin];
|
|
[logWin release];
|
|
}
|
|
return logWin;
|
|
}
|
|
|
|
- (VLPDFWindow *)pdfWin
|
|
{
|
|
if (!pdfWin) {
|
|
pdfWin = [[VLPDFWindow alloc] initWithWindowNibName: @"VLPDFWindow"];
|
|
[self addWindowController: pdfWin];
|
|
[pdfWin release];
|
|
}
|
|
return pdfWin;
|
|
}
|
|
|
|
- (void)makeWindowControllers
|
|
{
|
|
sheetWin = [[VLSheetWindow alloc] initWithWindowNibName: @"VLDocument"];
|
|
[self addWindowController: sheetWin];
|
|
[sheetWin setShouldCloseDocument:YES];
|
|
[sheetWin release];
|
|
}
|
|
|
|
- (void)showWindows
|
|
{
|
|
[sheetWin showWindow: self];
|
|
if ([pdfWin isWindowLoaded])
|
|
[pdfWin showWindow: self];
|
|
if ([logWin isWindowLoaded])
|
|
[logWin showWindow: self];
|
|
}
|
|
|
|
- (VLSong *) song
|
|
{
|
|
return song;
|
|
}
|
|
|
|
- (NSNumber *) songKey
|
|
{
|
|
const VLProperties & prop = song->fProperties.front();
|
|
|
|
return [NSNumber numberWithInt: (prop.fKey << 8) | (prop.fMode & 0xFF)];
|
|
}
|
|
|
|
- (void) setKey:(int)key transpose:(BOOL)transpose
|
|
{
|
|
[self willChangeSong];
|
|
|
|
VLProperties & prop = song->fProperties.front();
|
|
|
|
if (transpose)
|
|
song->Transpose((7*((key>>8)-prop.fKey) % 12));
|
|
|
|
prop.fKey = key >> 8;
|
|
prop.fMode= key & 0xFF;
|
|
|
|
[self updateChangeCount:NSChangeDone];
|
|
}
|
|
|
|
- (NSNumber *) songTime
|
|
{
|
|
const VLProperties & prop = song->fProperties.front();
|
|
|
|
return [NSNumber numberWithInt: (prop.fTime.fNum << 8) | prop.fTime.fDenom];
|
|
}
|
|
|
|
- (void) setTimeNum:(int)num denom:(int)denom
|
|
{
|
|
[self willChangeSong];
|
|
|
|
VLProperties & prop = song->fProperties.front();
|
|
|
|
prop.fTime = VLFraction(num, denom);
|
|
|
|
[self updateChangeCount:NSChangeDone];
|
|
}
|
|
|
|
- (NSNumber *) songDivisions
|
|
{
|
|
const VLProperties & prop = song->fProperties.front();
|
|
|
|
return [NSNumber numberWithInt: prop.fDivisions];
|
|
}
|
|
|
|
- (void) setDivisions:(int)divisions
|
|
{
|
|
[self willChangeSong];
|
|
|
|
VLProperties & prop = song->fProperties.front();
|
|
|
|
prop.fDivisions = divisions;
|
|
|
|
[self updateChangeCount:NSChangeDone];
|
|
}
|
|
|
|
- (int) repeatVolta
|
|
{
|
|
return repeatVolta;
|
|
}
|
|
|
|
- (bool) brandNew
|
|
{
|
|
return brandNew && ![self isDocumentEdited];
|
|
}
|
|
|
|
- (void) setRepeatVolta:(int)volta
|
|
{
|
|
repeatVolta = volta;
|
|
}
|
|
|
|
- (NSString *) tmpPath
|
|
{
|
|
if (!tmpPath) {
|
|
tmpPath = [[NSString alloc] initWithFormat:@"/var/tmp/VocalEasel.%08x",
|
|
self];
|
|
[[NSFileManager defaultManager] createDirectoryAtPath:tmpPath attributes:nil];
|
|
}
|
|
return tmpPath;
|
|
}
|
|
|
|
- (NSString *) workPath
|
|
{
|
|
if ([self fileURL]) // Prefer our wrapper directory
|
|
return [[self fileURL] path];
|
|
else
|
|
return [self tmpPath];
|
|
}
|
|
|
|
- (NSString *) baseName
|
|
{
|
|
return [[[self workPath] lastPathComponent] stringByDeletingPathExtension];
|
|
}
|
|
|
|
- (NSURL *) fileURLWithExtension:(NSString*)extension
|
|
{
|
|
return [NSURL fileURLWithPath:
|
|
[[[self workPath] stringByAppendingPathComponent:[self baseName]]
|
|
stringByAppendingPathExtension:extension]];
|
|
}
|
|
|
|
- (BOOL)saveToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation error:(NSError **)outError
|
|
{
|
|
NSFileWrapper * preservedVCSWrapper = nil;
|
|
switch (saveOperation) {
|
|
case NSSaveToOperation:
|
|
case NSAutosaveOperation:
|
|
preservedVCSWrapper = vcsWrapper;
|
|
[preservedVCSWrapper retain];
|
|
// Fall through
|
|
case NSSaveAsOperation:
|
|
[vcsWrapper release];
|
|
vcsWrapper = nil;
|
|
// Fall through
|
|
case NSSaveOperation:
|
|
break;
|
|
}
|
|
BOOL res = [super saveToURL:absoluteURL ofType:typeName
|
|
forSaveOperation:saveOperation error:outError];
|
|
if (!vcsWrapper)
|
|
vcsWrapper = preservedVCSWrapper;
|
|
|
|
return res;
|
|
}
|
|
|
|
- (NSFileWrapper *)fileWrapperOfType:(NSString *)typeName error:(NSError **)outError
|
|
{
|
|
if ([typeName isEqual:@"VLNativeType"]) {
|
|
return [self XMLFileWrapperWithError:outError flat:NO];
|
|
} else if ([typeName isEqual:@"VLMusicXMLType"]) {
|
|
return [self XMLFileWrapperWithError:outError flat:YES];
|
|
} else if ([typeName isEqual:@"VLLilypondType"]) {
|
|
return [self lilypondFileWrapperWithError:outError];
|
|
} else if ([typeName isEqual:@"VLMMAType"]) {
|
|
return [self mmaFileWrapperWithError:outError];
|
|
} else if ([typeName isEqual:@"VLMIDIType"]) {
|
|
return [self midiFileWrapperWithError:outError];
|
|
} else {
|
|
if (outError)
|
|
*outError = [NSError errorWithDomain:NSCocoaErrorDomain
|
|
code:NSPersistentStoreInvalidTypeError
|
|
userInfo:nil];
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
- (BOOL)readFromFileWrapper:(NSFileWrapper *)wrapper ofType:(NSString *)typeName error:(NSError **)outError
|
|
{
|
|
brandNew = false;
|
|
//
|
|
// On opening a document, close all unchanged empty documents
|
|
//
|
|
NSEnumerator * docs = [[[NSDocumentController sharedDocumentController]
|
|
documents] objectEnumerator];
|
|
while (VLDocument * doc = [docs nextObject])
|
|
if ([doc brandNew])
|
|
[[doc windowControllers]
|
|
makeObjectsPerformSelector:@selector(close)];
|
|
|
|
if ([typeName isEqual:@"VLNativeType"]) {
|
|
return [self readFromXMLFileWrapper:wrapper error:outError];
|
|
} else {
|
|
if (outError)
|
|
*outError = [NSError errorWithDomain:NSCocoaErrorDomain
|
|
code:NSPersistentStoreInvalidTypeError
|
|
userInfo:nil];
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
- (void) changedFileWrapper
|
|
{
|
|
if (NSURL * url = [self fileURL])
|
|
if (NSDate * modDate =
|
|
[[[NSFileManager defaultManager] fileAttributesAtPath:[url path]
|
|
traverseLink:YES]
|
|
objectForKey:NSFileModificationDate])
|
|
[self setFileModificationDate:modDate];
|
|
}
|
|
|
|
- (NSTask *) taskWithLaunchPath:(NSString *)launch arguments:(NSArray *)args;
|
|
{
|
|
NSTask * task = [[NSTask alloc] init];
|
|
NSString * path = [self workPath];
|
|
NSPipe * pipe = [NSPipe pipe];
|
|
|
|
[task setCurrentDirectoryPath: path];
|
|
[task setStandardOutput: pipe];
|
|
[task setStandardError: pipe];
|
|
[task setArguments: args];
|
|
[task setLaunchPath: launch];
|
|
|
|
[[self logWin] showWindow: self];
|
|
|
|
[NSThread detachNewThreadSelector:@selector(logFromFileHandle:) toTarget:logWin
|
|
withObject:[pipe fileHandleForReading]];
|
|
|
|
return task;
|
|
}
|
|
|
|
- (IBAction) engrave:(id)sender
|
|
{
|
|
NSString * base = [self baseName];
|
|
NSBundle * mainBundle = [NSBundle mainBundle];
|
|
|
|
//
|
|
// Convert to Lilypond format
|
|
//
|
|
NSError * err;
|
|
[self writeToURL:[self fileURLWithExtension:@"ly"]
|
|
ofType:@"VLLilypondType" error:&err];
|
|
NSString * launch =
|
|
[mainBundle pathForResource:@"lilyWrapper" ofType:@""
|
|
inDirectory:@"bin"];
|
|
NSString * tool =
|
|
[[NSUserDefaults standardUserDefaults]
|
|
stringForKey:@"VLLilypondPath"];
|
|
NSArray * args = [NSArray arrayWithObjects:tool, base, nil];
|
|
NSTask * task = [self taskWithLaunchPath:launch arguments:args];
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserver:self selector:@selector(engraveDone:)
|
|
name:NSTaskDidTerminateNotification object:task];
|
|
|
|
[task launch];
|
|
}
|
|
|
|
- (void)engraveDone:(NSNotification *)notification {
|
|
[[NSNotificationCenter defaultCenter] removeObserver: self];
|
|
int status = [[notification object] terminationStatus];
|
|
if (!status) {
|
|
[[self pdfWin] showWindow: self];
|
|
[pdfWin reloadPDF];
|
|
} else {
|
|
NSBeep();
|
|
}
|
|
[self changedFileWrapper];
|
|
}
|
|
|
|
- (IBAction) play:(id)sender
|
|
{
|
|
NSError * err;
|
|
[self writeToURL:[self fileURLWithExtension:@"mid"]
|
|
ofType:@"VLMIDIType" error:&err];
|
|
[self changedFileWrapper];
|
|
VLSoundOut::Instance()->PlayFile(
|
|
CFDataRef([NSData dataWithContentsOfURL:
|
|
[self fileURLWithExtension:@"mid"]]));
|
|
}
|
|
|
|
- (IBAction) stop:(id)sender
|
|
{
|
|
VLSoundOut::Instance()->Stop();
|
|
}
|
|
|
|
- (IBAction) showOutput:(id)sender
|
|
{
|
|
[[self pdfWin] showWindow:sender];
|
|
}
|
|
|
|
- (IBAction) showLog:(id)sender
|
|
{
|
|
[[self logWin] showWindow:sender];
|
|
}
|
|
|
|
- (void) willChangeSong
|
|
{
|
|
[self willChangeValueForKey:@"song"];
|
|
[[self undoManager] registerUndoWithTarget:self
|
|
selector:@selector(restoreSong:)
|
|
object:[VLSongWrapper wrapperWithSong:song]];
|
|
}
|
|
|
|
- (void) didChangeSong
|
|
{
|
|
[self didChangeValueForKey:@"song"];
|
|
[self updateChangeCount:NSChangeDone];
|
|
}
|
|
|
|
- (void) restoreSong:(VLSongWrapper *)savedSong
|
|
{
|
|
[self willChangeSong];
|
|
[self willChangeValueForKey:@"songKey"];
|
|
[self willChangeValueForKey:@"songTime"];
|
|
[self willChangeValueForKey:@"songDivisions"];
|
|
song->swap(*[savedSong song]);
|
|
[self didChangeValueForKey:@"songKey"];
|
|
[self didChangeValueForKey:@"songTime"];
|
|
[self didChangeValueForKey:@"songDivisions"];
|
|
[self didChangeSong];
|
|
}
|
|
|
|
@end
|