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 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 void VLNote::Name(std::string & name, bool useSharps) const
{ {
name = PitchName(fPitch, useSharps); name = PitchName(fPitch, useSharps);
@ -274,6 +279,53 @@ void VLNote::MMAName(std::string & name, VLFraction at, VLFraction dur, VLFracti
name += '~'; 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 { struct VLChordModifier {
const char * fName; const char * fName;
uint32_t fAddSteps; uint32_t fAddSteps;
@ -313,6 +365,11 @@ static const VLChordModifier kModifiers[] = {
{NULL, 0, 0} {NULL, 0, 0}
}; };
VLChord::VLChord(VLFraction dur, int pitch, int rootPitch)
: VLNote(dur, pitch), fSteps(0), fRootPitch(kNoPitch)
{
}
VLChord::VLChord(std::string name) VLChord::VLChord(std::string name)
{ {
size_t root; size_t root;
@ -846,6 +903,141 @@ bool VLMeasure::NoChords() const
&& fChords.front().fPitch == VLNote::kNoPitch; && 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) VLSong::VLSong(bool initialize)
{ {
if (!initialize) if (!initialize)
@ -865,9 +1057,8 @@ VLSong::VLSong(bool initialize)
void VLSong::AddMeasure() void VLSong::AddMeasure()
{ {
VLFraction dur = fProperties.front().fTime; VLFraction dur = fProperties.front().fTime;
VLLyricsNote rest = VLLyricsNote(VLRest(dur)); VLLyricsNote rest(dur);
VLChord rchord; VLChord rchord(dur);
rchord.fDuration = dur;
VLMeasure meas; VLMeasure meas;
meas.fChords.push_back(rchord); meas.fChords.push_back(rchord);
@ -1357,9 +1548,8 @@ void VLSong::ChangeTime(VLFraction newTime)
VLProperties & prop = fProperties.front(); VLProperties & prop = fProperties.front();
if (prop.fTime == newTime) if (prop.fTime == newTime)
return; // No change return; // No change
VLChord rchord; VLChord rchord(newTime-prop.fTime);
rchord.fDuration = newTime-prop.fTime; VLLyricsNote rnote(newTime-prop.fTime);
VLLyricsNote rnote = VLLyricsNote(VLRest(newTime-prop.fTime));
for (size_t measure=0; measure<fMeasures.size(); ++measure) { for (size_t measure=0; measure<fMeasures.size(); ++measure) {
if (newTime < prop.fTime) { if (newTime < prop.fTime) {
VLChordList::iterator i = fMeasures[measure].fChords.begin(); VLChordList::iterator i = fMeasures[measure].fChords.begin();
@ -2255,10 +2445,8 @@ void VLSong::PasteMeasures(size_t beginMeasure, const VLSong & measures, int mod
VLMeasure rest; VLMeasure rest;
rest.fPropIdx = fMeasures.back().fPropIdx; rest.fPropIdx = fMeasures.back().fPropIdx;
VLFraction dur = fProperties[rest.fPropIdx].fTime; VLFraction dur = fProperties[rest.fPropIdx].fTime;
rest.fMelody.push_back(VLLyricsNote(VLRest(dur))); rest.fMelody.push_back(VLLyricsNote(dur));
VLChord rchord; rest.fChords.push_back(VLChord(dur));
rchord.fDuration= dur;
rest.fChords.push_back(rchord);
fMeasures.insert(fMeasures.end(), nextMeasure-CountMeasures(), rest); 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) void VLSong::DeleteMeasures(size_t beginMeasure, size_t endMeasure, int mode)
{ {
if (mode == kOverwriteMelody) { if (mode == kOverwriteMelody) {
VLLyricsNote rest(VLRest(fProperties.front().fTime)); VLLyricsNote rest(fProperties.front().fTime);
for (size_t m=beginMeasure; m<endMeasure; ++m) { for (size_t m=beginMeasure; m<endMeasure; ++m) {
fMeasures[m].fMelody.clear(); fMeasures[m].fMelody.clear();
fMeasures[m].fMelody.push_back(rest); fMeasures[m].fMelody.push_back(rest);

View File

@ -132,20 +132,28 @@ struct VLNote {
kTiedWithNext = 1, kTiedWithNext = 1,
kTiedWithPrev = 2 kTiedWithPrev = 2
}; };
//
VLNote() : fPitch(kNoPitch) {} // Hint at visual representation (Computed in DecomposeNotes)
VLNote(VLFraction dur, int8_t pitch) //
: fDuration(dur), fPitch(pitch), fTied(kNotTied) uint8_t fVisual;
{} enum {
kWhole = 0,
kHalf = 1,
kQuarter = 2,
kEighth = 3,
k16th = 4,
k32nd = 5,
kNoteHead = 0x07,
kTriplet = 0x80
};
VLNote(VLFraction dur=0, int pitch=kNoPitch);
VLNote(std::string name); VLNote(std::string name);
void Name(std::string & name, bool useSharps = false) const; 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 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; void MMAName(std::string & name, VLFraction at, VLFraction dur, VLFraction prevDur, VLFraction nextDur, const VLProperties & prop) const;
}; void MakeRepresentable();
void AlignToGrid(VLFraction at, VLFraction grid);
struct VLRest : VLNote {
VLRest(VLFraction duration) : VLNote(duration, kNoPitch) {}
}; };
struct VLChord : VLNote { struct VLChord : VLNote {
@ -199,7 +207,7 @@ struct VLChord : VLNote {
}; };
int8_t fRootPitch; // kNoPitch == no root int8_t fRootPitch; // kNoPitch == no root
VLChord() {} VLChord(VLFraction dur=0, int pitch=kNoPitch, int rootPitch=kNoPitch);
VLChord(std::string name); VLChord(std::string name);
void Name(std::string & base, std::string & ext, std::string & root, bool useSharps = false) const; void Name(std::string & base, std::string & ext, std::string & root, bool useSharps = false) const;
void LilypondName(std::string & name, bool useSharps = false) const; void LilypondName(std::string & name, bool useSharps = false) const;
@ -228,9 +236,8 @@ struct VLProperties {
}; };
struct VLLyricsNote : VLNote { struct VLLyricsNote : VLNote {
VLLyricsNote() {} VLLyricsNote(const VLNote & note);
explicit VLLyricsNote(const VLNote & note) VLLyricsNote(VLFraction dur=0, int pitch = kNoPitch);
{ *static_cast<VLNote *>(this) = note; }
std::vector<VLSyllable> fLyrics; std::vector<VLSyllable> fLyrics;
}; };
@ -250,6 +257,8 @@ struct VLMeasure {
bool IsEmpty() const; bool IsEmpty() const;
bool NoChords() const; bool NoChords() const;
void DecomposeNotes(const VLProperties & prop, VLNoteList & decomposed) const;
}; };
struct VLRepeat { 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 accidental:(VLMusicElement)accidental tied:(BOOL)tied
{ {
NSPoint s = p; NSPoint s = p;
@ -147,11 +147,11 @@
// Draw note head // Draw note head
// //
NSImage * head; NSImage * head;
switch (dur.fDenom) { switch (visual & VLNote::kNoteHead) {
case 1: case VLNote::kWhole:
head = [self musicElement:kMusicWholeNote]; head = [self musicElement:kMusicWholeNote];
break; break;
case 2: case VLNote::kHalf:
head = [self musicElement:kMusicHalfNote]; head = [self musicElement:kMusicHalfNote];
s.x -= 1.0f; s.x -= 1.0f;
break; break;
@ -190,19 +190,19 @@
// //
// //
// //
if (dur.fDenom > 1) { if (visual > 0) {
NSBezierPath * bz = [NSBezierPath bezierPath]; NSBezierPath * bz = [NSBezierPath bezierPath];
NSPoint s1 = NSMakePoint(s.x, s.y+kStemH); NSPoint s1 = NSMakePoint(s.x, s.y+kStemH);
NSImage * flag = nil; NSImage * flag = nil;
switch (dur.fDenom) { switch (visual) {
case 8: case VLNote::kEighth:
flag = [self musicElement:kMusicEighthFlag]; flag = [self musicElement:kMusicEighthFlag];
break; break;
case 16: case VLNote::k16th:
flag = [self musicElement:kMusicSixteenthFlag]; flag = [self musicElement:kMusicSixteenthFlag];
s1.y += 5.0f; s1.y += 5.0f;
break; break;
case 32: case VLNote::k32nd:
flag = [self musicElement:kMusicThirtysecondthFlag]; flag = [self musicElement:kMusicThirtysecondthFlag];
s1.y += 13.0f; s1.y += 13.0f;
break; break;
@ -239,34 +239,34 @@
fLastNoteCenter = c; fLastNoteCenter = c;
} }
- (void) drawRest:(VLFraction)dur at:(NSPoint)p - (void) drawRest:(int)visual at:(NSPoint)p
{ {
// //
// Draw rest // Draw rest
// //
NSImage * head = nil; NSImage * head = nil;
switch (dur.fDenom) { switch (visual) {
case 1: case VLNote::kWhole:
head = [self musicElement:kMusicWholeRest]; head = [self musicElement:kMusicWholeRest];
p.y += kWholeRestY; p.y += kWholeRestY;
break; break;
case 2: case VLNote::kHalf:
head = [self musicElement:kMusicHalfRest]; head = [self musicElement:kMusicHalfRest];
p.y += kHalfRestY; p.y += kHalfRestY;
break; break;
case 4: case VLNote::kQuarter:
head = [self musicElement:kMusicQuarterRest]; head = [self musicElement:kMusicQuarterRest];
p.x -= kNoteX; p.x -= kNoteX;
break; break;
case 8: case VLNote::kEighth:
head = [self musicElement:kMusicEighthRest]; head = [self musicElement:kMusicEighthRest];
p.x -= kNoteX; p.x -= kNoteX;
break; break;
case 16: case VLNote::k16th:
head = [self musicElement:kMusicSixteenthRest]; head = [self musicElement:kMusicSixteenthRest];
p.x -= kNoteX; p.x -= kNoteX;
break; break;
case 32: case VLNote::k32nd:
head = [self musicElement:kMusicThirtysecondthRest]; head = [self musicElement:kMusicThirtysecondthRest];
p.x -= kNoteX; p.x -= kNoteX;
break; break;
@ -287,63 +287,48 @@
int measIdx = m+system*fMeasPerSystem; int measIdx = m+system*fMeasPerSystem;
if (measIdx >= song->CountMeasures()) if (measIdx >= song->CountMeasures())
break; break;
const VLMeasure measure = song->fMeasures[measIdx]; const VLMeasure & measure = song->fMeasures[measIdx];
const VLNoteList & melody = measure.fMelody; VLNoteList melody;
measure.DecomposeNotes(song->fProperties[measure.fPropIdx], melody);
VLFraction at(0); VLFraction at(0);
VLFraction prevDur(0);
bool triplet = false;
for (VLNoteList::const_iterator note = melody.begin(); for (VLNoteList::const_iterator note = melody.begin();
note != melody.end(); note != melody.end();
++note ++note
) { ) {
VLFraction dur = note->fDuration; BOOL tied = (note != melody.begin() || m)
VLFraction nextDur(0); && note->fTied & VLNote::kTiedWithPrev;
VLNoteList::const_iterator next = note; int pitch = note->fPitch;
if (++next != melody.end()) if (pitch != VLNote::kNoPitch) {
nextDur = next->fDuration; [self drawLedgerLinesWithPitch:pitch
BOOL first = !m || !(note->fTied & VLNote::kTiedWithPrev); at:NSMakePoint([self noteXInMeasure:m at:at], kSystemY)];
int pitch = note->fPitch; VLMusicElement accidental;
while (dur > 0) { NSPoint pos =
VLFraction partialDur; // Actual value of note drawn NSMakePoint([self noteXInMeasure:m at:at],
VLFraction noteDur; // Visual value of note kSystemY+[self noteYWithPitch:pitch
prop.PartialNote(at, dur, dur==prevDur || dur==nextDur, accidental:&accidental]);
&partialDur); VLMusicElement acc = accidental;
prop.VisualNote(at, partialDur, triplet, &noteDur, &triplet); int step= [self stepWithPitch:pitch];
prevDur = partialDur; if (acc == accidentals[step])
if (pitch != VLNote::kNoPitch) { acc = kMusicNothing; // Don't repeat accidentals
[self drawLedgerLinesWithPitch:pitch else if (acc == kMusicNothing)
at:NSMakePoint([self noteXInMeasure:m at:at], kSystemY)]; if (accidentals[step] == kMusicNatural) // Resume signature
VLMusicElement accidental; acc = prop.fKey < 0 ? kMusicFlat : kMusicSharp;
NSPoint pos = else
NSMakePoint([self noteXInMeasure:m at:at], acc = kMusicNatural;
kSystemY+[self noteYWithPitch:pitch [self drawNote:note->fVisual & VLNote::kNoteHead
accidental:&accidental]); at: pos
VLMusicElement acc = accidental; accidental: acc
int step= [self stepWithPitch:pitch]; tied:tied];
if (acc == accidentals[step]) accidentals[step] = accidental;
acc = kMusicNothing; // Don't repeat accidentals } else {
else if (acc == kMusicNothing) VLMusicElement accidental;
if (accidentals[step] == kMusicNatural) // Resume signature NSPoint pos =
acc = prop.fKey < 0 ? kMusicFlat : kMusicSharp; NSMakePoint([self noteXInMeasure:m at:at],
else kSystemY+[self noteYWithPitch:65
acc = kMusicNatural; accidental:&accidental]);
[self drawNote:noteDur [self drawRest:note->fVisual & VLNote::kNoteHead at: pos];
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;
} }
at += note->fDuration;
} }
} }
if (fCursorPitch != VLNote::kNoPitch && fCursorMeasure/fMeasPerSystem == system) if (fCursorPitch != VLNote::kNoPitch && fCursorMeasure/fMeasPerSystem == system)

View File

@ -38,7 +38,7 @@ std::ostream & operator<<(std::ostream & s, VLFraction f)
s << whole; s << whole;
f -= whole; f -= whole;
if (f.fNum) if (f.fNum)
s << int(f.fNum) << '/' << int(f.fDenom); s << '.' << int(f.fNum) << '/' << int(f.fDenom);
} else if (f.fNum) { } else if (f.fNum) {
s << int(f.fNum) << '/' << int(f.fDenom); s << int(f.fNum) << '/' << int(f.fDenom);
} else { } else {
@ -65,50 +65,44 @@ void PrintName(const VLChord & chord)
} }
} }
template <class C> class Printer { void ChordPrinter(const VLChord & chord)
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)
{ {
std::for_each(measure.fChords.begin(), measure.fChords.end(), PrintName(chord);
Printer<VLChord>(measure.fProperties)); 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::cout << std::endl;
std::for_each(measure.fMelody.begin(), measure.fMelody.end(), VLNoteList decomposed;
Printer<VLNote>(measure.fProperties)); measure.DecomposeNotes(prop, decomposed);
std::for_each(decomposed.begin(), decomposed.end(), NotePrinter);
std::cout << std::endl << std::endl; std::cout << std::endl << std::endl;
} }
void PrintSong(const VLSong & song) 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 []) int main(int, char *const [])
{ {
VLSong song; VLSong song;
song.fMeasures.resize(4);
char command; char command;
while (std::cin >> command) { while (std::cin >> command) {
int measure; int measure;
@ -117,7 +111,7 @@ int main(int, char *const [])
switch (command) { switch (command) {
case '+': case '+':
std::cin >> name >> measure >> at; std::cin >> name >> measure >> at;
song.AddNote(name, measure, at); song.AddNote(VLNote(name), measure, at);
PrintSong(song); PrintSong(song);
break; break;