mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 11:14:00 +00:00
Load / save / translate repeats & endings
This commit is contained in:
parent
5df833b2bf
commit
638ba81948
|
@ -29,8 +29,11 @@
|
|||
mmaFile += '\n';
|
||||
|
||||
std::string mmas;
|
||||
for (size_t m=0; m<song->CountMeasures(); ++m) {
|
||||
sprintf(buf, "%-5d", m+1);
|
||||
size_t meas = 0;
|
||||
VLSong::iterator end = song->end();
|
||||
for (VLSong::iterator i=song->begin(); i!=end; ++i) {
|
||||
size_t m = *i;
|
||||
sprintf(buf, "%-5d", ++meas);
|
||||
mmaFile += buf;
|
||||
song->fMeasures[m].MMAChords(mmas, prop);
|
||||
mmaFile += mmas;
|
||||
|
|
|
@ -1043,12 +1043,50 @@ size_t VLSong::CountStanzas() const
|
|||
|
||||
void VLSong::LilypondNotes(std::string & notes) const
|
||||
{
|
||||
notes = "";
|
||||
notes = "";
|
||||
std::string indent = "";
|
||||
size_t seenEnding = 0;
|
||||
int numEndings = 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();
|
||||
VLFraction at(0);
|
||||
|
||||
int times;
|
||||
size_t volta;
|
||||
bool repeat;
|
||||
|
||||
if (DoesBeginRepeat(measure, ×)) {
|
||||
char volta[8];
|
||||
sprintf(volta, "%d", times);
|
||||
notes = notes + "\\repeat volta "+volta+" {\n";
|
||||
indent = " ";
|
||||
seenEnding = 0;
|
||||
numEndings = 0;
|
||||
}
|
||||
if (DoesEndRepeat(measure)) {
|
||||
notes += "}\n";
|
||||
indent = "";
|
||||
}
|
||||
if (DoesBeginEnding(measure, &repeat, &volta)) {
|
||||
notes += seenEnding ? "}{\n" : "} \\alternative {{\n";
|
||||
notes += " \\set Score.repeatCommands = #'((volta \"";
|
||||
const char * comma = "";
|
||||
for (int r=0; r<8; ++r)
|
||||
if (volta & (1<<r)) {
|
||||
char volta[8];
|
||||
sprintf(volta, "%s%d.", comma, r+1);
|
||||
comma = ", ";
|
||||
notes += volta;
|
||||
}
|
||||
notes = notes + "\")" + (repeat ? "" : " end-repeat") + ")\n";
|
||||
seenEnding |= volta;
|
||||
++numEndings;
|
||||
} else if (DoesEndEnding(measure)) {
|
||||
notes += "}}\n";
|
||||
indent = "";
|
||||
}
|
||||
notes += indent;
|
||||
for (; i!=e; ++i) {
|
||||
std::string note;
|
||||
i->LilypondName(note, at, fProperties[fMeasures[measure].fPropIdx]);
|
||||
|
@ -1452,11 +1490,15 @@ bool VLSong::CanBeEnding(size_t beginMeasure, size_t endMeasure,
|
|||
return false;
|
||||
}
|
||||
|
||||
bool VLSong::DoesBeginRepeat(size_t measure) const
|
||||
bool VLSong::DoesBeginRepeat(size_t measure, int * times) const
|
||||
{
|
||||
for (size_t r=0; r<fRepeats.size(); ++r)
|
||||
if (fRepeats[r].fEndings[0].fBegin == measure)
|
||||
if (fRepeats[r].fEndings[0].fBegin == measure) {
|
||||
if (times)
|
||||
*times = fRepeats[r].fTimes;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1466,64 +1508,141 @@ bool VLSong::DoesEndRepeat(size_t measure, int * times) const
|
|||
if (fRepeats[r].fEndings[0].fEnd == measure
|
||||
&& fRepeats[r].fEndings.size() == 1
|
||||
) {
|
||||
if (times)
|
||||
*times = fRepeats[r].fTimes;
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VLSong::DoesBeginEnding(size_t measure, size_t * volta) const
|
||||
bool VLSong::DoesBeginEnding(size_t measure, bool * repeat, size_t * volta) const
|
||||
{
|
||||
for (size_t r=0; r<fRepeats.size(); ++r)
|
||||
if (fRepeats[r].fEndings[0].fEnd >= measure
|
||||
&& fRepeats[r].fEndings.size() > 1
|
||||
) {
|
||||
*volta = (1<<fRepeats[r].fTimes)-1;
|
||||
size_t v = (1<<fRepeats[r].fTimes)-1;
|
||||
for (size_t e=1; e<fRepeats[r].fEndings.size(); ++e)
|
||||
if (fRepeats[r].fEndings[e].fBegin == measure) {
|
||||
*volta = fRepeats[r].fEndings[e].fVolta;
|
||||
if (repeat)
|
||||
if (e == fRepeats[r].fEndings.size()-1
|
||||
&& fRepeats[r].fEndings[e].fVolta == v
|
||||
)
|
||||
*repeat = false; // Not after last alternative
|
||||
else
|
||||
*repeat = true;
|
||||
if (volta)
|
||||
*volta = fRepeats[r].fEndings[e].fVolta;
|
||||
|
||||
return true;
|
||||
} else
|
||||
*volta&= ~fRepeats[r].fEndings[e].fVolta;
|
||||
if (*volta && fRepeats[r].fEndings[0].fEnd == measure) {
|
||||
v &= ~fRepeats[r].fEndings[e].fVolta;
|
||||
if (v && fRepeats[r].fEndings[0].fEnd == measure) {
|
||||
//
|
||||
// Implied ending for all not mentioned
|
||||
//
|
||||
//
|
||||
if (repeat)
|
||||
*repeat = false;
|
||||
if (volta)
|
||||
*volta = v;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VLSong::DoesEndEnding(size_t measure, bool * repeat) const
|
||||
bool VLSong::DoesEndEnding(size_t measure, bool * repeat, size_t * volta) const
|
||||
{
|
||||
for (size_t r=0; r<fRepeats.size(); ++r)
|
||||
if (fRepeats[r].fEndings[0].fEnd+1 >= measure
|
||||
&& fRepeats[r].fEndings.size() > 1
|
||||
) {
|
||||
size_t volta = (1<<fRepeats[r].fTimes)-1;
|
||||
size_t v = (1<<fRepeats[r].fTimes)-1;
|
||||
for (size_t e=1; e<fRepeats[r].fEndings.size(); ++e)
|
||||
if (fRepeats[r].fEndings[e].fEnd == measure) {
|
||||
if (e == fRepeats[r].fEndings.size()-1
|
||||
&& fRepeats[r].fEndings[e].fVolta == volta
|
||||
)
|
||||
*repeat = false; // Don't repeat after last alternative
|
||||
else
|
||||
*repeat = true;
|
||||
|
||||
if (repeat)
|
||||
if (e == fRepeats[r].fEndings.size()-1
|
||||
&& fRepeats[r].fEndings[e].fVolta == v
|
||||
)
|
||||
*repeat = false; // Not after last alternative
|
||||
else
|
||||
*repeat = true;
|
||||
if (volta)
|
||||
*volta = fRepeats[r].fEndings[e].fVolta;
|
||||
return true;
|
||||
} else
|
||||
volta &= ~fRepeats[r].fEndings[e].fVolta;
|
||||
if (volta && fRepeats[r].fEndings[0].fEnd+1 == measure) {
|
||||
v &= ~fRepeats[r].fEndings[e].fVolta;
|
||||
if (v && fRepeats[r].fEndings[0].fEnd+1 == measure) {
|
||||
//
|
||||
// Implied ending for all not mentioned
|
||||
//
|
||||
*repeat = false;
|
||||
if (repeat)
|
||||
*repeat = false;
|
||||
if (volta)
|
||||
*volta = v;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
VLSong::iterator::iterator(const VLSong & song, bool end)
|
||||
: fSong(song)
|
||||
{
|
||||
fMeasure = end ? fSong.CountMeasures() : 0;
|
||||
AdjustStatus();
|
||||
}
|
||||
|
||||
VLSong::iterator & VLSong::iterator::operator++()
|
||||
{
|
||||
++fMeasure;
|
||||
AdjustStatus();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void VLSong::iterator::AdjustStatus()
|
||||
{
|
||||
int times;
|
||||
size_t volta;
|
||||
bool repeat;
|
||||
if (fSong.DoesEndRepeat(fMeasure)
|
||||
|| (fSong.DoesEndEnding(fMeasure, &repeat) && repeat)
|
||||
) {
|
||||
if (++fStatus.back().fVolta < fStatus.back().fTimes) {
|
||||
//
|
||||
// Repeat again
|
||||
//
|
||||
fMeasure = fStatus.back().fBegin;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (fMeasure == fSong.CountMeasures())
|
||||
while (fStatus.size())
|
||||
if (++fStatus.back().fVolta < fStatus.back().fTimes) {
|
||||
fMeasure = fStatus.back().fBegin;
|
||||
|
||||
return;
|
||||
} else
|
||||
fStatus.pop_back();
|
||||
while (fSong.DoesBeginEnding(fMeasure, 0, &volta)) {
|
||||
if (!(volta & (1<<fStatus.back().fVolta))) {
|
||||
//
|
||||
// Skip this ending this time around
|
||||
//
|
||||
do {
|
||||
++fMeasure;
|
||||
} while (!fSong.DoesEndEnding(fMeasure));
|
||||
} else
|
||||
break;
|
||||
}
|
||||
if (fSong.DoesBeginRepeat(fMeasure, ×)) {
|
||||
if (fStatus.size() && fStatus.back().fVolta == fStatus.back().fTimes)
|
||||
fStatus.pop_back();
|
||||
fStatus.push_back(Repeat(fMeasure, times));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -264,6 +264,45 @@ struct VLSong {
|
|||
std::vector<VLMeasure> fMeasures;
|
||||
std::vector<VLRepeat> fRepeats;
|
||||
|
||||
//
|
||||
// Iterate over measures in performance order
|
||||
//
|
||||
class iterator {
|
||||
public:
|
||||
size_t operator*() { return fMeasure; }
|
||||
iterator & operator++();
|
||||
bool operator==(const iterator & other) const {
|
||||
return fMeasure==other.fMeasure && fStatus == other.fStatus;
|
||||
}
|
||||
bool operator!=(const iterator & other) const {
|
||||
return fMeasure!=other.fMeasure || fStatus != other.fStatus;
|
||||
}
|
||||
protected:
|
||||
friend class VLSong;
|
||||
iterator(const VLSong & song, bool end);
|
||||
private:
|
||||
size_t fMeasure;
|
||||
const VLSong & fSong;
|
||||
struct Repeat {
|
||||
Repeat(size_t begin, int times)
|
||||
: fBegin(begin), fTimes(times), fVolta(0) {}
|
||||
size_t fBegin;
|
||||
int8_t fTimes;
|
||||
int8_t fVolta;
|
||||
|
||||
bool operator==(const Repeat & other) const {
|
||||
return fBegin==other.fBegin && fVolta == other.fVolta;
|
||||
}
|
||||
bool operator!=(const Repeat & other) const {
|
||||
return fBegin!=other.fBegin || fVolta != other.fVolta;
|
||||
}
|
||||
};
|
||||
std::vector<Repeat> fStatus;
|
||||
|
||||
void AdjustStatus();
|
||||
};
|
||||
iterator begin() { return iterator(*this, false); }
|
||||
iterator end() { return iterator(*this, true); }
|
||||
|
||||
void AddChord(VLChord chord, size_t measure, VLFraction at);
|
||||
void AddNote(VLLyricsNote note, size_t measure, VLFraction at);
|
||||
|
@ -276,10 +315,10 @@ struct VLSong {
|
|||
bool CanBeRepeat(size_t beginMeasure, size_t endMeasure, int * times = 0);
|
||||
bool CanBeEnding(size_t beginMeasure, size_t endMeasure,
|
||||
size_t * volta = 0, size_t * voltaOK = 0);
|
||||
bool DoesBeginRepeat(size_t measure) const;
|
||||
bool DoesEndRepeat(size_t measure, int * times) const;
|
||||
bool DoesBeginEnding(size_t measure, size_t * volta) const;
|
||||
bool DoesEndEnding(size_t measure, bool * repeat) const;
|
||||
bool DoesBeginRepeat(size_t measure, int * times = 0) const;
|
||||
bool DoesEndRepeat(size_t measure, int * times = 0) const;
|
||||
bool DoesBeginEnding(size_t measure, bool * repeat = 0, size_t * volta = 0) const;
|
||||
bool DoesEndEnding(size_t measure, bool * repeat = 0, size_t * volta = 0) const;
|
||||
void Transpose(int semitones);
|
||||
|
||||
bool FindWord(size_t stanza, size_t & measure, VLFraction & at);
|
||||
|
|
|
@ -332,10 +332,9 @@ VLMusicElement sSemi2Accidental[12][12] = {
|
|||
const float x = fClefKeyW+measure*fMeasureW;
|
||||
const float yy = kSystemY+4.0f*kLineH;
|
||||
bool repeat;
|
||||
int times;
|
||||
size_t volta;
|
||||
bool dotsPrecede= measure != 0 &&
|
||||
(song->DoesEndRepeat(m, ×)
|
||||
(song->DoesEndRepeat(m)
|
||||
|| (song->DoesEndEnding(m, &repeat) && repeat));
|
||||
bool dotsFollow = measure<fMeasPerSystem && song->DoesBeginRepeat(m);
|
||||
if (!dotsPrecede && !dotsFollow) {
|
||||
|
@ -384,7 +383,7 @@ VLMusicElement sSemi2Accidental[12][12] = {
|
|||
[bz removeAllPoints];
|
||||
}
|
||||
if (measure<fMeasPerSystem) {
|
||||
if (song->DoesBeginEnding(m, &volta)) {
|
||||
if (song->DoesBeginEnding(m, 0, &volta)) {
|
||||
[bz setLineWidth:kThin];
|
||||
[bz moveToPoint: NSMakePoint(x+kDblLineOff, yy+0.5f*kLineH)];
|
||||
[bz lineToPoint: NSMakePoint(x+kDblLineOff, yy+2.0f*kLineH)];
|
||||
|
|
|
@ -295,6 +295,98 @@ const char * sSteps = "C DbD EbE F GbG AbA BbB ";
|
|||
|
||||
NSXMLElement * harMeas = [melMeas copy];
|
||||
|
||||
size_t volta;
|
||||
bool repeat;
|
||||
int times;
|
||||
if (song->DoesBeginRepeat(measure)) {
|
||||
NSXMLElement * barline = [NSXMLNode elementWithName:@"barline"];
|
||||
[barline addAttribute: [NSXMLNode attributeWithName:@"location"
|
||||
stringValue:@"left"]];
|
||||
NSString * style = @"heavy-light";
|
||||
if (song->DoesEndRepeat(measure)
|
||||
|| (song->DoesEndEnding(measure, &repeat) && repeat)
|
||||
)
|
||||
style = @"heavy-heavy";
|
||||
[barline addChild: [NSXMLNode elementWithName:@"bar-style"
|
||||
stringValue:style]];
|
||||
NSXMLElement * repeat = [NSXMLNode elementWithName:@"repeat"];
|
||||
[repeat addAttribute: [NSXMLNode attributeWithName:@"direction"
|
||||
stringValue:@"forward"]];
|
||||
[barline addChild:repeat];
|
||||
[melMeas addChild:barline];
|
||||
}
|
||||
if (song->DoesBeginEnding(measure, 0, &volta)) {
|
||||
NSXMLElement * barline = [NSXMLNode elementWithName:@"barline"];
|
||||
[barline addAttribute: [NSXMLNode attributeWithName:@"location"
|
||||
stringValue:@"left"]];
|
||||
NSXMLElement * ending = [NSXMLNode elementWithName:@"ending"];
|
||||
[ending addAttribute: [NSXMLNode attributeWithName:@"type"
|
||||
stringValue:@"start"]];
|
||||
NSString * number = nil;
|
||||
for (size_t i = 0; i<8; ++i)
|
||||
if (volta & (1<<i))
|
||||
if (number)
|
||||
number = [NSString stringWithFormat:@"%@,%d",
|
||||
number, i+1];
|
||||
else
|
||||
number = [NSString stringWithFormat:@"%d", i+1];
|
||||
[ending addAttribute: [NSXMLNode attributeWithName:@"number"
|
||||
stringValue:number]];
|
||||
[barline addChild:ending];
|
||||
[melMeas addChild:barline];
|
||||
}
|
||||
if (song->DoesEndRepeat(measure+1, ×)) {
|
||||
NSXMLElement * barline = [NSXMLNode elementWithName:@"barline"];
|
||||
[barline addAttribute: [NSXMLNode attributeWithName:@"location"
|
||||
stringValue:@"right"]];
|
||||
NSString * style = @"light-heavy";
|
||||
if (song->DoesBeginRepeat(measure+1))
|
||||
style = @"heavy-heavy";
|
||||
[barline addChild: [NSXMLNode elementWithName:@"bar-style"
|
||||
stringValue:style]];
|
||||
NSXMLElement * repeat = [NSXMLNode elementWithName:@"repeat"];
|
||||
[repeat addAttribute: [NSXMLNode attributeWithName:@"direction"
|
||||
stringValue:@"backward"]];
|
||||
[repeat addAttribute:
|
||||
[NSXMLNode attributeWithName:@"times"
|
||||
stringValue:[NSString stringWithFormat:@"%d", times]]];
|
||||
[barline addChild:repeat];
|
||||
[melMeas addChild:barline];
|
||||
}
|
||||
if (song->DoesEndEnding(measure+1, &repeat, &volta)) {
|
||||
NSXMLElement * barline = [NSXMLNode elementWithName:@"barline"];
|
||||
[barline addAttribute: [NSXMLNode attributeWithName:@"location"
|
||||
stringValue:@"right"]];
|
||||
if (repeat) {
|
||||
NSString * style = @"light-heavy";
|
||||
if (song->DoesBeginRepeat(measure+1))
|
||||
style = @"heavy-heavy";
|
||||
[barline addChild: [NSXMLNode elementWithName:@"bar-style"
|
||||
stringValue:style]];
|
||||
NSXMLElement * repeat = [NSXMLNode elementWithName:@"repeat"];
|
||||
[repeat addAttribute: [NSXMLNode attributeWithName:@"direction"
|
||||
stringValue:@"backward"]];
|
||||
[barline addChild:repeat];
|
||||
}
|
||||
NSXMLElement * ending = [NSXMLNode elementWithName:@"ending"];
|
||||
[ending addAttribute:
|
||||
[NSXMLNode attributeWithName:@"type"
|
||||
stringValue:repeat ? @"stop" : @"discontinue"]];
|
||||
NSString * number = nil;
|
||||
for (size_t i = 0; i<8; ++i)
|
||||
if (volta & (1<<i))
|
||||
if (number)
|
||||
number = [NSString stringWithFormat:@"%@,%d",
|
||||
number, i+1];
|
||||
else
|
||||
number = [NSString stringWithFormat:@"%d", i+1];
|
||||
[ending addAttribute: [NSXMLNode attributeWithName:@"number"
|
||||
stringValue:number]];
|
||||
[barline addChild:ending];
|
||||
|
||||
[melMeas addChild:barline];
|
||||
}
|
||||
|
||||
[self addNotes:&song->fMeasures[measure].fMelody toMeasure:melMeas];
|
||||
[self addChords:&song->fMeasures[measure].fChords toMeasure:harMeas];
|
||||
|
||||
|
@ -404,10 +496,72 @@ int8_t sStepToPitch[] = {
|
|||
return n;
|
||||
}
|
||||
|
||||
- (void) addRepeat:(VLRepeat *)repeat
|
||||
{
|
||||
size_t end = repeat->fEndings.size() > 1
|
||||
? repeat->fEndings[1].fBegin : repeat->fEndings[0].fEnd;
|
||||
song->AddRepeat(repeat->fEndings[0].fBegin, end, repeat->fTimes);
|
||||
for (size_t e = 1; e<repeat->fEndings.size(); ++e)
|
||||
song->AddEnding(repeat->fEndings[e].fBegin, repeat->fEndings[e].fEnd,
|
||||
repeat->fEndings[e].fVolta);
|
||||
}
|
||||
|
||||
- (void) readBarlines:(NSArray *)barlines measure:(int)m
|
||||
repeat:(VLRepeat *)repeat inRepeat:(bool *)inRepeat
|
||||
error:(NSError **)outError
|
||||
{
|
||||
NSEnumerator * e = [barlines objectEnumerator];
|
||||
for (NSXMLElement * barline; barline = [e nextObject]; ) {
|
||||
NSXMLElement * rep = [barline nodeForXPath:@"./repeat" error:outError];
|
||||
NSXMLElement * ending = [barline nodeForXPath:@"./ending" error:outError];
|
||||
NSString * direction = nil;
|
||||
if (rep)
|
||||
direction = [rep stringForXPath:@"./@direction" error:outError];
|
||||
NSString * endingType = nil;
|
||||
size_t volta = 0;
|
||||
int maxEnding = 0;
|
||||
if (ending) {
|
||||
endingType = [ending stringForXPath:@"./@type" error:outError];
|
||||
NSEnumerator * n=
|
||||
[[[ending stringForXPath:@"./@number" error:outError]
|
||||
componentsSeparatedByString:@","] objectEnumerator];
|
||||
for (NSString * num; num = [n nextObject]; ) {
|
||||
int n = [num intValue];
|
||||
maxEnding = n;
|
||||
volta |= 1<<(n-1);
|
||||
}
|
||||
}
|
||||
|
||||
if ([direction isEqual:@"forward"]) {
|
||||
//
|
||||
// New repeat, add old one if there was one
|
||||
//
|
||||
if (*inRepeat)
|
||||
[self addRepeat:repeat];
|
||||
*inRepeat = true;
|
||||
repeat->fTimes = 0;
|
||||
repeat->fEndings.clear();
|
||||
repeat->fEndings.push_back(VLRepeat::Ending(m, 0, 0));
|
||||
} else if ([endingType isEqual:@"start"]) {
|
||||
repeat->fTimes = std::max<int8_t>(repeat->fTimes, maxEnding);
|
||||
repeat->fEndings.push_back(VLRepeat::Ending(m, 0, volta));
|
||||
} else if (endingType) {
|
||||
repeat->fEndings.back().fEnd = m+1;
|
||||
repeat->fEndings[0].fEnd = m+1;
|
||||
} else if (direction) {
|
||||
repeat->fTimes = [rep intForXPath:@"./@times" error:outError];
|
||||
repeat->fEndings[0].fEnd = m+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) readMelody:(NSArray *)measures error:(NSError **)outError
|
||||
{
|
||||
NSEnumerator * e = [measures objectEnumerator];
|
||||
|
||||
VLRepeat repeat;
|
||||
bool inRepeat = false;
|
||||
|
||||
for (NSXMLElement * measure; measure = [e nextObject]; ) {
|
||||
VLProperties & prop = song->fProperties.front();
|
||||
VLFraction unit(1, 4*prop.fDivisions);
|
||||
|
@ -418,6 +572,9 @@ int8_t sStepToPitch[] = {
|
|||
if (m >= song->CountMeasures())
|
||||
song->fMeasures.resize(m);
|
||||
|
||||
[self readBarlines:[measure elementsForName:@"barline"] measure:m
|
||||
repeat:&repeat inRepeat:&inRepeat error:outError];
|
||||
|
||||
NSEnumerator * n = [[measure elementsForName:@"note"] objectEnumerator];
|
||||
|
||||
for (NSXMLElement * note; note = [n nextObject]; ) {
|
||||
|
@ -427,6 +584,8 @@ int8_t sStepToPitch[] = {
|
|||
at += n.fDuration;
|
||||
}
|
||||
}
|
||||
if (inRepeat)
|
||||
[self addRepeat:&repeat];
|
||||
}
|
||||
|
||||
- (void) readChords:(NSArray *)measures error:(NSError **)outError
|
||||
|
|
Loading…
Reference in New Issue
Block a user