Monday, July 25, 2016

JFugue26. Transpose using a Transpose Class

Now we have 3 classes in 3 different Java files. We have a Transpose class which acts to encapsulate code so the main file can concentrate on creating the pattern, and have minimum number of statements for transposing.


When a Transpose object is created we have to pass in the interval to the constructor. The main work is done in transposeFromPattern(Pattern pattern) method. The initial pattern is read, and the pattern tokens are parsed and the proper interval added to each note. Finally the method returns the transposed pattern.


This Transpose method can not handle tokens such as C3maj, instead the notes have to be written as C3+E3+G3. With a little more work it is possible to find a proper solution.


package jfugue26;

import org.jfugue.pattern.Pattern;
import org.staccato.StaccatoParser;

public class Transpose {
    Pattern pattern;
    Pattern transpose;
    int interval;
    
    public Transpose(int interval) {
        this.interval = interval;
    }
    
    public Pattern transposeFromPattern(Pattern pattern) {
        this.pattern = new Pattern(pattern);
        StaccatoParser parser = new StaccatoParser();
        JFugue26a listener = new JFugue26a();
        parser.addParserListener(listener);
        listener.setInterval(interval);
        transpose = new Pattern();
        pattern.getTokens().stream().map((token) -> {
            listener.clearSB();
            parser.parse(token.getPattern());
            return token;
        }).forEach((_item) -> {
            transpose.add(listener.getString());
        });
        return transpose;
    }
}

The parse listener is defined in Ex26a, and which the Transpose class calls.


Because the Transpose class and the Parse Listener class will be almost identical except for names, they will not be shown from now on, whenever they are needed.



package jfugue26;

import org.jfugue.parser.ParserListenerAdapter;
import org.jfugue.theory.Note;

class JFugue26a extends ParserListenerAdapter {
    private final StringBuilder sb = new StringBuilder();
    private byte interval;
    private final Note n = new Note();
    
    @Override
    public void onNoteParsed(Note note) {
        if (note.isRest()) {
            sb.append("R/");
            sb.append(note.getDuration());
        }
        else if (note.isFirstNote()) {
            n.setValue((byte) (note.getValue() + interval));
            sb.append(n.getToneString());
            sb.append(n.getOctave());
            sb.append("/");
            sb.append(note.getDuration());
        }
        else if (note.isHarmonicNote()) {
            n.setValue((byte) (note.getValue() + interval));
            sb.append("+");
            sb.append(n.getToneString());
            sb.append(n.getOctave());
            sb.append("/");
            sb.append(note.getDuration());
        }
    }
    
    public String getString() {
        String val = sb.toString();
        String[] ret = val.split("/");
        String valOut = "";
        for (int i = 0; i< ret.length-1; i++) {
            valOut += ret[i].replaceAll(".*[+]", "+");
        }
        valOut = valOut + "/" + ret[ret.length-1];
        return valOut;
    }
    
    public void setInterval(int interval) {
        this.interval = (byte) interval;
    }
    
    public void clearSB() {
        sb.delete(0, sb.length());
    }
}


Finally the main class will only have 2 statements to deal with the Transpose class and Parse Listener.


The constructor will set the interval. Next the pattern is passed in via the method transposeFromPattern, and the transposed pattern is returned.



package jfugue26;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import org.jfugue.pattern.Pattern;
import org.jfugue.player.Player;

public class JFugue26 extends Application {
    
    public static void main(String[] args) {
        launch(args);
    }
    
    TextArea text;
    Button button;
    int interval = 2;
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        
        button = new Button("Transpose notes\n\tusing\n"
                + "Transpose class");
                
        button.setOnAction(e->example());
        button.setFont(Font.font("Verdana", 16));
        button.setPrefSize(200, 100);
        
        VBox examples = new VBox(10, button);
        examples.setPadding(new Insets(10));
        
        text = new TextArea();
        text.setPrefRowCount(20);
        text.setPrefColumnCount(32);
        text.setFont(Font.font("Verdana", 20));
        text.setEditable(false);
        text.setWrapText(true);
        
        HBox root = new HBox(50,examples,text);
        
        Scene scene = new Scene(root, 900, 600);
        primaryStage.setTitle("JFugue 26. Transpose Class");
        
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private void example() {
        
        Player player = new Player();
        
        Pattern p1=new Pattern("F5q Rq Ab5q Ri F5q F5i Bb5q F5q E#5q");
        Pattern p2=new Pattern("F5q Rq C6q Ri F5q F5i Db6q C6q Ab5q");
        Pattern p3=new Pattern("F5q C6q F6q F5i Eb5q Eb5i C5q G5q F5q.");
        Pattern pattern = new Pattern(p1,p2,p3);
        text.appendText("\n\nPattern:\n" + pattern + "\n\n");
        
        Transpose transpose = new Transpose(interval);
        Pattern transposePattern=transpose.transposeFromPattern(pattern);
        
        text.appendText(
                String.format("Transposing by %d semitones" 
                        + " (%.2f octave):\n",
                        interval, interval/12.0));
        
        text.appendText("\nTranspose Pattern:\n" + transposePattern);
        pattern.setInstrument("SYNTH_BASS_2").setTempo(220);
        transposePattern.setInstrument("SYNTH_BASS_1").setTempo(180);
        Pattern comp = new Pattern(pattern, new Pattern("Rh"),
                transposePattern);
        player.play(comp);
    }
}

This is the output after calling for a 2 semitone transposition:


No comments:

Post a Comment