Initial version

This commit is contained in:
Neil McPhail 2024-11-11 18:33:26 +00:00
commit 4c3ec80b05
16 changed files with 2482 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.tap
ttttt

39
Makefile Normal file
View File

@ -0,0 +1,39 @@
linuxmatters.tap: header.block body.block lmtheme.zx0.block
cat header.block body.block lmtheme.zx0.block > linuxmatters.tap
lmtheme.zx0.block: lmtheme.zx0 ttttt
./ttttt lmtheme.zx0 data
lmtheme.zx0: lmtheme
zx0 lmtheme
lmtheme: lmtheme.asm
pasmo lmtheme.asm lmtheme
header.block: header ttttt
./ttttt header header
header: header.asm body.block
pasmo header.asm headerlong
dd if=headerlong of=header bs=17 count=1
rm -f headerlong
body.block: body ttttt
./ttttt body data
body: remload.asm code.asm dzx0_turbo.asm lm1.zx0 lm2.zx0 lm3.zx0 lm4.zx0 lm5.zx0 lm6.zx0 lm7.zx0
pasmo remload.asm body
ttttt: ttttt.c
cc ttttt.c -o ttttt
clean:
rm -f *.tap
rm -f *.block
rm -f body
rm -f header
rm -f ttttt
rm -f lmtheme.zx0
rm -f lmtheme
.PHONY: clean

26
README.md Normal file
View File

@ -0,0 +1,26 @@
# Linux Matters Podcast Theme Demo
This is a ZX Spectrum program which plays the title and theme from the [Linux Matters Podcast](https://linuxmatters.sh/). It uses my [ZX Spectrum REMLoad routine](https://boarstone.mcphail.uk/mcphail/spectrum_remload), and should serve as an example of how to adapt it to your own projects.
## Building
On a Linux machine, run `make`. I have no idea if it will build on Windows or MacOS. Patches welcome!
## Requirements
- A C compiler
- `pasmo`
- `dd`
- Einar Saukas's `zx0` compression tool ( [Github Repository](https://github.com/einar-saukas/ZX0) )
- `make`
## Thanks
- [Mark](https://indieweb.social/@marxjohnson), [Wimpy](https://wimpysworld.com/) and [Popey](https://popey.com/) from the Linux Matters show
- [Joe Ressington](https://joeress.com/) for the music, artwork and for producing the show
- [Einar Saukas](https://github.com/einar-saukas) for the `zx0` compression routines
- [Shiru](https://shiru.untergrund.net/software.shtml) for the `BeepFX` tool used to sample and play the music
## Licence
In keeping with the podcast, this is released under the [Creative Commons Attribution-NonCommercial 4.0 International license](https://creativecommons.org/licenses/by-nc/4.0/). Please consult the `zx0` repository for the requirements for that code.

70
code.asm Normal file
View File

@ -0,0 +1,70 @@
; This is the main file you will edit to add your machine code to the REM statement.
; You _must_ include the "entry_point" tag to point to the address which will called by the BASIC "PRINT USR" statement.
; The default code below is for illustration only, and can be deleted.
; But REMEMBER TO ADD THE entry_point TAG TO YOUR OWN CODE!
loadmus EQU 55425
music EQU #8000
midscreen EQU #4800
data_start: ;if your routine starts here, move the entry_point tag here too
dw lm1,lm1,lm1,lm1,lm1,lm1,lm1,lm1,lm1
dw lm2,lm3,lm4,lm5,lm6,lm7,lm1,0
entry_point: ;move this tag to the start point of your code
ld hl, lm1
ld de, midscreen
call dzx0_turbo
scf
ld a, #ff
ld ix, loadmus
ld de, 10109
call #0556
ld hl, loadmus
ld de, music
call dzx0_turbo
repeat:
xor a
call music
ld hl, data_start
anim:
ld e, (hl)
inc hl
ld d, (hl)
inc hl
push hl
ld hl, -1
add hl, de
pop hl
jr nc, repeat
ex de, hl
push de
ld de, midscreen
halt
halt
halt
halt
halt
call dzx0_turbo
pop hl
jr anim
INCLUDE dzx0_turbo.asm
lm1:
INCBIN lm1.zx0
lm2:
INCBIN lm2.zx0
lm3:
INCBIN lm3.zx0
lm4:
INCBIN lm4.zx0
lm5:
INCBIN lm5.zx0
lm6:
INCBIN lm6.zx0
lm7:
INCBIN lm7.zx0

100
dzx0_turbo.asm Normal file
View File

@ -0,0 +1,100 @@
; -----------------------------------------------------------------------------
; ZX0 decoder by Einar Saukas & introspec
; "Turbo" version (126 bytes, 21% faster)
; -----------------------------------------------------------------------------
; Parameters:
; HL: source address (compressed data)
; DE: destination address (decompressing)
; -----------------------------------------------------------------------------
dzx0_turbo:
ld bc, $ffff ; preserve default offset 1
ld (dzx0t_last_offset+1), bc
inc bc
ld a, $80
jr dzx0t_literals
dzx0t_new_offset:
ld c, $fe ; prepare negative offset
add a, a
jp nz, dzx0t_new_offset_skip
ld a, (hl) ; load another group of 8 bits
inc hl
rla
dzx0t_new_offset_skip:
call nc, dzx0t_elias ; obtain offset MSB
inc c
ret z ; check end marker
ld b, c
ld c, (hl) ; obtain offset LSB
inc hl
rr b ; last offset bit becomes first length bit
rr c
ld (dzx0t_last_offset+1), bc ; preserve new offset
ld bc, 1 ; obtain length
call nc, dzx0t_elias
inc bc
dzx0t_copy:
push hl ; preserve source
dzx0t_last_offset:
ld hl, 0 ; restore offset
add hl, de ; calculate destination - offset
ldir ; copy from offset
pop hl ; restore source
add a, a ; copy from literals or new offset?
jr c, dzx0t_new_offset
dzx0t_literals:
inc c ; obtain length
add a, a
jp nz, dzx0t_literals_skip
ld a, (hl) ; load another group of 8 bits
inc hl
rla
dzx0t_literals_skip:
call nc, dzx0t_elias
ldir ; copy literals
add a, a ; copy from last offset or new offset?
jr c, dzx0t_new_offset
inc c ; obtain length
add a, a
jp nz, dzx0t_last_offset_skip
ld a, (hl) ; load another group of 8 bits
inc hl
rla
dzx0t_last_offset_skip:
call nc, dzx0t_elias
jp dzx0t_copy
dzx0t_elias:
add a, a ; interlaced Elias gamma coding
rl c
add a, a
jr nc, dzx0t_elias
ret nz
ld a, (hl) ; load another group of 8 bits
inc hl
rla
ret c
add a, a
rl c
add a, a
ret c
add a, a
rl c
add a, a
ret c
add a, a
rl c
add a, a
ret c
dzx0t_elias_loop:
add a, a
rl c
rl b
add a, a
jr nc, dzx0t_elias_loop
ret nz
ld a, (hl) ; load another group of 8 bits
inc hl
rla
jr nc, dzx0t_elias_loop
ret
; -----------------------------------------------------------------------------

21
header.asm Normal file
View File

@ -0,0 +1,21 @@
blocktype:
db 0 ;basic program
filename:
db "LOADER "
payloadlength:
dw endbodytap - bodytap - 4
autorunlinenumber:
dw 0
variablearea:
dw endbodytap - bodytap - 4
checksum:
db 0
bodytap:
INCBIN body.block
endbodytap:

BIN
lm1.zx0 Normal file

Binary file not shown.

BIN
lm2.zx0 Normal file

Binary file not shown.

BIN
lm3.zx0 Normal file

Binary file not shown.

BIN
lm4.zx0 Normal file

Binary file not shown.

BIN
lm5.zx0 Normal file

Binary file not shown.

BIN
lm6.zx0 Normal file

Binary file not shown.

BIN
lm7.zx0 Normal file

Binary file not shown.

2071
lmtheme.asm Normal file

File diff suppressed because it is too large Load Diff

58
remload.asm Normal file
View File

@ -0,0 +1,58 @@
org #5ccb
linenumber:
db #00 ;MSB
db #00 ;LSB
linelength:
dw eol - border ;will need calculated and adjusted post hoc. Length of text including ENTER
border:
db #e7, '0' ;BORDER 0
db #0e ;number
db 0,0 ;mantissa
dw 0 ;number
db 0
db ':'
paper:
db #da, '0' ;PAPER 0
db #0e
db 0,0
dw 0
db 0
db ':'
ink:
db #d9, '6' ;INK 7
db #0e
db 0,0
dw 6
db 0
db ':'
clear:
db #fd, "32767" ;CLEAR 59999 - presumably code will be loaded somewhere?
db #0e
db 0,0
dw 32767
db 0
db ':'
printusr:
db #f5, #c0 ;PRINT USR
db "0" ;don't know if actual value is important
db #0e
db 0,0
dw entry_point ;actual call to REM statement code
db 0
db ':'
remcode:
db #ea ;REM
code:
INCLUDE code.asm
enter:
db #0d
eol:

95
ttttt.c Normal file
View File

@ -0,0 +1,95 @@
/* McPhail's Tip-Top TAP Top-Tailer
* Takes a raw code file as input
* Prepends data length and appends xor checksum
* Outputs to new file with .block suffix */
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <linux/limits.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fdin, fdout;
char outfilename[PATH_MAX];
unsigned int count = 0;
unsigned char flagopt = 0;
unsigned char tally = 0;
unsigned char next = 0;
if ((argc<2) || (argc>3)) {
printf("Please specify file to read.\n");
return -1;
}
if (3==argc) {
if (!strcmp(argv[2], "header")) flagopt = 1;
if (!strcmp(argv[2], "data")) flagopt = 2;
if (!flagopt) {
printf("Optional flags are \"header\" or \"data\"\n");
return -1;
}
}
if (! flagopt) {
printf("*** WARNING ***\n");
printf("*** ttttt is running in RAW mode.\n");
printf("*** Have you manually included the format flag as the first byte of the block?\n");
printf("*** If not, run again passing 'header' or 'data' as the second parameter.\n");
printf("*** WARNING ***\n\n");
}
fdin = open(argv[1], O_RDONLY);
if (fdin<0) {
printf("Couldn't open %s. for reading\n", argv[1]);
return -1;
}
snprintf(outfilename, PATH_MAX, "%s.block", argv[1]);
outfilename[PATH_MAX - 1] = '\0';
if (! strcmp(argv[1], outfilename)) {
printf("Filename too long - would clobber existing.\n");
close(fdin);
return -1;
}
fdout = open(outfilename, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
if (fdout<0) {
printf("Couldn't open %s for writing.\n", outfilename);
close(fdin);
return -1;
}
while (read(fdin, &next, 1)) {
tally ^= next;
count ++;
};
lseek(fdin, 0, SEEK_SET);
if (flagopt) count++;
char lsb = (count+1)&255;
char msb = ((count+1)>>8)&255;
write(fdout, &lsb, 1);
write(fdout, &msb, 1);
next = 0;
if (flagopt == 2) next = 255;
if (flagopt) {
write(fdout, &next, 1);
tally ^= next;
}
while (read(fdin, &next, 1)) {
write(fdout, &next, 1);
}
write(fdout, &tally, 1);
close(fdout);
close(fdin);
printf("File %s written.\n", outfilename);
count--;
printf("Code length is %d (0x%.4X).\n", count, count);
return 0;
}