101 lines
3 KiB
Fennel
101 lines
3 KiB
Fennel
(local util (require :lib.util))
|
|
(local tape (require :asm.tape))
|
|
|
|
; https://www.apple.asimov.net/documentation/hardware/io/The%20Apple%20II%20Cassette%20Interface.txt
|
|
|
|
; header
|
|
; ---+ +-------------+ +-----+
|
|
; | | | | |
|
|
; +-------------+ +----+ |
|
|
|
|
; | 1300 microseconds | 200| 250 |
|
|
; header tone | synchronous bit |
|
|
|
|
; 0 and 1 bits
|
|
; +-----+ +----------+ +
|
|
; | | | | |
|
|
; + +-----+ +----------+
|
|
|
|
; | 500 usec | 1000 usec |
|
|
|
|
(local samplerate 48000)
|
|
(fn us-to-s [us] (* (/ 1 1000000) us))
|
|
(fn samplecount [us] (* samplerate (us-to-s us)))
|
|
|
|
(fn checksum [bytes]
|
|
(var sum 0xff)
|
|
(for [ibyte 1 (bytes:len)]
|
|
(set sum (bit.bxor sum (: (bytes:sub ibyte ibyte) :byte))))
|
|
sum)
|
|
|
|
(fn data []
|
|
{:cycles []
|
|
:samples 0
|
|
:cycle
|
|
(fn [self us count]
|
|
(local samples (samplecount us))
|
|
(local prev-cycle (. self.cycles (length self.cycles)))
|
|
(local prev-samples (-?> prev-cycle (. 1)))
|
|
(if (= samples prev-samples) (tset prev-cycle 2 (+ (. prev-cycle 2) count))
|
|
(table.insert self.cycles [samples count]))
|
|
(set self.samples (+ self.samples (* samples count))))
|
|
:header
|
|
(fn [self ?s]
|
|
(self:cycle (/ 1300 2) (math.floor (/ (or ?s 5) (us-to-s (/ 1300 2)))))
|
|
(self:cycle 200 1)
|
|
(self:cycle 250 1))
|
|
:bit (fn [self on] (self:cycle (if on 500 250) 2))
|
|
:byte
|
|
(fn [self byte]
|
|
(for [ibit 7 0 -1]
|
|
(self:bit (not= (bit.band byte (bit.lshift 1 ibit)) 0))))
|
|
:bytes
|
|
(fn [self bytes]
|
|
(for [ibyte 1 (bytes:len)]
|
|
(self:byte (: (bytes:sub ibyte ibyte) :byte))))
|
|
:bytes-with-checksum
|
|
(fn [self bytes]
|
|
(self:bytes bytes)
|
|
(self:byte (checksum bytes)))
|
|
:to-sound
|
|
(fn [self]
|
|
(local sound (love.sound.newSoundData (+ self.samples (samplecount 8500)) samplerate 16 1))
|
|
(var isample 0)
|
|
(var err 0)
|
|
(var on true)
|
|
(each [_ [samples count] (ipairs self.cycles)]
|
|
(for [_ 1 count]
|
|
(set err (+ err (% samples 1)))
|
|
(for [_ 1 (+ samples (math.floor err))]
|
|
(sound:setSample isample (if on 0.75 -0.75))
|
|
(set isample (+ isample 1)))
|
|
(set err (% err 1))
|
|
(set on (not on))))
|
|
; finish last bit
|
|
(set on (not on))
|
|
(for [_ 1 (samplecount 500)]
|
|
(sound:setSample isample (if on 0.75 -0.75))
|
|
(set isample (+ isample 1)))
|
|
; add a little pad of zeros
|
|
(for [_ 1 (samplecount 8000)]
|
|
(sound:setSample isample 0)
|
|
(set isample (+ isample 1)))
|
|
sound)})
|
|
|
|
(fn gen-bin [bytes ?s-header]
|
|
(local dat (data))
|
|
(dat:header ?s-header)
|
|
(dat:bytes-with-checksum bytes)
|
|
(dat:to-sound))
|
|
|
|
(fn gen-basic [bytes ?s-header]
|
|
(local dat (data))
|
|
(dat:header ?s-header)
|
|
(dat:bytes-with-checksum
|
|
(.. (util.int16-to-bytes (+ (bytes:len) 1)) (util.int8-to-bytes 0xd5)))
|
|
(dat:header ?s-header)
|
|
(dat:bytes-with-checksum bytes)
|
|
(dat:to-sound))
|
|
|
|
{: gen-bin : gen-basic : checksum : data : samplerate : samplecount}
|