First steps with MSX-Music

Page 3/3
1 | 2 |

Par Grauw

Ascended (9824)

Portrait de Grauw

11-03-2021, 14:35

snake wrote:

anyone managed to use drums?

According to ym2413 manual, register 0E enables drums and sets the tone, and registers 36-38 set volume.
So, i attempted to write the value 00100100 on register 0E and 11111111 to registers 36-38 but nothing happens...

You need to initialise the frequencies of channels 7-9 to the values on page 16 before using drums.

(Yes you can change the frequency of the drums, with some limitations.)

Par snake

Resident (51)

Portrait de snake

13-03-2021, 16:30

Now it works. I had to enable percussions (register 14 = 32), then set the appropriate values on registers 22-24 and 38-40 (r22=32 r23=80 r24=192 r38=5 r39=5 r40=1), and set the volumes (registers 54-56).
[ See chapter III-1-7 and III-1-8; i'm using decimal values instead of hex; registers and addresses are the same thing ]

Then, everytime i have to play percussions, i write the appropriate value (32-63) on register 14. Bits 0-4 play or mute the corresponding percussion. I always have to mute a percussion just before to play it (otherwise it doesn't play, i don't know why).

Par snake

Resident (51)

Portrait de snake

08-05-2021, 01:40

This topic is very useful, i couln't find any "hello world" example for Msx Music anywhere in Internet era!

Now i'm coding a very simple player: the routine is executed every interrupt, it sends raw data to YM2413 (register-value-register-value) from a chunk which ends with '255' and it's followed by another value that tells how many interrupts the player must wait before to play the next chunk.

Problem is to implement tempo. For instance, i want 120 bpm, so on 60 hz screen a Quarter should last 30 interrupts, a Eighth 15, and a Sixteenth 7,5. How can i manage decimal values? I'm thinking to simply round tempo in order to get integer lengths only (for instance play a Quarter every 32 interrupts instead 30 in order to have Sixteenth every 8 interrupts instead of 7,5).

Well, how do you do with your players? Smile

Par Grauw

Ascended (9824)

Portrait de Grauw

08-05-2021, 03:13

If you only want to work with integer wait lengths, make your music for 150 bpm, you’ll get 24 ticks per beat at 60 Hz which you can divide by 2 and 3 a couple of times. Other precise bpms are 90 bpm (no triplets), 100 bpm (no 32ths), 112.5 bpm (no triplets) and 128.6 bpm (no 32ths, no triplets).

If you want to use other bpms, then you gotta work with fractional values for the number of ticks and quantise them, so e.g. your example with 7.5 ticks, you alternate between 7 and 8 tick waits. The time of 1/60th of a second is small enough that it’s not super audible, just a subtly different rhythm (shuffle).

Implementation wise when generating the music data from the source material, just start counting time in floating or fixed point, floor the value and subtract the previous count to determine the delta, and generate that value as the wait value in your data stream. The waits will alternate appropriately to match the timing.

You can also do that cheaply at runtime using fixed point math. Just store the wait in 1/24th beats, calculate the step value (60 * 60 * 256) / (bpm * 24) once, add that step value to a counter every frame, and for each increment of the MSB you reduce the wait value. Because the math is simple and this only happens once per frame it doesn’t really cost any performance.

The latter is what VGMPlay MSX does, essentially.

Par Metalion

Paragon (1362)

Portrait de Metalion

08-05-2021, 08:42

snake wrote:

This topic is very useful, i couln't find any "hello world" example for Msx Music anywhere in Internet era!

That's why I started it in the first place.
I'm glad to see it was able to help someone else Smile

Par Pencioner

Scribe (1400)

Portrait de Pencioner

08-05-2021, 11:00

Rounding 16th to 8 instead of 7.5 doesn't seems right because two 16th in a row will make a bigger note than 8th. If i were going to stick with that tempo, i would make flipping, so the first one 8 and second 7 etc (so added together they fit right)

Par Metalion

Paragon (1362)

Portrait de Metalion

08-05-2021, 12:11

The solution explained by Grauw is the correct one.
That's the one I have implemented in my replayer.

Par snake

Resident (51)

Portrait de snake

30-05-2021, 23:24

Thanks for help, now it works very well with the method of interrupts approximation (for instance play after 7 interrupts instead of 7.5 , then after 8 interrupts, and so on).

However, my raw register-value-register-value format is too memory hungry. One minute of classical music that requires 8 Msx Music channels is about 20 kb!

So, i used another format: every chunk is 9 bytes, each representing the midi code of the note to play in the respective Msx Music channel; there are also special values for ties, pauses and inactivity.
The playback routine decodes on the fly the 9 bytes and sends the appropriate values to Msx Music registers. Then it reads a 16 bit value at the end of the chunk: this value tells the player how many interrupts to wait before to play the next chunk.

This way memory size is reduced to less than 10 kb, Playback is fine, but the routine is more complex: for every chunk to play there is a loop with 9 iterations; inside the loop there are three "if" statements and about 15 operations (memory read and write, arithmetic operations and I/O ports output).

I wonder if there are other methods to spare further memory or cpu power.

Par snake

Resident (51)

Portrait de snake

15-06-2021, 00:54

This is my SDCC code to play my high-level pseudo format (midi codes instead of raw register values):

__sfr __at (0x7c) fmpac_register;
__sfr __at (0x7d) fmpac_data;

char fmpac_play; // 1 on, 0 off
unsigned char * fmpac_pos; // pointer to the chunk to play 
unsigned short fmpac_wait; // interrupts to wait before to play the next chunk

// run this every interrupt
if (fmpac_play!=0) {
	static unsigned char c,s; static unsigned char * v;
	if (fmpac_wait==0) {
    	      if (*fmpac_pos==255) { fmpac_play=0; }
              else {
        	   for (c=0;c<9;c++) {
            	        fmpac_register=32+c; s=*fmpac_pos;
                        if (s==0) { fmpac_pos++; continue; }
                        if (s==1) { fmpac_pos++; fmpac_data=0; continue; }
                        if (s<128) { fmpac_data=0; } else { s-=128; }
                        s=s*2;
                        v=msx_tone+s;
                        fmpac_register=16+c; fmpac_data=*v; 
                        v++; fmpac_register=32+c; fmpac_data=*v;
                        fmpac_pos++;
                  }
                  fmpac_wait=*((unsigned short *)fmpac_pos);
      	          fmpac_pos+=2;
	     }
        } else { fmpac_wait--; }
}

"msx_tone" if the vector with the values to write on registers to play the corresponding note.

I'm satisfied about compression (210 seconds = 12 kb), but i'm not sure about speed. Do you think such routine can stand critical situations, as the loop of a fast shooter? Or is it too slow?

Page 3/3
1 | 2 |