Skip to content

Commit

Permalink
add workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiodl committed Jan 31, 2024
1 parent 2c40290 commit 6fbbcc4
Show file tree
Hide file tree
Showing 12 changed files with 1,191 additions and 0 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Convert Basic
on:
push:
branches:
- master
jobs:
convert:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install -r SC3000/script/requirements.txt
- run: |
CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | grep '^SC3000/.*\.\(bas\|basic\)$')
for FILE in $CHANGED_FILES; do
python SC3000/script/basicToWav.py "$FILE"
done
CHANGED_FILES=`git diff --name-only ${{ github.event.before }} ${{ github.event.after }}` python script/convert.py
git config user.name "bot"
git config user.email "github-actions@users.noreply.github.com"
git add .
git commit -m "Add converted file"
git push origin master
33 changes: 33 additions & 0 deletions SC3000/script/basicToWav.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import basicparse
import wavparse
import argparse
import pathlib
import section
import bitparse


def canonicalName(fname):
stem = pathlib.Path(fname).stem.upper()
return ''.join(char for char in stem if char.isalnum()).ljust(16)[:16]


if __name__ == "__main__":
remrate = 44100
parser = argparse.ArgumentParser()
parser.add_argument("infile")
parser.add_argument("outfile", nargs="?", default=None)
parser.add_argument("--program_name")
args = parser.parse_args()

opt = {k: v for k, v in vars(args).items() if v is not None}

if "program_name" not in opt:
opt["program_name"] = canonicalName(args.infile)

if args.outfile is None:
args.outfile = pathlib.Path(args.infile).with_suffix(".wav")

d = basicparse.readBasic(args.infile, opt)
section.parseBytesSections(d["sections"], True, False)
d["signal"] = bitparse.genSignal(d, remrate, True)
wavparse.writeWav(str(args.outfile), d, opt)
61 changes: 61 additions & 0 deletions SC3000/script/basicparse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from sc3000decoder import read_bas_as_hex_string, decode_hex_string, print_decoded, save_decoded_to, escape_char
from sc3000encoder import encode_script_string
import binascii
from basparse import getBasicSections

from section import KeyCode
from util import removeExtension
from pathlib import Path


def writeBasic(filename, d, opt): # already parsed
fname = removeExtension(filename)
codeChunks = []
for s in d["sections"]:
if s["type"] == "bytes" and KeyCode.code[s["keycode"]] == KeyCode.BasicData:
codeChunks.append(s["Program"])

ext = Path(filename).suffix
if len(codeChunks) == 1:
decode(fname + ext, codeChunks[0])
else:
for idx, c in enumerate(codeChunks):
decode(f"{fname}{idx}{ext}", c)


def writeFilename(filename, d, opt):
fname = removeExtension(filename)
names = []
for s in d["sections"]:
if s["type"] == "bytes" and KeyCode.code[s["keycode"]] == KeyCode.BasicHeader:
names.append(s["Filename"])
if len(names) == 1:
open(fname + ".filename", "w").write(names[0])
else:
for idx, na in enumerate(names):
open(f"{fname}{idx}.filename", "w").write(na)


def readBasic(filename, opts):
if "basic_raw_encoding" in opts:
raw = open(filename, "rb").read()
# print("==RAW==", raw)
script_string = "".join([escape_char(ch, toPass=[0x0A])
for ch in raw if ch not in [0x01, 0x0D]])
else:
script_string = open(filename).read()
suppress_error = False
encoded = encode_script_string(script_string, suppress_error)
result = ""
for line in encoded:
result += line["encoded"]
resultb = list(binascii.a2b_hex(result))
if "\\bin" not in encoded[-1]["raw"]:
resultb += [0x00, 0x00]
return getBasicSections(resultb, opts)


def decode(fname, chunk):
hex_string = bytes(chunk).hex().upper()
decoded = decode_hex_string(hex_string, suppress_error=True)
save_decoded_to(fname, decoded)
115 changes: 115 additions & 0 deletions SC3000/script/basparse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from section import KeyCode
from util import removeExtension, beint
import numpy as np


def writeFile(filename, l):
with open(filename, "wb") as f:
f.write(bytes(l))


def writeBas(filename, d, opts): # already parsed
codeChunks = []
for s in d["sections"]:
if s["type"] == "bytes" and KeyCode.code[s["keycode"]] == KeyCode.BasicData:
codeChunks.append(s["Program"])

if len(codeChunks)==1:
writeFile(filename,codeChunks[0])
else:
fname=removeExtension(filename)
for idx,c in enumerate(codeChunks):
writeFile(f"{fname}{idx}.bas",c)



def writeBin(filename,d,opt): #already parsed
fname=removeExtension(filename)
codeChunks=[]
idx=0
for s in d["sections"]:
if s["type"]=="bytes":
writeFile(f"{fname}{idx}.bin",s["bytes"])
idx+=1


def parity(x):
return 0x100-(0xFF&int(np.sum(x)))

def getBasicSections(program,opts):
d={
"sections":[]
}
sections=d["sections"]

if "program_type" in opts:
ptype=opts["program_type"]
if ptype=="machine":
isMachine=True
elif ptype=="basic":
isMachine=False
else:
raise Exception("Unknown code type "+ctype+" options are either 'basic' or 'machine'")
else:
isMachine=False


if isMachine:
if "program_start_addr" in opts:
startAddr=int(opts["program_start_addr"],16)
else:
print("\nWarning: start address not specified\n")
startAddr=0xC000


if "program_name" not in opts:
print("\nWarning: program name not specified\n")
else:
if isMachine:
header=[KeyCode.MachineHeader]
else:
header=[KeyCode.BasicHeader]
filename=opts["program_name"].ljust(16)[:16]
filename=[ord(c) for c in filename]
programLength=beint(len(program),2)

headerPayload=filename+programLength
if isMachine:
headerPayload+=beint(startAddr,2)
p=parity(headerPayload)
sections.append({
"t":-1,
"type":"bytes",
"bytes": header+headerPayload+[p,0x00,0x00]})

if isMachine:
header=[KeyCode.MachineData]
else:
header=[KeyCode.BasicData]
p=parity(program)

sections.append({
"t":-1,
"type":"bytes",
"bytes":header+program+[p,0x00,0x00]})
return d



def readBas(filename,opts):
start,end=0,None
d=open(filename,"rb").read()
if "program_from" in opts:
start=int(opts["program_from"],16)
if "program_to" in opts:
end=int(opts["program_to"],16)
if "program_size" in opts:
end=start+int(opts["program_size"],16)
if "program_rstrip" in opts:
endchar=int(opts["program_rstrip"],16)
d=d.rstrip(bytes([endchar]))
if end is None:
end=len(d)
print(f"Reading {filename} range {start:04x} : {end:04x}")
program=list(d[start:end])
return getBasicSections(program,opts)
114 changes: 114 additions & 0 deletions SC3000/script/bitparse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import numpy as np
from section import SectionList, KeyCode


def maybeByte(bs):
if len(bs) < 11:
return None
for b in bs[:11]:
if b not in ["0", "1"]:
return None
if bs[0] != "0" or bs[9] != "1" or bs[10] != "1":
return None
n = 0
for i in range(8):
if bs[1+i] == "1":
n += (1 << i)
return n


def readBit(filename, opts):
ignore_section_errors = "ignore_section_errors" in opts
return getSections(open(filename).read(), ignore_section_errors)


def getSections(data, ignore_section_errors=False):
sl = SectionList()
offset = 0
while offset < len(data):
n = maybeByte(data[offset:offset+11])
if n is not None:
sl.pushByte(offset, n)
offset += 11
elif data[offset] == "1":
sl.pushHeader(offset)
offset += 1
elif data[offset] == " ":
sl.pushLevel(offset, 0, 1)
offset += 1
else:
msg = f"Invalid char in bit file {data[offset]} at position {offset} out of {len(data)}, {data[offset:offset+11]}"
if ignore_section_errors:
print(msg)
offset += 1
else:
raise Exception(msg)
sl.finalize()
d = {"bitrate": 1200, "sections": sl.sections}
return d


def encodeByte(b):
return "0" + "".join(["1" if ((b >> i) & 0x01) == 1 else "0" for i in range(8)])+"11"


def encodeBytes(x):
return "".join([encodeByte(b) for b in x])


def toBitRaw(d):
data = ""
for s in d["sections"]:
stype = s["type"]
if stype == "level":
data += " "*int(np.round(s["length"]/d["bitrate"]*1200))
elif stype == "header":
data += "1"*s["count"]
elif stype == "bytes":
data += encodeBytes(s["bytes"])

return data


def toBitRemaster(d, fastStart=True):
data = ""
for s in d["sections"]:
stype = s["type"]
# print("section", s)
if stype == "bytes":
code = KeyCode.code[s["keycode"]]
if fastStart:
n = 0
elif code == KeyCode.BasicHeader or code == KeyCode.MachineHeader:
n = 10*1200
elif code == KeyCode.BasicData or code == KeyCode.MachineData:
n = 1*1200
data += " "*n+"1"*3600+encodeBytes(s["bytes"])
fastStart = False
return data


def genSignal(d, sampleRate, sectionRemaster):
val = 1
Space = np.zeros(int(sampleRate/1200))
Zero = np.hstack([v*np.ones(int(sampleRate/1200/2)) for v in [val, -val]])
One = np.hstack([v*np.ones(int(sampleRate/1200/4))
for v in [val, -val, val, -val]])
conv = {' ': Space, '0': Zero, '1': One}

if sectionRemaster:
bits = toBitRemaster(d, True)
else:
bits = toBitRaw(d)
sig = np.hstack([conv[b] for b in bits])
d["bitrate"] = sampleRate
return sig


def writeBit(filename, d, opt):
remaster = opt["remaster"] == "section"
with open(filename, "w") as f:
if remaster:
f.write(toBitRemaster(d))
else:
f.write(toBitRaw(d))
Loading

0 comments on commit 6fbbcc4

Please sign in to comment.