Implement new duration visual model

This commit is contained in:
Matthias Neeracher 2007-05-21 08:18:58 +00:00
parent fd684b8820
commit a7cf4a28df
4 changed files with 302 additions and 126 deletions

View File

@ -154,6 +154,11 @@ VLNote::VLNote(std::string name)
fPitch = kNoPitch; // Failed to parse completely
}
VLNote::VLNote(VLFraction dur, int pitch)
: fDuration(dur), fPitch(pitch), fTied(0), fVisual(0)
{
}
void VLNote::Name(std::string & name, bool useSharps) const
{
name = PitchName(fPitch, useSharps);
@ -274,6 +279,53 @@ void VLNote::MMAName(std::string & name, VLFraction at, VLFraction dur, VLFracti
name += '~';
}
void VLNote::MakeRepresentable()
{
if (fDuration > 1)
fDuration = 1;
fVisual = kWhole;
VLFraction part(1,1);
VLFraction triplet(2,3);
//
// Power of 2 denominators are not triplets
//
bool nonTriplet(!(fDuration.fDenom & (fDuration.fDenom-1)));
while (part.fDenom < 64) {
if (fDuration >= part) {
fDuration = part;
return;
} else if (!nonTriplet && fDuration >= triplet) {
fDuration = triplet;
fVisual |= kTriplet;
return;
}
part /= 2;
triplet /= 2;
++fVisual;
}
fprintf(stderr, "Encountered preposterously brief note: %d/%d\n",
fDuration.fNum, fDuration.fDenom);
abort();
}
void VLNote::AlignToGrid(VLFraction at, VLFraction grid)
{
if (at+fDuration > grid) {
fDuration = grid-at;
MakeRepresentable();
}
}
VLLyricsNote::VLLyricsNote(const VLNote & note)
: VLNote(note)
{
}
VLLyricsNote::VLLyricsNote(VLFraction dur, int pitch)
: VLNote(dur, pitch)
{
}
struct VLChordModifier {
const char * fName;
uint32_t fAddSteps;
@ -313,6 +365,11 @@ static const VLChordModifier kModifiers[] = {
{NULL, 0, 0}
};
VLChord::VLChord(VLFraction dur, int pitch, int rootPitch)
: VLNote(dur, pitch), fSteps(0), fRootPitch(kNoPitch)
{
}
VLChord::VLChord(std::string name)
{
size_t root;
@ -846,6 +903,141 @@ bool VLMeasure::NoChords() const
&& fChords.front().fPitch == VLNote::kNoPitch;
}
void VLMeasure::DecomposeNotes(const VLProperties & prop, VLNoteList & decomposed) const
{
decomposed.clear();
const VLFraction kQuarterDur(1,4);
const VLFraction kEighthLoc(1,8);
const VLFraction kQuarTripLoc(1,6);
VLFraction at(0);
VLNoteList::const_iterator i = fMelody.begin();
VLNoteList::const_iterator e = fMelody.end();
int prevTriplets = 0;
int prevVisual;
VLFraction prevTripDur;
while (i!=e) {
VLNoteList::const_iterator n = i;
++n;
VLLyricsNote c = *i; // Current note, remaining duration
VLLyricsNote p = c; // Next partial note
do {
//
// Start with longest possible note
//
p.MakeRepresentable();
//
// Prefer further triplets
//
if (prevTriplets) {
if (p.fDuration >= 2*prevTripDur) {
p.fDuration = 2*prevTripDur;
if (prevTriplets == 1) {
p.fVisual = prevVisual-1;
prevTriplets = 2; // 1/8th, 1/4th triplet or similar
} else {
p.fDuration = prevTripDur; // 1/8th, 1/8th, 1/4th
p.fVisual = prevVisual;
}
goto haveDuration;
} else if (p.fDuration >= prevTripDur) {
p.fDuration = prevTripDur;
p.fVisual = prevVisual;
goto haveDuration;
} else if (p.fDuration >= prevTripDur/2) {
p.fDuration = prevTripDur/2;
p.fVisual = prevVisual+1;
prevTripDur /= 2;
if (prevTriplets == 1)
prevTriplets = 2; // 1/4th, 1/8th
else
prevTriplets = 1; // 1/4th, 1/4th, 1/8th
goto haveDuration;
}
prevTriplets = 0;
}
if (at.fDenom > 4) {
//
// Break up notes not starting on quarter beat
// - Never cross middle of measure
//
VLFraction middle;
if (prop.fTime.fNum & 1) // Treat 5/4 as 3+2, not 2+3
middle = VLFraction((prop.fTime.fNum+1)/2, prop.fTime.fDenom);
else
middle = prop.fTime / 2;
if (at < middle)
p.AlignToGrid(at, middle);
VLFraction inBeat = at % kQuarterDur;
if ((inBeat == kEighthLoc || inBeat == kQuarTripLoc)
&& p.fDuration == kQuarterDur
)
; // Allow syncopated quarters
else
p.AlignToGrid(inBeat, kQuarterDur); // Align all others
}
if (p.fVisual & VLNote::kTriplet) {
//
// Distinguish swing 8ths/16ths from triplets
//
VLFraction swung(1, prop.fDivisions < 6 ? 8 : 16);
VLFraction grid(2*swung);
if (p.fDuration == 4*swung/3 && (at % grid == 0)) {
if (p.fDuration == c.fDuration && n!=e
&& (n->fDuration == p.fDuration
|| (n->fDuration == 2*p.fDuration))
) {
; // Triplet, not swing note
} else {
//
// First swing note (4th triplet -> 8th)
//
p.fVisual = (p.fVisual+1) & VLNote::kNoteHead;
}
} else if (p.fDuration == 2*swung/3
&& ((at+p.fDuration) % grid == 0)
) {
//
// Second swing note (8th triplet -> 8th)
//
p.fVisual &= VLNote::kNoteHead;
} else if (p.fDuration != c.fDuration
&& 2*p.fDuration != c.fDuration
) {
//
// Get rid of awkward triplets
//
p.fDuration *= VLFraction(3,4);
p.fVisual = (p.fVisual+1) & VLNote::kNoteHead;
}
}
haveDuration:
if (p.fVisual & VLNote::kTriplet)
if (prevTriplets = (prevTriplets+1)%3) {
prevTripDur = p.fDuration;
prevVisual = p.fVisual;
}
p.fTied &= VLNote::kTiedWithPrev;
if (p.fDuration == c.fDuration)
p.fTied |= c.fTied & VLNote::kTiedWithNext;
else
p.fTied |= VLNote::kTiedWithNext;
if (p.fPitch == VLNote::kNoPitch)
p.fTied = VLNote::kNotTied;
decomposed.push_back(p);
at += p.fDuration;
c.fDuration -= p.fDuration;
p.fDuration = c.fDuration;
p.fTied |= VLNote::kTiedWithPrev;
p.fLyrics.clear();
} while (c.fDuration > 0);
i = n;
}
}
VLSong::VLSong(bool initialize)
{
if (!initialize)
@ -865,9 +1057,8 @@ VLSong::VLSong(bool initialize)
void VLSong::AddMeasure()
{
VLFraction dur = fProperties.front().fTime;
VLLyricsNote rest = VLLyricsNote(VLRest(dur));
VLChord rchord;
rchord.fDuration = dur;
VLLyricsNote rest(dur);
VLChord rchord(dur);
VLMeasure meas;
meas.fChords.push_back(rchord);
@ -1357,9 +1548,8 @@ void VLSong::ChangeTime(VLFraction newTime)
VLProperties & prop = fProperties.front();
if (prop.fTime == newTime)
return; // No change
VLChord rchord;
rchord.fDuration = newTime-prop.fTime;
VLLyricsNote rnote = VLLyricsNote(VLRest(newTime-prop.fTime));
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();
@ -2255,10 +2445,8 @@ void VLSong::PasteMeasures(size_t beginMeasure, const VLSong & measures, int mod
VLMeasure rest;
rest.fPropIdx = fMeasures.back().fPropIdx;
VLFraction dur = fProperties[rest.fPropIdx].fTime;
rest.fMelody.push_back(VLLyricsNote(VLRest(dur)));
VLChord rchord;
rchord.fDuration= dur;
rest.fChords.push_back(rchord);
rest.fMelody.push_back(VLLyricsNote(dur));
rest.fChords.push_back(VLChord(dur));
fMeasures.insert(fMeasures.end(), nextMeasure-CountMeasures(), rest);
}
@ -2276,7 +2464,7 @@ void VLSong::PasteMeasures(size_t beginMeasure, const VLSong & measures, int mod
void VLSong::DeleteMeasures(size_t beginMeasure, size_t endMeasure, int mode)
{
if (mode == kOverwriteMelody) {
VLLyricsNote rest(VLRest(fProperties.front().fTime));
VLLyricsNote rest(fProperties.front().fTime);
for (size_t m=beginMeasure; m<endMeasure; ++m) {
fMeasures[m].fMelody.clear();
fMeasures[m].fMelody.push_back(rest);

View File

@ -132,20 +132,28 @@ struct VLNote {
kTiedWithNext = 1,
kTiedWithPrev = 2
};
//
// Hint at visual representation (Computed in DecomposeNotes)
//
uint8_t fVisual;
enum {
kWhole = 0,
kHalf = 1,
kQuarter = 2,
kEighth = 3,
k16th = 4,
k32nd = 5,
VLNote() : fPitch(kNoPitch) {}
VLNote(VLFraction dur, int8_t pitch)
: fDuration(dur), fPitch(pitch), fTied(kNotTied)
{}
kNoteHead = 0x07,
kTriplet = 0x80
};
VLNote(VLFraction dur=0, int pitch=kNoPitch);
VLNote(std::string name);
void Name(std::string & name, bool useSharps = false) const;
void LilypondName(std::string & name, VLFraction at, VLFraction prevDur, VLFraction nextDur, bool & triplet, bool & pickup, const VLProperties & prop) const;
void MMAName(std::string & name, VLFraction at, VLFraction dur, VLFraction prevDur, VLFraction nextDur, const VLProperties & prop) const;
};
struct VLRest : VLNote {
VLRest(VLFraction duration) : VLNote(duration, kNoPitch) {}
void MakeRepresentable();
void AlignToGrid(VLFraction at, VLFraction grid);
};
struct VLChord : VLNote {
@ -199,7 +207,7 @@ struct VLChord : VLNote {
};
int8_t fRootPitch; // kNoPitch == no root
VLChord() {}
VLChord(VLFraction dur=0, int pitch=kNoPitch, int rootPitch=kNoPitch);
VLChord(std::string name);
void Name(std::string & base, std::string & ext, std::string & root, bool useSharps = false) const;
void LilypondName(std::string & name, bool useSharps = false) const;
@ -228,9 +236,8 @@ struct VLProperties {
};
struct VLLyricsNote : VLNote {
VLLyricsNote() {}
explicit VLLyricsNote(const VLNote & note)
{ *static_cast<VLNote *>(this) = note; }
VLLyricsNote(const VLNote & note);
VLLyricsNote(VLFraction dur=0, int pitch = kNoPitch);
std::vector<VLSyllable> fLyrics;
};
@ -250,6 +257,8 @@ struct VLMeasure {
bool IsEmpty() const;
bool NoChords() const;
void DecomposeNotes(const VLProperties & prop, VLNoteList & decomposed) const;
};
struct VLRepeat {

View File

@ -134,7 +134,7 @@
}
}
- (void) drawNote:(VLFraction)dur at:(NSPoint)p
- (void) drawNote:(int)visual at:(NSPoint)p
accidental:(VLMusicElement)accidental tied:(BOOL)tied
{
NSPoint s = p;
@ -147,11 +147,11 @@
// Draw note head
//
NSImage * head;
switch (dur.fDenom) {
case 1:
switch (visual & VLNote::kNoteHead) {
case VLNote::kWhole:
head = [self musicElement:kMusicWholeNote];
break;
case 2:
case VLNote::kHalf:
head = [self musicElement:kMusicHalfNote];
s.x -= 1.0f;
break;
@ -190,19 +190,19 @@
//
//
//
if (dur.fDenom > 1) {
if (visual > 0) {
NSBezierPath * bz = [NSBezierPath bezierPath];
NSPoint s1 = NSMakePoint(s.x, s.y+kStemH);
NSImage * flag = nil;
switch (dur.fDenom) {
case 8:
switch (visual) {
case VLNote::kEighth:
flag = [self musicElement:kMusicEighthFlag];
break;
case 16:
case VLNote::k16th:
flag = [self musicElement:kMusicSixteenthFlag];
s1.y += 5.0f;
break;
case 32:
case VLNote::k32nd:
flag = [self musicElement:kMusicThirtysecondthFlag];
s1.y += 13.0f;
break;
@ -239,34 +239,34 @@
fLastNoteCenter = c;
}
- (void) drawRest:(VLFraction)dur at:(NSPoint)p
- (void) drawRest:(int)visual at:(NSPoint)p
{
//
// Draw rest
//
NSImage * head = nil;
switch (dur.fDenom) {
case 1:
switch (visual) {
case VLNote::kWhole:
head = [self musicElement:kMusicWholeRest];
p.y += kWholeRestY;
break;
case 2:
case VLNote::kHalf:
head = [self musicElement:kMusicHalfRest];
p.y += kHalfRestY;
break;
case 4:
case VLNote::kQuarter:
head = [self musicElement:kMusicQuarterRest];
p.x -= kNoteX;
break;
case 8:
case VLNote::kEighth:
head = [self musicElement:kMusicEighthRest];
p.x -= kNoteX;
break;
case 16:
case VLNote::k16th:
head = [self musicElement:kMusicSixteenthRest];
p.x -= kNoteX;
break;
case 32:
case VLNote::k32nd:
head = [self musicElement:kMusicThirtysecondthRest];
p.x -= kNoteX;
break;
@ -287,63 +287,48 @@
int measIdx = m+system*fMeasPerSystem;
if (measIdx >= song->CountMeasures())
break;
const VLMeasure measure = song->fMeasures[measIdx];
const VLNoteList & melody = measure.fMelody;
const VLMeasure & measure = song->fMeasures[measIdx];
VLNoteList melody;
measure.DecomposeNotes(song->fProperties[measure.fPropIdx], melody);
VLFraction at(0);
VLFraction prevDur(0);
bool triplet = false;
for (VLNoteList::const_iterator note = melody.begin();
note != melody.end();
++note
) {
VLFraction dur = note->fDuration;
VLFraction nextDur(0);
VLNoteList::const_iterator next = note;
if (++next != melody.end())
nextDur = next->fDuration;
BOOL first = !m || !(note->fTied & VLNote::kTiedWithPrev);
int pitch = note->fPitch;
while (dur > 0) {
VLFraction partialDur; // Actual value of note drawn
VLFraction noteDur; // Visual value of note
prop.PartialNote(at, dur, dur==prevDur || dur==nextDur,
&partialDur);
prop.VisualNote(at, partialDur, triplet, &noteDur, &triplet);
prevDur = partialDur;
if (pitch != VLNote::kNoPitch) {
[self drawLedgerLinesWithPitch:pitch
at:NSMakePoint([self noteXInMeasure:m at:at], kSystemY)];
VLMusicElement accidental;
NSPoint pos =
NSMakePoint([self noteXInMeasure:m at:at],
kSystemY+[self noteYWithPitch:pitch
accidental:&accidental]);
VLMusicElement acc = accidental;
int step= [self stepWithPitch:pitch];
if (acc == accidentals[step])
acc = kMusicNothing; // Don't repeat accidentals
else if (acc == kMusicNothing)
if (accidentals[step] == kMusicNatural) // Resume signature
acc = prop.fKey < 0 ? kMusicFlat : kMusicSharp;
else
acc = kMusicNatural;
[self drawNote:noteDur
at: pos
accidental: acc
tied:!first];
accidentals[step] = accidental;
} else {
VLMusicElement accidental;
NSPoint pos =
NSMakePoint([self noteXInMeasure:m at:at],
kSystemY+[self noteYWithPitch:65
accidental:&accidental]);
[self drawRest:noteDur at: pos];
}
dur -= partialDur;
at += partialDur;
first = NO;
BOOL tied = (note != melody.begin() || m)
&& note->fTied & VLNote::kTiedWithPrev;
int pitch = note->fPitch;
if (pitch != VLNote::kNoPitch) {
[self drawLedgerLinesWithPitch:pitch
at:NSMakePoint([self noteXInMeasure:m at:at], kSystemY)];
VLMusicElement accidental;
NSPoint pos =
NSMakePoint([self noteXInMeasure:m at:at],
kSystemY+[self noteYWithPitch:pitch
accidental:&accidental]);
VLMusicElement acc = accidental;
int step= [self stepWithPitch:pitch];
if (acc == accidentals[step])
acc = kMusicNothing; // Don't repeat accidentals
else if (acc == kMusicNothing)
if (accidentals[step] == kMusicNatural) // Resume signature
acc = prop.fKey < 0 ? kMusicFlat : kMusicSharp;
else
acc = kMusicNatural;
[self drawNote:note->fVisual & VLNote::kNoteHead
at: pos
accidental: acc
tied:tied];
accidentals[step] = accidental;
} else {
VLMusicElement accidental;
NSPoint pos =
NSMakePoint([self noteXInMeasure:m at:at],
kSystemY+[self noteYWithPitch:65
accidental:&accidental]);
[self drawRest:note->fVisual & VLNote::kNoteHead at: pos];
}
at += note->fDuration;
}
}
if (fCursorPitch != VLNote::kNoPitch && fCursorMeasure/fMeasPerSystem == system)

View File

@ -38,7 +38,7 @@ std::ostream & operator<<(std::ostream & s, VLFraction f)
s << whole;
f -= whole;
if (f.fNum)
s << int(f.fNum) << '/' << int(f.fDenom);
s << '.' << int(f.fNum) << '/' << int(f.fDenom);
} else if (f.fNum) {
s << int(f.fNum) << '/' << int(f.fDenom);
} else {
@ -65,50 +65,44 @@ void PrintName(const VLChord & chord)
}
}
template <class C> class Printer {
VLProperties * fProp;
VLFraction fAt;
public:
Printer(VLProperties * prop) : fProp(prop) {}
void operator()(const C& obj) {
PrintName(obj);
std::cout << '@';
for (VLFraction d = obj.fDuration; d > 0; ) {
VLFraction p;
fProp->PartialNote(fAt, d, &p);
if (d < obj.fDuration)
std::cout << '+';
std::cout << p;
d -= p;
fAt += p;
}
std::cout << ' ';
}
};
void PrintMeasure(const VLMeasure & measure)
void ChordPrinter(const VLChord & chord)
{
std::for_each(measure.fChords.begin(), measure.fChords.end(),
Printer<VLChord>(measure.fProperties));
PrintName(chord);
if (chord.fDuration != VLFraction(1,4))
std::cout << " * " << chord.fDuration;
std::cout << ' ';
}
void NotePrinter(const VLLyricsNote & note)
{
if (note.fTied & VLNote::kTiedWithPrev)
std::cout << "~ ";
PrintName(note);
std::cout << ' ' << note.fDuration
<< '[' << ((note.fVisual & VLNote::kTriplet) ? "T" : "")
<< (note.fVisual & VLNote::kNoteHead)["124863"] << "] ";
}
void PrintMeasure(const VLMeasure & measure, const VLProperties & prop)
{
std::for_each(measure.fChords.begin(), measure.fChords.end(), ChordPrinter);
std::cout << std::endl;
std::for_each(measure.fMelody.begin(), measure.fMelody.end(),
Printer<VLNote>(measure.fProperties));
VLNoteList decomposed;
measure.DecomposeNotes(prop, decomposed);
std::for_each(decomposed.begin(), decomposed.end(), NotePrinter);
std::cout << std::endl << std::endl;
}
void PrintSong(const VLSong & song)
{
std::for_each(song.fMeasures.begin(), song.fMeasures.end(), PrintMeasure);
for (size_t i=0; i<song.CountMeasures()-song.EmptyEnding(); ++i)
PrintMeasure(song.fMeasures[i], song.fProperties[song.fMeasures[i].fPropIdx]);
}
int main(int, char *const [])
{
VLSong song;
song.fMeasures.resize(4);
char command;
while (std::cin >> command) {
int measure;
@ -117,7 +111,7 @@ int main(int, char *const [])
switch (command) {
case '+':
std::cin >> name >> measure >> at;
song.AddNote(name, measure, at);
song.AddNote(VLNote(name), measure, at);
PrintSong(song);
break;