Building a loudness table in Pro Tracker 3 player

ZXNet echo conference «code.zx»

From Ivan Roshin To All 14 June 2004

Hello, All! ═══════════════════ make_vol.1 ══════════════════ (c) Ivan Roshchin, Moscow ZXNet: 500:95/462.53 E-mail: bestview@mtu-net.ru WWW: http://www.ivr.da.ru Building a loudness table in Pro Tracker 3 player ═════════════════════════ ══════════════════════════ ("Radiomir. Your computer" 4/2004) Music modules written in the Pro Tracker 3 editor (hereinafter referred to as PT3), played using a special set procedures called a player. In the player (at least for versions 3.3-3.6) there is a so-called volume table, located at offset #110 from the beginning of the player and occupying 240 (#F0) bytes. This table is 15 rows of 16 values in a line, each value occupies one byte and is number from 0 to 15. In PT3, each sample count has a volume from 0 to 15, and the sample can be played with a volume from 1 to 15 (zero volume is not used). Here's to determining the volume playing the next sample based on sample volume values and the volume of this sample in the sample is what the table is needed. At the intersection a row with a number equal to the volume of the sample, and a column with a number, equal to the volume of the sample in the sample, and the desired value is found volume. If you reset the space occupied by the volume table in the player, while adding a fragment to the program to build thistables, then when packing the program you will get a win. Indeed, the sequence of zeros in the place of the table will compress much better than the table itself, and the length of the added fragment of the program that builds the table is very small (as we we will see later), besides, it can also be compressed. Pro Tracker 3 - the most widely used currently time music editor for ZX Spectrum, written in it modules are used in a variety of programs (games, electronic newspapers and magazines, intro, demo). Accordingly, the area The use of this method of reducing program length promises be very wide. Below is a dump section of a standard Pro Tracker player 3.3-3.4, containing the loudness table. #0110: 0000 0000 0000 0000 0101 0101 0101 0101 #0120: 0000 0000 0000 0101 0101 0102 0202 0202 #0130: 0000 0000 0101 0101 0202 0202 0303 0303 #0140: 0000 0000 0101 0102 0202 0303 0304 0404 #0150: 0000 0001 0101 0202 0303 0304 0404 0505 #0160: 0000 0001 0102 0203 0303 0404 0505 0606 #0170: 0000 0101 0202 0303 0404 0505 0606 0707 #0180: 0000 0101 0202 0303 0405 0506 0607 0708 #0190: 0000 0101 0203 0304 0505 0606 0708 0809 #01A0: 0000 0102 0203 0404 0506 0607 0808 090A #01B0: 0000 0102 0303 0405 0606 0708 0909 0A0B #01C0: 0000 0102 0304 0405 0607 0808 090A 0B0C#01D0: 0000 0102 0304 0506 0707 0809 0A0B 0C0D #01E0: 0000 0102 0304 0506 0708 090A 0B0C 0D0E #01F0: 0001 0203 0405 0607 0809 0A0B 0C0D 0E0F I don't know what formula I used to calculate this table is the author of Pro Tracker, but this simple one fits exactly formula: ┌ ┐ │ (i+1)*j │ A(i,j) = │ ─────── │. │ 16 │ └ ┘ In this formula, square brackets denote the selection of an integer parts, i - table row number (numbered from 1), j - number columns (numbered from 0). Since the PT3 player always starts from address #XX00, and the table is located at offset #110 from the beginning of the player and, thus Thus, always starts from address #XX10, it turns out that the i and j coordinates of the table element can be obtained from the lowest byte of the address of this element: the value of the most significant nibble is i, and the youngest - j. Considering all of the above, I managed to write enough a short program fragment that builds a table: LD HL,VOL_TAB ;The table address is a number of the form #XX10. LD DE,#000F ;D - initial value of i. Easier to store ;and increment i separately, not ;receive from L. Now i=0, but before ;calculating the first element of the table;i will increase to 1. E is a constant #0F. ;Main loop. M1 LD A,L ;Low byte of the address of the current element. AND E ;=AND #0F - got j. LD C,A ;Remembered j. JR NZ,M2 ;Whenever j=0 (i.e. when INC D ;start processing new line), ;increase i by 1. M2 LD B,D ;Counter=i. ;Now A=j. M3 ADD A,C ;A=A+j. DJNZ M3 ;Calculated A=(i+1)*j. Now divide by 16. RRCA RRCA RRCA RRCA AND E ;=AND #0F. LD (HL),A ;Write the value of the element. INC L ;Go to the address of the next element. ;If the entire table is full, then L=0. JR NZ,M1 ;L<>0 - go to the beginning of the main ;cycle (continuation of filling the table). ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Ivan Roshin To All 14 June 2004

Hello, All! ═══════════════════ make_vol.1 ══════════════════ (c) Ivan Roshchin, Moscow ZXNet: 500:95/462.53 E-mail: bestview@mtu-net.ru WWW: http://www.ivr.da.ru Building a loudness table in Pro Tracker 3 player ═════════════════════════ ══════════════════════════ ("Radiomir. Your computer" 4/2004) Music modules written in the Pro Tracker 3 editor (hereinafter referred to as PT3), played using a special set procedures called a player. In the player (at least for versions 3.3-3.6) there is a so-called volume table, located at offset #110 from the beginning of the player and occupying 240 (#F0) bytes. This table is 15 rows of 16 values in a line, each value occupies one byte and is number from 0 to 15. In PT3, each sample count has a volume from 0 to 15, and the sample can be played with a volume from 1 to 15 (zero volume is not used). Here's to determining the volume playing the next sample based on sample volume values and the volume of this sample in the sample is what the table is needed. At the intersection a row with a number equal to the volume of the sample, and a column with a number, equal to the volume of the sample in the sample, and the desired value is found volume. If you reset the space occupied by the volume table in the player, while adding a fragment to the program to build thistables, then when packing the program you will get a win. Indeed, the sequence of zeros in the place of the table will compress much better than the table itself, and the length of the added fragment of the program that builds the table is very small (as we we will see later), besides, it can also be compressed. Pro Tracker 3 - the most widely used currently time music editor for ZX Spectrum, written in it modules are used in a variety of programs (games, electronic newspapers and magazines, intro, demo). Accordingly, the area The use of this method of reducing program length promises be very wide. Below is a dump section of a standard Pro Tracker player 3.3-3.4, containing the loudness table. #0110: 0000 0000 0000 0000 0101 0101 0101 0101 #0120: 0000 0000 0000 0101 0101 0102 0202 0202 #0130: 0000 0000 0101 0101 0202 0202 0303 0303 #0140: 0000 0000 0101 0102 0202 0303 0304 0404 #0150: 0000 0001 0101 0202 0303 0304 0404 0505 #0160: 0000 0001 0102 0203 0303 0404 0505 0606 #0170: 0000 0101 0202 0303 0404 0505 0606 0707 #0180: 0000 0101 0202 0303 0405 0506 0607 0708 #0190: 0000 0101 0203 0304 0505 0606 0708 0809 #01A0: 0000 0102 0203 0404 0506 0607 0808 090A #01B0: 0000 0102 0303 0405 0606 0708 0909 0A0B #01C0: 0000 0102 0304 0405 0607 0808 090A 0B0C#01D0: 0000 0102 0304 0506 0707 0809 0A0B 0C0D #01E0: 0000 0102 0304 0506 0708 090A 0B0C 0D0E #01F0: 0001 0203 0405 0607 0809 0A0B 0C0D 0E0F I don't know what formula I used to calculate this table is the author of Pro Tracker, but this simple one fits exactly formula: ┌ ┐ │ (i+1)*j │ A(i,j) = │ ─────── │. │ 16 │ └ ┘ In this formula, square brackets denote the selection of an integer parts, i - table row number (numbered from 1), j - number columns (numbered from 0). Since the PT3 player always starts from address #XX00, and the table is located at offset #110 from the beginning of the player and, thus Thus, always starts from address #XX10, it turns out that the i and j coordinates of the table element can be obtained from the lowest byte of the address of this element: the value of the most significant nibble is i, and the youngest - j. Considering all of the above, I managed to write enough a short program fragment that builds a table: LD HL,VOL_TAB ;The table address is a number of the form #XX10. LD DE,#000F ;D - initial value of i. Easier to store ;and increment i separately, not ;receive from L. Now i=0, but before ;calculating the first element of the table;i will increase to 1. E is a constant #0F. ;Main loop. M1 LD A,L ;Low byte of the address of the current element. AND E ;=AND #0F - got j. LD C,A ;Remembered j. JR NZ,M2 ;Whenever j=0 (i.e. when INC D ;start processing new line), ;increase i by 1. M2 LD B,D ;Counter=i. ;Now A=j. M3 ADD A,C ;A=A+j. DJNZ M3 ;Calculated A=(i+1)*j. Now divide by 16. RRCA RRCA RRCA RRCA AND E ;=AND #0F. LD (HL),A ;Write the value of the element. INC L ;Go to the address of the next element. ;If the entire table is full, then L=0. JR NZ,M1 ;L<>0 - go to the beginning of the main ;cycle (continuation of filling the table). ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Ivan Roshin To All 14 June 2004

Hello, All! ═══════════════════ make_vol.2 ══════════════════ The length of this fragment is only 25 bytes, and the length of the original table compressed using HRUST 1.3 - 94 bytes (excluding service 6 bytes of the received file, which stores identifier "HR", length of the original block and length of the packed one block). Then the gain from programmatic table construction is will be approximately 94-25=69 bytes (approximately - over several reasons: firstly, when compressing the entire program, and not just tables, the table may not be compressed to exactly 94 bytes; secondly, a compressed sequence of zeros in place of the table too will take up some space, albeit very little; thirdly, the program fragment in question can also be compressed). By the way, if the values of any registers are known to at the moment of execution of this fragment, then in some cases Additional optimization is possible. For example, if D=0, then you can replace LD DE,#000F with LD E,#0F, and if E=0, then you can rewrite the fragment so that the functions are swapped registers D and E, then instead of LD DE,#000F there will be LD DE,#0F00, and this command can already be replaced with LD D,#0F. Also when If necessary, you can swap the functions of register pairs HL and DE (for example, if by the time the fragment is executed in DE there is already contains the address VOL_TAB). The Pro Tracker 3.5-3.6 player uses a slightly different volume table:#0110: 0000 0000 0000 0000 0101 0101 0101 0101 #0120: 0000 0000 0101 0101 0101 0101 0202 0202 #0130: 0000 0001 0101 0101 0202 0202 0203 0303 #0140: 0000 0101 0101 0202 0202 0303 0303 0404 #0150: 0000 0101 0102 0202 0303 0304 0404 0505 #0160: 0000 0101 0202 0203 0304 0404 0505 0606 #0170: 0000 0101 0202 0303 0404 0505 0606 0707 #0180: 0001 0102 0203 0304 0405 0506 0607 0708 #0190: 0001 0102 0203 0404 0505 0607 0708 0809 #01A0: 0001 0102 0303 0405 0506 0707 0809 090A #01B0: 0001 0102 0304 0405 0607 0708 090A 0A0B #01C0: 0001 0202 0304 0506 0607 0809 0A0A 0B0C #01D0: 0001 0203 0304 0506 0708 090A 0A0B 0C0D #01E0: 0001 0203 0405 0607 0708 090A 0B0C 0D0E #01F0: 0001 0203 0405 0607 0809 0A0B 0C0D 0E0F In this case, it is not so easy to find a formula to calculate table elements. But, fortunately, I managed to find a subroutine construction of such a table in [1]. The original text was provided there player Pro Tracker 2.101, in which the volume table is built a special subroutine, and upon comparison it was discovered that this the table is the same as in the player Pro Tracker 3.5-3.6. By the way, strange: the source text of the player in [1] was commented as "standard player", and yet in the original playerPT2 table is contained directly, not built subroutine. I was able to achieve the 43 byte long subroutine given in [1] significantly optimized by slightly changing the logic of its operation and considering that the address of the table location is a number like #XX10. The result was the following subroutine, 32 bytes long: LD BC,VOL_TAB ;The table address is a number of the form #XX10. XOR A LD D,A INITV2 ADD A,#11 ;If as a result of this addition ;it turns out #100 (i.e. A=0 and flag C=1), SBC A,D ;then 0 will turn into #FF after SBC A,D. LD E,A LD H,D LD L,D ;HL=0 (D is always 0). INITV1 LD A,L R.L.A. LD A,H ADC A,D ;=ADC A,0. LD(BC),A ADD HL,DE INC C RET Z LD A,C AND 15 JR NZ,INITV1 LD A,E CP #77 JR NZ,INITV2 INC A JR INITV2 To use it as a directly embedded into the fragment program (so as not to waste money on the CALL command), it is enough to replace the RET Z command with JR Z,M1 (where M1 is the address following the command fragment). The result is a fragment of the program one byte longer - 33 bytes. The original table is compressed using HRUST 1.3 to 97 bytes (also excluding 6 service bytes). Thus, approximatethe gain from software construction of the table PT 3.5-3.6 will be 97-33=64 bytes. While looking at this routine I realized that it is possible write a similar subroutine to build the one already discussed previously tables PT 3.3-3.4: LD BC,VOL_TAB ;The table address is a number of the form #XX10. LD DE,#10 INITV2 LD HL,#10 ADD HL,DE ;After addition, flag C is cleared. EX DE,HL ;DE=DE+#10. SBC HL,HL ;HL=0. INITV1 LD A,H LD(BC),A ADD HL,DE INC C RET Z LD A,C AND 15 JR NZ,INITV1 JR INITV2 ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Ivan Roshin To All 14 June 2004

Hello, All! ═══════════════════ make_vol.3 ══════════════════ The length of this subroutine is 25 bytes, and when formatted in as a program fragment - 26 bytes. Compared to 25-byte fragment given at the beginning of the article, the winnings are not it worked. But still, the work was not done in vain: a the idea of combining two similar subroutines - this and the previous one - into one universal subroutine for constructing a loudness table. If you have to play modules with the same player, written in different versions of PT3, it is advisable before by playing each module, check which version of PT3 it is in written, and in accordance with this build the necessary table volume. Then each module will sound as intended its author. This is exactly what was done in my BestView program. The first bytes of the played PT3 module are the line "ProTracker 3.x", where x is the subversion number. Thus, you can by checking the byte in which the character x is stored (it is located at offset 13 from the beginning of the module), find out which of the two tables needs to be formed. Below is the text of the universal subroutine, which This is exactly what it does: it determines which of the two tables is needed build, and builds the desired table. The MODULE label is the address playable module. LD A,(MODULE+13) ;Version check. CP "5"LD HL,#11 ;Preparation of data for construction LD D,H ;tables PT 3.5-3.6. LD E,H ;DE=0. LD A,#17 ;RLA command code. JR NC,M1 ;Transition if version is 3.5 or 3.6. DEC L ;HL=#10. LD E,L ;DE=#10. XOR A ;A=0 (command code NOP). M1 LD (M2),A ;At address M2 there will be either NOP or RLA. LD BC,VOL_TAB ;The table address is a number of the form #XX10. INITV2 PUSH HL ;Value of term (#10 or #11) ;stored on the stack. ADD HL,DE ;After addition, flag C is cleared. EX DE,HL ;DE=DE+#10 or DE=DE+#11, depending on which ;we are building a table. SBC HL,HL ;HL=0. INITV1 LD A,L M2 DB #7D ;RLA or NOP will be here. LD A,H ;If the NOP command is located at address M2, then flag C is now exactly ;reset (either after the command SBC HL,HL, or after the command ;AND 15 at the end of the loop), and the next command will not change A. ADC A,0 LD(BC),A ADD HL,DE INC C LD A,C AND 15 JR NZ,INITV1 POP HL ;Restored the value of the term. LD A,E ;If we build table PT 3.3-3.4, CP #77 ;then E will never equal #77. JR NZ,M3 INC EM3 LD A,C ;If the entire table is filled, then C=0. AND A JR NZ,INITV2 RET The length of this subroutine is 53 bytes, and when formatted in as a program fragment - 52 bytes. As you can see, this is less than the total length of two separate subroutines and instructions for determining which one should be called. A small comment on this subroutine. Before construction table, the code self-modifies: it is entered at address M2 either the NOP command code or the RLA command code, depending on what kind of table should be built. As you can see, initially At address M2 there is byte #7D. Why him? Because he equal to the value of the previous byte of the fragment - the command code LD A, L. Thus, the program will have two following each other friend of identical bytes, which will have a positive effect on the degree of its compression. This optimization method is described in more detail in [2]. If the table needs to be built only once, or if the table construction routine is stored in packed form (possibly together with the player) and unpacked again before each time a new module is played, you can additionally optimize. Then you can immediately place the command at address M2 RLA, and from the beginning of the subroutine remove the command LD A, #17 and move the M1 label to the next command (LD BC,VOL_TAB). It remains to recall that the issue of reducing the size of programswhich use music modules (not necessarily written in Pro Tracker 3), I have already touched on earlier. So, in [3] improved compression of music modules due to automatic detection of those cells of the module and player, whose initial values are not used when playing, and filling these cells with the value from the previous cell being used. And in [4], among other things, it is given procedure for constructing a frequency table - thus, you can not store this table in the player and thereby improve compression programs. Sources ───────── 1. VfNG/NEW. Description of the format of Pro Tracker 2.101 modules. Electronic newspaper "Echo" #2. 2. I. Roshchin. "Improving the compression of Z80 assembly language programs." "Radiomir. Your computer" 4/2003. 3. I. Roshchin. "Increasing the compression ratio of music modules." "Radiomir. Your computer" 7/2002. 4. I. Roshchin. "Frequency table with zero error." "Radio Amateur. Your Computer" 6/2001, "Radiomir. Your computer" 7.8/2001. ════════════════════════════════ ════════════════════════════════ My other articles about AY music: 1. "Correctly change the envelope frequency in Pro Tracker 3." "Radio amateur. Your computer" 11/10/2000. 2. "Some features of the music coprocessor." "Radio amateur. Your computer" 11/2000, under a pseudonym BV_Creator. 3. "Optimization using the example of intro 'Start'". "Radiomir. Yourscomputer" 7-10/2001. (See this article for information about programming playing music.) ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Vladimir Karpenko To Ivan Roshin 13 August 2004

Hello Ivan. 14 Jun 04 17:38, you wrote to All: IR> ("Radiomir. Your computer" 4/2004) Where can I buy mono at 95? Vladimir [I.ZX] [Sprinter Developer] [MGAPI-UP-1'2003] [Sprinter2000Pro] [Daewoo CPC] [Sprinter UNofficial site: www.shaos.net/nuke/] [Liberation of Liberia]

From Eugene Palenock To Vladimir Karpenko 13 August 2004

Hello, Vladimir! 13 Aug 04 03:16, Vladimir Karpenko -> Ivan Roshin: IR>> ("Radiomir. Your computer" 4/2004) VK> Where can I buy mono at 95? Probably in Chip-Dip on Begovaya - there are many similar magazines there and for several months. Best regards, Evgeniy.

From Vladimir Karpenko To Eugene Palenock 13 August 2004

Hello Eugene. 13 Aug 04 04:39, you wrote to me: IR>>> ("Radiomir. Your computer" 4/2004) VK>> Where can I buy mono at 95? EP> Probably in Chip-Dip on Begovaya - there are a lot of similar magazines there EP> several months. Your computer is not there :( Vladimir [I.ZX] [Sprinter Developer] [MGAPI-UP-1'2003] [Sprinter2000Pro] [Daewoo CPC] [Sprinter UNofficial site: www.shaos.net/nuke/] [Liberation of Liberia]