Mon Jan 30, 2017 9:20 am
A[16 instruments of 8 bytes each][128 notes of 5 bytes each]
import sys
INST_SIZE = 8
INSTBUF_SIZE = 16
WAV = 0
ARD = 1
ARS = 2
VOL = 3
SLD = 4
SLS = 5
TRD = 6
TRS = 7
NOTE_SIZE = 5
NOTEBUF_SIZE = 128
CHA = 0
TIM = 1
PIT = 2
INS = 3
DUR = 4
SILENCE = 63
CMD_VOLUME = 0
CMD_INSTRUMENT = 1
CMD_SLIDE = 2
CMD_ARPEGGIO = 3
CMD_TREMOLO = 4
#For now just keep this fixed here
FPR = 2
def print_bytes(bytes):
out = ''
for b in bytes:
out += '%02x '%ord(b)
print out
def print_note(ins):
print 'Channel: %d'%ord(ins[CHA])
print 'Time: %d'%ord(ins[TIM])
print 'Pitch: %d'%ord(ins[PIT])
print 'Instrument: %d'%ord(ins[INS])
print 'Duration: %d'%ord(ins[DUR])
def convert_instruments(ibuf):
instruments = []
for i in range(0,INSTBUF_SIZE):
inst = []
for j in range(0,INST_SIZE):
inst.append(ord(ibuf[i*INST_SIZE + j]))
instruments.append(inst)
return instruments
def convert_notes(nbuf):
notes = []
for i in range(0,NOTEBUF_SIZE):
note = []
for j in range(0,NOTE_SIZE):
note.append(ord(nbuf[i*NOTE_SIZE + j]))
notes.append(note)
return notes
#Used by gen_instrument
def gen_command(ID, X, Y):
''' This code was adapted from RoDoT's tracker code '''
Y += 16
word = Y
word <<= 5
word += X
word <<= 4
word += ID
word <<= 2
word +=1 #set LSB to 1 to indicate it's a command
return '0x%02x,'%word
#Generate actual bytes to define instrument
def gen_instrument(inst):
out = ''
out += gen_command(CMD_INSTRUMENT,inst[WAV],0)
out += gen_command(CMD_ARPEGGIO,inst[ARD],inst[ARS])
out += gen_command(CMD_VOLUME,inst[VOL],0)
out += gen_command(CMD_SLIDE,inst[SLD],inst[SLS])
out += gen_command(CMD_TREMOLO,inst[TRD],inst[TRS])
return out
#Generate actual bytes we need for a note in a pattern
def gen_note(note):
''' This code was adapted from RoDoT's tracker code '''
word = note[DUR]
word <<= 6
word += note[PIT]
word <<= 2
return '0x%02x,'%word
#For simplicity convert only one channel at a time
def convert_pattern(instruments,notes,channel):
''' Some of the boilerplate code here was adapted from RoDoT's tracker code '''
out = 'const unsigned int pat0cha%d[] PROGMEM = {'%channel
lastnote = None
for time in range(0,255*FPR): #Go through each time slot (inefficient, but I don't care)
for note in notes:
if note[CHA] == channel and note[TIM]*FPR == time:
if lastnote is not None:
#print_note(lastnote)
#If we have a time gap between the two notes
if lastnote[TIM]*FPR + lastnote[DUR] < note[TIM]*FPR:
#We must insert a gap of silence to fill the space
silent = [channel,lastnote[TIM]*FPR + lastnote[DUR] + 1, SILENCE, 0, note[TIM]*FPR - (lastnote[TIM]*FPR + lastnote[DUR])]
print 'adding silence of %d frames'%silent[DUR]
out += gen_note(silent)
#If the note length is too long to fit in the gap
if lastnote[TIM]*FPR + lastnote[DUR] > note[TIM]*FPR:
print 'shortening note from %d frames to %d frames'%(lastnote[DUR], note[TIM]*FPR - lastnote[TIM]*FPR)
lastnote[DUR] = note[TIM]*FPR - lastnote[TIM]*FPR#We must shorten the note to fit the gap perfectly
out += gen_note(lastnote)
#Check for instrument change, and insert commands to change it if so
if( lastnote[INS] != note[INS] ):
out += gen_instrument(instruments[note[INS]])
lastnote = note
else:
#insert commands to set the instrument
out += gen_instrument(instruments[note[INS]])
lastnote = note
if lastnote is not None:
#print_note(lastnote)
out += gen_note(lastnote)
out += '0x000};\n'
return out
def convert_track(file):
bytes = file.read() #Read all 1k of EEPROM
if bytes[0] == 'A':
instruments = bytes[1:INSTBUF_SIZE*INST_SIZE+1]
#print_bytes(instruments)
instruments = convert_instruments(instruments)
notes = bytes[INSTBUF_SIZE*INST_SIZE+1:INSTBUF_SIZE*INST_SIZE+1 + NOTEBUF_SIZE*NOTE_SIZE]
#print_bytes(notes)
notes = convert_notes(notes)
#print_note(notes[0])
result = convert_pattern(instruments,notes,0)
result += convert_pattern(instruments,notes,1)
result += convert_pattern(instruments,notes,2)
result += '''
void playTrack(){
gb.sound.playPattern(pat0cha0, 0);
gb.sound.playPattern(pat0cha1, 1);
gb.sound.playPattern(pat0cha2, 2);
}
'''
return result
else:
print 'Invalid or corrupted EEPROM file.'
return ''
if __name__ == '__main__':
if len(sys.argv) > 1:
f = open(sys.argv[len(sys.argv)-1], 'rb')
converted = convert_track(f)
f.close()
if converted != '':
if sys.argv[1] == '-o' and len(sys.argv) == 4:
f = open(sys.argv[2], 'wb')
f.write(converted)
else:
print converted
else:
print 'Wrong number of arguments. Usage:\n\t%s [-o <output file name>] <EEPROM save file>'%sys.argv[0]
python convert_track.py ATTOTRAK.SAV
#include <Gamebuino.h>
Gamebuino gb;
const unsigned int pat0cha0[] PROGMEM = {0x8005,0x800d,0x8281,0x8009,0x8011,0x1fc,0x178,0x2fc,0x278,0x3fc,0x178,0x1fc,0x168,0x2fc,0x278,0x6fc,0x284,0x6fc,0x254,0x4fc,0x268,0x5fc,0x154,0x4fc,0x248,0x3fc,0x15c,0x3fc,0x164,0x1fc,0x160,0x2fc,0x25c,0x2fc,0x254,0x278,0x1fc,0x184,0x2fc,0x28c,0x1fc,0x17c,0x2fc,0x284,0x3fc,0x178,0x1fc,0x168,0x1fc,0x170,0x164,0x000};
const unsigned int pat0cha1[] PROGMEM = {0x000};
const unsigned int pat0cha2[] PROGMEM = {0x000};
void playTrack(){
gb.sound.playPattern(pat0cha0, 0);
//gb.sound.playPattern(pat0cha1, 1);
//gb.sound.playPattern(pat0cha2, 2);
}
void setup() {
// put your setup code here, to run once:
gb.begin();
gb.titleScreen(F("Song Test"));
playTrack();
}
void loop() {
if( gb.update() ){
gb.display.cursorX = 0;
gb.display.cursorY = 0;
gb.display.print(F("Song Test \16\n\n\25 to restart playback"));
if( gb.buttons.pressed(BTN_A) ){
playTrack();
}
}
}
Thu Feb 23, 2017 10:06 am