Skip to content

Commit

Permalink
write include file with song titles/authors
Browse files Browse the repository at this point in the history
first part of #38
  • Loading branch information
pinobatch committed Mar 8, 2019
1 parent 80fd9ea commit 9844852
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 3 deletions.
17 changes: 15 additions & 2 deletions src/musicseq.pently
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
# 3. This notice may not be removed or altered from any source distribution.
#

title Pently demo
author DJ Tepples
copyright 2019 Damian Yerrick

durations stick
notenames english

Expand Down Expand Up @@ -199,6 +203,7 @@ instrument isobeepsq
# documentary

song argument
title Argument?
time 3/4
scale 8
tempo 180
Expand Down Expand Up @@ -624,7 +629,7 @@ song Isometry
# from picking up sticks video
# By Damian Yerrick

song sticks
song Sticks
time 12/8
scale 16
tempo 100
Expand Down Expand Up @@ -737,6 +742,8 @@ song sticks
# Pre-1800

song twinkle
title The Naive Confidence
author traditional; arr. D. Yerrick
time 6/8
scale 8

Expand Down Expand Up @@ -783,6 +790,8 @@ song twinkle
# By Johann Pachelbel, pre-1800

song canon
title Canon in D
author J. Pachelbel; arr. D. Yerrick
time 4/4
scale 32
tempo 56.25
Expand Down Expand Up @@ -906,6 +915,7 @@ song canon
# By Damian Yerrick, 2015

song bf98
title Happy Flappy Crappy
time 9/8
scale 8
tempo 133.3
Expand Down Expand Up @@ -1067,6 +1077,7 @@ song bf98
# (chord names, single-note, top interval relative, inversion)
# by Damian Yerrick, 2017
song arp_waltz
title Arpeggio Waltz
time 3/4
scale 4
tempo 180
Expand Down Expand Up @@ -1135,6 +1146,7 @@ song arp_waltz
# By Damian Yerrick, 2015

song allfeatures
title Individual features
time 12/8
scale 16
tempo 120
Expand Down Expand Up @@ -1178,7 +1190,7 @@ song allfeatures
# by Damian Yerrick, 2015
# If anything, inspired by "Little Brown Jug"

song stairs
song Stairs
time 12/8
scale 8
tempo 100
Expand Down Expand Up @@ -1247,6 +1259,7 @@ song stairs
# for NES, but Greg Caldwell of Retrotainment Games said "It's
# different enough" in a Skype conversation on June 19, 2016
song attacktest
title Attack injection (no pulse!)
time 12/4
scale 8
tempo 85.9
Expand Down
139 changes: 138 additions & 1 deletion tools/pentlyas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Pently audio engine
# Music assembler
#
# Copyright 2015-2017 Damian Yerrick
# Copyright 2015-2019 Damian Yerrick
#
# This software is provided 'as-is', without any express or implied
# warranty. In no event will the authors be held liable for any damages
Expand Down Expand Up @@ -853,6 +853,7 @@ def __init__(self, pitchctx=None, rhyctx=None,
self.bytesize = 2
self.rehearsal_marks = {}
self.total_rows = self.last_mark_rows = 0
self.title = self.author = ""

def wait_rows(self, rows_to_wait):
"""Updates the tempo and beat duration if needed, then waits some rows."""
Expand Down Expand Up @@ -1502,6 +1503,7 @@ def __init__(self, filename=None):
self.unk_keywords = self.total_lines = 0
self.warnings = []
self.filename = filename or os.path.basename(sys.argv[0])
self.title = self.author = self.copyright = "<?>"

def append(self, s):
"""Parse one line of code."""
Expand Down Expand Up @@ -1567,6 +1569,29 @@ def get_pitchrhy_parent(self):
if self.cur_song is not None
else self)

def add_title(self, words):
self.cur_obj = None
title = " ".join(words[1:])
if self.cur_song:
self.cur_song.title = title
else:
self.title = title

def add_author(self, words):
self.cur_obj = None
title = " ".join(words[1:])
if self.cur_song:
self.cur_song.author = title
else:
self.author = title

def add_copyright(self, words):
self.cur_obj = None
title = " ".join(words[1:])
if self.cur_song:
raise ValueError("copyright must be at top level, not song")
self.copyright = title

def add_notenames(self, words):
if len(words) != 2:
raise ValueError("must have 2 words: notenames LANGUAGE")
Expand Down Expand Up @@ -1719,6 +1744,7 @@ def add_song(self, words):
name=songname, orderkey=self.total_lines,
fileline=tuple(self.filelinestack[-1]),
warn=self.warn)
song.title = songname # default songname
self.cur_song = self.songs[songname] = song

def end_song(self, words):
Expand Down Expand Up @@ -1951,6 +1977,9 @@ def add_definition(self, name, value):
'notenames': add_notenames,
'durations': add_durations,
'mmloctaves': add_mmloctaves,
'title': add_title,
'author': add_author,
'copyright': add_copyright,
'sfx': add_sfx,
'volume': add_volume,
'rate': add_rate,
Expand Down Expand Up @@ -2175,6 +2204,10 @@ def render_file(parser, segment='RODATA'):
}

lines = [
'; title: ' + parser.title,
'; author: ' + parser.author,
'; copyright: ' + parser.copyright,
';',
'.include "../../src/pentlyseq.inc"',
'.segment "%s"' % segment,
'NUM_SONGS=%d' % len(parser.songs),
Expand Down Expand Up @@ -2266,6 +2299,102 @@ def render_file(parser, segment='RODATA'):
lines.append('')
return lines

def ca65_escape_bytes(blo):
"""Encode an iterable of ints in 0-255, mostly ASCII, for ca65 .byte statement"""
runs = []
for c in blo:
if 32 <= c <= 126 and c != 34:
if runs and isinstance(runs[-1], bytearray):
runs[-1].append(c)
else:
runs.append(bytearray([c]))
else:
runs.append(c)
return ','.join('"%s"' % r.decode('ascii')
if isinstance(r, bytearray)
else '%d' % r
for r in runs)

def bytes_strcpy(b, length):
"""Crop or NUL-pad to exactly length bytes"""
b = b[:32]
return bytes(b) + bytes(32 - len(b))

def render_include_file(parser):
title_utf8 = parser.title.encode("utf-8")
author_utf8 = parser.author.encode("utf-8")
copyright_utf8 = parser.copyright.encode("utf-8")

lines = [
'; title: ' + parser.title,
'; author: ' + parser.author,
'; copyright: ' + parser.copyright,
';',
'NUM_SONGS=%d' % len(parser.songs),
'NUM_SOUNDS=%d' % len(parser.sfxs),
"",
".macro PENTLY_WRITE_NSFE_TITLE",
" .byte "+ca65_escape_bytes(title_utf8),
".endmacro",
".macro PENTLY_WRITE_NSFE_AUTHOR",
" .byte "+ca65_escape_bytes(author_utf8),
".endmacro",
".macro PENTLY_WRITE_NSFE_COPYRIGHT",
" .byte "+ca65_escape_bytes(copyright_utf8),
".endmacro",
".macro PENTLY_WRITE_NSF_TITLE",
" .byte "+ca65_escape_bytes(bytes_strcpy(title_utf8, 32)),
".endmacro",
".macro PENTLY_WRITE_NSF_AUTHOR",
" .byte "+ca65_escape_bytes(bytes_strcpy(author_utf8, 32)),
".endmacro",
".macro PENTLY_WRITE_NSF_COPYRIGHT",
" .byte "+ca65_escape_bytes(bytes_strcpy(copyright_utf8, 32)),
".endmacro",
"",
]

# Assembly names of everything
parts_to_print = [parser.sfxs, parser.instruments, parser.songs]
parts_to_print = [
sorted(objs.values(), key=lambda x: x.orderkey)
for objs in parts_to_print
]
songs = parts_to_print[2]
for objs in parts_to_print:
lines.extend(
"%s = %i" % (obj.asmname, i) for i, obj in enumerate(objs)
)

# Macros to write song names
lines.append(".macro PENTLY_WRITE_SONG_TITLES terminator")
lines.extend(
"PSTITLE_%d: .byte %s, terminator"
% (i, ca65_escape_bytes(song.title.encode("utf-8")))
for i, song in enumerate(songs)
)
lines.append(".endmacro")
lines.append(".macro PENTLY_WRITE_SONG_TITLE_PTRS")
lines.extend(
" .addr PSTITLE_%d" % i for i in range(len(songs))
)
lines.append(".endmacro")

lines.append(".macro PENTLY_WRITE_SONG_AUTHORS terminator")
lines.extend(
"PSAUTHOR_%d: .byte %s, terminator"
% (i, ca65_escape_bytes((song.author or parser.author).encode("utf-8")))
for i, song in enumerate(songs)
)
lines.append(".endmacro")
lines.append(".macro PENTLY_WRITE_SONG_AUTHOR_PTRS")
lines.extend(
" .addr PSAUTHOR_%d" % i for i in range(len(songs))
)
lines.append(".endmacro")

return lines

# Period table generation ###########################################

region_period_numerator = {
Expand All @@ -2291,6 +2420,8 @@ def parse_argv(argv):
help='Pently-MML file to process or - for standard input; omit for period table only')
parser.add_argument("-o", "--output", metavar='OUTFILENAME',
help='write output to a file instead of standard output')
parser.add_argument("--write-inc", metavar='INCFILENAME',
help='write metadata as include file')
parser.add_argument("--periods", type=int, default=0,
metavar='LENGTH',
help='include a period table in the output; LENGTH is usually 64 to 80')
Expand All @@ -2312,6 +2443,8 @@ def parse_argv(argv):
args.warn = set(args.warn or [])
if not args.infilename and not args.periods:
parser.error('at least one of infilename and --periods is required')
if args.write_inc and not args.infilename:
parser.error("cannot write include file without infilename")
if args.periods < 0:
parser.error('NUMSEMITONES cannot be negative')
if args.periods > 88:
Expand Down Expand Up @@ -2385,6 +2518,10 @@ def main(argv=None):
finally:
if not is_stdout:
outfp.close()
if args.write_inc:
lines = render_include_file(parser)
with open(args.write_inc, "w") as outfp:
outfp.write("\n".join(lines))

if __name__=='__main__':
## main(["pentlyas", "../src/musicseq.pently"])
Expand Down

0 comments on commit 9844852

Please sign in to comment.