#!/usr/pkg/bin/python """read ensoniq disk images you can get an image of an ensoniq disk by booting up Damn Small Linux or other Knoppix live CDROM on a PC, fetching epsread, and dumping the diskette in /dev/fd0 to a file: wget http://yceran.org/eps/epsread chmod +x epsread ./epsread > /tmp/workdisk.dat then copy it to someplace permanent: scp /tmp/workdisk.dat luser@tty.freeshell.org: don't try: dd if=/dev/fd0 of=/tmp/epsdisk.dat it'll seem to work, but you'll be missing a bunch of sectors disk format info is from: http://youngmonkey.ca/nose/audio_tech/synth/Ensoniq-DiskFormats.html and http://www.thoralt.de/download/EPSSongSeqInfo.pdf; the former resource seems to match the disk structure better, while the latter has more goodies on what's in the tracks""" Copyright = """ eps -- read ensoniq EPS disk images Copyright (C) 2005 John Comeau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. """ errormessage = "Not all needed libraries found, upgrade or check path" try: True # not defined in older Python releases except: True, False = 1, 0 try: import sys, os, types, re, pwd sys.path.append(os.sep.join([pwd.getpwuid(os.geteuid())[5], 'lib', 'python'])) from com.jcomeau import gpl, jclicense except: try: sys.stderr.write("%s\n" % errormessage) except: print errormessage raise # get name this program was called as self = sys.argv[0].split(os.sep)[-1] command = self.split('.')[0] # chop any suffix (extension) # now get name we gave it when we wrote it originalself = Copyright.split()[0] # globals and routines that should be in every program # (yes, you could import them, but there are problems in that approach too) def DebugPrint(*whatever): return False # defined instead by pytest module, use that for debugging def noTuples(*args): "reduces args to lists or scalars, helps with command-line testing" while type(args[0]) == types.TupleType: args = args[0] return args def scalars(*args): "reduces args to scalars, helps with command-line testing" args = noTuples(args) while type(args[0]) == types.ListType: args = args[0] return args # other globals, specific to this program import time, struct # the 'wtf' entries before are things for which I cannot find documentation vfx_commands = dict(( map(None, range(0, 0x58), ['note'] * 0x58) + map(None, range(0x58, 0xb0), ['aftertouch'] * (0xb0 - 0x58)) + [(0xb0, 'pitchwheel')] + [(0xb1, 'modwheel')] + [(0xb2, 'patchselect')] + [(0xb3, 'external_controller')] + [(0xb4, 'foot_controller')] + [(0xb5, 'volumepedal')] + [(0xb6, 'sustainpedal')] + [(0xb7, 'pressure')] + [(0xb8, 'wtf')] + [(0xb9, 'wtf')] + [(0xe6, 'timedelay')] + [(0xe9, 'endoftrack')] + [(0xff, 'wtf')] )) def eps(*args): """get command and process it example: eps /tmp/epsdisk.dat ls or: eps /tmp/epsdisk.dat cp "MY SONG" /tmp/my_song.dat """ try: args = scalars(args) image, command, args = args[0], args[1], args[2:] if command in dir(sys.modules['__main__']): eval(command)(image, args) else: raise Exception, "No such command %s" % command except: print >>sys.stderr, "Usage: %s ENSONIQ_DISK_IMAGE COMMAND [ARGS]" % self raise def rawdirectory(*args): """return list of files on disk image""" args = scalars(args) image, args = args[0], args[1:] directory = [] imagedata = open(image) epsdata = imagedata.read(0xa00) imagedata.close() if not (epsdata[0x41c:0x41e] == 'OS' and epsdata.endswith('DR')): raise Exception, 'Not a VFX-SD image, and no other EPS formats implemented' for block in range(2, 5): blockstart, skip = 0x200 * block, 0 if blockstart == 0x400: skip = 0x1e for offset in range(skip, 0x200 - 26, 26): entry = epsdata[blockstart + offset:blockstart + offset + 26] if not len(entry) < 26 and entry != '\x00' * 26: directory.append(entry) return directory def directory(*args): dir = [] for entry in rawdirectory(args): dir.append([ord(entry[0]), ord(entry[1]), entry[2:13], struct.unpack('>H', entry[14:16])[0], struct.unpack('>H', entry[16:18])[0], struct.unpack('>L', entry[18:22])[0], ord(entry[22]), struct.unpack('>L', entry[-4:])[0] & 0xffffff]) return dir def fabs(*args): """file allocation blocks""" args = scalars(args) image, args = args[0], args[1:] fabs = [] for blocknumber in range(5, 14): blockdata = block(image, blocknumber) if not blockdata.endswith('FB'): continue for fab in range(0, 0x200 - 3, 3): fabs.append(struct.unpack('>L', '\x00' + blockdata[fab:fab + 3])[0]) return fabs def block(*args): args = scalars(args) image, block = args if type(block) == types.StringType: block = eval(block) # allows commandline input as decimal or hex imagedata = open(image) imagedata.seek(block * 0x200) epsdata = imagedata.read(0x200) imagedata.close() return epsdata def dumpfiles(*args): """dump all sequence/song files to target directory""" args = scalars(args) image, args = args targetdir = args[0] DebugPrint(image, targetdir) if not os.path.isdir(targetdir): raise Exception, '2nd arg must specify target directory' for entry in directory(image): name, inode = entry[2].strip(), entry[5] fileblocks = [inode] for blocknumber in fabs(image)[inode:]: if blocknumber == 1: break else: fileblocks.append(blocknumber) try: outfile = open(os.path.join(targetdir, "%s.efv" % name), 'wb') for blocknumber in fileblocks: outfile.write(block(image, blocknumber)) except: DebugPrint('skipping file "%s.efv" due to error' % name) pass def vfxsequence(*args): args = scalars(args) for rawfilename in args: rawfile = open(rawfilename, 'rb') data = rawfile.read() rawfile.close() pointer, tracks = 0x240, [] while True: tracklength = struct.unpack('>L', data[pointer:pointer + 4])[0] - 4 if tracklength < 0: break DebugPrint('tracklength', tracklength) pointer += 4 tracks.append(data[pointer:pointer + tracklength]) pointer += tracklength print repr(midievents(tracks)) def midievents(*args): args = scalars(args) mididata = [['MThd', 6, 1, len(args), 96]] for track in args: mididata.append(['MTrk', 0]) pointer, ticks = 0, 0 while pointer < len(track): commandword = struct.unpack('>H', track[pointer:pointer + 2])[0] pointer += 2 DebugPrint('commandword: %x' % commandword) command_signifier, delay, command = (commandword & 0x8000, (commandword & 0x7f00) >> 8, commandword & 0xff) if command_signifier == 0: raise Exception, 'found non-command when expecting command' else: vfx_command = vfx_commands[command] DebugPrint('command found: ', vfx_command) pointer, ticks = eval(vfx_command)(command, pointer, ticks, track, mididata) if delay: DebugPrint('delay to next event: ', delay) ticks += delay # not sure if they should be added before or after? return mididata def note(*args): args = scalars(args) command, pointer, ticks, track, mididata = args note = command & 0xff channel = len(mididata) - 2 # use channel number same as track number notedata = struct.unpack('>H', track[pointer:pointer + 2])[0] pointer += 2 velocity, duration = (notedata & 0xfc00) >> 8, notedata & 0x3ff if duration == 0: DebugPrint('processing double-length note event') duration = struct.unpack('>H', track[pointer:pointer + 2])[0] pointer += 2 DebugPrint('velocity', velocity, 'duration', duration) insertevent(ticks, [0x90 + channel, note, velocity], mididata) insertevent(ticks + duration, [0x90 + channel, note, 0], mididata) return pointer, ticks def wtf(*args): "found this in one sequence just before end-of-track, maybe a no-op?" args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('effing data', data) pointer += 2 return pointer, ticks def volumepedal(*args): args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('volume pedal data', data) pointer += 2 return pointer, ticks def sustainpedal(*args): args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('sustain pedal data', data) pointer += 2 return pointer, ticks def pressure(*args): args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('pressure data', data) pointer += 2 return pointer, ticks def pitchwheel(*args): args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('pitchwheel data', data) pointer += 2 return pointer, ticks def modwheel(*args): args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('modwheel data', data) pointer += 2 return pointer, ticks def patchselect(*args): args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('patch select', data) pointer += 2 return pointer, ticks def external_controller(*args): args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('external controller', data) pointer += 2 return pointer, ticks def foot_controller(*args): args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('foot controller', data) pointer += 2 return pointer, ticks def aftertouch(*args): args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('aftertouch data', data) pointer += 2 return pointer, ticks def timedelay(*args): #DebugPrint('timedelay, args:', args) args = scalars(args) command, pointer, ticks, track, mididata = args data = struct.unpack('>H', track[pointer:pointer + 2])[0] DebugPrint('timedelay data', data) pointer += 2 return pointer, ticks + data def endoftrack(*args): args = scalars(args) command, pointer, ticks, track, mididata = args if pointer != len(track): DebugPrint('garbage at end of track', track[pointer:]) else: DebugPrint('end of track') insertevent(ticks, [0xff, 0x2f, 0, ''], mididata) return pointer, ticks def insertevent(*args): """insert MIDI event into track list at correct time offset not by any means optimized, going for "simple" rather than "fast" """ args = scalars(args) # can work because first passed value is tick count ticks, event, tracks = args DebugPrint('ticks', ticks, 'event', event) deltatimes = map(lambda element: element[0], tracks[-1][2:]) for index in range(0, len(deltatimes)): delta = tracks[-1][2 + index][0] # skip 'MTrk' and count if ticks - delta < 0: tracks[-1].insert(2 + index, [ticks] + event) return else: ticks -= delta # if we got this far, we haven't inserted it yet tracks[-1].append([ticks] + event) def ls(*args): """list files on disk image""" args = scalars(args) image, args = args[0], args[1:] stat = os.stat(image) modbits, mtime, timeformat = stat[0] & 0x1ff, stat[8], '%b %e %Y' if time.localtime()[0] == time.localtime(mtime)[0]: timeformat = '%b %e %H:%M' print '%o %s' % (modbits, time.strftime(timeformat, time.localtime(mtime))) for entry in directory(image): entrytype, name, inode, size = entry[1], entry[2], entry[5], entry[7] if entrytype == 17: print '"%s" %10s' % (name, size) elif entrytype == 2: print '"%s"' % name elif entrytype == 0: pass else: print 'Unimplemented directory entry type %d' % entrytype if __name__ == '__main__': # if this program was imported by another, the above test will fail, # and this following code won't be used... eval(command)(sys.argv[1:]) else: # if you want something to be done on import, do it here; otherwise pass pass