Skip to content

Commit

Permalink
fix fallthrough transpose
Browse files Browse the repository at this point in the history
This does most of #1, except for stitching the last pattern's
pitch range to those of earlier patterns.

Also document the fallthrough command and list the commands that
close a pattern.
  • Loading branch information
pinobatch committed Mar 13, 2019
1 parent 85ae176 commit 5918f57
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 19 deletions.
14 changes: 12 additions & 2 deletions docs/pentlyas.md
Expand Up @@ -446,7 +446,16 @@ slurred group. This is useful for tying notes together or producing
legato (HOPO). Slurring into a note with the same pitch is the same
as a wait: `eb2~ eb8`, `eb2( eb8)`, and `eb2 w8` mean the same.

Notes are separated by at least one space.
Notes are separated by at least one space. Notes are added to a
pattern until it is closed by one of these commands: `title`,
`author`, `copyright`, `drum`, `song`, `segno`, `fine`, `dal segno`,
`da capo`, `at`, `attack`, `play`, `stop`, or `fallthrough`.

If a pattern ends with the `fallthrough` command, playback continues
into the following pattern in the score. For example, if pattern
`AAA` falls through and the next pattern is `BBB`, then playing `AAA`
produces `AAA`, `BBB`, `AAA`, `BBB`, ..., while playing `BBB`
produces `BBB`, `BBB`, ...

A simple pattern might look like this:

Expand Down Expand Up @@ -569,7 +578,8 @@ where `0` means the measure preceding the first full measure, and

The `pickup` command also works in patterns for bar check use.
If defining a pattern within a song, make sure to specify `pickup`
outside a pattern, such as before the start of the pattern.
outside a pattern definition, such as before the start of the first
pattern or after a command that closes a pattern.

In addition to the tracks for pitched channels (`pulse1`, `pulse2`,
and `triangle`) and the drum track, Pently has an **attack track**
Expand Down
42 changes: 25 additions & 17 deletions tools/pentlyas.py
Expand Up @@ -510,6 +510,7 @@ def set_measure(self, measure=1, beat=1, row=0):
"""Set the current musical time."""
measure, row, _, _ = self.parse_measure(measure, beat, row)
self.cur_measure, self.row_in_measure = measure, row
print("set_measure to %d:%d in %x" % (self.cur_measure, self.row_in_measure, id(self)))

def add_rows(self, rows):
"""Add a duration in rows to the current musical time."""
Expand All @@ -522,6 +523,7 @@ def wait_for_measure(self, measure, beat=1, row=0):
"""Seek to a given musical time.
Return rows between old and new positions."""
print("wait from %d:%d in %x" % (self.cur_measure, self.row_in_measure, id(self)))
measure, row, measure_length, beat_length = self.parse_measure(measure, beat, row)
if (measure < self.cur_measure
or (measure == self.cur_measure and row < self.row_in_measure)):
Expand Down Expand Up @@ -1326,13 +1328,6 @@ def find_transpose_runs(data):
runs[-1][1:3] = lo, hi
return [tuple(i) for i in runs]

def calc_transpose(self):
if self.track == 'drum':
self.transpose_runs, self.transpose = [], None
return
self.transpose_runs = self.find_transpose_runs(self.notes)
self.transpose = self.transpose_runs[0][1]

@staticmethod
def collapse_ties(notes, tie_rests=False):
"""Interpret slur commands; combine w notes and slurred same-pitch notes.
Expand Down Expand Up @@ -1423,6 +1418,17 @@ def collapse_effects(notes):

return rnotes

def make_final(self):
"""Collapse ties, collapse arpeggio effects, and calculate transpose runs"""
pitched = self.track != 'drum'
if self.track == 'drum':
self.transpose_runs, self.transpose = [], None
return
self.notes = self.collapse_ties(self.notes, not pitched)
self.notes = self.collapse_effects(self.notes)
self.transpose_runs = self.find_transpose_runs(self.notes)
self.transpose = self.transpose_runs[0][1]

row_to_duration = [
(16, '|D_1'), (12, '|D_D2'), (8, '|D_2'), (6, '|D_D4'),
(4, '|D_4'), (3, '|D_D8'), (2, '|D_8'), (1, '')
Expand All @@ -1440,23 +1446,20 @@ def numrows_to_durations(self, numrows):

def render(self, scopes):
is_drum = self.track == 'drum'
notes = self.collapse_ties(self.notes, is_drum)
self.notes = notes = self.collapse_effects(notes)

bytedata = []
if self.transpose is None: self.calc_transpose()
transpose_runs, cur_transpose = self.transpose_runs, self.transpose
last_slur = False

# If the first run isn't in range, which may happen in a
# FALLTHROUGH, schedule a TRANSPOSE for the first note
transpose_pos = 1
if (transpose_runs
and (transpose_pos > transpose_runs[0][1]
or transpose_pos - 24 < transpose_runs[0][2])):
and (cur_transpose > transpose_runs[0][1]
or cur_transpose + 24 < transpose_runs[0][2])):
transpose_pos = 0

for i, note in enumerate(notes):
for i, note in enumerate(self.notes):
if (transpose_runs
and transpose_pos < len(transpose_runs)
and i >= transpose_runs[transpose_pos][0]):
Expand Down Expand Up @@ -2238,13 +2241,16 @@ def render_file(parser, segment='RODATA'):
# Propagate transposition base backward
revpatterns = sorted(parser.patterns.values(), key=lambda x: x.orderkey,
reverse=True)
last_transpose = last_pitched = last_patname = None
last_transpose = last_pitched = last_patname = last_lowest_note = None
for pat in revpatterns:
pat.calc_transpose()
pitched = pat.track != 'drum'
pat.make_final()
lowest_note = (
min(x[1] for x in pat.transpose_runs) if pitched else None
)
if not pat.fallthrough:
last_patname, last_pitched = pat.name, pitched
last_transpose = pat.transpose
last_transpose, last_lowest_note = pat.transpose, lowest_note
if last_patname is None:
raise ValueError("%s falls through but is the last pattern"
% pat.name)
Expand All @@ -2254,7 +2260,9 @@ def render_file(parser, segment='RODATA'):
% (pat.name, "pitched" if pitched else "drum",
last_patname, "pitched" if last_pitched else "drum")
)
pat.transpose = last_transpose
if lowest_note is not None:
last_lowest_note = min(lowest_note, last_lowest_note)
pat.transpose, pat.lowest_note = last_transpose, last_lowest_note

# Pack byte arrays that are subsequences of another byte array
# into the longer one
Expand Down

0 comments on commit 5918f57

Please sign in to comment.