Fully implement Lilypond export

This commit is contained in:
Matthias Neeracher 2006-10-23 07:42:53 +00:00
parent 6d6b3c1c2a
commit 6886a2d9f9
9 changed files with 200 additions and 49 deletions

View File

@ -1,11 +1,11 @@
\version "1.8.0"
\version "2.8.0"
\header {
title = "<{TITLE}>"
poet = "<{POET}>"
composer= "<{COMPOSER}>"
arranger = "<{ARRANGER}>"
tagline = #(string-append "Vocalese <{VLVERSION}> / Lilypond" (lilypond-version))
tagline = #(string-append "Created with VocalEasel <{VLVERSION}> / Lilypond " (lilypond-version))
}
\paper {

View File

@ -27,6 +27,10 @@
VLSong * song;
VLEditable *editTarget;
NSString * lilypondTemplate;
NSString * songTitle;
NSString * songLyricist;
NSString * songComposer;
NSString * songArranger;
}
- (VLSong *) song;

View File

@ -53,6 +53,10 @@
song = new VLSong;
editTarget = nil;
lilypondTemplate = @"default";
songTitle = @"";
songLyricist = @"";
songComposer = @"";
songArranger = @"";
}
return self;
}
@ -133,20 +137,30 @@
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
if ([typeName isEqual:@"Song"])
if ([typeName isEqual:@"VLNativeType"]) {
return [self XMLDataWithError:outError];
else if ([typeName isEqual:@"Lilypond"])
} else if ([typeName isEqual:@"VLLilypondType"]) {
return [self lilypondDataWithError:outError];
else
} else {
if (outError)
*outError = [NSError errorWithDomain:NSCocoaErrorDomain
code:NSPersistentStoreInvalidTypeError
userInfo:nil];
return nil;
}
}
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
if ([typeName isEqual:@"Song"])
if ([typeName isEqual:@"VLNativeType"]) {
return [self readFromXMLData:data error:outError];
else
} else {
if (outError)
*outError = [NSError errorWithDomain:NSCocoaErrorDomain
code:NSPersistentStoreInvalidTypeError
userInfo:nil];
return NO;
}
}
@end

View File

@ -19,6 +19,8 @@
- (void) substituteMacro:(NSString *)m withValue:(NSString *)value
{
if ([value isEqual:@""])
return;
NSString * macro = [NSString stringWithFormat:@"<{%@}>", m];
NSRange range =
[value rangeOfCharacterFromSet:
@ -82,19 +84,49 @@
@implementation VLDocument (Lilypond)
const int kMajorOffset = 6;
const int kMinorOffset = 9;
const char * sKeyNames[] = {
"ges", "des", "as", "es", "bes", "f",
"c", "g", "d", "a", "e", "b", "fis", "cis", "gis"
};
- (NSData *)lilypondDataWithError:(NSError **)outError
{
NSBundle * bndl = [NSBundle mainBundle];
NSString * tmpl =
const VLProperties & prop = song->fProperties.front();
NSBundle * bndl = [NSBundle mainBundle];
NSString * tmpl =
[bndl pathForResource:lilypondTemplate
ofType:@"lyt" inDirectory:@"Templates"];
NSStringEncoding enc = NSUTF8StringEncoding;
NSError * err;
NSMutableString * ly =
[[NSString stringWithContentsOfFile:tmpl encoding:enc error:&err]
mutableCopy];
NSStringEncoding enc = NSUTF8StringEncoding;
NSMutableString * ly =
[NSMutableString stringWithContentsOfFile:tmpl encoding:enc error:outError];
[ly substituteMacro:@"TITLE" withValue:songTitle];
[ly substituteMacro:@"POET" withValue:songLyricist];
[ly substituteMacro:@"COMPOSER" withValue:songComposer];
[ly substituteMacro:@"ARRANGER" withValue:songArranger];
[ly substituteMacro:@"VLVERSION" withValue:
[bndl objectForInfoDictionaryKey:@"CFBundleVersion"]];
[ly substituteMacro:@"PAPERSIZE" withValue:@"letter"];
[ly substituteMacro:@"FORMATTING" withValue:@"ragged-last-bottom = ##f"];
std::string lys;
song->LilypondChords(lys);
[ly substituteMacro:@"VLVERSION" withValue:
[bndl objectForInfoDictionaryKey:@"CFBundleVersion"]];
[ly substituteMacro:@"CHORDS" withValue:
[NSString stringWithUTF8String:lys.c_str()]];
[ly substituteMacro:@"TIME" withValue:
[NSString stringWithFormat:@"%d/%d",
prop.fTime.fNum, prop.fTime.fDenom]];
[ly substituteMacro:@"KEY" withValue: prop.fMode > 0
? [NSString stringWithFormat:@"%s \\major",
sKeyNames[prop.fKey+kMajorOffset]]
: [NSString stringWithFormat:@"%s \\minor",
sKeyNames[prop.fKey+kMinorOffset]]];
song->LilypondNotes(lys);
[ly substituteMacro:@"NOTES" withValue:
[NSString stringWithUTF8String:lys.c_str()]];
[ly purgeMacros];
return [ly dataUsingEncoding:enc];
}

View File

@ -135,9 +135,42 @@ void VLNote::Name(std::string & name, bool useSharps) const
name = PitchName(fPitch, useSharps);
}
void VLNote::LilypondName(std::string & name, bool useSharps) const
void VLNote::LilypondName(std::string & name, VLFraction at, const VLProperties & prop) const
{
name = LilypondPitchName(fPitch, useSharps);
std::string n = LilypondPitchName(fPitch, prop.fKey >= 0);
if (fPitch != kNoPitch) {
for (int ticks = (fPitch-kMiddleC+kOctave)/kOctave; ticks>0; --ticks)
n += '\'';
for (int commas = (kMiddleC-kOctave-fPitch)/kOctave; commas>0; --commas)
n += ',';
}
std::vector<std::string> durations;
VLFraction prevPart(0);
for (VLFraction dur = fDuration; dur.fNum; ) {
char duration[32];
VLFraction part, visual;
bool triplet;
prop.PartialNote(at, dur, &part);
prop.VisualNote(at, part, &visual, &triplet);
if (!triplet && fPitch != kNoPitch && part == dur && 2*visual == prevPart) {
durations.pop_back();
sprintf(duration, "%s%d.", n.c_str(), visual.fDenom/2);
} else if (triplet) {
sprintf(duration, "\times 2/3 { %s%d }", n.c_str(), visual.fDenom);
} else {
sprintf(duration, "%s%d", n.c_str(), visual.fDenom);
}
durations.push_back(duration);
prevPart = part;
at += part;
dur -= part;
}
for (size_t i=0; i<durations.size(); ++i) {
if (i)
name += " ~ ";
name += durations[i];
}
}
struct VLChordModifier {
@ -312,7 +345,7 @@ void VLChord::Name(std::string & base, std::string & ext, std::string & root, bo
}
static const char * kLilypondStepNames[] = {
"", "", "sus2", "", "", "sus", "5-", "", "5+", "7-", "7", "7+", "",
"", "", "sus2", "", "", "sus", "5-", "", "5+", "6", "7", "7+", "",
"9-", "9", "9+", "", "11", "11+", "", "13-", "13"
};
@ -325,6 +358,8 @@ void VLChord::LilypondName(std::string & name, bool useSharps) const
else
sprintf(duration, "1*%d/%d", fDuration.fNum, fDuration.fDenom);
name += std::string(duration);
if (fPitch == kNoPitch)
return;
std::string ext;
uint32_t steps = fSteps & ~(kmUnison | km5th);
@ -355,7 +390,7 @@ void VLChord::LilypondName(std::string & name, bool useSharps) const
// 6/9
//
if ((steps & (kmDim7th|kmMaj9th)) == (kmDim7th|kmMaj9th)) {
if (ext.size())
if (ext.size() && !isalpha(ext[ext.size()-1]))
ext += '.';
ext += "6.9";
steps&= ~(kmDim7th|kmMaj9th);
@ -369,7 +404,7 @@ void VLChord::LilypondName(std::string & name, bool useSharps) const
for (int step = kMaj13th; step > kDim7th; --step)
if (unaltered & (1 << step)) {
std::string sn = kLilypondStepNames[step];
if (ext.size() && sn.size())
if (ext.size() && !isalpha(ext[ext.size()-1]) && sn.size())
ext += '.';
ext += sn;
break;
@ -443,6 +478,32 @@ void VLProperties::PartialNote(VLFraction at, VLFraction totalDuration,
*noteDuration *= VLFraction(3,4); // avoid frivolous triplets
}
void VLProperties::VisualNote(VLFraction at, VLFraction actualDur,
VLFraction *visualDur, bool * triplet) const
{
bool swing= !(fDivisions % 3); // In swing mode?
VLFraction swung(3, fDivisions*8, true); // Which notes to swing
VLFraction swingGrid(2*swung); // Alignment of swing notes
if (*triplet = !(actualDur.fDenom % 3)) {
if (swing) { // Swing 8ths / 16ths are written as straight 8ths
if (actualDur == 4*swung/3 && (at % swingGrid) == 0) {
*visualDur = swung;
*triplet = false;
} else if (actualDur == 2*swung/3 && ((at+actualDur) % swingGrid) == 0) {
*visualDur = swung;
*triplet = false;
} else {
*visualDur = 4*actualDur/3;
}
} else {
*visualDur = 4*actualDur/3;
}
} else {
*visualDur = actualDur;
}
}
VLMeasure::VLMeasure()
: fProperties(0)
{
@ -691,3 +752,51 @@ void VLSong::Transpose(int semi)
}
}
}
void VLSong::LilypondNotes(std::string & notes) const
{
notes = "";
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
VLNoteList::const_iterator i = fMeasures[measure].fMelody.begin();
VLNoteList::const_iterator e = fMeasures[measure].fMelody.end();
VLFraction at(0);
for (; i!=e; ++i) {
std::string note;
i->LilypondName(note, at, *fMeasures[measure].fProperties);
at += i->fDuration;
notes += note+" ";
}
notes += '|';
if (!(measure % 4)) {
char measNo[8];
sprintf(measNo, " %% %d", measure+1);
notes += measNo;
}
if (measure < fMeasures.size()-1)
notes += '\n';
}
}
void VLSong::LilypondChords(std::string & chords) const
{
chords = "";
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
bool useSharps = fMeasures[measure].fProperties->fKey>=0;
VLChordList::const_iterator i = fMeasures[measure].fChords.begin();
VLChordList::const_iterator e = fMeasures[measure].fChords.end();
for (; i!=e; ++i) {
std::string chord;
i->LilypondName(chord, useSharps);
chords += chord+" ";
}
if (!(measure % 4)) {
char measNo[8];
sprintf(measNo, " %% %d", measure+1);
chords += measNo;
}
if (measure < fMeasures.size()-1)
chords += '\n';
}
}

View File

@ -91,12 +91,15 @@ inline bool operator>=(VLFraction one, VLFraction other)
return one.fNum*other.fDenom >= other.fNum*one.fDenom;
}
class VLProperties;
struct VLNote {
VLFraction fDuration;
int8_t fPitch; // Semitones
enum {
kNoPitch = -128,
kMiddleC = 60
kMiddleC = 60,
kOctave = 12
};
//
// We only allow ties BETWEEN measures. Within measures, we just store
@ -115,7 +118,7 @@ struct VLNote {
VLNote(std::string name);
void Name(std::string & name, bool useSharps = false) const;
void LilypondName(std::string & name, bool useSharps = false) const;
void LilypondName(std::string & name, VLFraction at, const VLProperties & prop) const;
};
struct VLRest : VLNote {
@ -189,6 +192,10 @@ struct VLProperties {
// Subdivide a note and adjust for swing
//
void PartialNote(VLFraction at, VLFraction totalDuration, VLFraction * noteDuration) const;
//
// Determine visual representation of note head
//
void VisualNote(VLFraction at, VLFraction actualDur, VLFraction *visualDur, bool * triplet) const;
};
struct VLSyllable {
@ -222,6 +229,8 @@ struct VLSong {
void Transpose(int semitones);
size_t CountMeasures() const { return fMeasures.size(); }
void LilypondNotes(std::string & notes) const;
void LilypondChords(std::string & chords) const;
};
// Local Variables:

View File

@ -270,28 +270,11 @@
int pitch = note->fPitch;
while (dur > 0) {
VLFraction partialDur; // Actual value of note drawn
VLFraction noteDur; // Visual value of note
bool triplet;
measure.fProperties->PartialNote(at, dur, &partialDur);
measure.fProperties->VisualNote(at, partialDur, &noteDur, &triplet);
BOOL triplet = !(partialDur.fDenom % 3);
VLFraction noteDur(1); // Visual value of note
if (triplet) {
if (swing) { // Swing 8ths / 16ths are written as straight 8ths
if (partialDur == 4*swung/3 && (at % swingGrid) == 0) {
noteDur = swung;
triplet = NO;
} else if (partialDur == 2*swung/3 && ((at+partialDur) % swingGrid) == 0) {
noteDur = swung;
triplet = NO;
} else {
noteDur = 4*partialDur/3;
}
} else {
noteDur = 4*partialDur/3;
}
} else {
noteDur = partialDur;
}
if (pitch != VLNote::kNoPitch) {
VLMusicElement accidental;
NSPoint pos =

View File

@ -16,11 +16,11 @@ int main(int, char *const argv[])
VLDocument * doc = [[VLDocument alloc] init];
NSString * file = [NSString stringWithUTF8String:argv[1]];
NSError * err;
[doc readFromURL:[NSURL fileURLWithPath:file] ofType:@"Song" error:&err];
[doc readFromURL:[NSURL fileURLWithPath:file] ofType:@"VLNativeType" error:&err];
[doc writeToURL:[NSURL fileURLWithPath:
[[file stringByDeletingPathExtension]
stringByAppendingPathExtension:@"ly"]]
ofType:@"Lilypond" error:&err];
ofType:@"VLLilypondType" error:&err];
[pool release];
exit(0);

View File

@ -60,7 +60,7 @@
2A37F4C5FDCFA73011CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
32DBCF750370BD2300C91783 /* Vocalese_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Vocalese_Prefix.pch; path = Sources/Vocalese_Prefix.pch; sourceTree = "<group>"; };
8D15AC360486D014006FF6A4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = Info.plist; path = Resources/Info.plist; sourceTree = "<group>"; };
8D15AC370486D014006FF6A4 /* Vocalese.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Vocalese.app; sourceTree = BUILT_PRODUCTS_DIR; };
8D15AC370486D014006FF6A4 /* VocalEasel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VocalEasel.app; sourceTree = BUILT_PRODUCTS_DIR; };
952CBB98095FD19D00434E43 /* TVLSoundOut */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TVLSoundOut; sourceTree = BUILT_PRODUCTS_DIR; };
952CBB9A095FD1CA00434E43 /* VLSoundOut.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = VLSoundOut.cpp; path = Sources/VLSoundOut.cpp; sourceTree = "<group>"; };
952CBB9B095FD1CA00434E43 /* VLSoundOut.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = VLSoundOut.h; path = Sources/VLSoundOut.h; sourceTree = "<group>"; };
@ -163,7 +163,7 @@
19C28FB0FE9D524F11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
8D15AC370486D014006FF6A4 /* Vocalese.app */,
8D15AC370486D014006FF6A4 /* VocalEasel.app */,
955E595C0957C0FC0045FDA5 /* TVLChord */,
952CBB98095FD19D00434E43 /* TVLSoundOut */,
959408A0096922CA007CCCF8 /* TVLEdit */,
@ -275,7 +275,7 @@
name = Vocalese;
productInstallPath = "$(HOME)/Applications";
productName = Vocalese;
productReference = 8D15AC370486D014006FF6A4 /* Vocalese.app */;
productReference = 8D15AC370486D014006FF6A4 /* VocalEasel.app */;
productType = "com.apple.product-type.application";
};
952CBB97095FD19D00434E43 /* TVLSoundOut */ = {
@ -512,7 +512,7 @@
INFOPLIST_FILE = Resources/Info.plist;
INSTALL_PATH = "$(HOME)/Applications";
PREBINDING = NO;
PRODUCT_NAME = Vocalese;
PRODUCT_NAME = VocalEasel;
WRAPPER_EXTENSION = app;
ZERO_LINK = YES;
};
@ -531,7 +531,7 @@
INFOPLIST_FILE = Resources/Info.plist;
INSTALL_PATH = "$(HOME)/Applications";
PREBINDING = NO;
PRODUCT_NAME = Vocalese;
PRODUCT_NAME = VocalEasel;
WRAPPER_EXTENSION = app;
ZERO_LINK = NO;
};
@ -547,7 +547,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = Vocalese;
PRODUCT_NAME = VocalEasel;
WRAPPER_EXTENSION = app;
};
name = Default;