Initial version
This commit is contained in:
commit
4c3ec80b05
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.tap
|
||||
ttttt
|
39
Makefile
Normal file
39
Makefile
Normal 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
26
README.md
Normal 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
70
code.asm
Normal 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
100
dzx0_turbo.asm
Normal 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
21
header.asm
Normal 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:
|
2071
lmtheme.asm
Normal file
2071
lmtheme.asm
Normal file
File diff suppressed because it is too large
Load Diff
58
remload.asm
Normal file
58
remload.asm
Normal 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
95
ttttt.c
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user