Enclaimer: I'm an expert. (I'm an expert at making up words as well.)
Another thing that could be used for syncing is the DIN sync output of a drum machine or TB-303.
I was going to suggest reading the joypad more often than the regular every VBlank, but you're already doing that. Good. However, I think (from reading the source code) that your joypad check routine is buggy in how it handles multiple, For example, I think if you say, hold up permanently pressed, and then press start, that the start button press will be registered as an up press.
There's a way to solve this to create a trigger mask sing XOR then AND. Pseudo code, in a more Z80-ish syntax:
call READPAD ; Let's sy this function returns the currently pressed keys in A
ld B,A ; Save current mask for later (copy to B)
ld A,[OLDJOYPAD] ; Get previous joypad state (load to A)
xor A,B ; XOR with current state to detect any changed buttons (target: A)
and A,B ; AND with current state to only store currently pressed (ie newly pressed) buttons
ld [JOYTRIGGER],A ; Save trigger mask
ld A,B ; Load B into A
ld [OLDJOYPAD],A ; Save current state as old state for the next comparison
Now check all bits in JOYTRIGGER.
Another idea: You could make the program have a master mode as well. In this mode, the joypad is only checked once every frame (or more exactly, once every player tick, if a song should happen to use a different sync source than VSync (NMI). (The sync source must be internal in this mode.) This would read out the controller like normal, but it would also have the side effect of ending out 8 clock pulses, one per bit that is being read. If you connect the NES controller clock line to the Gameboy's clock line, (and ground-ground of course) these 8 bits will be read by the Ganeboy as an incoming byte on the serial port. In LSDj's MIDI mode, this will be enough to synch the song.