mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 19:23:59 +00:00
Implement division/time change
This commit is contained in:
parent
bab7111fa3
commit
4afefae126
|
@ -204,16 +204,10 @@
|
|||
- (void) setKey:(int)key transpose:(BOOL)transpose
|
||||
{
|
||||
[self willChangeSong];
|
||||
|
||||
VLProperties & prop = song->fProperties.front();
|
||||
|
||||
if (transpose)
|
||||
song->Transpose((7*((key>>8)-prop.fKey) % 12));
|
||||
|
||||
prop.fKey = key >> 8;
|
||||
prop.fMode= key & 0xFF;
|
||||
|
||||
[self updateChangeCount:NSChangeDone];
|
||||
[self willChangeValueForKey:@"songKey"];
|
||||
song->ChangeKey(key>>8, key & 0xFF, transpose);
|
||||
[self didChangeValueForKey:@"songKey"];
|
||||
[self didChangeSong];
|
||||
}
|
||||
|
||||
- (NSNumber *) songTime
|
||||
|
@ -226,12 +220,10 @@
|
|||
- (void) setTimeNum:(int)num denom:(int)denom
|
||||
{
|
||||
[self willChangeSong];
|
||||
|
||||
VLProperties & prop = song->fProperties.front();
|
||||
|
||||
prop.fTime = VLFraction(num, denom);
|
||||
|
||||
[self updateChangeCount:NSChangeDone];
|
||||
[self willChangeValueForKey:@"songTime"];
|
||||
song->ChangeTime(VLFraction(num, denom));
|
||||
[self didChangeValueForKey:@"songTime"];
|
||||
[self didChangeSong];
|
||||
}
|
||||
|
||||
- (NSNumber *) songDivisions
|
||||
|
@ -244,12 +236,11 @@
|
|||
- (void) setDivisions:(int)divisions
|
||||
{
|
||||
[self willChangeSong];
|
||||
|
||||
VLProperties & prop = song->fProperties.front();
|
||||
|
||||
prop.fDivisions = divisions;
|
||||
|
||||
[self updateChangeCount:NSChangeDone];
|
||||
[self willChangeValueForKey:@"songDivisions"];
|
||||
[self willChangeSong];
|
||||
song->ChangeDivisions(divisions);
|
||||
[self didChangeValueForKey:@"songDivisions"];
|
||||
[self didChangeSong];
|
||||
}
|
||||
|
||||
- (int) repeatVolta
|
||||
|
|
|
@ -852,7 +852,7 @@ VLSong::VLSong(bool initialize)
|
|||
|
||||
void VLSong::AddMeasure()
|
||||
{
|
||||
VLFraction dur = fProperties[0].fTime;
|
||||
VLFraction dur = fProperties.front().fTime;
|
||||
VLLyricsNote rest = VLLyricsNote(VLRest(dur));
|
||||
VLChord rchord;
|
||||
rchord.fDuration = dur;
|
||||
|
@ -1131,22 +1131,6 @@ void VLSong::ExtendNote(size_t measure, VLFraction at)
|
|||
}
|
||||
}
|
||||
|
||||
static void TransposePinned(int8_t & pitch, int semi)
|
||||
{
|
||||
if (pitch == VLNote::kNoPitch)
|
||||
return;
|
||||
|
||||
int pitchInOctave = pitch % 12;
|
||||
int octave = pitch-pitchInOctave;
|
||||
pitchInOctave += semi;
|
||||
if (pitchInOctave < 0)
|
||||
pitch = octave+pitchInOctave+12;
|
||||
else if (pitchInOctave > 11)
|
||||
pitch = octave+pitchInOctave-12;
|
||||
else
|
||||
pitch = octave+pitchInOctave;
|
||||
}
|
||||
|
||||
bool VLSong::IsNonEmpty() const
|
||||
{
|
||||
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
||||
|
@ -1168,8 +1152,31 @@ bool VLSong::IsNonEmpty() const
|
|||
return false;
|
||||
}
|
||||
|
||||
void VLSong::Transpose(int semi)
|
||||
static void TransposePinned(int8_t & pitch, int semi)
|
||||
{
|
||||
if (pitch == VLNote::kNoPitch)
|
||||
return;
|
||||
|
||||
int pitchInOctave = pitch % 12;
|
||||
int octave = pitch-pitchInOctave;
|
||||
pitchInOctave += semi;
|
||||
if (pitchInOctave < 0)
|
||||
pitch = octave+pitchInOctave+12;
|
||||
else if (pitchInOctave > 11)
|
||||
pitch = octave+pitchInOctave-12;
|
||||
else
|
||||
pitch = octave+pitchInOctave;
|
||||
}
|
||||
|
||||
void VLSong::ChangeKey(int newKey, bool newMode, bool transpose)
|
||||
{
|
||||
VLProperties & prop = fProperties.front();
|
||||
int semi = 7*(newKey-prop.fKey) % 12;
|
||||
prop.fKey = newKey;
|
||||
prop.fMode = newMode;
|
||||
if (!transpose)
|
||||
return;
|
||||
|
||||
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
||||
VLChordList::iterator i = fMeasures[measure].fChords.begin();
|
||||
VLChordList::iterator e = fMeasures[measure].fChords.end();
|
||||
|
@ -1203,6 +1210,192 @@ void VLSong::Transpose(int semi)
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// We try a table based approach for converting the beginning and end of
|
||||
// notes
|
||||
//
|
||||
|
||||
static const uint8_t sDiv2_3[] = {0, 2};
|
||||
static const uint8_t sDiv2_4[] = {0, 2};
|
||||
static const uint8_t sDiv2_6[] = {0, 3};
|
||||
static const uint8_t sDiv2_8[] = {0, 4};
|
||||
static const uint8_t sDiv2_12[]= {0, 6};
|
||||
static const uint8_t * sDiv2[] = {
|
||||
NULL, sDiv2_3, sDiv2_4, NULL, sDiv2_6,
|
||||
NULL, sDiv2_8, NULL, NULL, NULL, sDiv2_12};
|
||||
|
||||
static const uint8_t sDiv3_2[] = {0, 1, 1};
|
||||
static const uint8_t sDiv3_4[] = {0, 2, 3};
|
||||
static const uint8_t sDiv3_6[] = {0, 2, 4};
|
||||
static const uint8_t sDiv3_8[] = {0, 3, 6};
|
||||
static const uint8_t sDiv3_12[]= {0, 4, 8};
|
||||
static const uint8_t * sDiv3[] = {
|
||||
sDiv3_2, NULL, sDiv3_4, NULL, sDiv3_6,
|
||||
NULL, sDiv3_8, NULL, NULL, NULL, sDiv3_12};
|
||||
|
||||
static const uint8_t sDiv4_2[] = {0, 0, 1, 1};
|
||||
static const uint8_t sDiv4_3[] = {0, 1, 2, 2};
|
||||
static const uint8_t sDiv4_6[] = {0, 2, 3, 5};
|
||||
static const uint8_t sDiv4_8[] = {0, 2, 4, 6};
|
||||
static const uint8_t sDiv4_12[]= {0, 3, 6, 9};
|
||||
static const uint8_t * sDiv4[] = {
|
||||
sDiv4_2, sDiv4_3, NULL, NULL, sDiv4_6,
|
||||
NULL, sDiv4_8, NULL, NULL, NULL, sDiv4_12};
|
||||
|
||||
static const uint8_t sDiv6_2[] = {0, 0, 0, 1, 1, 1};
|
||||
static const uint8_t sDiv6_3[] = {0, 0, 1, 1, 2, 2};
|
||||
static const uint8_t sDiv6_4[] = {0, 1, 2, 2, 3, 3};
|
||||
static const uint8_t sDiv6_8[] = {0, 2, 3, 4, 6, 7};
|
||||
static const uint8_t sDiv6_12[]= {0, 2, 4, 6, 8,10};
|
||||
static const uint8_t * sDiv6[] = {
|
||||
sDiv6_2, sDiv6_3, sDiv6_4, NULL, NULL,
|
||||
NULL, sDiv6_8, NULL, NULL, NULL, sDiv6_12};
|
||||
|
||||
static const uint8_t sDiv8_2[] = {0, 0, 0, 0, 1, 1, 1, 1};
|
||||
static const uint8_t sDiv8_3[] = {0, 0, 1, 1, 1, 2, 2, 2};
|
||||
static const uint8_t sDiv8_4[] = {0, 0, 1, 1, 2, 2, 3, 3};
|
||||
static const uint8_t sDiv8_6[] = {0, 1, 2, 2, 3, 4, 5, 5};
|
||||
static const uint8_t sDiv8_12[]= {0, 2, 3, 5, 6, 8, 9,11};
|
||||
static const uint8_t * sDiv8[] = {
|
||||
sDiv8_2, sDiv8_3, sDiv8_4, NULL, sDiv8_6,
|
||||
NULL, NULL, NULL, NULL, NULL, sDiv8_12};
|
||||
|
||||
static const uint8_t sDiv12_2[]= {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1};
|
||||
static const uint8_t sDiv12_3[]= {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2};
|
||||
static const uint8_t sDiv12_4[]= {0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3};
|
||||
static const uint8_t sDiv12_6[]= {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5};
|
||||
static const uint8_t sDiv12_8[]= {0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 7};
|
||||
static const uint8_t * sDiv12[]= {
|
||||
sDiv12_2, sDiv12_3, sDiv12_4, NULL, sDiv12_6,
|
||||
NULL, sDiv12_8, NULL, NULL, NULL, NULL};
|
||||
|
||||
static const uint8_t ** sDiv[] = {
|
||||
sDiv2, sDiv3, sDiv4, NULL, sDiv6,
|
||||
NULL, sDiv8, NULL, NULL, NULL, sDiv12};
|
||||
|
||||
class VLRealigner {
|
||||
public:
|
||||
VLRealigner(int oldDiv, int newDiv);
|
||||
|
||||
VLFraction operator()(VLFraction at);
|
||||
private:
|
||||
VLFraction fOldFrac;
|
||||
VLFraction fNewFrac;
|
||||
const uint8_t * fTable;
|
||||
};
|
||||
|
||||
VLRealigner::VLRealigner(int oldDiv, int newDiv)
|
||||
: fOldFrac(1, 4*oldDiv), fNewFrac(1, 4*newDiv),
|
||||
fTable(sDiv[oldDiv-2][newDiv-2])
|
||||
{
|
||||
}
|
||||
|
||||
VLFraction VLRealigner::operator()(VLFraction at)
|
||||
{
|
||||
VLFraction quarters(4*at.fNum / at.fDenom, 4);
|
||||
at = (at-quarters) / fOldFrac;
|
||||
|
||||
return quarters + fTable[at.fNum / at.fDenom]*fNewFrac;
|
||||
}
|
||||
|
||||
void VLSong::ChangeDivisions(int newDivisions)
|
||||
{
|
||||
VLProperties & prop = fProperties.front();
|
||||
if (newDivisions == prop.fDivisions)
|
||||
return; // Unchanged
|
||||
|
||||
VLRealigner realign(prop.fDivisions, newDivisions);
|
||||
//
|
||||
// Only melody needs to be realigned, chords are already quarter notes
|
||||
//
|
||||
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;
|
||||
rchord.fDuration = newTime-prop.fTime;
|
||||
VLLyricsNote rnote = VLLyricsNote(VLRest(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);
|
||||
VLChordList newChords;
|
||||
|
||||
for (; i!=e; ++i) {
|
||||
VLChord c = *i;
|
||||
if (at+c.fDuration >= newTime) {
|
||||
if (at < newTime) {
|
||||
c.fDuration = newTime-at;
|
||||
newChords.push_back(c);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
newChords.push_back(c);
|
||||
at += c.fDuration;
|
||||
}
|
||||
}
|
||||
fMeasures[measure].fChords.swap(newChords);
|
||||
} else
|
||||
fMeasures[measure].fChords.push_back(rchord);
|
||||
|
||||
if (newTime < prop.fTime) {
|
||||
VLNoteList::iterator i = fMeasures[measure].fMelody.begin();
|
||||
VLNoteList::iterator e = fMeasures[measure].fMelody.end();
|
||||
VLFraction at(0);
|
||||
VLNoteList newMelody;
|
||||
|
||||
for (; i!=e; ++i) {
|
||||
VLLyricsNote n = *i;
|
||||
if (at+n.fDuration >= newTime) {
|
||||
if (at < newTime) {
|
||||
n.fDuration = newTime-at;
|
||||
newMelody.push_back(n);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
newMelody.push_back(n);
|
||||
at += n.fDuration;
|
||||
}
|
||||
}
|
||||
fMeasures[measure].fMelody.swap(newMelody);
|
||||
} else
|
||||
fMeasures[measure].fMelody.push_back(rnote);
|
||||
}
|
||||
prop.fTime = newTime;
|
||||
}
|
||||
|
||||
size_t VLSong::EmptyEnding() const
|
||||
{
|
||||
size_t full = fMeasures.size();
|
||||
|
@ -1289,7 +1482,7 @@ void VLSong::LilypondNotes(std::string & notes) const
|
|||
int numEndings = 0;
|
||||
size_t endMeasure = fMeasures.size()-EmptyEnding();
|
||||
bool pickup = fMeasures[0].NoChords();
|
||||
for (size_t measure=0; measure<fMeasures.size(); ++measure) {
|
||||
for (size_t measure=0; measure<endMeasure; ++measure) {
|
||||
VLNoteList::const_iterator i = fMeasures[measure].fMelody.begin();
|
||||
VLNoteList::const_iterator e = fMeasures[measure].fMelody.end();
|
||||
VLFraction at(0);
|
||||
|
|
|
@ -333,7 +333,9 @@ struct VLSong {
|
|||
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;
|
||||
bool IsNonEmpty() const;
|
||||
void Transpose(int semitones);
|
||||
void ChangeKey(int newKey, bool newMode, bool transpose);
|
||||
void ChangeDivisions(int newDivisions);
|
||||
void ChangeTime(VLFraction newTime);
|
||||
|
||||
bool FindWord(size_t stanza, size_t & measure, VLFraction & at);
|
||||
bool PrevWord(size_t stanza, size_t & measure, VLFraction & at);
|
||||
|
|
Loading…
Reference in New Issue
Block a user