Spaces:
Running
Running
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,
}
|