mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 03:04:00 +00:00
Fully implement Lilypond export
This commit is contained in:
parent
6d6b3c1c2a
commit
6886a2d9f9
|
@ -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 {
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
VLSong * song;
|
||||
VLEditable *editTarget;
|
||||
NSString * lilypondTemplate;
|
||||
NSString * songTitle;
|
||||
NSString * songLyricist;
|
||||
NSString * songComposer;
|
||||
NSString * songArranger;
|
||||
}
|
||||
|
||||
- (VLSong *) song;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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, ¬eDur, &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 =
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user