Spaces:
Sleeping
Sleeping
| from smallmuse.temporals import NoteList, TemporalNote | |
| # Parameters | |
| QUANTIZATION = 8 # positions per bar | |
| TICKS_PER_BEAT = 2 | |
| STEPS_PER_BAR = QUANTIZATION | |
| MAX_TIME_STEP = 32 | |
| BAR_LIMIT = .5 # ±1 bar displacement allowed | |
| DISSONANT_INTERVALS = {6, 13} | |
| def _load_cp_model(): | |
| try: | |
| from ortools.sat.python import cp_model | |
| except ImportError as exc: | |
| raise ImportError( | |
| "OR-Tools is required for temporal note optimization. " | |
| "Install it with `python -m pip install ortools`." | |
| ) from exc | |
| return cp_model | |
| # Note class | |
| # class Note: | |
| # def __init__(self, pitch, start_time, duration): | |
| # self.pitch = pitch | |
| # self.start_time = start_time | |
| # self.duration = duration | |
| # Generate notes | |
| # notes = [] | |
| # for i in range(NUM_NOTES): | |
| # pitch = random.randint(48, 72) | |
| # start_time = i * 0.5 # uniform spacing (beats) | |
| # duration = 0.5 | |
| # notes.append(Note(pitch, start_time, duration)) | |
| def optimize(note_list): | |
| cp_model = _load_cp_model() | |
| # Model | |
| notes = note_list.notes | |
| NUM_NOTES = len(notes) | |
| model = cp_model.CpModel() | |
| original_steps = [int(note.start_time * TICKS_PER_BEAT) for note in notes] | |
| starts = [] | |
| for i in range(NUM_NOTES): | |
| lower = max(0, original_steps[i] - int(BAR_LIMIT * STEPS_PER_BAR)) | |
| upper = min(MAX_TIME_STEP - 1, original_steps[i] + int(BAR_LIMIT * STEPS_PER_BAR)) | |
| var = model.NewIntVar(lower, upper, f'start_{i}') | |
| starts.append(var) | |
| # Order constraint | |
| for i in range(NUM_NOTES - 1): | |
| model.Add(starts[i] <= starts[i + 1]) | |
| # Dissonance constraint | |
| for i in range(NUM_NOTES): | |
| for j in range(i + 1, NUM_NOTES): | |
| interval = abs(notes[i].pitch - notes[j].pitch) | |
| if interval in DISSONANT_INTERVALS: | |
| model.Add(starts[i] != starts[j]) | |
| # Objective: minimize squared pitch difference | |
| # interval_terms = [(notes[i + 1].pitch - notes[i].pitch) ** 2 for i in range(NUM_NOTES - 1)] | |
| # model.Minimize(sum(interval_terms)) | |
| # Objective: minimize total displacement from original start times | |
| displacements = [] | |
| for i in range(NUM_NOTES): | |
| diff = model.NewIntVar(0, MAX_TIME_STEP, f'diff_{i}') | |
| model.AddAbsEquality(diff, starts[i] - original_steps[i]) | |
| displacements.append(diff) | |
| model.Minimize(sum(displacements)) | |
| # Solve | |
| solver = cp_model.CpSolver() | |
| status = solver.Solve(model) | |
| # Output | |
| result = NoteList() | |
| if status in [cp_model.OPTIMAL, cp_model.FEASIBLE]: | |
| for i in range(NUM_NOTES): | |
| result.add_note(TemporalNote(notes[i].pitch, int(solver.Value(starts[i]) / 2), notes[i].duration())) | |
| # print(f"Note {i}: pitch={notes[i].pitch}, " | |
| # f"original={original_steps[i]}, " | |
| # f"quantized={solver.Value(starts[i])}") | |
| else: | |
| print("No solution found.") | |
| result.join_notes() | |
| return result | |