Basic math functions added : cos, tan, atn, asin, acos, sqr (sin was already there)
8-bit Atari style command shortcuts added
'mode' command can be now used with text names instead of numbers
After adding these Atari type shortcut I still didn't feel "at home". Atari Basic doesn't require any spaces after such commands, the dot is sufficient to separate it from the next token... corrected now.
An example of shortcuts abusing:
10 m.st:cursor off:mouse off:cls
20 f.i=0 to 270 st. 10
30 pl.377,152+i:dr.377+i,422:b.200+10*i,100:pl.377+i,152:dr.647,i+152
40 n.i
90 c.rnd(256):fi.477,252,rnd(256),getpixel(477,252):fc.512,288,40
100 f.i=270 to 0 st. -10
110 pl.377,152+i:dr.377+i,422:b.200+10*i,100:pl.377+i,152:dr.647,i+152
120 n.i
130 c.rnd(256):fi.477,252,rnd(256),getpixel(477,252):fc.512,288,40
140 g.20
The result (it continuously plots and beeps changing colors):
Added deg (switches trigonometry to 360 degree system), rad ( switches trigonometry to radians) and int (gets anything on input, returns integer on output)
Default trigonometry unit is now radian.
After that, this Atari Basic program
100 SX=144:SY=56:SZ=64:CX=320:CY=192
110 C1=2.2*SY:C2=1.6*SY
120 DIM RR(CX)
130 FOR I=0 TO CX:RR(I)=CY:NEXT I
140 GRAPHICS 8+16:SETCOLOR 2,0,0:COLOR 1
150 CX=CX*0.5:CY=CY*0.46875:FX=SX/64:FZ=SZ/64
160 XF=4.71238905/SX
170 FOR ZI=64 TO -64 STEP -1
180 ZT=ZI*FX:ZS=ZT*ZT
190 XL=INT(SQR(SX*SX-ZS)+0.5)
200 ZX=ZI*FZ+CX:ZY=CY+ZI*FZ
210 FOR XI=0 TO XL
220 A=SIN(SQR(XI*XI+ZS)*XF)
230 Y1=ZY-A*(C1-C2*A*A)
240 X1=XI+ZX
250 IF RR(X1)>Y1 THEN RR(X1)=Y1:PLOT X1,Y1
260 X1=ZX-XI
270 IF RR(X1)>Y1 THEN RR(X1)=Y1:PLOT X1,Y1
280 NEXT XI
290 NEXT ZI
runs with only 2 changes needed:
120 DIM RR(321) - as my interpreter cannot DIM at the runtime, and I have DIM parameter equals number of elements and not the last indes
140 CLS - as I have no graphics 8+16 and setcolor (setcolor will be added). The system is already in the graphics mode so only clear the screen
Of course the plot that fills the Atari 8-bit screen is much smaller - 1024x576 insted of 320x192
4.8 seconds on a P2
Started measurement on the Atari... this will be much longer. 6502 at 1.7 MHz instead of P2 at 340 MHz...
... >52 minutes.
It cannot have expressions as a DIM parameter, only numbers are allowed here in the current version. This is because DIM is done while precompliling, and not while running, and expressions values are not known yet.
To allow expressions in DIM i have to move it to the runtime. That may be a complex task and it needs a proper memory manager.
This Basic needed a proper audio subsystem and that means I had to upgrade my audio driver.
Now it can not only play samples, but also does envelopes.
The driver became an universal 8-channel audio driver that can behave like Amiga's Paula using its 3.5 MHz sample rate/period system, but it can also use a standard phase accumulator model and, with reduced to ~1.3 MHz sample rate, it can use low noise PWM DAC mode
The envelopes are also sampled and can have up to 1024 16-bit points. The values between points are interpolated at every sample.
The mailbox for the driver had to be extended to 16 longs: 4 new longs are now used for envelopes, 4 are unused.
Having the upgraded audio driver I added 3 new instructions to the interpreter: defsnd, defenv and play - alias sound, shortcut so. for partial Atari 8 Basic compatibility.
Defsnd seems to be finished. It has several modes:
defsnd channel, "filename" : loadd the waveform from PC Softsynth type .s2 file. This file has a 16-bytes header that can be simply skipped, and then 1024, 16-bit signed samples that defines one period of a waveform. I have several such files so I added this option. PC Softsynth is a program that interprets music files from Atari Softsynth, the program for 8-bit Atari written in Germany in 1985. I wrote this several years ago. Unfinished, but plays.
defsnd channel, harmonic,harmonic... (up to 16) synthesizes the waveform from given amount of harmonics
defsnd channel, negative number, negative number - these 2 parameters are coefficients for even and odd harmonics, so defsnd 0,0.5,0.3 will use 1,0.5,0.3,0.25,0.09,0.125,0.003 (up to 16th harmonic)
defenv is not finished yet. The only mode that now works is defenv "filename.h2". that loads 256-point envelope from PC Softsynth .h2 file
To do is 4-parameter ADSR mode and 8-parameter Yamaha DX7 style with R1/L1..R4/L4
Play is also not yet finished. As it is now it gets up to 5 parameters: channel, frequency, waveform, volume and envelope. Waveforms and envelopes have to be defined earlier by defsnd/defenv. It plays as expected, but it needs to be refined and simplified. A good example is an Atari Sorfsynth's MASIC (Music BASIC) syntax that can be very simple while giving a lot of control. The ultimate goal is to enable this Basic to play these tunes, after simple conversion process. Here are PC Softsynth examples:
I pushed the current version to Github with /media directory that contains these .s2 adn .h2 files. The /media has to be copied to the SD to be accessible by the interpreter. A flash filesystem can be convenient to keep this kind of stuff.
Math function added : deg, rad, int
First version of my overengineered audio subsystem(tm) now works.
Audio system
------------
The P2 Retromachine Basic has an 8-channel audio generator that plays sampled waveforms.
Every audio channel can play a sound that has its frequency, volume, waveform, envelope, length (=time of playing), wait after command, stereo position ('pan') and sustain point.
It has also 8 sets of these parameters that can be assigned to any audio channel.
At the start, and after 'new' command these sets are initialized as:
- waveforms: 0 - sine, 1 - triangle, 2 - sawtooth, 3 - pulse, 12.5%, 4 - square, 5 - pulse, 25%, 6 - Atari Pokey waveform 12, 7 - Atari Pokey waveform 2
Use Atari Pokey waveforms at low frequencies, or aliasing will occur.
- envelopes: 0: instant attack, linear release, 1: instant attack, exponential release, 2: instant attack, long sustain, instant release,
3: smooth both ends, 4: slow linear attack, instant release, 5: slow attack as in 4, smooth release, 6: triangle wave, 7: "classic ADSR"
- all lengths are set to 1 (second), delays to 0, stereo position to center, volumes to 4.0, that's about 1/4 of maximum to avoid clipping, and sustain point to 255 (=no sustain)
- every audio channels gets the waveform and envelope the same number as the channel
To play the sound, there is 'play' instruction that has 0 to 9 parameters. For parameters that aren't provided, defaults will be used. If a parameter is provided, it becomes a new default.
The negative numbers will be ignored and defaults used instead except pan, that has range from -1.0 to 1.0. To ignore it, use less than -1
The syntax is: play channel, frequency, volume, waveform, envelope, length, delay, pan, sustain
Channel is the integer number from 0 to 7
Frequency is float, in Hz. The resolution is ~0.25 Hz, the effective sample rate is 65820 Hz
Volume is a float from 0 to 16.384
Waveform is an integer number from 0 to 8. 0..7 selects one of predefined waveforms (as described above, if not changed by the user, see below). 8 is noise
Envelope is an integer number from 0 to 8. 0..7 selects one of predefined waveforms (as described above, if not changed by the user, see below).
8 is no envelope, the sound will not stop until next play command.
Length is a float, and it is the time in seconds for the sound to play
Delay is integer, the time in ms, that the play instruction will wait as if waitms was added at the end
Pan is a float in range -1 .0 to 1.0, -1 is left, 1 is right
Sustain is the sustain point needed to play an instrument using the keyboard. The sound will stay at the sustain volume until released.
Setting the sustain point < 255 will cause the envelope to stop there. To end playing, there is the command 'release' channel. The envelope will then continue to the end.
Envelopes and waveforms can be redefined.
'defsnd' defines the waveform at given slot from 0 to 7. The syntax is:
defsnd slot, "filename" - loads the file in /media/s directory. This is PC-Softsynth type .s2 file that has 16 bytes of a header and 1024 16-bit signed samples that define one wave period.
The interpreter doesn't check the header, it simply loads 1024 words from the offset #16, so the user can deefine anything and save it in such a file
defsnd slot, harmonic,harmonic... (up to 16 of them) synthesizes the waveform from harmonics
defsnd slot, negative number,number also synthesizes the wave from harmonics, but these numbers are dampening coefficients for even and odd harmonics
'defenv' defines the envelope.
defenv slot, filename loads the file in /media/h directory. This is PC-Softsynth type .h2 file, that has 16 bytes of header and 256 unsigned 8-bit samples
defenv slot,a,d,s,r defines standard ADSR envelope. a,d and r can be anything that is not negative, s should be a float in range 0.0..1.0
Setting ADSR envelope will also compute its sustain point that can be retrieved by getenvsustain(slot)
While the interpreter remembers all parameters from a previous play command, there is a set of commands to change them individually and then simply use play channel, freq only
These are setenv, setlen, setpan, setrvol, setwave and setsustain. They all have syntax setxxx(channel,parameter)
As 'play' command remembers all these params, to play a melody with the same parameters only 'play channel,freq' is needed after they are set. I will try to make some examples. I also want to define note constants for making playing easier
'defsnd' and 'defenv' can also get a pointer to an array of samples, but I have no "addr" or "varptr" implemented yet to allow this to be usable. "Peek", "poke" and "addr" will be added in the next iteration.
There is also sound command, with so. abbreviation, that now is only alias for 'play' but I added it for 8-bit Atari Basic compatibility so it is intended to use Atari 8 syntax and play something as similar as possible with this audio system.
Maybe the command should be TONE or something. PLAY I think is more usually an MML player (which I guess is a good next step for retro BASIC themed audio nonsense)
I looked at this MML player stuff and it is somewhat similar to my audio nonsense, that is inspired by Atari Softsynth. The program was made in 1985 in Germany: here is the original instruction, that has German characters encoded in a standard I don't know, so I cannot open it in anything I have to see the proper German text. We had the same problem in Poland: until Unicode, there were at least 7 different standard of coding these extra characters (and this means I use yet another one in my ST font definition where I crammed non-standard letters at codes <32 to fit all in 7 bits.
In Poland, Germany and maybe several other countries, the sound that is a half tone lower than C is called "H". "B" is a sound that is a full tone lower than C
In USA, the sound that is a half tone lower than C is called "B"
I left "H" for Atari Softsynth compatibility, and added "B" as an alias for H. Softsynth doesn't use B, it uses A# instead.
Poke/peek family works for the full memory space: if address<$80000, HUB is used, else PSRAM is used. To access first 512k of the PSRAM< use addresses >$2_000_000: the PSRAM driver wraps around the end of the memory.
fre returns the free Basic usable memory between program top (goes up while writing the program and memtop (goes down when declaring arrays)
getnotevalue returns the frequency of the MIDI note#
Started to implement adr, that returns the address of a variable (= a pointer to it)
Math function added: abs
Added poke,dpoke,lpoke,peek,dpeek,lpeek,adr,fre,inkey$
Added getnotevalue(), changed order of 'play' parameters
'run' can now load and run the program if a filename provided
If a filename is provided without ".bas" and the file doesn't exist. the interpreter will try to add ".bas" itself
A loaded file name is kept by interpreter, then 'save' without parameters saves to that filename
If no file was loades yet, the default name is 'noname.bas'
'for-next' loop doesnt crash/work weird if a float parameters used: they are now rounded
Symbolic note names can be used in format #c4 or #c#4. They are internally converted to frequencies in Hz, as single. This means you can 'print #a4' and got 440.0
'dir' now lists the directory in 4-columns format
Several more bugs fixed.
I started to enhance the screen editor. Now it is like in Atari 8-bit : the cursor can be moved everywhere, the line under the cursor can be corrected and Enter enters it. Editing is now much easier, no more rewriting the full line.
This needs finishing - ins/del don't work yet, only bksp - and debugging, but the current version is now pushed to Github
I also changed the resolution to 1024x600. That's because of these Waveshare touch screens. This messed with the aspect ratio. It seems a "graphics" instruction has to be added. 1024x576 for a monitor, 1024x600 for Waveshare 7", 800x480 for its 5" little brother... While changing 576<->600 is easy, going 800x480 can be more challenging in this version of the driver as it is based on 4-lines long rdfast. However 4*800 is 3200, it can be divided by 64, so this can be done without rewriting the driver.
It has now a full screen editing mode. Not an 'edit' as in PicoMite, but rather as in Atari 8bit, you can move the cursor with arrows and edit wherever you want, then enter the line with Enter. No more rewriting a full line to change one character.
Also I added a string related functions: asc,chr$,len,mid$,left$,right$,str$,bin$,hex$,val
A command/function list became long now:
abs(x) if x is negative, multiply by -1, else left intact
asc(string) returns ASCII code of the first character in the string
acos(x) returns the inverse cosine
adr(var) (or addr or varptr) returns the address of the variable (= a pointer) in the memory
asin(x) returns the inverse sine
atn(x) returns the inverse tangent
beep frequency, time (b.) generates the square vave at frequency in Hz for the time in ms
bin$(value,length) returns a string that is a binary representation of the argument, in 'length' digits Length is optional
box x1,y1,x2,y2 draws a filled rectangle from x1,y1 to x2,y2
brun filename (br.) loads and executes a binary file compiled for a P2 by anything
chr$(value) returns a one-character string that represents a given ASCII code
circle x,y,r (ci.) draws the empty circle with the center at x,y and radius r
click on/off or 1/0 switches the keyboard click on and off
cls clears the screen
color colornum (c.) sets a color for graphic operations. There are 256 colors, 16 hues (high nibble) and 16 brightnesses (low nibble) similar to the 8-bit Atari. 0 to 15 are greys
cos(x) returns a cosine of x. The unit is now 1/360 of full circle (switching units is planned by deg and rad commands as in Atari Basic)
csave (cs.) tries to save a program to an audio cassette. Experimental and there is no cload yet.
cursor on/off or 1/0 switches the text cursor on and off. Because of a bug in this version, use 1/0 in programs.
defenv channel,params defines the sound envelope for the channel
defsnd channel, params defines the waveform for the channel
defsprite spritenum,x,y,w,h (ds.) makes a sprite from a screen rectangle
deg switches trigonometric functions to 360 degree system
dim declares an array or a typed variable
dir lists the working directory of an SD card
dpeek(x) returns a 16-bit unsigned value from the memory at address x
dpoke x,val writes a 16-bit unsigned val to the memory at address x
draw end_x, end_y (dr.) draws the line to point end_x, end_y. This will be the starting point for the new draw. The first starting point can be set with 'plot'
fcircle x,y,r (fc.) draws the filled circle with the center at x,y and radius r
fill x,y,newcolor,oldcolor (fi.) flood fill. Starts at x,y and replaces all pixels with oldcolor to newcolor until other color boundary fouund.
font fontnum sets the font family for characters to print. 2 fonts are implemented, 0=Atari ST mono, 1=PC DOS
for (f.) starts a loop (see program control)
frame (fr.) draws an empty rectangle from x1,y1 to x2,y2
fre returns amount of free Basic memory
getenvsustain(slot) gets the sustain point set by defenv, to use with setsustain
getnotevalue(midinote) returns the frequency of the MIDI note in Hz
getpixel(x,y) returns the color of the pixel at screen position x,y. The function returns the bacgkround pixel color even if there is a sprite drawn over this pixel.
goto line (g.) jumps to the line
hex$(value) returns a string that is a hexadecimal representation of the argument, in 'length' digits Length is optimal, 8 used if not provided
if with 'then' and 'else' controls the program flow
ink colornum (i.) sets the color of the characters to print
inkey$ returns one-character string of the last pressed key, or "" if no key pressed
int converts anything, including strings if doable, to integer
left$(string,num) returns first num characters of the string
len(string) returns the length of the string
list startline, endline (l.) outputs the code on the screen form startline to endline. If no endline specified, the program will be listed to the end.
load "filename" (lo.) loads a Basic program from the file. May be used in format load filename - without ""
lpeek(x) returns a 32-bit unsigned value from the memory at address x
lpoke x,val writes a 32-bit unsigned val to the memory at address x
mid$(string,pos,amount) returns amount character of tthe string starting from pos. The base position is 1
mode modenum (m.) sets "look and feel" of the interpreter. 0 - Atari style, 1 - PC amber, 2 - PC green, 3 - PC white, 4 - Atari ST mono
Names can be used instead of numbers: 'atari', pc_amber", 'pc_green', 'pc_white', 'st'
mouse on/off or 1/0 switches the mouse pointer on and off
mousex returns the x coordinate of a mouse pointer
mousey returns the y coordinate of a mouse pointer
mousek returns the mouse key state: 0 - not pressed, 1-left, 2-right, 4-middle
mousew returns the mousewheel position. It is unbounded 16-bit signed integer.
new clears the program memory and all variables
next (n.) closes a 'for' loop
paper colornum (p.) sets the background of the characters to print
peek(x) returns an 8-bit unsigned value from the memory at address x
pinfloat pin sets the pin to float
pinhi pin sets the pin to logic 1 (normally about 3.3V)
pinlo pin sets the pin to logic 0 (normally about 0V)
pinread pin returns the pin logoc state (0 or 1)
pinstart pin, mode, x,y starts the smart pin mode and sets x,y registers of the pin
pintoggle pin toggles the state of the pin (from 0 to 1 or from 1 to 0). Doesn't work if the pin floats.
pinwrite pin, value if value=0, sets the pin output to low, else sets the pin output to high. Use to blink a led.
play channel, (parameters) plays a sound
plot x,y (pl.) sets a pixel color at x,y to the color determined by a previous "color" command and sets a new starting point for 'draw'
poke x,val writes an 8-bit unsigned val to the memory at address x
position x,y (po.) sets a cursor position. The x resolution is half a character, so multiply number of characters by 2. This allows centering strings that have odd number of characters.
print (?) outputs to the screen and moves the cursor to the new line if , or ; is not used.
Use , after an argument to print the new one after a tab (8 characters), use ; to print the next argument directly after the previous.
rad switches trigonometric functions to radians
rdpin pin returns the smartpin output register value, notifies the smart pin
release channel makes the envelope in the audio channel to continue to the end.
right$(string,num) returns last num characters of the string
rnd(value) returns a random value. If no arguments, it is a 32-bit unsigned integer. If integer or float parameter is given, it returns integer or float that is less than,0 the parameter.
rqpin pin returns the smartpin output register value, does not notify the smart pin
run starts the program. You can use "run" inside the program to restart it.
save filename (s.) saves the program to the file
setdelay channel, delay sets the delay in ms for the audio channel to wait after 'play' instruction. Default=0
setenv channel,env# sets the predefined envelope for the audio channel. Redefine it with defenv if needed
setlen channel, len sets the time of the sound in seconds. Default 1.0
setpan channel, pan sets the stereo position, -1.0 left, +1.0 right, 0.0 center. Default 0.0
setvol channel, vol sets the volume of the channel, from 0.0 to 16.384. Higher values will cause clipping; default=4.0
setwave channel, wave# sets the predefined waveform for the channel. Redefine it with defsnd.
setsustain channel,point sets the sustain point for the envelope. The envelope will stop there until 'release' command is executed
sin(x) returns a sine of x. The unit is now 1/360 of full circle (switching units is planned by deg and rad commands as in Atari Basic)
sprite spritenum,x,y (sp.) move the sprite number sprite# (from 0 to 15
sqr(x) returns the square root of x
stick(joynum) returns the position of a digital joystick
strig(joynum) returns the button state of a digital jystick
str$(value) converts a value to a string.
tan(x) returns a tangent of x. The unit is now 1/360 of full circle (switching units is planned by deg and rad commands as in Atari Basic)
val(string) convert a string to a number. If it can, it returns an integer, if not, returns a single. If failed, returns 0
waitms time waits "time" miliseconds. Doesn't have any upper limits as it creates an internal loop when time>5000 ms
waitclock (wc.) waits for the internal 5 ms/200Hz clock tick. The clock is vblank synchronized so there is 1 vblank for 4 ticks
waitvbl (wv.) waits for the screen vertical blank. Use to synchronize the program with the screen refresh
wrpin pin writes to the mode register of the smart pin
wxpin pin writes to the X register of the smar tpin
wypin pin writes to the Y register of the smart pin
@pik33 said:
I started to enhance the screen editor. Now it is like in Atari 8-bit : the cursor can be moved everywhere, the line under the cursor can be corrected and Enter enters it. Editing is now much easier, no more rewriting the full line.
This needs finishing - ins/del don't work yet, only bksp - and debugging, but the current version is now pushed to Github
I also changed the resolution to 1024x600. That's because of these Waveshare touch screens. This messed with the aspect ratio. It seems a "graphics" instruction has to be added. 1024x576 for a monitor, 1024x600 for Waveshare 7", 800x480 for its 5" little brother... While changing 576<->600 is easy, going 800x480 can be more challenging in this version of the driver as it is based on 4-lines long rdfast. However 4*800 is 3200, it can be divided by 64, so this can be done without rewriting the driver.
Wow, your Basic seems to be progressing nicely @pik33. Will have to give it a try soon.
Coincidentally that Android Auto/CarPlay tablet thing I'm waiting on right now with the AHD camera input is also natively 1024x600, although it's a 9 inch screen not 7 inches like those Waveshare LCDs.
I called it "slow goto" as it has to find the jump target in the whole program and that can be very slow if the program is big. This can be slightly optimized in the future, to search not from the start, but from the current line, up and then down.
If no target line found, no jump will be performed.
Now, gosub/return
Edit: gosub/return/pop done. The next step: input/read/data
Case..else and other advanced things may go to v 2.0...
I will leave this "not jump at all" as you can always add "end" to stop there or react to the invalid input as in the example below (I have no 'input' implement yet, but this is the next step)
10 print "Input your age" : input a
20 goto 100+10*int(a/120)
30 print "You are an alien"
35 end
100 ? "You are young" : goto 35
110 ? "You are old"
Meanwhile I moved the memlo to $80000, and now poke and peek work in the continuous address space from 0 to $207FFFF, where the pointers <$80000 point to the HUB and >$2000000 point to the first 512k of the PSRAM. I put the audio sample buffers there, freeing 16k of hub from keeping these samples as the audio driver can play directly from the PSRAM. This also allows to make more than 8 definitions available. I also need the scratch space in the PSRAM, for example to make DIR display file names in alphabetic order instead of what it is now without wasting a precious hub ram space - not all things can go to the PSRAM, and the most memory consuming things are sprites. I don't have time in the video driver to put sprites in the PSRAM. However I can keep unused definitions of them there and download them on demand in vblank.
After testing the "audio nonsense" (name (c) Wuerfel21) I discovered it aliases as !@#$. Having a Paula based audio driver and aliases at the same time is, in reality, a full nonsense.
I overused a 'skip' parameter in the driver that allows to finetune the frequency while introducing aliases and subharmonics.
This means I need a better method to convert the frequency to the values passed to the driver's mailbox, and to do this, I needed to made several simple calculation. Instead of searching for a calculator ans a sheet of paper to write several values, or even much worse, run the spreadsheet and think how to program it (I am not fluent in using modern spreadsheets)... I simply wrote five lines in the interpeter and got my values listed on the screen
Using the interpreter is also testing it and discovering what I forgot... this time it was log function that computes logarithm.
I added it in the extended version: log(x) computes a natural logarithm, log(x,y) computes logarithm of x, that's base is y. The interpreter's structure allows to adding functions with variable number of parameters.. that's convenient.
Now, to do, is to implement what I have computed using the interpreter, into the interpreter itself.
There was a problem... I can either use 95 clocks per sample and noise dac, or 256 clocks per sample and pwm dac.
If noise dac used, a Paula's philosophy can be used. Set a period, the sample rate is always integer*the wave frequency, then the aliases are harmonics.
If PWM dac used, however, the base frequency is too low and the granularity of audio frequencies available to generate is too coarse.
The funny stuff caused by the noise dac is hearable and very unpleasant with a sine wave of a low volume. The recording shows random peaks here and there, caused by the noise DAC itself.
The workaround used:
the main driver loop works at Paula's frequency. I have to slightly go up with a system clock, to Paulax96.. (340500000)
when the samples are uploaded to the LUT buffer, there is a dropper that drops 5 samples from 8, passing only 3 of them to the buffer. The pattern is 10010010. That's why I had to go up from 95 to 96. 95 doesn't allow the dropper to be that simple. This introduces a jitter, but it works at MHz frequencies.
then the DAC works in PWM mode at 1.32 MHz
This works. No ringing at low volumes, no inharmonic aliases.
The sample rate is in range of about 8..17 kHz, slightly too low.. but higher SR doesn't provide a frequency granularity fine enough. I can keep the sample rate in one octave range as I still have the sample skip option, but it now can be a power of 2, so no subharmonics are introduced.
The better solution is to make a filter instead of a dumb sample dropper. Maybe later. I still have to add on..goto/gosub, input, read and file ops to go beta.
While testing the audio, that's now good enough to left it alone until more important things added (=doesn't annoy with too much distortions), I add several more audio related instructions - changewave, changevol, changefreq, changepan, shutup
Changexxx instructions changes the parameter while playing and without resetting the sample and envelope pointer. They also don't change saved defaults for the channel
'Shutup' silences the audio. WIth a channel# it silences the channel, without any parameters silences all . That thing is very useful when audio experiments go wrong.
This discussion is making me think about porting my old P1 byte code basic interpreter to the P2. I just ordered an Edge module with 32MB of RAM. Is there a C library to access the RAM chip?
@"David Betz" said:
This discussion is making me think about porting my old P1 byte code basic interpreter to the P2. I just ordered an Edge module with 32MB of RAM. Is there a C library to access the RAM chip?
There is a rogloh's Spin2 driver. In my repository it is psram.spin2 (high level function set) and psram16drv.spin2 (low level stuff) or psram4.drv for 4-bit interface.
Meanwhile, I added another instructions: rem, enter and round.
rem is of course a comment. Enter loads the program, but does not do "new" before, so the new code can append that, what is already in the memory.
'round' was added because 'int' in old school Basics truncates the float instead of rounding it, so now int does exactly that. As in most cases rounding a variable is more convenient, I had to add 'round' that rounds the variable.
Open, close, get, put started to work. Not optimal in any way, it seems it will end with a class written in C, but now it's good it works at all. I have to find and correct a bug that doesn' allow to have a..f in hexadecimals.
A lot of cleaning and bug fixing. Dir now lists alphabetically sorted entries. Hexadecimals can have a..f in them. Abbreviations work after then and else.
Constants can be predefined in format #name, so 'open' can be used in form open #3,#read,"filename". I have to copy/paste SPIN/PASM pin control constants there
Cleaning the big mess that I did with the interpreter is at about 25%. This includes commenting, indentation, proper variable declaring and alphabetically sorting tokens and functions.
I am doing this before implementing the last stuff planned for beta, as the mess became too big.
Comments
0.24:
Basic math functions added : cos, tan, atn, asin, acos, sqr (sin was already there)
8-bit Atari style command shortcuts added
'mode' command can be now used with text names instead of numbers
After adding these Atari type shortcut I still didn't feel "at home". Atari Basic doesn't require any spaces after such commands, the dot is sufficient to separate it from the next token... corrected now.
An example of shortcuts abusing:
The result (it continuously plots and beeps changing colors):
Added deg (switches trigonometry to 360 degree system), rad ( switches trigonometry to radians) and int (gets anything on input, returns integer on output)
Default trigonometry unit is now radian.
After that, this Atari Basic program
runs with only 2 changes needed:
120 DIM RR(321) - as my interpreter cannot DIM at the runtime, and I have DIM parameter equals number of elements and not the last indes
140 CLS - as I have no graphics 8+16 and setcolor (setcolor will be added). The system is already in the graphics mode so only clear the screen
Of course the plot that fills the Atari 8-bit screen is much smaller - 1024x576 insted of 320x192
4.8 seconds on a P2
Started measurement on the Atari... this will be much longer. 6502 at 1.7 MHz instead of P2 at 340 MHz...
... >52 minutes.
That is a cool demo.
Can you do DIM RR(CX+1)? Is CX treated as variable or constant?
It cannot have expressions as a DIM parameter, only numbers are allowed here in the current version. This is because DIM is done while precompliling, and not while running, and expressions values are not known yet.
To allow expressions in DIM i have to move it to the runtime. That may be a complex task and it needs a proper memory manager.
This Basic needed a proper audio subsystem and that means I had to upgrade my audio driver.
Now it can not only play samples, but also does envelopes.
The driver became an universal 8-channel audio driver that can behave like Amiga's Paula using its 3.5 MHz sample rate/period system, but it can also use a standard phase accumulator model and, with reduced to ~1.3 MHz sample rate, it can use low noise PWM DAC mode
The envelopes are also sampled and can have up to 1024 16-bit points. The values between points are interpolated at every sample.
The mailbox for the driver had to be extended to 16 longs: 4 new longs are now used for envelopes, 4 are unused.
Having the upgraded audio driver I added 3 new instructions to the interpreter: defsnd, defenv and play - alias sound, shortcut so. for partial Atari 8 Basic compatibility.
Defsnd seems to be finished. It has several modes:
defsnd channel, "filename" : loadd the waveform from PC Softsynth type .s2 file. This file has a 16-bytes header that can be simply skipped, and then 1024, 16-bit signed samples that defines one period of a waveform. I have several such files so I added this option. PC Softsynth is a program that interprets music files from Atari Softsynth, the program for 8-bit Atari written in Germany in 1985. I wrote this several years ago. Unfinished, but plays.
defsnd channel, harmonic,harmonic... (up to 16) synthesizes the waveform from given amount of harmonics
defsnd channel, negative number, negative number - these 2 parameters are coefficients for even and odd harmonics, so defsnd 0,0.5,0.3 will use 1,0.5,0.3,0.25,0.09,0.125,0.003 (up to 16th harmonic)
defenv is not finished yet. The only mode that now works is defenv "filename.h2". that loads 256-point envelope from PC Softsynth .h2 file
To do is 4-parameter ADSR mode and 8-parameter Yamaha DX7 style with R1/L1..R4/L4
Play is also not yet finished. As it is now it gets up to 5 parameters: channel, frequency, waveform, volume and envelope. Waveforms and envelopes have to be defined earlier by defsnd/defenv. It plays as expected, but it needs to be refined and simplified. A good example is an Atari Sorfsynth's MASIC (Music BASIC) syntax that can be very simple while giving a lot of control. The ultimate goal is to enable this Basic to play these tunes, after simple conversion process. Here are PC Softsynth examples:
http://eksperymenty.edu.pl/images/mp3/opswiata1.mp3
http://eksperymenty.edu.pl/images/mp3/wodo.mp3
Atari 8 originals look like this
I pushed the current version to Github with /media directory that contains these .s2 adn .h2 files. The /media has to be copied to the SD to be accessible by the interpreter. A flash filesystem can be convenient to keep this kind of stuff.
0.25:
Math function added : deg, rad, int
First version of my overengineered audio subsystem(tm) now works.
As 'play' command remembers all these params, to play a melody with the same parameters only 'play channel,freq' is needed after they are set. I will try to make some examples. I also want to define note constants for making playing easier
'defsnd' and 'defenv' can also get a pointer to an array of samples, but I have no "addr" or "varptr" implemented yet to allow this to be usable. "Peek", "poke" and "addr" will be added in the next iteration.
There is also sound command, with so. abbreviation, that now is only alias for 'play' but I added it for 8-bit Atari Basic compatibility so it is intended to use Atari 8 syntax and play something as similar as possible with this audio system.
This program plays random, pentatonic melodies while changing waveforms and stereo position and printing the frequencies:
Maybe the command should be TONE or something. PLAY I think is more usually an MML player (which I guess is a good next step for retro BASIC themed audio nonsense)
I looked at this MML player stuff and it is somewhat similar to my audio nonsense, that is inspired by Atari Softsynth. The program was made in 1985 in Germany: here is the original instruction, that has German characters encoded in a standard I don't know, so I cannot open it in anything I have to see the proper German text. We had the same problem in Poland: until Unicode, there were at least 7 different standard of coding these extra characters (and this means I use yet another one in my ST font definition where I crammed non-standard letters at codes <32 to fit all in 7 bits.
Note names added. They starts with #
and they are internally convetred to floats so they may be used in other commands and expressions:
print (#c4+100)
returns 361.63
Names are #c, #c#, #d, #d#, #e, #f, #f#, #g,, #g#, #a,, #a#, #b, #h
About #b and #h:
In Poland, Germany and maybe several other countries, the sound that is a half tone lower than C is called "H". "B" is a sound that is a full tone lower than C
In USA, the sound that is a half tone lower than C is called "B"
I left "H" for Atari Softsynth compatibility, and added "B" as an alias for H. Softsynth doesn't use B, it uses A# instead.
Added : poke, lpoke, dpoke, peek, lpeek, dpeek, fre, getnotevalue.
Poke/peek family works for the full memory space: if address<$80000, HUB is used, else PSRAM is used. To access first 512k of the PSRAM< use addresses >$2_000_000: the PSRAM driver wraps around the end of the memory.
fre returns the free Basic usable memory between program top (goes up while writing the program and memtop (goes down when declaring arrays)
getnotevalue returns the frequency of the MIDI note#
Started to implement adr, that returns the address of a variable (= a pointer to it)
Time to make a "BETA todo list..."
Implement:
- adr done
- binary and hexadecimal notation done
- chr$,val, str$, bin$,hex$,left$,right$,mid$ done
- goto expr done
- gosub/return/pop done
- file operations done except print# and input#
- on expr goto/gosub
Add a proper error reporting (now it is chaotic)
Clean the code
Bump the version# to 0.9
0.27
Changelog from 0.25:
Math function added: abs
Added poke,dpoke,lpoke,peek,dpeek,lpeek,adr,fre,inkey$
Added getnotevalue(), changed order of 'play' parameters
'run' can now load and run the program if a filename provided
If a filename is provided without ".bas" and the file doesn't exist. the interpreter will try to add ".bas" itself
A loaded file name is kept by interpreter, then 'save' without parameters saves to that filename
If no file was loades yet, the default name is 'noname.bas'
'for-next' loop doesnt crash/work weird if a float parameters used: they are now rounded
Symbolic note names can be used in format #c4 or #c#4. They are internally converted to frequencies in Hz, as single. This means you can 'print #a4' and got 440.0
'dir' now lists the directory in 4-columns format
Several more bugs fixed.
I started to enhance the screen editor. Now it is like in Atari 8-bit : the cursor can be moved everywhere, the line under the cursor can be corrected and Enter enters it. Editing is now much easier, no more rewriting the full line.
This needs finishing - ins/del don't work yet, only bksp - and debugging, but the current version is now pushed to Github
I also changed the resolution to 1024x600. That's because of these Waveshare touch screens. This messed with the aspect ratio. It seems a "graphics" instruction has to be added. 1024x576 for a monitor, 1024x600 for Waveshare 7", 800x480 for its 5" little brother... While changing 576<->600 is easy, going 800x480 can be more challenging in this version of the driver as it is based on 4-lines long rdfast. However 4*800 is 3200, it can be divided by 64, so this can be done without rewriting the driver.
I marked 0.28 as pre-beta
It has now a full screen editing mode. Not an 'edit' as in PicoMite, but rather as in Atari 8bit, you can move the cursor with arrows and edit wherever you want, then enter the line with Enter. No more rewriting a full line to change one character.
Also I added a string related functions: asc,chr$,len,mid$,left$,right$,str$,bin$,hex$,val
A command/function list became long now:
Wow, your Basic seems to be progressing nicely @pik33. Will have to give it a try soon.
Coincidentally that Android Auto/CarPlay tablet thing I'm waiting on right now with the AHD camera input is also natively 1024x600, although it's a 9 inch screen not 7 inches like those Waveshare LCDs.
"Slow goto" seems to work. Now this is possible:
The program prints "50"
I called it "slow goto" as it has to find the jump target in the whole program and that can be very slow if the program is big. This can be slightly optimized in the future, to search not from the start, but from the current line, up and then down.
If no target line found, no jump will be performed.
Now, gosub/return
Edit: gosub/return/pop done. The next step: input/read/data
Or stop and output a "Line not found" error, which is perhaps safer.
The current design allows a useful CASE .. ELSE handling.
Case..else and other advanced things may go to v 2.0...
I will leave this "not jump at all" as you can always add "end" to stop there or react to the invalid input as in the example below (I have no 'input' implement yet, but this is the next step)
Meanwhile I moved the memlo to $80000, and now poke and peek work in the continuous address space from 0 to $207FFFF, where the pointers <$80000 point to the HUB and >$2000000 point to the first 512k of the PSRAM. I put the audio sample buffers there, freeing 16k of hub from keeping these samples as the audio driver can play directly from the PSRAM. This also allows to make more than 8 definitions available. I also need the scratch space in the PSRAM, for example to make DIR display file names in alphabetic order instead of what it is now without wasting a precious hub ram space - not all things can go to the PSRAM, and the most memory consuming things are sprites. I don't have time in the video driver to put sprites in the PSRAM. However I can keep unused definitions of them there and download them on demand in vblank.
The interpreter became usable
After testing the "audio nonsense" (name (c) Wuerfel21) I discovered it aliases as !@#$. Having a Paula based audio driver and aliases at the same time is, in reality, a full nonsense.
I overused a 'skip' parameter in the driver that allows to finetune the frequency while introducing aliases and subharmonics.
This means I need a better method to convert the frequency to the values passed to the driver's mailbox, and to do this, I needed to made several simple calculation. Instead of searching for a calculator ans a sheet of paper to write several values, or even much worse, run the spreadsheet and think how to program it (I am not fluent in using modern spreadsheets)... I simply wrote five lines in the interpeter and got my values listed on the screen
Using the interpreter is also testing it and discovering what I forgot... this time it was log function that computes logarithm.
I added it in the extended version: log(x) computes a natural logarithm, log(x,y) computes logarithm of x, that's base is y. The interpreter's structure allows to adding functions with variable number of parameters.. that's convenient.
Now, to do, is to implement what I have computed using the interpreter, into the interpreter itself.
There was a problem... I can either use 95 clocks per sample and noise dac, or 256 clocks per sample and pwm dac.
If noise dac used, a Paula's philosophy can be used. Set a period, the sample rate is always integer*the wave frequency, then the aliases are harmonics.
If PWM dac used, however, the base frequency is too low and the granularity of audio frequencies available to generate is too coarse.
The funny stuff caused by the noise dac is hearable and very unpleasant with a sine wave of a low volume. The recording shows random peaks here and there, caused by the noise DAC itself.
The workaround used:
This works. No ringing at low volumes, no inharmonic aliases.
The sample rate is in range of about 8..17 kHz, slightly too low.. but higher SR doesn't provide a frequency granularity fine enough. I can keep the sample rate in one octave range as I still have the sample skip option, but it now can be a power of 2, so no subharmonics are introduced.
The better solution is to make a filter instead of a dumb sample dropper. Maybe later. I still have to add on..goto/gosub, input, read and file ops to go beta.
While testing the audio, that's now good enough to left it alone until more important things added (=doesn't annoy with too much distortions), I add several more audio related instructions - changewave, changevol, changefreq, changepan, shutup
Changexxx instructions changes the parameter while playing and without resetting the sample and envelope pointer. They also don't change saved defaults for the channel
'Shutup' silences the audio. WIth a channel# it silences the channel, without any parameters silences all . That thing is very useful when audio experiments go wrong.
This discussion is making me think about porting my old P1 byte code basic interpreter to the P2. I just ordered an Edge module with 32MB of RAM. Is there a C library to access the RAM chip?
There is a rogloh's Spin2 driver. In my repository it is psram.spin2 (high level function set) and psram16drv.spin2 (low level stuff) or psram4.drv for 4-bit interface.
Meanwhile, I added another instructions: rem, enter and round.
rem is of course a comment. Enter loads the program, but does not do "new" before, so the new code can append that, what is already in the memory.
'round' was added because 'int' in old school Basics truncates the float instead of rounding it, so now int does exactly that. As in most cases rounding a variable is more convenient, I had to add 'round' that rounds the variable.
Open, close, get, put started to work. Not optimal in any way, it seems it will end with a class written in C, but now it's good it works at all. I have to find and correct a bug that doesn' allow to have a..f in hexadecimals.
A lot of cleaning and bug fixing. Dir now lists alphabetically sorted entries. Hexadecimals can have a..f in them. Abbreviations work after then and else.
Constants can be predefined in format #name, so 'open' can be used in form open #3,#read,"filename". I have to copy/paste SPIN/PASM pin control constants there
Cleaning the big mess that I did with the interpreter is at about 25%. This includes commenting, indentation, proper variable declaring and alphabetically sorting tokens and functions.
I am doing this before implementing the last stuff planned for beta, as the mess became too big.
These are tokens left as todo for a first beta:
A funny bug found while cleaning the code. 'if" doesn't check for "then" - at all. It simply skips the token.... so this:
10 if a=10 qqq b=2 : else b=3
works as if 'qqq" was 'then'. Anything, even a keyword, does the job there. To be corrected...
I am now about line 1800 of 5400 lines of the interpreter in the cleaning process
0.31. Cleaned up : interpreter/tokenizer, precompiler and expression evaluator. To clean : runtime.