mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 11:14:00 +00:00
667 lines
22 KiB
Plaintext
667 lines
22 KiB
Plaintext
//
|
|
// File: VLPListDocument.h - Convert document from and to Cocoa plist
|
|
//
|
|
// Author(s):
|
|
//
|
|
// (MN) Matthias Neeracher
|
|
//
|
|
// Copyright © 2007-2018 Matthias Neeracher
|
|
//
|
|
|
|
#import "VLPListDocument.h"
|
|
#import "VLModel.h"
|
|
#import "VLPitchGrid.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) {}
|
|
|
|
void Visit(VLSong & song) override;
|
|
protected:
|
|
void VisitMeasure(uint32_t m, VLProperties & p, VLMeasure & meas) override;
|
|
void VisitNote(VLLyricsNote & n) override;
|
|
void VisitChord(VLChord & c) override;
|
|
|
|
NSArray * EncodeProperties(const std::vector<VLProperties> & properties);
|
|
NSDictionary * EncodeProperties(const VLProperties & properties);
|
|
|
|
NSMutableDictionary * fPlist;
|
|
NSMutableArray * fMeasures;
|
|
NSMutableArray * fNotes;
|
|
NSMutableArray * fChords;
|
|
NSMutableArray * fNotesInTuplet;
|
|
bool fPerfOrder;
|
|
const VLSong * fSong;
|
|
VLVisualFilter fVisFilter;
|
|
int fInTuplet;
|
|
uint16_t fTuplet;
|
|
uint16_t fTupletNote;
|
|
VLFraction fTupletDur;
|
|
};
|
|
|
|
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",
|
|
[NSString stringWithUTF8String:properties.fGroove.c_str()], @"groove",
|
|
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(uint32_t m, VLProperties & p, VLMeasure & meas)
|
|
{
|
|
fNotes = [NSMutableArray arrayWithCapacity:1];
|
|
fChords= [NSMutableArray arrayWithCapacity:1];
|
|
|
|
fVisFilter.ResetWithKey(p.fKey);
|
|
fInTuplet = 0;
|
|
VisitNotes(meas, p, true);
|
|
VisitChords(meas);
|
|
if (fInTuplet)
|
|
[[fNotesInTuplet lastObject] setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
|
|
|
|
NSMutableDictionary * md =
|
|
[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithUnsignedInt: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 numberWithUnsignedInt: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 numberWithUnsignedInt:volta], @"volta",
|
|
nil]
|
|
forKey: @"end-ending"];
|
|
if (fSong->fGoToCoda == m+1)
|
|
[md setObject:[NSNumber numberWithBool:YES] forKey:@"tocoda"];
|
|
if (fSong->fCoda == m)
|
|
[md setObject:[NSNumber numberWithBool:YES] forKey:@"coda"];
|
|
if (meas.fBreak & VLMeasure::kNewSystem)
|
|
[md setObject:[NSNumber numberWithBool:YES] forKey:@"new-system"];
|
|
if (meas.fBreak & VLMeasure::kNewPage)
|
|
[md setObject:[NSNumber numberWithBool:YES] forKey:@"new-page"];
|
|
[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]];
|
|
|
|
int grid = n.fPitch==VLNote::kNoPitch ? 0 : VLPitchToGrid(n.fPitch, n.fVisual, 0);
|
|
NSMutableDictionary * nd =
|
|
[NSMutableDictionary 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:fVisFilter(grid, n.fVisual)], @"visual",
|
|
ly, @"lyrics",
|
|
nil];
|
|
if (uint16_t newTuplet = n.fVisual & VLNote::kTupletMask) {
|
|
if (!fInTuplet || newTuplet != fTuplet) {
|
|
if (fInTuplet) {
|
|
[[fNotesInTuplet lastObject] setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
|
|
fInTuplet = 0;
|
|
}
|
|
fNotesInTuplet = [NSMutableArray array];
|
|
fTuplet = newTuplet;
|
|
fTupletNote = n.fVisual & VLNote::kNoteHeadMask;
|
|
fTupletDur = 0;
|
|
[nd setObject:[NSNumber numberWithInt:1] forKey:@"tuplet"];
|
|
}
|
|
int tupletNum = VLNote::TupletNum(fTuplet);
|
|
int tupletDenom = VLNote::TupletDenom(fTuplet);
|
|
[nd setObject:[NSNumber numberWithInt:tupletNum] forKey:@"actualNotes"];
|
|
[nd setObject:[NSNumber numberWithInt:tupletDenom] forKey:@"normalNotes"];
|
|
++fInTuplet;
|
|
fTupletDur += n.fDuration;
|
|
if (fTuplet == VLNote::kTriplet) {
|
|
uint16_t newNote = n.fVisual & VLNote::kNoteHeadMask;
|
|
if (newNote == fTupletNote) {
|
|
if (fInTuplet == 3)
|
|
fInTuplet = 0;
|
|
} else if (fInTuplet == 2 && newNote == fTupletNote-1) {
|
|
//
|
|
// 8th, 4th triplet
|
|
//
|
|
[nd setObject:[NSNumber numberWithInt:fTupletNote] forKey:@"normalType"];
|
|
fInTuplet = 0;
|
|
} else if (fInTuplet == 3 && newNote == fTupletNote-1) {
|
|
//
|
|
// 8th 8th 4th
|
|
//
|
|
for (NSMutableDictionary * prevNote in fNotesInTuplet) {
|
|
[prevNote setObject:[NSNumber numberWithInt:newNote] forKey:@"normalType"];
|
|
}
|
|
fInTuplet = 2;
|
|
fTupletNote = newNote;
|
|
} else {
|
|
//
|
|
// 4th 4th 8th
|
|
//
|
|
[nd setObject:[NSNumber numberWithInt:fTupletNote] forKey:@"normalType"];
|
|
if (fTupletDur.IsPowerOfTwo())
|
|
fInTuplet = 0;
|
|
}
|
|
} else if (fInTuplet == tupletNum)
|
|
fInTuplet = 0;
|
|
if (!fInTuplet)
|
|
[nd setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
|
|
else
|
|
[fNotesInTuplet addObject:nd];
|
|
} else if (fInTuplet) {
|
|
[[fNotesInTuplet lastObject] setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
|
|
fInTuplet = 0;
|
|
}
|
|
[fNotes addObject:nd];
|
|
}
|
|
|
|
void VLPlistVisitor::VisitChord(VLChord & c)
|
|
{
|
|
int pitchGrid =
|
|
c.fPitch==VLNote::kNoPitch ? 0 : VLPitchToGrid(c.fPitch, c.fVisual, 0);
|
|
VLVisualFilter pitchFilter(0);
|
|
int rootGrid =
|
|
c.fRootPitch==VLNote::kNoPitch ? 0 : VLPitchToGrid(c.fRootPitch, c.fRootAccidental, 0);
|
|
VLVisualFilter rootFilter(0);
|
|
NSDictionary * cd =
|
|
[NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithInt:c.fDuration.fNum], @"durNum",
|
|
[NSNumber numberWithInt:c.fDuration.fDenom], @"durDenom",
|
|
[NSNumber numberWithInt:c.fPitch], @"pitch",
|
|
[NSNumber numberWithInt:pitchFilter(pitchGrid, c.fVisual)], @"visual",
|
|
[NSNumber numberWithInt:c.fSteps], @"steps",
|
|
[NSNumber numberWithInt:c.fRootPitch], @"root",
|
|
[NSNumber numberWithInt:rootFilter(rootGrid, c.fRootAccidental)], @"rootvisual",
|
|
nil];
|
|
[fChords addObject: cd];
|
|
}
|
|
|
|
- (id)plistInPerformanceOrder:(BOOL)performanceOrder
|
|
{
|
|
NSMutableDictionary * plist =
|
|
[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
songTitle, @"title", [NSNumber numberWithLong:lround(songTempo)], @"tempo",
|
|
[NSString stringWithUTF8String:song->PrimaryGroove().c_str()], @"groove",
|
|
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];
|
|
switch ([sender tag]) {
|
|
case 0:
|
|
//
|
|
// Dump as plist
|
|
//
|
|
NSLog(@"\n%@\n", plist);
|
|
break;
|
|
case 1:
|
|
//
|
|
// Dump as XML
|
|
//
|
|
plist = [[[NSString alloc] initWithData:
|
|
[NSPropertyListSerialization dataFromPropertyList:plist
|
|
format:NSPropertyListXMLFormat_v1_0 errorDescription:nil]
|
|
encoding:NSUTF8StringEncoding] autorelease];
|
|
NSLog(@"\n%@\n", plist);
|
|
break;
|
|
case 2:
|
|
//
|
|
// Dump after roundtrip
|
|
//
|
|
[self readFromPlist:plist error:nil];
|
|
plist = [self plistInPerformanceOrder:NO];
|
|
NSLog(@"\n%@\n", plist);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We try to keep the number of divisions as small as possible, so we keep track
|
|
// of all note onsets per quarter note. In addition, we keep track of potential
|
|
// swing 8ths [0, 1/8]->[0,1/6] and
|
|
// swing 16ths [0,1/16]->[0,1/12] [1/8,3/16]->[1/8,1/6]
|
|
// so we can recognize swing songs containing triplets and note them with 3 (6)
|
|
// divisions instead of 6 (12)
|
|
//
|
|
enum {
|
|
kPotentialSwing8th = 12,
|
|
kPotentialSwing16th
|
|
};
|
|
|
|
- (void)readMelody:(NSArray *)melody inMeasure:(uint32_t)measNo onsets:(int *)onsets lyrics:(uint8_t *)prevKind
|
|
{
|
|
VLLocation at = {measNo, VLFraction(0)};
|
|
int lastOnset = 0;
|
|
VLLocation tiedStart = {measNo, VLFraction(0)};
|
|
VLLyricsNote tiedNote;
|
|
|
|
for (NSEnumerator * ne = [melody objectEnumerator];
|
|
NSDictionary * ndict = [ne nextObject];
|
|
) {
|
|
VLLyricsNote note;
|
|
note.fDuration =
|
|
VLFraction([[ndict objectForKey:@"durNum"] intValue],
|
|
[[ndict objectForKey:@"durDenom"] intValue],
|
|
true);
|
|
note.fPitch = [[ndict objectForKey:@"pitch"] intValue];
|
|
note.fVisual = [[ndict objectForKey:@"visual"] intValue]
|
|
& VLNote::kAccidentalsMask;
|
|
note.fTied = 0;
|
|
|
|
if ([ndict objectForKey:@"actualNotes"])
|
|
note.fVisual |= VLNote::Tuplet([[ndict objectForKey:@"actualNotes"] intValue],
|
|
[[ndict objectForKey:@"normalNotes"] intValue]);
|
|
|
|
if ([[ndict objectForKey:@"tied"] intValue] & VLNote::kTiedWithPrev) {
|
|
if (at.fAt != VLFraction(0) && note.fPitch == tiedNote.fPitch) {
|
|
//
|
|
// Extend preceding note
|
|
//
|
|
tiedNote.fDuration += note.fDuration;
|
|
song->DelNote(tiedStart);
|
|
song->AddNote(tiedNote, tiedStart);
|
|
|
|
goto advanceAt;
|
|
} else {
|
|
//
|
|
// Slur or 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);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Sanitize syllabic information which was corrupt in early
|
|
// versions.
|
|
//
|
|
for (size_t i = 0; i<note.fLyrics.size(); ++i)
|
|
if (note.fLyrics[i].fText.size()) {
|
|
if (prevKind[i] & VLSyllable::kHasNext)
|
|
note.fLyrics[i].fKind |= VLSyllable::kHasPrev;
|
|
else
|
|
note.fLyrics[i].fKind &= ~VLSyllable::kHasPrev;
|
|
prevKind[i] = note.fLyrics[i].fKind;
|
|
} else {
|
|
prevKind[i] = 0;
|
|
}
|
|
|
|
tiedStart = at;
|
|
tiedNote = note;
|
|
|
|
song->AddNote(note, at);
|
|
|
|
if (!(note.fTied & VLNote::kTiedWithPrev)) {
|
|
VLFraction inQuarter = at.fAt % VLFraction(1,4);
|
|
int onset = inQuarter.fNum * 48 / inQuarter.fDenom;
|
|
++onsets[onset];
|
|
switch (onset) {
|
|
case 3:
|
|
if (lastOnset == 0)
|
|
++onsets[kPotentialSwing16th];
|
|
break;
|
|
case 6:
|
|
if (lastOnset == 0)
|
|
++onsets[kPotentialSwing8th];
|
|
break;
|
|
case 9:
|
|
if (lastOnset == 6 || lastOnset == 3 || lastOnset == 0)
|
|
++onsets[kPotentialSwing16th];
|
|
break;
|
|
}
|
|
}
|
|
advanceAt:
|
|
at.fAt = at.fAt + note.fDuration;
|
|
}
|
|
}
|
|
|
|
- (void)readChords:(NSArray *)chords inMeasure:(uint32_t)measNo
|
|
{
|
|
VLLocation at = {measNo, VLFraction(0)};
|
|
|
|
for (NSEnumerator * ce = [chords objectEnumerator];
|
|
NSDictionary * cdict = [ce nextObject];
|
|
) {
|
|
VLChord chord;
|
|
chord.fDuration =
|
|
VLFraction([[cdict objectForKey:@"durNum"] intValue],
|
|
[[cdict objectForKey:@"durDenom"] intValue],
|
|
true);
|
|
chord.fPitch = [[cdict objectForKey:@"pitch"] intValue];
|
|
chord.fVisual = [[cdict objectForKey:@"visual"] intValue];
|
|
chord.fRootPitch = [[cdict objectForKey:@"root"] intValue];
|
|
chord.fRootAccidental = [[cdict objectForKey:@"rootvisual"] intValue];
|
|
chord.fSteps = [[cdict objectForKey:@"steps"] intValue];
|
|
|
|
song->AddChord(chord, at);
|
|
|
|
at.fAt = at.fAt + chord.fDuration;
|
|
}
|
|
}
|
|
|
|
- (void)readMeasuresFromPlist:(NSArray *)measures
|
|
{
|
|
std::vector<size_t> repeatStack;
|
|
|
|
size_t measNo = 0;
|
|
int onsets[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
uint8_t lyricsKind[20];
|
|
memset(lyricsKind, 0, 20*sizeof(lyricsKind[0]));
|
|
for (NSEnumerator * me = [measures objectEnumerator];
|
|
NSDictionary * mdict = [me nextObject];
|
|
++measNo
|
|
) {
|
|
if (NSNumber * mNo = [mdict objectForKey:@"measure"])
|
|
measNo = static_cast<size_t>([mNo intValue]);
|
|
if (NSNumber * mPx = [mdict objectForKey:@"properties"])
|
|
song->SetProperties(measNo, [mPx intValue]);
|
|
|
|
[self readMelody:[mdict objectForKey:@"melody"] inMeasure:measNo onsets:onsets lyrics:&lyricsKind[0]];
|
|
[self readChords:[mdict objectForKey:@"chords"] inMeasure:measNo];
|
|
|
|
if ([[mdict objectForKey:@"tocoda"] boolValue])
|
|
song->fGoToCoda = measNo+1;
|
|
if ([[mdict objectForKey:@"coda"] boolValue])
|
|
song->fCoda = measNo;
|
|
if ([[mdict objectForKey:@"new-system"] boolValue])
|
|
song->fMeasures[measNo].fBreak |= VLMeasure::kNewSystem;
|
|
if ([[mdict objectForKey:@"new-page"] boolValue])
|
|
song->fMeasures[measNo].fBreak |= VLMeasure::kNewPage;
|
|
|
|
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<<rep.fTimes) - 1) & ending.fVolta) < ending.fVolta)
|
|
++rep.fTimes;
|
|
if ([[endEnd objectForKey:@"last"] boolValue]) {
|
|
rep.fEndings[0].fEnd = measNo+1;
|
|
repeatStack.pop_back();
|
|
}
|
|
}
|
|
if (NSDictionary * endRep = [mdict objectForKey:@"end-repeat"]) {
|
|
VLRepeat & rep = song->fRepeats[repeatStack.back()];
|
|
|
|
if (NSNumber * times = [endRep objectForKey:@"times"])
|
|
rep.fTimes = [times intValue];
|
|
|
|
rep.fEndings[0].fEnd = measNo+1;
|
|
rep.fEndings[0].fVolta = (1<<rep.fTimes)-1;
|
|
|
|
repeatStack.pop_back();
|
|
}
|
|
}
|
|
size_t empty = song->EmptyEnding();
|
|
while (empty-- > 1)
|
|
song->fMeasures.pop_back();
|
|
if (!song->fProperties.back().fDivisions) {
|
|
if (!(onsets[1]+onsets[5]+onsets[7]+onsets[11]))
|
|
if (!(onsets[3]+onsets[9]-onsets[kPotentialSwing16th]))
|
|
if (onsets[kPotentialSwing16th]) {
|
|
song->fProperties.back().fDivisions = 12;
|
|
song->ChangeDivisions(song->fProperties.size()-1, 6);
|
|
} else if (!(onsets[2]+onsets[4]+onsets[8]+onsets[10])) {
|
|
song->fProperties.back().fDivisions = 2;
|
|
} else if (!(onsets[2]+onsets[10]
|
|
+ onsets[6]-onsets[kPotentialSwing8th])) {
|
|
if (onsets[kPotentialSwing8th]) {
|
|
song->fProperties.back().fDivisions = 6;
|
|
song->ChangeDivisions(song->fProperties.size()-1, 3);
|
|
} else {
|
|
song->fProperties.back().fDivisions = 3;
|
|
}
|
|
} else {
|
|
song->fProperties.back().fDivisions = 6;
|
|
}
|
|
else if (!(onsets[2]+onsets[4]+onsets[8]+onsets[10]))
|
|
song->fProperties.back().fDivisions = 4;
|
|
else
|
|
song->fProperties.back().fDivisions = 12;
|
|
else
|
|
song->fProperties.back().fDivisions = 12;
|
|
}
|
|
}
|
|
|
|
- (void)readPropertiesFromPlist:(NSArray *)properties
|
|
{
|
|
song->fProperties.clear();
|
|
for (NSEnumerator * pe = [properties objectEnumerator];
|
|
NSDictionary * pdict = [pe nextObject];
|
|
) {
|
|
VLProperties prop;
|
|
|
|
prop.fTime =
|
|
VLFraction([[pdict objectForKey:@"timeNum"] intValue],
|
|
[[pdict objectForKey:@"timeDenom"] intValue],
|
|
false);
|
|
prop.fKey = [[pdict objectForKey:@"key"] intValue];
|
|
prop.fMode = [[pdict objectForKey:@"mode"] intValue];
|
|
prop.fDivisions = [[pdict objectForKey:@"divisions"] intValue];
|
|
|
|
if (NSString * groove = [pdict objectForKey:@"groove"])
|
|
prop.fGroove = [groove UTF8String];
|
|
else
|
|
prop.fGroove = [songGroove UTF8String];
|
|
|
|
song->fProperties.push_back(prop);
|
|
}
|
|
}
|
|
|
|
- (void)setValueFromPlist:(id)plist plistKey:(NSString *)plistKey forKey:(NSString *)key
|
|
{
|
|
id value = [plist objectForKey:plistKey];
|
|
if (value)
|
|
[self setValue:value forKey:key];
|
|
}
|
|
|
|
- (BOOL)readFromPlist:(id)plist error:(NSError **)outError
|
|
{
|
|
NSUndoManager * undoMgr = [self undoManager];
|
|
[undoMgr disableUndoRegistration];
|
|
song->clear();
|
|
|
|
[self setValueFromPlist:plist plistKey:@"title" forKey:@"songTitle"];
|
|
[self setValueFromPlist:plist plistKey:@"composer" forKey:@"songComposer"];
|
|
[self setValueFromPlist:plist plistKey:@"lyricist" forKey:@"songLyricist"];
|
|
[self setValueFromPlist:plist plistKey:@"groove" forKey:@"songGroove"];
|
|
[self setValueFromPlist:plist plistKey:@"tempo" forKey:@"songTempo"];
|
|
[self readPropertiesFromPlist:[plist objectForKey:@"properties"]];
|
|
[self readMeasuresFromPlist:[plist objectForKey:@"measures"]];
|
|
[undoMgr enableUndoRegistration];
|
|
|
|
if (song->fMeasures.empty()) {
|
|
delete song;
|
|
song = new VLSong(true);
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (NSData *)runFilter:(NSString *)filterName withContents:(NSData *)contents
|
|
{
|
|
NSString * filterPath = [[NSBundle bundleForClass:[VLDocument class]] 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]) {
|
|
[contents writeToFile:@"/var/tmp/VocalEaselFilterInput" atomically:NO];
|
|
[error writeToFile:@"/var/tmp/VocalEaselFilterError" atomically:NO];
|
|
NSString * errStr = [[[NSString alloc] initWithData:error
|
|
encoding:NSUTF8StringEncoding] autorelease];
|
|
[NSException raise:NSInvalidArgumentException
|
|
format:@"Filter %@: %@", filterName, errStr];
|
|
}
|
|
|
|
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)
|
|
[NSException raise:NSInvalidArgumentException
|
|
format:@"Plist %@: %@", filterName, errString];
|
|
return [self readFromPlist:outPlist error:outError];
|
|
}
|
|
|
|
@end
|