This post originally appeared on Spectrum Computing.

STRMS and CHANS on SAM with Network

SAM's ROM supports the Network as a device for LOADing and SAVEing but doesn't really have a Net channel that we can use with BASIC streams. The BAISC interpreter will allow you to use an “N” channel specifier, but it expects DOS to provide the routines. (I think? But none of SAMDOS, MasterDOS or BDOS seem to provide it, in fact they (BDOS in particular) tend to clobber the save/load functions).

Adding a custom channel specification is well documented for the Spectrum, and SAM works in much the same way. SAM's networking is not compatible with Interface 1 Net (unlike MGT DiSCIPLE) but Streams and Channels are very similar to the ZX Spectrum implementation, even using the same system variable locations (see Ian Beardsmore's article Spectrolysis: Channels and Streams).

A typical channel definition is a group of 5 bytes (Microdrives are a bit different). They contain pointer to an input routine, a pointer to an output routine, and a channel letter. As with the Spectrum it seems the letter doesn’t really matter.

I set up a couple of routines that use the ROM routines for Net/MIDI input and output. The routines expect the data byte in register A and are called for each byte to be sent or received. ROM1 needs to be paged in, and for INKEY$ it needs to remain paged in after the routine RETurns.

I picked $4000 as a load and run address (which is the start of the system heap) because it is kept paged-in along with the system variables while stuff is being shuffled in the higher memory area. I didn't bother using JHEAPROOM to formally request some heap space, but that would be more elegant I think. Maybe there's a better place to stash these routines anyway!

Once installed, you can use stream #4 for input or output. It works fine, unfortunately it's REALLY slow! But it works.

disk icon sam-net-channel.dsk

BASIC example

PRINT #4; "Spectrum Computing"
INPUT #4; A$ : PRINT A$
PRINT INKEY$ #4
LIST #4
DIR #4;1

Source code

; MAKEROOM works similarly to ZX MAKESPACE but also has other entry points
; to handle paging etc. The entry point 0x1E1B will open BC bytes at (hl).

; The Tech manual gives STRMS as 0x5C0C, (but that includes the extra ones
; that the Spectrum doesn't have?). The ones in common start at 0x5C10 (as
; they do on ZX Spectrum) so we can use the same values.

DELBC:      equ 0x005F	; Delay BC
MAKEROOM:   equ 0x1E1B	; Open BC bytes at (HL), must be in area C.
PROGP:		equ 0x5A9F	; RAM page number (0-31) (usually 0)
PROG:		equ	0x5AA0	; Offset within PROGP, plus 0x8000 (usually 0x9CD5)
HPST:		equ 0x5BC8  ; Start of Sytem Heap (usually 0x4000)
STRMS:		equ 0x5C10	; Streams area (same as ZX)
CHANS:		equ 0x5C4F	; Channels area (same as ZX)
NMOUT:		equ 0xE54D	; Net/MIDI out (byte in A)
CKNET:		equ 0xE73D	; Check if Net busy
NMIN:		equ 0xE74F	; Net/MIDI in. Byte in A with carry set
						; or NC and A=0 if no byte was recieved.
LMPR:		equ 0xFA	; The Lower Memory Page Register Address 
HMPR:		equ 0xFB	; The Higher Memory Page Register Address 

	org 0x4000			; 16384 // start of Heap
	dump 0,0			; RAM page 0, offset 0
	autoexec			; AUTO boot

		; Move heap pointer to account for our program
		ld hl,newhpst
		ld (HPST),hl

		di
		in a,(HMPR)
		push af
		ld a,(PROGP)		; Usually 0
		out (HMPR),a		; Page bank 0 into area C (0x8000-0xBFFF)

		ld hl,(PROG)		; Usually holds 0x9CD5
		dec hl				; Make space for 5 bytes immediately below PROG.
		ld bc,0x0005		; (We will find the top of the CHANS
		call MAKEROOM		; area by going backwards from PROG).
		inc hl				; hl points to 1st byte of new channel data
		ld a, nso \ 0x100	; LSB of output routine
		ld (hl),a
		inc hl
		push hl				; Save address of 2nd byte of new channel data
		ld a, nso / 0x100	; MSB of output routine
		ld (hl),a
		inc hl
		ld a,nsi \ 0x100	; LSB of input routine
		ld (hl),a
		inc hl
		ld a,nsi / 0x100	; MSB of input routine
		ld (hl),a
		inc hl
		ld a,0x4E     ; Channel name 'N'
		ld (hl),a
		POP hl        ; Get address of 2nd byte of output routine
		ld de,(CHANS) ; Calculate the offset to the channel data
		and a         ; 
		sbc hl,de     ;
		ex de,hl      ; and store it in DE
		ld hl,STRMS   ;
		ld a,0x04     ; Stream to open, in this case #4.
		add a,0x03    ; Account for streams -3, -2, -1
		add a,a       ; Calculate the offset (2 bytes per stream)
		ld b,0x00     ; 
		ld c,a        ;
		add hl,bc     ; … and store it in hl
		ld (hl),e     ; LSB of 2nd byte of new channel data
		inc hl        ;
		ld (hl),d     ; MSB of 2nd byte of new channel data

		pop af
		out (HMPR),a

		ret

; Net/MIDI output
nso:
		di
		push af
		in a,(LMPR)		;
		set 6,a			;
		out (LMPR),a	; enable ROM1
		push bc
		ld B,100		; delay
		call DELBC		;
		POP bc
		call CKNET		; wait for Net ready
		POP af
		call NMOUT		; send byte
		ei
		ret

; Net/MIDI Input
nsi:
		di
		in a,(LMPR)
		set 6,a
		out (LMPR),a
		call CKNET		;wait for Net ready

wait:
		call NMIN		;wait for a byte
		JR NC,wait
		ei
		ret

newhpst: equ $