Skip to content

Commit

Permalink
Merge pull request #453 from guiv42/musicXml
Browse files Browse the repository at this point in the history
musicXml export: multi-voice
  • Loading branch information
guiv42 committed Jul 4, 2024
2 parents 45c6ec9 + a6859dd commit 366d8b2
Showing 1 changed file with 67 additions and 134 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.herac.tuxguitar.io.musicxml;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
Expand All @@ -18,7 +20,6 @@
import org.herac.tuxguitar.gm.GMChannelRouterConfigurator;
import org.herac.tuxguitar.io.base.TGFileFormatException;
import org.herac.tuxguitar.io.musicxml.MusicXMLLyricWriter.MusicXMLMeasureLyric;
import org.herac.tuxguitar.song.factory.TGFactory;
import org.herac.tuxguitar.song.managers.TGSongManager;
import org.herac.tuxguitar.song.models.TGBeat;
import org.herac.tuxguitar.song.models.TGChannel;
Expand Down Expand Up @@ -153,10 +154,7 @@ private void writeTrack(TGTrack track, Node parent){

Iterator<TGMeasure> measures = track.getMeasures();
while(measures.hasNext()){
// TODO: Add multivoice support.
TGMeasure srcMeasure = (TGMeasure)measures.next();
TGMeasure measure = new TGVoiceJoiner(this.manager.getFactory(),srcMeasure).process();

TGMeasure measure = (TGMeasure)measures.next();
Node measureNode = this.addAttribute(this.addNode(part,"measure"), "number",Integer.toString(measure.getNumber()));

this.writeMeasureAttributes(measureNode, measure, previous, track.isPercussion());
Expand All @@ -165,22 +163,35 @@ private void writeTrack(TGTrack track, Node parent){

this.writeBarline(measureNode, measure);

MusicXMLMeasureLyric[] measureLyrics = lyricWriter.generateLyricList(srcMeasure);
MusicXMLMeasureLyric[] measureLyrics = lyricWriter.generateLyricList(measure);

// score
this.writeBeats(measureNode, measure, false, measureLyrics);

boolean measureIsEmpty = true;
for (int nVoice=0; nVoice<TGBeat.MAX_VOICES; nVoice++) {
// assuming lyrics are attached to voice 0
this.writeBeats(measureNode, measure, nVoice, measureIsEmpty, false, nVoice==0 ? measureLyrics : null);
measureIsEmpty = false;
}
// tab
if (!track.isPercussion()) {
Node backupNode = this.addNode(measureNode, "backup");
TGTimeSignature ts = measure.getTimeSignature();
this.addNode(backupNode, "duration", String.valueOf((int)(TGDuration.QUARTER * DURATION_DIVISIONS * ts.getNumerator() / ts.getDenominator().getValue())));
this.writeBeats(measureNode, measure, true, null);
measureIsEmpty = true;
backToMeasureStart(measureNode, measure);
for (int nVoice=0; nVoice<TGBeat.MAX_VOICES; nVoice++) {
this.writeBeats(measureNode, measure, nVoice, measureIsEmpty, true, null);
measureIsEmpty = false;
}
}

previous = measure;
}
}

private void backToMeasureStart(Node parent, TGMeasure measure) {
Node backupNode = this.addNode(parent, "backup");
TGTimeSignature ts = measure.getTimeSignature();
this.addNode(backupNode, "duration", String.valueOf((int)(TGDuration.QUARTER * DURATION_DIVISIONS * ts.getNumerator() / ts.getDenominator().getValue())));
}

private void writeBarline(Node parent, TGMeasure measure) {
boolean needBarline = measure.isRepeatOpen() || (measure.getRepeatClose() > 0);

Expand Down Expand Up @@ -322,21 +333,39 @@ private void writeDirection(Node parent, TGMeasure measure, TGMeasure previous){
}


private void writeBeats(Node parent, TGMeasure measure, boolean isTablature, MusicXMLMeasureLyric[] lyrics){
private void writeBeats(Node parent, TGMeasure measure, int nVoice, boolean measureIsEmpty, boolean isTablature, MusicXMLMeasureLyric[] lyrics){
int ks = measure.getKeySignature();
int beatCount = measure.countBeats();

int lyricIndex = 0;
// store first rest beats of voice in list, before finding possibly a non-empty beat
List<TGBeat> firstBeats = new ArrayList<TGBeat>();
boolean wroteSomething = false;
long lastWrittenNoteEnd = 0; // tick of last (non-empty) beat corresponding to voice

for(int b = 0; b < beatCount; b ++){
TGBeat beat = measure.getBeat( b );
TGVoice voice = beat.getVoice(0);
TGVoice voice = beat.getVoice(nVoice);

if (voice.isRestVoice() && !wroteSomething) {
firstBeats.add(beat);
continue;
}
// here, something has been found to be written
// need to rewind to measure start?
if (!measureIsEmpty && !wroteSomething) {
backToMeasureStart(parent, measure);
}
// need to insert rests before?
if (!firstBeats.isEmpty()) {
for (TGBeat restBeat : firstBeats) {
insertRest(parent, restBeat.getVoice(nVoice).getDuration(), nVoice, isTablature);
}
firstBeats.clear();
}
if(voice.isRestVoice()){
Node noteNode = this.addNode(parent,"note");
this.addNode(noteNode,"rest");
this.writeDuration(noteNode, voice.getDuration(), false);

if (beat.getStart() >= lastWrittenNoteEnd) {
insertRest(parent, voice.getDuration(), nVoice, isTablature);
}
} else {
int noteCount = voice.countNotes();

Expand All @@ -353,8 +382,7 @@ private void writeBeats(Node parent, TGMeasure measure, boolean isTablature, Mus

Node pitchNode = this.addNode(noteNode,"pitch");
this.writeNote(pitchNode, "", value, ks);
this.writeDuration(noteNode, voice.getDuration(), note.isTiedNote());

this.writeDurationAndVoice(noteNode, voice.getDuration(), note.isTiedNote(), nVoice);
this.addNode(noteNode, "staff", isTablature ? "2" : "1");

if (isTablature) {
Expand All @@ -373,11 +401,27 @@ private void writeBeats(Node parent, TGMeasure measure, boolean isTablature, Mus
// can be null if there is an offset
}
}
lastWrittenNoteEnd = beat.getStart() + voice.getDuration().getTime();
}
wroteSomething = true;
}
}
// empty measure? If so, fill with rests
if (!wroteSomething && measureIsEmpty && !firstBeats.isEmpty()) {
for (TGBeat restBeat : firstBeats) {
insertRest(parent, restBeat.getVoice(nVoice).getDuration(), nVoice, isTablature);
}
}
}

private void insertRest(Node parent, TGDuration duration, int nVoice, boolean isTablature) {
Node noteRestNode = this.addNode(parent,"note");
this.addNode(noteRestNode,"rest");
this.writeDurationAndVoice(noteRestNode, duration, false, nVoice);
this.addNode(noteRestNode, "staff", isTablature ? "2" : "1");
}


private void writeLyric(Node parent, MusicXMLMeasureLyric measureLyric) {
if (measureLyric.text.length() > 0) {
Node lyricNode = this.addNode(parent, "lyric");
Expand All @@ -387,7 +431,7 @@ private void writeLyric(Node parent, MusicXMLMeasureLyric measureLyric) {
}
}

private void writeDuration(Node parent, TGDuration duration, boolean isTiedNote){
private void writeDurationAndVoice(Node parent, TGDuration duration, boolean isTiedNote, int nVoice){
int index = duration.getIndex();
if( index >=0 && index <= 6 ){
int value = (DURATION_VALUES[ index ] * duration.getDivision().getTimes() / duration.getDivision().getEnters());
Expand All @@ -402,6 +446,7 @@ else if(duration.isDoubleDotted()){
if(isTiedNote){
this.addAttribute(this.addNode(parent,"tie"),"type","stop");
}
this.addNode(parent, "voice", String.valueOf(nVoice+1));

this.addNode(parent,"type",DURATION_NAMES[ index ]);

Expand Down Expand Up @@ -471,117 +516,5 @@ private void saveDocument() {
}
}

private static class TGVoiceJoiner {
private TGFactory factory;
private TGMeasure measure;

public TGVoiceJoiner(TGFactory factory,TGMeasure measure){
this.factory = factory;
this.measure = measure.clone(factory, measure.getHeader());
this.measure.setTrack( measure.getTrack() );
}

public TGMeasure process(){
this.orderBeats();
this.joinBeats();
return this.measure;
}

public void joinBeats(){
TGBeat previous = null;
boolean finish = true;

long measureStart = this.measure.getStart();
long measureEnd = (measureStart + this.measure.getLength());
for(int i = 0;i < this.measure.countBeats();i++){
TGBeat beat = this.measure.getBeat( i );
TGVoice voice = beat.getVoice(0);
for(int v = 1; v < beat.countVoices(); v++ ){
TGVoice currentVoice = beat.getVoice(v);
if(!currentVoice.isEmpty()){
for(int n = 0 ; n < currentVoice.countNotes() ; n++ ){
TGNote note = currentVoice.getNote( n );
voice.addNote( note );
}
}
}
if( voice.isEmpty() ){
this.measure.removeBeat(beat);
finish = false;
break;
}

long beatStart = beat.getStart();
if(previous != null){
long previousStart = previous.getStart();

TGDuration previousBestDuration = null;
for(int v = /*1*/0; v < previous.countVoices(); v++ ){
TGVoice previousVoice = previous.getVoice(v);
if(!previousVoice.isEmpty()){
long length = previousVoice.getDuration().getTime();
if( (previousStart + length) <= beatStart){
if( previousBestDuration == null || length > previousBestDuration.getTime() ){
previousBestDuration = previousVoice.getDuration();
}
}
}
}

if(previousBestDuration != null){
previous.getVoice(0).getDuration().copyFrom( previousBestDuration );
}else{
if(voice.isRestVoice()){
this.measure.removeBeat(beat);
finish = false;
break;
}
TGDuration duration = TGDuration.fromTime(this.factory, (beatStart - previousStart) );
previous.getVoice(0).getDuration().copyFrom( duration );
}
}

TGDuration beatBestDuration = null;
for(int v = /*1*/0; v < beat.countVoices(); v++ ){
TGVoice currentVoice = beat.getVoice(v);
if(!currentVoice.isEmpty()){
long length = currentVoice.getDuration().getTime();
if( (beatStart + length) <= measureEnd ){
if( beatBestDuration == null || length > beatBestDuration.getTime() ){
beatBestDuration = currentVoice.getDuration();
}
}
}
}

if(beatBestDuration == null){
if(voice.isRestVoice()){
this.measure.removeBeat(beat);
finish = false;
break;
}
TGDuration duration = TGDuration.fromTime(this.factory, (measureEnd - beatStart) );
voice.getDuration().copyFrom( duration );
}
previous = beat;
}
if(!finish){
joinBeats();
}
}

public void orderBeats(){
for(int i = 0;i < this.measure.countBeats();i++){
TGBeat minBeat = null;
for(int j = i;j < this.measure.countBeats();j++){
TGBeat beat = this.measure.getBeat(j);
if(minBeat == null || beat.getStart() < minBeat.getStart()){
minBeat = beat;
}
}
this.measure.moveBeat(i, minBeat);
}
}
}

}

0 comments on commit 366d8b2

Please sign in to comment.