// // 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 & 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 & properties) { NSMutableArray * pa = [NSMutableArray arrayWithCapacity:properties.size()]; for (std::vector::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, ×)) [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, ×)) [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; iDelNote(measNo, tiedStart); song->AddNote(tiedNote, measNo, tiedStart); goto advanceAt; } else { // // Extend previous measure // note.fTied |= VLNote::kTiedWithPrev; } } else { for (NSEnumerator * le = [[ndict objectForKey:@"lyrics"] objectEnumerator]; NSDictionary * ldict = [le nextObject]; ) { VLSyllable syll; if (NSString * t = [ldict objectForKey:@"text"]) syll.fText = [t UTF8String]; syll.fKind = [[ldict objectForKey:@"kind"] intValue]; note.fLyrics.push_back(syll); } } tiedStart = at; tiedNote = note; song->AddNote(note, measNo, at); advanceAt: at += note.fDuration; } } - (void)readChords:(NSArray *)chords inMeasure:(size_t)measNo { VLFraction at(0); for (NSEnumerator * ce = [chords objectEnumerator]; NSDictionary * cdict = [ce nextObject]; ) { VLChord chord; chord.fDuration.fNum = [[cdict objectForKey:@"durNum"] intValue]; chord.fDuration.fDenom = [[cdict objectForKey:@"durDenom"] intValue]; chord.fPitch = [[cdict objectForKey:@"pitch"] intValue]; chord.fRootPitch = [[cdict objectForKey:@"root"] intValue]; chord.fSteps = [[cdict objectForKey:@"steps"] intValue]; song->AddChord(chord, measNo, at); at += chord.fDuration; } } - (void)readMeasuresFromPlist:(NSArray *)measures { std::vector repeatStack; size_t measNo = 0; for (NSEnumerator * me = [measures objectEnumerator]; NSDictionary * mdict = [me nextObject]; ++measNo ) { if (NSNumber * mNo = [mdict objectForKey:@"measure"]) measNo = static_cast([mNo intValue]); [self readMelody:[mdict objectForKey:@"melody"] inMeasure:measNo]; [self readChords:[mdict objectForKey:@"chords"] inMeasure:measNo]; if ([[mdict objectForKey:@"tocoda"] boolValue]) song->fGoToCoda = measNo; if ([[mdict objectForKey:@"coda"] boolValue]) song->fCoda = measNo; if (NSDictionary * beginRep = [mdict objectForKey:@"begin-repeat"]) { VLRepeat rep; VLRepeat::Ending ending(measNo, measNo, 0); rep.fTimes = [[beginRep objectForKey:@"times"] intValue]; rep.fEndings.push_back(ending); repeatStack.push_back(song->fRepeats.size()); song->fRepeats.push_back(rep); } if (NSDictionary * beginEnd = [mdict objectForKey:@"begin-ending"]) { VLRepeat & rep = song->fRepeats[repeatStack.back()]; VLRepeat::Ending ending(measNo, measNo, 0); ending.fVolta = [[beginEnd objectForKey:@"volta"] intValue]; rep.fEndings.push_back(ending); } if (NSDictionary * endEnd = [mdict objectForKey:@"end-ending"]) { VLRepeat & rep = song->fRepeats[repeatStack.back()]; VLRepeat::Ending & ending = rep.fEndings.back(); ending.fEnd = measNo+1; if (NSNumber * volta = [endEnd objectForKey:@"volta"]) ending.fVolta = [volta intValue]; while ((((1<fRepeats[repeatStack.back()]; if (NSNumber * times = [endRep objectForKey:@"times"]) rep.fTimes = [times intValue]; rep.fEndings[0].fEnd = measNo+1; rep.fEndings[0].fVolta = (1<fProperties.clear(); for (NSEnumerator * pe = [properties objectEnumerator]; NSDictionary * pdict = [pe nextObject]; ) { VLProperties prop; prop.fTime.fNum = [[pdict objectForKey:@"timeNum"] intValue]; prop.fTime.fDenom = [[pdict objectForKey:@"timeDenom"] intValue]; prop.fKey = [[pdict objectForKey:@"key"] intValue]; prop.fMode = [[pdict objectForKey:@"mode"] intValue]; prop.fDivisions = [[pdict objectForKey:@"divisions"] intValue]; song->fProperties.push_back(prop); } } - (BOOL)readFromPlist:(id)plist error:(NSError **)outError { song->clear(); [self setValue:[plist objectForKey:@"title"] forKey:@"songTitle"]; [self setValue:[plist objectForKey:@"composer"] forKey:@"songComposer"]; [self setValue:[plist objectForKey:@"lyricist"] forKey:@"songLyricist"]; [self setValue:[plist objectForKey:@"groove"] forKey:@"songGroove"]; [self setValue:[plist objectForKey:@"tempo"] forKey:@"songTempo"]; [self readPropertiesFromPlist:[plist objectForKey:@"properties"]]; [self readMeasuresFromPlist:[plist objectForKey:@"measures"]]; return YES; } - (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