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]