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 { \header {
title = "<{TITLE}>" title = "<{TITLE}>"
poet = "<{POET}>" poet = "<{POET}>"
composer= "<{COMPOSER}>" composer= "<{COMPOSER}>"
arranger = "<{ARRANGER}>" arranger = "<{ARRANGER}>"
tagline = #(string-append "Vocalese <{VLVERSION}> / Lilypond" (lilypond-version)) tagline = #(string-append "Created with VocalEasel <{VLVERSION}> / Lilypond " (lilypond-version))
} }
\paper { \paper {

View File

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

View File

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

View File

@ -19,6 +19,8 @@
- (void) substituteMacro:(NSString *)m withValue:(NSString *)value - (void) substituteMacro:(NSString *)m withValue:(NSString *)value
{ {
if ([value isEqual:@""])
return;
NSString * macro = [NSString stringWithFormat:@"<{%@}>", m]; NSString * macro = [NSString stringWithFormat:@"<{%@}>", m];
NSRange range = NSRange range =
[value rangeOfCharacterFromSet: [value rangeOfCharacterFromSet:
@ -82,19 +84,49 @@
@implementation VLDocument (Lilypond) @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 - (NSData *)lilypondDataWithError:(NSError **)outError
{ {
NSBundle * bndl = [NSBundle mainBundle]; const VLProperties & prop = song->fProperties.front();
NSString * tmpl = NSBundle * bndl = [NSBundle mainBundle];
NSString * tmpl =
[bndl pathForResource:lilypondTemplate [bndl pathForResource:lilypondTemplate
ofType:@"lyt" inDirectory:@"Templates"]; ofType:@"lyt" inDirectory:@"Templates"];
NSStringEncoding enc = NSUTF8StringEncoding; NSStringEncoding enc = NSUTF8StringEncoding;
NSError * err; NSMutableString * ly =
NSMutableString * ly = [NSMutableString stringWithContentsOfFile:tmpl encoding:enc error:outError];
[[NSString stringWithContentsOfFile:tmpl encoding:enc error:&err] [ly substituteMacro:@"TITLE" withValue:songTitle];
mutableCopy]; [ly substituteMacro:@"POET" withValue:songLyricist];
[ly substituteMacro:@"COMPOSER" withValue:songComposer];
[ly substituteMacro:@"ARRANGER" withValue:songArranger];
[ly substituteMacro:@"VLVERSION" withValue: [ly substituteMacro:@"VLVERSION" withValue:
[bndl objectForInfoDictionaryKey:@"CFBundleVersion"]]; [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]; [ly purgeMacros];
return [ly dataUsingEncoding:enc]; return [ly dataUsingEncoding:enc];
} }

View File

@ -135,9 +135,42 @@ void VLNote::Name(std::string & name, bool useSharps) const
name = PitchName(fPitch, useSharps); 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 { struct VLChordModifier {
@ -312,7 +345,7 @@ void VLChord::Name(std::string & base, std::string & ext, std::string & root, bo
} }
static const char * kLilypondStepNames[] = { 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" "9-", "9", "9+", "", "11", "11+", "", "13-", "13"
}; };
@ -325,6 +358,8 @@ void VLChord::LilypondName(std::string & name, bool useSharps) const
else else
sprintf(duration, "1*%d/%d", fDuration.fNum, fDuration.fDenom); sprintf(duration, "1*%d/%d", fDuration.fNum, fDuration.fDenom);
name += std::string(duration); name += std::string(duration);
if (fPitch == kNoPitch)
return;
std::string ext; std::string ext;
uint32_t steps = fSteps & ~(kmUnison | km5th); uint32_t steps = fSteps & ~(kmUnison | km5th);
@ -355,7 +390,7 @@ void VLChord::LilypondName(std::string & name, bool useSharps) const
// 6/9 // 6/9
// //
if ((steps & (kmDim7th|kmMaj9th)) == (kmDim7th|kmMaj9th)) { if ((steps & (kmDim7th|kmMaj9th)) == (kmDim7th|kmMaj9th)) {
if (ext.size()) if (ext.size() && !isalpha(ext[ext.size()-1]))
ext += '.'; ext += '.';
ext += "6.9"; ext += "6.9";
steps&= ~(kmDim7th|kmMaj9th); steps&= ~(kmDim7th|kmMaj9th);
@ -369,7 +404,7 @@ void VLChord::LilypondName(std::string & name, bool useSharps) const
for (int step = kMaj13th; step > kDim7th; --step) for (int step = kMaj13th; step > kDim7th; --step)
if (unaltered & (1 << step)) { if (unaltered & (1 << step)) {
std::string sn = kLilypondStepNames[step]; std::string sn = kLilypondStepNames[step];
if (ext.size() && sn.size()) if (ext.size() && !isalpha(ext[ext.size()-1]) && sn.size())
ext += '.'; ext += '.';
ext += sn; ext += sn;
break; break;
@ -443,6 +478,32 @@ void VLProperties::PartialNote(VLFraction at, VLFraction totalDuration,
*noteDuration *= VLFraction(3,4); // avoid frivolous triplets *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() VLMeasure::VLMeasure()
: fProperties(0) : 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; return one.fNum*other.fDenom >= other.fNum*one.fDenom;
} }
class VLProperties;
struct VLNote { struct VLNote {
VLFraction fDuration; VLFraction fDuration;
int8_t fPitch; // Semitones int8_t fPitch; // Semitones
enum { enum {
kNoPitch = -128, kNoPitch = -128,
kMiddleC = 60 kMiddleC = 60,
kOctave = 12
}; };
// //
// We only allow ties BETWEEN measures. Within measures, we just store // We only allow ties BETWEEN measures. Within measures, we just store
@ -115,7 +118,7 @@ struct VLNote {
VLNote(std::string name); VLNote(std::string name);
void Name(std::string & name, bool useSharps = false) const; 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 { struct VLRest : VLNote {
@ -189,6 +192,10 @@ struct VLProperties {
// Subdivide a note and adjust for swing // Subdivide a note and adjust for swing
// //
void PartialNote(VLFraction at, VLFraction totalDuration, VLFraction * noteDuration) const; 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 { struct VLSyllable {
@ -222,6 +229,8 @@ struct VLSong {
void Transpose(int semitones); void Transpose(int semitones);
size_t CountMeasures() const { return fMeasures.size(); } size_t CountMeasures() const { return fMeasures.size(); }
void LilypondNotes(std::string & notes) const;
void LilypondChords(std::string & chords) const;
}; };
// Local Variables: // Local Variables:

View File

@ -270,28 +270,11 @@
int pitch = note->fPitch; int pitch = note->fPitch;
while (dur > 0) { while (dur > 0) {
VLFraction partialDur; // Actual value of note drawn VLFraction partialDur; // Actual value of note drawn
VLFraction noteDur; // Visual value of note
bool triplet;
measure.fProperties->PartialNote(at, dur, &partialDur); 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) { if (pitch != VLNote::kNoPitch) {
VLMusicElement accidental; VLMusicElement accidental;
NSPoint pos = NSPoint pos =

View File

@ -16,11 +16,11 @@ int main(int, char *const argv[])
VLDocument * doc = [[VLDocument alloc] init]; VLDocument * doc = [[VLDocument alloc] init];
NSString * file = [NSString stringWithUTF8String:argv[1]]; NSString * file = [NSString stringWithUTF8String:argv[1]];
NSError * err; NSError * err;
[doc readFromURL:[NSURL fileURLWithPath:file] ofType:@"Song" error:&err]; [doc readFromURL:[NSURL fileURLWithPath:file] ofType:@"VLNativeType" error:&err];
[doc writeToURL:[NSURL fileURLWithPath: [doc writeToURL:[NSURL fileURLWithPath:
[[file stringByDeletingPathExtension] [[file stringByDeletingPathExtension]
stringByAppendingPathExtension:@"ly"]] stringByAppendingPathExtension:@"ly"]]
ofType:@"Lilypond" error:&err]; ofType:@"VLLilypondType" error:&err];
[pool release]; [pool release];
exit(0); 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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 */ = { 19C28FB0FE9D524F11CA2CBB /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8D15AC370486D014006FF6A4 /* Vocalese.app */, 8D15AC370486D014006FF6A4 /* VocalEasel.app */,
955E595C0957C0FC0045FDA5 /* TVLChord */, 955E595C0957C0FC0045FDA5 /* TVLChord */,
952CBB98095FD19D00434E43 /* TVLSoundOut */, 952CBB98095FD19D00434E43 /* TVLSoundOut */,
959408A0096922CA007CCCF8 /* TVLEdit */, 959408A0096922CA007CCCF8 /* TVLEdit */,
@ -275,7 +275,7 @@
name = Vocalese; name = Vocalese;
productInstallPath = "$(HOME)/Applications"; productInstallPath = "$(HOME)/Applications";
productName = Vocalese; productName = Vocalese;
productReference = 8D15AC370486D014006FF6A4 /* Vocalese.app */; productReference = 8D15AC370486D014006FF6A4 /* VocalEasel.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
952CBB97095FD19D00434E43 /* TVLSoundOut */ = { 952CBB97095FD19D00434E43 /* TVLSoundOut */ = {
@ -512,7 +512,7 @@
INFOPLIST_FILE = Resources/Info.plist; INFOPLIST_FILE = Resources/Info.plist;
INSTALL_PATH = "$(HOME)/Applications"; INSTALL_PATH = "$(HOME)/Applications";
PREBINDING = NO; PREBINDING = NO;
PRODUCT_NAME = Vocalese; PRODUCT_NAME = VocalEasel;
WRAPPER_EXTENSION = app; WRAPPER_EXTENSION = app;
ZERO_LINK = YES; ZERO_LINK = YES;
}; };
@ -531,7 +531,7 @@
INFOPLIST_FILE = Resources/Info.plist; INFOPLIST_FILE = Resources/Info.plist;
INSTALL_PATH = "$(HOME)/Applications"; INSTALL_PATH = "$(HOME)/Applications";
PREBINDING = NO; PREBINDING = NO;
PRODUCT_NAME = Vocalese; PRODUCT_NAME = VocalEasel;
WRAPPER_EXTENSION = app; WRAPPER_EXTENSION = app;
ZERO_LINK = NO; ZERO_LINK = NO;
}; };
@ -547,7 +547,7 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = Info.plist; INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(HOME)/Applications"; INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = Vocalese; PRODUCT_NAME = VocalEasel;
WRAPPER_EXTENSION = app; WRAPPER_EXTENSION = app;
}; };
name = Default; name = Default;