MMBasic for Windows - betas


Author Message
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 3782
Posted: 11:35pm 28 Feb 2022      

Some more grist for the mill:

1. On the CMM2 this lists the named file, on MMB4W it does nothing:
> LIST "file-that-exists.bas"


2. This crashes MMB4W:
> RUN "file-that-exists.bas"
Hello World
> RUN "file-that-does-not-exist.bas"
Failed to open file C:\home-thwill\git_sandbox\github\file-that-does-not-exist.bas
CRASHES!!!


3. PEEK(VARHEADER variable) is unimplemented.

4. INC/CAT doesn't handle case where result is > 255 characters, you had this problem and fixed it on the CMM2 where it now reports "String too long":
> Dim x$
> x$ = String$(128, "a")
> Inc x$, String$(128, "b")
> Len(x$)
98


5. DAY$() function crashes given a date before the epoch:

CMM2:
> ? Day$("31-12-1969")
Wednesday

MMB4W:
> ? Day$("31-12-1969")
CRASHES!!!


6. DATETIME$() function can't handle negative argument:

CMM2:
> ? DateTime$(-1000)
31-12-1969 23:43:20

MMB4W:
> ? DateTime$(-1000)
Epoch<0


Interestingly the EPOCH() function does seem to work with a date before the epoch:

CMM2:
> ? Epoch("31-12-1969 23:43:20")
-1000

MMB4W:
> ? Epoch("31-12-1969 23:43:20")
-1000


It's a pity this isn't a paying gig .

Best wishes,

Tom
Edited 2022-03-01 09:42 by thwill

goc30

Guru

Joined: 12/04/2017
Location: France
Posts: 425
Posted: 01:43am 01 Mar 2022      

  matherp said  How about I get rid of full screen mode completely but create a new mode for 1080p screens which is the maximum size that will fit above the task bar completely within the screen. Something like 1850x980? I can play with the setting to get the optimum


MS Window, from win3.11 to win11 can draw window with or without any border in full screen  from EGA to XGA or Full HD, I don't understand why it is not possible in MMBasic.

I have somes progs who use full screen without border, like Maps, for 1 to 4 screen. It work perfect with vb6, but now with win10 and win11, it is more difficulte to execute. For that I can be interesting by MMB, but if it is not possible to work in full screen without border, I must search other langage Lazarus or Purebasic

exemple


Goksteroo
Senior Member

Joined: 15/03/2021
Location: Australia
Posts: 110
Posted: 03:20am 01 Mar 2022      

  matherp said  Fullscreen modes removed
Noooooooo.....

mclout999
Guru

Joined: 05/07/2020
Location: United States
Posts: 426
Posted: 06:45am 01 Mar 2022      

  Goksteroo said  
  matherp said  Fullscreen modes removed
Noooooooo.....


I second that, NOOOOOOOOOO!!!!.

matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 8412
Posted: 07:38am 01 Mar 2022      

Thanks Tom/all for the feedback.
No updates now for the next 3 days

thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 3782
Posted: 12:40pm 01 Mar 2022      

Enjoy your "break" Peter,

Probably a variant of number 2 above:
> RUN "HelloWorld.bas"
Hello World
> NEW
CRASHES!!!

Best wishes,

Tom

Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 3340
Posted: 02:28pm 01 Mar 2022      

Hi Peter,

Please check the MATH FFT function. For CMM2 we had to fix it, and for MMB4W we seem to run into a similar issue (although not identical).

When I take the FFT magnitude from a sinesquare windowed sine wave, and display it in linear scale, it should look like this (CMM2 fft calculation from below program):



However, the MMB4W math function shows many spurious peaks. Below graph shows the sinesquare windowed sine signal (top), as well as the fft (lower graph)




 
I use this code to check it (the program can change the windowing function with "w", and change the number of sinewaves for fft with "a" and "z", and save the graph to disk with "s").

'check fft magnitude function

n=1023 'samples
cy=10  'cycles
sq=0   'sq=1 = square wave. sq=0 = sine wave

dim a!(n),fm!(n)
w=0  'window type, 0=none

do
 cls

 'draw 2 frames using mm.hres mm.vres
 box 50,0,(mm.hres-51),mm.vres/2-2
 box 50,mm.vres/2,mm.hres-51,mm.vres-1

 'prepare input signal
 sinfill
 if k$="w" then w=(w+1) mod 4
 if w then window
 
 'plot input signal
 plot_in

 'do fft
 math fft magnitude a!(),fm!()

 'convert to logaritm
 'lin2log

 'plot fft  
 plot_fft

 'keyboard commands
 do
   k$=inkey$
 loop until k$<>""
 if k$="a" then cy=cy+1
 if k$="z" then cy=cy-1:cy=max(cy,2)
 if k$="s" then save image "fft",0,0,800,600
loop until k$="q"

end

'subroutines

'fill the input array a!() with cy cycles of sinewave
sub sinfill
 for i=0 to n
   a!(i)=sin(2*pi*cy*i/n)
   if sq=1 then
     if a!(i)>0 then
       a!(i)=1
     else
       a!(i)=-1
     end if
   end if
 next i
end sub

'apply a window over the input signal (triangle for now)
sub window
 if w=1 then
   for i=0 to n/2  'linear
     a!(i)=(i/n)*a!(i)
     a!(n-i)=(i/n)*a!(n-i)
   next i
   ? @(60,5) "triangle window"
 else if w=2 then
   for i=0 to n    'quadratic (do linear twice)
     a!(i)=(i/n)*a!(i)
     a!(n-i)=(i/n)*a!(n-i)
   next i
   ? @(60,5) "quadratic window"
 else if w=3 then
   for i=0 to n    'sine^2 (hann) window
     a!(i)=sin(pi*i/n)*sin(pi*i/n)*a!(i)
   next i
   ? @(60,5) "hann window"
 end if
end sub

'plot the input signal in the upper window
sub plot_in
 'find minimum and maximum
 mi!=0:ma!=0
 for i=0 to n
   mi!=min(mi!,a!(i))
   ma!=max(ma!,a!(i))
 next i
 '? mi!,ma!
 'calculate gain and offset to fit it into the window
 xgain=(mm.hres-50)/n
 xoffs=50
 ygain=-(mm.vres/2.3)/(ma!-mi!)
 yoffs=mm.vres/4-ygain*(ma!+mi!)/2
 'plot the actual samples  using linear interpolation
 for i=0 to n-1
   line i*xgain+xoffs,a!(i)*ygain+yoffs,(i+1)*xgain+xoffs,a!(i+1)*ygain+yoffs
 next i
 'add legend
 ? @(mm.hres/2,5) "input linear"
 ? @(5,5) str$(ma!,2,2)
 ? @(5,mm.vres/2-20) str$(mi!,2,2)
end sub

'plot the fft output in the lower window
sub plot_fft
 'find min and max and where max is
 mi!=1e6:ma!=-1e6
 for i=0 to n/2  'fft output is symetrical
   mi!=min(mi!,fm!(i))
   ma!=max(ma!,fm!(i))
   if ma!=fm!(i) then cf=i':? cf
 next i
 'apply some logaritmic scaling
 'mi!=-120:ma!=60
 'mi!=0:ma!=200
 'mi!=max(mi!,-140):ma!=min(ma!,120)
 'for i=0 to n
 '  fm!(i)=min(ma!,max(mi!,fm!(i)))
 'next i
 'calculate scaling to fit the window
 xgain=2*(mm.hres-50)/n
 xoffs=50
 ygain=-(mm.vres/2.3)/(ma!-mi!)
 yoffs=mm.vres-mi!*ygain-20
 'plot the fft graph
 for i=0 to n/2-2
   line i*xgain+xoffs,fm!(i)*ygain+yoffs,(i+1)*xgain+xoffs,fm!(i+1)*ygain+yoffs
 next i
 'add legend
 ? @(mm.hres/2,mm.vres/2+15) "magintude logarithm"
 ? @(5,mm.vres/2+5) str$(ma!,2,2)
 ? @(5,mm.vres-30) str$(mi!,2,2);
 'put marker ticks in multiples of centre frequency
 for i=0 to n/2-2 step cf
   line i*xgain+xoffs,mm.vres/2,i*xgain+xoffs,mm.vres/2+5
   if ((i mod(5*cy)) = 0) then
     line i*xgain+xoffs,mm.vres/2,i*xgain+xoffs,mm.vres/2+15
   end if
 next i
end sub

'convert linear output to logarithmic
sub lin2log
 for i=0 to n/2
   fm!(i)= 20*log(fm!(i)+1e-300)/log(10)
 next i
end sub

Edited 2022-03-02 17:27 by Volhout

KD5ZXG
Regular Member

Joined: 21/01/2022
Location: United States
Posts: 53
Posted: 08:48pm 02 Mar 2022      

I originally tried sending this to Volhout as PM, but that function appears broken. Appears sent, but the TO: name somehow went blank. Forgive if this response is not entirely on-topic.

A sinewave times itself makes for rectified McDonalds arches, but squared in height. Rectification because negative times negative is positive.

Times a squarewave +/-1 of same phase and frequency also makes rectified mmmmmm's, but the gain is unity and not squared in magnitude.

Both the above outcomes have a positive DC component. If the phases oppose, the DC will be negative. If phases offset, the result flips between both signs. DC can be lowpass filtered to measure phase shift.

A sinewave times any other frequency makes a mess that low passes to zilch. Over time, all other frequencies but harmonics will remove themselves from the result. How much you lowpass determines how much mess leaks through, but very narrow lowpass is easy.

I have almost described a homodyne detector or lock-in amplifier. Except they usually work in 90 degree pairs, so to produce a two part vector. Let's do that.

Instead of multiplying sines and integrating over time, lets multiply only by the sign +1/-1 of a squarewave and integrate. Much easier, we only need add or subtract...

Now multiply our sine times a bunch of "sign"waves that differ only in period. Just need an array of countdowns and an array to integrate each result.

The sine*sine method is kinda like RMS, except we havn't yet taken the root of the mean squares. Sine*sign is plain old average. Probably relate just like they would for a multimeter. Vectors might need slight adjustment to account for that cheat.

I am not mathematical and don't know how FFT works. I'm not sure this alternate makes sense or less work. If I were describing the same thing, I would not know.

KD5ZXG
Edited 2022-03-03 07:10 by KD5ZXG

thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 3782
Posted: 10:59pm 02 Mar 2022      

Picking up from https://www.thebackshed.com/forum/ViewTopic.php?TID=14585&P=7

  matherp said  Going back to the READ issue. How about READ SAVE/RESTORE? These commands could be used by an INC file to save the current datapointer then do the local read, then restore it after. That way the INC could know it was being a good citizen.


Thanks again for implementing this Peter. I did wonder at the time whether I was "just being difficult", but when I switched my encryption code (used by SAAINT) to initialise from DATA statements rather than a "misused" CSUB my unit-tests for that code (which were also using DATA) immediately hit this very issue, so not artifical or made up.

EDIT: Having "played" with it, I'm inclined to think:

1. Report an error when READ RESTORE called without a prior READ SAVE: "READ RESTORE called before READ SAVE"
2. Have READ RESTORE clear the cached state, and have READ SAVE report an error if the cached state isn't clear: "READ SAVE called twice without intervening READ RESTORE"

I think this would help lock the behaviour down and would help avoid breaking any existing programs if in the future it was extended to support multiple levels of READ SAVE (presumably using a stack).

IMHO it's a real pity the global data pointer isn't a single atomic value that could simply be PEEKed and POKEed as that would provide ultimate flexibility for those advanced users who actually need this facility.

YMMV,

Tom
Edited 2022-03-03 09:54 by thwill

KD5ZXG
Regular Member

Joined: 21/01/2022
Location: United States
Posts: 53
Posted: 07:49am 03 Mar 2022      

Somewhere in some other BASIC before (not sure which) was a function that gave the memory address of the variable. I remember poking Z80 into string variables on the trs80, but finding the address wasn't easy. Maybe I searched memory for a known key-string first? A$="FindThis89ABCDEF0123456789ABCDEF"

https://rosettacode.org/wiki/Address_of_a_variable

QuickBASIC was Y=VARPTR(X)
Never seen so many other ways, but anything that works is fine.
We may already have a way for finding variables I am unaware of.

Can we locate the READ data pointer? Quite another question.
What feature is missing that wants direct access to this pointer?
Edited 2022-03-03 18:00 by KD5ZXG

Goksteroo
Senior Member

Joined: 15/03/2021
Location: Australia
Posts: 110
Posted: 09:18am 03 Mar 2022      

From Maximite MMBasic help page check out VARADDR and their example of peeking and poking that address is here:
sample$ = "The Colour Maximite 2 is the latest in a long series of microcomputers."

startB = PEEK(VARADDR sample$)
PRINT "          00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
PRINT
dump startB
FOR k = 0 TO 255
  POKE BYTE startB+k,k
NEXT k
PRINT
dump startB

SUB dump(startblock)
LOCAL INTEGER k,b,n
LOCAL a$
FOR k = 0 TO &h0f
  a$ = "   "
  PRINT HEX$(startblock+k*16,8);"  ";
  FOR n = 0 TO 15
    b = PEEK(BYTE startBlock+n+k*16)
    PRINT HEX$(b,2);" ";
    IF b > 31 AND b < 128 THEN
      a$ = a$ + CHR$(b)
    ELSE
      a$ = a$ + "."
    ENDIF
  NEXT n
  PRINT a$
NEXT k
END SUB
END
Mind you, this is for the Maximite version of MM Basic and I haven't tried it on MMB4W.

jirsoft

Guru

Joined: 18/09/2020
Location: Czech Republic
Posts: 532
Posted: 09:22am 03 Mar 2022      

  KD5ZXG said  We may already have a way for finding variables I am unaware of.


VARADDR

Too late  
Edited 2022-03-03 19:22 by jirsoft

thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 3782
Posted: 09:37am 03 Mar 2022      

  KD5ZXG said  We may already have a way for finding variables I am unaware of.


CMM2 Manual:



And there are corresponding POKEs for VAR and VARTBL.

PEEK(VARHEADER) is currently unimplemented in MMB4W but I suspect that is just an oversight that Peter will address.

This doesn't help with the DATA pointer since that is not an MMBasic variable - and infact is two internal variables which need to be updated as a single atomic unit.

  KD5ZXG said  What feature is missing that wants direct access to this pointer?


There is a fundamental design flaw in the whole BASIC DATA concept, you can't iterate through multiple DATA sections "simultaneously" because there is only one (hidden) pointer. Workarounds include either reading an entire DATA section into memory before operating on it, or designing the format of your DATA such that you can always RESTORE it to the start and then iterate through it to find a point you were at previously.

Anway, I have an idea, time to "Fire up the Quattro" - bonus pop culture points for anyone who knows the reference without looking it up.

Best wishes,

Tom

Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 3340
Posted: 10:23am 03 Mar 2022      

  KD5ZXG said  I originally tried sending this to Volhout as PM, but that function appears broken. Appears sent, but the TO: name somehow went blank. Forgive if this response is not entirely on-topic.

A sinewave times itself makes for rectified McDonalds arches, but squared in height. Rectification because negative times negative is positive.

Times a squarewave +/-1 of same phase and frequency also makes rectified mmmmmm's, but the gain is unity and not squared in magnitude.

Both the above outcomes have a positive DC component. If the phases oppose, the DC will be negative. If phases offset, the result flips between both signs. DC can be lowpass filtered to measure phase shift.

A sinewave times any other frequency makes a mess that low passes to zilch. Over time, all other frequencies but harmonics will remove themselves from the result. How much you lowpass determines how much mess leaks through, but very narrow lowpass is easy.

I have almost described a homodyne detector or lock-in amplifier. Except they usually work in 90 degree pairs, so to produce a two part vector. Let's do that.

Instead of multiplying sines and integrating over time, lets multiply only by the sign +1/-1 of a squarewave and integrate. Much easier, we only need add or subtract...

Now multiply our sine times a bunch of "sign"waves that differ only in period. Just need an array of countdowns and an array to integrate each result.

The sine*sine method is kinda like RMS, except we havn't yet taken the root of the mean squares. Sine*sign is plain old average. Probably relate just like they would for a multimeter. Vectors might need slight adjustment to account for that cheat.

I am not mathematical and don't know how FFT works. I'm not sure this alternate makes sense or less work. If I were describing the same thing, I would not know.

KD5ZXG


Fourier transform works on repetitive signals. (google "wiki FFT").
In theory these signals repeat until eternity.
If you take a snapshot of these signals (i.e. 10 sine waves) the beginning and the end of the 10 sine waves represent a distortion of the "eternal signal", they are distortions.

To minimize the disruption effect of the beginning and end of the snapshot, each sample in the snapshot is "weighted". It's contribution to the Fourier Transform is made more -or less- important. The "weighting" is called "windowing".

There are many types of windows. There is a rectangular window (meaning all samples have the same weight), there is triangular weighting (the first and last samples have zero weight, the centre samples have maximum weight. Samples in between have a weight that is a linear interpolation between first and centre sample.
If you execute the program in my post, and press "w" a few times, you see a number of windowing techniques, and the effect on waveform and FFT.

One of the weighting standards is the "Hann Window". This is similar to the triangular windowing, but samples between first and centre are weighted with a sine^2 function.

Play with the program, and experience.

Vohout

note: the FFT window shows linear magnitude FFT for demonstration purpose (the lin2log function is commented out).
Edited 2022-03-03 20:24 by Volhout

thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 3782
Posted: 11:03am 03 Mar 2022      

Peter,

I offer this for your consideration regarding manipulating the global data pointer. It's been "mangled" due to the changes I've been making to modularise the MMBasic code for MMB4L, but I'm sure you can figure out how to apply it to your implementation:

/** PEEK(DATAPOS) */
static void peek_datapos(int argc, char **argv, char *p) {
   if (argc != 1) ERROR_SYNTAX;
   uint64_t data_pos = ((NextDataLine - ProgMemory) << 32) + NextData;
   g_integer_rtn = data_pos;
   g_rtn_type = T_INT;
}

/** POKE DATAPOS data_pos% */
static void poke_datapos(int argc, char** argv, char *p) {
   if (argc != 1) ERROR_ARGUMENT_COUNT;
   uint64_t data_pos = (uint64_t) getinteger(p);
   NextDataLine = ProgMemory + (data_pos >> 32);
   NextData = data_pos & 0xFFFFFFFF;
}


And example MMBasic:
Option Base 0
Option Default None
Option Explicit On

Restore my_data
mad(5)
End

Sub mad(depth%)
 If depth% = 0 Then Exit Sub
 Local data_pos% = Peek(DataPos) ' Cache the data pointer.
 Local s$
 Restore my_data
 my_label: ' There seems to be an issue with using recursion and DO or FOR loops ?
           ' Might be MMB4L specific.
   Read s$
   If s$ = "" Then
     Print
     Poke DataPos data_pos% ' Restore the data pointer.
     Exit Sub
   EndIf
   Print Space$(5 - depth%) Str$(depth%) s$ " ";
   If depth% > 1 Then Print
   mad(depth% - 1)
 Goto my_label
End Sub

my_data:
Data "A", "B", "C", "D", "E", ""


EDIT: And before anyone starts to "have a go at me" for diverging from "standard" BASIC, we had an extremely flexible way of providing stateless data access by using CSUBs as binary data blobs, until Peter decided it was a hack and wasn't going to be supported by MMB4W  .
Edited 2022-03-03 21:18 by thwill

Mixtel90

Guru

Joined: 05/10/2019
Location: United Kingdom
Posts: 5571
Posted: 12:37pm 03 Mar 2022      

The DATA system on the PicoMite isn't a disaster now you can have relative RESTORE points. There's no need for READ loops to get to where you want. You can have as many pointers at you like pointing to the data block.


DATA
DATA
DATA
DATA
DATA
DATA
DATA
DATA
DATA
DATA
DATA
DATA

mypointer=1
mypointer20=20

RESTORE mypointer 'restores to first item

RESTORE (mypointer+5) '6th item

RESTORE (mypointer20+5) '25th item


Note that mypointer and mypointer20 are independent as are the values added to them. You only need a RESTORE() to get to any DATA item.
Edited 2022-03-03 22:47 by Mixtel90

thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 3782
Posted: 12:58pm 03 Mar 2022      

  Mixtel90 said  The DATA system on the PicoMite isn't a disaster now you can have relative RESTORE points. There's no need for READ loops to get to where you want. You can have as many pointers at you like pointing to the data block.


If the manual is correct then they aren't relative:

RESTORE [line]  Resets the line and position counters for the READ statement.
                If 'line' is specified the counters will be reset to the
                beginning of the specified line. 'line' can be a line number
                or label or a variable with these values. If 'line' is not
                specified the counters will be reset to the start of the
                program.

Relative would look like:

RESTORE [line_num|label], line_offset

or possibly:

RESTORE RELATIVE line_offset

where any of 'line_num', 'label' and 'line_offset' could be literals or variables.

Which I think would also provide a way to deal with the issue, provided you were careful in the arrangement of your DATA statements and were prepared to live with it all going haywire if you decided to add some comments or additional lines within the DATA statements.

I really was quite happy with CSUB and PEEK(CFUNADDR cfun).

Best wishes,

Tom
Edited 2022-03-03 23:14 by thwill

Mixtel90

Guru

Joined: 05/10/2019
Location: United Kingdom
Posts: 5571
Posted: 03:29pm 03 Mar 2022      

Good point - RESTORE n only restores to the line, not the actual data. I messed up there. Fine if you have an equal number of items on every line and you read a full line though. Why would comments make a difference?

Peter does have a point though, CSUB was never intended to be used for data storage and using them as such is a first class kludge - even if it does work. :)

I suppose fixed-length data can be POKED into strings or even longsstrings.

(Waiting for Tom to store data in BMP files...  :) )

William Leue
Guru

Joined: 03/07/2020
Location: United States
Posts: 379
Posted: 04:23pm 03 Mar 2022      

I get "illegal option" when I do

OPTION USBKEYBOARD US

-Bill

thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 3782
Posted: 04:25pm 03 Mar 2022      

  Mixtel90 said  Good point - RESTORE n only restores to the line, not the actual data. I messed up there. Fine if you have an equal number of items on every line and you read a full line though. Why would comments make a difference?


Because it would change the line numbers and mess up any carefully setup pointers from your original example.

  Quote  Peter does have a point though, CSUB was never intended to be used for data storage and using them as such is a first class kludge - even if it does work. :)


I agree, let's have:
BINARY name
   01234567 89ABCDE
   ...
END BINARY


Though due to MMBasic limitations BINARY and CSUB would probably end up having to share a command token so basically you're back to using the CSUB mechanism with some syntactic sugar.

My real issue is that we had something with all the power required and so far we're looking at replacing it with something less capable.

  Quote  I suppose fixed-length data can be POKED into strings or even longsstrings.


It's not where to store the data in memory that is the problem, it's where to read the data you want to store in memory from, or to put it another way how to initialise the memory storage.

  Quote  (Waiting for Tom to store data in BMP files...  :) )


If I wanted to use files I could just use binary files, they wouldn't have to be bitmaps. Also locating files relative to a .INC file is problematic since once a program is running MMBasic doesn't know anything about the arrangement of the .INC files that made up any given program.

But it does pose the question without the CSUB mechanism how do you put bitmaps into programs on 'mites that don't have an SD card reader ?

Best wishes,

Tom
Edited 2022-03-04 02:26 by thwill