Low level lyrics support

This commit is contained in:
Matthias Neeracher 2006-11-27 07:07:45 +00:00
parent fbf53052e3
commit 5ce21f2f78
5 changed files with 255 additions and 21 deletions

View File

@ -17,7 +17,7 @@
@implementation NSMutableString (VLLilypond) @implementation NSMutableString (VLLilypond)
- (void) substituteMacro:(NSString *)m withValue:(NSString *)value - (void) substituteMacro:(NSString *)m withValue:(NSString *)value repeat:(BOOL)repeat
{ {
if ([value isEqual:@""]) if ([value isEqual:@""])
return; return;
@ -26,10 +26,12 @@
[value rangeOfCharacterFromSet: [value rangeOfCharacterFromSet:
[NSCharacterSet characterSetWithCharactersInString:@"\n"]]; [NSCharacterSet characterSetWithCharactersInString:@"\n"]];
BOOL hasEOL= range.location != NSNotFound; BOOL hasEOL= range.location != NSNotFound;
unsigned from = 0;
for (range = [self rangeOfString:macro]; for (range = [self rangeOfString:macro];
range.location != NSNotFound; range.location != NSNotFound;
range = [self rangeOfString:macro] range = [self rangeOfString:macro options:0
range:NSMakeRange(from, [self length]-from)]
) { ) {
if (hasEOL) { if (hasEOL) {
// //
@ -49,27 +51,43 @@
[[self substringWithRange:suffix] [[self substringWithRange:suffix]
stringByTrimmingCharactersInSet: stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceCharacterSet]]; [NSCharacterSet whitespaceCharacterSet]];
NSString * nl;
if ([nonBlank length]) { if ([nonBlank length]) {
NSRange nb = [pfxStr rangeOfString:nonBlank]; NSRange nb = [pfxStr rangeOfString:nonBlank];
prefix.length = nb.location - prefix.location; prefix.length = nb.location;
pfxStr = pfxStr =
[[self substringWithRange:prefix] [[self substringWithRange:prefix]
stringByAppendingString:@" "]; stringByAppendingString:@" "];
sfxStr = [NSString stringWithFormat:@"\n%@", pfxStr]; sfxStr = [NSString stringWithFormat:@"\n%@", pfxStr];
nl = @"\n";
} else { } else {
range = line; range = line;
nl = @"";
} }
NSArray * lines = [value componentsSeparatedByString:@"\n"]; NSArray * lines = [value componentsSeparatedByString:@"\n"];
value = value =
[NSString stringWithFormat:@"%@%@%@", pfxStr, [NSString stringWithFormat:@"%@%@%@%@", nl, pfxStr,
[lines componentsJoinedByString: [lines componentsJoinedByString:
[@"\n" stringByAppendingString:pfxStr]], [@"\n" stringByAppendingString:pfxStr]],
sfxStr]; sfxStr];
} }
from = range.location + [value length];
if (repeat) {
NSRange line = [self lineRangeForRange:range];
[self insertString:[self substringWithRange:line]
atIndex:line.location+line.length];
from = line.location+2*line.length+[value length]-range.length;
}
[self replaceCharactersInRange:range withString:value]; [self replaceCharactersInRange:range withString:value];
} }
} }
- (void)substituteMacro:(NSString*)macro withValue:(NSString*)value
{
[self substituteMacro:macro withValue:value repeat:NO];
}
- (void) purgeMacros - (void) purgeMacros
{ {
for (NSRange range = [self rangeOfString:@"<{"]; for (NSRange range = [self rangeOfString:@"<{"];
@ -87,7 +105,7 @@
const int kMajorOffset = 6; const int kMajorOffset = 6;
const int kMinorOffset = 9; const int kMinorOffset = 9;
const char * sKeyNames[] = { static const char * sKeyNames[] = {
"ges", "des", "as", "es", "bes", "f", "ges", "des", "as", "es", "bes", "f",
"c", "g", "d", "a", "e", "b", "fis", "cis", "gis" "c", "g", "d", "a", "e", "b", "fis", "cis", "gis"
}; };
@ -127,6 +145,13 @@ const char * sKeyNames[] = {
song->LilypondNotes(lys); song->LilypondNotes(lys);
[ly substituteMacro:@"NOTES" withValue: [ly substituteMacro:@"NOTES" withValue:
[NSString stringWithUTF8String:lys.c_str()]]; [NSString stringWithUTF8String:lys.c_str()]];
if (size_t stanzas = song->CountStanzas())
for (size_t s=0; s++<stanzas; ) {
song->LilypondStanza(lys, s);
[ly substituteMacro:@"LYRICS" withValue:
[NSString stringWithUTF8String:lys.c_str()]
repeat: s<stanzas];
}
[ly purgeMacros]; [ly purgeMacros];
return [ly dataUsingEncoding:enc]; return [ly dataUsingEncoding:enc];
} }

View File

@ -781,8 +781,8 @@ VLSong::VLSong()
fProperties.push_back(defaultProperties); fProperties.push_back(defaultProperties);
fMeasures.resize(32); // Leadin, AABA fMeasures.resize(32); // Leadin, AABA
VLNote rest = VLRest(1); VLLyricsNote rest = VLLyricsNote(VLRest(1));
VLChord rchord; VLChord rchord;
rchord.fDuration = 1; rchord.fDuration = 1;
for (int i=0; i<32; ++i) { for (int i=0; i<32; ++i) {
@ -874,7 +874,7 @@ uint8_t & LastTie(VLMeasure & measure)
// //
// Dealing with notes is similar, but we also have to handle ties // Dealing with notes is similar, but we also have to handle ties
// //
void VLSong::AddNote(VLNote note, size_t measure, VLFraction at) void VLSong::AddNote(VLLyricsNote note, size_t measure, VLFraction at)
{ {
VLNoteList::iterator i = fMeasures[measure].fMelody.begin(); VLNoteList::iterator i = fMeasures[measure].fMelody.begin();
VLFraction t(0); VLFraction t(0);
@ -1017,6 +1017,25 @@ void VLSong::Transpose(int semi)
} }
} }
size_t VLSong::CountStanzas() const
{
size_t stanzas = 0;
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();
for (; i!=e; ++i)
if (i->fLyrics.size() > stanzas)
for (size_t s = stanzas; s < i->fLyrics.size(); ++s)
if (i->fLyrics[s])
stanzas = s+1;
}
return stanzas;
}
void VLSong::LilypondNotes(std::string & notes) const void VLSong::LilypondNotes(std::string & notes) const
{ {
notes = ""; notes = "";
@ -1064,3 +1083,113 @@ void VLSong::LilypondChords(std::string & chords) const
chords += '\n'; chords += '\n';
} }
} }
void VLSong::LilypondStanza(std::string & lyrics, size_t stanza) const
{
lyrics = "";
std::string sep;
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) {
if (i->fPitch == VLNote::kNoPitch
|| (i->fTied & VLNote::kTiedWithPrev)
) {
continue; // Rest or continuation note, skip
} else if (i->fLyrics.size() < stanza || !i->fLyrics[stanza-1]) {
lyrics += sep + "\\skip1";
} else {
lyrics += sep + i->fLyrics[stanza-1].fText;
if (i->fLyrics[stanza-1].fKind & VLSyllable::kHasNext)
lyrics += " --";
}
sep = " ";
}
if ((measure % 4) == 3) {
sep = "\n";
}
}
}
bool VLSong::FindWord(size_t & measure, VLFraction & at)
{
at += VLFraction(1,64);
return PrevWord(measure, at);
}
bool VLSong::PrevWord(size_t & measure, VLFraction & at)
{
#if 0
do {
VLMeasure & meas = fMeasures[measure];
VLNoteList::iterator note = fMeasures[measure].fMelody.begin();
VLNoteList::iterator end = fMeasures[measure].fMelody.end();
bool hasWord = false;
VLFraction word(0);
VLFraction now(0);
for (VLNoteList::iterator note = meas.fMelody.begin();
note != meas.fMelody.end() && now < at;
++note
) {
if (!(note->fTied & VLNote::kTiedWithPrev)
&& !(note->fHyphen & VLNote::kHyphenToPrev)
) {
word = now;
hasWord = true;
}
now += note->fDuration;
}
if (hasWord) {
at = word;
return true;
} else {
at = meas.fProperties->fTime;
}
} while (measure-- > 0);
#endif
return false;
}
bool VLSong::NextWord(size_t & measure, VLFraction & at)
{
#if 0
bool firstMeasure = true;
do {
VLMeasure & meas = fMeasures[measure];
VLNoteList::iterator note = fMeasures[measure].fMelody.begin();
VLNoteList::iterator end = fMeasures[measure].fMelody.end();
bool hasWord = false;
VLFraction word(0);
VLFraction now(0);
for (VLNoteList::iterator note = meas.fMelody.begin();
note != meas.fMelody.end();
++note
) {
if (!(note->fTied & VLNote::kTiedWithPrev)
&& !(note->fHyphen & VLNote::kHyphenToPrev)
&& (!firstMeasure || now > at)
) {
word = now;
hasWord = true;
}
now += note->fDuration;
}
if (hasWord) {
at = word;
return true;
} else {
firstMeasure = false;
}
} while (++measure < fMeasures.size());
#endif
return false;
}

View File

@ -98,6 +98,21 @@ inline bool operator>=(VLFraction one, VLFraction other)
class VLProperties; class VLProperties;
struct VLSyllable {
std::string fText; // Syllable text
uint8_t fKind; // Adjacency information
enum {
kSingle = 0,
kBegin = 1,
kEnd = 2,
kMiddle = 3,
kHasNext = 1,
kHasPrev = 2
};
operator bool() const { return fText.size() > 0; }
};
struct VLNote { struct VLNote {
VLFraction fDuration; VLFraction fDuration;
int8_t fPitch; // Semitones int8_t fPitch; // Semitones
@ -116,6 +131,7 @@ struct VLNote {
kTiedWithNext = 1, kTiedWithNext = 1,
kTiedWithPrev = 2 kTiedWithPrev = 2
}; };
VLNote() : fPitch(kNoPitch) {} VLNote() : fPitch(kNoPitch) {}
VLNote(VLFraction dur, int8_t pitch) VLNote(VLFraction dur, int8_t pitch)
: fDuration(dur), fPitch(pitch), fTied(kNotTied) : fDuration(dur), fPitch(pitch), fTied(kNotTied)
@ -205,20 +221,21 @@ struct VLProperties {
void VisualNote(VLFraction at, VLFraction actualDur, VLFraction *visualDur, bool * triplet) const; void VisualNote(VLFraction at, VLFraction actualDur, VLFraction *visualDur, bool * triplet) const;
}; };
struct VLSyllable { struct VLLyricsNote : VLNote {
std::string fText; VLLyricsNote() {}
bool fHyphen; // Followed by hyphen explicit VLLyricsNote(const VLNote & note)
{ *static_cast<VLNote *>(this) = note; }
std::vector<VLSyllable> fLyrics;
}; };
typedef std::list<VLChord> VLChordList; typedef std::list<VLChord> VLChordList;
typedef std::list<VLNote> VLNoteList; typedef std::list<VLLyricsNote> VLNoteList;
typedef std::list<VLSyllable> VLSyllList;
struct VLMeasure { struct VLMeasure {
VLProperties * fProperties; VLProperties * fProperties;
VLChordList fChords; VLChordList fChords;
VLNoteList fMelody; VLNoteList fMelody;
VLSyllList fLyrics;
VLMeasure(); VLMeasure();
@ -233,14 +250,20 @@ struct VLSong {
std::vector<VLMeasure> fMeasures; std::vector<VLMeasure> fMeasures;
void AddChord(VLChord chord, size_t measure, VLFraction at); void AddChord(VLChord chord, size_t measure, VLFraction at);
void AddNote(VLNote note, size_t measure, VLFraction at); void AddNote(VLLyricsNote note, size_t measure, VLFraction at);
void DelChord(size_t measure, VLFraction at); void DelChord(size_t measure, VLFraction at);
void DelNote(size_t measure, VLFraction at); void DelNote(size_t measure, VLFraction at);
void Transpose(int semitones); void Transpose(int semitones);
bool FindWord(size_t & measure, VLFraction & at);
bool PrevWord(size_t & measure, VLFraction & at);
bool NextWord(size_t & measure, VLFraction & at);
size_t CountMeasures() const { return fMeasures.size(); } size_t CountMeasures() const { return fMeasures.size(); }
size_t CountStanzas() const;
void LilypondNotes(std::string & notes) const; void LilypondNotes(std::string & notes) const;
void LilypondChords(std::string & chords) const; void LilypondChords(std::string & chords) const;
void LilypondStanza(std::string & lyrics, size_t stanza) const;
}; };
// Local Variables: // Local Variables:

View File

@ -23,7 +23,7 @@
if (fClickMode == 'k') if (fClickMode == 'k')
[self song]->DelNote(fCursorMeasure, fCursorAt); [self song]->DelNote(fCursorMeasure, fCursorAt);
else else
[self song]->AddNote(newNote, fCursorMeasure, fCursorAt); [self song]->AddNote(VLLyricsNote(newNote), fCursorMeasure, fCursorAt);
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
[[self document] updateChangeCount:NSChangeDone]; [[self document] updateChangeCount:NSChangeDone];

View File

@ -146,6 +146,38 @@ const char * sSteps = "C DbD EbE F GbG AbA BbB ";
return note; return note;
} }
- (NSXMLElement *)syllable:(const VLSyllable *)syllable inStanza:(int)stanza
{
NSString * syll;
switch (syllable->fKind) {
default:
case VLSyllable::kSingle:
syll = @"single";
break;
case VLSyllable::kBegin:
syll = @"begin";
break;
case VLSyllable::kEnd:
syll = @"end";
break;
case VLSyllable::kMiddle:
syll = @"middle";
break;
}
NSString * text = [NSString stringWithUTF8String:syllable->fText.c_str()];
NSXMLNode* num = [NSXMLNode attributeWithName:@"number"
stringValue:[NSString stringWithFormat:@"%d",
stanza]];
return [NSXMLNode
elementWithName:@"lyric"
children: [NSArray arrayWithObjects:
[NSXMLNode elementWithName:@"syllabic" stringValue:syll],
[NSXMLNode elementWithName:@"text" stringValue:text],
nil]
attributes: [NSArray arrayWithObject:num]];
}
- (void)addNotes:(VLNoteList *)notes toMeasure:(NSXMLElement *)meas - (void)addNotes:(VLNoteList *)notes toMeasure:(NSXMLElement *)meas
{ {
VLFraction resolution(1, song->fProperties.front().fDivisions*4); VLFraction resolution(1, song->fProperties.front().fDivisions*4);
@ -156,7 +188,13 @@ const char * sSteps = "C DbD EbE F GbG AbA BbB ";
) { ) {
VLFraction u = note->fDuration / resolution; VLFraction u = note->fDuration / resolution;
int units = (u.fNum+u.fDenom/2)/u.fDenom; int units = (u.fNum+u.fDenom/2)/u.fDenom;
[meas addChild:[self noteWithPitch:note->fPitch duration:units useSharps:useSharps]]; NSXMLElement*n =
[self noteWithPitch:note->fPitch duration:units useSharps:useSharps];
for (size_t i=0; i<note->fLyrics.size(); ++i)
if (note->fLyrics[i])
[n addChild:[self syllable:&note->fLyrics[i] inStanza:i+1]];
[meas addChild:n];
} }
} }
@ -325,10 +363,10 @@ int8_t sStepToPitch[] = {
9, 11, 0, 2, 4, 5, 7 9, 11, 0, 2, 4, 5, 7
}; };
- (VLNote) readNote:(NSXMLElement*)note withUnit:(VLFraction)unit - (VLLyricsNote) readNote:(NSXMLElement*)note withUnit:(VLFraction)unit
{ {
NSError * outError; NSError * outError;
VLNote n; VLLyricsNote n;
n.fTied = 0; n.fTied = 0;
@ -343,6 +381,25 @@ int8_t sStepToPitch[] = {
n.fPitch += [[alter stringValue] intValue]; n.fPitch += [[alter stringValue] intValue];
} }
n.fDuration = VLFraction([note intForXPath:@"./duration" error:&outError])*unit; n.fDuration = VLFraction([note intForXPath:@"./duration" error:&outError])*unit;
NSEnumerator * e = [[note elementsForName:@"lyric"] objectEnumerator];
for (NSXMLElement * lyric; lyric = [e nextObject]; ) {
int stanza = [[[lyric attributeForName:@"number"]
stringValue] intValue];
if (stanza > n.fLyrics.size())
n.fLyrics.resize(stanza);
--stanza;
NSString * kind = [lyric stringForXPath:@"./syllabic" error:&outError];
if ([kind isEqual:@"begin"])
n.fLyrics[stanza].fKind = VLSyllable::kBegin;
else if ([kind isEqual:@"end"])
n.fLyrics[stanza].fKind = VLSyllable::kEnd;
else if ([kind isEqual:@"middle"])
n.fLyrics[stanza].fKind = VLSyllable::kMiddle;
else
n.fLyrics[stanza].fKind = VLSyllable::kSingle;
n.fLyrics[stanza].fText =
[[lyric stringForXPath:@"./text" error:&outError] UTF8String];
}
return n; return n;
} }
@ -364,7 +421,7 @@ int8_t sStepToPitch[] = {
NSEnumerator * n = [[measure elementsForName:@"note"] objectEnumerator]; NSEnumerator * n = [[measure elementsForName:@"note"] objectEnumerator];
for (NSXMLElement * note; note = [n nextObject]; ) { for (NSXMLElement * note; note = [n nextObject]; ) {
VLNote n = [self readNote:note withUnit:unit]; VLLyricsNote n = [self readNote:note withUnit:unit];
if (n.fPitch != VLNote::kNoPitch) if (n.fPitch != VLNote::kNoPitch)
song->AddNote(n, m, at); song->AddNote(n, m, at);
at += n.fDuration; at += n.fDuration;