Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
What extra features does VARPTR provide?
#1
I'm in trouble again!
I'm experimenting with VARPTR$ and built the experimental prog below, to compare with a sample given in Help that uses VARPTR$.
It seems I can get the same result without VARPTR$, so I don't see the reason for using it.
What am I missing?

Code: (Select All)
Screen 2
Cls
WIND$ = "r10 d7 l10 u7 br20" '                                        wind$ is a rectangle and "blind" move to right
ROW$ = WIND$ + WIND$ + WIND$ + WIND$ + "bl80 bd11" '                  row$ is four wind$, and "blind" moves left and down
For a = 1 To 4: Draw ROW$: Next '                                     draw four rows of wind$

Sleep: Cls '                                                          and to include the TA feature...
WIND$ = "ta45 r10 d7 l10 u7 br20"
ROW$ = WIND$ + WIND$ + WIND$ + WIND$ + "bl80 bd11"
For a = 1 To 4: Draw ROW$: Next
Of all the places on Earth, and all the planets in the Universe, I'd rather live here (Perth, W.A.) Big Grin
Please visit my Website at: http://oldendayskids.blogspot.com/
Reply
#2
VARPTR (VARiable PoinTeR) is used to locate the address offset of a variable within a 16bit block of code.

VARPTR$ does the same thing but returns a string of the variable's value located at that address. Just like using STR$() on a numerical variable.

It's not useful for variables that are already strings as you have found out. Both commands are a throwback from the 16bit era of coding and are no longer useful in QB64 except to support legacy source code that uses them.

In all the years coding I don't believe I ever used VARPTR$ when dealing with DRAW. I simply converted numerical values using STR$().
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#3
VARPTR$ can come in handy when using DRAW or PLAY inside tight loops where you want to avoid the overhead and speed penalty of say string concatenation or other string ops.

Like in the example below, we avoid a string concatenation inside the FOR loop by using VARPTR$ for DRAW. This still works in QB64, thanks to the length Galleon went to emulate such things.

This is probably the only reason I'd use VARPTR$. Else, it is better to avoid such legacy commands.

Code: (Select All)

SCREEN 12

drawCmd$ = "TA=" + VARPTR$(angle) + "BU100"

FOR angle = 0 TO 360 STEP 30 ' 360/12 hour circles = 30 degrees apart
    PSET (175, 250), 6 ' stay at center point of clock
    DRAW drawCmd$ ' move invisibly to set next circle's center point
    CIRCLE STEP(0, 0), 5, 12 ' circle placed at end of blind line
    DRAW "P9, 12" ' paint inside of circle
    SLEEP 1 ' slowed for demo only
NEXT
Reply
#4
(06-22-2023, 01:44 PM)a740g Wrote: VARPTR$ can come in handy when using DRAW or PLAY inside tight loops where you want to avoid the overhead and speed penalty of say string concatenation or other string ops.

Like in the example below, we avoid a string concatenation inside the FOR loop by using VARPTR$ for DRAW. This still works in QB64, thanks to the length Galleon went to emulate such things.

This is probably the only reason I'd use VARPTR$. Else, it is better to avoid such legacy commands.
That's a valid point I should have addressed. When using STR$() more concatenation then comes into play slowing things down considerably.
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#5
(06-22-2023, 01:44 PM)a740g Wrote: VARPTR$ can come in handy when using DRAW or PLAY inside tight loops where you want to avoid the overhead and speed penalty of say string concatenation or other string ops.

Like in the example below, we avoid a string concatenation inside the FOR loop by using VARPTR$ for DRAW. This still works in QB64, thanks to the length Galleon went to emulate such things.

This is probably the only reason I'd use VARPTR$. Else, it is better to avoid such legacy commands.

Code: (Select All)

SCREEN 12

drawCmd$ = "TA=" + VARPTR$(angle) + "BU100"

FOR angle = 0 TO 360 STEP 30 ' 360/12 hour circles = 30 degrees apart
    PSET (175, 250), 6 ' stay at center point of clock
    DRAW drawCmd$ ' move invisibly to set next circle's center point
    CIRCLE STEP(0, 0), 5, 12 ' circle placed at end of blind line
    DRAW "P9, 12" ' paint inside of circle
    SLEEP 1 ' slowed for demo only
NEXT

Thanks both.
It seems the consensus is that it's not likely to be useful in most cases. 
I can see that it may shorten the coding in some cases, but this doesn't 
seem likely to be of much use for my simple programming style.
Another "pothole" negotiated!   Tongue
Of all the places on Earth, and all the planets in the Universe, I'd rather live here (Perth, W.A.) Big Grin
Please visit my Website at: http://oldendayskids.blogspot.com/
Reply
#6
(06-22-2023, 11:45 PM)TerryRitchie Wrote:
(06-22-2023, 01:44 PM)a740g Wrote: VARPTR$ can come in handy when using DRAW or PLAY inside tight loops where you want to avoid the overhead and speed penalty of say string concatenation or other string ops.

Like in the example below, we avoid a string concatenation inside the FOR loop by using VARPTR$ for DRAW. This still works in QB64, thanks to the length Galleon went to emulate such things.

This is probably the only reason I'd use VARPTR$. Else, it is better to avoid such legacy commands.
That's a valid point I should have addressed. When using STR$() more concatenation then comes into play slowing things down considerably.

So how's using STR$ inside draw routines bad and when does it come into play slowing things down? The speed of today's hardware really masks some of these things for me. I used to avoid DRAW because it was so darn slow but these days I've been having fun with it.
Reply
#7
(06-22-2023, 11:50 PM)James D Jarvis Wrote: So how's using STR$ inside draw routines bad and when does it come into play slowing things down? The speed of today's hardware really masks some of these things for me. I used to avoid DRAW because it was so darn slow but these days I've been having fun with it.

If you keep your strings short as possible. Because with a million bytes to work with, it becomes noticeable even on the latest equipment. Steve ran an example that illustrated that concatenation is the classic performance robber, and this is true in other programming languages. Even extra "memcpy()" and "memmove()" commands in C are avoided wherever possible because they take time.

Remember the brilliant SaveImage() example that Galleon left us, which saves an image handle's picture to BMP file? The original source code uses concatenation (especially inside "py&" loop), which is unbearably slow to save an image as large as 1920x1020 or whatever else much larger than my laptop's screen. It could take five seconds or longer to save such a screen even in the version I fixed up which avoided concatenation and instead allocates a ridiculous amount of SPACE$() and uses the MID$() command to buffer things.

https://qb64phoenix.com/qb64wiki/index.php/SAVEIMAGE

VARPTR and VARPTR$ were black boxes to me that I never used, only got "Illegal function call" out of trying to make sense with them. It might make sense using it, involving a bunch of integers which values are constantly changed toward a fairly large string using them to set angle and scale and color for DRAW, or setting the tempo and volume and octave for PLAY.
Reply
#8
(06-22-2023, 11:50 PM)James D Jarvis Wrote:
(06-22-2023, 11:45 PM)TerryRitchie Wrote:
(06-22-2023, 01:44 PM)a740g Wrote: VARPTR$ can come in handy when using DRAW or PLAY inside tight loops where you want to avoid the overhead and speed penalty of say string concatenation or other string ops.

Like in the example below, we avoid a string concatenation inside the FOR loop by using VARPTR$ for DRAW. This still works in QB64, thanks to the length Galleon went to emulate such things.

This is probably the only reason I'd use VARPTR$. Else, it is better to avoid such legacy commands.
That's a valid point I should have addressed. When using STR$() more concatenation then comes into play slowing things down considerably.

So how's using STR$ inside draw routines bad and when does it come into play slowing things down? The speed of today's hardware really masks some of these things for me. I used to avoid DRAW because it was so darn slow but these days I've been having fun with it.
It's not for small programs. It's when your code starts becoming large with many different things going on where this kind of optimization matters. This is especially true in games where many different operations are going on at once. String manipulation is very CPU intensive and the cumulative effect of optimizations, such as keeping concatenation to a minimum, using integers where ever possible, avoiding square root and refactoring your algorithms to use only multiplication removing division, and using DO LOOPs instead of using FOR NEXT loops can really add up.
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#9
Music 
This program (which could be run in any "modern" QB64) is a somewhat frivolous and lengthy example of VARPTR$(). I fixed my "musak" creator to do one set of phrases. This program limits itself only to messing around with the note lengths and with the volume. Smile

Code: (Select All)

'by mnrvovrfc 23-June-2023
OPTION _EXPLICIT

DIM frag(1 TO 48) AS STRING
DIM scales(1 TO 2, 1 TO 5) AS INTEGER
DIM phrase(1 TO 15) AS STRING
DIM AS INTEGER basenote, numfrag, n, vu, ve, u, a, b, c, d
DIM AS INTEGER x, y
DIM AS INTEGER el4, el8, el16, pvol
DIM e$, f$, notes$, nop$, ncl$, double1 AS _BYTE, double2 AS _BYTE
DIM octav$, setdot AS _BYTE, fe as long

notes$ = "C C#D D#E F F#G G#A A#B "

RANDOMIZE TIMER

octav$ = "O3"
double1 = Random1(2) - 1
double2 = Random1(2) - 1
y = 1
basenote = Random1(12) * 2 - 1
FOR x = 1 TO 5
scales(y, x) = basenote
basenote = basenote + Rand(3, 6) * 2
if y = 1 and basenote >= 40 then octav$ = "O2"
NEXT

vu = 0
ve = 0
FOR y = 1 TO 32
setdot = 0
u = Random1(10)
SELECT CASE u
CASE 1: e$ = "L=" + VARPTR$(el4) + "~~"
CASE 2: e$ = "L=" + VARPTR$(el8) + "~~~~"
CASE 3: e$ = "L=" + VARPTR$(el4) + "~L=" + VARPTR$(el8) + "~~"
CASE 4: e$ = "L=" + VARPTR$(el8) + "~~L=" + VARPTR$(el4) + "~"
CASE 5: e$ = "L=" + VARPTR$(el8) + "~L=" + VARPTR$(el4) + "~L=" + VARPTR$(el8) + "~"
CASE 6: e$ = "L=" + VARPTR$(el16) + "~~L=" + VARPTR$(el8) + "~L=" + VARPTR$(el4) + "~"
CASE 7: e$ = "L=" + VARPTR$(el16) + "~~~~L=" + VARPTR$(el4) + "~"
CASE 8: e$ = "L=" + VARPTR$(el8) + "~~L=" + VARPTR$(el16) + "~~~~"
CASE 9: e$ = "L=" + VARPTR$(el8) + "~L=" + VARPTR$(el16) + "~~L=" + VARPTR$(el8) + "~L=" + VARPTR$(el16) + "~~"
CASE 10
setdot = 1
e$ = "L=" + VARPTR$(el4) + "~L=" + VARPTR$(el8) + "~"
END SELECT

setdot = 0
x = CountString(e$, "~")
n = scales(1, Random1(2))
DO WHILE x > 0
nop$ = ""
ncl$ = ""
DO WHILE n > 23
nop$ = nop$ + ">"
ncl$ = ncl$ + "<"
n = n - 24
LOOP
DO
vu = Random1(3)
LOOP WHILE vu = ve
ve = vu
SELECT CASE vu
CASE 1: nop$ = nop$ + "V50"
CASE 2: nop$ = nop$ + "V25"
CASE 3: nop$ = nop$ + "V12"
END SELECT
IF setdot THEN
setdot = 0
nop$ = nop$ + RTRIM$(MID$(notes$, n, 2)) + "." + ncl$
ELSE
nop$ = nop$ + RTRIM$(MID$(notes$, n, 2)) + ncl$
END IF
ReplaceString2 e$, "~", nop$, 1
n = scales(1, Random1(5))
x = x - 1
LOOP
frag(y) = e$
NEXT

DO 'ONLY ONCE

e$ = "MBMNT" + _TRIM$(STR$(Rand(9, 16) * 10)) + octav$

_TITLE "Press [ESC] to quit."
CLS
PRINT "Sorry, cannot print out the song because it contains junk known to computers!"
PLAY e$

numfrag = Rand(6, 16)
n = numfrag
FOR x = 1 TO 5
a = Random1(10)
b = Random1(10)
c = Random1(10)
d = Random1(10)
phrase(x) = frag(a) + frag(b) + frag(c) + frag(d)
IF double1 AND x < 3 THEN phrase(x) = phrase(x) + frag(a) + frag(b) + frag(c) + frag(d)
NEXT

u = Rand(11, 24)
n = numfrag
DO WHILE n > 0
a = advanceu(11, 32, u)
b = advanceu(11, 32, u)
c = advanceu(11, 32, u)
d = advanceu(11, 32, u)
f$ = phrase(Random1(5)) + frag(a) + frag(b) + frag(c) + frag(d)
u = Random1(4)
select case u
case 1
el4 = 2: el8 = 4: el16 = 8
case 2
el4 = 8: el8 = 16: el16 = 32
case else
el4 = 4: el8 = 8: el16 = 16
end select
e$ = e$ + f$
PLAY f$
DO WHILE PLAY(0) > 0
_LIMIT 600
IF _KEYDOWN(27) THEN EXIT DO
LOOP
IF _KEYDOWN(27) THEN EXIT DO
n = n - 1
LOOP

LOOP UNTIL 1
SYSTEM


'CHANGED: u
FUNCTION advanceu% (fromval AS INTEGER, totoval AS INTEGER, u AS INTEGER)
u = u + 1
IF u > totoval THEN u = fromval
advanceu% = u
END FUNCTION


FUNCTION Rand& (fromval&, toval&)
DIM sg%, f&, t&
IF fromval& = toval& THEN
Rand& = fromval&
EXIT FUNCTION
END IF
f& = fromval&
t& = toval&
IF (f& < 0) AND (t& < 0) THEN
sg% = -1
f& = f& * -1
t& = t& * -1
ELSE
sg% = 1
END IF
IF f& > t& THEN SWAP f&, t&
Rand& = INT(RND * (t& - f& + 1) + f&) * sg%
END FUNCTION

FUNCTION Random1& (maxvaluu&)
DIM sg%
sg% = SGN(maxvaluu&)
IF sg% = 0 THEN
Random1& = 0
ELSE
IF sg% = -1 THEN maxvaluu& = maxvaluu& * -1
Random1& = INT(RND * maxvaluu& + 1) * sg%
END IF
END FUNCTION

SUB ReplaceString2 (tx AS STRING, sfind AS STRING, repl AS STRING, numtimes AS _UNSIGNED LONG)
DIM AS STRING s, t
DIM AS _UNSIGNED LONG ls, lx, count, u
DIM goahead AS _BYTE
IF (tx = "") OR (sfind = "") OR (sfind = repl) OR (LEN(sfind) > LEN(tx)) THEN EXIT SUB
s = UCASE$(sfind): t = UCASE$(tx)
ls = LEN(s)
count = 0
goahead = 1
DO
u = INSTR(t, s)
IF u > 0 THEN
tx$ = LEFT$(tx, u - 1) + repl + MID$(tx, u + ls)
t = UCASE$(tx)
IF numtimes > 0 THEN count = count + 1: IF count >= numtimes THEN goahead = 0
ELSE
goahead = 0
END IF
LOOP WHILE goahead
END SUB

FUNCTION CountString% (tx$, delim$)
DIM AS LONG count, z1, z2, lx
IF (tx$ = "") OR (delim$ = "") THEN
CountString% = 0
EXIT FUNCTION
END IF
lx = LEN(delim$)
z1 = 1
z2 = INSTR(tx$, delim$)
count = 0
DO UNTIL z2 = 0
count = count + 1
z1 = z2 + lx
z2 = INSTR(z1, tx$, delim$)
LOOP
CountString% = count
END FUNCTION

The "L4" was simply changed to "L=" + VARPTR$(el4) for which in half the cases of choosing a random number, "el4" is set to four. There are three variables which control the length and are constantly changed in a related way, so that a portion of the music is played half as fast, or twice as fast. A couple of "phrases" are being played back with the changes to the three note length variables. Note that the volume is still being forced as string because it would require at least an array of four elements each having its own value, and going through it in series for each note in a "phrase".
Reply
#10
(06-23-2023, 06:52 PM)mnrvovrfc Wrote: This program (which could be run in any "modern" QB64) is a somewhat frivolous and lengthy example of VARPTR$(). I fixed my "musak" creator to do one set of phrases. This program limits itself only to messing around with the note lengths and with the volume. Smile

Code: (Select All)

'by mnrvovrfc 23-June-2023
OPTION _EXPLICIT

DIM frag(1 TO 48) AS STRING
DIM scales(1 TO 2, 1 TO 5) AS INTEGER
DIM phrase(1 TO 15) AS STRING
DIM AS INTEGER basenote, numfrag, n, vu, ve, u, a, b, c, d
DIM AS INTEGER x, y
DIM AS INTEGER el4, el8, el16, pvol
DIM e$, f$, notes$, nop$, ncl$, double1 AS _BYTE, double2 AS _BYTE
DIM octav$, setdot AS _BYTE, fe as long

notes$ = "C C#D D#E F F#G G#A A#B "

RANDOMIZE TIMER

octav$ = "O3"
double1 = Random1(2) - 1
double2 = Random1(2) - 1
y = 1
basenote = Random1(12) * 2 - 1
FOR x = 1 TO 5
    scales(y, x) = basenote
    basenote = basenote + Rand(3, 6) * 2
    if y = 1 and basenote >= 40 then octav$ = "O2"
NEXT

vu = 0
ve = 0
FOR y = 1 TO 32
    setdot = 0
    u = Random1(10)
    SELECT CASE u
        CASE 1: e$ = "L=" + VARPTR$(el4) + "~~"
        CASE 2: e$ = "L=" + VARPTR$(el8) + "~~~~"
        CASE 3: e$ = "L=" + VARPTR$(el4) + "~L=" + VARPTR$(el8) + "~~"
        CASE 4: e$ = "L=" + VARPTR$(el8) + "~~L=" + VARPTR$(el4) + "~"
        CASE 5: e$ = "L=" + VARPTR$(el8) + "~L=" + VARPTR$(el4) + "~L=" + VARPTR$(el8) + "~"
        CASE 6: e$ = "L=" + VARPTR$(el16) + "~~L=" + VARPTR$(el8) + "~L=" + VARPTR$(el4) + "~"
        CASE 7: e$ = "L=" + VARPTR$(el16) + "~~~~L=" + VARPTR$(el4) + "~"
        CASE 8: e$ = "L=" + VARPTR$(el8) + "~~L=" + VARPTR$(el16) + "~~~~"
        CASE 9: e$ = "L=" + VARPTR$(el8) + "~L=" + VARPTR$(el16) + "~~L=" + VARPTR$(el8) + "~L=" + VARPTR$(el16) + "~~"
        CASE 10
            setdot = 1
            e$ = "L=" + VARPTR$(el4) + "~L=" + VARPTR$(el8) + "~"
    END SELECT

    setdot = 0
    x = CountString(e$, "~")
    n = scales(1, Random1(2))
    DO WHILE x > 0
        nop$ = ""
        ncl$ = ""
        DO WHILE n > 23
            nop$ = nop$ + ">"
            ncl$ = ncl$ + "<"
            n = n - 24
        LOOP
        DO
            vu = Random1(3)
        LOOP WHILE vu = ve
        ve = vu
        SELECT CASE vu
            CASE 1: nop$ = nop$ + "V50"
            CASE 2: nop$ = nop$ + "V25"
            CASE 3: nop$ = nop$ + "V12"
        END SELECT
        IF setdot THEN
            setdot = 0
            nop$ = nop$ + RTRIM$(MID$(notes$, n, 2)) + "." + ncl$
        ELSE
            nop$ = nop$ + RTRIM$(MID$(notes$, n, 2)) + ncl$
        END IF
        ReplaceString2 e$, "~", nop$, 1
        n = scales(1, Random1(5))
        x = x - 1
    LOOP
    frag(y) = e$
NEXT

DO  'ONLY ONCE

    e$ = "MBMNT" + _TRIM$(STR$(Rand(9, 16) * 10)) + octav$

    _TITLE "Press [ESC] to quit."
    CLS
    PRINT "Sorry, cannot print out the song because it contains junk known to computers!"
    PLAY e$

    numfrag = Rand(6, 16)
    n = numfrag
    FOR x = 1 TO 5
        a = Random1(10)
        b = Random1(10)
        c = Random1(10)
        d = Random1(10)
        phrase(x) = frag(a) + frag(b) + frag(c) + frag(d)
        IF double1 AND x < 3 THEN phrase(x) = phrase(x) + frag(a) + frag(b) + frag(c) + frag(d)
    NEXT

    u = Rand(11, 24)
    n = numfrag
    DO WHILE n > 0
        a = advanceu(11, 32, u)
        b = advanceu(11, 32, u)
        c = advanceu(11, 32, u)
        d = advanceu(11, 32, u)
        f$ = phrase(Random1(5)) + frag(a) + frag(b) + frag(c) + frag(d)
        u = Random1(4)
        select case u
            case 1
                el4 = 2: el8 = 4: el16 = 8
            case 2
                el4 = 8: el8 = 16: el16 = 32
            case else
                el4 = 4: el8 = 8: el16 = 16
        end select
        e$ = e$ + f$
        PLAY f$
        DO WHILE PLAY(0) > 0
            _LIMIT 600
            IF _KEYDOWN(27) THEN EXIT DO
        LOOP
        IF _KEYDOWN(27) THEN EXIT DO
        n = n - 1
    LOOP

LOOP UNTIL 1
SYSTEM


'CHANGED: u
FUNCTION advanceu% (fromval AS INTEGER, totoval AS INTEGER, u AS INTEGER)
    u = u + 1
    IF u > totoval THEN u = fromval
    advanceu% = u
END FUNCTION


FUNCTION Rand& (fromval&, toval&)
    DIM sg%, f&, t&
    IF fromval& = toval& THEN
        Rand& = fromval&
        EXIT FUNCTION
    END IF
    f& = fromval&
    t& = toval&
    IF (f& < 0) AND (t& < 0) THEN
        sg% = -1
        f& = f& * -1
        t& = t& * -1
    ELSE
        sg% = 1
    END IF
    IF f& > t& THEN SWAP f&, t&
    Rand& = INT(RND * (t& - f& + 1) + f&) * sg%
END FUNCTION

FUNCTION Random1& (maxvaluu&)
    DIM sg%
    sg% = SGN(maxvaluu&)
    IF sg% = 0 THEN
        Random1& = 0
    ELSE
        IF sg% = -1 THEN maxvaluu& = maxvaluu& * -1
        Random1& = INT(RND * maxvaluu& + 1) * sg%
    END IF
END FUNCTION

SUB ReplaceString2 (tx AS STRING, sfind AS STRING, repl AS STRING, numtimes AS _UNSIGNED LONG)
    DIM AS STRING s, t
    DIM AS _UNSIGNED LONG ls, lx, count, u
    DIM goahead AS _BYTE
    IF (tx = "") OR (sfind = "") OR (sfind = repl) OR (LEN(sfind) > LEN(tx)) THEN EXIT SUB
    s = UCASE$(sfind): t = UCASE$(tx)
    ls = LEN(s)
    count = 0
    goahead = 1
    DO
        u = INSTR(t, s)
        IF u > 0 THEN
            tx$ = LEFT$(tx, u - 1) + repl + MID$(tx, u + ls)
            t = UCASE$(tx)
            IF numtimes > 0 THEN count = count + 1: IF count >= numtimes THEN goahead = 0
        ELSE
            goahead = 0
        END IF
    LOOP WHILE goahead
END SUB

FUNCTION CountString% (tx$, delim$)
    DIM AS LONG count, z1, z2, lx
    IF (tx$ = "") OR (delim$ = "") THEN
        CountString% = 0
        EXIT FUNCTION
    END IF
    lx = LEN(delim$)
    z1 = 1
    z2 = INSTR(tx$, delim$)
    count = 0
    DO UNTIL z2 = 0
        count = count + 1
        z1 = z2 + lx
        z2 = INSTR(z1, tx$, delim$)
    LOOP
    CountString% = count
END FUNCTION

The "L4" was simply changed to "L=" + VARPTR$(el4) for which in half the cases of choosing a random number, "el4" is set to four. There are three variables which control the length and are constantly changed in a related way, so that a portion of the music is played half as fast, or twice as fast. A couple of "phrases" are being played back with the changes to the three note length variables. Note that the volume is still being forced as string because it would require at least an array of four elements each having its own value, and going through it in series for each note in a "phrase".
Of all the places on Earth, and all the planets in the Universe, I'd rather live here (Perth, W.A.) Big Grin
Please visit my Website at: http://oldendayskids.blogspot.com/
Reply




Users browsing this thread: 9 Guest(s)