Browse Source

First available version. 2021-08-09 10:45 ZRY.
Only MZ-700 is supported now.
Support for other machine will be add soon.

zry 2 years ago
parent
commit
38116b6329
10 changed files with 548 additions and 0 deletions
  1. 61 0
      ascii_convert.go
  2. 9 0
      file_attr.go
  3. 5 0
      go.mod
  4. 2 0
      go.sum
  5. 46 0
      machine.go
  6. 71 0
      mz700_ascii_convert_table.go
  7. 13 0
      standard_file.go
  8. 86 0
      standard_gen.go
  9. 24 0
      utils.go
  10. 231 0
      wavegen.go

+ 61 - 0
ascii_convert.go

@@ -0,0 +1,61 @@
+package mz700_tape_wav_gen
+
+import (
+	"bytes"
+)
+
+func ASCII2MZ700(asciiStr string) []byte {
+	sb := []byte(asciiStr)
+	db := make([]byte, 0, len(sb))
+	bb := bytes.NewBuffer(db)
+	for _, b := range sb {
+		if b >= 0x20 && b <= 0x7e {
+			cvb := ASCII_TO_MZ700_TABLE[b]
+			if cvb == 0 {
+				bb.WriteByte(b)
+			} else {
+				bb.WriteByte(cvb)
+			}
+		}
+	}
+	return bb.Bytes()
+}
+
+func ConvertFilename(ascii string) []byte {
+	cb := ASCII2MZ700(ascii)
+	l := len(cb)
+	if l > 16 {
+		cb = cb[:16]
+		l = 16
+	}
+	padl := 17 - l
+	for i := 0; i < padl; i++ {
+		cb = append(cb, 0x0D)
+	}
+	return cb
+}
+
+func ConvertStringCommnet(comment string) []byte {
+	cb := ASCII2MZ700(comment)
+	l := len(cb)
+	if l > 103 {
+		cb = cb[:103]
+		l = 103
+	}
+	cb = append(cb, 0x0D)
+	l++
+	padl := 104 - l
+	for i := 0; i < padl; i++ {
+		cb = append(cb, 0x00)
+	}
+	return cb
+}
+
+func PaddingRawComment(cb []byte) []byte {
+	l := len(cb)
+	padl := 104 - l
+	for i := 0; i < padl; i++ {
+		cb = append(cb, 0x00)
+	}
+	return cb
+}

+ 9 - 0
file_attr.go

@@ -0,0 +1,9 @@
+package mz700_tape_wav_gen
+
+const (
+	FILE_ATTRIBUTE_MACHINE_CODE byte = 0x01
+	FILE_ATTRIBUTE_MZ80_BASIC   byte = 0x02
+	FILE_ATTRIBUTE_MZ80_DATA    byte = 0x03
+	FILE_ATTRIBUTE_MZ700_DATA   byte = 0x04
+	FILE_ATTRIBUTE_MZ700_BASIC  byte = 0x05
+)

+ 5 - 0
go.mod

@@ -0,0 +1,5 @@
+module mz700-tape-wav-gen
+
+go 1.16
+
+require github.com/zenwerk/go-wave v0.0.0-20190102022600-1be84bfef50c

+ 2 - 0
go.sum

@@ -0,0 +1,2 @@
+github.com/zenwerk/go-wave v0.0.0-20190102022600-1be84bfef50c h1:DgZMI1Q1YuuaY69YbTu3arBk0qlnKWzv4YM2GmNC5tk=
+github.com/zenwerk/go-wave v0.0.0-20190102022600-1be84bfef50c/go.mod h1:qCAwkQ567FyudNBeV5wgzDEUsdEEcPgciHPmPlnIgXA=

+ 46 - 0
machine.go

@@ -0,0 +1,46 @@
+package mz700_tape_wav_gen
+
+type MachineType uint8
+
+const (
+	MACHINE_MZ_700 MachineType = iota
+	MACHINE_MZ_80K MachineType = iota
+	MACHINE_MZ_80A MachineType = iota
+	MACHINE_MZ_80B MachineType = iota
+	MACHINE_MZ_800 MachineType = iota
+)
+
+type machineRelatedConfig struct {
+	Unsupported           bool
+	ShortPulseSampleHigh  int
+	ShortPulseSampleTotal int
+	LongPulseSampleHigh   int
+	LongPulseSampleTotal  int
+	LongGapSize           int
+	ShortGapSize          int
+	ShortTapemarkSize     int
+	LongTapemarkSize      int
+	TapemarkEndLongPulse  int
+}
+
+func getMachineRelatedConfigByType(mt MachineType) machineRelatedConfig {
+	switch mt {
+	case MACHINE_MZ_700:
+		return machineRelatedConfig{
+			Unsupported:           false,
+			ShortPulseSampleHigh:  10,
+			ShortPulseSampleTotal: 21,
+			LongPulseSampleHigh:   20,
+			LongPulseSampleTotal:  42,
+			LongGapSize:           22000,
+			ShortGapSize:          11000,
+			ShortTapemarkSize:     20,
+			LongTapemarkSize:      40,
+			TapemarkEndLongPulse:  1,
+		}
+	default:
+		return machineRelatedConfig{
+			Unsupported: true,
+		}
+	}
+}

+ 71 - 0
mz700_ascii_convert_table.go

@@ -0,0 +1,71 @@
+package mz700_tape_wav_gen
+
+var MZ700_TO_ASCII_TABLE = [256]byte{
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x00,
+	0x5F, 0x00, 0x65, 0x60, 0x7E, 0x00, 0x74, 0x67,
+	0x68, 0x00, 0x62, 0x78, 0x64, 0x72, 0x70, 0x63,
+	0x71, 0x61, 0x7A, 0x77, 0x73, 0x75, 0x69, 0x00,
+	0x00, 0x6B, 0x66, 0x76, 0x00, 0x00, 0x00, 0x6A,
+	0x6E, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x6F,
+	0x6C, 0x00, 0x00, 0x00, 0x00, 0x79, 0x7B, 0x00,
+	0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+}
+
+var ASCII_TO_MZ700_TABLE = [256]byte{
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x90,
+	0x93, 0xA1, 0x9A, 0x9F, 0x9C, 0x92, 0xAA, 0x97,
+	0x98, 0xA6, 0xAF, 0xA9, 0xB8, 0xB3, 0xB0, 0xB7,
+	0x9E, 0xA0, 0x9D, 0xA4, 0x96, 0xA5, 0xAB, 0xA3,
+	0x9B, 0xBD, 0xA2, 0xBE, 0xC0, 0x80, 0x94, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+}

+ 13 - 0
standard_file.go

@@ -0,0 +1,13 @@
+package mz700_tape_wav_gen
+
+type StandardTapeFile struct {
+	FileAttribute    byte
+	Filename         string
+	LoadAddress      uint16
+	ExecuteAfterLoad bool
+	ExecuteAddress   uint16
+	isStringComment  bool
+	StringComment    string
+	BinaryComment    []byte
+	File             []byte
+}

+ 86 - 0
standard_gen.go

@@ -0,0 +1,86 @@
+package mz700_tape_wav_gen
+
+import (
+	"fmt"
+	"io"
+	"math"
+)
+
+type StandardTapeWaveGenerator struct {
+	InnerWaveGen *MZ700TapeWavGen
+	LastError    error
+}
+
+func NewStandardTapeWaveGenerator(f io.WriteCloser, machineType MachineType, invertPolarity bool) (*StandardTapeWaveGenerator, error) {
+	iwg, err := NewMZ700TapeWavGen(f, machineType, invertPolarity)
+	if err != nil {
+		return nil, err
+	}
+	g := &StandardTapeWaveGenerator{
+		InnerWaveGen: iwg,
+	}
+	return g, nil
+}
+
+func (g *StandardTapeWaveGenerator) WriteStandardFile(f *StandardTapeFile) error {
+	g.InnerWaveGen.WriteGAP(true)
+	g.InnerWaveGen.WriteTapeMark(true)
+	g.InnerWaveGen.WriteLongPulse()
+	g.writeTapeHeader(f)
+	g.InnerWaveGen.Write256S()
+	g.writeTapeHeader(f)
+	g.InnerWaveGen.WriteGAP(false)
+	g.InnerWaveGen.WriteTapeMark(false)
+	g.InnerWaveGen.WriteLongPulse()
+	fchksum := GetByteSliceChecksum(f.File)
+	_, _ = g.InnerWaveGen.Write(f.File)
+	g.InnerWaveGen.WriteUint16BigEndian(fchksum)
+	g.InnerWaveGen.WriteLongPulse()
+	g.InnerWaveGen.Write256S()
+	_, _ = g.InnerWaveGen.Write(f.File)
+	g.InnerWaveGen.WriteUint16BigEndian(fchksum)
+	g.InnerWaveGen.WriteLongPulse()
+	return g.LastError
+}
+
+func (g *StandardTapeWaveGenerator) writeTapeHeader(f *StandardTapeFile) {
+	var hdr_chksum uint16
+	hdr_chksum = 0
+	hdr_chksum += GetSingleByteChecksum(f.FileAttribute)
+	g.InnerWaveGen.WriteFileAttribute(f.FileAttribute)
+	fdata := ConvertFilename(f.Filename)
+	hdr_chksum += g.InnerWaveGen.WriteFilenameRaw(fdata)
+	filelenRaw := len(f.File)
+	if filelenRaw > math.MaxUint16 {
+		g.LastError = fmt.Errorf("file too long (%d/%d bytes)", filelenRaw, math.MaxUint16)
+	}
+	filelen := uint16(filelenRaw)
+	hdr_chksum += g.InnerWaveGen.WriteUint16LittleEndian(filelen)
+	hdr_chksum += g.InnerWaveGen.WriteUint16LittleEndian(f.LoadAddress)
+	if f.ExecuteAfterLoad {
+		hdr_chksum += g.InnerWaveGen.WriteUint16LittleEndian(f.ExecuteAddress)
+	} else {
+		hdr_chksum += g.InnerWaveGen.WriteUint16LittleEndian(0x00AD)
+	}
+	if f.isStringComment {
+		ctw := ConvertStringCommnet(f.StringComment)
+		hdr_chksum += g.InnerWaveGen.WriteCommentRaw(ctw)
+	} else {
+		hdr_chksum += g.InnerWaveGen.WriteCommentRaw(f.BinaryComment)
+	}
+	g.InnerWaveGen.WriteUint16BigEndian(hdr_chksum)
+	g.InnerWaveGen.WriteLongPulse()
+}
+
+func (g *StandardTapeWaveGenerator) Close() error {
+	err := g.InnerWaveGen.Close()
+	if err != nil {
+		return err
+	} else {
+		return g.LastError
+	}
+}
+
+func (g *StandardTapeWaveGenerator) GetLastError() error {
+	return g.LastError
+}

+ 24 - 0
utils.go

@@ -0,0 +1,24 @@
+package mz700_tape_wav_gen
+
+func GetSingleByteChecksum(b byte) uint16 {
+	var num byte = 0
+	/*
+		v := b
+		for v > 0 {
+			v &= v - 1
+			num++
+		}
+	*/
+	for i := 0; i < 8; i++ {
+		num += (b >> i) & 0x01
+	}
+	return uint16(num)
+}
+
+func GetByteSliceChecksum(bs []byte) uint16 {
+	var chksum uint16 = 0
+	for _, sb := range bs {
+		chksum += GetSingleByteChecksum(sb)
+	}
+	return chksum
+}

+ 231 - 0
wavegen.go

@@ -0,0 +1,231 @@
+package mz700_tape_wav_gen
+
+import (
+	"encoding/binary"
+	"fmt"
+	"github.com/zenwerk/go-wave"
+	"io"
+	"math"
+)
+
+type MZ700TapeWavGen struct {
+	wwr            *wave.Writer
+	longPulseData  []int16
+	shortPulseData []int16
+	shortGapData   []int16
+	longGapData    []int16
+	lastError      error
+	mc             machineRelatedConfig
+	hlv            int16
+	llv            int16
+}
+
+func (g *MZ700TapeWavGen) Write(p []byte) (n int, err error) {
+	l := len(p)
+	for _, b := range p {
+		g.WriteDataByte(b)
+	}
+	if g.lastError != nil {
+		return 0, g.lastError
+	}
+	return l, nil
+}
+
+func NewMZ700TapeWavGen(f io.WriteCloser, machineType MachineType, invertPolarity bool) (*MZ700TapeWavGen, error) {
+	g := &MZ700TapeWavGen{
+		lastError: nil,
+		mc:        getMachineRelatedConfigByType(machineType),
+	}
+	if g.mc.Unsupported {
+		return nil, fmt.Errorf("unsupported machine type")
+	}
+	if invertPolarity {
+		g.llv = math.MaxInt16 / 2
+		g.hlv = math.MinInt16 / 2
+	} else {
+		g.llv = math.MinInt16 / 2
+		g.hlv = math.MaxInt16 / 2
+	}
+	wwrp := wave.WriterParam{
+		Out:           f,
+		Channel:       1,
+		SampleRate:    44100,
+		BitsPerSample: 16,
+	}
+	var err error
+	g.wwr, err = wave.NewWriter(wwrp)
+	if err != nil {
+		return nil, err
+	}
+	g.initPulseDataTemplate()
+	return g, nil
+}
+
+func (g *MZ700TapeWavGen) initPulseDataTemplate() {
+	g.longPulseData = make([]int16, g.mc.LongPulseSampleTotal)
+	g.shortPulseData = make([]int16, g.mc.ShortPulseSampleTotal)
+	for i := 0; i < g.mc.ShortPulseSampleHigh; i++ {
+		g.shortPulseData[i] = g.hlv
+	}
+	for i := g.mc.ShortPulseSampleHigh; i < g.mc.ShortPulseSampleTotal; i++ {
+		g.shortPulseData[i] = g.llv
+	}
+	for i := 0; i < g.mc.LongPulseSampleHigh; i++ {
+		g.longPulseData[i] = g.hlv
+	}
+	for i := g.mc.LongPulseSampleHigh; i < g.mc.LongPulseSampleTotal; i++ {
+		g.longPulseData[i] = g.llv
+	}
+	g.shortGapData = make([]int16, g.mc.ShortGapSize*g.mc.ShortPulseSampleTotal)
+	g.longGapData = make([]int16, g.mc.LongGapSize*g.mc.ShortPulseSampleTotal)
+	for j := 0; j < g.mc.ShortGapSize; j++ {
+		for i := 0; i < g.mc.ShortPulseSampleHigh; i++ {
+			g.shortGapData[j*g.mc.ShortPulseSampleTotal+i] = g.hlv
+		}
+		for i := g.mc.ShortPulseSampleHigh; i < g.mc.ShortPulseSampleTotal; i++ {
+			g.shortGapData[j*g.mc.ShortPulseSampleTotal+i] = g.llv
+		}
+	}
+	for j := 0; j < g.mc.LongGapSize; j++ {
+		for i := 0; i < g.mc.ShortPulseSampleHigh; i++ {
+			g.longGapData[j*g.mc.ShortPulseSampleTotal+i] = g.hlv
+		}
+		for i := g.mc.ShortPulseSampleHigh; i < g.mc.ShortPulseSampleTotal; i++ {
+			g.longGapData[j*g.mc.ShortPulseSampleTotal+i] = g.llv
+		}
+	}
+}
+
+func (g *MZ700TapeWavGen) GetLastError() error {
+	return g.lastError
+}
+
+func (g *MZ700TapeWavGen) WriteLongPulse() {
+	_, err := g.wwr.WriteSample16(g.longPulseData)
+	if err != nil {
+		g.lastError = err
+	}
+}
+
+func (g *MZ700TapeWavGen) WriteShortPulse() {
+	_, err := g.wwr.WriteSample16(g.shortPulseData)
+	if err != nil {
+		g.lastError = err
+	}
+}
+
+func (g *MZ700TapeWavGen) WriteBit(b bool) {
+	if b {
+		g.WriteLongPulse()
+	} else {
+		g.WriteShortPulse()
+	}
+}
+
+func (g *MZ700TapeWavGen) WriteDataByte(b byte) {
+	var mask uint8
+	for mask = 0x80; mask >= 1; mask = mask >> 1 {
+		if b&mask > 0 {
+			g.WriteLongPulse()
+		} else {
+			g.WriteShortPulse()
+		}
+	}
+	g.WriteLongPulse()
+}
+
+func (g *MZ700TapeWavGen) WriteGAP(isLong bool) {
+	var err error
+	if isLong {
+		_, err = g.wwr.WriteSample16(g.longGapData)
+	} else {
+		_, err = g.wwr.WriteSample16(g.shortGapData)
+	}
+	if err != nil {
+		g.lastError = err
+	}
+}
+
+func (g *MZ700TapeWavGen) WriteTapeMark(isLong bool) {
+	var tl int
+	if isLong {
+		tl = g.mc.LongTapemarkSize
+	} else {
+		tl = g.mc.ShortTapemarkSize
+	}
+	for i := 0; i < tl; i++ {
+		g.WriteLongPulse()
+	}
+	for i := 0; i < tl; i++ {
+		g.WriteShortPulse()
+	}
+	for i := 0; i < g.mc.TapemarkEndLongPulse; i++ {
+		g.WriteLongPulse()
+	}
+}
+
+func (g *MZ700TapeWavGen) Write256S() {
+	for i := 0; i < 256; i++ {
+		g.WriteShortPulse()
+	}
+}
+
+func (g *MZ700TapeWavGen) WriteFileAttribute(attr byte) {
+	g.WriteDataByte(attr)
+}
+
+// Return checksum
+func (g *MZ700TapeWavGen) WriteFilenameRaw(fnraw []byte) uint16 {
+	chksum := GetByteSliceChecksum(fnraw)
+	_, g.lastError = g.Write(fnraw)
+	return chksum
+}
+
+// Return checksum. Sizes, addresses are in little endian.
+func (g *MZ700TapeWavGen) WriteUint16LittleEndian(n uint16) uint16 {
+	b := make([]byte, 2)
+	binary.LittleEndian.PutUint16(b, n)
+	var chksum uint16 = 0
+	chksum += GetSingleByteChecksum(b[0])
+	chksum += GetSingleByteChecksum(b[1])
+	_, g.lastError = g.Write(b)
+	return chksum
+}
+
+// Return checksum. Checksums are in big endian.
+func (g *MZ700TapeWavGen) WriteUint16BigEndian(n uint16) uint16 {
+	b := make([]byte, 2)
+	binary.BigEndian.PutUint16(b, n)
+	var chksum uint16 = 0
+	chksum += GetSingleByteChecksum(b[0])
+	chksum += GetSingleByteChecksum(b[1])
+	_, g.lastError = g.Write(b)
+	return chksum
+}
+
+// Return checksum
+func (g *MZ700TapeWavGen) WriteCommentRaw(rb []byte) uint16 {
+	l := len(rb)
+	if l > 104 {
+		g.lastError = fmt.Errorf("comment too long")
+		return 0
+	}
+	var wb []byte
+	if l == 104 {
+		wb = rb
+	} else {
+		wb = PaddingRawComment(rb)
+	}
+	chksum := GetByteSliceChecksum(wb)
+	_, g.lastError = g.Write(wb)
+	return chksum
+}
+
+func (g *MZ700TapeWavGen) Close() error {
+	err := g.wwr.Close()
+	if err != nil {
+		return err
+	} else {
+		return g.lastError
+	}
+}