File size: 4,122 Bytes
187bf9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""Regular constraints shared by generation engines."""

from __future__ import annotations

from collections.abc import Iterable

from .common import Symbol, duration_units, is_triplet_duration


class ThemeGenerationAcceptor:
    """Dense weighted DFA for endpoint weights and rhythmic phase constraints."""

    rejected_state = -1

    def __init__(
        self,
        *,
        length: int,
        alphabet: Iterable[Symbol],
        start_weights: dict[int, float],
        end_weights: dict[int, float],
        strength: float,
        enforce_triplet_groups: bool,
    ) -> None:
        self.length = length
        self.start_weights = start_weights
        self.end_weights = end_weights
        self.strength = strength
        self.enforce_triplet_groups = enforce_triplet_groups
        self.name = "theme_generation_dense"

        alphabet_tuple = tuple(alphabet)
        self.alphabet = frozenset(alphabet_tuple)
        self.start_state = self.encode_state(0, 0, 0)
        self.state_count_value = (length + 1) * 12 * 3
        self.states = frozenset(range(self.state_count_value))
        self.accept_states = frozenset(self.encode_state(length, phase, 0) for phase in range(12))
        self.dense_transition_by_symbol = {
            symbol: self.transition_column(symbol)
            for symbol in alphabet_tuple
        }

    @staticmethod
    def encode_state(position: int, phase: int, triplets_to_close: int) -> int:
        return ((position * 12) + phase) * 3 + triplets_to_close

    @staticmethod
    def decode_state(state: int) -> tuple[int, int, int]:
        position_phase, triplets_to_close = divmod(state, 3)
        position, phase = divmod(position_phase, 12)
        return position, phase, triplets_to_close

    def transition_column(self, symbol: Symbol) -> tuple[int, ...]:
        return tuple(self.next_state_for_symbol(state, symbol) for state in self.states)

    def next_state_for_symbol(self, state: int, symbol: Symbol) -> int:
        position, phase, triplets_to_close = self.decode_state(state)
        if position >= self.length:
            return self.rejected_state

        units = duration_units(symbol.duration)
        is_triplet = is_triplet_duration(symbol.duration)
        next_triplets_to_close = 0
        if self.enforce_triplet_groups:
            if triplets_to_close > 0:
                if not is_triplet:
                    return self.rejected_state
                next_triplets_to_close = triplets_to_close - 1
            elif is_triplet:
                if phase != 0:
                    return self.rejected_state
                next_triplets_to_close = 2

        return self.encode_state(
            position + 1,
            (phase + units) % 12,
            next_triplets_to_close,
        )

    def next_state(self, state: int, symbol: Symbol) -> int | None:
        transitions = self.dense_transition_by_symbol.get(symbol)
        if transitions is None:
            return None
        next_state = transitions[state]
        if next_state == self.rejected_state:
            return None
        return next_state

    def is_accepting(self, state: int) -> bool:
        return state in self.accept_states

    def transition_weight(self, state: int, symbol: Symbol) -> float:
        position, _, _ = self.decode_state(state)
        if position == 0:
            return self.start_weights.get(symbol.rpc, 1.0) ** self.strength
        if position == self.length - 1:
            return self.end_weights.get(symbol.rpc, 1.0) ** self.strength
        return 1.0

    def state_count(self) -> int:
        return self.state_count_value


def make_theme_acceptor(
    *,
    length: int,
    alphabet: Iterable[Symbol],
    start_weights: dict[int, float],
    end_weights: dict[int, float],
    strength: float,
    enforce_triplet_groups: bool,
) -> ThemeGenerationAcceptor:
    return ThemeGenerationAcceptor(
        length=length,
        alphabet=alphabet,
        start_weights=start_weights,
        end_weights=end_weights,
        strength=strength,
        enforce_triplet_groups=enforce_triplet_groups,
    )