An elegant way of doing this simple thing?

Blitz3D Forums/Blitz3D Beginners Area/An elegant way of doing this simple thing?

Right, I've just begun project #1 which I'm aiming to be similar Spheres of Chaos (see http://www.spheresofchaos.com/ and download the demo and then buy the full game!).

Rather than just trying to get the thing done and finished I'm using it as an excercise in re-learning good programming practice (I haven't 'done code' since the Amiga's hey-day).

Anyhoo, I'm looking for comments on the following code, of which [EDIT 3rd Oct: removed dead link, see later post in the thread] has a runnable version (i.e, has keyboard.bb and the wav files).

All it does at the moment is load in 4 wavs and plays them when you bash the numbers 1-4 on the keyboard.

Not exactly impressive, I know. What took me the time (half of the day) was figuring the best way of doing things. I'm especially interested in peoples views on how the wavs get loaded.

Main program:
; Spheres.

; Version 0.02 25th Sep 2005.

; copyright (c) 2005 Stefan Holmes a.k.a VinylPusher.

; This project started just after a new install of Blitz3D on 24th Sep 2005. This project, unlike most
; other first-projects, is intended to provide functions reusable in all future projects.

AppTitle "Spheres"

Const DEBUG=False

Const SCREEN_WIDTH	= 800 , SCREEN_HALFWIDTH	= SCREEN_WIDTH / 2	; Used when calculating sound
																	; panning.
Const SCREEN_HEIGHT	= 600 , SCREEN_HALFHEIGHT	= SCREEN_HEIGHT / 2	; Haven't decided a use yet!
Const SCREEN_BPP	= 32	; I'm not aiming for this to run on an S3 Virge.

Graphics SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,2
SetBuffer BackBuffer()

Include "include\keyboard.bb"	; This loads a bunch of Consts all starting KEY_
								; My brain cannot remember scancodes and I cannot be bothered looking
								; through the help files.

Include "include\sfx.bb"		; Do have a read of this file if you want to see how a reusable sound
								; effect loading routine works.

bootstrap()	; Let's load in all the resources we need.
			; Only a small game so everything can easily go into RAM on startup.


While Not KeyDown(KEY_ESC)
	
	If KeyHit(KEY_1) Then PlaySound sfx_array(SFX_BIGEXP)\sound
	If KeyHit(KEY_2) Then PlaySound sfx_array(SFX_LASER)\sound
	If KeyHit(KEY_3) Then PlaySound sfx_array(SFX_SHOOT)\sound
	If KeyHit(KEY_4) Then PlaySound sfx_array(SFX_ZAPPER)\sound

	WaitKey
Wend

Function bootstrap()
	returned$=""
	returned$=sfx_acquire$("sfx-44.1k-16b")	; Here I'm just selecting the folder to load samples from.
											; Perhaps future Blitz versions will support 96Khz 24bit.
	If returned$<>""
		If DEBUG
			Print "DEBUG INFO"
			Print "Error loading samples: " + Left$(returned$,Len(returned$)-1) + "."
			Print
			If Len(returned$)=1
				Print "Either the file was missing, misnamed or it got loaded out-of-order."
			Else
				Print "Either the files were missing, misnamed or were loaded out-of-order."
			End If
			Print
			Print "** Press any key to continue **"
			WaitKey
		Else
			Print "There was an error loading sound(s)."
			Print "We will continue in a moment, but things may be a little quiet..."
			For i=0 To 499 : VWait : Next 
		End If
	EndIf
	gfx_acquire()
End Function

Function gfx_acquire()
End Function


included sfx.bb
;-----------------------------------------------------------------------------------------------------
; One of the first things I didn't like about BB was that the most popular way of implementing sound
; effect code was to use a proliferation of individual Global variables. Messy.
;
; I had a think, and in the back of my mind I had the idea that the next best thing to Globals were
; Const's.
;
; Having poked around a lot I found there are no totally elegant ways to do this, but here's my
; attempt at making the whole thing as modular as possible.
;
; This implementation generates only one Global (sfx_array). The identifiers for individual sounds are
; Const's (which are, IMHO, better). These can be eliminated and you can just use the numerical index
; to identify each sound. Thing is though, this isn't DarkBasic ;-)
;
; The whole point is to make the main code as human-readable as possible.
;-----------------------------------------------------------------------------------------------------

Type	sfx
Field	sound		; This is to hold the value returned by LoadSound().
Field	volume#
Field	panning#
Field	filename$
End Type


; ****************************************************************************************************
; **                                                                                                **
; **  This section is the config area, everything else is to be left well alone.                    **
; **                                                                                                **
; ****************************************************************************************************

Const SFX_COUNT		= 4-1:Dim sfx_array.sfx(SFX_COUNT)

; Use SFX_<anything> (except SFX_COUNT, duh!) to keep things consistent.
Const SFX_BIGEXP	= 0
Const SFX_LASER		= 1
Const SFX_SHOOT		= 2
Const SFX_ZAPPER	= 3

Function sfx_acquire$(folder$)
	error$=""
	i=0
	
	; I can't think of a neater way to do this. Can't go in a loop. Suggestions welcome.
	
	sfx_array(SFX_BIGEXP)\sound	= LoadSound(folder$ + "" + sfx_array(SFX_BIGEXP)\filename$)
	error$ = error$ + sfx_errorCheck$(i) : i=i+1

	sfx_array(SFX_LASER)\sound	= LoadSound(folder$ + "" + sfx_array(SFX_LASER)\filename$)
	error$ = error$ + sfx_errorCheck$(i) : i=i+1

	sfx_array(SFX_SHOOT)\sound	= LoadSound(folder$ + "" + sfx_array(SFX_SHOOT)\filename$)
	error$ = error$ + sfx_errorCheck$(i) : i=i+1

	sfx_array(SFX_ZAPPER)\sound	= LoadSound(folder$ + "" + sfx_array(SFX_ZAPPER)\filename$)
	error$ = error$ + sfx_errorCheck$(i) : i=i+1
	
	Return error$
	

End Function

.sfx_filenames
; Data <filename$,volume#,panning#>. sfx_acquire() prepends "" to the filenames.
; These latter 2 values could have been initialised later, but it makes sense to initiliase things in
; one place and not split the code between different source files.
Data "bigexp.wav",1,0
Data "laser.wav",1,0
Data "shoot.wav",1,0
Data "zapper.wav",1,0

; ****************************************************************************************************
; **                                                                                                **
; **  End of the configuration section. Shouldn't need to change anything outside of here.          **
; **                                                                                                **
; ****************************************************************************************************


Restore sfx_filenames
For i = 0 To SFX_COUNT
	sfx_array(i) = New sfx
	Read sfx_array(i)\filename$
	Read sfx_array(i)\volume#
	Read sfx_array(i)\panning#
Next

Function sfx_errorCheck$(j)
	err$=""
	If Not sfx_array(j)\sound Then err$ = sfx_array(j)\filename$+","
	
	Return err$
End Function


To use a sound again and again, simply load it and use PlaySound(). I think you overcomplicating this in your description.

I would do something like...


Graphics 800,600
SetBuffer BackBuffer()

Const No_Sounds = 4

Dim Sounds(No_Sounds)

For loop = 1 to No_Sounds
   Sounds(loop) = LoadSound("sound"+loop+".wav")
Next

While Not KeyHit(1)
 
  Cls

  If KeyHit(2) then PlaySound(Sounds(1))
  If KeyHit(3) then PlaySound(Sounds(2))
  If KeyHit(4) then PlaySound(Sounds(3))
  If KeyHit(5) then PlaySound(Sounds(4))

  Flip
Wend
End



What you can do is do...


chn = PlaySound( sound)



And assign the playing sound to a channel. Then you can alter the playing sounds volume and panning. Your code at present is using a type collection to store these values, but there isn't much point unless you assign your playing sound to a channel. You might be better off, if this is the approach your taking, adding in a channel field, you can assign the sound whilst it's playing.

Your code whilst being propery written, isn't very simple to follow. Unless you need that kind of control over your code, you might be better off taking a simplier approach. Although, your loading method is good and will track down errors easily. Just my opinion of course :o)

Some really good comments, thanks Ross C :)

I didn't know channels existed. Something for me to investigate!

My code IS a long-winded way of acheiving what it acheives when you consider that the program as it stands is incredibly simplistic and can be acheived with a LOT less code (as you mention).

Following points to consider though: -

1) As much of the 'initialise and load stuff' code is abstracted away from the main source file as is reasonably possible.
2) If I want to load my samples in a different way (unencrypting/unpacking from a .pak file for instance) then I only change sfx.bb and every project that depends on it continues to work without alteration.
3) The sfx_acquire() function will be very easy to base future resource loader functions on (like one for graphics, which is actually the plan).
4) Having an sfx type means that all the information relating to a sound effect is stored exactly where it needs to be.
5) The code pretty much comments itself as to what it is doing (my comments just add the 'why'). This is how it should be in any language but seems hard to do in B3D (compared to C or ASM).

I did consider alternative ways of doing things (just using a load of globals, using a global typeless multidimensional array) but they had such severe drawbacks that I binned them. Using a lod of Globals is just asking for trouble (this was even before I read the thread regarding using Global types instead of bare Globals). Using a multidimensional array (to store panning, volume, 3d positional) results in poorly documented code and a slew of changes throughout the source file if you add a new data element.

If I need to add a 3D positional element to any audio sample (or channel, now I have the idea!) I only need add a new Field to the sfx type and add a little initilisation code to the Read'ing loop.

The project right now is hardly off the ground but what I'm doing is solving scalability problems before they become an issue and start to impact on development of the /proper/ code, the game loop! If everything is modular (as OOP as possible) then seemingly radical design changes later on will not cause a big drama.

One last, possibly irrelevant at this stage, benefit is that the code looks to be quite nicely portable to C++ if ever I decide to go down that road :)

** edit **

The bit where I actually load the wavs in... it looks a bit rough (and I might change it yet) but the idea was to keep the mnemonic references to the sounds (e.g. SFX_BIGEXP) throughout the project. It also enables me to do the extra little sanity check to help ensure the right sound gets allocated to the right constant name (or vice-versa :) ).

I've seen a lot of B3D code that has zero error-checking where resources are loaded. Just because the program continues doesn't mean that it's OK to ignore the error!

AFAIR there won't be a MAV when you try to play a nonexistant sound. Blitz simply ignores the call in that case.

Basicly I agree, there should be more error checking and tolerant code, so it will run even under extremly mean condition. Like a Browser that should display a HTML page that contains lots of nonos.

If you move to a different language (C++, C#) and you carry on the habit of not explicitly checking for errors every step of the way then you are going to have the hardest time adapting to the 'correct' way of doing things.

In C and ASM you have to load in or hook up to many external resources and you have to be able to exit gracefully if even one of them fails to load. Even back in the Amiga days you had to constantly 'watch your back' for things like low-memory conditions, missing libraries and OS version differences (in ROM and in software). Even different CPU's or the (non) presence of an FPU.

** EDIT 2nd Oct 2005: This project now continuing at http://www.blitzbasic.com/Community/posts.php?topic=51593 with full source released under GPL at http://www.planetpgr.com/spheres/