2

I have run into a problem with JFugue 5.0.7: I'm coding a realtime application that plays music indefinitely, but the RealtimePlayer provided by the jfugue library mixes instruments with each other.

I have the following code

public class HelloJFugue {

    public static int BPM = 150;
    public static int SIGNATURE = 3;
    public static boolean RANDOM = false;

    public static void main(String[] args) throws MidiUnavailableException {
        RealtimePlayer player = new RealtimePlayer();
        BlockingQueue<Pattern> queue = new SynchronousQueue<>();

        List<PatternProducer> pProducers = createPatternProducers();

        Thread pConsumer = new Thread(new PatternConsumer(player, queue));
        Thread pProducer = new Thread(new PatternMediator(queue, pProducers));

        pConsumer.start();
        pProducer.start();
    }

    private static List<PatternProducer> createPatternProducers() {
        Random rand = new Random();

        PatternProducer rightHand = new PatternProducer() {
            int counter = 0;
            String[] patterns = {
                "Rq Rq E6i D#6i",
                "E6i D#6i E6i B5i D6i C6i",
                "A5q Ri C5i E5i A5i",
                "B5q Ri E5i G#5i B5i",
                "C6q Ri E5i E6i D#6i",
                "E6i D#6i E6i B5i D6i C6i",
                "A5q Ri C5i E5i A5i",
                "B5q Ri E5i C6i B5i",
                "A5q Ri E5i E6i D#6i"
            };

            @Override
            public Pattern getPattern() {
                Pattern p = new Pattern(patterns[RANDOM ? rand.nextInt(patterns.length - 1) + 1 : counter])
                        .setVoice(0)
                        .setInstrument("Piano");
                counter++;
                if (counter >= patterns.length) {
                    counter = 1;
                }
                return p;
            }
        };

        PatternProducer leftHand = new PatternProducer() {
            int counter = 0;
            String[] patterns = {
                "Rq Rq Rq",
                "Rq Rq Rq",
                "A3i E4i A4i Ri Rq",
                "E3i E4i G#4i Ri Rq",
                "A3i E4i A4i Ri Rq",
                "Rq Rq Rq",
                "A3i E4i A4i Ri Rq",
                "E3i E4i G#4i Ri Rq",
                "A3i E4i A4i Ri Rq"
            };

            @Override
            public Pattern getPattern() {
                Pattern p = new Pattern(patterns[RANDOM ? rand.nextInt(patterns.length - 1) + 1 : counter])
                        .setVoice(1)
                        .setInstrument("Guitar");
                counter++;
                if (counter >= patterns.length) {
                    counter = 1;
                }
                return p;
            }
        };

        return new ArrayList<PatternProducer>() {
            {
                add(rightHand);
                add(leftHand);
            }
        };
    }
}

public class PatternMediator implements Runnable {

    private BlockingQueue<Pattern> queue;
    private List<PatternProducer> producers;

    public PatternMediator(BlockingQueue<Pattern> queue, List<PatternProducer> producers) {
        this.queue = queue;
        this.producers = producers;
    }

    private void fillQueue() throws InterruptedException {
        Pattern p = new Pattern();
        for (PatternProducer producer : producers) {
            p.add(producer.getPattern().setTempo(HelloJFugue.BPM));
        }
        this.queue.put(p);
    }

    @Override
    public void run() {
        while (true) {
            try {
                fillQueue();
            } catch (InterruptedException ex) {
                Logger.getLogger(PatternMediator.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

public class PatternConsumer implements Runnable {

    private RealtimePlayer player;
    private BlockingQueue<Pattern> queue;

    public PatternConsumer(RealtimePlayer player, BlockingQueue<Pattern> queue) {
        this.player = player;
        this.queue = queue;
    }

    private void playFromQueue() throws InterruptedException {
        player.play(queue.take());
    }

    @Override
    public void run() {
        while (true) {
            try {
                playFromQueue();
                Thread.sleep(HelloJFugue.SIGNATURE * 60000 / HelloJFugue.BPM);
            } catch (InterruptedException ex) {
                Logger.getLogger(PatternConsumer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

The problem is that, when playing the two instruments (guitar and piano) get mixed, and the two cannot be played simultanously. Instead the player changes between the two instruments haphazardly, so that only one instrument, playing both patterns, can be heard at one time.

Esa Lindqvist
  • 311
  • 2
  • 6
  • I realize that `sleep` is a horrible way to synchronize. This is me just trying out the capabilities of JFugue – Esa Lindqvist Feb 15 '17 at 11:02
  • 1
    I see what's happening here: voices and instruments are getting intermixed from your left and right hand patterns. What JFugue needs is a way to recognize a voice + layer + instrument + note tuplet as a single, atomic musical element. Driven by your example, I'm working on a new class, "Atom", and a pattern.atomize() method (as well as a new AtomSubparser). My goal is that you can use the same code as you have, with the addition of a pattern.atomize() call. I'll keep you posted and answer this question when it works. – David Koelle Feb 15 '17 at 14:43
  • Thanks David Koelle, that's awesome. – Esa Lindqvist Feb 15 '17 at 17:54
  • 1
    Totally not related to your bug, but given the beautiful music you're working on (and yes, atomize() is now working... answer coming soon!), have you heard of "Musikalisches Würfelspiel"? https://en.wikipedia.org/wiki/Musikalisches_W%C3%BCrfelspiel – David Koelle Feb 16 '17 at 02:42
  • 1
    I had not. Though it sounds a lot like what I'm trying to build here. My thought was to make chord progressions + melody semi-random using weighted probability based on 1) the scale degree of the currently playing chord and 2) selected scale.So basically I'm trying to create an algorithmic composition (created and played in realtime), but with more leeway in terms of melody and scale progression. – Esa Lindqvist Feb 16 '17 at 08:14
  • I have one request: could you add method "addParserListener" (can be protected) to RealtimePlayer class, so that I could add my own listeners (that are notified on parse completion etc.)? Or do you have a preferable solution to synchronize between Pattern producers and consumers without timers? – Esa Lindqvist Feb 18 '17 at 18:49
  • Let's continue this conversation via email, I think there are some details to delve into here. You'll easily find my email address on bottom of the JFugue webpage. – David Koelle Feb 19 '17 at 05:04
  • Thanks, I sent you an email. – Esa Lindqvist Feb 19 '17 at 18:28

1 Answers1

2

This has been resolved with the introduction of an Atom in JFugue 5.0.8. An Atom contains voice, instrument, and note information in one atomic unit, so instruments won't become disconnected from the notes they are used to play.

I describe this update more fully here: https://medium.com/@dmkoelle/whats-new-in-jfugue-5-0-8-7479abca3be4

David Koelle
  • 20,726
  • 23
  • 93
  • 130