Search code examples
javajfugue

JFugue 5 note problems


I'm trying to use JFugue 5.0.9 in my Java project to create *.midi files with it. While implementing JFugue's midi capabilities into my project that uses frequencies of a 24 tune makam piano, I realized it has some tune issues.

For example this code:

ChordProgression cp = new ChordProgression("I-III-IV-iv").setKey("E");
System.out.println(cp);
Player player = new Player();
player.play(cp);

should print

E4MAJ G#4MAJ A4MAJ A4MIN

on console as it's said here.

But prints

E4MAJ E4MAJ E4MAJ A4MIN

in my project. Yes, first three chords are same for some reason. They sound same also, of course.

Also, while using microtones like "m390", it doesn't sound exactly in that frequency.

In another file, in the main method I wrote this:

Player player = new Player();
player.play("A4 m440 m400 m390 m380 m370 m360");

I know that A4 and m440 are the same but as it's said here, A5 and m440 should sound same. But in my project A4 and m440 sound same but it's not exactly 440Hz. When I realized something goes wrong, I decided to use a tuning app and here are the frequencies it calculated respectively:

221.5    221.5    197.5    186.6    186.6    186.6    176.2

As you can see, it plays something very near A3 instead of a clear A4. But this is not all. It also sounds exactly same for m390, m380 and m370.

What exactly is wrong here? Any help would be appreciated.

A little note: I'm pretty sure it has nothing to do with my project. I tryed running the codes above in a brand new project, the same problems occured. And there's no problem with my system because my main project and any other softwares like SunVox, they all sound very good actually.


Solution

  • OK, I had a look at the source codes of JFugue 5.0.9 and here is what I got in ChordProgression.java:

        /** 
     * Only converts Roman numerals I through VII, because that's all we need in music theory... 
     * VIII would be the octave and equal I!
     */
    private int romanNumeralToIndex(String romanNumeral) {
        String s = romanNumeral.toLowerCase();
        if (s.startsWith("vii")) { return 6; } 
        else if (s.startsWith("vi")) { return 5; }
        else if (s.startsWith("v")) { return 4; }
        else if (s.startsWith("iv")) { return 3; }
        else if (s.startsWith("iii")) { return 2; }
        else if (s.startsWith("ii")) { return 1; }
        else if (s.startsWith("i")) { return 0; }
        else { return 0; }
    }
    

    A little laziness... :D One of the problems lies right here. If you use toLowerCase() method without specifying a locale, it causes some problems in runtime. In Turkish alphabet which I'm using right now lower case of I is "ı", not "i". So, the program converts my chords from "I-II-III" to "i-i-i" because as you can see there is no if statement for lower case I of Turkish alphabet (ı) and this leads it to return 0 as in the case of "i".

    To solve this issue, we have to either delete lower case conversion and write all if statements for also upper case I's or set default locale to "en" to make sure it converts (upper case)I's to (lower case)i's. So, this should be used in the source:

    Locale.setDefault(new Locale("en"));
    

    Fortunately, JFugue is an open source software published under Apache 2.0.

    This still doesn't explain why tunes sound wrong but now at least we know that these are totally different problems. Or maybe it seems so... I don't know. I will edit this answer if I ever find out an explanation or a solution for the rest.

    Edit

    Finally I figured out the problem with microtones by accident. I decided to look at microtone calculation functions in the source one more time.

    In MicrotonePreprocessor class (which is in org.staccato package) there is a function named convertFrequencyToStaccato(). What this function does is convert frequency to midi note number and pitch bend value. At the 107th line, that code rounds semitone, octave and pitch values if calculated pitch value is very close to the next note:

    // If we're close enough to the next note, just use the next note. 
    if (pitches >= 16380)
    {
       pitches = 0;
       semitone += 1;
       if (semitone == 12)
       {
           octave += 1;
           semitone = 0;
       }
    }
    

    The line where pitch is reset should be changed as:

    pitches = 8192;
    

    Because, you know, neutral pitch value is 8192. 0 (zero) is minimum pitch value and 16384 is maximum pitch value. At first, I thought the same way as the developer: "After 16384, it should be 0. That is okay. No problem here.". Then I said "What if I change pitch-reset value from 0 to 8192?". It worked. This was a beautiful perception error we both had. :D I'm really having a lot of laugh now.

    This fixes the microtone issue. I can hear perfect intervals now! I feel happy and satisfied.

    Edit2

    I just wanted to share my further changes which results in better microtonal tunning:

    if (pitches >= 12288)
    {
            int diff = 16384-pitches;
            int applieddiff = 8192-diff;
            pitches = applieddiff;
            semitone += 1;
            if (semitone == 12)
            {
                octave += 1;
                semitone = 0;
            }
    }
    

    This also helps use semitone (black) keys on a midi-board instead of only tone (white) keys with high pitch values. So, if you send the data to another software, it will detect all keys seperately. For example, there were no G and G# before, only G and G-with-high-pitch. These caused keys to stop eachother when note-on messages are sent simultaneously.