Skip to content

Commit

Permalink
Merge pull request #2 from fabiodl/master
Browse files Browse the repository at this point in the history
Automatic generation if wav files
  • Loading branch information
bodhi-baum authored Jan 31, 2024
2 parents 7b17bd2 + fea2560 commit 49757d3
Show file tree
Hide file tree
Showing 19 changed files with 1,202 additions and 3 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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\)$' || true)
if [ -n "$CHANGED_FILES" ]; then
echo "Changed Files: "
echo "$CHANGED_FILES"
for FILE in $CHANGED_FILES; do
python SC3000/script/basicToWav.py "$FILE"
done
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
else
echo "Nothing to do"
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SC3000/script/__pycache__/

4 changes: 2 additions & 2 deletions SC3000/Flying_Ufo.bas
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
10 REM **** UFO GAME *****
20 SCREEN 2,2:MAG 1
30 GOSUB 130
IF MN=0 THEN 530
40 IF MN=0 THEN 530
50 K$=INKEY$
60 IF K$=CHR$(28) AND MH+16<215 THEN MA=MH+8:GOSUB 610
70 IF K$=CHR$(29) AND MH-1>43 THEN MH=MH-8:GOSUB 610
Expand Down Expand Up @@ -66,4 +66,4 @@ IF MN=0 THEN 530
660 RETURN
670 REM UFO
680 SPRITE 1,(FX,FY),4,0
690 RETURN
690 RETURN
Binary file added SC3000/Flying_Ufo.wav
Binary file not shown.
2 changes: 1 addition & 1 deletion SC3000/face.bas
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
70 REM
80 SCREEN2,1:COLOR,1,,1:CLS
90 FORX=1TO45:READ A,B,C:LINE(A,B)-(C,B),15:NEXT
100DATA100,101,102,100,102,104,101,103,104,108,103,104,108,103,114,108,103,114,100,104,115,99,105,116,99,106,117,97,107,117,97,108,116,98,109,100
100 DATA100,101,102,100,102,104,101,103,104,108,103,104,108,103,114,108,103,114,100,104,115,99,105,116,99,106,117,97,107,117,97,108,116,98,109,100
110 DATA103,109,107,104,110,106,111,110,112,105,111,106,105,112,106,109,112,110,115,112,118,99,113,102,104,113,106,109,113,111,113,113,117
120 DATA 98,114,106,108,114,117,97,115,117,97,116,117,98,117,117,98,118,116,98,119,106,109,119,116,99,120,104,109,120,104,109,120,116
130 DATA 99,121,104,110,121,115,99,122,105,108,122,116,99,123,105,112,123,105,112,123,116,100,124,107,109,124,115,100,125,105,109,125,114,102,126,112,103,127,112,104,128,110,105,129,109
Expand Down
Binary file added SC3000/face.wav
Binary file not shown.
1 change: 1 addition & 0 deletions SC3000/hello.bas
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
10 PRINT "HELLO"
Binary file added SC3000/hello.wav
Binary file not shown.
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 49757d3

Please sign in to comment.