Browse Source

Release Version v0.1.0. 2021-08-17 16:49

zry 2 years ago
parent
commit
144427479f

+ 0 - 1
.gitignore

@@ -20,7 +20,6 @@ _cgo_export.*
 
 _testmain.go
 
-*.exe
 *.test
 *.prof
 

+ 23 - 0
.idea/$CACHE_FILE$

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectInspectionProfilesVisibleTreeState">
+    <entry key="Project Default">
+      <profile-state>
+        <expanded-state>
+          <State />
+          <State>
+            <id>General</id>
+          </State>
+          <State>
+            <id>XPath</id>
+          </State>
+        </expanded-state>
+        <selected-state>
+          <State>
+            <id>CSS</id>
+          </State>
+        </selected-state>
+      </profile-state>
+    </entry>
+  </component>
+</project>

+ 2 - 0
.idea/.gitignore

@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml

+ 8 - 0
.idea/binary2sharp-mz-700-tape-wav.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 5 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,5 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="PROJECT_PROFILE" />
+  </settings>
+</component>

+ 86 - 0
.idea/markdown-navigator.xml

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MarkdownProjectSettings" wasCopied="true">
+    <PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="NONE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="false" showSelectionInPreview="true" openRemoteLinks="true" replaceUnicodeEmoji="false" lastLayoutSetsDefault="false">
+      <PanelProvider>
+        <provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.panel" providerName="Default - Swing" />
+      </PanelProvider>
+    </PreviewSettings>
+    <ParserSettings gitHubSyntaxChange="false" emojiShortcuts="1" emojiImages="0">
+      <PegdownExtensions>
+        <option name="ABBREVIATIONS" value="false" />
+        <option name="ANCHORLINKS" value="true" />
+        <option name="ASIDE" value="false" />
+        <option name="ATXHEADERSPACE" value="true" />
+        <option name="AUTOLINKS" value="true" />
+        <option name="DEFINITIONS" value="false" />
+        <option name="DEFINITION_BREAK_DOUBLE_BLANK_LINE" value="false" />
+        <option name="FENCED_CODE_BLOCKS" value="true" />
+        <option name="FOOTNOTES" value="false" />
+        <option name="HARDWRAPS" value="false" />
+        <option name="HTML_DEEP_PARSER" value="false" />
+        <option name="INSERTED" value="false" />
+        <option name="QUOTES" value="false" />
+        <option name="RELAXEDHRULES" value="true" />
+        <option name="SMARTS" value="false" />
+        <option name="STRIKETHROUGH" value="true" />
+        <option name="SUBSCRIPT" value="false" />
+        <option name="SUPERSCRIPT" value="false" />
+        <option name="SUPPRESS_HTML_BLOCKS" value="false" />
+        <option name="SUPPRESS_INLINE_HTML" value="false" />
+        <option name="TABLES" value="true" />
+        <option name="TASKLISTITEMS" value="true" />
+        <option name="TOC" value="false" />
+        <option name="WIKILINKS" value="true" />
+      </PegdownExtensions>
+      <ParserOptions>
+        <option name="ADMONITION_EXT" value="false" />
+        <option name="ATTRIBUTES_EXT" value="false" />
+        <option name="COMMONMARK_LISTS" value="true" />
+        <option name="DUMMY" value="false" />
+        <option name="EMOJI_SHORTCUTS" value="true" />
+        <option name="ENUMERATED_REFERENCES_EXT" value="false" />
+        <option name="FLEXMARK_FRONT_MATTER" value="false" />
+        <option name="GFM_LOOSE_BLANK_LINE_AFTER_ITEM_PARA" value="false" />
+        <option name="GFM_TABLE_RENDERING" value="true" />
+        <option name="GITBOOK_URL_ENCODING" value="false" />
+        <option name="GITHUB_LISTS" value="false" />
+        <option name="GITHUB_WIKI_LINKS" value="true" />
+        <option name="GITLAB_EXT" value="false" />
+        <option name="GITLAB_MATH_EXT" value="false" />
+        <option name="GITLAB_MERMAID_EXT" value="false" />
+        <option name="HEADER_ID_NON_ASCII_TO_LOWERCASE" value="false" />
+        <option name="HEADER_ID_NO_DUPED_DASHES" value="false" />
+        <option name="JEKYLL_FRONT_MATTER" value="false" />
+        <option name="MACROS_EXT" value="false" />
+        <option name="NO_TEXT_ATTRIBUTES" value="false" />
+        <option name="PARSE_HTML_ANCHOR_ID" value="false" />
+        <option name="PLANTUML_FENCED_CODE" value="false" />
+        <option name="PUML_FENCED_CODE" value="false" />
+        <option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
+      </ParserOptions>
+    </ParserSettings>
+    <HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" embedUrlContent="false" addPageHeader="true" embedImages="false" embedHttpImages="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" plantUmlConversion="0" mathConversion="-1">
+      <GeneratorProvider>
+        <provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.generator" providerName="Default Swing HTML Generator" />
+      </GeneratorProvider>
+      <headerTop />
+      <headerBottom />
+      <bodyTop />
+      <bodyBottom />
+    </HtmlSettings>
+    <CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="false" isCssTextEnabled="false" isDynamicPageWidth="true">
+      <StylesheetProvider>
+        <provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.css" providerName="Default Swing Stylesheet" />
+      </StylesheetProvider>
+      <ScriptProviders />
+      <cssText />
+      <cssUriHistory />
+    </CssSettings>
+    <AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
+    <HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imageUniquifyType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
+    <LinkMapSettings>
+      <textMaps />
+    </LinkMapSettings>
+  </component>
+</project>

+ 6 - 0
.idea/misc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="ES6" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/binary2sharp-mz-700-tape-wav.iml" filepath="$PROJECT_DIR$/.idea/binary2sharp-mz-700-tape-wav.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 32 - 0
Makefile

@@ -0,0 +1,32 @@
+GO_BIN = go
+DIST = bin
+BIN_NAME_PREFIX = bin2mz700wav
+PKG_NEED = ./cmd/*
+PKG_NAME = ./cmd
+
+build =set GOOS=$(1)&&set GOARCH=$(2)&&go build -o $(DIST)/$(BIN_NAME_PREFIX)-$(3) $(PKG_NAME)
+
+.PHONY : all
+.PHONY : win32
+.PHONY : win64
+.PHONY : linux386
+.PHONY : linuxa64
+
+all : win32 win64 linux386 linuxa64
+
+win32 : $(DIST)/$(BIN_NAME_PREFIX)-win32.exe
+win64 : $(DIST)/$(BIN_NAME_PREFIX)-win64.exe
+linux386 : $(DIST)/$(BIN_NAME_PREFIX)-linux-386
+linuxa64 : $(DIST)/$(BIN_NAME_PREFIX)-linux-amd64
+
+$(DIST)/$(BIN_NAME_PREFIX)-win32.exe : $(PKG_NEED)
+	$(call build,windows,386,win32.exe)
+
+$(DIST)/$(BIN_NAME_PREFIX)-win64.exe : $(PKG_NEED)
+	$(call build,windows,amd64,win64.exe)
+
+$(DIST)/$(BIN_NAME_PREFIX)-linux-386 : $(PKG_NEED)
+	$(call build,linux,386,linux-386)
+
+$(DIST)/$(BIN_NAME_PREFIX)-linux-amd64 : $(PKG_NEED)
+	$(call build,linux,amd64,linux-amd64)

+ 232 - 1
README.md

@@ -1,3 +1,234 @@
 # binary2sharp-mz-700-tape-wav
 
-A simple convert program for converting binary file into SHARP MZ-700 Tape wave file.
+A simple convert program for converting binary file into SHARP MZ-700 Tape wave file.
+
+## 中文文档
+
+### 版本说明 & ChangeLog
+
+目前是第一个发布的版本。
+
+本程序是按照
+[这篇文档](https://www.sharpmz.no/articles/the-mz-series/mz-80/mz-80-knowhow/mz-80-tape-data-structure/)
+制作的,在SHARP MZ-700上测试过,MZ-80A/B/K和MZ-800未测试(作者没有这些机器)。
+
+### 命令行说明
+
+命令行具有的子命令: `simple` `from-yaml` `example-yaml` `convert-ascii` `help`
+
+其中`simple`可简写作`s`, `from-yaml`可简写作`fy`,`example-yaml`可简写作`ey`,
+`convert-yaml`可简写作`ca`。 
+具体可以所以`help`命令或者`-h`全局标志查看程序内置的命令行帮助说明。
+
+用户接口设计逻辑:
+
+* `simple`子命令提供最基础的将一个二进制文件制作成SHARP MZ Tape的音频文件的功能。
+* 如果需要更复杂的高级功能,需要写一个yaml格式配置文件代替命令行参数输入,并使用`from-yaml`命令,
+程序将根据yaml文件进行磁带音频生成。
+* 使用`example-yaml`命令,可以提供和`simple`命令相同的命令行标志,生成一个样例yaml文件
+* 使用`convert-ascii`命令可以对文本文件在MZ-700 ASCII和标准ASCII之间进行转换。
+
+在本帮助文档中暂时不对每一条命令行参数进行详解,只是说明一下使用的思路和提供几个例子。
+具体的每个命令行参数请使用`-h`标志查询。`-h`中所说`See documentation`的部分请看本文档的附录。
+
+以下是使用说明。
+
+#### 概念解释
+
+**输出文件:** 输出的磁带WAV文件,可以录制成磁带通过SHARP MZ系列机器加载,
+或者通过其它方式替输入到机器中。
+
+**目标机器:** 生成的输出文件应用到的机器型号。支持的机器列表见[附录](#支持的机器列表)。
+
+**磁带文件:** 注意同`输出文件`区别。在SHARP MZ系列机器的磁带格式设计中,存在`文件`的概念。
+一盘磁带中可以录制一个或多个磁带文件,每个文件具有`HEADER`信息,
+在本文档中我们称之为`元数据`,`元数据`描述了磁带文件的文件属性、文件名等信息。
+一盘磁带可以有多个文件,但是,在MZ系列机器的`MONITOR`程序(例如MZ-700的`1Z-013A`)
+中执行命令`L`(或者`LOAD`)一次只会加载一个文件,磁带便会暂停。如果磁带中录制了多个文件,
+您可能需要自己编写一个LOADER程序将它们全部加载,然后将LOADER程序作为第一个文件,
+在MONITOR执行`L`命令后会加载和运行这个LOADER程序以加载所有文件。
+本文档中暂时不详细介绍具体实现的方式,如果后续有时间可能会增补这部分内容至本文档附录。
+
+**元数据:** 在上一条目`磁带文件`中已经提到。
+
+**文件类型:** 指定该磁带文件的类型。存储在元数据中。
+SHARP官方定义的文件类型列表见[附录](#SHARP官方规定的文件类型列表)。
+
+**磁带文件名:** 磁带文件的文件名。最长17字节。
+
+**加载地址:** 磁带文件被加载到的存储器地址。
+
+**执行地址:** 使用MONTIOR程序的`L`命令加载磁带文件,加载完成后,
+MONITOR程序将跳转到该地址。MONITOR程序一定会在加载完成后进行跳转,
+所以如果您想加载完磁带文件后回到MONITOR程序,
+需要跳转到MONITOR程序的Warm Start Subroutine(在MZ-700上是0x00AD)。
+如果想在加载完成后RESET,跳转到0x0000。
+
+**文件注释:** 在元数据内有一段104字节的注释区,可用于自定义用途。
+
+#### 基本的转换
+
+一个很经典的例子:
+
+```
+bin2mz700wav simple -o test.wav --mt mz-700 \
+    --fa 01 --fn TestTape --la 0x1200 --ea 0x1200 --in hello.bin -v
+```
+
+使用以上命令行可以将hello.bin转换成MZ-700磁带音频文件。
+使用磁带时,MZ-700将会将hello.bin的内容加载到0x1200,
+加载完成后从0x1200开始执行。
+
+* `simple`是子命令名,也可以简写作`s`。
+* `-o test.wav`指定输出文件为`test.wav`,若不指定,默认是`tape.wav`
+* `--mt mz-700`指定目标机器型号为`mz-700`,`--mt`标志是`--machine`的简写。
+见[附录](#支持的机器列表)。若不指定该标志,默认是`mz-700`。
+* `--fa 01`指定磁带文件的类型。使用十六进制表示,范围00~FF,无需前缀0x或后缀h。
+`--fa`是`--file-attrib`的简写。
+SHARP官方定义的文件类型列表见[附录](#SHARP官方规定的文件类型列表)。
+* `--fn TestTape`指定磁带文件名为`TestTape`,`--fn`是`--file-name`的简写。
+* `--la 0x1200`指定加载地址为0x1200。支持十进制、十六进制、八进制、二进制。
+不指定前后缀默认为十进制,指定前缀`0x`或后缀`H`则为十六进制,
+指定后缀`O`则为八进制,指定后缀`B`则为二进制。`--la`是`--load-addr`的简写。
+* `--ea 0x1200`指定执行地址为0x1200,执行地址概念详见[概念解释](#概念解释),
+若不指定该标志,则该地址被指定为MONITOR程序的Warm Start Subrountine。
+* `--in hello.bin`指定磁带文件的数据来自`hello.bin`。
+来自您计算机上的该文件将被转换到磁带文件中。
+* 使用`-v`标志,程序将在转换过程中在标准输出打印出详细的信息。
+`-v`是`--verbose`的简写。
+
+一个很简单的例子:
+
+```
+bin2mz700wav s --fa 01 --fn TestTape --la 0x1200 --in hello.bin
+```
+
+以上例子大部分标志参数都使用了默认值。
+
+#### 使用YAML配置文件
+
+如果您需要生成较复杂的输出文件,例如包含多个磁带文件的输出文件,
+您需要编写YAML配置文件,并使用`from-yaml`子命令从YAML配置生成输出文件。
+
+例子:
+
+```
+bin2mz700wav fy --yaml test.yaml
+```
+
+YAML的每个字段的说明,将在后续完善,本次发布的文档暂未完成这部分的编写。
+
+您可以使用`example-yaml`子命令生成一示例YAML文件,并编辑修改。
+
+`example-yaml`子命令的标志参数和`simple`子命令基本一致,
+但是需要指定`--yaml`标志指定输出YAML文件的文件名,以及,没有`--verbose`标志。
+
+例子:
+
+```
+bin2mz700wav ey --fa 01 --fn TestTape --la 0x1200 --in hello.bin --yaml test.yaml
+```
+
+以下是YAML文件结构的定义结构体代码,用作参考。
+
+```go
+package main
+
+type ConfigDef struct {
+	OutputWav string `yaml:"output_wav"`
+	MachineType string `yaml:"machine_type"`
+	InvertPolarity bool `yaml:"invert_polarity"`
+	TapeFiles []ConfigItemTapeFile `yaml:"tape_files"`
+}
+
+type ConfigItemTapeFile struct {
+	FileAttribute string `yaml:"file_attrib"`
+	FileName string `yaml:"filename"`
+	LoadAddress uint16 `yaml:"load_addr"`
+	ExecuteAfterLoad bool `yaml:"exec_after_load"`
+	ExecuteAddress uint16 `yaml:"exec_addr"`
+	UseRawCommentFromFile bool `yaml:"use_raw_comment_from_file"`
+	RawCommentFileName string `yaml:"comment_from_filename"`
+	StringComment string `yaml:"text_comment"`
+	DateSource string `yaml:"data_source"`
+	FileDataSource string `yaml:"binary_filename"`
+	ASCIITextFileDataSource string `yaml:"ascii_text_filename"`
+	IgnoreNonDisplayChar bool `yaml:"ignore_non_disp_char"`
+	IgnoreCR bool `yaml:"ignore_cr"`
+	RawData *[]byte `yaml:"raw_data"`
+}
+```
+#### 文本文件转换
+
+使用`convert-ascii`子命令可以对文本文件在MZ ASCII和标准ASCII之间互相转换。
+
+该子命令有`mz2ascii`(简写`m2a`)和`ascii2mz`(简写`a2m`)两个子命令。
+
+两个子命令共有的标志参数如下:
+
+* `--in`指定输入的文件
+* `--out`指定输出的文件
+* `--ignore-non-disp-char`标志,若指定该标志,则对于不显示的字符不作转换。
+
+例子:
+
+```
+bin2mz700wav ca a2m -i input.txt -o output.bin
+```
+
+```
+bin2mz700wav ca m2a -i input.bin -o output.txt --ign
+```
+
+关于忽略不显示字符和换行符处理详见[附录](#文本文件转换的规则)
+
+
+### 附录
+
+#### 支持的机器列表
+| 型号 | 在本程序中的命名 | 是否测试 |
+| :--:| :--: | :-- |
+| MZ-700 | mz-700 | 已测试 |
+| MZ-80A | mz-80a | 未测试 |
+| MZ-80B | mz-80b | 未测试 |
+| MZ-80K | mz-80k | 未测试 |
+| MZ-800 | mz-800 | 未测试 |
+
+#### SHARP官方规定的文件类型列表
+
+| 文件类型(十六进制) | 说明 |
+| :--:| :---- |
+| 01 | Machine code program file |
+| 02 | MZ-80 BASIC program file |
+| 03 | MZ-80 data file |
+| 04 | MZ-700 data file |
+| 05 | MZ-700 BASIC program file |
+
+#### 文本文件转换的规则
+
+在对MZ ASCII和标准ASCII互转的时候,本程序按照以下规则进行转换:
+
+* 对于标准ASCII的0x20至0x5D范围内的字符,MZ ASCII和标准ASCII完全一一对应,故不作转换直接输出。
+* 对于标准ASCII的0x5E至0x7E范围内的字符,在MZ ASCII内有相同的字符,
+但其对应的码和标准ASCII不一致,将进行对应的转换。
+* 对于换行符,请参见下方换行符说明
+* 对于其它字符,如果`--ignore-non-disp-char`标志未设置,则原样输出,若设置,则忽略。
+
+**换行符的处理:**
+
+在标准ASCII中,有两个和换行有关的字符:`CR`(`\r`,0x0D)和`LF`(`\n`,0x0A)。
+
+在MZ ASCII中,只有一种换行符:0x0D。
+
+从标准ASCII转换到MZ ASCII,若指定了`--ignore-return`标志,则`CR`会被忽略,
+`LF`会被转换到0x0D,适用于Windows等使用CR+LF换行的平台的文本文件。
+因为CR+LF最终会变成MZ ASCII的0x0D换行符。若不指定该标志,则将`LF`转换为0x0D,
+`CR`本身标准ASCII码就是0x0D,将继续保留原样。对于CR+LF换行的文件,
+这样转换后换行符将变成两个0x0D。
+
+从MZ ASCII转换到标准ASCII,若指定了`use-cr-lf`标志,则0x0D会被替换为CR+LF,否则替换为LF。
+
+# English Documentation
+
+**TODO: Complete English Documentation**
+

BIN
bin/bin2mz700wav-linux-386


BIN
bin/bin2mz700wav-linux-amd64


BIN
bin/bin2mz700wav-win32.exe


BIN
bin/bin2mz700wav-win64.exe


+ 76 - 0
cmd/ascii_conv.go

@@ -0,0 +1,76 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"git.swzry.com/zry/mz700-tape-wav-gen"
+	"github.com/urfave/cli/v2"
+	"io/ioutil"
+)
+
+func ConvertBetweenASCIIandMZ(context *cli.Context, asc2mz bool, rf bool) error {
+	fnin := context.String("in")
+	fnout := context.String("out")
+	ign := context.Bool("ignore-non-disp-char")
+	din, err := ioutil.ReadFile(fnin)
+	if err != nil {
+		return fmt.Errorf("failed read input file: %s", err.Error())
+	}
+	var dout []byte
+	if asc2mz {
+		dout = _ASCII2MZ700(din, ign, rf)
+	} else {
+		dout = _MZ7002ASCII(din, ign, rf)
+	}
+	err = ioutil.WriteFile(fnout, dout, 0666)
+	if err != nil {
+		return fmt.Errorf("failed write to output file: %s", err.Error())
+	}
+	return nil
+}
+
+func _ASCII2MZ700(in []byte, ign bool, ir bool) []byte {
+	db := make([]byte, 0, len(in))
+	bb := bytes.NewBuffer(db)
+	for _, b := range in {
+		if b == 0x0A {
+			bb.WriteByte(0x0D)
+			continue
+		}
+		if b == 0x0D && ir {
+			continue
+		}
+		if !ign || b >= 0x20 && b <= 0x7e {
+			cvb := mz700_tape_wav_gen.ASCII_TO_MZ700_TABLE[b]
+			if cvb == 0 {
+				bb.WriteByte(b)
+			} else {
+				bb.WriteByte(cvb)
+			}
+		}
+	}
+	return bb.Bytes()
+}
+
+func _MZ7002ASCII(in []byte, ign bool, ir bool) []byte {
+	db := make([]byte, 0, len(in))
+	bb := bytes.NewBuffer(db)
+	for _, b := range in {
+		if b == 0x0D {
+			if ir {
+				bb.WriteByte(0x0D)
+			}
+			bb.WriteByte(0x0A)
+			continue
+		}
+		if b >= 0x20 && b <= 0x5D {
+			bb.WriteByte(b)
+			continue
+		}
+		cvb := mz700_tape_wav_gen.ASCII_TO_MZ700_TABLE[b]
+		if !ign || (cvb >= 0x20 && cvb <= 0x7E) {
+			bb.WriteByte(cvb)
+		}
+	}
+	return bb.Bytes()
+}

+ 89 - 0
cmd/cli_entry.go

@@ -0,0 +1,89 @@
+package main
+
+import (
+	"fmt"
+	"github.com/urfave/cli/v2"
+	"gopkg.in/yaml.v2"
+	"io/ioutil"
+)
+
+func CmdSimple(context *cli.Context) error {
+	ydat, err := SimpleArgsToYAMLData(context)
+	if err != nil {
+		return fmt.Errorf("failed parse args: %s", err.Error())
+	}
+	verbose := context.Bool("verbose")
+	if verbose {
+		yb, err := yaml.Marshal(ydat)
+		if err != nil {
+			return fmt.Errorf("failed marshal yaml: %s", err.Error())
+		}
+		ys := string(yb)
+		fmt.Println("==== Processing Config ====")
+		fmt.Println(ys)
+	}
+	fmt.Println("==== Starting Converting ====")
+	err = ProcessingConvert(ydat, verbose)
+	if err != nil {
+		return fmt.Errorf("failed do converting: %s", err.Error())
+	}
+	return nil
+}
+
+func CmdYamlConfig(context *cli.Context) error {
+	ifn := context.String("in")
+	if ifn == "" {
+		return fmt.Errorf("no input file specified")
+	}
+	fdata, err := ioutil.ReadFile(ifn)
+	if err != nil {
+		return fmt.Errorf("failed read yaml file: %s", err.Error())
+	}
+	var ydat ConfigDef
+	err = yaml.Unmarshal(fdata, &ydat)
+	if err != nil {
+		return fmt.Errorf("failed parse yaml: %s", err.Error())
+	}
+	verbose := context.Bool("verbose")
+	fmt.Println("==== Starting Converting ====")
+	err = ProcessingConvert(&ydat, verbose)
+	if err != nil {
+		return fmt.Errorf("failed do converting: %s", err.Error())
+	}
+	return nil
+}
+
+func CmdExampleYaml(context *cli.Context) error {
+	ydat, err := SimpleArgsToYAMLData(context)
+	youtf := context.String("yaml")
+	if err != nil {
+		return fmt.Errorf("failed parse args: %s", err.Error())
+	}
+	yb, err := yaml.Marshal(ydat)
+	if err != nil {
+		return fmt.Errorf("failed marshal yaml: %s", err.Error())
+	}
+	verbose := context.Bool("verbose")
+	if verbose {
+		ys := string(yb)
+		fmt.Println("==== YAML Content ====")
+		fmt.Println(ys)
+		fmt.Println("========")
+	}
+	err = ioutil.WriteFile(youtf, yb, 0666)
+	if err != nil {
+		return fmt.Errorf("failed write out yaml file: %s", err.Error())
+	}
+	fmt.Println("Output: ", youtf)
+	return nil
+}
+
+func CmdConvertASCII2MZ(context *cli.Context) error {
+	rf := context.Bool("ignore-return")
+	return ConvertBetweenASCIIandMZ(context, true, rf)
+}
+
+func CmdConvertMZ2ASCII(context *cli.Context) error {
+	rf := context.Bool("use-cr-lf")
+	return ConvertBetweenASCIIandMZ(context, false, rf)
+}

+ 25 - 0
cmd/config.go

@@ -0,0 +1,25 @@
+package main
+
+type ConfigDef struct {
+	OutputWav      string               `yaml:"output_wav"`
+	MachineType    string               `yaml:"machine_type"`
+	InvertPolarity bool                 `yaml:"invert_polarity"`
+	TapeFiles      []ConfigItemTapeFile `yaml:"tape_files"`
+}
+
+type ConfigItemTapeFile struct {
+	FileAttribute           string  `yaml:"file_attrib"`
+	FileName                string  `yaml:"filename"`
+	LoadAddress             uint16  `yaml:"load_addr"`
+	ExecuteAfterLoad        bool    `yaml:"exec_after_load"`
+	ExecuteAddress          uint16  `yaml:"exec_addr"`
+	UseRawCommentFromFile   bool    `yaml:"use_raw_comment_from_file"`
+	RawCommentFileName      string  `yaml:"comment_from_filename"`
+	StringComment           string  `yaml:"text_comment"`
+	DateSource              string  `yaml:"data_source"`
+	FileDataSource          string  `yaml:"binary_filename"`
+	ASCIITextFileDataSource string  `yaml:"ascii_text_filename"`
+	IgnoreNonDisplayChar    bool    `yaml:"ignore_non_disp_char"`
+	IgnoreCR                bool    `yaml:"ignore_cr"`
+	RawData                 *[]byte `yaml:"raw_data"`
+}

+ 19 - 0
cmd/help_texts.go

@@ -0,0 +1,19 @@
+package main
+
+const (
+	HELP_MACHINE_TYPE = `Specify machine type. Default value: 'mz-700'.
+	Can be these value above:
+		mz-700
+		mz-80k
+		mz-80a
+		mz-80b
+		mz-800`
+	HELP_FILE_ATTRIBUTE = `Specify tape file attribute. Default value: '01'.
+	Can be the hex value between 00 to ff.
+	SHARP official documented attributes:
+		01 Machine code program file
+		02 MZ-80 BASIC program file
+		03 MZ-80 data file
+		04 MZ-700 data file
+		05 MZ-700 BASIC program file`
+)

+ 209 - 0
cmd/main.go

@@ -0,0 +1,209 @@
+package main
+
+import (
+	"fmt"
+	"github.com/gotidy/copy"
+	"github.com/urfave/cli/v2"
+	"os"
+)
+
+const VERSION = "v0.1.0"
+
+var App *cli.App
+
+func main() {
+	sflags := []cli.Flag{
+		&cli.StringFlag{
+			Name:    "out",
+			Aliases: []string{"o"},
+			Usage:   "Specify output wav file. Default value: 'tape.wav'",
+			Value:   "tape.wav",
+		},
+		&cli.StringFlag{
+			Name:    "machine",
+			Aliases: []string{"mt"},
+			Usage:   HELP_MACHINE_TYPE,
+			Value:   "mz-700",
+		},
+		&cli.BoolFlag{
+			Name:    "invert-polarity",
+			Aliases: []string{"inv"},
+			Usage:   "Invert audio polarity. Default value: false",
+			Value:   false,
+		},
+		&cli.StringFlag{
+			Name:     "file-attrib",
+			Aliases:  []string{"fa"},
+			Usage:    HELP_FILE_ATTRIBUTE,
+			Required: true,
+		},
+		&cli.StringFlag{
+			Name:     "file-name",
+			Aliases:  []string{"fn"},
+			Usage:    "Filename for tape file. (Not the input file name)",
+			Required: true,
+		},
+		&cli.StringFlag{
+			Name:     "load-addr",
+			Aliases:  []string{"la"},
+			Usage:    "Address for load.",
+			Required: true,
+		},
+		&cli.StringFlag{
+			Name:     "exec-addr",
+			Aliases:  []string{"ea"},
+			Usage:    "Address that will be jump to when tape loaded. If not specified, will not execute.",
+			Required: false,
+		},
+		&cli.StringFlag{
+			Name:     "comment",
+			Aliases:  []string{"cm"},
+			Usage:    "Tape file comment. If not specified, will be blank.",
+			Required: false,
+		},
+		&cli.StringFlag{
+			Name:     "in",
+			Aliases:  []string{"i"},
+			Usage:    "Input binary file.",
+			Required: true,
+		},
+		&cli.BoolFlag{
+			Name:    "verbose",
+			Aliases: []string{"v"},
+			Usage:   "Print out verbose information.",
+			Value:   false,
+		},
+	}
+	var eyflags []cli.Flag
+	copier := copy.New()
+	copier.Copy(&eyflags, &sflags)
+	eyfout := &cli.StringFlag{
+		Name:     "yaml",
+		Aliases:  []string{"yml"},
+		Usage:    "Output yaml file",
+		Required: true,
+	}
+	eyflags = append(eyflags, eyfout)
+	App = &cli.App{
+		Name:        "bin2mz700wav",
+		Usage:       "Convert binary file into SHARP MZ-700 Tape wave file.",
+		Version:     VERSION,
+		Description: "A simple convert program for converting binary file into SHARP MZ-700 Tape wave file.",
+		Commands: []*cli.Command{
+			{
+				Name:    "simple",
+				Aliases: []string{"s"},
+				Usage:   "Convert with specified config file",
+				Flags:   sflags,
+				Action:  CmdSimple,
+			},
+			{
+				Name:    "from-yaml",
+				Aliases: []string{"fy"},
+				Usage:   "Convert with specified yaml config file",
+				Flags: []cli.Flag{
+					&cli.StringFlag{
+						Name:     "in",
+						Aliases:  []string{"i", "y", "yml", "yaml"},
+						Usage:    "Input yaml file.",
+						Required: true,
+					},
+				},
+				Action: CmdYamlConfig,
+			},
+			{
+				Name:    "example-yaml",
+				Aliases: []string{"ey"},
+				Usage:   "Generate example yaml config file",
+				Flags:   eyflags,
+				Action:  CmdExampleYaml,
+			},
+			{
+				Name:    "convert-ascii",
+				Aliases: []string{"ca"},
+				Usage:   "convert text file between MZ-700 ASCII and standard ASCII",
+				Subcommands: []*cli.Command{
+					{
+						Name:    "mz2ascii",
+						Aliases: []string{"m2a"},
+						Usage:   "",
+						Action:  CmdConvertMZ2ASCII,
+						Flags: []cli.Flag{
+							&cli.StringFlag{
+								Name:     "in",
+								Aliases:  []string{"i"},
+								Usage:    "Input MZ text file.",
+								Required: true,
+							},
+							&cli.StringFlag{
+								Name:     "out",
+								Aliases:  []string{"o"},
+								Usage:    "Output ASCII text file. Override if file exists, create if file not exist.",
+								Required: true,
+							},
+							&cli.BoolFlag{
+								Name:    "ignore-non-disp-char",
+								Aliases: []string{"ign"},
+								Usage:   "Ignore non-display ASCII characters. (See documentation)",
+								Value:   false,
+							},
+							&cli.BoolFlag{
+								Name:    "use-cr-lf",
+								Aliases: []string{"crlf"},
+								Usage:   "Convert 0x0D into \\r\\n. (See documentation)",
+								Value:   false,
+							},
+						},
+					},
+					{
+						Name:    "ascii2mz",
+						Aliases: []string{"a2m"},
+						Usage:   "",
+						Action:  CmdConvertASCII2MZ,
+						Flags: []cli.Flag{
+							&cli.StringFlag{
+								Name:     "in",
+								Aliases:  []string{"i"},
+								Usage:    "Input ASCII text file.",
+								Required: true,
+							},
+							&cli.StringFlag{
+								Name:     "out",
+								Aliases:  []string{"o"},
+								Usage:    "Output MZ text file. Override if file exists, create if file not exist.",
+								Required: true,
+							},
+							&cli.BoolFlag{
+								Name:    "ignore-non-disp-char",
+								Aliases: []string{"ign"},
+								Usage:   "Ignore non-display ASCII characters. (See documentation)",
+								Value:   false,
+							},
+							&cli.BoolFlag{
+								Name:    "ignore-return",
+								Aliases: []string{"ir"},
+								Usage:   "Ignore \\r (0x0D). (See documentation)",
+								Value:   false,
+							},
+						},
+					},
+				},
+			},
+		},
+		Flags:                nil,
+		EnableBashCompletion: true,
+		HideHelp:             false,
+		HideHelpCommand:      false,
+		HideVersion:          false,
+		Authors: []*cli.Author{
+			{
+				Name:  "zry",
+				Email: "admin@z-touhou.org",
+			},
+		},
+	}
+	err := App.Run(os.Args)
+	if err != nil {
+		fmt.Println("failed run command: ", err.Error())
+	}
+}

+ 177 - 0
cmd/processing.go

@@ -0,0 +1,177 @@
+package main
+
+import (
+	"fmt"
+	mz700_tape_wav_gen "git.swzry.com/zry/mz700-tape-wav-gen"
+	"io/ioutil"
+	"os"
+)
+
+func ProcessingConvert(cfg *ConfigDef, verbose bool) error {
+	fname := cfg.OutputWav
+	fmt.Println("Output WAV File: ", fname)
+	fout, err := os.OpenFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
+	if err != nil {
+		return fmt.Errorf("failed open output file '%s': %s", fname, err)
+	}
+	if verbose {
+		fmt.Println("Output WAV file opened.")
+		fmt.Println("Initializing SHARP MZ Tape WAV Generator object...")
+	}
+	var machine mz700_tape_wav_gen.MachineType
+	switch cfg.MachineType {
+	case "mz-700":
+		machine = mz700_tape_wav_gen.MACHINE_MZ_700
+		break
+	case "mz-80k":
+		machine = mz700_tape_wav_gen.MACHINE_MZ_80K
+		break
+	case "mz-80a":
+		machine = mz700_tape_wav_gen.MACHINE_MZ_80A
+		break
+	case "mz-80b":
+		machine = mz700_tape_wav_gen.MACHINE_MZ_80B
+		break
+	case "mz-800":
+		machine = mz700_tape_wav_gen.MACHINE_MZ_800
+		break
+	default:
+		return fmt.Errorf("invalid machine type '%s'", cfg.MachineType)
+	}
+	fmt.Println("Target Machine Type: ", cfg.MachineType)
+	if cfg.InvertPolarity {
+		fmt.Println("Polarity: Inverted")
+	} else {
+		fmt.Println("Polarity: Normal")
+	}
+	stwg, err := mz700_tape_wav_gen.NewStandardTapeWaveGenerator(fout, machine, cfg.InvertPolarity)
+	defer func() {
+		err2 := stwg.Close()
+		if err2 != nil {
+			fmt.Printf("failed close file '%s': %s", fname, err.Error())
+		}
+	}()
+	if err != nil {
+		return fmt.Errorf("failed create object StandardTapeWaveGenerator: %s", err.Error())
+	}
+	if verbose {
+		fmt.Println("Generator object initialization OK.")
+	}
+	tfcount := len(cfg.TapeFiles)
+	if tfcount == 0 {
+		fmt.Println("No tape file will be put into tape WAV.")
+		err = fout.Close()
+		if err != nil {
+			return fmt.Errorf("failed to close opened WAV file: %s", err.Error())
+		}
+		fmt.Println("End.")
+		return nil
+	}
+	fmt.Printf("%d Tape file(s) will be put into tape WAV.\n", tfcount)
+	for i, v := range cfg.TapeFiles {
+		if verbose {
+			fmt.Printf("Processing File %d ...\n", i+1)
+		}
+		fa, sfa, err := ParseFileAttrib(v.FileAttribute)
+		if err != nil {
+			return fmt.Errorf("error @ Tape File %d ('%s'): %s", i+1, v.FileName, err.Error())
+		}
+		if verbose {
+			fmt.Printf("\tFile Attribute: 0x%02X (%s) \n", fa, sfa)
+			fmt.Println("\tFile Name: ", v.FileName)
+			fmt.Printf("\tLoad Address: 0x%04X\n", v.LoadAddress)
+			if v.ExecuteAfterLoad {
+				fmt.Printf("\tExecute Option:  Will Jump To 0x%04X After Load.\n", v.ExecuteAddress)
+			} else {
+				fmt.Println("\tExecute Option: Back to Monitor.")
+			}
+			if v.UseRawCommentFromFile {
+				fmt.Printf("\tComment Option: Raw from File '%s'\n", v.RawCommentFileName)
+			} else {
+				fmt.Println("\tComment Option: Text")
+				fmt.Println("\t==== Comment ====")
+				fmt.Println(AddTabBeforeEachLine(v.StringComment))
+				fmt.Println("\t========")
+			}
+		}
+		var rawcmdat []byte
+		if v.UseRawCommentFromFile {
+			rawcmdat, err = ioutil.ReadFile(v.RawCommentFileName)
+			if err != nil {
+				return fmt.Errorf("error @ Tape File %d ('%s'): raw comment file read: %s", i+1, v.FileName, err.Error())
+			}
+		}
+		var ftdata []byte
+		switch v.DateSource {
+		case "bin-file":
+			ftdata, err = DS_BinFile(v.FileDataSource)
+			break
+		case "ascii-text-file":
+			ftdata, err = DS_ASCIITextFile(v.ASCIITextFileDataSource, v.IgnoreNonDisplayChar, v.IgnoreCR)
+			break
+		case "raw":
+			ftdata, err = DS_Raw(v.RawData)
+			break
+		default:
+			return fmt.Errorf("unsupport data source '%s'", v.DateSource)
+		}
+		if verbose {
+			fmt.Println("\tData Source Type: ", v.DateSource)
+			fmt.Println("\tData Length: ", len(ftdata))
+		}
+		var scm string
+		var bcm []byte
+		if v.UseRawCommentFromFile {
+			bcm = rawcmdat
+			scm = ""
+		} else {
+			scm = v.StringComment
+			bcm = nil
+		}
+		tf := &mz700_tape_wav_gen.StandardTapeFile{
+			FileAttribute:    fa,
+			Filename:         v.FileName,
+			LoadAddress:      v.LoadAddress,
+			ExecuteAfterLoad: v.ExecuteAfterLoad,
+			ExecuteAddress:   v.ExecuteAddress,
+			IsStringComment:  !v.UseRawCommentFromFile,
+			StringComment:    scm,
+			BinaryComment:    bcm,
+			File:             ftdata,
+		}
+		err = stwg.WriteStandardFile(tf)
+		if err != nil {
+			return fmt.Errorf("error @ Tape File %d ('%s'): %s", i+1, v.FileName, err.Error())
+		}
+		stwg.WriteSilent1Second()
+		stwg.WriteSilent1Second()
+		stwg.WriteSilent1Second()
+	}
+	fmt.Println("Done.")
+	return nil
+}
+
+func DS_BinFile(filename string) ([]byte, error) {
+	fdat, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	} else {
+		return fdat, nil
+	}
+}
+
+func DS_ASCIITextFile(filename string, ign bool, icr bool) ([]byte, error) {
+	fdat, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	rdat := _ASCII2MZ700(fdat, ign, icr)
+	return rdat, nil
+}
+
+func DS_Raw(in *[]byte) ([]byte, error) {
+	if in == nil {
+		return []byte{}, nil
+	}
+	return *in, nil
+}

+ 51 - 0
cmd/simple_args_to_yaml.go

@@ -0,0 +1,51 @@
+package main
+
+import (
+	"fmt"
+	"github.com/urfave/cli/v2"
+)
+
+func SimpleArgsToYAMLData(context *cli.Context) (*ConfigDef, error) {
+	sla := context.String("load-addr")
+	u64la, err := ParseUInt16WithMultiBase(sla)
+	if err != nil {
+		return nil, fmt.Errorf("failed parse arg 'load-addr': %s", err.Error())
+	}
+	var exec_after_load bool
+	var exec_addr uint16
+	sea := context.String("exec-addr")
+	if sea == "" {
+		exec_after_load = false
+	} else {
+		exec_after_load = true
+		u64ea, err := ParseUInt16WithMultiBase(sea)
+		if err != nil {
+			return nil, fmt.Errorf("failed parse arg 'exec-addr': %s", err.Error())
+		}
+		exec_addr = u64ea
+	}
+	binf := context.String("in")
+	ydata := &ConfigDef{
+		OutputWav:      context.String("out"),
+		MachineType:    context.String("machine"),
+		InvertPolarity: context.Bool("invert-polarity"),
+		TapeFiles: []ConfigItemTapeFile{
+			{
+				FileAttribute:           context.String("file-attrib"),
+				FileName:                context.String("file-name"),
+				LoadAddress:             u64la,
+				ExecuteAfterLoad:        exec_after_load,
+				ExecuteAddress:          exec_addr,
+				UseRawCommentFromFile:   false,
+				RawCommentFileName:      "",
+				StringComment:           context.String("comment"),
+				DateSource:              "bin-file",
+				FileDataSource:          binf,
+				ASCIITextFileDataSource: "",
+				IgnoreNonDisplayChar:    false,
+				RawData:                 nil,
+			},
+		},
+	}
+	return ydata, nil
+}

+ 112 - 0
cmd/utils.go

@@ -0,0 +1,112 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+func ParseUInt16WithMultiBase(str string) (uint16, error) {
+	nstr := str
+	base := 10
+	fld := false
+	if strings.HasPrefix(nstr, "0x") {
+		fld = true
+		nstr = strings.TrimPrefix(nstr, "0x")
+		base = 16
+	}
+	if strings.HasSuffix(nstr, "h") {
+		if fld {
+			return 0, fmt.Errorf("do not use multiple base indicator")
+		}
+		fld = true
+		nstr = strings.TrimSuffix(nstr, "h")
+		base = 16
+	}
+	if strings.HasSuffix(nstr, "H") {
+		if fld {
+			return 0, fmt.Errorf("do not use multiple base indicator")
+		}
+		fld = true
+		nstr = strings.TrimSuffix(nstr, "H")
+		base = 16
+	}
+	if strings.HasSuffix(nstr, "o") {
+		if fld {
+			return 0, fmt.Errorf("do not use multiple base indicator")
+		}
+		fld = true
+		nstr = strings.TrimSuffix(nstr, "o")
+		base = 8
+	}
+	if strings.HasSuffix(nstr, "O") {
+		if fld {
+			return 0, fmt.Errorf("do not use multiple base indicator")
+		}
+		fld = true
+		nstr = strings.TrimSuffix(nstr, "O")
+		base = 8
+	}
+	if strings.HasSuffix(nstr, "b") {
+		if fld {
+			return 0, fmt.Errorf("do not use multiple base indicator")
+		}
+		fld = true
+		nstr = strings.TrimSuffix(nstr, "b")
+		base = 2
+	}
+	if strings.HasSuffix(nstr, "B") {
+		if fld {
+			return 0, fmt.Errorf("do not use multiple base indicator")
+		}
+		fld = true
+		nstr = strings.TrimSuffix(nstr, "B")
+		base = 2
+	}
+	u64la, err := strconv.ParseUint(nstr, base, 16)
+	if err != nil {
+		return 0, err
+	}
+	return uint16(u64la), nil
+}
+
+func ParseFileAttrib(str string) (byte, string, error) {
+	b, err := strconv.ParseUint(str, 16, 8)
+	if err != nil {
+		return 0, "", fmt.Errorf("failed parse file attribute: %s", err.Error())
+	}
+	s := "UNKNOWN"
+	switch b {
+	case 0x01:
+		s = "MACHINE CODE PROGRAM FILE"
+		break
+	case 0x02:
+		s = "MZ-80 BASIC PROGRAM FILE"
+		break
+	case 0x03:
+		s = "MZ-80 DATA FILE"
+		break
+	case 0x04:
+		s = "MZ-700 DATA FILE"
+		break
+	case 0x05:
+		s = "MZ-700 BASIC PROGRAM FILE"
+		break
+	default:
+		s = "UNKNOWN"
+		break
+	}
+	return byte(b), s, nil
+}
+
+func AddTabBeforeEachLine(in string) string {
+	lines := strings.Split(in, "\n")
+	strwr := bytes.NewBufferString("")
+	for _, v := range lines {
+		strwr.WriteRune('\t')
+		strwr.WriteString(v)
+		strwr.WriteRune('\n')
+	}
+	return strwr.String()
+}

+ 10 - 0
go.mod

@@ -0,0 +1,10 @@
+module binary2sharp-mz-700-tape-wav
+
+go 1.16
+
+require (
+	git.swzry.com/zry/mz700-tape-wav-gen v0.0.0-20210816195136-e2b25c19982c
+	github.com/gotidy/copy v0.6.0
+	github.com/urfave/cli/v2 v2.3.0
+	gopkg.in/yaml.v2 v2.2.3
+)

+ 23 - 0
go.sum

@@ -0,0 +1,23 @@
+git.swzry.com/zry/mz700-tape-wav-gen v0.0.0-20210816195136-e2b25c19982c h1:jEPrY9vlnJ4t1cBZmzHzlOUFQoU3Q6v4a7eEAYkx1NI=
+git.swzry.com/zry/mz700-tape-wav-gen v0.0.0-20210816195136-e2b25c19982c/go.mod h1:2dPOpuupgU2ADPKy5yPxH0LEmmsciuwYnsB43TQ+hXc=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/gotidy/copy v0.6.0 h1:BQ8bRSuhGusdenh5KWe+9z9GTnguUEqmLDWx5SNBmVo=
+github.com/gotidy/copy v0.6.0/go.mod h1:5+A072M589lvnzFi8TKS+R2Hf0g2cKGrr+2DsfHrF7Y=
+github.com/gotidy/ptr v1.3.0 h1:5wdrH1G8X4txy6fbWWRznr7k974wMWtePWP3p6s1API=
+github.com/gotidy/ptr v1.3.0/go.mod h1:vpltyHhOZE+NGXUiwpVl3wV9AGEBlxhdnaimPDxRLxg=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+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=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

BIN
hello.bin


+ 21 - 0
test-yml2.yaml

@@ -0,0 +1,21 @@
+output_wav:
+  output_wav_filename: testy.wav
+  override_if_exists: true
+  create_new_file_if_not_exists: true
+machine_type: mz-700
+invert_polarity: false
+tape_files:
+- file_attrib: "01"
+  filename: TapeTTT
+  load_addr: 4608
+  exec_after_load: true
+  exec_addr: 4608
+  use_raw_comment_from_file: false
+  comment_from_filename: ""
+  text_comment: ""
+  data_source: bin-file
+  binary_filename: hello.bin
+  ascii_text_filename: ""
+  ignore_non_disp_char: false
+  ignore_cr: false
+  raw_data: null

BIN
test.wav


BIN
testx.wav


BIN
testy.wav