Skip to content

Instantly share code, notes, and snippets.

@koubas
Last active September 15, 2017 11:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koubas/86d805bb0620ccf2cdedd95ad6b9898e to your computer and use it in GitHub Desktop.
Save koubas/86d805bb0620ccf2cdedd95ad6b9898e to your computer and use it in GitHub Desktop.
ATARI 800 cassette signal generator (but ATARI doesn't "hear: it :()
package main
import (
"fmt"
"os"
"bytes"
"encoding/binary"
"math"
)
const freqMark = 5327.0
const freqSpace = 3995.0
const baudRate = 600.0
const baudDur = 1 / baudRate
type wavHeader struct {
riffTag [4]uint8
riffLength uint32
waveTag [4]uint8
fmtTag [4]uint8
fmtLength uint32
audioFormat uint16
numChanels uint16
sampleRate uint32
byteRate uint32
blockAlign uint16
bitsPerSample uint16
dataTag [4]uint8
dataLength uint32
}
func failOnError(e error) {
if e != nil {
panic(e)
}
}
func (h *wavHeader) ToBytes() []byte {
buffer := new(bytes.Buffer)
binary.Write(buffer, binary.LittleEndian, h)
return buffer.Bytes()
}
func createWav(filename string) (*os.File, *wavHeader) {
fmt.Printf("Creating wav file %v\n", filename)
file, err := os.Create(filename)
failOnError(err)
header := &wavHeader{}
copy(header.riffTag[:], "RIFF")
copy(header.waveTag[:], "WAVE")
copy(header.fmtTag[:], "fmt ")
copy(header.dataTag[:], "data")
header.riffLength = 0
header.fmtLength = 16
header.dataLength = 0
header.audioFormat = 1
header.numChanels = 1
header.sampleRate = 44100
header.bitsPerSample = 16
header.byteRate = header.sampleRate * uint32(header.numChanels) * uint32(header.bitsPerSample/8)
header.blockAlign = header.numChanels * uint16(header.bitsPerSample/8)
file.Write(header.ToBytes())
return file, header
}
func writeWav(file *os.File, waveform []uint16) {
data := make([]uint8, len(waveform)*2)
i := 0
for _, sample := range waveform {
var h, l uint8 = uint8(sample >> 8), uint8(sample & 0xff)
data[i] = l
i++
data[i] = h
i++
}
file.WriteAt(data, 44)
}
func closeWav(file *os.File, header *wavHeader) {
defer file.Close()
fileInfo, err := file.Stat()
failOnError(err)
fileLength := uint32(fileInfo.Size())
header.riffLength = fileLength - 8
header.dataLength = fileLength - 44
file.WriteAt(header.ToBytes(), 0)
}
func tone(freq float64, length float64, phase float64) ([]uint16, float64) {
const sampleRate = 44100
const gain = 0.5
radsPerSample := freq * (2 * math.Pi / sampleRate)
samples := int(length * sampleRate)
waveform := make([]uint16, samples)
for i := 0; i < samples; i++ {
phase += radsPerSample
amp := math.Sin(phase) * gain * 32767
waveform[uint32(i)] = uint16(amp)
}
if freq == freqSpace {
fmt.Printf("0")
} else {
fmt.Printf("1")
}
return waveform, phase
}
func wireByte(val uint8, phase float64, checksum uint8) ([]uint16, float64, uint8) {
// start bit
waveform, phase := tone(freqSpace, baudDur, phase)
// data bits
var mask uint8 = 0x1
var freq float64
var w1 []uint16
for i := 0; i < 8; i++ {
if (mask & val) > 0 {
freq = freqMark
} else {
freq = freqSpace
}
w1, phase = tone(freq, baudDur, phase)
waveform = append(waveform, w1...)
mask = mask << 1
}
// stop bit
w1, phase = tone(freqMark, baudDur, phase)
waveform = append(waveform, w1...)
// checksum
checksum += val
if checksum < val {
checksum++
}
fmt.Printf(" ")
return waveform, phase, checksum
}
func wireRecord(data []uint8, controlByte uint8, phase float64) ([]uint16, float64) {
// PRWT (Pre-record write tone)
fmt.Printf("\nPRWT\n")
w1, phase := tone(freqMark, 0.25, phase)
// 1. MARKER
fmt.Printf("\nMARKER\n")
w2, phase, checksum := wireByte(0x55, phase, 0)
waveform := append(w1, w2...)
// 2. MARKER
fmt.Printf("\nMARKER\n")
w2, phase, checksum = wireByte(0x55, phase, checksum)
waveform = append(waveform, w2...)
// control byte
fmt.Printf("\nCONTROL BYTE\n")
w2, phase, checksum = wireByte(controlByte, phase, checksum)
waveform = append(waveform, w2...)
// data
fmt.Printf("\nDATA\n")
var w3 []uint16
for _, b := range data {
w3, phase, checksum = wireByte(b, phase, checksum)
waveform = append(waveform, w3...)
}
// checksum
w2, phase, checksum = wireByte(checksum, phase, checksum)
waveform = append(waveform, w2...)
//PRG (Post-record gap)
fmt.Printf("\nPRG\n")
w2, phase = tone(freqMark, 0, phase)
waveform = append(waveform, w2...)
return waveform, phase
}
func main() {
wav, header := createWav("track.wav")
var w []uint16
var str string = "10 PRINT \"Tohle je 128 znaku dlouhy text, kterym chci zaplacnout payload presne jednoho kazetoveho zaznamu. A jeste neco\":GOTO 10"
var data []uint8
for i := 0; i < len(str); i++ {
data = append(data, uint8(str[i]))
}
waveform, phase := tone(freqMark, 3, 0)
w, phase = wireRecord(data, 0xFC, phase)
waveform = append(waveform, w...)
data = []uint8{}
for i := 0; i < 128; i++ {
data = append(data, 0)
}
w, phase = wireRecord(data, 0xFE, 0)
waveform = append(waveform, w...)
fmt.Printf("phase: %f\n", phase)
writeWav(wav, waveform)
closeWav(wav, header)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment