Space Harrier Boss BGM in BASIC

Started by satoshi_matrix, September 22, 2007, 10:51:55 pm

Previous topic - Next topic

satoshi_matrix

A very cool video of a guy programming on the Family BASIC keyboard the Space Harrier boss BGM. Very very cool.  Makes me want to try to duplicate it!

http://www.youtube.com/watch?v=5dRwXv7ZvXY&mode=related&search=

UglyJoe

Haha.  That's pretty awesome.  Makes me want to try and figure out how it works.  Looks like he wrote some kind of music player, and then typed up the song in some kind of notation (all of the DATA lines), and then fed it to the player.  That's my guess, anyway, without really analyzing it.

(PS: sorry I never wrote up that BASIC tutorial.  I've got some other projects I need to work on before I can spend time on that).

133MHz

I learned BASIC on an Atari computer with the manuals provided with it. They're very detailed, straightforward and easy to understand (geared towards people who've never used a computer before, let alone make their own programs), so if someone wants to learn BASIC, I recommend an Atari computer and Atari BASIC manual, you can get those at your local flea market for dirt cheap, or look for them online for free.

UglyJoe

He was looking for HU-BASIC.  I found a couple of Japanese guides to it, but nothing in English.  The concepts are all pretty much the same, although HU-BASIC has a bunch of commands that are used specifically for placing background tiles and placing/moving sprites.  I doubt those would be very universal.

133MHz

Yup, every flavor of BASIC has its special statements for the machine it was designed. I gave my recommendation as a starting point for learning the basics of it. Once you get the hang of it you can start looking for your specific flavor.

satoshi_matrix

UglyJoe, would you mind providing me with some links to the japesne totorials of the hu-basic?


A while back when I was really adament about learning basic, I had picked up a book on ebay:

Basic BASIC: An introduction to Computer Programming in BASIC language Second Edition by James S. Coan published in 1978

The book does do a somewhat decent job at explaining the terminology, but it quickly becomes complicated in dealing with Time Shares specifically.

For you guys who know BASIC, can you recommend a good introductionary book to check out? Again, I am specifically looking to program for the Family BASIC maybe just a simple game of  2D tic-tac-toe. I dont plan to do anything too fabulous with the process as the guy who reproducted music in BASIC.

UglyJoe

September 23, 2007, 08:28:56 pm #6 Last Edit: September 24, 2007, 06:51:25 pm by UglyJoe
This is the page I was talking about:

http://page.freett.com/familybasic/familybasic_manual.html

Check the links at the menu at the bottom (Grammar and Control Code, specifically).  It's in Japanese, of course, but it describes what all the commands do.  It's really more of a reference than a tutorial.

He's also got a few example programs on there.  It could be useful to type them up, make sure they work, and then edit them to see what changes.  I tend to learn a lot by doing this.

-----

I typed up the code from that YouTube video.  It plays, but the sound is waaaay off.  I don't know if I made a typo somewhere, or if this is just a fault in the emulator (fceu is far from perfect, after all).  Now that I'm able to look at it, I can see that my theory about how it works is right!  The code makes the audio by poking the audio registers, which is way awesome.  I never would have thought that that would work. 

There's a couple of lines that I'm not positive about, but once I get those figured out I'll add some comments to the code to explain what it's doing (if anyone is interested, anyway).

In case anyone is curious or wants to double-check my code (wink wink), here it is:


1 CLS
2 PRINT"  SPACE HARRIER BOSS1 BGM"
3 PRINT ""
4 PRINT ""
100 POKE&H4015, 15
110 POKE&H4000,  6,8
120 POKE&H4004,  6,8
130 POKE&H4008, 16
140 POKE&H400C,  0
190 '
200 FOR I=1 TO 16
201 M=M+1
202 IF M>32 THEN 999
210  ON I RESTORE 600,610,600,610,600,610,600,620,630,640,630,640,630,640,630,650
220  READ A$
230  ON I RESTORE 700,710,700,710,700,710,700,720,730,740,730,740,730,740,730,750
240  READ B$
250  ON I RESTORE 800,800,800,800,800,800,800,810,820,820,820,820,820,820,820,830
260  READ C$
270  ON I RESTORE 900,910,900,910,900,910,900,920,900,910,900,910,900,910,900,920
280  READ D$
300  FOR J=0 TO 15
310   A=(ASC(MID$(A$,J+1,1))-35)*2
320   B=(ASC(MID$(B$,J+1,1))-35)*2
330   C=(ASC(MID$(C$,J+1,1))-35)*2 -24
340   D= ASC(MID$(D$,J+1,1))-35
350   P=0 : N=A : GOSUB 500
360   P=1 : N=B : GOSUB 500
370   P=2 : N=C : GOSUB 500
375   IF D<0 THEN 390
380   POKE&H400E,D,56
390   FOR W=0 TO 141:NEXT
400  NEXT
410 NEXT
420 GOTO 200
490 '
500 IF N<0 THEN FOR W=0 TO 6  : NEXT : RETURN
510 IF N>255 THEN 530
520 POKE &H4002+P*4,PEEK(&H8000+N),PEEK(&H8001+N) : RETURN
530 POKE &H4000+P*4,((N*2) AND &HC0) OR ((N/2) AND &HF) ; RETURN
595 '
596 ' PART A
597 '    0123456789ABCDEF
600 DATA 11=11=11=11=//;@
610 DATA 11=11=11=11=//;!
620 DATA 11=22>33?44@55A!
630 DATA 66B66B66B66B44@E
640 DATA 66B66B66B66B44@!
650 DATA 66B55A44@33?22>!
695 '
696 ' PART B
697 '    0123456789ABCDEF
700 DATA **6**6**6**6((44
710 DATA **6**6**6**6((4!
720 DATA"**6++7,,8--9..:!
730 DATA //;//;//;//;--99
740 DATA //;//;//;//;--9!
750 DATA"//;..:--9,,8++7!
795 '
796 ' PART C
797 '    0123456789ABCDEF
800 DATA B!!B!!BB!BB!@!@!
810 DATA B!BC!CD!DE!EFFF!
820 DATA G!!G!!GG!GG!E!E!
830 DATA G!GF!FE!ED!DCCC!
895 '
896 ' PART D
897 '    0123456789ABCDEF
900 DATA 0%%%+%%%00%%+%00
910 DATA 0%%%+%%00%%%00+%
920 DATA"0,+0-+0.+0/+00+%
999 END

satoshi_matrix

Sweet. This is way cool. Thanks Uglyjoe.

By the way, any idea how this video was done? Nobody can type that fast even if they knew exacly what keys to press. It's almost the work of a robot or something!

UglyJoe

It could have been a bunch of different things.  My guess is that he had it all typed up and stored as a macro, and then fed the macro to the emulator he was using.  The typing "clack" sound doesn't match up, either, so I'm assuming he sped up that part of the video.

UglyJoe

Ok, explanation time.  I'll have to jump around a bit instead of going straight through, but it'll make more sense this way.  I'm going to write this up as more of a HU-BASIC tutorial rather than a simple explanation.

First, a brief and limited explanation of the Famicom's sound processor.  Basically, you've got five sound channels to play with: two square/pulse channels (A & B), a triangle channel (C), a noise channel (D), and a DMC channel.  We're only interested in the first 4 channels (the fifth is used for playing back samples, like crude voices and drum samples). 

You write data to specific memory addresses in order to interact with these sound channels.  These types of addresses are called registers.  You write data to all these registers, the channels each produce part of a sound, and then all of the channels are mixed together to get the desired output.

A relevant analogy is to see each of the sound channels as a different musician in a band (vocals, guitar, bass, drums; for example).  Writing to the registers is like writing up sheet music one note at a time.  Then, the band just plays the sheet music as you're writing it.

Okay, on to the code.  Let's start with the end.  Lines 596 - 920 are some notes that we want to play.  Each line is more like a stanza of 16 notes.  When these stanzas are arranged and repeated in a certain order, we'll get the song we want.  The lines starting with a ' are comments.  These are strictly there for the programmer's sanity and organization.  In this case, he's making a note of PART A going to channel A, PART B going to channel B, and so on.

Now let's go to the top.  Line 1 simply clears the screen of any text.  Lines 2-4 print out SPACE HARRIER BOSS1 BGM and few blank lines.  Lines 100-140 tweak the settings for the five sound channels.  If we keep with our band analogy, you can see these lines as the musicians tuning their instruments.  The POKE command is used to write a value to a memory address.  Line 100, for example, is writing the value 15 to the address H4015.  The & is used to denote a memory address.  The H is used to denote the address being a hex value (base 16).  If there's no letter next to a number (such as the 15 at the end of the line), it's assumed to be decimal (base 10).  Line 110 works similarly, except it's writing two successive values.  6 is being written to H4000 and 8 is being written to H4001.  Note that, in reality, these memory addresses are registers.

Line 200 begins the main program loop.  It's a for loop that runs to line 410.  The NEXT statement there denotes the end of a loop.  Because it's a for loop, the value of I will be incremented at the end of each loop.  Once the value of I is 17 or higher, it will no longer loop.  After that loop is complete, it hits a GOTO statement at line 420.  This forces the program to go back to line 200, which will force the loop to start over again (resetting I to 1).

So will this loop never end?  Not quite.  If you look inside the loop, the first thing it does is run line 201, which increments a variable M.  Line 202 does a check on M.  If it's greater than 32, it goes to line 999.  At the very very bottom, we have line 999 which contains just an END statement.  This will stop our program.  So, no, we're not going to loop forever.  The main loop gets run 32 x 16 times.

So what is the main loop doing?  Line 210 and 220 are used to load a stanza for the first sound channel.  Line 210 is using the ON command to simplify things.  "ON I" means the program is going to
look at the value of I (which we know is going to be from 1 to 16).  The next part of the line is RESTORE, followed by a bunch of numbers.  The end result of this line is that that RESTORE command is going to be used with one of the following numbers based on the value of I.  When I = 1, the line is "RESTORE 600".  When I = 2, the line is "RESTORE 610".  And so on.

Now, the RESTORE command itself is being used to recall data defined by the DATA commands further along down the code.  "RESTORE 600" is a reference to line 600.  So, really, when I = 1, the line is essentially just "DATA **6**6**6**6((44".  The DATA command is being used to simply define some data.  In this case, the data is one of our stanzas.  Finally, we move on to line 220.  The READ command is simply taking the data from the previous line and assigning it to the variable A.  The dollar sign following the A is used to declare it as a string of characters instead of a number.  So, in summary, lines 210 and 220 read in a stanza and then assign it to A.  The next 6 lines do the same thing for the other sound channels.

This brings us to line 300.  It's another loop!  This time it's using the variable J and it's running from 0 to 15.  Line 310 is being used to get a note out of our stanza.  It's really doing two things. 

First, it's using the MID$ function to get a single character out of the stanza.  MID$ is used to get a substring of a string.  It has three arguments.  The first is the string we're going to be working with.  In our case, it's A$, the stanza for the first channel.  The second argument says where the beginning of our substring is.  In this case, for this iteration of the loop, it's J+1 or 0+1 or 1.  This means we're starting at the beginning of the string.  The third argument is used to say how long our substring should be.  We're using 1 here because we just want a single character (or note).  So, we're just getting the first character out of our stanza string.  In each successive iteration of this loop, J will increase, so we'll be getting the next note and the next note and the next note until we get the last note of the stanza and the loop ends. 

Second, we have to get this note character (or string of length 1) into a numerical value.  We don't write letters to registers, we write numbers.  This is where the ASC function comes into play.  Once we've gotten our note character, we use ASC to get its numeric value.  After this, the code modifies the value a bit.  I can't say for sure why he does the calculations he does, but they do work.  After we do that, we assign the value to A.  Note that this A is a different variable than A$, since we're assigning it a number instead of a string.  A$ still has our whole stanza.  Lines 320-340 do the same thing for the other channels.

Onward to line 350.  This line introduces the colon.  You can use it to put more than one command on a single line.  First, we're setting P to 0.  Then, we're setting N to A. Then, we're going to go to 500.  Note that this is a GOSUB and not a GOTO.  The difference is that GOSUB leaves a sort of "bookmark" to where it's currently at.  At anytime after the jump, if you hit a RETURN statement, the program will jump back up to the command that's following the GOSUB command.

Line 500 is an IF statement and FOR loop wrapped into one.  All it's doing is checking N (our note) for a value less than 0.  If it is, then it iterates through an empty loop 6 times and then returns.  I'm assuming this is used for "rest" note, but I'm not entirely sure.  If N is greater than 0, then we move on to line 510.  This line checks if N is greater than 255.  If it is, we're jumping to line 530, essentially just skipping line 520. 

That leaves us with 520 and 530.  These are where the music happens for the first three channels.  Line 520 is using a POKE to write to a register at H4002 and H4003.  The values that its writing are taken from a PEEK command.  This is the sort of opposite to POKE.  Instead of writing a value to an address, PEEK reads the value from an address.  Now, why he's chosen to take the note values and add them to the values at the &H8000 area is still beyond me.  It *is* however the reason why the code wasn't working for me before.  I was using the v3.0 version of HU-BASIC instead of the v2.1 version.  I happen to know that the &H8000 area is where the Famicom can read the value of the PRG-ROM (or the current bank of the PRG-ROM).  Since the PRG-ROM is somewhat different between the different versions, I had been writing the wrong values to the registers.  Once I typed the code back up into 2.1, it worked fine.  Anyway, after the POKE, we hit a RETURN and jump back up to our GOSUB.

Now, assuming N was greater than 255 and we had jumped to 530, we have to analyze this line.  I don't feel like getting into bitwise logic, but that's what the AND and OR are being used for.  If you want to know what this line does, check out lines 110-130.  Notice that they're writing to the same register  ;)

The last three lines we're looking at are 375-390.  These are used to write notes to the fourth channel.  They're all using commands I've explained already, so you should be able to figure them out.  Beyond that, we just keep looping and looping and looping until M goes over 32.

Ugh.  That ended up being a lot longer than I expected.  I hope it ends up helping someone  ;D

133MHz

The video has been removed due to terms of use violation :'(

UglyJoe

April 06, 2008, 08:21:11 pm #11 Last Edit: April 09, 2008, 06:28:27 pm by UglyJoe
Quote from: 133MHz on April 06, 2008, 07:56:58 pm
The video has been removed due to terms of use violation :'(


Type it up in the Family Basic 2.1 rom and watch it  ;D

(it really doesn't take too long)

edit:

I found my old savestate!  You can download it here:

http://ximwix.net/storage/Family%20BASIC%20(V21a)%20(J).fc2

Use FCEUXD SP v1.07, drop the file into the "fcs" subdirectory, and make sure your Family Basic 2.1a rom file is called "Family BASIC (V21a) (J).nes" (the rom filename and savestate filename have to match).  Load up the rom, press 2 to select the second savestate and press F7 to load it.  Press your scroll lock key to activate the Famicom Keyboard and then type RUN and hit enter.  Hope that works for you! 

(You may have to select the Famicom Keyboard in the Config -> Input menu, under Famicom Expansion Port.  I forget if it does that automatically).

UglyJoe

Why would 2kb memory for program and data be a problem?

ramidavis

May 15, 2012, 09:58:17 pm #13 Last Edit: May 15, 2012, 10:07:46 pm by ramidavis
Quote from: UglyJoe on September 23, 2007, 08:28:56 pm
Spoiler
This is the page I was talking about:

http://page.freett.com/familybasic/familybasic_manual.html

Check the links at the menu at the bottom (Grammar and Control Code, specifically).  It's in Japanese, of course, but it describes what all the commands do.  It's really more of a reference than a tutorial.

He's also got a few example programs on there.  It could be useful to type them up, make sure they work, and then edit them to see what changes.  I tend to learn a lot by doing this.

-----

I typed up the code from that YouTube video.  It plays, but the sound is waaaay off.  I don't know if I made a typo somewhere, or if this is just a fault in the emulator (fceu is far from perfect, after all).  Now that I'm able to look at it, I can see that my theory about how it works is right!  The code makes the audio by poking the audio registers, which is way awesome.  I never would have thought that that would work.  

There's a couple of lines that I'm not positive about, but once I get those figured out I'll add some comments to the code to explain what it's doing (if anyone is interested, anyway).

In case anyone is curious or wants to double-check my code (wink wink), here it is:
[close]


1 CLS
2 PRINT"  SPACE HARRIER BOSS1 BGM"
3 PRINT ""
4 PRINT ""
100 POKE&H4015, 15
110 POKE&H4000,  6,8
120 POKE&H4004,  6,8
130 POKE&H4008, 16
140 POKE&H400C,  0
190 '
200 FOR I=1 TO 16
201 M=M+1
202 IF M>32 THEN 999
210  ON I RESTORE 600,610,600,610,600,610,600,620,630,640,630,640,630,640,630,650
220  READ A$
230  ON I RESTORE 700,710,700,710,700,710,700,720,730,740,730,740,730,740,730,750
240  READ B$
250  ON I RESTORE 800,800,800,800,800,800,800,810,820,820,820,820,820,820,820,830
260  READ C$
270  ON I RESTORE 900,910,900,910,900,910,900,920,900,910,900,910,900,910,900,920
280  READ D$
300  FOR J=0 TO 15
310   A=(ASC(MID$(A$,J+1,1))-35)*2
320   B=(ASC(MID$(B$,J+1,1))-35)*2
330   C=(ASC(MID$(C$,J+1,1))-35)*2 -24
340   D= ASC(MID$(D$,J+1,1))-35
350   P=0 : N=A : GOSUB 500
360   P=1 : N=B : GOSUB 500
370   P=2 : N=C : GOSUB 500
375   IF D<0 THEN 390
380   POKE&H400E,D,56
390   FOR W=0 TO 141:NEXT
400  NEXT
410 NEXT
420 GOTO 200
490 '
500 IF N<0 THEN FOR W=0 TO 6  : NEXT : RETURN
510 IF N>255 THEN 530
520 POKE &H4002+P*4,PEEK(&H8000+N),PEEK(&H8001+N) : RETURN
530 POKE &H4000+P*4,((N*2) AND &HC0) OR ((N/2) AND &HF) ; RETURN
595 '
596 ' PART A
597 '    0123456789ABCDEF
600 DATA 11=11=11=11=//;@
610 DATA 11=11=11=11=//;!
620 DATA 11=22>33?44@55A!
630 DATA 66B66B66B66B44@E
640 DATA 66B66B66B66B44@!
650 DATA 66B55A44@33?22>!
695 '
696 ' PART B
697 '    0123456789ABCDEF
700 DATA **6**6**6**6((44
710 DATA **6**6**6**6((4!
720 DATA"**6++7,,8--9..:!
730 DATA //;//;//;//;--99
740 DATA //;//;//;//;--9!
750 DATA"//;..:--9,,8++7!
795 '
796 ' PART C
797 '    0123456789ABCDEF
800 DATA B!!B!!BB!BB!@!@!
810 DATA B!BC!CD!DE!EFFF!
820 DATA G!!G!!GG!GG!E!E!
830 DATA G!GF!FE!ED!DCCC!
895 '
896 ' PART D
897 '    0123456789ABCDEF
900 DATA 0%%%+%%%00%%+%00
910 DATA 0%%%+%%00%%%00+%
920 DATA"0,+0-+0.+0/+00+%
999 END


Fun Note:
if you change line 202 from
202 IF M>32 THEN 999

to

202 IF M>32 THEN 210

the music keeps playing endlessly :)