def __onMoveTo(self, where: MoveTo) -> None: if self.__player_id is None: logger.warning("Player action without active player.") return if where == MoveTo.Start: self.setCurrentTime(audioproc.MusicalTime()) elif where == MoveTo.End: self.setCurrentTime(self.time_mapper.end_time) elif where == MoveTo.PrevBeat: beat = int( (self.__current_time + audioproc.MusicalDuration(3, 16)) / audioproc.MusicalTime(1, 4)) new_time = audioproc.MusicalTime(beat - 1, 4) if new_time < audioproc.MusicalTime(0, 1): new_time = audioproc.MusicalTime(0, 1) self.setCurrentTime(new_time) elif where == MoveTo.NextBeat: beat = int( (self.__current_time + audioproc.MusicalDuration(3, 16)) / audioproc.MusicalTime(1, 4)) new_time = audioproc.MusicalTime(beat + 1, 4) if new_time > self.time_mapper.end_time: new_time = self.time_mapper.end_time self.setCurrentTime(new_time) else: raise ValueError(where)
def max_allowed_dots(self) -> int: base_duration = self.base_duration if base_duration <= audioproc.MusicalDuration(1, 32): return 0 if base_duration <= audioproc.MusicalDuration(1, 16): return 1 if base_duration <= audioproc.MusicalDuration(1, 8): return 2 return 3
async def test_create_note(self): track = await self._add_track() measure = track.measure_list[0].measure self.assertEqual(len(measure.notes), 0) with self.project.apply_mutations('test'): measure.create_note(0, value_types.Pitch('F2'), audioproc.MusicalDuration(1, 4)) self.assertEqual(len(measure.notes), 1) self.assertEqual(measure.notes[0].pitches[0], value_types.Pitch('F2')) self.assertEqual(measure.notes[0].base_duration, audioproc.MusicalDuration(1, 4))
async def test_add_beat(self): track = await self._add_track() measure = track.measure_list[0].measure with self.project.apply_mutations('test'): measure.create_beat(audioproc.MusicalDuration(1, 4)) self.assertEqual(measure.beats[0].time, audioproc.MusicalDuration(1, 4)) self.assertEqual(measure.beats[0].velocity, 100) with self.project.apply_mutations('test'): measure.create_beat(audioproc.MusicalDuration(2, 4), 120) self.assertEqual(measure.beats[1].time, audioproc.MusicalDuration(2, 4)) self.assertEqual(measure.beats[1].velocity, 120)
def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.__grid_step = audioproc.MusicalDuration(1, 1) self.scaleXChanged.connect(self.__scaleXChanged) self.__scaleXChanged(self.scaleX())
def __scaleXChanged(self, scale_x: fractions.Fraction) -> None: self.__grid_step = audioproc.MusicalDuration(1, 64) min_dist = 96 while int(self.__grid_step * scale_x) <= min_dist: self.__grid_step *= 2 if int(self.__grid_step) > 1: min_dist = 36
def __durationChanged( self, change: music.PropertyValueChange[audioproc.MusicalDuration] ) -> None: num_beats = change.new_value / audioproc.MusicalDuration(1, 4) assert num_beats.denominator == 1 self.__duration.setValue(num_beats.numerator) self.__pianoroll.setDuration(change.new_value)
def create_beat(self, time: audioproc.MusicalDuration, velocity: int = 100) -> Beat: assert audioproc.MusicalDuration(0, 1) <= time < self.duration assert 0 <= velocity <= 127 beat = self._pool.create(Beat, time=time, velocity=velocity) self.beats.append(beat) return beat
def __durationEdited(self, beats: int) -> None: duration = audioproc.MusicalDuration(beats, 4) if duration == self.__node.duration: return with self.project.apply_mutations('%s: Change duration' % self.__node.name): self.__node.set_duration(duration)
def __init__(self, node: model.MidiLooper, session_prefix: str, **kwargs: Any) -> None: super().__init__(**kwargs) self.__node = node self.__visible = False self.__slot_connections = slots.SlotConnectionManager( session_prefix='midi_looper:%016x:%s' % (self.__node.id, session_prefix), context=self.context) self.add_cleanup_function(self.__slot_connections.cleanup) self.__listeners = core.ListenerMap[str]() self.add_cleanup_function(self.__listeners.cleanup) self.__listeners[ 'node-messages'] = self.audioproc_client.node_messages.add( '%016x' % self.__node.id, self.__nodeMessage) self.__event_map = [] # type: List[int] self.__recorded_events = [] # type: List[value_types.MidiEvent] self.__duration = QtWidgets.QSpinBox() self.__duration.setObjectName('duration') self.__duration.setSuffix(" beats") self.__duration.setKeyboardTracking(False) self.__duration.setRange(1, 100) num_beats = self.__node.duration / audioproc.MusicalDuration(1, 4) assert num_beats.denominator == 1 self.__duration.setValue(num_beats.numerator) self.__duration.valueChanged.connect(self.__durationEdited) self.__listeners['duration'] = self.__node.duration_changed.add( self.__durationChanged) self.__record = RecordButton() self.__record.clicked.connect(self.__recordClicked) self.__pianoroll = pianoroll.PianoRoll() self.__pianoroll.setDuration(self.__node.duration) for event in self.__node.patches[0].events: self.__event_map.append(self.__pianoroll.addEvent(event)) self.__listeners['events'] = self.__node.patches[0].events_changed.add( self.__eventsChanged) l2 = QtWidgets.QHBoxLayout() l2.setContentsMargins(0, 0, 0, 0) l2.addWidget(self.__record) l2.addWidget(self.__duration) l2.addStretch(1) l1 = QtWidgets.QVBoxLayout() l1.setContentsMargins(0, 0, 0, 0) l1.addLayout(l2) l1.addWidget(self.__pianoroll) self.setLayout(l1)
async def test_delete_beat(self): track = await self._add_track() measure = track.measure_list[0].measure with self.project.apply_mutations('test'): measure.create_beat(audioproc.MusicalDuration(1, 4)) with self.project.apply_mutations('test'): measure.delete_beat(measure.beats[0]) self.assertEqual(len(measure.beats), 0)
def _create_events( self, time: audioproc.MusicalTime, measure: base_track.Measure ) -> Iterator[base_track.PianoRollInterval]: measure = down_cast(BeatMeasure, measure) for beat in measure.beats: beat_time = time + beat.time event = base_track.PianoRollInterval( beat_time, beat_time + audioproc.MusicalDuration(1, 4), self._node.pitch, 127) yield event
def duration(self) -> audioproc.MusicalDuration: duration = self.base_duration dots = self.dots tuplet = self.tuplet for _ in range(dots): duration *= fractions.Fraction(3, 2) if tuplet == 3: duration *= fractions.Fraction(2, 3) elif tuplet == 5: duration *= fractions.Fraction(4, 5) return audioproc.MusicalDuration(duration)
def __update(self) -> None: if self.displayMode() == self.DisplayMode.MusicalTime: beat = self.__current_time / audioproc.MusicalDuration(1, 4) self.display('%.3f' % beat) else: assert self.displayMode() == self.DisplayMode.RealTime t = (self.__time_mapper.musical_to_sample_time(self.__current_time) / self.__time_mapper.sample_rate) millis = int(1000 * t) % 1000 seconds = int(t) % 60 minutes = int(t) // 60 self.display('%d:%02d.%03d' % (minutes, seconds, millis))
async def test_duration(self): widget = node_ui.MidiLooperNodeWidget(node=self.node, session_prefix='test', context=self.context) try: duration = widget.findChild(QtWidgets.QSpinBox, 'duration') assert duration is not None self.assertEqual(duration.value(), (self.node.duration / audioproc.MusicalDuration(1, 4)).numerator) with self.project.apply_mutations('test'): self.node.set_duration(audioproc.MusicalDuration(5, 4)) self.assertEqual(audioproc.MusicalDuration(duration.value(), 4), self.node.duration) duration.setValue(7) self.assertEqual(self.node.duration, audioproc.MusicalDuration(7, 4)) finally: widget.cleanup()
def create(self, *, pitches: Optional[Iterable[value_types.Pitch]] = None, base_duration: Optional[audioproc.MusicalDuration] = None, dots: int = 0, tuplet: int = 0, **kwargs: Any) -> None: super().create(**kwargs) if pitches is not None: self.pitches.extend(pitches) if base_duration is None: base_duration = audioproc.MusicalDuration(1, 4) self.base_duration = base_duration self.dots = dots self.tuplet = tuplet assert (self.base_duration.numerator == 1 and self.base_duration.denominator in (1, 2, 4, 8, 16, 32)), \ self.base_duration
def __onAppendMeasuresDone(self, buttons: QtWidgets.QButtonGroup, count_input: QtWidgets.QSpinBox) -> None: track = self.track.track if buttons.checkedId() == 1: with self.project.apply_mutations('%s: Fill track to end' % track.name): duration = audioproc.MusicalDuration() for mref in track.measure_list: duration += mref.measure.duration cnt = int((self.project.duration - duration) / track.measure_list[-1].measure.duration) for _ in range(cnt): track.append_measure() else: assert buttons.checkedId() == 2 cnt = count_input.value() with self.project.apply_mutations('%s: Append %d measures' % (track.name, cnt)): for _ in range(cnt): track.append_measure()
def paintForeground(self, painter: QtGui.QPainter) -> None: ymid = self.height() // 2 for beat in self.measure.beats: if not audioproc.MusicalDuration(0, 1) <= beat.time < self.measure.duration: logger.warning( "Beat outside of measure: %s not in [0,%s)", beat.time, self.measure.duration) continue pos = int(self.width() * (beat.time / self.measure.duration).fraction) painter.fillRect( pos + 2, ymid + 8, 4, 22 * beat.velocity // 127, QtGui.QColor(255, 200, 200)) painter.setPen(Qt.NoPen) painter.setBrush(Qt.black) polygon = QtGui.QPolygon() polygon.append(QtCore.QPoint(pos, ymid - 12)) polygon.append(QtCore.QPoint(pos, ymid + 12)) polygon.append(QtCore.QPoint(pos + 8, ymid)) painter.drawPolygon(polygon)
class ScoreMeasureEditor(measured_track_editor.MeasureEditor): FOREGROUND = 'fg' BACKGROUND = 'bg' GHOST = 'ghost' layers = [ BACKGROUND, measured_track_editor.MeasureEditor.PLAYBACK_POS, FOREGROUND, GHOST, ] def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self._edit_areas = [] # type: List[Tuple[int, int, int, bool]] self._note_area = None # type: Tuple[int, int] self.__mouse_pos = None # type: QtCore.QPoint self.__ghost_pos = None # type: QtCore.QPoint self.track_editor.currentToolChanged.connect( lambda _: self.updateGhost(self.__mouse_pos)) @property def track_editor(self) -> 'ScoreTrackEditor': return down_cast(ScoreTrackEditor, super().track_editor) @property def track(self) -> model.ScoreTrack: return down_cast(model.ScoreTrack, super().track) @property def measure(self) -> model.ScoreMeasure: return down_cast(model.ScoreMeasure, super().measure) _accidental_map = { '': 'accidental-natural', '#': 'accidental-sharp', 'b': 'accidental-flat', '##': 'accidental-double-sharp', 'bb': 'accidental-double-flat', } def addMeasureListeners(self) -> None: self._measure_listeners.add( self.measure.content_changed.add( lambda _=None: self.invalidatePaintCache(self.FOREGROUND)) ) # type: ignore[misc] self._measure_listeners.add( self.measure.clef_changed.add(self.onClefChanged)) self._measure_listeners.add( self.measure.key_signature_changed.add(self.onKeySignatureChanged)) def onClefChanged( self, change: music.PropertyValueChange[value_types.Clef]) -> None: self.invalidatePaintCache(self.BACKGROUND, self.FOREGROUND) self.next_sibling.invalidatePaintCache(self.BACKGROUND, self.FOREGROUND) def onKeySignatureChanged( self, change: music.PropertyValueChange[value_types.KeySignature] ) -> None: self.invalidatePaintCache(self.BACKGROUND, self.FOREGROUND) self.next_sibling.invalidatePaintCache(self.BACKGROUND, self.FOREGROUND) def paintLayer(self, layer: str, painter: QtGui.QPainter) -> None: if layer == self.BACKGROUND: self.paintBackground(painter) elif layer == self.FOREGROUND: self.paintForeground(painter) elif layer == self.GHOST: self.paintGhost(painter) def paintBackground(self, painter: QtGui.QPainter) -> None: ymid = self.height() // 2 painter.setPen(Qt.black) painter.setBrush(Qt.black) for l in range(-2, 3): painter.drawLine(0, ymid + 20 * l, self.width() - 1, ymid + 20 * l) if self.is_first: painter.fillRect(0, ymid - 40, 2, 20 * 4, Qt.black) painter.drawLine(self.width() - 1, ymid - 40, self.width() - 1, ymid + 40) if not self.measure_reference.is_first: prev_sibling = down_cast( model.ScoreMeasure, self.measure_reference.prev_sibling.measure) else: prev_sibling = None base_stave_line = self.measure.clef.center_pitch.stave_line base_octave = self.measure.clef.base_octave x = 0 paint_clef = prev_sibling is None or self.measure.clef != prev_sibling.clef if paint_clef and self.width() - x > 200: svg_symbol.paintSymbol( painter, 'clef-%s' % self.measure.clef.symbol, QtCore.QPoint( x + 30, ymid - 10 * (self.measure.clef.base_pitch.stave_line - base_stave_line) )) x += 60 acc_map = { 'C#': 'C%d' % (base_octave + 1), 'D#': 'D%d' % (base_octave + 1), 'E#': 'E%d' % (base_octave + 1), 'F#': 'F%d' % (base_octave + 1), 'G#': 'G%d' % (base_octave + 1), 'A#': 'A%d' % base_octave, 'B#': 'B%d' % base_octave, 'Cb': 'C%d' % (base_octave + 1), 'Db': 'D%d' % (base_octave + 1), 'Eb': 'E%d' % (base_octave + 1), 'Fb': 'F%d' % base_octave, 'Gb': 'G%d' % base_octave, 'Ab': 'A%d' % base_octave, 'Bb': 'B%d' % base_octave, } paint_key_signature = ( prev_sibling is None or self.measure.key_signature != prev_sibling.key_signature) if paint_key_signature and self.width() - x > 200: for acc in self.measure.key_signature.accidentals: value = acc_map[acc] stave_line = value_types.Pitch( value).stave_line - base_stave_line svg_symbol.paintSymbol( painter, self._accidental_map[acc[1:]], QtCore.QPoint(x + 10, ymid - 10 * stave_line)) x += 10 if self.measure.key_signature.accidentals: x += 10 paint_time_signature = ( prev_sibling is None or self.measure.time_signature != prev_sibling.time_signature) if paint_time_signature and self.width() - x > 200: font = QtGui.QFont('FreeSerif', 30, QtGui.QFont.Black) font.setStretch(120) painter.setFont(font) painter.drawText(x, ymid - 5, '%d' % self.measure.time_signature.upper) painter.drawText(x, ymid + 32, '%d' % self.measure.time_signature.lower) x += 40 if self.width() - x > 100: self._note_area = (x + 20, self.width() - x - 20) else: self._note_area = (0, self.width()) def paintForeground(self, painter: QtGui.QPainter) -> None: assert self._note_area is not None self._edit_areas.clear() ymid = self.height() // 2 base_stave_line = self.measure.clef.center_pitch.stave_line base_octave = self.measure.clef.base_octave acc_map = { 'C#': 'C%d' % (base_octave + 1), 'D#': 'D%d' % (base_octave + 1), 'E#': 'E%d' % (base_octave + 1), 'F#': 'F%d' % (base_octave + 1), 'G#': 'G%d' % (base_octave + 1), 'A#': 'A%d' % base_octave, 'B#': 'B%d' % base_octave, 'Cb': 'C%d' % (base_octave + 1), 'Db': 'D%d' % (base_octave + 1), 'Eb': 'E%d' % (base_octave + 1), 'Fb': 'F%d' % base_octave, 'Gb': 'G%d' % base_octave, 'Ab': 'A%d' % base_octave, 'Bb': 'B%d' % base_octave, } active_accidentals = {} for acc in self.measure.key_signature.accidentals: value = acc_map[acc] active_accidentals[value[:1]] = acc[1:] x, note_area_width = self._note_area if note_area_width > 80: px = x - 20 note_time = audioproc.MusicalDuration(0) for idx, note in enumerate(self.measure.notes): overflow = note_time + note.duration > self.measure.duration if note.is_rest: sym = { 1: 'rest-whole', 2: 'rest-half', 4: 'rest-quarter', 8: 'rest-8th', 16: 'rest-16th', 32: 'rest-32th', }[note.base_duration.denominator] svg_symbol.paintSymbol(painter, sym, QtCore.QPoint(x, ymid)) if note.base_duration >= audioproc.MusicalDuration(1, 2): dx = 25 dy = -10 else: dx = 12 dy = 0 for d in range(note.dots): painter.setPen(Qt.NoPen) painter.setBrush(Qt.black) painter.drawEllipse(dx - 4 + 10 * d, dy - 4, 9, 9) if note.tuplet != 0: painter.setPen(Qt.black) painter.drawText(-5, -45, '%d' % note.tuplet) # if overflow: # n.setOpacity(0.4) elif len(note.pitches) > 0: min_stave_line = 1000 max_stave_line = -1000 for pitch in note.pitches: stave_line = pitch.stave_line - base_stave_line min_stave_line = min(min_stave_line, stave_line) max_stave_line = max(max_stave_line, stave_line) painter.setPen(Qt.black) painter.setOpacity(0.4 if overflow else 0.8) # Ledger lines above stave. for l in range(6, max_stave_line + 1, 2): painter.drawLine(x - 20, ymid - 10 * l, x + 20, ymid - 10 * l) # Ledger lines below stave. for l in range(-6, min_stave_line - 1, -2): painter.drawLine(x - 20, ymid - 10 * l, x + 20, ymid - 10 * l) painter.setOpacity(1.0) for pitch in note.pitches: stave_line = pitch.stave_line - base_stave_line y = ymid - 10 * stave_line active_accidental = active_accidentals.get( pitch.value, '') if pitch.accidental != active_accidental: sym = self._accidental_map[pitch.accidental] svg_symbol.paintSymbol(painter, sym, QtCore.QPoint(x - 12, y)) active_accidentals[pitch.value] = pitch.accidental if note.base_duration >= audioproc.MusicalDuration( 1, 2): svg_symbol.paintSymbol(painter, 'note-head-void', QtCore.QPoint(x, y)) else: svg_symbol.paintSymbol(painter, 'note-head-black', QtCore.QPoint(x, y)) if note.base_duration <= audioproc.MusicalDuration( 1, 2): painter.fillRect(x + 8, y - 63, 3, 60, Qt.black) if note.base_duration == audioproc.MusicalDuration( 1, 8): flags = 1 elif note.base_duration == audioproc.MusicalDuration( 1, 16): flags = 2 elif note.base_duration == audioproc.MusicalDuration( 1, 32): flags = 3 else: flags = 0 for f in range(flags): svg_symbol.paintSymbol( painter, 'note-flag-down', QtCore.QPoint(x + 11, y - 63 + 12 * f)) for d in range(note.dots): painter.setPen(Qt.NoPen) painter.setBrush(Qt.black) painter.drawEllipse(x + 12 + 10 * d, y - 4, 9, 9) if note.tuplet != 0: painter.drawText(x - 5, y - 85, '%d' % note.tuplet) # if overflow: # n.setOpacity(0.4) x1 = max(x - 12, px) x2 = max(x + 13, x1) if x1 > px: self._edit_areas.append((px, x1, idx, False)) px = x1 if x2 > x1: self._edit_areas.append((x1, x2, idx, True)) px = x2 note_time += note.duration x += int(note_area_width * note.duration.fraction) if px < self.width(): self._edit_areas.append( (px, self.width(), len(self.measure.notes), False)) else: self._note_area = (0, self.width()) def paintGhost(self, painter: QtGui.QPainter) -> None: if self.__ghost_pos is None: return ymid = self.height() // 2 tool = self.track_editor.currentToolType() pos = self.__ghost_pos painter.setOpacity(0.4) if tool.is_rest: sym = { 1: 'rest-whole', 2: 'rest-half', 4: 'rest-quarter', 8: 'rest-8th', 16: 'rest-16th', 32: 'rest-32th', }[self._tool_duration_map[tool].denominator] svg_symbol.paintSymbol(painter, sym, QtCore.QPoint(pos.x(), ymid)) elif tool.is_note: duration = self._tool_duration_map[tool] if duration >= audioproc.MusicalDuration(1, 2): svg_symbol.paintSymbol(painter, 'note-head-void', pos) else: svg_symbol.paintSymbol(painter, 'note-head-black', pos) if duration <= audioproc.MusicalDuration(1, 2): painter.fillRect(pos.x() + 8, pos.y() - 63, 3, 60, Qt.black) if duration == audioproc.MusicalDuration(1, 8): flags = 1 elif duration == audioproc.MusicalDuration(1, 16): flags = 2 elif duration == audioproc.MusicalDuration(1, 32): flags = 3 else: flags = 0 for f in range(flags): svg_symbol.paintSymbol( painter, 'note-flag-down', QtCore.QPoint(pos.x() + 11, pos.y() - 63 + 12 * f)) elif tool.is_accidental: accidental = { tools.ToolType.ACCIDENTAL_NATURAL: '', tools.ToolType.ACCIDENTAL_FLAT: 'b', tools.ToolType.ACCIDENTAL_SHARP: '#', tools.ToolType.ACCIDENTAL_DOUBLE_FLAT: 'bb', tools.ToolType.ACCIDENTAL_DOUBLE_SHARP: '##', }[tool] sym = self._accidental_map[accidental] svg_symbol.paintSymbol(painter, sym, pos) else: painter.setPen(Qt.NoPen) painter.setBrush(Qt.black) painter.drawEllipse(pos.x() - 15, pos.y() - 15, 31, 31) def paintPlaybackPos(self, painter: QtGui.QPainter) -> None: assert self._note_area is not None left, width = self._note_area pos = left + int(width * (self.playbackPos() / self.measure.duration).fraction) painter.fillRect(pos, 0, 2, self.height(), QtGui.QColor(0, 0, 160)) def getEditArea(self, x: int) -> Tuple[int, bool, int]: for x1, x2, idx, overwrite in self._edit_areas: if x1 < x <= x2: return idx, overwrite, (x1 + x2) // 2 return -1, False, 0 _tool_duration_map = { tools.ToolType.NOTE_WHOLE: audioproc.MusicalDuration(1, 1), tools.ToolType.NOTE_HALF: audioproc.MusicalDuration(1, 2), tools.ToolType.NOTE_QUARTER: audioproc.MusicalDuration(1, 4), tools.ToolType.NOTE_8TH: audioproc.MusicalDuration(1, 8), tools.ToolType.NOTE_16TH: audioproc.MusicalDuration(1, 16), tools.ToolType.NOTE_32TH: audioproc.MusicalDuration(1, 32), tools.ToolType.REST_WHOLE: audioproc.MusicalDuration(1, 1), tools.ToolType.REST_HALF: audioproc.MusicalDuration(1, 2), tools.ToolType.REST_QUARTER: audioproc.MusicalDuration(1, 4), tools.ToolType.REST_8TH: audioproc.MusicalDuration(1, 8), tools.ToolType.REST_16TH: audioproc.MusicalDuration(1, 16), tools.ToolType.REST_32TH: audioproc.MusicalDuration(1, 32), } def durationForTool(self, tool: tools.ToolType) -> audioproc.MusicalDuration: assert tool.is_note or tool.is_rest return self._tool_duration_map[tool] def setGhost(self, pos: QtCore.QPoint) -> None: if pos == self.__ghost_pos: return self.__ghost_pos = pos self.invalidatePaintCache(self.GHOST) def updateGhost(self, pos: QtCore.QPoint) -> None: if pos is None: self.setGhost(None) return ymid = self.height() // 2 stave_line = int(ymid + 5 - pos.y() ) // 10 + self.measure.clef.center_pitch.stave_line idx, overwrite, insert_x = self.getEditArea(pos.x()) if idx < 0: self.setGhost(None) return tool = self.track_editor.currentToolType() if tool.is_note or tool.is_rest: self.setGhost( QtCore.QPoint( insert_x, ymid - 10 * (stave_line - self.measure.clef.center_pitch.stave_line))) elif tool.is_accidental and overwrite: self.setGhost( QtCore.QPoint( insert_x - 12, ymid - 10 * (stave_line - self.measure.clef.center_pitch.stave_line))) else: self.setGhost(None) def leaveEvent(self, evt: QtCore.QEvent) -> None: self.__mouse_pos = None self.setGhost(None) super().leaveEvent(evt)
def create(self, num: int, pos: value_types.Pos2F) -> None: # pylint: disable=import-outside-toplevel mixer_node = self.project.create_node( 'builtin://mixer', name='Track #%d' % num, graph_pos=pos) mixer_node.set_control_value('gain', -10.0 * random.random()) mixer_node.set_control_value('pan', 2.0 * random.random() - 1.0) self.project.create_node_connection( mixer_node, 'out:left', self.master_mixer_node, 'in:left') self.project.create_node_connection( mixer_node, 'out:right', self.master_mixer_node, 'in:right') from noisicaa.builtin_nodes.instrument import model as instrument instr_node = cast( instrument.Instrument, self.project.create_node( 'builtin://instrument', graph_pos=pos - value_types.Pos2F(400, 0))) instr_node.name, instr_node.instrument_uri = self.get_instrument() self.project.create_node_connection( instr_node, 'out:left', mixer_node, 'in:left') self.project.create_node_connection( instr_node, 'out:right', mixer_node, 'in:right') from noisicaa.builtin_nodes.score_track import model as score_track track_node = cast( score_track.ScoreTrack, self.project.create_node( 'builtin://score-track', name='Track #%d' % num, num_measures=0, graph_pos=pos - value_types.Pos2F(800, 0))) self.project.create_node_connection( track_node, 'out', instr_node, 'in') note_durations = ( [audioproc.MusicalDuration(1, 4)] * 10 + [audioproc.MusicalDuration(1, 8)] * 7 + [audioproc.MusicalDuration(1, 16)] * 5 + [audioproc.MusicalDuration(1, 32)] * 2 + [audioproc.MusicalDuration(1, 2)] * 2 + [audioproc.MusicalDuration(1, 1)] * 1 ) while track_node.duration < self.project.duration: measure = cast(score_track.ScoreMeasure, track_node.append_measure()) duration_left = measure.duration while duration_left > audioproc.MusicalDuration(0, 1): durations = [d for d in note_durations if d <= duration_left] if not durations: break note = measure.create_note( index=len(measure.notes), pitch=value_types.Pitch.from_midi(max(30, min(90, int(random.gauss(60, 5))))), duration=random.choice(durations)) duration_left -= note.duration
def create(self, **kwargs: Any) -> None: super().create(**kwargs) self.duration = audioproc.MusicalDuration(8, 4) self.patches.append(self._pool.create(MidiLooperPatch))
def setup_testcase(self): self.grid = pianoroll.PianoRollGrid() self.grid.setReadOnly(False) self.grid.setDuration(audioproc.MusicalDuration(8, 4)) self.grid.resize(600, 400) self.setWidgetUnderTest(self.grid)
def xToTime(self, x: int) -> audioproc.MusicalDuration: return audioproc.MusicalDuration( int(8 * self.measure.time_signature.upper * x / self.width()), 8 * self.measure.time_signature.upper)
def durationPerPixel(self) -> audioproc.MusicalDuration: return audioproc.MusicalDuration(1 / self.scaleX())
async def test_duration(self): node = await self._add_node() self.assertEqual(node.duration, audioproc.MusicalDuration(8, 4)) with self.project.apply_mutations('test'): node.set_duration(audioproc.MusicalDuration(4, 4)) self.assertEqual(node.duration, audioproc.MusicalDuration(4, 4))
def paintForeground(self, painter: QtGui.QPainter) -> None: assert self._note_area is not None self._edit_areas.clear() ymid = self.height() // 2 base_stave_line = self.measure.clef.center_pitch.stave_line base_octave = self.measure.clef.base_octave acc_map = { 'C#': 'C%d' % (base_octave + 1), 'D#': 'D%d' % (base_octave + 1), 'E#': 'E%d' % (base_octave + 1), 'F#': 'F%d' % (base_octave + 1), 'G#': 'G%d' % (base_octave + 1), 'A#': 'A%d' % base_octave, 'B#': 'B%d' % base_octave, 'Cb': 'C%d' % (base_octave + 1), 'Db': 'D%d' % (base_octave + 1), 'Eb': 'E%d' % (base_octave + 1), 'Fb': 'F%d' % base_octave, 'Gb': 'G%d' % base_octave, 'Ab': 'A%d' % base_octave, 'Bb': 'B%d' % base_octave, } active_accidentals = {} for acc in self.measure.key_signature.accidentals: value = acc_map[acc] active_accidentals[value[:1]] = acc[1:] x, note_area_width = self._note_area if note_area_width > 80: px = x - 20 note_time = audioproc.MusicalDuration(0) for idx, note in enumerate(self.measure.notes): overflow = note_time + note.duration > self.measure.duration if note.is_rest: sym = { 1: 'rest-whole', 2: 'rest-half', 4: 'rest-quarter', 8: 'rest-8th', 16: 'rest-16th', 32: 'rest-32th', }[note.base_duration.denominator] svg_symbol.paintSymbol(painter, sym, QtCore.QPoint(x, ymid)) if note.base_duration >= audioproc.MusicalDuration(1, 2): dx = 25 dy = -10 else: dx = 12 dy = 0 for d in range(note.dots): painter.setPen(Qt.NoPen) painter.setBrush(Qt.black) painter.drawEllipse(dx - 4 + 10 * d, dy - 4, 9, 9) if note.tuplet != 0: painter.setPen(Qt.black) painter.drawText(-5, -45, '%d' % note.tuplet) # if overflow: # n.setOpacity(0.4) elif len(note.pitches) > 0: min_stave_line = 1000 max_stave_line = -1000 for pitch in note.pitches: stave_line = pitch.stave_line - base_stave_line min_stave_line = min(min_stave_line, stave_line) max_stave_line = max(max_stave_line, stave_line) painter.setPen(Qt.black) painter.setOpacity(0.4 if overflow else 0.8) # Ledger lines above stave. for l in range(6, max_stave_line + 1, 2): painter.drawLine(x - 20, ymid - 10 * l, x + 20, ymid - 10 * l) # Ledger lines below stave. for l in range(-6, min_stave_line - 1, -2): painter.drawLine(x - 20, ymid - 10 * l, x + 20, ymid - 10 * l) painter.setOpacity(1.0) for pitch in note.pitches: stave_line = pitch.stave_line - base_stave_line y = ymid - 10 * stave_line active_accidental = active_accidentals.get( pitch.value, '') if pitch.accidental != active_accidental: sym = self._accidental_map[pitch.accidental] svg_symbol.paintSymbol(painter, sym, QtCore.QPoint(x - 12, y)) active_accidentals[pitch.value] = pitch.accidental if note.base_duration >= audioproc.MusicalDuration( 1, 2): svg_symbol.paintSymbol(painter, 'note-head-void', QtCore.QPoint(x, y)) else: svg_symbol.paintSymbol(painter, 'note-head-black', QtCore.QPoint(x, y)) if note.base_duration <= audioproc.MusicalDuration( 1, 2): painter.fillRect(x + 8, y - 63, 3, 60, Qt.black) if note.base_duration == audioproc.MusicalDuration( 1, 8): flags = 1 elif note.base_duration == audioproc.MusicalDuration( 1, 16): flags = 2 elif note.base_duration == audioproc.MusicalDuration( 1, 32): flags = 3 else: flags = 0 for f in range(flags): svg_symbol.paintSymbol( painter, 'note-flag-down', QtCore.QPoint(x + 11, y - 63 + 12 * f)) for d in range(note.dots): painter.setPen(Qt.NoPen) painter.setBrush(Qt.black) painter.drawEllipse(x + 12 + 10 * d, y - 4, 9, 9) if note.tuplet != 0: painter.drawText(x - 5, y - 85, '%d' % note.tuplet) # if overflow: # n.setOpacity(0.4) x1 = max(x - 12, px) x2 = max(x + 13, x1) if x1 > px: self._edit_areas.append((px, x1, idx, False)) px = x1 if x2 > x1: self._edit_areas.append((x1, x2, idx, True)) px = x2 note_time += note.duration x += int(note_area_width * note.duration.fraction) if px < self.width(): self._edit_areas.append( (px, self.width(), len(self.measure.notes), False)) else: self._note_area = (0, self.width())
def duration(self) -> audioproc.MusicalDuration: time_signature = self.get_property_value('time_signature') return audioproc.MusicalDuration(time_signature.upper, time_signature.lower)
def duration(self) -> audioproc.MusicalDuration: return audioproc.MusicalDuration(1, 1)
def paintGhost(self, painter: QtGui.QPainter) -> None: if self.__ghost_pos is None: return ymid = self.height() // 2 tool = self.track_editor.currentToolType() pos = self.__ghost_pos painter.setOpacity(0.4) if tool.is_rest: sym = { 1: 'rest-whole', 2: 'rest-half', 4: 'rest-quarter', 8: 'rest-8th', 16: 'rest-16th', 32: 'rest-32th', }[self._tool_duration_map[tool].denominator] svg_symbol.paintSymbol(painter, sym, QtCore.QPoint(pos.x(), ymid)) elif tool.is_note: duration = self._tool_duration_map[tool] if duration >= audioproc.MusicalDuration(1, 2): svg_symbol.paintSymbol(painter, 'note-head-void', pos) else: svg_symbol.paintSymbol(painter, 'note-head-black', pos) if duration <= audioproc.MusicalDuration(1, 2): painter.fillRect(pos.x() + 8, pos.y() - 63, 3, 60, Qt.black) if duration == audioproc.MusicalDuration(1, 8): flags = 1 elif duration == audioproc.MusicalDuration(1, 16): flags = 2 elif duration == audioproc.MusicalDuration(1, 32): flags = 3 else: flags = 0 for f in range(flags): svg_symbol.paintSymbol( painter, 'note-flag-down', QtCore.QPoint(pos.x() + 11, pos.y() - 63 + 12 * f)) elif tool.is_accidental: accidental = { tools.ToolType.ACCIDENTAL_NATURAL: '', tools.ToolType.ACCIDENTAL_FLAT: 'b', tools.ToolType.ACCIDENTAL_SHARP: '#', tools.ToolType.ACCIDENTAL_DOUBLE_FLAT: 'bb', tools.ToolType.ACCIDENTAL_DOUBLE_SHARP: '##', }[tool] sym = self._accidental_map[accidental] svg_symbol.paintSymbol(painter, sym, pos) else: painter.setPen(Qt.NoPen) painter.setBrush(Qt.black) painter.drawEllipse(pos.x() - 15, pos.y() - 15, 31, 31)
def duration(self) -> audioproc.MusicalDuration: duration = audioproc.MusicalDuration() for mref in self.measure_list: duration += mref.measure.duration return duration