Retro Music in BlitzMax

BlitzMax Forums/BlitzMax Programming/Retro Music in BlitzMax

taxlerendiosk(Posted 2005) [#1]
Ok, I have a small demo which should demonstrate sound from NSF (Nintendo Sound Format, sound-related NES code and data isolated and ripped from the original ROM) played in BlitzMax.

1) Choose a location for all the following files (e.g. "C:\Blitz\NSF\")
2) Download the Festalon source code from here: http://www.2a03.org/software.php and extract the "festalon" directory into the directory chosen above (not the contents of the directory, the directory itself).
3) Download the Super Mario Bros 1 NSF file from the top of here: http://www.ocremix.org/list.php?type=soundarchives&offset=143&sort=gameasc (the "NSF" link in the right column) into the directory and rename it to "smb.nsf".
4) Create festalonheader.bmx in the directory and paste this code into it:
Import "festalon\nsf.c"
Import "festalon\x6502.c"
Import "festalon\sound.c"
Import "festalon\cart.c"
Import "festalon\filter.c"
Import "festalon\ext\ay.c"
Import "festalon\ext\emu2413.c"
Import "festalon\ext\fds.c"
Import "festalon\ext\mmc5.c"
Import "festalon\ext\n106.c"
Import "festalon\ext\vrc6.c"
Import "festalon\ext\vrc7.c"

Extern "C"
	Function FESTAI_SetVolume(volume:Int)
	Function FESTAI_SetSoundQuality(q:Int)
	Function FESTAI_Sound(Rate:Int)

	Function FESTAI_Disable(t:Int)

	Function FESTAI_Load:Byte Ptr(buf:Byte Ptr, size:Int) ' Returns an NSFHeader struct
	Function FESTAI_NSFControl:Int(z:Int, o:Int)
		' z = track, o = absolute (1) or relative (0). Returns the actual
		' track, in case the chosen track is in fact not available.
		
	Function FESTAI_Emulate:Byte Ptr(Count:Int Var)
		' Returns a pointer to a chunk of sample data. Count is set to the
		' length of it.

	Function FESTAI_Close()
End Extern

OnEnd FESTAI_Close

FESTAI_SetSoundQuality(0)
FESTAI_SetVolume(100)


5) Create nsfdemo.bmx and paste this code into it:

Strict

Import "festalonheader.bmx"

Incbin "smb.nsf"

Local SMB1_Overworld:TFreeAudioSound = LoadSoundFromNSF("smb.nsf", 1, 10.0)
Local SMB1_Death:TFreeAudioSound = LoadSoundFromNSF("smb.nsf", 8, 2.7)

Local ch:TFreeAudioChannel = PlayNSFSound(SMB1_Overworld)

While ChannelPlaying(ch)
	' ...
Wend

PlayNSFSound(SMB1_Death, ch)

While ChannelPlaying(ch)
	' ...
Wend

End

Function PlayNSFSound:TFreeAudioChannel( Snd:TFreeAudioSound, alloced_channel:TChannel = Null )
	Local channel:TFreeAudioChannel,fa_channel
	If alloced_channel
		channel=TFreeAudioChannel( alloced_channel )
		If Not channel Return
		fa_channel=channel.fa_channel
	EndIf
	fa_channel=fa_PlaySound(Snd.fa_sound,False,fa_channel)
	If Not fa_channel Return
	If channel And channel.fa_channel=fa_channel Return channel

	Return TFreeAudioChannel.CreateWithChannel( fa_channel )
End Function


Function LoadSoundFromNSF:TFreeAudioSound(IncbinURL:String, Track:Short, Seconds:Float, Rate:Int=44100)
	Local SAMPLES:Int = Ceil(Rate * Seconds)
	
	FESTAI_Load(IncbinPtr(IncbinURL), IncbinLen(IncbinURL))
	FESTAI_NSFControl(Track,1)
	FESTAI_Sound(Rate)
	
	Local fa_sound:Int = fa_CreateSound( SAMPLES,16,2,Rate )

	Local buf:Byte Ptr
	Local sam:Int
	Local written:Int
	
	While (written < SAMPLES)
		buf = FESTAI_Emulate(sam)
		If (written + sam) > SAMPLES
			sam = SAMPLES - written
		End If
		fa_WriteSound( fa_sound,buf,sam )
		written :+ sam
	Wend
	
	FESTAI_Close()
	
	Return TFreeAudioSound.CreateWithSound(fa_sound)
End Function


6) Compile & execute nsfdemo.bmx

That's it! I have only tested this on Windows, I'd be interested to hear if it works on Linux/OSX.

There are some issues with this:
* It can only load fixed-length samples, not properly streaming music. I would be able to do this if I knew how to reset the WritePos of the FreeAudio sample data interface, but I don't. FMOD is a possibility but I can't seem to get that working either.
* There is a noticeable delay as the sound is emulated and stored in a FreeAudio buffer. I actually have another demo that uses threading and the sound plays immediately, but didn't include it as (a) it's Windows-only since I don't understand threads for anything else and (b) Festalon's internal state is one set of variables, which means that you cannot load in multiple sounds in parellel - not without changing a bunch of stuff in the Festalon source that I'm not smart enough to do.


taxlerendiosk(Posted 2005) [#2]
Jeez. I know this stuff has limited niche use but I did put some effort in and this isn't much encouragement to try to do more advanced stuff for the community.


LarsG(Posted 2005) [#3]
hey, chin up.. I'm sure there are someone out there who could use this,
but it's a niche market (as you said yourself), so it's not very likely that everyone is testing it yet.. hehe
personally, I currently have no use for this, at this time..
but thanks for contributing the source... :)


DannyD(Posted 2005) [#4]
denzilquixode,
Thanks alot for your hard work.I tried it on Mac Os X 10.4.3 (latest) with the latest BlitzMax.Works but only for about 10 seconds even though the tune is 1 or 2 mins long. I tried with different .nsf audio files, same result.



Btw: 5) Create nsfdemo.bb and paste this code into it: shoud read
5) Create nsfdemo.BMX and paste this code into it:


Neuro(Posted 2005) [#5]
I just saw this right now, I didnt have Blitzmax a month ago but am very interested it. I downloaded all the files but still couldnt get it to work after setting it up though. Got this error :
Build Error: failed to compile C:/BlitzMax/NSF/festalon/nsf.c



taxlerendiosk(Posted 2005) [#6]
(Whoops, sorry for that outburst earlier. I was just feeling down at the time. Should probably edit it out.)

DannyD (thanks, good to know it works on the Mac) - yeah, that's deliberate. The thing is, there is no way to tell how long an NSF track actually is. Festalon will happily generate the same looped music over and over again, or hours of silence after a short sound effect. If you look at the LoadSoundFromNSF function, the third argument is the length of time in seconds to load - you're right, it does load exactly 10.0 seconds. If you change this to 120.0 you'll get two minutes of music, but it will also cause quite a delay to generate it all (at least, it does here.) So at the moment, the system is only suitable for loading short sound effects, and you need to use trial-and-error to work out exactly how long a sound effect is to load it. To play music, I need to work out how to do streaming sound, something that TFreeAudio doesn't seem to be able to do. I did try to work out FMOD streams, but they seem to invariably get me a memory-exception error.

Thanks for the typo check, I'll edit that.

Neuro - possibly you need MinGW set up? I can't remember if that's just for modules or what.


Neuro(Posted 2005) [#7]
I have mingw installed and setup already....was I suppose to build the festalon source files first?


taxlerendiosk(Posted 2005) [#8]
No - it should just build them in, as easily as .bmx files. I don't know what's going on there, sorry. Anyone got an idea?


BlackSp1der(Posted 2005) [#9]
It don't work for me -_-' I can't hear any beautiful "beep".
--------------------------
Building nsfdemo
Compiling:nsf.c

Process complete
---------------------------
No errors, no exe compiled file. nothing. -_-'

MinGW installed. Don't know if it's working, I think it's only for compile new modules.


taxlerendiosk(Posted 2005) [#10]
BlackSp1der - I just tried removing the PATH and MINGW Environment Variables pointing to the MinGW bin directory and got the same result as you. Set those two up, restart Blitz and see if that helps.

Edit: Neuro - since it does use MinGW, maybe you are using a different version or something?


BlackSp1der(Posted 2005) [#11]
Thanks denzilquixode, it's working now.
Finally I noticed what is the mean for "PATH"


Neuro(Posted 2005) [#12]
I actually do have the latest mingw.....maybe i should try building everything again. I'll try again when I get home...


Filax(Posted 2005) [#13]
Nice !!


Red(Posted 2005) [#14]
Could you convert it into a standard mod ?


taxlerendiosk(Posted 2005) [#15]
Yeah, pretty easily, but I'd rather not do so before I'm happier with it. This is more of a proof of concept than a full system yet.


taxlerendiosk(Posted 2006) [#16]
Finally got this working with FMOD:
Strict

Import Fmod.Fmod
Import "festalonheader.bmx"

Incbin "smb.nsf"

Global FestalonBuffer:TBank = CreateBank(2000 * 4)
Global FestalonPtr:Byte Ptr = BankBuf(FestalonBuffer)
Global BytesInBuffer:Int


Graphics 300,150,0

Local Rate:Int = 22050

PlayIncbinNSF("smb.nsf",1,Rate)

FSOUND_Init(44100,32,0)

Local streamID:Int = FSOUND_Stream_Create( Byte Ptr(FestalonCallback), 1000 * 4, ..
		FSOUND_16BITS | FSOUND_STEREO, ..
		Rate, Null )

FSOUND_Stream_Play(FSOUND_FREE, streamID)

Repeat
Until KeyHit(KEY_ESCAPE)

FSOUND_Stream_Close(streamID)
FSOUND_Close()

End

Function PlayIncbinNSF(IncbinURL:String,Track:Int,Rate:Int)
	FESTAI_Load(IncbinPtr(IncbinURL), IncbinLen(IncbinURL))
	FESTAI_NSFControl(Track,1)
	FESTAI_Sound(Rate)
End Function

Function FestalonCallback(streamObject:Byte Ptr, streamDataBuffer:Byte Ptr, bufferLength:Int, ..
							userData:Byte Ptr) "Win32"
	
	Local SamBuffer:Byte Ptr
	Local AddedSamples:Int, AddedBytes:Int
	
	While BytesInBuffer < BufferLength
		SamBuffer = FESTAI_Emulate(AddedSamples)
		AddedBytes = AddedSamples*4
		
		MemCopy FestalonPtr + BytesInBuffer, SamBuffer, AddedBytes
		
		BytesInBuffer :+ AddedBytes
		
	End While
	
	MemCopy streamDataBuffer, FestalonPtr, bufferLength
		
	If BytesInBuffer > bufferLength
		MemMove FestalonPtr, FestalonPtr + bufferLength, BytesInBuffer-BufferLength
	End If
		
	BytesInBuffer :- bufferLength

	Return 1
	
End Function