File size: 7,496 Bytes
43a0d3f
 
 
055747e
43a0d3f
 
055747e
43a0d3f
f7e26a4
 
cee0097
f7e26a4
 
43a0d3f
 
 
055747e
43a0d3f
 
 
f7e26a4
43a0d3f
055747e
43a0d3f
 
 
 
 
f7e26a4
43a0d3f
cee0097
 
 
 
 
 
 
055747e
43a0d3f
 
055747e
 
 
 
 
 
 
 
 
 
43a0d3f
 
9ccc1e0
 
 
 
 
f7e26a4
9ccc1e0
 
 
 
 
 
 
 
f7e26a4
9ccc1e0
cee0097
 
 
 
 
 
 
9ccc1e0
 
 
 
 
 
 
f7e26a4
9ccc1e0
 
 
f7e26a4
9ccc1e0
 
 
f7e26a4
9ccc1e0
 
 
 
f7e26a4
9ccc1e0
 
f7e26a4
 
 
 
 
 
 
 
 
9ccc1e0
 
f7e26a4
 
 
 
 
 
 
 
 
9ccc1e0
 
 
 
 
f7e26a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cee0097
 
 
 
 
 
 
f7e26a4
 
 
cee0097
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f7e26a4
 
 
cee0097
 
 
 
 
 
 
f7e26a4
 
 
cee0097
 
f7e26a4
 
43a0d3f
 
 
 
 
 
 
 
f7e26a4
 
 
 
 
43a0d3f
 
 
 
 
 
 
f7e26a4
43a0d3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
"""
Virtual MIDI Keyboard - Engines

MIDI processing engines that transform, analyze, or manipulate MIDI events.
"""

from typing import List, Dict, Any

from midi_model import (
    count_out_of_range_events,
    fold_events_to_keyboard_range,
    get_model,
)


# =============================================================================
# PARROT ENGINE
# =============================================================================


class ParrotEngine:
    """
    Parrot Engine - plays back MIDI exactly as recorded.

    This is the simplest engine - it just repeats what the user played.
    """

    def __init__(self):
        self.name = "Parrot"

    def process(
        self,
        events: List[Dict[str, Any]],
        options: Dict[str, Any] | None = None,
        request: Any | None = None,
        device: str = "auto",
    ) -> List[Dict[str, Any]]:
        """Return events unchanged"""
        if not events:
            return []
        return [
            {
                "type": e.get("type"),
                "note": e.get("note"),
                "velocity": e.get("velocity"),
                "time": e.get("time"),
                "channel": e.get("channel", 0),
            }
            for e in events
        ]


# =============================================================================
# REVERSE PARROT ENGINE
# =============================================================================


class ReverseParrotEngine:
    """
    Reverse Parrot Engine - plays back MIDI in reverse order.

    Takes the recorded performance and reverses the sequence of notes,
    playing them backwards while maintaining their timing relationships.
    """

    def __init__(self):
        self.name = "Reverse Parrot"

    def process(
        self,
        events: List[Dict[str, Any]],
        options: Dict[str, Any] | None = None,
        request: Any | None = None,
        device: str = "auto",
    ) -> List[Dict[str, Any]]:
        """Reverse the sequence of note numbers while keeping timing and event types"""
        if not events:
            return []

        # Separate note_on and note_off events
        note_on_events = [e for e in events if e.get("type") == "note_on"]
        note_off_events = [e for e in events if e.get("type") == "note_off"]

        # Extract note numbers from note_on events and reverse them
        on_notes = [e.get("note") for e in note_on_events]
        reversed_on_notes = list(reversed(on_notes))

        # Extract note numbers from note_off events and reverse them
        off_notes = [e.get("note") for e in note_off_events]
        reversed_off_notes = list(reversed(off_notes))

        # Reconstruct events with reversed notes but original structure
        result = []
        on_index = 0
        off_index = 0

        for event in events:
            if event.get("type") == "note_on":
                result.append(
                    {
                        "type": "note_on",
                        "note": reversed_on_notes[on_index],
                        "velocity": event.get("velocity"),
                        "time": event.get("time"),
                        "channel": event.get("channel", 0),
                    }
                )
                on_index += 1
            elif event.get("type") == "note_off":
                result.append(
                    {
                        "type": "note_off",
                        "note": reversed_off_notes[off_index],
                        "velocity": event.get("velocity"),
                        "time": event.get("time"),
                        "channel": event.get("channel", 0),
                    }
                )
                off_index += 1

        return result


# =============================================================================
# GODZILLA CONTINUATION ENGINE
# =============================================================================


class GodzillaContinuationEngine:
    """
    Continue a short MIDI phrase with the Godzilla Piano Transformer.

    Generates a small continuation and appends it after the input events.
    """

    def __init__(self, generate_tokens: int = 32):
        self.name = "Godzilla"
        self.generate_tokens = generate_tokens

    def process(
        self,
        events: List[Dict[str, Any]],
        options: Dict[str, Any] | None = None,
        request: Any | None = None,
        device: str = "auto",
    ) -> List[Dict[str, Any]]:
        if not events:
            return []

        generate_tokens = self.generate_tokens
        seed = None
        temperature = 0.9
        top_p = 0.95
        num_candidates = 3
        if isinstance(options, dict):
            requested_tokens = options.get("generate_tokens")
            if isinstance(requested_tokens, int):
                generate_tokens = max(8, min(256, requested_tokens))
            requested_seed = options.get("seed")
            if isinstance(requested_seed, int):
                seed = requested_seed
            requested_temperature = options.get("temperature")
            if isinstance(requested_temperature, (int, float)):
                temperature = max(0.2, min(1.5, float(requested_temperature)))
            requested_top_p = options.get("top_p")
            if isinstance(requested_top_p, (int, float)):
                top_p = max(0.5, min(0.99, float(requested_top_p)))
            requested_candidates = options.get("num_candidates")
            if isinstance(requested_candidates, int):
                num_candidates = max(1, min(6, requested_candidates))

        model = get_model("godzilla")
        new_events = model.generate_continuation(
            events,
            tokens=generate_tokens,
            seed=seed,
            temperature=temperature,
            top_p=top_p,
            num_candidates=num_candidates,
            request=request,
            device=device,
        )
        out_of_range = count_out_of_range_events(new_events)
        if out_of_range:
            print(f"Godzilla: remapped {out_of_range} out-of-range events by octave folding")
        return fold_events_to_keyboard_range(new_events)


# =============================================================================
# ENGINE REGISTRY
# =============================================================================


class EngineRegistry:
    """Registry for managing available MIDI engines"""

    _engines = {
        "parrot": ParrotEngine,
        "reverse_parrot": ReverseParrotEngine,
        "godzilla_continue": GodzillaContinuationEngine,
    }

    @classmethod
    def register(cls, engine_id: str, engine_class: type):
        """Register a new engine"""
        cls._engines[engine_id] = engine_class

    @classmethod
    def get_engine(cls, engine_id: str):
        """Get an engine instance by ID"""
        if engine_id not in cls._engines:
            raise ValueError(f"Unknown engine: {engine_id}")
        return cls._engines[engine_id]()

    @classmethod
    def list_engines(cls) -> List[str]:
        """List all available engines"""
        return list(cls._engines.keys())

    @classmethod
    def get_engine_info(cls, engine_id: str) -> Dict[str, str]:
        """Get info about an engine"""
        if engine_id not in cls._engines:
            raise ValueError(f"Unknown engine: {engine_id}")
        engine = cls._engines[engine_id]()
        return {
            "id": engine_id,
            "name": engine.name,
        }