The play routine never returns, I think that it is the only way of having RAW PCM (MuseTracker do the same). And some emus do not work because they does not emulate the APU frame timer which is used for timing reference. Getting accurate timing over variable sample rates without an external timing reference might be possible but it will require some horribly complex code.
This is why MuseTracker uses ONLY 7.8K samples. The player is fully timed in software, and uses no external reference.
But DefleMask supports variable sampling rate. However, PowerPak has the APU frame timer so the files should play fine there.
Yeah, I can see now that you read $4015 to try and poll the frame counter interrupt flag to know when to play the next frame. This won't work with NSF; you can't use interrupts because the player needs them. (Works fine in NES though. Some players seem tolerant of it, but it really is forbidden by the NSF spec.)
The NSF 2 spec has a specification for interrupts for just this reason. It's why there was never a proper NSF rip for Battletoads or Ultimate Stuntman. Unfortunately there are no NSF 2 players yet.
Have you actually tried this on the powerpak? I would expect it to crash given what I'm seeing. Edit: tried it, actually seems to play. Interesting.
Seems to work in foo_gep, FCEUX and Nestopia, and on a PowerPak. Doesn't in VirtuaNES, VirtuaNSF, NEZPlug++ or NSFPlay.
NSFPlay I know it's because the $4017 interrupt capability was never implemented (since its use is forbidden by the NSF spec).
I'm kinda suprised it works on PowerPak, since it runs its own timed interrupt and not the APU frame counter. Its mapper isn't open source though, so I can't really speculate too much.
Ah, I found the powerpak NSF player source (but not source for its mapper), and this is interesting:
The PowerPak player does not rely on interrupts or NMI. Its hardware timer is polled in a busy-wait loop. So, I guess because you SEI before writing $4017, no IRQ ever gets through, and everything is okay.
One note: you may occasionally get a double frame, as polling $4015 on the same cycle the APU frame flag goes on fails to clear the flag.
I would love hardware envelopes in Deflemask! It would be cool to have them in Famitracker too but because there's already software envelopes, that won't be supported.
Anyhow, I've been thinking about it, and I think it's a pretty neat hack. It's still not recommended, since as we've seen a lot of players don't really take $4107 interrupts into account, and a hardware player that actually uses IRQ for playback would fail to start (though, PowerPak at least doesn't rely on IRQ).
You should get rid of that write to $2000, though. That's probably harmless in most cases, but there's no reason to do it, and it's the job of the player to use the PPU how it wants.
As for NSFPlay, its APU frame counter is overdue for a rewrite anyway. Your technique will probably be supported when I do that.
Due to the reports I fixed the remaning bugs in DefleMask 8:
The write to $2000 in the NSF exporter was removed.
The waveform viewer now has correct size and it will not make the checkboxes disappear anymore.
The manual was corrected.
Well I've got a Mac, if you're willing to pass me the source. I'll be honest, though, I've never used DefleMask myself. (I want to, but I haven't gotten around to it.)
Btw, due to awesome feedback, this minor details has been added to DefleMask to improve the workflow:
Now you can use step 0 if you want, this will prevent the cursor to move, but you can enter notes in the selected row.
Now if you set a new instrument in the patterns, that instrument will be automatically selected from the instrument list.
Fixed a bug with the EF effect.
I just thought of something. If you have a busy loop polling $4015, occasionally you will poll $4015 on the cycle that the flag gets set. When that happens it will fail to clear the flag and you'll end up doing 2 frames in a row. Maybe make sure to read it one extra time after you get a hit:
pcm_loop:
' ... ; play sample and delay
' LDA $4015
' AND #$40
' BNE pcm_loop
' LDA $4015 ; makes sure bit 4 is cleared
' ... ; playback loop
' JMP pcm_loop
Possibly this isn't a big deal, since your PCM sample is probably a few hundred cycles, so a double frame only happens every few seconds maybe.
Out of curiosity, do you find you get a 60hz buzzing in your PCM playback, since the player code is interrupting it all the time?
On another note, I have thought about writing a simple player that just plays back a simple register log stream. Maybe the stream would look something like this:
$XX, $YY (where $XX < $15)
' write to one register
' LDX $XX
' LDA $YY
' STA $4000, XX
$FE, $YY
' switch to sample $YY
$FF, $YY
' do nothing for $YY samples
$FD
' jump to loop point
That way you only have 4 code paths to carefully time, and you can ensure that PCM playback is extremely regular. The only problem is the data is not very compact at all, since all macros/instruments will be expanded in the stream, but with bankswitchingg it's probably pretty viable for songs up to a certain length, at least.