Rudimentary UI support for setting per-section properties

This commit is contained in:
Matthias Neeracher 2008-01-12 23:55:15 +00:00
parent deb08552d7
commit b8040292b1
7 changed files with 200 additions and 101 deletions

View File

@ -55,9 +55,9 @@ enum {
- (int) repeatVolta; - (int) repeatVolta;
- (bool) brandNew; - (bool) brandNew;
- (void) setKey:(int)key transpose:(BOOL)transpose; - (void) setKey:(int)key transpose:(BOOL)transpose inSections:(NSRange)sections;
- (void) setTimeNum:(int)num denom:(int)denom; - (void) setTimeNum:(int)num denom:(int)denom inSections:(NSRange)sections;
- (void) setDivisions:(int)divisions; - (void) setDivisions:(int)divisions inSections:(NSRange)sections;
- (void) setRepeatVolta:(int)repeatVolta; - (void) setRepeatVolta:(int)repeatVolta;
- (IBAction) showOutput:(id)sender; - (IBAction) showOutput:(id)sender;

View File

@ -203,11 +203,12 @@
return [NSNumber numberWithInt: (prop.fKey << 8) | (prop.fMode & 0xFF)]; return [NSNumber numberWithInt: (prop.fKey << 8) | (prop.fMode & 0xFF)];
} }
- (void) setKey:(int)key transpose:(BOOL)transpose - (void) setKey:(int)key transpose:(BOOL)transpose inSections:(NSRange)sections
{ {
[self willChangeSong]; [self willChangeSong];
[self willChangeValueForKey:@"songKey"]; [self willChangeValueForKey:@"songKey"];
song->ChangeKey(key>>8, key & 0xFF, transpose); while (sections.length-- > 0)
song->ChangeKey(sections.location++, key>>8, key & 0xFF, transpose);
[self didChangeValueForKey:@"songKey"]; [self didChangeValueForKey:@"songKey"];
[self didChangeSong]; [self didChangeSong];
} }
@ -219,11 +220,12 @@
return [NSNumber numberWithInt: (prop.fTime.fNum << 8) | prop.fTime.fDenom]; return [NSNumber numberWithInt: (prop.fTime.fNum << 8) | prop.fTime.fDenom];
} }
- (void) setTimeNum:(int)num denom:(int)denom - (void) setTimeNum:(int)num denom:(int)denom inSections:(NSRange)sections
{ {
[self willChangeSong]; [self willChangeSong];
[self willChangeValueForKey:@"songTime"]; [self willChangeValueForKey:@"songTime"];
song->ChangeTime(VLFraction(num, denom)); while (sections.length-- > 0)
song->ChangeTime(sections.location++, VLFraction(num, denom));
[self didChangeValueForKey:@"songTime"]; [self didChangeValueForKey:@"songTime"];
[self didChangeSong]; [self didChangeSong];
} }
@ -235,12 +237,13 @@
return [NSNumber numberWithInt: prop.fDivisions]; return [NSNumber numberWithInt: prop.fDivisions];
} }
- (void) setDivisions:(int)divisions - (void) setDivisions:(int)divisions inSections:(NSRange)sections
{ {
[self willChangeSong]; [self willChangeSong];
[self willChangeValueForKey:@"songDivisions"]; [self willChangeValueForKey:@"songDivisions"];
[self willChangeSong]; [self willChangeSong];
song->ChangeDivisions(divisions); while (sections.length-- > 0)
song->ChangeDivisions(sections.location++, divisions);
[self didChangeValueForKey:@"songDivisions"]; [self didChangeValueForKey:@"songDivisions"];
[self didChangeSong]; [self didChangeSong];
} }

View File

@ -896,9 +896,9 @@ static void TransposePinned(int8_t & pitch, int semi)
pitch = octave+pitchInOctave; pitch = octave+pitchInOctave;
} }
void VLSong::ChangeKey(int newKey, int newMode, bool transpose) void VLSong::ChangeKey(int section, int newKey, int newMode, bool transpose)
{ {
VLProperties & prop = fProperties.front(); VLProperties & prop = fProperties[section];
int semi = 7*(newKey-prop.fKey) % 12; int semi = 7*(newKey-prop.fKey) % 12;
prop.fKey = newKey; prop.fKey = newKey;
prop.fMode = newMode; prop.fMode = newMode;
@ -906,6 +906,9 @@ void VLSong::ChangeKey(int newKey, int newMode, bool transpose)
return; return;
for (size_t measure=0; measure<fMeasures.size(); ++measure) { for (size_t measure=0; measure<fMeasures.size(); ++measure) {
if (fMeasures[measure].fPropIdx != section)
continue;
VLChordList::iterator i = fMeasures[measure].fChords.begin(); VLChordList::iterator i = fMeasures[measure].fChords.begin();
VLChordList::iterator e = fMeasures[measure].fChords.end(); VLChordList::iterator e = fMeasures[measure].fChords.end();
@ -918,6 +921,9 @@ void VLSong::ChangeKey(int newKey, int newMode, bool transpose)
int8_t low = 127; int8_t low = 127;
int8_t high = 0; int8_t high = 0;
for (size_t measure=0; measure<fMeasures.size(); ++measure) { for (size_t measure=0; measure<fMeasures.size(); ++measure) {
if (fMeasures[measure].fPropIdx != section)
continue;
VLNoteList::iterator i = fMeasures[measure].fMelody.begin(); VLNoteList::iterator i = fMeasures[measure].fMelody.begin();
VLNoteList::iterator e = fMeasures[measure].fMelody.end(); VLNoteList::iterator e = fMeasures[measure].fMelody.end();
@ -1026,65 +1032,29 @@ VLFraction VLRealigner::operator()(VLFraction at)
return quarters + fTable[at.fNum / at.fDenom]*fNewFrac; return quarters + fTable[at.fNum / at.fDenom]*fNewFrac;
} }
void VLSong::ChangeDivisions(int newDivisions) static VLChordList Realign(const VLChordList & chords,
const VLProperties& fromProp,
const VLProperties& toProp)
{ {
VLProperties & prop = fProperties.front(); if (fromProp.fTime == toProp.fTime)
if (newDivisions == prop.fDivisions) return chords;
return; // Unchanged if (fromProp.fTime < toProp.fTime) {
VLChord rchord(toProp.fTime-fromProp.fTime);
VLChordList newChords(chords);
newChords.push_back(rchord);
VLRealigner realign(prop.fDivisions, newDivisions); return newChords;
// } else {
// Only melody needs to be realigned, chords are already quarter notes VLChordList::const_iterator i = chords.begin();
// VLChordList::const_iterator e = chords.end();
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
VLNoteList newMelody;
VLFraction at(0);
VLFraction lastAt;
VLNoteList::iterator i = fMeasures[measure].fMelody.begin();
VLNoteList::iterator e = fMeasures[measure].fMelody.end();
for (; i!=e; ++i) {
VLLyricsNote n = *i;
VLFraction newAt = realign(at);
if (newMelody.empty()) {
newMelody.push_back(n);
lastAt = newAt;
} else if (newAt != lastAt) {
newMelody.back().fDuration = newAt-lastAt;
newMelody.push_back(n);
lastAt = newAt;
}
at += n.fDuration;
}
if (lastAt == at)
newMelody.pop_back();
else
newMelody.back().fDuration = at-lastAt;
fMeasures[measure].fMelody.swap(newMelody);
}
prop.fDivisions = newDivisions;
}
void VLSong::ChangeTime(VLFraction newTime)
{
VLProperties & prop = fProperties.front();
if (prop.fTime == newTime)
return; // No change
VLChord rchord(newTime-prop.fTime);
VLLyricsNote rnote(newTime-prop.fTime);
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
if (newTime < prop.fTime) {
VLChordList::iterator i = fMeasures[measure].fChords.begin();
VLChordList::iterator e = fMeasures[measure].fChords.end();
VLFraction at(0); VLFraction at(0);
VLChordList newChords; VLChordList newChords;
for (; i!=e; ++i) { for (; i!=e; ++i) {
VLChord c = *i; VLChord c = *i;
if (at+c.fDuration >= newTime) { if (at+c.fDuration >= toProp.fTime) {
if (at < newTime) { if (at < toProp.fTime) {
c.fDuration = newTime-at; c.fDuration = toProp.fTime-at;
newChords.push_back(c); newChords.push_back(c);
} }
break; break;
@ -1093,34 +1063,106 @@ void VLSong::ChangeTime(VLFraction newTime)
at += c.fDuration; at += c.fDuration;
} }
} }
fMeasures[measure].fChords.swap(newChords);
} else
fMeasures[measure].fChords.push_back(rchord);
if (newTime < prop.fTime) { return newChords;
VLNoteList::iterator i = fMeasures[measure].fMelody.begin(); }
VLNoteList::iterator e = fMeasures[measure].fMelody.end(); }
static VLNoteList Realign(const VLNoteList & notes,
const VLProperties& fromProp,
const VLProperties& toProp)
{
if (fromProp.fTime == toProp.fTime && fromProp.fDivisions == toProp.fDivisions)
return notes;
VLNoteList newNotes(notes);
if (fromProp.fTime < toProp.fTime) {
VLNote rest(toProp.fTime-fromProp.fTime);
newNotes.push_back(rest);
} else if (fromProp.fTime > toProp.fTime) {
VLNoteList::const_iterator i = notes.begin();
VLNoteList::const_iterator e = notes.end();
VLFraction at(0); VLFraction at(0);
VLNoteList newMelody;
for (; i!=e; ++i) { for (; i!=e; ++i) {
VLLyricsNote n = *i; VLNote n = *i;
if (at+n.fDuration >= newTime) { if (at+n.fDuration >= toProp.fTime) {
if (at < newTime) { if (at < toProp.fTime) {
n.fDuration = newTime-at; n.fDuration = toProp.fTime-at;
newMelody.push_back(n); newNotes.push_back(n);
} }
break; break;
} else { } else {
newMelody.push_back(n); newNotes.push_back(n);
at += n.fDuration; at += n.fDuration;
} }
} }
fMeasures[measure].fMelody.swap(newMelody);
} else
fMeasures[measure].fMelody.push_back(rnote);
} }
prop.fTime = newTime; if (fromProp.fDivisions == toProp.fDivisions) {
VLRealigner realign(fromProp.fDivisions, toProp.fDivisions);
VLNoteList alignedNotes;
VLFraction at(0);
VLFraction lastAt;
VLNoteList::iterator i = newNotes.begin();
VLNoteList::iterator e = newNotes.end();
for (; i!=e; ++i) {
VLLyricsNote n = *i;
VLFraction newAt = realign(at);
if (alignedNotes.empty()) {
alignedNotes.push_back(n);
lastAt = newAt;
} else if (newAt != lastAt) {
alignedNotes.back().fDuration = newAt-lastAt;
alignedNotes.push_back(n);
lastAt = newAt;
}
at += n.fDuration;
}
if (lastAt == at)
alignedNotes.pop_back();
else
alignedNotes.back().fDuration = at-lastAt;
return alignedNotes;
} else
return newNotes;
}
void VLSong::ChangeDivisions(int section, int newDivisions)
{
VLProperties & prop = fProperties[section];
if (prop.fDivisions == newDivisions)
return; // Unchanged
VLProperties newProp = prop;
newProp.fDivisions = newDivisions;
//
// Only melody needs to be realigned, chords are already quarter notes
//
for (size_t measure=0; measure<fMeasures.size(); ++measure)
if (fMeasures[measure].fPropIdx == section)
fMeasures[measure].fMelody =
Realign(fMeasures[measure].fMelody, prop, newProp);
prop = newProp;
}
void VLSong::ChangeTime(int section, VLFraction newTime)
{
VLProperties & prop = fProperties[section];
if (prop.fTime == newTime)
return; // No change
VLProperties newProp = prop;
newProp.fTime = newTime;
for (size_t measure=0; measure<fMeasures.size(); ++measure)
if (fMeasures[measure].fPropIdx == section) {
fMeasures[measure].fChords =
Realign(fMeasures[measure].fChords, prop, newProp);
fMeasures[measure].fMelody =
Realign(fMeasures[measure].fMelody, prop, newProp);
}
prop = newProp;
} }
size_t VLSong::EmptyEnding() const size_t VLSong::EmptyEnding() const
@ -1867,12 +1909,34 @@ void VLSong::PasteMeasures(size_t beginMeasure, const VLSong & measures, int mod
{ {
size_t numMeas = measures.CountMeasures(); size_t numMeas = measures.CountMeasures();
size_t nextMeasure = mode==kInsert ? beginMeasure : beginMeasure+numMeas; size_t nextMeasure = mode==kInsert ? beginMeasure : beginMeasure+numMeas;
//
// Ignore properties for now. We don't use multiple properties yet.
//
if (mode == kInsert) { if (mode == kInsert) {
int propAt = fMeasures[beginMeasure].fPropIdx;
int propOffset = 0;
VLPropertyList::const_iterator beginProp = measures.fProperties.begin();
VLPropertyList::const_iterator endProp = measures.fProperties.end();
if (beginMeasure) {
propOffset = fMeasures[beginMeasure-1].fPropIdx;
if (fProperties[propOffset] == beginProp[0])
++beginProp;
else
++propOffset;
if (fProperties[propAt] == endProp[-1])
--endProp;
}
int postOffset = endProp - beginProp;
fProperties.insert(fProperties.begin()+propAt, beginProp, endProp);
fMeasures.insert(fMeasures.begin()+beginMeasure, fMeasures.insert(fMeasures.begin()+beginMeasure,
measures.fMeasures.begin(), measures.fMeasures.end()); measures.fMeasures.begin(), measures.fMeasures.end());
if (propOffset)
for (size_t meas=beginMeasure; meas<beginMeasure+numMeas; ++meas)
fMeasures[meas].fPropIdx += propOffset;
if (postOffset)
for (size_t meas=beginMeasure+numMeas; meas<fMeasures.size(); ++meas)
fMeasures[meas].fPropIdx += postOffset;
for (size_t r=0; r<fRepeats.size(); ++r) { for (size_t r=0; r<fRepeats.size(); ++r) {
VLRepeat & repeat = fRepeats[r]; VLRepeat & repeat = fRepeats[r];
for (size_t e=0; e<repeat.fEndings.size(); ++e) { for (size_t e=0; e<repeat.fEndings.size(); ++e) {
@ -1904,9 +1968,13 @@ void VLSong::PasteMeasures(size_t beginMeasure, const VLSong & measures, int mod
const VLMeasure & srcMeas = measures.fMeasures[m]; const VLMeasure & srcMeas = measures.fMeasures[m];
VLMeasure & dstMeas = fMeasures[beginMeasure+m]; VLMeasure & dstMeas = fMeasures[beginMeasure+m];
if (mode & kOverwriteChords) if (mode & kOverwriteChords)
dstMeas.fChords = srcMeas.fChords; dstMeas.fChords = Realign(srcMeas.fChords,
measures.fProperties[srcMeas.fPropIdx],
fProperties[dstMeas.fPropIdx]);
if (mode & kOverwriteMelody) if (mode & kOverwriteMelody)
dstMeas.fMelody = srcMeas.fMelody; dstMeas.fMelody = Realign(srcMeas.fMelody,
measures.fProperties[srcMeas.fPropIdx],
fProperties[dstMeas.fPropIdx]);
} }
} }
} }

View File

@ -269,15 +269,19 @@ struct VLRepeat {
std::vector<Ending> fEndings; std::vector<Ending> fEndings;
}; };
typedef std::vector<VLProperties> VLPropertyList;
typedef std::vector<VLMeasure> VLMeasureList;
typedef std::vector<VLRepeat> VLRepeatList;
class VLSong { class VLSong {
public: public:
VLSong(bool initialize = true); VLSong(bool initialize = true);
void swap(VLSong & other); void swap(VLSong & other);
void clear(); void clear();
std::vector<VLProperties> fProperties; VLPropertyList fProperties;
std::vector<VLMeasure> fMeasures; VLMeasureList fMeasures;
std::vector<VLRepeat> fRepeats; VLRepeatList fRepeats;
int8_t fGoToCoda; int8_t fGoToCoda;
int8_t fCoda; int8_t fCoda;
@ -340,9 +344,9 @@ public:
bool DoesTieWithPrevRepeat(size_t measure) const; bool DoesTieWithPrevRepeat(size_t measure) const;
bool DoesTieWithNextRepeat(size_t measure) const; bool DoesTieWithNextRepeat(size_t measure) const;
bool IsNonEmpty() const; bool IsNonEmpty() const;
void ChangeKey(int newKey, int newMode, bool transpose); void ChangeKey(int section, int newKey, int newMode, bool transpose);
void ChangeDivisions(int newDivisions); void ChangeDivisions(int section, int newDivisions);
void ChangeTime(VLFraction newTime); void ChangeTime(int section, VLFraction newTime);
bool FindWord(size_t stanza, size_t & measure, VLFraction & at); bool FindWord(size_t stanza, size_t & measure, VLFraction & at);
bool PrevWord(size_t stanza, size_t & measure, VLFraction & at); bool PrevWord(size_t stanza, size_t & measure, VLFraction & at);

View File

@ -591,7 +591,8 @@ const char * sBreak[3] = {"", "\xE2\xA4\xBE", "\xE2\x8E\x98"};
return; return;
int key = [[sender selectedItem] tag]; int key = [[sender selectedItem] tag];
[[self document] setKey:key transpose:returnCode==NSAlertDefaultReturn]; [[self document] setKey:key transpose:returnCode==NSAlertDefaultReturn
inSections:[self sectionsInSelection]];
fNeedsRecalc = kRecalc; fNeedsRecalc = kRecalc;
[self setNeedsDisplay: YES]; [self setNeedsDisplay: YES];
} }
@ -617,7 +618,8 @@ const char * sBreak[3] = {"", "\xE2\xA4\xBE", "\xE2\x8E\x98"};
{ {
int time = [[sender selectedItem] tag]; int time = [[sender selectedItem] tag];
[[self document] setTimeNum: time >> 8 denom: time & 0xFF]; [[self document] setTimeNum: time >> 8 denom: time & 0xFF
inSections:[self sectionsInSelection]];
fNeedsRecalc = kRecalc; fNeedsRecalc = kRecalc;
[self setNeedsDisplay: YES]; [self setNeedsDisplay: YES];
} }
@ -626,7 +628,7 @@ const char * sBreak[3] = {"", "\xE2\xA4\xBE", "\xE2\x8E\x98"};
{ {
int div = [[sender selectedItem] tag]; int div = [[sender selectedItem] tag];
[[self document] setDivisions: div]; [[self document] setDivisions: div inSections:[self sectionsInSelection]];
fNeedsRecalc = kRecalc; fNeedsRecalc = kRecalc;
[self setNeedsDisplay: YES]; [self setNeedsDisplay: YES];
} }

View File

@ -12,6 +12,7 @@
- (void)editSelection; - (void)editSelection;
- (void)adjustSelection:(NSEvent *)event; - (void)adjustSelection:(NSEvent *)event;
- (NSRange)sectionsInSelection;
- (BOOL)validateUserInterfaceItem:(id)item; - (BOOL)validateUserInterfaceItem:(id)item;
- (IBAction)cut:(id)sender; - (IBAction)cut:(id)sender;

View File

@ -61,6 +61,27 @@ static VLSong sPasteboard;
fCursorRegion = kRegionMeasure; fCursorRegion = kRegionMeasure;
} }
- (NSRange)sectionsInSelection
{
NSRange sections;
int firstSection;
int lastSection;
VLSong * song = [self song];
if (fSelEnd > -1) {
firstSection = song->fMeasures[fSelStart].fPropIdx;
lastSection = fSelEnd > fSelStart+1 ?
song->fMeasures[fSelEnd].fPropIdx : firstSection;
} else {
firstSection = 0;
lastSection = song->fMeasures.back().fPropIdx;
}
sections.location = firstSection;
sections.length = lastSection-firstSection+1;
return sections;
}
- (BOOL)validateMenuItem:(id) item - (BOOL)validateMenuItem:(id) item
{ {
SEL action = [item action]; SEL action = [item action];