Bob the Hamster | 2 Sep 2005 16:09
Favicon

reverse-engineering the .NOT format

> I should've asked this question a long time ago, however...
> 
> What's the format of the .NOT files that NOTATE produces. My idea is:
> Notate is the second most useless utility on the planet (roughly),
> because it can't edit pre-existing BAMs (i.e anything that MIDI2BAM
> produces). So, there's two ways to fix it:
> 
> 1. Modify NOTATE to read BAM files. This would essentially take a
> re-write of NOTATE.
> 2. Write a converter to convert between BAM and NOT formats. Much 
> easier, AFAIK
>
> However, I'm too lazy... er, that is, pressed for time to go through
> the code for NOTATE and figure it out. So, I want someone else to do
> it for me :)
> 
> Or, if there's any existing docs on it, that'd be nice. The BAM SDK
> has nothing...

The only thing I remember about the NOT format is that it is very 
simple, lazy, and wasteful.

Here is the code from notate.bas that does the evil deed.

---------
OPEN file$ FOR BINARY AS #1
FOR i = 0 TO 8
 a$ = CHR$(cinst(i))
 PUT #1, 32770 + i, a$
NEXT i
PUT #1, 32770 + 9, last
FOR i = 1 TO 15
 PUT #1, 32770 + 11 + i * 2, tag(i)
NEXT i
FOR i = 0 TO 99
 PUT #1, 32770 + 45 + (i * 6 + 0), jump(i)
 PUT #1, 32770 + 45 + (i * 6 + 2), jid(i)
 PUT #1, 32770 + 45 + (i * 6 + 4), j2tag(i)
NEXT i
CLOSE #1
copyfile file$ + CHR$(0), f$ + ".not" + CHR$(0), buffer()
---------

Lets go through it a bit at a time.

> FOR i = 0 TO 8
>  a$ = CHR$(cinst(i))
>  PUT #1, 32770 + i, a$
> NEXT i

starting at offset 32770 are nine chars which I believe are the 
instrument ID numbers. Unlike a .BAM file, a .NOT file does not embed 
instrument data in the file. Instead it just keeps track of which 
instrument in IBANK.IBK each voice uses. Also, NOT files do not support 
mid-song instrument changes.

> PUT #1, 32770 + 9, last

at offset 32779 is the size of the song. This, I think, is the total 
number of 1/32 notes.. (or is it 1/64 notes? I forget) ..*checks the 
BAM docs* Yeah, 1/32 notes.

> FOR i = 1 TO 15
>  PUT #1, 32770 + 11 + i * 2, tag(i)
> NEXT i

at offset 32781 there are 15 16-bit integers, each containing tag 
locations (offset in 1/32nd notes from the beginning of the song)

> FOR i = 0 TO 99
>  PUT #1, 32770 + 45 + (i * 6 + 0), jump(i)
>  PUT #1, 32770 + 45 + (i * 6 + 2), jid(i)
>  PUT #1, 32770 + 45 + (i * 6 + 4), j2tag(i)
> NEXT i

So staring at offset 32815 we have 100 records for jump information. 
Each record is 6 bytes long. A 16-bit int for jump position "jump", a 
16-bit int for jump type "jid" and a 16-bit int for jump destination tag 
"j2tag"
jid = 0 is unuised
jid = 1 thru 253 is a finite loop
jid = 254 is an infitie loop
jid = 255 is a gosub (chorus)
jid = -1 is a return (end-of-chorus)

Now, what that save routine /doesn't/ tell us anything about is the rest 
of the song data, in offsets 0 to 32769 ... (actually, I would guess 
that 0 - 32767 is what we care about, and that 32768-32769 are two 
bytes that I just wasted for no obvious reason)

Lets look at the rather ugly "setnote" block of code...

> setnote:
> fmkeyon ins, vptr + sharp: noff(ins) = nlen(nl)

This just makes the note play when you place it...

> OPEN file$ FOR BINARY AS #1
> a$ = " "

Notate writes directly to a temporary working .NOT file  to avoid having 
to keep the song in memory.

> GET #1, 1 + (ptr * 9) + ins, a$

ptr is the current position time-wize in the song. So if ptr was 0 we 
would be at the beginning. If ptr was 32 we would be one whole note into 
the song. if ptr was 111 we would be 3 whole notes + 15 1/32 notes into 
the song.

The data appears to be in blocks of nine bytes per each 1/32 note. One 
byte for each instrument. So if you song is silent for 500 1/32 notes, 
you have 500 * 9 bytes of empty space to show for it.

> b = ASC(a$)
> n(ptr - left, ins) = vptr + sharp
> l(ptr - left, ins) = nlen(nl) * dot!
> IF (b AND 128) = 128 THEN n(ptr - left, ins) = (n(ptr - left, ins) OR 128)
> a$ = CHR$(n(ptr - left, ins))
> PUT #1, 1 + (ptr * 9) + ins, a$
> FOR i = ptr + 1 TO (ptr + (nlen(nl) * dot!)) - 1
>  a$ = CHR$(0)
>  PUT #1, 1 + (i * 9) + ins, a$
> NEXT i
> a$ = " "
> GET #1, 1 + ((ptr + (nlen(nl) * dot!)) * 9) + ins, a$
> b = (ASC(a$) OR 128)
> a$ = CHR$(b)
> PUT #1, 1 + ((ptr + (nlen(nl) * dot!)) * 9) + ins, a$

ouch. this makes my head hurt. Okay, here is what I think is happening. 
For each 1/32 note in the song we have nine bytes of data, right? one 
for each of the 9 voices. The low 7 bits contains a frequency value 
0-127. This is the same number to use in the "Start-Note" command in a 
BAM stream. This marks the beginning of a note.

The eight bit of each byte is set to mark a stop-note. (that's why 
this ugly code has stuff like "x AND 128" and "x OR 128")

> CLOSE #1
> IF ptr + (nlen(nl) * dot!) > last THEN last = ptr + (nlen(nl) * dot!) + 2
> GOSUB visnotes
> RETURN

I think that is enough information for somebody to write a BAM2NOT 
converter. Building BAM import capabilities into notate would naturally 
be better... except then you would have to touch that awful ugly code :P

---
Bob the Hamster


Gmane