It is currently Fri Jul 19, 2019 9:35 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 10 posts ] 
Author Message
PostPosted: Sat Jun 01, 2019 11:35 am 
Offline

Joined: Sat Apr 27, 2019 11:56 am
Posts: 24
This should be my last post for help before actually attempting to port a song into the Taito engine. Thus far I have a repository with song pointers, init routines, and note array data labeled for the Taito audio engine (Bubble Bobble). I wrote a small Python script to go through the game's NSF file and export notes and corresponding register values. These notes are put into a basic midi file and on my first or second go the exported midi was sounding pretty close to the real deal when guessing note durations.

Problem:
While the envelope, sweep, and frequency parts of the register values are making sense according to nesdev's APU definitions, I cannot make much of the note duration/length counter values. Specifically this table structure is not lining up with my findings. This game's basic structure of a pulse channel's note data array is as follows:

; Array of Note Structures (The most important part of structure is pointer to NoteData)
activityByte; if this is non-zero a different activity occurs. For 90% of notes this is zero and the note is initialized and played in linear order.
dutyEnvelopeByte; Directly corresponds to $4000 per this definition. The length counter halt never appears to be set.
sweepByte; This is often zero for basic pulse output but seems to be the value for $4001.
ptrToNoteData; When dereferencing this you reach two bytes of data.

;NoteDataPtr dereferences to.
4002Byte; When combining these two bytes for note data the math works out perfect for all note frequencies.
4003Byte; When taking the top 5 bits here to represent the length load counter I couldn't make sense of the values.

The length counter load values I see are 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, and 0x12. 0x06 appears the most throughout the theme song so I assumed this as a quarter note based on sheet music I found online that is pretty close. Unfortunately when I try and stay true to the length counter table on nesdev the song sounds out of whack.

Here are the first few notes in the song with their corresponding duration values:
('Bb5', '0x6')
('A5', '0x6')
('G5', '0x0')
('F5', '0x2')
('A5', '0x6')
('G5', '0x6')
('F5', '0x6')
('Eb5', '0x6')
('G5', '0x6')
('F5', '0x6')
('Eb5', '0x2')
('D5', '0x6')
('F5', '0x4')

The notes match up well to the expected when listening to the theme. These note values start at about 9 seconds after the small intro. When I reach the duration of 0x00 I am not sure what is going on. Any help is appreciated. If this post is not structured well let me know and I will re-read and try to make things shorter/clearer.


Top
 Profile  
 
PostPosted: Tue Jun 04, 2019 4:17 pm 
Offline

Joined: Sun Sep 19, 2004 11:07 pm
Posts: 172
Are you sure the length byte translates directly into a register setting?

The engines I've looked at usually bounce it through another table, as the music engine will need to get a length in frames as well to know how to move through the tune, and to handle rests.

That said, most of the ones I recall tended to leave the length hold flag set and do things entirely manually, but I haven't looked at any Taito ones off the top of my head.

If ptrToNoteData is being treated as the usual index into a frequency table that mostly aligns with MIDI, it seems odd that it would have lengths in the same table.


Top
 Profile  
 
PostPosted: Tue Jun 04, 2019 6:47 pm 
Offline

Joined: Sat Apr 27, 2019 11:56 am
Posts: 24
Thanks Reaper. That is a really good thought. I will sit down with the binary tonight and review where the top byte is getting loaded in that dereferenced note value pointer. I was pretty confident these two bytes directly correlated to the length counter load and timer values. For example the frequency equation works out with all notes when I splice off the first 11 bits and when changing the upper 5 bits I can hear the note duration differences when playing the song back.

As a test tonight I will try and make recordings of the notes at each duration and maybe a pattern will stick out.


Top
 Profile  
 
PostPosted: Tue Jun 04, 2019 10:35 pm 
Offline

Joined: Sat Apr 27, 2019 11:56 am
Posts: 24
Ok so I modified nosefart to dump the value of 0x4006 and 0x4007 each time there is a note update. It seems as though the direct correspondence is not true after all. This seems to raise a different issue though. The value of 0x08 is always written to 0x4007 despite which note the song is on. It seems like the value 0x08 is written to 0x4007 on some sort of timer which is determining the note length rather than a static value.

Thanks for the input! It looks like I have more work ahead of me.


Top
 Profile  
 
PostPosted: Wed Jun 05, 2019 11:30 am 
Offline

Joined: Sun Sep 19, 2004 11:07 pm
Posts: 172
That's fairly typical, most engines seem to handle note length, and volume enveloping manually. Several will do frequency sweeps manually as well.

Does it actually pack 4 bytes per note, every note? Almost every engine I've looked at has gone with a channel structure along the lines of:

byte >= 0x80 : command, some amount of trailing data, used for changing duty parameters, looping, adjusting volume settings, etc
byte < 0x80: note, either in {note,length} pairs, or tightly packed sequences of {note,note,note} with a fixed length set by one of the 0x80+ commands.

note has almost always been a 0..128 index into a frequency table set to match up roughly with MIDI, length has usually been encoded with 4-5 bits as an index into a length table, and a couple of other bits controlling things like envelope pattern resets, portamento slides, vibrato and whatnot, or indicating rests.

stuffing raw duty/vol and sweep byte data for each note seems a bit more mod tracker like, which is simple to deal with, but not particularly space efficient.


Top
 Profile  
 
PostPosted: Wed Jun 05, 2019 3:29 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21507
Location: NE Indiana, USA (NTSC)
Super Mario Bros., Klax, and games using Pently (Concentration Room, Thwaite, Zap Ruder, Action 53 menu, RHDE, and Nova the Squirrel) pack a 5-bit note number (index into a period table) and a 3-bit index into a duration table into one byte. Klax reserves one of the eight duration table values for special commands, while Pently reserves five note numbers for effect commands (such as changing the transpose value added to each note number). SMB just excludes all unused notes from the period table.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Wed Jun 05, 2019 8:40 pm 
Offline

Joined: Sat Apr 27, 2019 11:56 am
Posts: 24
Reaper

As far as the 5-byte note structure it seems like they are not too concerned with saving space in this audio engine. I started with the INIT routine in the NSF header. This landed me in a pointer table that lined up really well with the number of tracks in the game. The tracks point to an init routine followed by the 5-byte arrays representing the notes. The 5-byte note structure is pretty solid at this point from my findings. I modified the nsf to test if the output changed as expected and I can control each individual note's frequency, duty, and sweep characteristics. I can control the duration of each note by changing the value of the second byte in the dereferenced note pointer but it didn't match up with the APU documentation so my parsing script just makes educated guesses.

tepples

Is there any commented disassembly I can read for the games you listed or maybe an example of this note packing in action?

I am happy to share my Taito disassembly repository with labels and comments.


Top
 Profile  
 
PostPosted: Wed Jun 05, 2019 8:44 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4154
Location: A world gone mad
Pently is open-source; Tepples is its author. https://github.com/pinobatch/pently


Top
 Profile  
 
PostPosted: Wed Jun 05, 2019 9:57 pm 
Offline

Joined: Sat Apr 27, 2019 11:56 am
Posts: 24
Thanks for the pently link. The tool is really cool. The wave visualizer was impressive.


Top
 Profile  
 
PostPosted: Sat Jun 08, 2019 7:44 pm 
Offline

Joined: Sat Apr 27, 2019 11:56 am
Posts: 24
After a bit more work my resulting MIDI sounds exactly like the song in the game. I looked at the duration register being loaded and moved backwards a bit. Originally I thought the top 5 bits of the 2-byte note structure served as the duration value. On second look the top 4 bits are masked and shifted into the accumulator. These bits then serve as an index into a duration table. You guys input was invaluable and unfortunately I had labeled the duration table as related to a song speed table.

At this point I think porting over a known song should be possible. I plan to start with Mega Man 2 based on the labeled disassembly we have online.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group