Orgs in Java

Oct 13, 2010 at 11:54 PM
graters gonna grate
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jul 2, 2008
Location: &
Posts: 1886
Age: 31
So I made a thingy in case there are any Java programmers out there interested in giving their Java programs the ability to play *.org files.

Java organya player
Source code

This version is hopefully final.

Also, I figure I might as well post this here for convenience:
Notes on Organya playback details by Bavi_H



The format of the "orgsamp.dat" file:

Code:
All integer values are unsigned big-endian,
except sample frames, which are signed (2s complement).

1 byte: number of melody samples (100)
3 bytes: number of sample frames per melody sample (256)
for each melody sample:
for each sample frame:
1 byte: sample frame
1 byte: number of drum samples (28)
2 bytes: sampling rate of drum samples in Hertz at lowest non-zero frequency (2205)
for each drum sample:
3 bytes: number of sample frames
for each sample frame:
1 byte: sample frame



The *.org format specs:

Code:
All integer values are unsigned little-endian.
A "click" is the smallest unit of time in an org file.

6 bytes: ascii string "Org-02" (or "Org-03" if the file uses percussion instruments only available in orgmaker 2.05)
2 bytes: "wait" value (the length of a click in milliseconds)
1 byte: beats per measure
1 byte: clicks per beat
4 bytes: position of the loop start, in clicks (the first click being position 0)
4 bytes: position of the loop end, in clicks
for each track:
2 bytes: "freq" value*
1 byte: instrument
1 byte: 1 if "pi" checkbox is checked, 0 otherwise*
2 bytes: number of resources
for each track:
for each resource:
4 bytes: position of the resource, in clicks
for each resource:
1 byte: note (0=lowest note, 45=A440, 95=highest note, 255=no change)
for each resource:
1 byte: duration (in clicks, I believe this is ignored if note value is "no change")
for each resource:
1 byte: volume (0=silent, 200=default, 254=max, 255=no change)
for each resource:
1 byte: pan (0=full left, 6=center, 12=full right, 255=no change)

*Even though orgmaker only allows you to edit these for melody tracks, percussion tracks also have this data, 
with the default values of freq=1000, pi=0.
I haven't tested to see if modifying these has any effect on playback.
 
Oct 14, 2010 at 12:17 AM
Not anymore
"Run, rabbit run. Dig that hole, forget the sun."
Join Date: Jan 28, 2010
Location: Internet
Posts: 1369
Age: 34
Some cool stuff here.

It's nice that Pixel's music formats are more available in other mediums.
 
Nov 13, 2010 at 8:25 PM
Neophyte Member
"Fresh from the Bakery"
Join Date: Nov 13, 2010
Location:
Posts: 4
First of all thanks for the Java version :D

Unfortunately there is some kind of bug when loading Wanpak2.org aka Scorching Back: An ArrayIndexOutOfBoundsException happens in the constructor of Organya...

Code:
        this.data = new int[14][this.songLen];
        this.retrig = new boolean[14][this.songLen];

        for (int i = 0; i < 14; i++) {
            int volume = 0, hold = 0, pan = 0;

            for (int j = 0; j < this.tracksizes[i]; j++) {
                orgStream.read(stuff, 0, 4);

                final int time = unsign(stuff[0]) + 256 * stuff[1];
                try {
                    this.data[i][time] = 1;
                } catch (ArrayIndexOutOfBoundsException e) {
                    e.printStackTrace();
                }
            }

            ...
        }

time can be >= than this.data.length, why so ever...

Unfortunately I don't know a lot about the org format and neither hacking the org file to set the "correct" song length nor only allowing times which are smaller than this.data.length solved the problem...

Any hint where the problem lies?
 
Nov 13, 2010 at 10:21 PM
graters gonna grate
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jul 2, 2008
Location: &
Posts: 1886
Age: 31
Ah, good catch there.

replace this:
Code:
data[i][unsign(stuff[0])+256*stuff[1]]=1;

with this:
Code:
int time=unsign(stuff[0])+256*stuff[1];
if(time<songLen) data[i][time]=1;

and it will work.


Notice that, in Scorching Back, the percussion tracks keep going even after the end of the song, so there are notes that never actually get played, but are just superfluous. That extra if statement will make it automatically ignore such notes.


Also, I may make an update eventually which will allow the use of other percussion instruments (including 2.05 ones) and support the "freq" and "pi" track settings, and of course this new bug fix. If anyone could hook me up with the samples used in the non-cavestory percussion instruments, that would be great (If not, I could probably just use Audacity).
 
Nov 14, 2010 at 12:04 AM
Neophyte Member
"Fresh from the Bakery"
Join Date: Nov 13, 2010
Location:
Posts: 4
Ok, that's the same fix that I've applied ^^

But it causes another bug in getSample():
Just as the bug before the samp2 might try to access the melody array where it's not defined... I fixed it with another check which solved the problem.

Here's the code:
Code:
                double samp2 = 0.0;

                if (j < 8) {
                    final int pos = 256 * this.instruments[j] + (int) (256 * ((this.tpos[j] + 1) % 256));

                    if (pos < this.melody.length) {
                        samp2 = this.melody[pos];
                    }
                } else {
                    samp2 = (this.tpos[j] + 1 < this.drums[j - 8].length) ? unsign(this.drums[j - 8][(int) this.tpos[j] + 1]) - 128 : 0.0;
                }

Finally the Scorching Back playback works like a charm :)
 
Nov 14, 2010 at 1:52 AM
graters gonna grate
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jul 2, 2008
Location: &
Posts: 1886
Age: 31
Actually, looking back at that code, it looks like I messed up the parenthesis in that line, and the line after that (which does interpolation) was wrong too. I fail at life apparently...

Code:
double samp2=j<8? melody[256*instruments[j]+((int)(256*tpos[j])+1)%256]:tpos[j]+1<drums[j-8].length?(unsign(drums[j-8][(int)tpos[j]+1])-128):0.0;
double samp=samp1+(samp2-samp1)*(tpos[j]*256-(int)(tpos[j]*256));

^ that's what it should be, and it fixes the error.

Incidentally, after experimenting, I find it actually sounds closer to the original if you remove the interpolation entirely (which amounts to changing the second line to "double samp=samp1;" and makes the first line useless, since the variable samp2 is never used).
 
Nov 14, 2010 at 12:59 PM
Neophyte Member
"Fresh from the Bakery"
Join Date: Nov 13, 2010
Location:
Posts: 4
Ahh... ok

But I also think that it sounds better without the interpolation than with it :p

When I find some time I'll programm a little gui for a more convenient player ^^

Thanks alot for your quick fixes. :)
After the java xm player went offline I had no org player which runs natively under OSX. But this is even better *likes source code*
 
Nov 18, 2010 at 12:15 AM
Neophyte Member
"Fresh from the Bakery"
Join Date: Nov 13, 2010
Location:
Posts: 4
Hi again

I created a little gui using Wedge's playback-engine.
If the tune list is empty just press play and you can select any number of tracks from a single directory to the list. To empty the list later on press eject. Everything else is self-explanatory.

Currently you cannot remove single tunes or mix them from different directories or save playlists, etc...

http://www.mediafire.com/?779x6o6wx2x7l1x

The zip file contains a runnable jar as well as the "wave100" file which contains the samples for the melody and drum instruments (see first post). Both have to be in the same directory or otherwise you won't here anything.

The app ist Java 1.5 complient and don't expect miracles from the app ^^
 
Nov 18, 2010 at 1:55 AM
graters gonna grate
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jul 2, 2008
Location: &
Posts: 1886
Age: 31
Ah I think I figured out the best way to handle the interpolation. Use it for drum tracks, but not for melody tracks. So say like double samp=samp1; if(j>=8) samp+=all that other interpolation stuff; If you play a song that has low percussion, it sounds really bad without interpolation.
 
Nov 18, 2010 at 1:58 AM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
This is sorta random, but could you port this to the far lesser language of C?
Or make a dll?


Haven't looked at the code yet, but I'm sure it'll be interesting.
Good work bud.
 
Nov 18, 2010 at 2:01 AM
graters gonna grate
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jul 2, 2008
Location: &
Posts: 1886
Age: 31
Eh, syntactically C is very similar to Java, so you could probably just do it yourself. Or you could ask Noxid, because he's trying to edit his C++ pxtone player to play orgs as well based on the code I sent him (with comments). Or I could forward you said commented code, in fact I'll go do that now.
 
Nov 18, 2010 at 2:10 AM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
ja I know they're close. (Laze triumphing over Lace atm)
And tharnks.
 
Dec 17, 2011 at 6:47 PM
graters gonna grate
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jul 2, 2008
Location: &
Posts: 1886
Age: 31
Alright, I'm going to occasionally update the first post of this thread with stuff over the course of the next week or so. Starting with the .org format specs.
 
Dec 18, 2011 at 1:28 AM
Senior Member
"This is the greatest handgun ever made! You have to ask yourself, do I feel lucky?"
Join Date: Jul 11, 2009
Location: Texas, USA
Posts: 90
Wedge of Cheese said:
6 bytes: ascii string "Org-02" (this is the same regardless of which version of orgmaker you saved with)

When you compose in OrgMaker version 2:
* If you only use drums available in OrgMaker version 1 (Bass01 to Tom02), the file will begin with "Org-02".
* If you use any drums that are new in OrgMaker version 2 (Bass04 to Cat), the file will begin with "Org-03".
 
Dec 18, 2011 at 3:44 AM
graters gonna grate
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jul 2, 2008
Location: &
Posts: 1886
Age: 31
Ah, thanks. Updated first post.
 
Dec 19, 2011 at 12:46 AM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
2 bytes: position of the resource, in clicks

Are you sure this isn't 4 bytes?
I'm pretty sure this is 4 bytes.

also
1 byte: duration (in clicks, I believe this is ignored if note value is "no change")
I think the duration is used in "No Change" notes for continuing a note if its length extends beyond 0xFF ticks.
 
Dec 19, 2011 at 2:00 AM
graters gonna grate
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jul 2, 2008
Location: &
Posts: 1886
Age: 31
Ah yes, you're right. First post fixed.
 
Dec 19, 2011 at 2:40 AM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
for each resource:
1 byte: note (0=lowest note, 45=A440, 95=highest note, 255=no change)
1 byte: duration (in clicks, I believe this is ignored if note value is "no change"**)
1 byte: volume (0=silent, 200=default, 254=max, 255=no change)
1 byte: pan (0=full left, 6=center, 12=full right, 255=no change)

So I'm a bit confused; at first I thought this meant
Code:
[track1]
[track 1 event 1]
[t1 e1 note]
[t1 e1 duration]
[t1 e1 volume]
[t1 e1 pan]
[track 1 event 2]
[t1 e2 note]
[t1 e2 duration]
[t1 e2 volume]
[t1 e2 pan]
[track 1 event 3]
[t1 e3 note]
[t1 e3 duration]
[t1 e3 volume]
[t1 e3 pan]
...
[track 1 event n]
[t1 en note]
[t1 en duration]
[t1 en volume]
[t1 en pan]
[track 2]
[track 2 event 1]
...
but looking at some of the data it seems that
Code:
[track 1]
[t1 e1 note]
[t1 e2 note]
[t1 e3 note] 
...
[t1 en note]
[t1 e1 duration]
[t1 e2 duration]
[t1 e3 duration]
...
[track 2]
[t2 e1 note]
[t2 e2 note]
...
might be more likely. Clarification would be appreciated.
 
Dec 19, 2011 at 3:02 AM
graters gonna grate
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jul 2, 2008
Location: &
Posts: 1886
Age: 31
Fixed.

Also, so people who weren't on IRC don't get confused, I've experimentally verified that the org format does not allow notes longer than 255 clicks.
 
Dec 21, 2011 at 4:31 AM
Senior Member
"This is the greatest handgun ever made! You have to ask yourself, do I feel lucky?"
Join Date: Jul 11, 2009
Location: Texas, USA
Posts: 90
Pi and Freq

Wedge of Cheese said:
It now handles "pi" and "freq" values correctly (the former for both melody and drums, the latter for melody only). There are two static int constants in the Organya class to which control how these are handled. piMillis is the number of milliseconds a note in a pi track should last. It's currently 32, though this is just an approximation (I'm fairly confident, however, that the correct value is between 30 and 35). freqDivisor tells what to divide the freq value by after subtracting 1000 from it. I'm 95% sure 256 is the actual correct value for this one.

When I examined what recordings of melodic OrgMaker notes looked like in Audacity, I found that when the Pi (short for pizzicato?) checkbox is enabled, OrgMaker outputs a specific number of wave periods in each OrgMaker octave.

Code:
  OrgMaker
octave  periods
=======  =======
0      4
1      8
2     12
3     16
4     20
5     24
6     28
7     32

In other words, octave 0 starts with 4 periods, each additional octave adds 4 periods.

Terminology reminder: In a periodic wave, the period is the length before the wave repeats. For example, if you use a sine wave, one period is one cycle of the sine wave.

I mainly tested with the English version of OrgMaker v2, I think I tested with other versions, but can't remember. I think I remember noticing that clicking on the piano keys and the track buttons also used the same number of periods, so OrgMaker might use the same method for emiting those notes as it does for Pi notes. (The track buttons use OrgMaker note C3 = middle C.) Edit: Tested again now, they're not.

Freq is more complicated to explain my testing methods that prove how it works. (Update: Posted OrgMaker Notes - Pitch.) Here's what I've found: In OrgMaker octave 3 (the octave with middle C and A440) the pitch frequency (periods per second) of the OrgMaker notes are:

Code:
C    ( 33408 + (f-1000) ) / 128
C#   ( 35584 + (f-1000) ) / 128
D    ( 37632 + (f-1000) ) / 128
D#   ( 39808 + (f-1000) ) / 128
E    ( 42112 + (f-1000) ) / 128
F    ( 44672 + (f-1000) ) / 128
F#   ( 47488 + (f-1000) ) / 128
G    ( 50048 + (f-1000) ) / 128
G#   ( 52992 + (f-1000) ) / 128
A    ( 56320 + (f-1000) ) / 128
A#   ( 59648 + (f-1000) ) / 128
B    ( 63232 + (f-1000) ) / 128

f is the Freq number for the track (range 100 to 1900, default 1000).
Each time you go up an octave, the denominator halves (the resulting pitch frequencies double).
Each time you go down an octave, the denominator doubles (the resulting pitch frequencies half).
In all octaves, the numerators stay the same.

__________

When I found cavestory.org, I was immediately fascinated by OrgMaker and the ORG file formats. In my first post here, I chipped in about how MIDI files store tempo and mentioned I was making a ORG to MIDI converter of my own.

I did made decent start on a ORG to MIDI converter, but the code is very sloppy (basically one big main function), and I never added all the features I wanted to. While making the ORG to MIDI converter, and at various times since then, I've been testing OrgMaker outputs by making test recordings, and recently by using OllyDbg to poke around.

I'm not sure if I'm comfortable releasing my sloppy ORG to MIDI code, but I have been intermittently drafing up my tests and findings for a while, because I wanted to at least share what I've found so others can make various converters and things. Wedge of Cheese's recent posts have made me interested in writing up these findings again.
 
Top