summaryrefslogtreecommitdiff
path: root/utilities/blinkenrocket.py
diff options
context:
space:
mode:
Diffstat (limited to 'utilities/blinkenrocket.py')
-rw-r--r--utilities/blinkenrocket.py315
1 files changed, 315 insertions, 0 deletions
diff --git a/utilities/blinkenrocket.py b/utilities/blinkenrocket.py
new file mode 100644
index 0000000..d16b7cf
--- /dev/null
+++ b/utilities/blinkenrocket.py
@@ -0,0 +1,315 @@
+#!/usr/bin/env python
+
+import sys, wave
+
+class modem:
+
+ # Modem specific constants
+ bits = [[3 * chr(0), 5 * chr(0)], [3 * chr(255), 5 * chr(255)]]
+ sync = [17 * chr(0), 17 * chr(255)]
+ # Variable to alternate high and low
+ hilo = 0
+ supportedFrequencies = [16000,22050,24000,32000,44100,48000]
+ cnt = 0
+ # Data variables
+ data = []
+ parity = True
+ frequency = 48000
+
+ # Hamming code translation table
+ _hammingCalculateParityLowNibble = [0, 3, 5, 6, 6, 5, 3, 0, 7, 4, 2, 1, 1, 2, 4, 7]
+ _hammingCalculateParityHighNibble = [0, 9, 10, 3, 11, 2, 1, 8, 12, 5, 6, 15, 7, 14, 13, 4]
+
+ # Almost nothing here
+ def __init__(self, data=[], parity=True, frequency=48000):
+ self.data = data
+ self.parity = parity
+ self.frequency = frequency if frequency in self.supportedFrequencies else 48000
+ self.cnt = 0
+
+ # Calculate Hamming parity for 12,8 code (12 bit of which 8bit data)
+ def hammingCalculateParity128(self, byte):
+ return self._hammingCalculateParityLowNibble[byte&0x0F] ^ self._hammingCalculateParityHighNibble[byte >> 4]
+
+ # Calculate Hamming parity for 24,16 code (24 bit of which 16 bit are data)
+ def hammingCalculateParity2416(self, first, second):
+ return self.hammingCalculateParity128(second) << 4 | self.hammingCalculateParity128(first)
+
+ # Generate one sync-pulse
+ def syncsignal(self):
+ self.hilo ^= 1
+ return self.sync[self.hilo]
+
+ # Generate a number of sync signals
+ def generateSyncSignal(self, number):
+ sound = ""
+ for i in xrange(number):
+ sound += self.syncsignal()
+ return sound
+
+ # Decode bits to modem signals
+ def modemcode(self, byte):
+ bleep = ""
+ for x in xrange(8):
+ self.hilo ^= 1
+ bleep += self.bits[self.hilo][byte & 0x01]
+ byte >>= 1
+ return bleep
+
+ # Return <length> samples of silence
+ def silence(self, length):
+ return chr(127) * length
+
+ # Set data for modem code
+ def setData(self, data):
+ self.data = data
+
+ # Set whether to use parity or not
+ def setParity(self, parity):
+ self.parity = parity
+
+ # Set the frequency for the audio
+ def setFrequency(self, frequency):
+ self.frequency = frequency if frequency in self.supportedFrequencies else 48000
+
+ # Generates the audio frames based on the data
+ def generateAudioFrames(self):
+ if self.parity:
+ tmpdata = []
+ # for uneven length data, we have to append a null byte
+ if not len(self.data) % 2 == 0:
+ self.data.append(chr(0))
+ # insert the parity information every two bytes, sorry for the heavy casting
+ for index in range(0, len(self.data), 2):
+ tmpdata.extend(self.data[index:index+2])
+ tmpdata.append(chr(self.hammingCalculateParity2416(ord(self.data[index]),ord(self.data[index+1]))))
+ self.data = tmpdata
+ # generate the audio itself
+ # add 1000ms of sync signal before the data
+ # (some sound cards take a while to produce a proper output signal)
+ sound = self.generateSyncSignal(3000)
+ # process the data and insert sync signal every 10 bytes
+ for byte in self.data:
+ sound += self.modemcode(ord(byte))
+ self.cnt += 1
+ if self.cnt == 9: # ! do not send sync inside (byte1 byte2 parity) triples
+ sound += self.generateSyncSignal(4)
+ self.cnt = 0
+ # add some sync signals in the end
+ sound += self.generateSyncSignal(4)
+ return sound
+
+ def saveAudio(self,filename):
+ wav = wave.open(filename, 'wb')
+ wav.setparams((1, 1, self.frequency, 0, "NONE", None))
+ wav.writeframes(self.generateAudioFrames())
+ wav.close()
+
+class Frame( object ):
+ """ Returns the frame information """
+ def getFrameHeader(self):
+ raise NotImplementedError("You should implement this!")
+
+ """ Returns the representation """
+ def getRepresentation(self):
+ raise NotImplementedError("Should have implemented this")
+
+class textFrame(Frame):
+ text = ""
+ speed = 0
+ delay = 0
+ direction = 0
+ # identifier as of message specification: 0001
+ identifier = 0x01
+
+ def __init__(self,text,speed=13,delay=0,direction=0):
+ self.text = text
+ self.setSpeed(speed)
+ self.setDelay(delay)
+ self.setDirection(direction)
+
+ def setSpeed(self,speed):
+ self.speed = speed if speed < 16 else 1
+
+ def setDelay(self,delay):
+ self.delay = delay if delay < 16 else 0
+
+ def setDirection(self,direction):
+ self.direction = direction if direction in [0,1] else 0
+
+ # Frame header: 4 bit type + 12 bit length
+ def getFrameHeader(self):
+ return [chr(self.identifier << 4 | len(self.text) >> 8), chr(len(self.text) & 0xFF) ]
+
+ # Header -> 4bit speed, 4 bit delay, 4 bit direction, 4 bit zero
+ def getHeader(self):
+ return [chr(self.speed << 4 | self.delay), chr(self.direction << 4 | 0x00)]
+
+ def getRepresentation(self):
+ retval = []
+ retval.extend(self.getFrameHeader())
+ retval.extend(self.getHeader())
+ retval.extend(list(self.text))
+ return retval
+
+class animationFrame(Frame):
+ animation = []
+ speed = 0
+ delay = 0
+ # identifier as per specification: 0010
+ identifier = 0x02
+
+ def __init__(self,animation,speed=13,delay=0):
+ self.setAnimation(animation)
+ self.setSpeed(speed)
+ self.setDelay(delay)
+
+ def setAnimation(self,animation):
+ if len(animation) % 8 is not 0:
+ raise Exception
+ else:
+ self.animation = animation
+
+ def setSpeed(self,speed):
+ self.speed = speed if speed < 16 else 1
+
+ def setDelay(self,delay):
+ self.delay = delay if delay < 16 else 0
+
+ # Frame header: 4 bit type + 12 bit length
+ def getFrameHeader(self):
+ return [chr(self.identifier << 4 | len(self.animation) >> 8), chr(len(self.animation) & 0xFF) ]
+
+ # Header -> 4bit zero, 4bit speed, 4 bit zero, 4 bit direction
+ def getHeader(self):
+ return [chr(self.speed), chr(self.delay)]
+
+ def getRepresentation(self):
+ retval = []
+ retval.extend(self.getFrameHeader())
+ retval.extend(self.getHeader())
+ retval.extend(self.animation)
+ return retval
+
+
+class blinkenrocket():
+
+ eeprom_size = 65536
+ startcode = chr(0x99)
+ patterncode = chr(0xA9)
+ endcode = chr(0x84)
+ frames = []
+
+ def __init__(self,eeprom_size=65536):
+ self.eeprom_size = eeprom_size if eeprom_size < 256*1024*1024 else 65536
+
+ def addFrame(self, frame):
+ if not isinstance(frame, Frame):
+ raise RuntimeError("Incorrect frame supplied")
+ else:
+ self.frames.append(frame)
+
+ def getMessage(self):
+ output = [self.startcode, self.startcode]
+ for frame in self.frames:
+ output.extend([self.patterncode,self.patterncode])
+ output.extend(frame.getRepresentation())
+ output.extend([self.endcode,self.endcode])
+ return output
+
+
+
+
+if __name__ == '__main__':
+ m = modem(parity=True, frequency=48000)
+ b = blinkenrocket()
+
+ for message in sys.argv[2:]:
+ b.addFrame(textFrame(message, speed=13))
+ b.addFrame(textFrame(" \x04 "))
+ b.addFrame(animationFrame(map(lambda x : chr(x), [0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255]), speed=0))
+ b.addFrame(animationFrame(map(lambda x : chr(x), [
+ 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 8,
+ 0, 0, 0, 0, 0, 0, 0, 16,
+ 0, 0, 0, 0, 0, 0, 0, 32,
+ 0, 0, 0, 0, 0, 0, 0, 64,
+ 0, 0, 0, 0, 0, 0, 0, 128,
+ 0, 0, 0, 0, 0, 0, 128, 0,
+ 0, 0, 0, 0, 0, 128, 0, 0,
+ 0, 0, 0, 0, 128, 0, 0, 0,
+ 0, 0, 0, 128, 0, 0, 0, 0,
+ 0, 0, 128, 0, 0, 0, 0, 0,
+ 0, 128, 0, 0, 0, 0, 0, 0,
+ 128, 0, 0, 0, 0, 0, 0, 0,
+ 64, 0, 0, 0, 0, 0, 0, 0,
+ 32, 0, 0, 0, 0, 0, 0, 0,
+ 16, 0, 0, 0, 0, 0, 0, 0,
+ 8, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0, 16, 0,
+ 0, 0, 0, 0, 0, 0, 32, 0,
+ 0, 0, 0, 0, 0, 0, 64, 0,
+ 0, 0, 0, 0, 0, 64, 0, 0,
+ 0, 0, 0, 0, 64, 0, 0, 0,
+ 0, 0, 0, 64, 0, 0, 0, 0,
+ 0, 0, 64, 0, 0, 0, 0, 0,
+ 0, 64, 0, 0, 0, 0, 0, 0,
+ 0, 32, 0, 0, 0, 0, 0, 0,
+ 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 8, 0, 0, 0, 0, 0, 0,
+ 0, 4, 0, 0, 0, 0, 0, 0,
+ 0, 0, 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 8, 0, 0,
+ 0, 0, 0, 0, 0, 16, 0, 0,
+ 0, 0, 0, 0, 0, 32, 0, 0,
+ 0, 0, 0, 0, 32, 0, 0, 0,
+ 0, 0, 0, 32, 0, 0, 0, 0,
+ 0, 0, 32, 0, 0, 0, 0, 0,
+ 0, 0, 16, 0, 0, 0, 0, 0,
+ 0, 0, 8, 0, 0, 0, 0, 0,
+ 0, 0, 0, 8, 0, 0, 0, 0,
+ 0, 0, 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 16, 0, 0, 0,
+ 0, 0, 0, 16, 0, 0, 0, 0,
+ 0, 0, 0, 24, 24, 0, 0, 0,
+ 0, 0, 60, 36, 36, 60, 0, 0,
+ 0, 126, 66, 66, 66, 66, 126, 0,
+ 255, 129, 129, 129, 129, 129, 129, 255,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ ]), speed=15, delay=1))
+ b.addFrame(animationFrame(map(lambda x : chr(x), [
+ 0, 0, 0, 24, 24, 0, 0, 0,
+ 0, 0, 60, 36, 36, 60, 0, 0,
+ 0, 126, 66, 66, 66, 66, 126, 0,
+ 255, 129, 129, 129, 129, 129, 129, 255,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ ]), speed=14, delay=1))
+
+ m.setData(b.getMessage())
+ m.saveAudio(sys.argv[1])
+
+ #print b.getMessage()
+
+