VocalEasel/Sources/VLPListDocument.mm
2007-08-27 00:10:37 +00:00

271 lines
8.4 KiB
Plaintext

//
// File: VLPListDocument.h - Convert document from and to Cocoa plist
//
// Author(s):
//
// (MN) Matthias Neeracher
//
// Copyright © 2007 Matthias Neeracher
//
#import "VLPListDocument.h"
#import "VLModel.h"
//
// To convert from and to complex file formats, we use ruby scripts operating
// on the XML representation of a Cocoa property list. The property list
// representation is strictly intended as an intermediate representation,
// subject to change as necessary.
//
@implementation VLDocument (Plist)
class VLPlistVisitor : public VLSongVisitor {
public:
VLPlistVisitor(NSMutableDictionary * plist, bool performanceOrder)
: fPlist(plist), fPerfOrder(performanceOrder) {}
virtual void Visit(VLSong & song);
protected:
virtual void VisitMeasure(size_t m, VLProperties & p, VLMeasure & meas);
virtual void VisitNote(VLLyricsNote & n);
virtual void VisitChord(VLChord & c);
NSArray * EncodeProperties(const std::vector<VLProperties> & properties);
NSDictionary * EncodeProperties(const VLProperties & properties);
NSMutableDictionary * fPlist;
NSMutableArray * fMeasures;
NSMutableArray * fNotes;
NSMutableArray * fChords;
bool fPerfOrder;
const VLSong * fSong;
};
NSArray * VLPlistVisitor::EncodeProperties(const std::vector<VLProperties> & properties)
{
NSMutableArray * pa = [NSMutableArray arrayWithCapacity:properties.size()];
for (std::vector<VLProperties>::const_iterator i = properties.begin();
i != properties.end(); ++i)
[pa addObject:EncodeProperties(*i)];
return pa;
}
NSDictionary * VLPlistVisitor::EncodeProperties(const VLProperties & properties)
{
return [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt: properties.fTime.fNum], @"timeNum",
[NSNumber numberWithInt: properties.fTime.fDenom], @"timeDenom",
[NSNumber numberWithInt: properties.fKey], @"key",
[NSNumber numberWithInt: properties.fMode], @"mode",
[NSNumber numberWithInt: properties.fDivisions], @"divisions",
nil];
}
void VLPlistVisitor::Visit(VLSong & song)
{
fSong = &song;
fMeasures = [NSMutableArray arrayWithCapacity:32];
VisitMeasures(song, fPerfOrder);
[fPlist setObject:EncodeProperties(song.fProperties) forKey:@"properties"];
[fPlist setObject:fMeasures forKey:@"measures"];
}
void VLPlistVisitor::VisitMeasure(size_t m, VLProperties & p, VLMeasure & meas)
{
fNotes = [NSMutableArray arrayWithCapacity:1];
fChords= [NSMutableArray arrayWithCapacity:1];
VisitNotes(meas, p, true);
VisitChords(meas);
NSMutableDictionary * md =
[NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:m], @"measure",
[NSNumber numberWithInt:meas.fPropIdx], @"properties",
fNotes, @"melody", fChords, @"chords",
nil];
int times;
bool last;
size_t volta;
if (fSong->DoesBeginRepeat(m, &times))
[md setObject:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:times], @"times", nil]
forKey: @"begin-repeat"];
if (fSong->DoesBeginEnding(m, &last, &volta))
[md setObject:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:!last], @"last",
[NSNumber numberWithInt:volta], @"volta",
nil]
forKey: @"begin-ending"];
if (fSong->DoesEndRepeat(m+1, &times))
[md setObject:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:times], @"times", nil]
forKey: @"end-repeat"];
if (fSong->DoesEndEnding(m+1, &last, &volta))
[md setObject:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:!last], @"last",
[NSNumber numberWithInt:volta], @"volta",
nil]
forKey: @"end-ending"];
if (fSong->fGoToCoda == m)
[md setObject:[NSNumber numberWithBool:YES] forKey:@"tocoda"];
if (fSong->fCoda == m)
[md setObject:[NSNumber numberWithBool:YES] forKey:@"coda"];
[fMeasures addObject:md];
}
void VLPlistVisitor::VisitNote(VLLyricsNote & n)
{
NSMutableArray * ly = [NSMutableArray arrayWithCapacity:0];
for (size_t i = 0; i<n.fLyrics.size(); ++i)
[ly addObject:n.fLyrics[i].fText.size()
? [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithUTF8String:n.fLyrics[i].fText.c_str()], @"text",
[NSNumber numberWithInt:n.fLyrics[i].fKind], @"kind",
nil]
: [NSDictionary dictionary]];
NSDictionary * nd =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:n.fDuration.fNum], @"durNum",
[NSNumber numberWithInt:n.fDuration.fDenom], @"durDenom",
[NSNumber numberWithInt:n.fPitch], @"pitch",
[NSNumber numberWithInt:n.fTied], @"tied",
[NSNumber numberWithInt:n.fVisual], @"visual",
ly, @"lyrics",
nil];
[fNotes addObject:nd];
}
void VLPlistVisitor::VisitChord(VLChord & c)
{
NSDictionary * cd =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:c.fDuration.fNum], @"durNum",
[NSNumber numberWithInt:c.fDuration.fDenom], @"durDenom",
[NSNumber numberWithInt:c.fPitch], @"pitch",
[NSNumber numberWithInt:c.fSteps], @"steps",
[NSNumber numberWithInt:c.fRootPitch], @"root",
nil];
[fChords addObject: cd];
}
- (id)plistInPerformanceOrder:(BOOL)performanceOrder
{
NSMutableDictionary * plist =
[NSMutableDictionary dictionaryWithObjectsAndKeys:
songTitle, @"title",
songGroove, @"groove", songTempo, @"tempo",
songComposer, @"composer", songLyricist, @"lyricist",
[NSDate date], @"saved",
[NSString stringWithFormat:@"VocalEasel %@",
[[NSBundle mainBundle]
objectForInfoDictionaryKey:@"CFBundleVersion"]],
@"software",
nil];
VLPlistVisitor songWriter(plist, performanceOrder);
songWriter.Visit(*song);
return plist;
}
- (IBAction)dump:(id)sender
{
id plist = [self plistInPerformanceOrder:NO];
if ([sender tag])
plist = [[[NSString alloc] initWithData:
[NSPropertyListSerialization dataFromPropertyList:plist format:NSPropertyListXMLFormat_v1_0 errorDescription:nil]
encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"%@\n", plist);
}
- (BOOL)readFromPlist:(id)plist error:(NSError **)outError
{
return NO;
}
- (NSData *)runFilter:(NSString *)filterName withContents:(NSData *)contents
{
NSString * filterPath = [[NSBundle mainBundle] pathForResource:filterName
ofType:nil
inDirectory:@"Filters"];
NSPipe * filterInput = [NSPipe pipe];
NSPipe * filterOutput = [NSPipe pipe];
NSPipe * filterError = [NSPipe pipe];
NSTask * filterTask = [[NSTask alloc] init];
[filterTask setLaunchPath:filterPath];
[filterTask setStandardInput:filterInput];
[filterTask setStandardOutput:filterOutput];
[filterTask setStandardError:filterError];
[filterTask launch];
NSFileHandle * inputHandle = [filterInput fileHandleForWriting];
[inputHandle writeData:contents];
[inputHandle closeFile];
NSFileHandle * outputHandle = [filterOutput fileHandleForReading];
NSData * output = [outputHandle readDataToEndOfFile];
NSFileHandle * errorHandle = [filterError fileHandleForReading];
NSData * error = [errorHandle readDataToEndOfFile];
[filterTask waitUntilExit];
[filterTask release];
if ([error length])
[NSException raise:NSInvalidArgumentException
format:@"Filter %@: %@", filterName, error];
return output;
}
- (NSFileWrapper *)fileWrapperWithFilter:(NSString *)filterName
error:(NSError **)outError
{
NSBundle * mainBundle = [NSBundle mainBundle];
BOOL perfOrder = [mainBundle pathForResource:filterName
ofType:@"pwriter" inDirectory:@"Filters"] != nil;
filterName = [filterName stringByAppendingPathExtension:
perfOrder ? @"pwriter" : @"writer"];
id inPlist= [self plistInPerformanceOrder:perfOrder];
NSData * inData =
[NSPropertyListSerialization dataFromPropertyList:inPlist
format:NSPropertyListXMLFormat_v1_0
errorDescription:nil];
NSData * outData= [self runFilter:filterName withContents:inData];
return [[[NSFileWrapper alloc] initRegularFileWithContents:outData]
autorelease];
}
- (BOOL)readFromFileWrapper:(NSFileWrapper *)wrapper
withFilter:(NSString *)filterName
error:(NSError **)outError
{
filterName = [filterName stringByAppendingPathExtension:@"reader"];
NSData * inData = [wrapper regularFileContents];
NSData * outData = [self runFilter:filterName withContents:inData];
NSString*errString;
id outPlist =
[NSPropertyListSerialization propertyListFromData:outData
mutabilityOption:NSPropertyListImmutable
format:NULL errorDescription:&errString];
if (!outPlist)
return NO;
else
return [self readFromPlist:outPlist error:outError];
}
@end