working on music tools

This commit is contained in:
Steven Hugg 2017-03-31 13:25:18 -04:00
parent 4a5c6f86ed
commit 49cc581b2b
8 changed files with 509 additions and 12 deletions

224
presets/vicdual/music.c Normal file

File diff suppressed because one or more lines are too long

View File

@ -27,7 +27,26 @@ byte __at (0xe800) tileram[256][8];
#define START2 !(input3 & 0x20)
#define VSYNC (input1 & 0x8)
// GAME CODE
inline void set8910(byte reg, byte data) {
ay8910_reg = reg;
ay8910_data = data;
}
typedef enum {
AY_PITCH_A_LO, AY_PITCH_A_HI,
AY_PITCH_B_LO, AY_PITCH_B_HI,
AY_PITCH_C_LO, AY_PITCH_C_HI,
AY_NOISE_PERIOD,
AY_ENABLE,
AY_ENV_VOL_A,
AY_ENV_VOL_B,
AY_ENV_VOL_C,
AY_ENV_PERI_LO, AY_ENV_PERI_HI,
AY_ENV_SHAPE
} AY8910Register;
// STARTUP CODE
void main();
void gsinit();
@ -61,16 +80,8 @@ __asm
__endasm;
}
// SOUND CODE
// PLATFORM CODE
inline void set8910(byte reg, byte data) {
ay8910_reg = reg;
ay8910_data = data;
}
////////
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register#Galois_LFSRs
static word lfsr = 1;
word rand() {
unsigned lsb = lfsr & 1; /* Get LSB (i.e., the output bit). */
@ -132,7 +143,7 @@ void draw_box(byte x, byte y, byte x2, byte y2, const char* chars) {
}
}
//
// GAME CODE
void main() {
palette = 0;

View File

@ -113,7 +113,7 @@ const char* const AY8910MASKS[14] = {
"11111111", " 1111",
" 11111",
" cbaCBA",
" E11111", " E11111", " E11111",
" E1111", " E1111", " E1111",
"11111111", " 1111",
" CALH"
};

View File

@ -7,6 +7,7 @@ var VICDUAL_PRESETS = [
{id:'soundtest.c', name:'Sound Test'},
{id:'snake1.c', name:'Snake Game (Prototype)'},
{id:'snake2.c', name:'Snake Game (Full)'},
{id:'music.c', name:'Music Example'},
];
var VicDualPlatform = function(mainElement) {

BIN
tools/entertainer.mid Normal file

Binary file not shown.

156
tools/midi2song.py Normal file
View File

@ -0,0 +1,156 @@
#!/usr/bin/python
import sys, string, math
import mido
min_note = 21
max_note = 21+63
max_voices = 3
one_voice_per_channel = 1
tempo = 48
compress = 0
transpose = 0
fn = sys.argv[1]
mid = mido.MidiFile(fn)
def hex2(n):
return '0x%02x'%n
def only_notes(s):
for ch in s:
if ord(ch) == 0xff:
return False
return True
def find_common_substrings(s):
results = {}
for l in range(64, 6, -1):
for i in range(0,len(s)-l*2):
match = s[i:i+l]
if not only_notes(match):
continue
count = 0
j = i+l
while j < len(s):
p = s.find(match, j)
if p > 0:
count += 1
j = p+l
else:
break
if count:
n = count*(l-1)-1
if not results.get(i) or n > results[i][0]:
results[i] = (n,l)
return results
def get_best_substring(ss):
best = (0,0,0)
for k,v in ss.items():
if v[0] > best[2]:
best = (k,v[1],v[0])
return best
def offset2str(ofs):
return chr(ofs & 0xff) + chr((ofs >> 8) & 0xff)
#return chr(0xc0 | (ofs & 0x3f)) + chr(0xc0 | ((ofs >> 6) & 0x3f))
g_code = 0xc1
g_subs = []
def replace_substrings(s, bss):
global g_code
i,l,n = bss
count = (n+1)/(l-1)
match = s[i:i+l]
print i,l,n,count,repr(match)
r = s[0:i]
while i<len(s):
r += chr(g_code)
p = s.find(match,i+l)
if p < 0:
p = len(s)
r += s[i+l:p]
i = p
g_subs.append(match + chr(0xff))
g_code += 1
print len(s), len(r)+n+l
assert len(s) == len(r)+n+l
return r
def channels_for_track(track):
channels = set()
for msg in track:
if msg.type == 'note_on':
channels.add(msg.channel)
return list(channels)
if len(sys.argv) < 3:
print mid
print mid.length, 'seconds'
for i, track in enumerate(mid.tracks):
print('Track {}: {} ({}) {}'.format(i, track.name, len(track), channels_for_track(track)))
#for msg in track:
# print(msg)
else:
if len(sys.argv) > 3:
transpose = int(sys.argv[3])
gtime = 0
curtime = 0
nnotes = 0
nvoices = 0
curchans = 0
channels = [int(x) for x in string.split(sys.argv[2], ',')]
print
print "// %s %s" % (mid, channels)
output = []
for msg in mid:
gtime += msg.time * tempo
if msg.type == 'note_on' and msg.channel in channels:
note = msg.note + transpose
vel = msg.velocity
t = int(math.ceil(gtime))
if vel > 0:
while curtime < t:
dt = min(63, t-curtime)
curtime += dt
if nnotes > 0:
nvoices = 0
curchans = 0
output.append(dt+128)
if note >= min_note and note <= max_note and nvoices < max_voices:
if not (one_voice_per_channel and (curchans & (1<<msg.channel))):
n = note - min_note
output.append(n)
nnotes += 1
nvoices += 1
curchans |= 1<<msg.channel
output.append(0xff)
print string.join([hex2(x) for x in output], ',')
if compress:
# replace common substrings
bout = ''.join(chr(i) for i in output)
for iter in range(0,32):
ss = find_common_substrings(bout)
bss = get_best_substring(ss)
print bss
if bss[1] == 0:
break
bout = replace_substrings(bout, bss)
print repr(bout)
# build substring table
ofs = (len(g_subs)+1)*2
zout = offset2str(ofs)
ofs += len(bout)
for ss in g_subs:
ofs += len(ss)
zout += offset2str(ofs)
# output strings
zout += bout
for ss in g_subs:
zout += ss
# print output
print string.join([hex2(ord(x)) for x in zout], ',')
print "// compressed %d -> %d bytes" % (len(output), len(zout))

34
tools/mknotes.py Normal file
View File

@ -0,0 +1,34 @@
#!/usr/bin/python
import sys, math
test_notes = int(sys.argv[1]) or 49
final_notes = int(sys.argv[2]) or 64
basehz = 3579545/32.0
results = []
for a440 in range(4300,4500):
error = 0
for note in range(1,test_notes):
notehz = a440 / 10.0 * math.pow(2.0, (note - 49) / 12.0);
period = int(round(basehz / notehz))
tonehz = basehz / period
error += abs(notehz-tonehz)
#print a440,note,notehz,notehz-tonehz,period
#if a440 == 4405:
# print '%d,%f,%d' % (note,tonehz-notehz,period)
results.append((error, a440))
results.sort()
best_error, best_a440 = results[0]
best_a440 /= 10.0
print '//', best_a440, best_error, test_notes
print "const int note_table[%d] = {" % final_notes
for note in range(0,final_notes):
notehz = best_a440 * math.pow(2.0, (note - 49) / 12.0);
period = int(round(basehz / notehz))
print '%d,' % period,
print "};"

71
tools/svg2vector.py Executable file
View File

@ -0,0 +1,71 @@
#!/usr/bin/python
import sys
from xml.dom import minidom
def textToFloat(name):
if name:
return float(name)
else:
return None
def textToColor(name,opacity):
if name is None or not name[0] == '#':
return None
color = int(name[1:],16) << 8
if opacity:
print 'opacity',opacity
return color
svg_file = sys.argv[1]
doc = minidom.parse(svg_file) # parseString also exists
groups = doc.getElementsByTagName('g')
for grp in groups:
groupID = grp.getAttribute('id')
paths = grp.getElementsByTagName('path')
print groupID,paths
if len(paths):
for path in paths:
shape = {}
d = path.getAttribute('d')
styleAttrs = {}
style = path.getAttribute('style')
sarr = style.split(';')
for s in sarr:
nvarr = s.split(':', 2)
if len(nvarr) == 2:
styleAttrs[nvarr[0]] = nvarr[1]
print path,d,styleAttrs
shape['pts'] = []
shape['currentColor'] = textToColor(styleAttrs.get("stroke"), styleAttrs.get("stroke-opacity"))
shape['currentWidth'] = textToFloat(styleAttrs.get("strokeWidth"))
hidden = "none" == styleAttrs.get("display")
cmds = d.split(' ')
x = 0
y = 0
i0 = 0
start = True
for cmd in cmds:
print cmd
ch = cmd[0]
if ch == 'm' or ch == 'l':
start = True
elif ch == 'z':
origin = shape['pts'][i0]
shape.lineTo(origin.x, origin.y)
elif ch == 'c':
skip = True
else:
xy = cmd.split(',')
print xy
x += float(xy[0])
y -= float(xy[1])
if start or hidden:
shape.moveTo(x,y)
else:
shape.lineTo(x,y)
start = False
doc.unlink()