mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 19:23:59 +00:00
Low level lyrics support
This commit is contained in:
parent
fbf53052e3
commit
5ce21f2f78
|
@ -17,7 +17,7 @@
|
|||
|
||||
@implementation NSMutableString (VLLilypond)
|
||||
|
||||
- (void) substituteMacro:(NSString *)m withValue:(NSString *)value
|
||||
- (void) substituteMacro:(NSString *)m withValue:(NSString *)value repeat:(BOOL)repeat
|
||||
{
|
||||
if ([value isEqual:@""])
|
||||
return;
|
||||
|
@ -26,10 +26,12 @@
|
|||
[value rangeOfCharacterFromSet:
|
||||
[NSCharacterSet characterSetWithCharactersInString:@"\n"]];
|
||||
BOOL hasEOL= range.location != NSNotFound;
|
||||
unsigned from = 0;
|
||||
|
||||
for (range = [self rangeOfString:macro];
|
||||
range.location != NSNotFound;
|
||||
range = [self rangeOfString:macro]
|
||||
range = [self rangeOfString:macro options:0
|
||||
range:NSMakeRange(from, [self length]-from)]
|
||||
) {
|
||||
if (hasEOL) {
|
||||
//
|
||||
|
@ -49,27 +51,43 @@
|
|||
[[self substringWithRange:suffix]
|
||||
stringByTrimmingCharactersInSet:
|
||||
[NSCharacterSet whitespaceCharacterSet]];
|
||||
NSString * nl;
|
||||
if ([nonBlank length]) {
|
||||
NSRange nb = [pfxStr rangeOfString:nonBlank];
|
||||
prefix.length = nb.location - prefix.location;
|
||||
prefix.length = nb.location;
|
||||
pfxStr =
|
||||
[[self substringWithRange:prefix]
|
||||
stringByAppendingString:@" "];
|
||||
sfxStr = [NSString stringWithFormat:@"\n%@", pfxStr];
|
||||
nl = @"\n";
|
||||
} else {
|
||||
range = line;
|
||||
nl = @"";
|
||||
}
|
||||
NSArray * lines = [value componentsSeparatedByString:@"\n"];
|
||||
value =
|
||||
[NSString stringWithFormat:@"%@%@%@", pfxStr,
|
||||
[NSString stringWithFormat:@"%@%@%@%@", nl, pfxStr,
|
||||
[lines componentsJoinedByString:
|
||||
[@"\n" stringByAppendingString:pfxStr]],
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)substituteMacro:(NSString*)macro withValue:(NSString*)value
|
||||
{
|
||||
[self substituteMacro:macro withValue:value repeat:NO];
|
||||
}
|
||||
|
||||
- (void) purgeMacros
|
||||
{
|
||||
for (NSRange range = [self rangeOfString:@"<{"];
|
||||
|
@ -87,7 +105,7 @@
|
|||
const int kMajorOffset = 6;
|
||||
const int kMinorOffset = 9;
|
||||
|
||||
const char * sKeyNames[] = {
|
||||
static const char * sKeyNames[] = {
|
||||
"ges", "des", "as", "es", "bes", "f",
|
||||
"c", "g", "d", "a", "e", "b", "fis", "cis", "gis"
|
||||
};
|
||||
|
@ -127,6 +145,13 @@ const char * sKeyNames[] = {
|
|||
song->LilypondNotes(lys);
|
||||
[ly substituteMacro:@"NOTES" withValue:
|
||||
[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];
|
||||
return [ly dataUsingEncoding:enc];
|
||||
}
|
||||
|
|
|
@ -781,8 +781,8 @@ VLSong::VLSong()
|
|||
fProperties.push_back(defaultProperties);
|
||||
fMeasures.resize(32); // Leadin, AABA
|
||||
|
||||
VLNote rest = VLRest(1);
|
||||
VLChord rchord;
|
||||
VLLyricsNote rest = VLLyricsNote(VLRest(1));
|
||||
VLChord rchord;
|
||||
rchord.fDuration = 1;
|
||||
|
||||
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
|
||||
//
|
||||
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();
|
||||
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
|
||||
{
|
||||
notes = "";
|
||||
|
@ -1064,3 +1083,113 @@ void VLSong::LilypondChords(std::string & chords) const
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -98,6 +98,21 @@ inline bool operator>=(VLFraction one, VLFraction other)
|
|||
|
||||
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 {
|
||||
VLFraction fDuration;
|
||||
int8_t fPitch; // Semitones
|
||||
|
@ -116,6 +131,7 @@ struct VLNote {
|
|||
kTiedWithNext = 1,
|
||||
kTiedWithPrev = 2
|
||||
};
|
||||
|
||||
VLNote() : fPitch(kNoPitch) {}
|
||||
VLNote(VLFraction dur, int8_t pitch)
|
||||
: fDuration(dur), fPitch(pitch), fTied(kNotTied)
|
||||
|
@ -205,20 +221,21 @@ struct VLProperties {
|
|||
void VisualNote(VLFraction at, VLFraction actualDur, VLFraction *visualDur, bool * triplet) const;
|
||||
};
|
||||
|
||||
struct VLSyllable {
|
||||
std::string fText;
|
||||
bool fHyphen; // Followed by hyphen
|
||||
struct VLLyricsNote : VLNote {
|
||||
VLLyricsNote() {}
|
||||
explicit VLLyricsNote(const VLNote & note)
|
||||
{ *static_cast<VLNote *>(this) = note; }
|
||||
|
||||
std::vector<VLSyllable> fLyrics;
|
||||
};
|
||||
|
||||
typedef std::list<VLChord> VLChordList;
|
||||
typedef std::list<VLNote> VLNoteList;
|
||||
typedef std::list<VLSyllable> VLSyllList;
|
||||
typedef std::list<VLLyricsNote> VLNoteList;
|
||||
|
||||
struct VLMeasure {
|
||||
VLProperties * fProperties;
|
||||
VLChordList fChords;
|
||||
VLNoteList fMelody;
|
||||
VLSyllList fLyrics;
|
||||
|
||||
VLMeasure();
|
||||
|
||||
|
@ -233,14 +250,20 @@ struct VLSong {
|
|||
std::vector<VLMeasure> fMeasures;
|
||||
|
||||
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 DelNote(size_t measure, VLFraction at);
|
||||
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 CountStanzas() const;
|
||||
void LilypondNotes(std::string & notes) const;
|
||||
void LilypondChords(std::string & chords) const;
|
||||
void LilypondStanza(std::string & lyrics, size_t stanza) const;
|
||||
};
|
||||
|
||||
// Local Variables:
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
if (fClickMode == 'k')
|
||||
[self song]->DelNote(fCursorMeasure, fCursorAt);
|
||||
else
|
||||
[self song]->AddNote(newNote, fCursorMeasure, fCursorAt);
|
||||
[self song]->AddNote(VLLyricsNote(newNote), fCursorMeasure, fCursorAt);
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[[self document] updateChangeCount:NSChangeDone];
|
||||
|
|
|
@ -146,6 +146,38 @@ const char * sSteps = "C DbD EbE F GbG AbA BbB ";
|
|||
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
|
||||
{
|
||||
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;
|
||||
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:¬e->fLyrics[i] inStanza:i+1]];
|
||||
|
||||
[meas addChild:n];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,10 +363,10 @@ int8_t sStepToPitch[] = {
|
|||
9, 11, 0, 2, 4, 5, 7
|
||||
};
|
||||
|
||||
- (VLNote) readNote:(NSXMLElement*)note withUnit:(VLFraction)unit
|
||||
- (VLLyricsNote) readNote:(NSXMLElement*)note withUnit:(VLFraction)unit
|
||||
{
|
||||
NSError * outError;
|
||||
VLNote n;
|
||||
NSError * outError;
|
||||
VLLyricsNote n;
|
||||
|
||||
n.fTied = 0;
|
||||
|
||||
|
@ -343,6 +381,25 @@ int8_t sStepToPitch[] = {
|
|||
n.fPitch += [[alter stringValue] intValue];
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -364,7 +421,7 @@ int8_t sStepToPitch[] = {
|
|||
NSEnumerator * n = [[measure elementsForName:@"note"] objectEnumerator];
|
||||
|
||||
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)
|
||||
song->AddNote(n, m, at);
|
||||
at += n.fDuration;
|
||||
|
|
Loading…
Reference in New Issue
Block a user