cr1901 wrote:
The goal is to render sprites/fg objects in 3d, while having the VIP render the background using precalculated tables, and cycling tiles/offsets to give the impression of a moving background.
To save processing time in my F-Zero project, I pre-calculated all the affine parameters and stored them in ROM. This might sound like a lot, but only the first 90 degrees’ worth needs to be stored, since the other three quadrants just require simple negation or reordering of the first quadrant’s values.
Regarding the parallax setting, don’t go for anything fancy: just use linear interpolation between the foremost and backmost row of pixels. It may not be perspective-correct that way, but since you’ve only got 30 pixels or so before you start to see double images, it’s not like you can make it look realistic anyway.
Perspective is a particular brand of non-linear transformation. Consequently, it can’t be done with matrix multiplication alone, so the process will never be only one step.
The process in general is known as projection, and it’s the task of taking a 3D point and “projecting” it onto a 2D plane. In this case, you take world coordinates and map them to screen coordinates.
Graphics cards in modern computers use very low-level projections that can do perspective effects, orthographic/isometric mappings or whatever else the programmer defines. What I did for F-Zero was a one-hit wonder: it literally only does perspective from a plane in 3D to the VB’s 384×224-pixel screen.
I’ll let you research the mathematics involved, but the basic algorithm I used was this:
* Set up a background map with the course graphics. I only used one 64×64-character map, and it updates whatever is farthest from the current position as you move around.
* Treat the BG map as a 3D plane. Since BG coordinates are in X,Y, I opted to imagine the BG as being on the “wall” rather than the “floor”, such that X and Y remained BG coordinates and Z became the “distance” coordinate.
* Create a 3D point somewhat away from the BG to represent the camera’s position.
* Create a rectangle between the camera position and the BG, angled somewhat, to represent the window’s viewport.
* Cast a line from the camera point through the left and right edges of the rectangle (starting and ending at the corners) to determine the X,Y coordinates on the BG map where the reverse projection will fall. You’ll need to do this at various spots along the left and right edges according to how many pixels tall the output will be.
* Use those BG XY coordinates to configure the affine parameters.
I’ll be watching this thread, so if you need more assistance, I’m here to help. (-:
cr1901 wrote:
Additionally, what does the repeat value actually do when drawing an image? Does it make sure the light for a given LED stays on the same amount of time as the next “repeat number” columns are drawn.
The LEDs turn off after the duration specified by their brightness registers. The system has a fourth brightness register ([font=Courier]0x0005F82A[/font]) that is used as the duration of the “rest” period between transmissions. That is to say, after an LED shuts off, it won’t turn back on until the idle period elapses. The repeat value in the column table causes the whole LED cycle to happen again for the specified number of times.
Due to the way the eye processes photons, the effective result of repeating an emission is that it gets brighter and brighter each time. Over the course of a single column’s emission, the greater the proportion of time spent on to time spent off directly correlates to the perceived brightness. Therefore, repeating a column’s emission once will double the brightness, repeating it twice will triple it, etc.
The intro cinematic to Wario Land kind of fades out on the edges. This was done by manipulating the repeat values in the column table.
cr1901 wrote:
I remember reading that the VB will emit 4 or so LEDs out of the 224 at a time. Since the mirrors are always in motion, doesn’t this mean that even the angle of incidence for a single column of a frame will vary as each consecutive pack of 4 LEDs is lit?
All 224 LEDs will emit light provided the video memory specifies non-black color values for each of them. The 4-column figure comes into play by the fact that each entry in the column table specifies the total time to draw 4 columns, rather than one entry for each horizontal pixel. That is to say, even if you configure the repeat value for each sequential table entry, the brightness will only apply to every group of 4 pixels horizontally.
I’ve yet to see an emulator that properly emulates the column table’s repeat values, and even that Wario Land screenshot came from a fudged hack in Mednafen, so I can’t point at it as an example of exactly how the repeat value affects the fade-out effect.
Attachments:
I haven’t done any bench testing on it, but I can say that the CPU usage isn’t a big impact. I made use of integer operations exclusively, and function pointers where appropriate, to simplify the execution path the CPU needs to take.
For example, one of the event types is a simple operation, such as a = b + c. The tracker itself has a few bytes of memory allocated to it for the purpose of giving the music some variables to play with, and you can literally write a program inside the music file by coupling operations with the “goto” event type (which itself will not activate if the most recent operation result was zero). This kind of thing can be useful if, for instance, you want the music to sound slightly different while under water…
But I digress. Accounting for each of the operation types (both signed and unsigned), we’re looking at 27 different possible operations in the current spec (add, less than, right shift, etc.). Each operation has a numeric code, and that code is used directly as the index into an array of function pointers. So instead of saying “if code is this do this, or if code is that do that”, it just says “perform function
on this data". The event list processor works the same way. Each event type has a numeric code, and each is indexed in an array of function pointers. When it's all said and done, there isn't much at all that needs to be done even when there [i]is[/i] something to do. In practice, most audio frames will not have any new tracker actions and will take very little CPU in total.
I can’t think of any reason importing from MIDI can’t be done, at least on a “this track plays these notes” level, but the structure of the Utopia Music file isn’t really well-suited for copying MIDI content wholesale because it’s designed to work a bit more efficiently…
Take a drum loop for example. In MIDI, you have drum notes, then the same drum notes again, and again and again and again and so-forth, throughout the entire file. Utopia lets you define a drum loop once, then play it just once, but with a duration so the engine automatically repeats it again and again and again for as long as you desire.
I guess what I’m trying to say is that composing in MIDI and then converting to Utopia poorly utilizes Utopia’s features, in a way that yields a terrible return on data size. A typical Utopia music note is 12 bytes, and an event list call is 16 bytes. The drum notes themselves notwithstanding, this means you can do your percussion in 28 bytes for the entire soundtrack. But if every single instance of every percussion note needs to be encoded as a 12-byte data structure, that’s going to add up quick.
__________
EDIT
Attached to this post is a VB ROM that tests Utopia’s millisecond timing, looped event list calls and event list envelopes. It basically just uses a single channel to play C major while fading out and then back in. Press the A button to hear it.
The entire UM file is 152 bytes, but that includes header information like an identifier and version ID, the 32 samples for wave memory… The actual tracker data is only 76 bytes (including the note definitions). How big is that, really? Well, the following quoted sentence is 76 characters: “Hello, my name is Ultra Dude and I like donuts with colorful sprinkles atop.”
C major is being played on a single channel thanks to a fast arpeggio, where it plays each constituent note for 25 milliseconds and just repeats it over and over for 2 full seconds. That means a total of, uh… 80 music notes are being played.
In other words, for a two-second chord, UM was able to schedule more music notes than there were bytes in the data scheduling them.
-
This reply was modified 11 years, 11 months ago by
Guy Perfect.
Attachments:
[font=Arial][size=24px]August 2014
It was on the 15th when it was brought to my attention that the Virtual Boy is turning 20 years old next year. I decided I had to do something to commemorate the event, and my thoughts turned back to my first Virtual Fall project. Surely with a whole year to get my thoughts and plans together, and a coworker with a keen eye, I’d be able to do a little better job this time around.
Well, I was right! So far. I mean, I’ve been working non-stop on this for a mere week and a half, but I’ve kinda surprised myself with the amount of work I’ve accomplished. Unfortunately, I didn’t get to the point I wanted to be at, which would have involved attaching a Virtual Boy ROM to this post showcasing what I’ve created. So that’s gonna be another week or so.
But until then, have I ever got something to tell you guys…
[size=24px]Utopia Audio
Remember how I mentioned I was working on a third Virtual Fall project after the F-Zero thing went bust, but didn’t have time to finish the design? Well, I’ve finished the design. And implemented it. It now exists, and is running on my Virtual Boy. But alas, I still can’t quite demonstrate it yet. Let me explain…
Planet Virtual Boy has seen its fair share of tools and utilities pass through the forums over the moons, but one thing that’s a bit less common is audio stuff. For Virtual Fall, I figured I could make a sound and music engine, then release the code for all to enjoy. I didn’t finish it in time. Heck, I didn’t finish designing it in time. So no audio libraries during Virtual Fall.
I’ve got it now, though. Two weeks after VB’s birthday #19, I finalized the specs and put together a couple of libraries that I’m quite proud to present to the Virtual Boy scene. There’s still that matter of them being designed for use with devkitV810, though. Really gotta find dasi. |-:
Utopia Sound
This one’s a mixer engine that exposes all of the VSU’s hardware capabilities to the application while at the same time managing state information for sound contexts.
The basic architecture of the mixer is that there are “sound” instances configured in memory that are linked with an application-defined callback function to be run each audio frame. The callback function defines the behavior of the sound, such as a sound effect or music note, and doesn’t suffer from the usual limitations of trying to pack every conceivable use into a file format.
When processing, the mixer will maintain a record of which sounds have priority on which hardware channels, and manage VSU registers accordingly. For instance, sounds with higher priority on a channel will interrupt but not stop sounds of lower priority on the same channel. Individual sounds can configure their own local registers all willy-nilly without worrying about stepping on the toes of the actual hardware and the sounds allotted to it.
Utopia Music
My little pride and joy, this is a music engine that is modeled as a sort of lightweight version of a non-VB project I’ve been planning for years and years. While I won’t say that it provides comprehensive support for Virtual Boy musical needs, I will say that it goes above and beyond, and I’m really itching to get to play with it.
Music data is structured into event lists, which are maintained by a tracker. Music notes are one of an assortment of event types, with others being things like sound changes, tempo modifications or calling other event lists. All active event lists are processed concurrently, providing dynamic and versatile scheduling of music information.
Envelopes can be defined to gradually modify pitch, panning, volume and tempo over time, or individual literal values can be used directly. An instance of an event list has its own panning and volume features, which recursively apply to all notes that the list generates. Identical envelopes across events can be used as presets, such as piano-like intensity graphs, reducing the size of the data.
Taking a step out of the ordinary, the engine provides two time types for all of its time-based features–beats or realtime–and they can be used independently and simultaneously by different events. When something is scheduled with beats, the time is relative to the master musical position and, ultimately, the tempo. When something is scheduled in realtime, on the other hand, it will process for some fixed number of milliseconds, at the artist’s discretion. Why is that useful? Well, you ever hear those super-fast arpeggios used by demoscene chiptune artists? Oh yes, that’s precisely what I have in mind.
Music notes are scheduled directly into the Utopia Sound mixer. In fact, there is a Utopia Music library function designated as a sound callback for use with mixer processing. It’s really quite a lovely development of one library leveraging the capabilities of another. (-:
The whole kit and kaboodle is designed to reduce data size and minimize memory usage. Decoding the file format to the point where the entire thing can be represented only requires 28 bytes in RAM–everything else can subsequently be directly accessed from read-only data. The read-only data itself makes use of redundancy elimination techniques, bit-packed data and in some cases variable-size data fields all in the interest of slimming down the file size without adding any processing overhead.
[size=24px]The Composer
This is why I don’t have a presentation. In order to actually make music to go with this awesome engine I put together, I’ll need some software designed for that purpose. And most of my week and a half went towards engine development itself, with only the past three days being dedicated to composer development. So even though I was able to hand-craft data files in a hex editor, no composer means no music, and no music means no presentation.
Among other things, I had to convert the entire Utopia Audio suite to Java (my preferred GUI platform), emulate the VSU for preview and playback purposes, and am in the process of creating the user application to tie it all together.
Just today I started working on the GUI, and when it became apparent that there was no way I’d have it ready to go for the August presentation, I decided to put it down, stretch my back, take a break and actually do some laundry and clean up around the apartment like I haven’t done for almost two weeks now. I tells ya, Planet Virtual Boy, I’m devoted to the cause!
While the data back-end is solid, the composer’s GUI isn’t far enough along to make for any good screenshots. So instead, here’s some concept art I drew of it!
-
This reply was modified 11 years, 11 months ago by
Guy Perfect.
Attachments:
I don’t use GCCVB for my Virtual Boy development, so I’m not readily able to just type in some commands to see what works, but I can tell you that ordinary multi-file compiles in gcc look something like this:
gcc main.c another.c third_file.c -o binary.exe
Pretending for a moment you didn’t already know that, you might give it a try and see if it helps you do what you’re trying to do.
I didn’t encounter this when doing PCM of my own last year:

I have a hypothesis as to why, and I think it has to do with your source data. It’s a characteristic phenomenon of PCM processing on any architecture that if you don’t start and stop a sound on silence, you’ll hear a pop when it abruptly switches between states. The same is true when pausing and unpausing an audio stream mid-playback, but it’s less noticeable the higher the sampling rate and/or bit depth are (plus, either the media player or the hardware tends to apply a super-fast fade when the playback state changes).
Try it yourself: load up some sound in your editor and find a spot with a sample with a value far from zero:

Silence the track at that point, but don’t delete the remainder outright (to prevent any hardware-based fade). You might have to listen closely, but there’s definitely an itty-bitty high-pitched pop if you do that.
So that’s my advice. Check your source data, and if it doesn’t begin and end on silence, use a short fade yourself that’s too fast to hear. That should hopefully solve your issue.
HorvatM wrote:
The note looks like “本体上部のVBカセットスロットの止め具です。” to me, but I’m no expert.
According to my contact:
The guy that posted the transcript of that text was exactly right. 😛
“This is a top-part VB cartridge slot fastener.”
Anyone feel like getting psyched up? Try this one on for size:
A coworker of mine is an accomplished artist. He’s agreed to do the artwork for that game I wanted to make for the Virtual Fall event. I’m expanding the scale of the project to commemorate the Virtual Boy’s 20th birthday.
In that case, I think the most specific localization would be “Setting up the VUE Debugger”
VUEデバガーを使うにあたり。
My contact wasn’t entirely sure what to make of the word あたり since he’s still learning Japanese, but the best we can figure is something to the effect of “How to use the VUE-Debugger”. Sorry, it’s the best we could do. (-:
I’d say thunderstruck has the right idea. This shouldn’t be a competition: it’s a celebration. It should be a party!
Heck, I plan to put something together even if no one else does, competition or not. (-:
I figured out the operation of MPYHW.
* reg1 is treated as a 17-bit integer, sign-extended to 32 bits in size.
* reg2 is treated as a 32-bit, signed integer.
* Multiplication happens normally, storing the result in reg2.
* r30 is not affected as it is in MUL and MULU.
Algorithm:
// On an unsigned variable reg2 *= (reg1 & 0x0001FFFF) | ((reg1 & 0x00010000) ? 0xFFFE0000 : 0); // On a signed variable reg2 *= reg1 << 15 >> 15;
I put together a program to answer this once and for all. It tests all register-based instructions with a simple assembly loop:
# s32 CycleTest(s32 arg1, s32 arg2, s32 num);
vueFunction(_CycleTest)
# r2 = 0x02000000, base address for hardware control ports
# r6 = arg1
# r7 = arg2
# r8 = num, also used as the loop iterator
# Configure the hardware timer
MOVHI 0x0200, r0, r2
MOV -1, r1
ST.B r1, 0x0018[r2] # Count/reload low = 0xFF
ST.B r1, 0x001C[r2] # Count/reload high = 0xFF
# Enable and clear the instruction cache
MOVEA 0x0803, r0, r1
LDSR r1, CHCW
# Enable the timer with 20-microsecond ticks
MOVEA 0x0011, r0, r1
ST.B r1, 0x0020[r2]
# Execute the instruction 10 times for the given number of iterations
.Lcycle_loop:
MOV r6, r9
MOV r7, r10
# This comment is located 32 bytes into the function.
# When the function is not modified, nothing happens in this loop
# The following bytes are meant to be overwritten in RAM
BR .Lcycle_end; NOP; NOP; NOP; # Written by 16- and 32-bit instructions
BR .Lcycle_end; NOP; NOP; NOP; # Written by 32-bit instructions
BR .Lcycle_end # Always present for consistency
# End-of-loop code for 32-bit instructions (3 16-bit instructions)
.Lcycle_end:
ADD -1, r8
BNZ .Lcycle_loop
# End-of-loop label
# Disable the timer and instruction cache
ST.B r0, 0x0020[r2]
LDSR r0, CHCW
# Retrieve and return the number of timer ticks taken
IN.B 0x0018[r2], r6 # Timer count low
IN.B 0x001C[r2], r7 # Timer count high
SHL 8, r7 # r7 = r7 << 8 | r6;
OR r6, r7
MOV -1, r10 # r10 = -1 - r7 & 0xFFFF;
SUB r7, r10
ANDI 0xFFFF, r10, r10
JMP [r31]
vueEnd(_CycleTest)
This function gets copied into RAM at run-time. Those NOPs are dummy bytes that are replaced with meaningful instructions by the program. The reason there are two sets of NOPs is to accommodate both 16- and 32-bit instructions. The following BR instruction is always present to ensure that the loop takes the same number of cycles always except for the desired instructions.
The C code that drives this looks like this:
// Gets the number of timer ticks for a loop of 4 instances of an instruction
s32 GetCount(const INST *inst, s32 num) {
s32 len = (SIZE_CYCLETEST + 3) / 4;
u32 arg1 = 0, arg2 = 0;
s32 x, y, offset = 32;
u8 func[len];
u16 bits[2];
// Copy the function into memory
memcpy32(func, &CycleTest, len);
// If we're not overwriting with an instruction, ignore this all
if (inst != NULL) {
// Encode the instruction into data bits and get its size
len = FORMATS[inst->format](inst, bits);
// Copy the instruction into the function buffer 4 times
for (x = 0; x < 4; x++) for (y = 0; y < len; y++) {
*(u16 *)(&func[offset]) = bits[y];
offset += 2;
}
// Grab the instruction's pre-defined operands
arg1 = inst->val1;
arg2 = inst->val2;
}
// Call the function from the byte buffer
return ((s32 (*)(u32, u32, s32)) func)(arg1, arg2, num);
}
My main function calls this function 5 times for each instruction (predefined in a const table at the top of the program), and averages the counts. It then subtracts the count from a null call (no instruction overwritten), then divides by the count for ADD, which is known to be 1 cycle.
The output on the hardware looks like this:
ADD (Immediate) 051E = 1 cycle ADD (Register) 051E = 1 cycle ADDF.S 6F5C = 22 cycles ADDI 051F = 1 cycle AND 051E = 1 cycle ANDI 051E = 1 cycle CLI 3D71 = 12 cycles CMP (Immediate) 06ED = 1 cycle CMP (Register) 051E = 1 cycle CMPF.S 228F = 7 cycles CVT.SW 4666 = 14 cycles CVT.WS 27AE = 8 cycles DIV C148 = 38 cycles DIVF.S DFFF = 44 cycles DIVU B70A = 36 cycles LDSR 28F6 = 8 cycles MOV (Immediate) 051F = 1 cycle MOV (Register) 051E = 1 cycle MOVEA 051F = 1 cycle MOVHI 051E = 1 cycle MPYHW 2CCC = 9 cycles MUL 4148 = 13 cycles MULF.S 83D7 = 26 cycles MULU 4147 = 13 cycles NOT 051E = 1 cycle OR 051E = 1 cycle ORI 051F = 1 cycle REV 6F5C = 22 cycles SAR (Immediate) 051E = 1 cycle SAR (Register) 051E = 1 cycle SEI 3D70 = 12 cycles SETF 051E = 1 cycle SHL (Immediate) 051E = 1 cycle SHL (Register) 051E = 1 cycle SHR (Immediate) 051F = 1 cycle SHR (Register) 051E = 1 cycle STSR 28F5 = 8 cycles SUB 051E = 1 cycle SUBF.S 83D7 = 26 cycles TRNC.SW 4147 = 13 cycles XB 1D70 = 6 cycles XH 051F = 1 cycle XOR 051E = 1 cycle XORI 051E = 1 cycle
All instructions with documented cycle counts have the correct count, so that's a relief. The floating-point instructions fall within their given range. The undocumented cycle counts? Well, that's why I made this program.
LDSR and STSR are 8 cycles each. I was expecting 1 cycle. This is how we learn things, though. Suddenly the CLI and SEI instructions being 12 cycles don't sound so bad.
MPYHW is 9 cycles as seen before. Likewise for XB at 6 cycles, XH at 1 cycle and REV at 22 cycles.
A ROM of this program is attached to this post. After the test is finished, up and down on the left D-Pad scroll the list of instructions.
I wasn’t able to find anything in any documentation about the destination being 1-63 bits after the source. Alls I can say is that when I put it through a physical test, it breaks, and it doesn’t break in quite the same way twice.
MPYHW definitely performs a faithful multiplication, but exactly how it behaves when extending off the left side of the register is something I’m gonna have to investigate further in the morning.
In the mean time, have a ROM! All controls are done with the left D-Pad. Up and down change the value of the current digit, and left and right change the digit.
For the third time, the output isn’t the same between executions, let alone resets or whatever else. I was sitting there pressing the A button and watching the output be different every time.
As long as I’m at it…
The development manual indicates that using any register other than r0 for reg1 in the XB and XH instructions may cause problems, but regardless of which registers I specified or the values in those registers, the instructions performed correctly.
MPYHW, on the other hand, is giving me some mysterious results when bits 16-31 don’t sign-extend bit 15 (just like the developer’s manual says). I’m gonna have to put together a test program just for that instruction to figure out exactly what it’s doing.






