Last active
September 15, 2017 11:31
-
-
Save koubas/86d805bb0620ccf2cdedd95ad6b9898e to your computer and use it in GitHub Desktop.
ATARI 800 cassette signal generator (but ATARI doesn't "hear: it :()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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