OidosConvert.py 31.2 KB
Newer Older
Aske Simon Christensen's avatar
Aske Simon Christensen committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python

import sys
import zipfile
import XML
import struct
import ctypes
import math
import datetime

TOTAL_SEMITONES = 120
SAMPLERATE = 44100

class InputException(Exception):
	def __init__(self, message):
		Exception.__init__(self)
		self.message = message


class Volume:
	def __init__(self, left, right):
		self.left = left
		self.right = right

	def __mul__(self, other):
		return Volume(self.left * other.left, self.right * other.right)

	def __eq__(self, other):
		return self.left == other.left and self.right == other.right

	def isPanned(self):
		return self.left != self.right

def makeVolume(xvolume):
	v = float(xvolume)
	return Volume(v,v)

def makePanning(xpanning):
	p = float(xpanning)
	return Volume(math.sqrt(2.0 * (1.0 - p)), math.sqrt(2.0 * p))


class Instrument:
44
45
46
47
48
	NAMES = ["seed", "modes", "fat", "width",
			 "overtones", "sharpness", "harmonicity", "decaylow", "decayhigh",
			 "filterlow", "fslopelow", "fsweeplow", "filterhigh", "fslopehigh", "fsweephigh",
			 "gain", "attack", "release",
			 "dummy",
Aske Simon Christensen's avatar
Aske Simon Christensen committed
49
			 "q_decaydiff", "q_decaylow", "q_harmonicity", "q_sharpness", "q_width",
50
			 "q_f_low", "q_fs_low", "q_fsw_low", "q_f_high", "q_fs_high", "q_fsw_high",
Aske Simon Christensen's avatar
Aske Simon Christensen committed
51
52
53
			 "q_gain", "q_attack", "q_release"
	]

54
55
56
57
58
	def __init__(self, number, name, params, legacy):
		if legacy:
			# Duplicate filter sweep parameter
			params = params[:11] + [params[13]] + params[11:17] + [0.0] + params[20:27] + [params[29]] + params[27:33]

Aske Simon Christensen's avatar
Aske Simon Christensen committed
59
60
61
		names = Instrument.NAMES
		self.number = number
		self.name = name
62
		self.params = params
Aske Simon Christensen's avatar
Aske Simon Christensen committed
63
64
65
66
67
68
69
		for i,p in enumerate(self.params):
			self.__dict__[names[i]] = p
		self.volume = Volume(1.0, 1.0)
		self.maxsamples = 0
		self.title = "%02X|%s" % (self.number, self.name)


Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class Reverb:
	NAMES = ["mix", "pan", "delaymin", "delaymax", "delayadd",
			 "halftime", "filterlow", "filterhigh", "dampenlow", "dampenhigh",
			 "n", "seed", "dummy1", "dummy2", "dummy3",
			 "q_mixpan", "q_flow", "q_fhigh", "q_dlow", "q_dhigh"
	]

	def __init__(self, params):
		names = Reverb.NAMES
		self.params = (params + [0.0] * len(names))[:len(names)]
		for i,p in enumerate(self.params):
			self.__dict__[names[i]] = p

		self.p_min_delay   = math.floor(self.delaymin * 100 + 0.5) * 256
		self.p_max_delay   = math.floor(self.delaymax * 100 + 0.5) * 256
		self.p_add_delay   = math.floor(self.delayadd * 100 + 0.5) * 256
		self.p_filter_low  = min(1, quantize(math.pow(self.filterlow,  2), self.q_flow))
		self.p_filter_high = min(1, quantize(math.pow(self.filterhigh, 2), self.q_fhigh))
		self.p_dampen_low  = min(1, quantize(math.pow(self.dampenlow,  2), self.q_dlow))
		self.p_dampen_high = min(1, quantize(math.pow(self.dampenhigh, 2), self.q_dhigh))
90
		self.p_num_delays  = math.floor(self.n * 100 + 0.5) * 2
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
91
		self.p_seed        = math.floor(self.seed * 100 + 0.5) * 2048
Aske Simon Christensen's avatar
Aske Simon Christensen committed
92
93
		self.p_decay_mul   = math.pow(2.0, 1.0 / (self.halftime * SAMPLERATE))
		self.p_max_decay   = math.pow(self.p_decay_mul, -self.p_max_delay)
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
94
95

		mix = self.mix * 10 / math.sqrt(self.p_num_delays)
96
		self.p_volumes     = [quantize(mix * math.sqrt(1 + s - 2 * s * self.pan), self.q_mixpan) for s in [1,-1]]
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
97
98
99
100
101

	def __eq__(self, other):
		return all(p1 == p2 for p1,p2 in zip(self.params, other.params))


Aske Simon Christensen's avatar
Aske Simon Christensen committed
102
103
104
105
106
107
108
109
110
111
class Note:
	NOTEBASES = {
		"C": 0, "D": 2, "E": 4, "F": 5, "G": 7, "A": 9, "B": 11
	}

	NOTENAMES = {
		0: "C-", 1: "C#", 2: "D-", 3: "D#", 4: "E-", 5: "F-",
		6: "F#", 7: "G-", 8: "G#", 9: "A-", 10: "A#", 11: "B-"
	}

112
	def __init__(self, tname, column, line, songpos, pat, patline, note, instr, velocity):
Aske Simon Christensen's avatar
Aske Simon Christensen committed
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
		note = str(note)
		self.line = int(line)
		self.songpos = int(songpos)
		self.pat = int(pat)
		self.patline = patline
		if note == "OFF":
			self.off = True
			self.tone = None
			self.instr = 0
			self.velocity = 0
		else:
			self.off = False
			octave = int(note[2])
			notebase = Note.NOTEBASES[note[0]]
			sharp = int(note[1] == "#")
			self.tone = octave * 12 + notebase + sharp
			self.instr = int(str(instr), 16)
			try:
				self.velocity = 127 if str(velocity) == "" or str(velocity) == ".." else int(str(velocity), 16)
			except ValueError:
133
				print "Track '%s' column %d pattern %d line %d: Illegal velocity value '%s'" % (tname, column, pat, patline, str(velocity));
Aske Simon Christensen's avatar
Aske Simon Christensen committed
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
				self.velocity = 127

def notename(tone):
	return Note.NOTENAMES[tone%12] + str(tone/12)


def instplugins(xinst):
	xplugins = xinst.PluginProperties
	if xplugins:
		return xplugins
	return xinst.PluginGenerator

def isactive(xdevice):
	if not xdevice:
		return False
	if xdevice.IsActive.Value:
		return float(xdevice.IsActive.Value) != 0.0
	else:
		return str(xdevice.IsActive) == "true"


Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
155
156
157
158
159
160
def f2i(f):
	return struct.unpack('I', struct.pack('f', f))[0]

def i2f(i):
	return struct.unpack('f', struct.pack('I', i))[0]

Aske Simon Christensen's avatar
Aske Simon Christensen committed
161
162
163
164
def quantize(value, level):
	bit = 1 << int(math.floor(level * 31))
	mask = 0x100000000 - bit
	add = bit >> 1
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
165
	i = f2i(value)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
166
167
168
	i = (i + add) & mask
	if i == 0x80000000:
		i = 0x00000000
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
169
	q = i2f(i)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
170
171
172
173
174
175
176
177
178
179
180
181
182
	return q

def makeParamBlock(inst, uses_panning):
	modes = max(1, math.floor(0.5 + inst.modes * 100))
	fat = max(1, math.floor(0.5 + inst.fat * 100))
	seed = math.floor(0.5 + inst.seed * 100)
	overtones = math.floor(0.5 + inst.overtones * 100)
	decaydiff = inst.decayhigh - inst.decaylow
	decaylow = inst.decaylow
	harmonicity = inst.harmonicity * 2 - 1
	sharpness = inst.sharpness * 5 - 4
	width = 100 * math.pow(inst.width, 5)

183
184
	fsweeplow = -math.pow(inst.fsweeplow - 0.5, 3) * 100 * TOTAL_SEMITONES / SAMPLERATE
	fsweephigh = -math.pow(inst.fsweephigh - 0.5, 3) * 100 * TOTAL_SEMITONES / SAMPLERATE
Aske Simon Christensen's avatar
Aske Simon Christensen committed
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
	fslopelow = math.pow((1 - inst.fslopelow), 3)
	fslopehigh = -math.pow((1 - inst.fslopehigh), 3)
	filterlow = (inst.filterlow * 2 - 1) * TOTAL_SEMITONES
	filterhigh = (inst.filterhigh * 2 - 1) * TOTAL_SEMITONES

	gain = math.pow(4096, inst.gain - 0.25)
	attack = 2.0 if inst.attack == 0.0 else 1 / (inst.attack * inst.attack) / SAMPLERATE
	release = -(2.0 if inst.release == 0.0 else 1 / inst.release / SAMPLERATE)

	decaydiff = quantize(decaydiff, inst.q_decaydiff)
	decaylow = quantize(decaylow, inst.q_decaylow)
	harmonicity = quantize(harmonicity, inst.q_harmonicity)
	sharpness = quantize(sharpness, inst.q_sharpness)
	width = quantize(width, inst.q_width)
	filterlow = quantize(filterlow, inst.q_f_low)
	fslopelow = quantize(fslopelow, inst.q_fs_low)
201
	fsweeplow = quantize(fsweeplow, inst.q_fsw_low)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
202
203
	filterhigh = quantize(filterhigh, inst.q_f_high)
	fslopehigh = quantize(fslopehigh, inst.q_fs_high)
204
	fsweephigh = quantize(fsweephigh, inst.q_fsw_high)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
	gain = quantize(gain, inst.q_gain)
	attack = quantize(attack, inst.q_attack)
	release = quantize(release, inst.q_release)

	maxdecay = max(decaylow, decaylow + decaydiff)
	releasetime = inst.maxtime * SAMPLERATE + 1.0 / -release if release != 0.0 else float('inf')
	decaytime = (math.log(0.01, maxdecay)) * 4096 if maxdecay < 1.0 else float('inf')
	if math.isinf(releasetime) and math.isinf(decaytime):
		raise InputException("Instrument '%s' has infinite duration" % inst.title)
	inst.maxsamples = int(min(releasetime, decaytime) + 65535) & -65536

	left_volume = inst.volume.left * inst.velocity_quantum * 128
	right_volume = inst.volume.right * inst.velocity_quantum * 128
	volume = (left_volume + right_volume) / 2
	pan = right_volume / volume - 1
	volume = quantize(volume, 0.65)
	pan = quantize(pan, 0.55)

	return [int(modes), int(fat), int(seed), int(overtones),
			decaydiff, decaylow, harmonicity, sharpness, width,
225
			filterlow, filterhigh, fslopelow, fslopehigh, fsweeplow, fsweephigh,
226
			gain, inst.maxsamples, release, attack,
Aske Simon Christensen's avatar
Aske Simon Christensen committed
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
			volume] + ([pan] if uses_panning else [])


class Track:
	def __init__(self, number, column, name, notes, volume, instruments):
		self.number = number
		self.column = column
		self.name = name
		self.notes = notes
		self.volume = volume
		self.notemap = dict()
		self.tav_repr = dict()
		self.note_lengths = dict()

		self.title = "%s, column %d" % (self.name, self.column)

		self.labelname = ""
		for c in self.name:
			if c.isalnum() or c == '_':
				self.labelname += c

		self.instr = None
249
		self.latest_note = 0
Aske Simon Christensen's avatar
Aske Simon Christensen committed
250
251
252
253
254
255
		self.max_length = 0

		prev = None
		for note in notes:
			if prev is not None and not prev.off:
				if prev.instr is None:
256
					raise InputException("Track '%s' column %d pattern %d line %d: Undefined instrument" % (name, column, prev.pat, prev.patline));
Aske Simon Christensen's avatar
Aske Simon Christensen committed
257
				if self.instr is not None and prev.instr != self.instr:
258
					raise InputException("Track '%s' column %d pattern %d line %d: More than one instrument in track" % (name, column, prev.pat, prev.patline))
Aske Simon Christensen's avatar
Aske Simon Christensen committed
259
260
261
				self.instr = prev.instr
				length = note.line - prev.line
				if length < 0:
262
					raise InputException("Track '%s' column %d pattern %d: Reversed note order from %d to %d" % (name, column, prev.pat, prev.patline, note.patline))
Aske Simon Christensen's avatar
Aske Simon Christensen committed
263
				if prev.tone is None:
264
265
					raise InputException("Track '%s' column %d pattern %d line %d: Toneless note" % (name, column, prev.pat, prev.patline))
				self.latest_note = max(self.latest_note, prev.line)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
266
267
268
269
270
271
272
273
274
275
276
				self.max_length = max(self.max_length, length)
				tav = (prev.tone, prev.velocity)
				self.notemap[prev] = tav

				if length not in self.note_lengths:
					self.note_lengths[length] = 0
				self.note_lengths[length] += 1

			prev = note

		if not prev.off:
Aske Simon Christensen's avatar
Aske Simon Christensen committed
277
			 raise InputException("Track '%s' column %d pattern %d line %d: Note not terminated (insert OFF)" % (name, column, prev.pat, prev.patline))
Aske Simon Christensen's avatar
Aske Simon Christensen committed
278
279
280

		if len(self.note_lengths) == 1:
			for l in self.note_lengths:
281
				self.singular_length = l if l <= 255 else None
Aske Simon Christensen's avatar
Aske Simon Christensen committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295
		else:
			self.singular_length = None

		self.tavs = sorted(set(self.notemap.values()), key = (lambda (t,v) : (t,v)))
		for i,tav in enumerate(self.tavs):
			if tav[0] is None:
				raise InputException("Track '%s' has a toneless note" % name)
			self.tav_repr[tav] = i

		self.longest_sample = None
		self.sample_length_sum = None


class Music:
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
296
	def __init__(self, tracks, instruments, length, ticklength, n_reverb_tracks, reverb, master_volume):
Aske Simon Christensen's avatar
Aske Simon Christensen committed
297
298
299
300
		self.tracks = tracks
		self.instruments = instruments
		self.length = length
		self.ticklength = ticklength
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
301
302
		self.n_reverb_tracks = n_reverb_tracks
		self.reverb = reverb
Aske Simon Christensen's avatar
Aske Simon Christensen committed
303
304
		self.master_volume = master_volume

Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
		# Reorder instruments according to reverb
		self.instrument_map = dict()
		with_reverb = set(t.instr for t in self.tracks[:self.n_reverb_tracks])
		without_reverb = set(t.instr for t in self.tracks[self.n_reverb_tracks:])
		instruments_with_reverb = []
		instruments_without_reverb = []
		for instr in self.instruments:
			if instr is None:
				continue
			self.instrument_map[instr.number] = instr

			if instr.number in with_reverb:
				if instr.number in without_reverb:
					raise InputException("Instrument '%s' is used both with and without reverb" % instr.title)
				instruments_with_reverb.append(instr)
			if instr.number in without_reverb:
				instruments_without_reverb.append(instr)

		self.instruments = instruments_with_reverb + instruments_without_reverb
		self.n_reverb_instruments = len(instruments_with_reverb)

Aske Simon Christensen's avatar
Aske Simon Christensen committed
326
327
328
		# Calculate track order and velocity set
		self.track_order = []
		self.uses_panning = False
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
329
		for instr in self.instruments:
Aske Simon Christensen's avatar
Aske Simon Christensen committed
330
331
			velocities = set()
			instr.columns = 0
332
			instr.latest_note = 0
Aske Simon Christensen's avatar
Aske Simon Christensen committed
333
334
			volume = None
			for ti,track in enumerate(self.tracks):
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
335
				if track.instr == instr.number:
Aske Simon Christensen's avatar
Aske Simon Christensen committed
336
337
					self.track_order.append(ti)
					instr.columns += 1
338
					instr.latest_note = max(instr.latest_note, track.latest_note)
339
340
					track.volume *= self.master_volume
					if volume is not None and not track.volume == volume:
Aske Simon Christensen's avatar
Aske Simon Christensen committed
341
						raise InputException("Track '%s' has different volume/panning than previous tracks with same instrument" % track.title)
342
					volume = track.volume
Aske Simon Christensen's avatar
Aske Simon Christensen committed
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
					for t,v in track.tavs:
						velocities.add(v)
			instr.volume *= volume
			instr.velocities = sorted(list(velocities))
			if instr.volume.isPanned():
				self.uses_panning = True

			quantum = 128
			while quantum > 1:
				if all(v == 127 or v % quantum == 0 for v in instr.velocities):
					break
				quantum /= 2
			instr.velocity_quantum = quantum

		# Calculate longest sample
		self.max_maxsamples = 0
		self.max_total_samples = 0
360
		self.end_of_sound = 0
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
361
		for instr in self.instruments:
Aske Simon Christensen's avatar
Aske Simon Christensen committed
362
			instr.maxtime = 0
363
364
			tonecount = dict()
			velocitycount = dict()
Aske Simon Christensen's avatar
Aske Simon Christensen committed
365
			for ti,track in enumerate(self.tracks):
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
366
				if track.instr != instr.number:
Aske Simon Christensen's avatar
Aske Simon Christensen committed
367
368
369
370
					continue
				instr.maxtime = max(instr.maxtime, track.max_length * ticklength)
				for note in track.notes:
					if note.tone is not None:
371
372
						tonecount[note.tone] = tonecount[note.tone] + 1 if note.tone in tonecount else 1
						velocitycount[note.velocity] = velocitycount[note.velocity] + 1 if note.velocity in velocitycount else 1
Aske Simon Christensen's avatar
Aske Simon Christensen committed
373

374
375
376
			instr.tonecount = tonecount
			instr.velocitycount = velocitycount
			instr.tones = sorted(list(tonecount.keys()))
Aske Simon Christensen's avatar
Aske Simon Christensen committed
377
378
379
380
381
382
			instr.tonemap = dict()
			for i,t in enumerate(instr.tones):
				instr.tonemap[t] = i

			instr.paramblock = makeParamBlock(instr, self.uses_panning)

383
384
385
386
			instr.end_of_sound = instr.latest_note * ticklength * SAMPLERATE + instr.maxsamples
			if instr.number in with_reverb:
				instr.end_of_sound += reverb.halftime * 10 * SAMPLERATE

Aske Simon Christensen's avatar
Aske Simon Christensen committed
387
388
			self.max_maxsamples = max(self.max_maxsamples, instr.maxsamples)
			self.max_total_samples = max(self.max_total_samples, instr.maxsamples * len(instr.tones))
389
			self.end_of_sound = max(self.end_of_sound, instr.end_of_sound)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449

		self.datainit = None
		self.out = None

	def dataline(self, data):
		if len(data) > 0:
			line = self.datainit
			first = True
			for d in data:
				if not first:
					line += ","
				line += str(d)
				first = False
			line += "\n"
			self.out += line

	def comment(self, c):
		self.out += "\t; %s\n" % c

	def label(self, l):
		self.out += "%s:\n" % l

	def notelist(self, datafunc, trackterm, prefix):
		for ti in self.track_order:
			track = self.tracks[ti]
			self.comment(track.title)
			self.label("%s%s_%d" % (prefix, track.labelname, track.column))
			prev_n = None
			pat_data = []
			for n in track.notes:
				if track.singular_length and n.off and n.line > 0:
					continue
				if prev_n is not None:
					pat_data += datafunc(track,prev_n,n)
				if not n.off if prev_n is None else n.songpos != prev_n.songpos:
					self.dataline(pat_data)
					pat_data = []
					self.comment("Position %d, pattern %d" % (n.songpos, n.pat))
				prev_n = n
			pat_data += datafunc(track,prev_n,None)
			self.dataline(pat_data)
			self.dataline(trackterm)
			self.out += "\n"

	def lendata(self, t, pn, n):
		if n is None:
			return []
		step = n.line-pn.line
		return [-1 - (step >> 8), step & 255] if step > 127 else [step]

	def samdata(self, t, pn, n):
		if pn.off:
			return [0]
		return [1 + t.tav_repr[t.notemap[pn]]]

	def export(self):
		self.datainit = "\tdb\t"
		self.out = ""

		spt = int(self.ticklength * SAMPLERATE)
450
		total_samples = max((self.length * self.ticklength) * SAMPLERATE, self.end_of_sound)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
451
452
453
454
455
456
457

		def roundup(v):
			return (int(v) & -0x10000) + 0x10000

		global infile
		self.out += "; Music converted from %s %s\n" % (infile, str(datetime.datetime.now())[:-7])
		self.out += "\n"
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
458
		self.out += "%%define MUSIC_LENGTH %d\n" % self.length
459
		self.out += "%%define TOTAL_SAMPLES %d\n" % roundup(total_samples)
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
460
		self.out += "%%define MAX_TOTAL_INSTRUMENT_SAMPLES %d\n" % roundup(self.max_total_samples)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
461
		self.out += "\n"
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
462
463
464
465
466
467
468
469
470
471
472
473
474
475
		self.out += "%%define SAMPLES_PER_TICK %d\n" % spt
		self.out += "%%define TICKS_PER_SECOND %.9f\n" % (1.0 / self.ticklength)
		self.out += "\n"
		self.out += "%%define NUM_TRACKS_WITH_REVERB %d\n" % self.n_reverb_instruments
		self.out += "%%define NUM_TRACKS_WITHOUT_REVERB %d\n" % (len(self.instruments) - self.n_reverb_instruments)

		if self.n_reverb_instruments > 0:
			reverb = self.reverb
			self.out += "\n"
			self.out += "%%define REVERB_NUM_DELAYS   %d\n" % reverb.p_num_delays
			self.out += "%%define REVERB_MIN_DELAY    %d\n" % reverb.p_min_delay
			self.out += "%%define REVERB_MAX_DELAY    %d\n" % reverb.p_max_delay
			self.out += "%%define REVERB_ADD_DELAY    %d\n" % reverb.p_add_delay
			self.out += "%%define REVERB_RANDOMSEED   %d\n" % reverb.p_seed
Aske Simon Christensen's avatar
Aske Simon Christensen committed
476
477
			self.out += "%%define REVERB_MAX_DECAY    %.9f\n" % reverb.p_max_decay
			self.out += "%%define REVERB_DECAY_MUL    %.9f\n" % reverb.p_decay_mul
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
478
479
480
481
482
483
			self.out += "%%define REVERB_FILTER_HIGH  0x%08X ; %.9f\n" % (f2i(reverb.p_filter_high), reverb.p_filter_high)
			self.out += "%%define REVERB_FILTER_LOW   0x%08X ; %.9f\n" % (f2i(reverb.p_filter_low), reverb.p_filter_low)
			self.out += "%%define REVERB_DAMPEN_HIGH  0x%08X ; %.9f\n" % (f2i(reverb.p_dampen_high), reverb.p_dampen_high)
			self.out += "%%define REVERB_DAMPEN_LOW   0x%08X ; %.9f\n" % (f2i(reverb.p_dampen_low), reverb.p_dampen_low)
			self.out += "%%define REVERB_VOLUME_LEFT  0x%08X ; %.9f\n" % (f2i(reverb.p_volumes[0]), reverb.p_volumes[0])
			self.out += "%%define REVERB_VOLUME_RIGHT 0x%08X ; %.9f\n" % (f2i(reverb.p_volumes[1]), reverb.p_volumes[1])
Aske Simon Christensen's avatar
Aske Simon Christensen committed
484
485
486
487
488
489
490

		if self.uses_panning:
			self.out += "\n%define USES_PANNING\n"

		# Instrument parameters
		self.out += "\n\n\tsection iparam data align=4\n"
		self.out += "\n_InstrumentParams:\n"
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
491
492
		for instr in self.instruments:
			self.label(".i%02d" % instr.number)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
493
494
495
496
497
498
499
			self.comment(instr.title)
			self.out += "\tdd\t"
			first = True
			for p in instr.paramblock:
				if not first:
					self.out += ","
				if isinstance(p, float):
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
500
					self.out += "0x%08X" % f2i(p)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
501
502
503
504
505
506
507
508
509
				else:
					self.out += "%d" % p
				first = False
			self.out += "\n"
		self.out += "\n"

		# Instrument tones
		self.out += "\n\n\tsection itones data align=1\n"
		self.out += "\n_InstrumentTones:\n"
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
510
511
		for instr in self.instruments:
			self.label(".i%02d" % instr.number)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
512
513
514
515
516
517
518
519
520
521
522
523
524
525
			self.comment(instr.title)
			self.out += "\tdb\t"
			prev_tone = 0
			for tone in instr.tones:
				self.out += str(tone - prev_tone) + ","
				prev_tone = tone
				first = False
			self.out += "%d\n" % (-129 + instr.columns)

		# Track data
		self.out += "\n\n\tsection trdata data align=1\n"
		self.out += "\n_TrackData:\n"
		for ti in self.track_order:
			track = self.tracks[ti]
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
526
			instr = self.instrument_map[track.instr]
Aske Simon Christensen's avatar
Aske Simon Christensen committed
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570

			self.label(".t_%s_%d" % (track.labelname, track.column))
			self.comment(track.title)

			# List tones and velocities
			tavdata = [track.singular_length if track.singular_length else 0]
			prev_tone_id = 0
			for t,v in track.tavs:
				tone_id = instr.tonemap[t]
				vol = (v + instr.velocity_quantum / 2) / instr.velocity_quantum
				tavdata += [tone_id - prev_tone_id, vol]
				prev_tone_id = tone_id
			tavdata += [-128]
			self.dataline(tavdata)

		# Lengths of notes
		self.out += "\n\tsection notelen data align=1\n"
		self.out += "\n_NoteLengths:\n"
		self.notelist(self.lendata, [0], "L_")

		# Samples for notes
		self.out += "\n\tsection notesamp data align=1\n"
		self.out += "\n_NoteSamples:\n"
		self.notelist(self.samdata, [], "S_")

		return self.out

	def makeDeltas(self, init_delta, lines_per_beat):
		beats_per_line = 1.0/lines_per_beat
		deltas = []
		for t in self.tracks:
			tdeltas = []
			delta = init_delta
			note_i = 0
			for p in range(0, self.length):
				while t.notes[note_i].line <= p:
					if not t.notes[note_i].off:
						delta = p * beats_per_line
					note_i += 1
				tdeltas.append(delta)
			deltas.append(tdeltas)
		return deltas


571
def extractTrackNotes(xsong, tr, column):
Aske Simon Christensen's avatar
Aske Simon Christensen committed
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
	outside_pattern = 0
	xsequence = xsong.PatternSequence.PatternSequence
	if not xsequence:
		xsequence = xsong.PatternSequence.SequenceEntries.SequenceEntry
	xpatterns = xsong.PatternPool.Patterns.Pattern
	tname = str(xsong.Tracks.SequencerTrack[tr].Name)

	notes = []

	pattern_top = 0
	prev_instr = None
	for posn,xseq in enumerate(xsequence):
		patn = int(xseq.Pattern)
		xpat = xpatterns[patn]
		nlines = int(xpat.NumberOfLines)
		if tr in [int(xmt) for xmt in xseq.MutedTracks.MutedTrack]:
588
			off = Note(tname, column, pattern_top, posn, patn, 0, "OFF", None, 127)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
589
590
591
592
593
594
595
			notes.append(off)
		else:
			xtrack = xpat.Tracks.PatternTrack[tr]
			for xline in xtrack.Lines.Line:
				index = int(xline("index"))
				if index < nlines:
					line = pattern_top + index
596
					xcol = xline.NoteColumns.NoteColumn[column - 1]
Aske Simon Christensen's avatar
Aske Simon Christensen committed
597
598
599
600
					if xcol.Note and str(xcol.Note) != "---":
						instr = str(xcol.Instrument)
						if instr == ".." and str(xcol.Note) != "OFF":
							if prev_instr is None:
601
								raise InputException("Track '%s' column %d pattern %d line %d: Unspecified instrument" % (tname, column, patn, index))
Aske Simon Christensen's avatar
Aske Simon Christensen committed
602
603
604
							instr = prev_instr
						prev_instr = instr

605
						note = Note(tname, column, line, posn, patn, index, xcol.Note, instr, xcol.Volume)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
606
607
608
						notes.append(note)

						if (note.velocity == 0 or note.velocity > 127) and not note.off:
609
							raise InputException("Track '%s' column %d pattern %d line %d: Illegal velocity value" % (tname, column, patn, index))
Aske Simon Christensen's avatar
Aske Simon Christensen committed
610
611
612
613

					# Check for illegal uses of panning, delay and effect columns
					def checkColumn(x, allow_zero, msg):
						if x and not (str(x) == "" or str(x) == ".." or (allow_zero and str(x) == "00")):
614
							raise InputException("Track '%s' column %d pattern %d line %d: %s" % (tname, column, patn, index, msg))
Aske Simon Christensen's avatar
Aske Simon Christensen committed
615
616
617
618
619
620
621
622
623
					checkColumn(xcol.Panning, False, "Panning column used")
					checkColumn(xcol.Delay, True, "Delay column used")
					for xeff in xline.EffectColumns.EffectColumn.Number:
						checkColumn(xeff, True, "Effect column used")
				else:
					outside_pattern += 1
		pattern_top += nlines

	# Add inital OFF and remove redundant OFFs
624
	if len(notes) > 0 and notes[0].line == 0:
Aske Simon Christensen's avatar
Aske Simon Christensen committed
625
626
627
		notes2 = []
		off = False
	else:
628
		notes2 = [Note(tname, column, 0, 0, int(xsequence[0].Pattern), 0, "OFF", 0, 127)]
Aske Simon Christensen's avatar
Aske Simon Christensen committed
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
		off = True
	for n in notes:
		if n.off:
			if not off:
				notes2.append(n)
				off = True
		else:
			notes2.append(n)
			off = False

	if outside_pattern > 0:
		print " * Track '%s': %d note%s outside patterns ignored" % (tname, outside_pattern, "s" * (outside_pattern > 1))

	return notes2

Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
644
645
646
647
648
649
def pickupReverb(xdevices, reverb, tname, ticklength):
	xplugin = xdevices.AudioPluginDevice
	if len(xplugin) > 1:
		raise InputException("Track '%s' has more than one plugin device" % tname);

	if isactive(xplugin):
650
651
		plugin_id = str(xplugin.PluginIdentifier)
		if plugin_id not in ["MetaEffect", "OidosReverb"]:
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
652
653
654
655
656
657
658
659
660
661
			raise InputException("Track '%s' has an unknown plugin device" % tname);

		params = [float(x) for x in xplugin.Parameters.Parameter.Value]
		new_reverb = Reverb(params)

		if reverb is not None and not new_reverb == reverb:
			raise InputException("Track '%s' has different reverb from an earlier track" % tname);
		reverb = new_reverb

	return reverb
Aske Simon Christensen's avatar
Aske Simon Christensen committed
662
663
664

def makeTracks(xsong, ticklength):
	instruments = []
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
665
666
667
	reverb_tracks = []
	non_reverb_tracks = []
	reverb = None
Aske Simon Christensen's avatar
Aske Simon Christensen committed
668
669
670
671

	for ii,xinst in enumerate(xsong.Instruments.Instrument):
		params = [float(v) for v in instplugins(xinst).PluginDevice.Parameters.Parameter.Value]
		if params:
672
673
			legacy = str(instplugins(xinst).PluginDevice.PluginIdentifier) == "MetaSynth"
			instrument = Instrument(ii, str(xinst.Name), params, legacy)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
674
675
676
677
678
679
			instrument.volume = makeVolume(instplugins(xinst).Volume)
			instruments.append(instrument)
			
		else:
			instruments.append(None)

680
681
682
683
684
685
686
687
688
689
690
691
692
693
	for xgrouptrack in xsong.Tracks.SequencerGroupTrack:
		tname = xgrouptrack.Name
		xdevices = xgrouptrack.FilterDevices.Devices
		for xmixer in xdevices.GroupTrackMixerDevice:
			if isactive(xmixer):
				if float(xmixer.Volume.Value) != 1.0 or float(xmixer.PostVolume.Value) != 1.0:
					raise InputException("Group track '%s' has non-zero volume" % tname);
				if float(xmixer.Panning.Value) != 0.5 or float(xmixer.PostPanning.Value) != 0.5:
					raise InputException("Group track '%s' has non-center panning" % tname);
		if isactive(xdevices.SendDevice):
			raise InputException("Group track '%s' uses Send" % tname);
		if isactive(xdevices.AudioPluginDevice):
			raise InputException("Group track '%s' uses reverb" % tname);

Aske Simon Christensen's avatar
Aske Simon Christensen committed
694
	for tr,xtrack in enumerate(xsong.Tracks.SequencerTrack):
Aske Simon Christensen's avatar
Aske Simon Christensen committed
695
696
697
		if str(xtrack.State) != "Active":
			continue

Aske Simon Christensen's avatar
Aske Simon Christensen committed
698
699
700
701
702
703
704
705
		tname = str(xtrack.Name)
		ncols = int(xtrack.NumberOfVisibleNoteColumns)
		xdevices = xtrack.FilterDevices.Devices
		xdevice = xdevices.SequencerTrackDevice
		if not xdevice:
			xdevice = xdevices.TrackMixerDevice
		volume = makeVolume(xdevice.Volume.Value)
		volume *= makePanning(xdevice.Panning.Value)
706
707
		volume *= makeVolume(xdevice.PostVolume.Value)
		volume *= makePanning(xdevice.PostPanning.Value)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
708
		while isactive(xdevices.SendDevice):
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
709
710
			if isactive(xdevices.AudioPluginDevice):
				raise InputException("Track '%s' uses both reverb and send" % tname);
Aske Simon Christensen's avatar
Aske Simon Christensen committed
711
712
713
714
715
716
717
718
719
720
721
			if str(xdevices.SendDevice.MuteSource) != "true":
				raise InputException("Track '%s' uses send without Mute Source" % tname);
			volume *= makeVolume(xdevices.SendDevice.SendAmount.Value)
			volume *= makePanning(xdevices.SendDevice.SendPan.Value)
			dest = int(float(xdevices.SendDevice.DestSendTrack.Value))
			xdevices = xsong.Tracks.SequencerSendTrack[dest].FilterDevices.Devices
			xdevice = xdevices.SequencerSendTrackDevice
			if not xdevice:
				xdevice = xdevices.SendTrackMixerDevice
			volume *= makeVolume(xdevice.Volume.Value)
			volume *= makePanning(xdevice.Panning.Value)
722
723
			volume *= makeVolume(xdevice.PostVolume.Value)
			volume *= makePanning(xdevice.PostPanning.Value)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
724

725
		for column in range(1, ncols + 1):
Aske Simon Christensen's avatar
Aske Simon Christensen committed
726
727
728
			if str(xtrack.NoteColumnStates.NoteColumnState[column - 1]) != "Active":
				continue

729
			notes = extractTrackNotes(xsong, tr, column)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
730
731
732
733
734
735

			track_instrs = []
			for note in notes:
				if not note.off:
					instr = instruments[note.instr]
					if instr is None:
736
						raise InputException("Track '%s' column %d pattern %d line %d: Undefined instrument (%d)" % (tname, column, note.pat, note.patline, note.instr));
Aske Simon Christensen's avatar
Aske Simon Christensen committed
737
738
739
740
					if note.instr not in track_instrs:
						track_instrs.append(note.instr)

			for instr in track_instrs:
741
				track = Track(tr, column, tname, notes, volume, instruments)
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
742
743
				if isactive(xdevices.AudioPluginDevice):
					reverb_tracks.append(track)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
744
				else:
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
745
					non_reverb_tracks.append(track)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
746
	
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
747
		reverb = pickupReverb(xdevices, reverb, tname, ticklength)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
748
749
750

	for xtrack in xsong.Tracks.SequencerSendTrack:
		xdevices = xtrack.FilterDevices.Devices
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
751
752
		if xdevices.AudioPluginDevice:
			reverb = pickupReverb(xdevices, reverb, tname, ticklength)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
753

Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
754
	return (reverb_tracks + non_reverb_tracks), len(reverb_tracks), reverb, instruments
Aske Simon Christensen's avatar
Aske Simon Christensen committed
755
756
757
758
759
760
761
762
763
764
765
766

def makeMusic(xsong):
	xgsd = xsong.GlobalSongData
	if xgsd.PlaybackEngineVersion and int(xgsd.PlaybackEngineVersion) >= 4:
		lines_per_minute = float(xgsd.BeatsPerMin) * float(xgsd.LinesPerBeat)
		print "New timing format: %d ticks per minute" % lines_per_minute
	else:
		lines_per_minute = float(xgsd.BeatsPerMin) * 24.0 / float(xgsd.TicksPerLine)
		print "Old timing format: %d ticks per minute" % lines_per_minute
	ticklength = 60.0 / lines_per_minute
	print

Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
767
	tracks,n_reverb_tracks,reverb,instruments = makeTracks(xsong, ticklength)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787

	xpositions = xsong.PatternSequence.PatternSequence.Pattern
	if not xpositions:
		xpositions = xsong.PatternSequence.SequenceEntries.SequenceEntry.Pattern
	xpatterns = xsong.PatternPool.Patterns.Pattern
	length = 0
	for xpos in xpositions:
		patn = int(xpos)
		xpat = xpatterns[patn]
		nlines = int(xpat.NumberOfLines)
		length += nlines

	xmstdev = xsong.Tracks.SequencerMasterTrack.FilterDevices.Devices.SequencerMasterTrackDevice
	if not xmstdev:
		xmstdev = xsong.Tracks.SequencerMasterTrack.FilterDevices.Devices.MasterTrackMixerDevice
	master_volume = makeVolume(xmstdev.Volume.Value)
	master_volume *= makePanning(xmstdev.Panning.Value)
	master_volume *= makeVolume(xmstdev.PostVolume.Value)
	master_volume *= makePanning(xmstdev.PostPanning.Value)

Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
788
	return Music(tracks, instruments, length, ticklength, n_reverb_tracks, reverb, master_volume)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
789
790


791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
def printMusicStats(music, ansi):
	def form(s):
		f = ""
		for c in s:
			if c == 'T':
				f += "\033[33m%s\033[0m" if ansi else "%s"
			elif c == 'V':
				f += "\033[31m%02X\033[0m" if ansi else "%02X"
			elif c == 'L':
				f += "\033[36m%d\033[0m" if ansi else "%d"
			elif c == 'N':
				f += ":%d" if ansi else ":%d"
			elif c == 'H':
				f += "\033[32m%s\033[0m" if ansi else "%s"
			elif c == 'X':
				f += "\033[34mx\033[0m" if ansi else "x"
			elif c == 'I':
				f += "\033[31m(%d bits)\033[0m" if ansi else "(%d bits)"
			elif c == 'B':
				f += "\033[35m%.f\033[0m" if ansi else "%.f"
			elif c == 'D':
				f += "\033[35m%dm%02ds\033[0m" if ansi else "%dm%02ds"
			else:
				f += c
		return f

Aske Simon Christensen's avatar
Aske Simon Christensen committed
817
	print "Music length: %d ticks at %0.2f ticks per minute" % (music.length, 60.0 / music.ticklength)
818

819
820
	max_memory_mb = 0
	max_memory_instr = None
821
	total_burden = 0
Aske Simon Christensen's avatar
Aske Simon Christensen committed
822
	ii = None
823
	for ti,tn in enumerate(music.track_order):
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
824
825
826
827
828
829
830
		if music.n_reverb_tracks > 0 and ti == 0:
			print
			print "Tracks with reverb:"
		if music.n_reverb_tracks > 0 and ti == music.n_reverb_tracks:
			print
			print "Tracks without reverb:"

831
		track = music.tracks[tn]
Aske Simon Christensen's avatar
Aske Simon Christensen committed
832
833
834
		if track.instr != ii:
			print
			ii = track.instr
Aske Simon Christensen's avatar
Reverb    
Aske Simon Christensen committed
835
			instr = music.instrument_map[ii]
Aske Simon Christensen's avatar
Aske Simon Christensen committed
836
837
			modes = instr.paramblock[0]
			fat = instr.paramblock[1]
838
			longest = float(instr.paramblock[16]) / SAMPLERATE
839
840
841
842
843
			memory = len(instr.tones) * longest
			memory_mb = memory * SAMPLERATE * 16 / 1024 / 1024
			if memory_mb > max_memory_mb:
				max_memory_mb = memory_mb
				max_memory_instr = instr.title
Aske Simon Christensen's avatar
Aske Simon Christensen committed
844
			burden = modes * fat * len(instr.tones) * longest
845
			total_burden += burden
846
			print form("H") % instr.title
847
			print " Memory:    " + form(" tones X longest = %d X %.3f = B (B %s)") % (len(instr.tones), longest, memory, memory_mb, "MB")
848
			print " Burden:    " + form(" modes X fat X tones X longest = %d X %d X %d X %.3f = B") % (modes, fat, len(instr.tones), longest, burden)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
849
850
			tones = ""
			for t in instr.tones:
851
				tones += form(" TN") % (notename(t), instr.tonecount[t])
Aske Simon Christensen's avatar
Aske Simon Christensen committed
852
853
854
			print " Tones:     " + tones
			velocities = ""
			for v in instr.velocities:
855
				velocities += form(" VN") % (v, instr.velocitycount[v])
Aske Simon Christensen's avatar
Aske Simon Christensen committed
856
			vbits = int(round(math.log(128 / instr.velocity_quantum, 2)))
857
			print " Velocities:" + velocities + form(" I") % vbits
858
859
860
861
862
			v2 = track.volume * track.volume
			volume = math.log((v2.left + v2.right) / 2, 10) * 10
			panning = round(v2.right / (v2.left + v2.right) * 100) - 50
			ptext = "Center" if panning == 0 else "%d L" % -panning if panning < 0 else "%d R" % panning
			print " Volume:     %+.1f dB  %s" % (volume, ptext)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
863

864
		print form(" H") % track.title
Aske Simon Christensen's avatar
Aske Simon Christensen committed
865
866
867

		lengths = ""
		for l in sorted(track.note_lengths.keys()):
868
			lengths += form(" LN") % (l, track.note_lengths[l])
Aske Simon Christensen's avatar
Aske Simon Christensen committed
869
870
871
872
873
874
875
876
		print "  Lengths:  " + lengths

		tnotes = ""
		for t,v in track.tavs:
			num_notes = 0
			for n in [note for note in track.notes if not note.off and note.instr == track.instr]:
				if track.notemap[n] == (t,v):
					num_notes += 1
877
			tnotes += form(" T/VN") % (notename(t), v, num_notes)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
878
879
		print "  Notes:    " + tnotes

880
881
	seconds = int(round(total_burden / 5000))
	print
882
	print "Maximum instrument memory: " + form("B %s (H)") % (max_memory_mb, "MB", max_memory_instr)
883
884
	print "Total burden: " + form("B (approximately D on a fast %s)") % (total_burden, seconds / 60, seconds % 60, "CPU")

Aske Simon Christensen's avatar
Aske Simon Christensen committed
885
886
887
888
889
890
891

def writefile(filename, s):
	f = open(filename, "wb")
	f.write(s)
	f.close()
	print "Wrote file %s" % filename

892
893
894
895
896
897
898
ansi = False
files = []
for a in sys.argv[1:]:
	if a == "-ansi":
		ansi = True
	else:
		files.append(a)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
899

900
901
if len(files) != 2:
	print "Usage: %s [-ansi] <input xrns file> <output asm file>" % sys.argv[0]
Aske Simon Christensen's avatar
Aske Simon Christensen committed
902
903
	sys.exit(1)

904
905
infile = files[0]
outfile = files[1]
Aske Simon Christensen's avatar
Aske Simon Christensen committed
906
907
908
909
910

x = XML.makeXML(zipfile.ZipFile(infile).read("Song.xml"))
try:
	music = makeMusic(x.RenoiseSong)
	print
911
	printMusicStats(music, ansi)
Aske Simon Christensen's avatar
Aske Simon Christensen committed
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
	print

	writefile(outfile, music.export())

	if len(sys.argv) > 4:
		deltas = music.makeDeltas(0.0, 1.0)
		syncfile = sys.argv[3]
		header = ""
		header += struct.pack('I', 1)
		header += struct.pack('I', music.length*4)
		header += struct.pack('I', len(music.tracks)*music.length*4)
		body = ""
		for t,tdeltas in enumerate(deltas):
			body += struct.pack("%df" % len(tdeltas), *tdeltas)
		data = header + body
		writefile(syncfile, data)

except InputException, e:
	print "Error in input song: %s" % e.message