mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 11:14:00 +00:00
Store per-section groove, store tempo in file
This commit is contained in:
parent
77dce75c96
commit
7b7ae7f3d8
|
@ -102,6 +102,7 @@ class MusicXMLListener
|
|||
|
||||
def initialize
|
||||
@text = ""
|
||||
@nativeFile = false
|
||||
end
|
||||
|
||||
def tag_start(tag, attrs)
|
||||
|
@ -199,6 +200,12 @@ class MusicXMLListener
|
|||
when 'beat-type'
|
||||
@kind = 'prop'
|
||||
@key = 'timeDenom'
|
||||
when 'words'
|
||||
#
|
||||
# 'words' is a very general direction type, but if we encoded the file
|
||||
# we know that we used it for groove names
|
||||
#
|
||||
@kind = 'groove' if @nativeFile
|
||||
when 'harmony'
|
||||
@note = { 'pitch' => VL::NoPitch, 'steps' => 0, 'root' => VL::NoPitch,
|
||||
'at' => @at }
|
||||
|
@ -304,6 +311,9 @@ class MusicXMLListener
|
|||
case @kind
|
||||
when 'textProp'
|
||||
OUTPUT[@key] = @text
|
||||
if @key == 'software' && @text =~ /VocalEasel/
|
||||
@nativeFile = true # We (presumably) encoded this
|
||||
end
|
||||
when 'dateProp'
|
||||
begin
|
||||
OUTPUT[@key] = Time.parse(@text)
|
||||
|
@ -312,6 +322,8 @@ class MusicXMLListener
|
|||
end
|
||||
when 'prop'
|
||||
@prop[@key] = @text.to_i
|
||||
when 'groove'
|
||||
@prop['groove'] = @text
|
||||
when 'mode'
|
||||
@prop['mode'] = @text == 'minor' ? -1 : 1
|
||||
when 'step'
|
||||
|
|
|
@ -79,6 +79,28 @@ def _attributes(prop)
|
|||
return attr
|
||||
end
|
||||
|
||||
def _groove(groove)
|
||||
dir = REXML::Element.new('direction')
|
||||
dty = REXML::Element.new('direction-type')
|
||||
dty.add_element newTextElement('words', groove)
|
||||
dir.add_element(dty)
|
||||
|
||||
return dir
|
||||
end
|
||||
|
||||
def _tempo(tempo)
|
||||
dir = REXML::Element.new('direction')
|
||||
dty = REXML::Element.new('direction-type')
|
||||
metro = REXML::Element.new('metronome')
|
||||
metro.add_element newTextElement('beat-unit', 'quarter')
|
||||
metro.add_element newTextElement('per-minute', tempo)
|
||||
dty.add_element(metro)
|
||||
dir.add_element(dty)
|
||||
dir.add_element 'sound', {'tempo' => tempo}
|
||||
|
||||
return dir
|
||||
end
|
||||
|
||||
STEPS = 'C DbD EbE F GbG AbA BbB '
|
||||
|
||||
def _pitch(name, pitch, prefix="")
|
||||
|
@ -293,6 +315,7 @@ def _melody
|
|||
melody.add_attribute('id', 'MELO')
|
||||
|
||||
lastProp = -1
|
||||
lastGroove = nil
|
||||
measNum = 0
|
||||
repeat = [0]
|
||||
INPUT['measures'].each do |meas|
|
||||
|
@ -306,8 +329,16 @@ def _melody
|
|||
measNum += 1
|
||||
m = REXML::Element.new('measure')
|
||||
m.add_attribute('number', measNum.to_s);
|
||||
if measNum == 1
|
||||
m.add_element(_tempo(INPUT['tempo'].to_i))
|
||||
end
|
||||
if meas['properties'] != lastProp
|
||||
lastProp = meas['properties']
|
||||
groove = INPUT['properties'][lastProp]['groove']
|
||||
if groove != lastGroove
|
||||
lastGroove = groove
|
||||
m.add_element(_groove(lastGroove))
|
||||
end
|
||||
m.add_element(_attributes(INPUT['properties'][lastProp]))
|
||||
end
|
||||
if meas['new-page']
|
||||
|
|
|
@ -540,7 +540,7 @@ VLSong::VLSong(bool initialize)
|
|||
return;
|
||||
|
||||
const VLFraction fourFour(4,4);
|
||||
VLProperties defaultProperties = {fourFour, 0, 1, 3};
|
||||
VLProperties defaultProperties = {fourFour, 0, 1, 3, "Swing"};
|
||||
|
||||
fProperties.push_back(defaultProperties);
|
||||
|
||||
|
@ -2053,7 +2053,7 @@ VLFract VLSong::TiedDuration(size_t measure)
|
|||
return total;
|
||||
}
|
||||
|
||||
bool VLSong::DoesBeginSection(size_t measure)
|
||||
bool VLSong::DoesBeginSection(size_t measure) const
|
||||
{
|
||||
return measure && measure < fMeasures.size()
|
||||
&& fMeasures[measure-1].fPropIdx!=fMeasures[measure].fPropIdx;
|
||||
|
@ -2081,6 +2081,47 @@ void VLSong::DelSection(size_t measure)
|
|||
--fMeasures[measure++].fPropIdx;
|
||||
}
|
||||
|
||||
std::string VLSong::PrimaryGroove() const
|
||||
{
|
||||
std::string bestGroove = fProperties[0].fGroove;
|
||||
|
||||
for (size_t p=1; p<fProperties.size()-EmptyEnding(); ++p)
|
||||
if (fProperties[p].fGroove != bestGroove) {
|
||||
//
|
||||
// Multiple grooves in song, count them the hard way
|
||||
//
|
||||
std::vector<size_t> numMeas(fProperties.size());
|
||||
for (size_t m=0; m<fMeasures.size(); ++m)
|
||||
++numMeas[fMeasures[m].fPropIdx];
|
||||
|
||||
size_t bestCount = numMeas[0];
|
||||
for (size_t px=1; px<fProperties.size(); ++px)
|
||||
if (fProperties[px].fGroove == bestGroove) {
|
||||
bestCount += numMeas[px];
|
||||
numMeas[px]= 0;
|
||||
}
|
||||
|
||||
for (; p<fProperties.size(); ++p)
|
||||
if (numMeas[p]) {
|
||||
std::string curGroove= fProperties[p].fGroove;
|
||||
size_t curCount = numMeas[p];
|
||||
for (size_t px=p+1; px<fProperties.size(); ++px)
|
||||
if (fProperties[px].fGroove == curGroove) {
|
||||
curCount += numMeas[px];
|
||||
numMeas[px]= 0;
|
||||
}
|
||||
if (curCount > bestCount) {
|
||||
bestGroove= curGroove;
|
||||
bestCount = curCount;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return bestGroove;
|
||||
}
|
||||
|
||||
//////////////////////// VLSongVisitor ////////////////////////////////
|
||||
|
||||
VLSongVisitor::~VLSongVisitor()
|
||||
|
|
|
@ -221,10 +221,11 @@ struct VLProperties {
|
|||
int8_t fKey; // Circle of fifths from C, >0 sharps, <0 flats
|
||||
int8_t fMode; // 1 = major -1 = minor
|
||||
int8_t fDivisions; // Number of divisions per quarter note
|
||||
std::string fGroove; // MMA Groove
|
||||
|
||||
bool operator==(const VLProperties & other)
|
||||
{ return fTime == other.fTime && fKey == other.fKey && fMode == other.fMode
|
||||
&& fDivisions == other.fDivisions;
|
||||
&& fDivisions == other.fDivisions && fGroove == other.fGroove;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -377,9 +378,11 @@ public:
|
|||
return fProperties[fMeasures[measure].fPropIdx];
|
||||
}
|
||||
|
||||
bool DoesBeginSection(size_t measure);
|
||||
bool DoesBeginSection(size_t measure) const;
|
||||
void AddSection(size_t measure);
|
||||
void DelSection(size_t measure);
|
||||
|
||||
std::string PrimaryGroove() const;
|
||||
private:
|
||||
void AddMeasure();
|
||||
};
|
||||
|
|
|
@ -61,6 +61,7 @@ NSDictionary * VLPlistVisitor::EncodeProperties(const VLProperties & properties)
|
|||
[NSNumber numberWithInt: properties.fKey], @"key",
|
||||
[NSNumber numberWithInt: properties.fMode], @"mode",
|
||||
[NSNumber numberWithInt: properties.fDivisions], @"divisions",
|
||||
[NSString stringWithUTF8String:properties.fGroove.c_str()], @"groove",
|
||||
nil];
|
||||
}
|
||||
|
||||
|
@ -166,8 +167,8 @@ void VLPlistVisitor::VisitChord(VLChord & c)
|
|||
{
|
||||
NSMutableDictionary * plist =
|
||||
[NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
songTitle, @"title",
|
||||
songGroove, @"groove", songTempo, @"tempo",
|
||||
songTitle, @"title", songTempo, @"tempo",
|
||||
[NSString stringWithUTF8String:song->PrimaryGroove().c_str()], @"groove",
|
||||
songComposer, @"composer", songLyricist, @"lyricist",
|
||||
[NSDate date], @"saved",
|
||||
[NSString stringWithFormat:@"VocalEasel %@",
|
||||
|
@ -405,6 +406,11 @@ advanceAt:
|
|||
prop.fMode = [[pdict objectForKey:@"mode"] intValue];
|
||||
prop.fDivisions = [[pdict objectForKey:@"divisions"] intValue];
|
||||
|
||||
if (NSString * groove = [pdict objectForKey:@"groove"])
|
||||
prop.fGroove = [groove UTF8String];
|
||||
else
|
||||
prop.fGroove = [songGroove UTF8String];
|
||||
|
||||
song->fProperties.push_back(prop);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,18 +13,6 @@
|
|||
|
||||
@implementation VLDocument (XML)
|
||||
|
||||
|
||||
- (NSArray *)propertyKeys
|
||||
{
|
||||
static NSArray * sPropertyKeys = nil;
|
||||
|
||||
if (!sPropertyKeys)
|
||||
sPropertyKeys = [[NSArray alloc] initWithObjects:
|
||||
@"songGroove", @"songTempo", nil];
|
||||
|
||||
return sPropertyKeys;
|
||||
}
|
||||
|
||||
- (NSFileWrapper *)XMLFileWrapperWithError:(NSError **)outError flat:(BOOL)flat;
|
||||
{
|
||||
NSFileWrapper * contents = [self fileWrapperWithFilter:@"VLMusicXMLType" error:outError];
|
||||
|
@ -40,13 +28,6 @@
|
|||
autorelease];
|
||||
[contents setPreferredFilename:@"Song"];
|
||||
[wrap addFileWrapper:contents];
|
||||
NSDictionary * prop =
|
||||
[self dictionaryWithValuesForKeys:[self propertyKeys]];
|
||||
[wrap addRegularFileWithContents:
|
||||
[NSPropertyListSerialization dataFromPropertyList:prop
|
||||
format:NSPropertyListXMLFormat_v1_0
|
||||
errorDescription:nil]
|
||||
preferredFilename:@"Properties"];
|
||||
if (vcsWrapper)
|
||||
[wrap addFileWrapper:vcsWrapper];
|
||||
|
||||
|
@ -62,6 +43,9 @@
|
|||
|| (vcsWrapper = [wrappers objectForKey:@".svn"])
|
||||
)
|
||||
[vcsWrapper retain];
|
||||
//
|
||||
// Read properties dictionary for backward compatibility
|
||||
//
|
||||
NSFileWrapper * prop = [wrappers objectForKey:@"Properties"];
|
||||
if (prop) {
|
||||
NSUndoManager * undoMgr = [self undoManager];
|
||||
|
|
Loading…
Reference in New Issue
Block a user