├── domain ├── __init__.py ├── lom │ ├── __init__.py │ ├── clip │ │ ├── __init__.py │ │ ├── automation │ │ │ └── __init__.py │ │ ├── ClipEnvelopeShowedEvent.py │ │ ├── ClipColorEnum.py │ │ ├── ClipConfig.py │ │ ├── ClipSlotSelectedEvent.py │ │ ├── ClipCreatedOrDeletedEvent.py │ │ ├── ClipAppearance.py │ │ └── ClipPlayingPosition.py │ ├── loop │ │ ├── __init__.py │ │ └── LoopableInterface.py │ ├── note │ │ └── __init__.py │ ├── scene │ │ ├── __init__.py │ │ ├── ScenesMappedEvent.py │ │ ├── PlayingSceneChangedEvent.py │ │ ├── ScenePositionScrolledEvent.py │ │ ├── SceneFiredEvent.py │ │ ├── SceneLastBarPassedEvent.py │ │ ├── NextSceneStartedEvent.py │ │ ├── SceneCropScroller.py │ │ ├── SceneAppearance.py │ │ └── LoopingSceneToggler.py │ ├── set │ │ ├── __init__.py │ │ ├── AbletonSetChangedEvent.py │ │ └── MixingService.py │ ├── song │ │ ├── __init__.py │ │ ├── components │ │ │ ├── __init__.py │ │ │ ├── QuantizationComponent.py │ │ │ └── TempoComponent.py │ │ ├── SongStartedEvent.py │ │ ├── SongStoppedEvent.py │ │ └── SongInitializedEvent.py │ ├── track │ │ ├── __init__.py │ │ ├── routing │ │ │ ├── __init__.py │ │ │ ├── InputRoutingChannelEnum.py │ │ │ ├── InputRoutingTypeEnum.py │ │ │ ├── OutputRoutingTypeEnum.py │ │ │ ├── TrackRoutingInterface.py │ │ │ ├── TrackOutputRouting.py │ │ │ └── TrackInputRouting.py │ │ ├── abstract_track │ │ │ ├── __init__.py │ │ │ ├── AbstractTrackNameUpdatedEvent.py │ │ │ └── AbstractTrackSelectedEvent.py │ │ ├── group_track │ │ │ ├── __init__.py │ │ │ ├── ext_track │ │ │ │ ├── __init__.py │ │ │ │ ├── ExtArmedEvent.py │ │ │ │ ├── SimpleAudioExtTrack.py │ │ │ │ ├── SimpleMidiExtTrack.py │ │ │ │ └── SimpleBaseExtTrack.py │ │ │ ├── matching_track │ │ │ │ ├── __init__.py │ │ │ │ ├── MatchingTrackCreatorInterface.py │ │ │ │ └── MatchingTrackSoloState.py │ │ │ ├── VocalsTrack.py │ │ │ ├── DrumsTrack.py │ │ │ └── NormalGroupMatchingTrack.py │ │ ├── simple_track │ │ │ ├── __init__.py │ │ │ ├── audio │ │ │ │ ├── __init__.py │ │ │ │ ├── master │ │ │ │ │ └── __init__.py │ │ │ │ ├── special │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── ResamplingTrack.py │ │ │ │ │ └── ReferenceTrack.py │ │ │ │ └── SimpleReturnTrack.py │ │ │ ├── midi │ │ │ │ ├── __init__.py │ │ │ │ └── special │ │ │ │ │ └── __init__.py │ │ │ ├── SimpleTrackSaveStartedEvent.py │ │ │ ├── SimpleTrackArmedEvent.py │ │ │ ├── SimpleTrackFlattenedEvent.py │ │ │ ├── SimpleTrackCreatedEvent.py │ │ │ ├── SimpleTrackDeletedEvent.py │ │ │ ├── SimpleTrackService.py │ │ │ ├── SimpleTrackClips.py │ │ │ └── SimpleTrackMonitoringState.py │ │ ├── TrackAddedEvent.py │ │ ├── TracksMappedEvent.py │ │ ├── SelectedTrackChangedEvent.py │ │ ├── CurrentMonitoringStateEnum.py │ │ ├── TrackColorEnum.py │ │ ├── P0TrackInterface.py │ │ ├── TrackDisconnectedEvent.py │ │ └── TrackService.py │ ├── clip_slot │ │ ├── __init__.py │ │ ├── ClipSlotHasClipEvent.py │ │ ├── MidiClipSlot.py │ │ └── ClipSlotAppearance.py │ ├── device │ │ ├── __init__.py │ │ ├── Sample │ │ │ ├── __init__.py │ │ │ └── SampleNotFoundError.py │ │ ├── DrumRackLoadedEvent.py │ │ ├── DeviceLoadedEvent.py │ │ ├── DeviceEnumGroup.py │ │ ├── DrumRackDevice.py │ │ ├── PluginDevice.py │ │ └── MixerDevice.py │ ├── instrument │ │ ├── __init__.py │ │ ├── preset │ │ │ ├── __init__.py │ │ │ ├── preset_changer │ │ │ │ ├── __init__.py │ │ │ │ ├── PresetChangerInterface.py │ │ │ │ ├── SamplePresetChanger.py │ │ │ │ ├── ProgramChangePresetChanger.py │ │ │ │ └── InstrumentRackPresetChanger.py │ │ │ ├── preset_importer │ │ │ │ ├── __init__.py │ │ │ │ ├── NullPresetImporter.py │ │ │ │ ├── FilePresetImporter.py │ │ │ │ ├── RackDevicePresetImporter.py │ │ │ │ ├── PluginDevicePresetImporter.py │ │ │ │ └── PresetImportInterface.py │ │ │ ├── preset_initializer │ │ │ │ ├── __init__.py │ │ │ │ ├── PresetInitializerGroupTrackName.py │ │ │ │ ├── PresetInitializerInterface.py │ │ │ │ └── PresetInitializerDevicePresetName.py │ │ │ ├── SampleSelectedEvent.py │ │ │ ├── PresetProgramSelectedEvent.py │ │ │ ├── PresetDisplayOptionEnum.py │ │ │ └── InstrumentPreset.py │ │ ├── instrument │ │ │ ├── __init__.py │ │ │ ├── InstrumentSampler.py │ │ │ ├── InstrumentOpus.py │ │ │ ├── InstrumentPlay.py │ │ │ ├── InstrumentKontakt.py │ │ │ ├── InstrumentDrumRack.py │ │ │ ├── InstrumentSerum.py │ │ │ ├── InstrumentAddictiveKeys.py │ │ │ ├── InstrumentMinitaur.py │ │ │ └── InstrumentSimpler.py │ │ ├── InstrumentSelectedEvent.py │ │ ├── InstrumentActivatedEvent.py │ │ └── InstrumentColorEnum.py │ ├── sample │ │ ├── __init__.py │ │ └── SampleCategoryEnum.py │ ├── validation │ │ ├── __init__.py │ │ ├── sub_validators │ │ │ ├── __init__.py │ │ │ ├── AggregateValidator.py │ │ │ ├── CallbackValidator.py │ │ │ └── SimpleTrackHasDeviceValidator.py │ │ ├── object_validators │ │ │ ├── __init__.py │ │ │ ├── external_synth_track │ │ │ │ └── __init__.py │ │ │ ├── AbstractGroupTrackValidator.py │ │ │ ├── SimpleTrackValidator.py │ │ │ ├── SceneValidator.py │ │ │ └── SimpleAudioTrackValidator.py │ │ └── ValidatorInterface.py │ └── device_parameter │ │ ├── __init__.py │ │ └── DeviceParameterValue.py ├── audit │ ├── __init__.py │ ├── stats │ │ ├── __init__.py │ │ ├── Stats.py │ │ ├── TrackStats.py │ │ ├── SongStats.py │ │ └── SceneStats.py │ ├── utils.py │ ├── SongStatsService.py │ └── LOMAnalyzerService.py ├── shared │ ├── __init__.py │ ├── ui │ │ ├── __init__.py │ │ └── ColorEnum.py │ ├── backend │ │ ├── __init__.py │ │ ├── NotificationColorEnum.py │ │ ├── BackendEvent.py │ │ └── Backend.py │ ├── errors │ │ ├── __init__.py │ │ ├── Protocol0Error.py │ │ ├── ErrorRaisedEvent.py │ │ ├── Protocol0Warning.py │ │ └── error_handler.py │ ├── event │ │ ├── __init__.py │ │ └── HasEmitter.py │ ├── script │ │ └── __init__.py │ ├── utils │ │ ├── __init__.py │ │ ├── list.py │ │ ├── string.py │ │ ├── debug.py │ │ └── concurrency.py │ ├── scheduler │ │ ├── __init__.py │ │ ├── BarEndingEvent.py │ │ ├── BarChangedEvent.py │ │ ├── Last16thPassedEvent.py │ │ ├── Last32thPassedEvent.py │ │ ├── Last8thPassedEvent.py │ │ ├── LastBeatPassedEvent.py │ │ ├── ThirdBeatPassedEvent.py │ │ ├── TickSchedulerEventInterface.py │ │ ├── BeatSchedulerInterface.py │ │ └── TickSchedulerInterface.py │ ├── InterfaceClicksServiceInterface.py │ ├── SessionServiceInterface.py │ ├── LiveObject.py │ ├── BrowserServiceInterface.py │ └── ValueToggler.py └── track_recorder │ ├── __init__.py │ ├── config │ ├── __init__.py │ └── RecordProcessors.py │ ├── count_in │ ├── __init__.py │ ├── CountInInterface.py │ └── CountInShort.py │ ├── event │ ├── __init__.py │ ├── RecordEndedEvent.py │ ├── RecordCancelledEvent.py │ └── RecordStartedEvent.py │ ├── simple │ ├── __init__.py │ └── PostRecordSimple.py │ ├── external_synth │ ├── __init__.py │ ├── record_audio │ │ ├── __init__.py │ │ ├── record_multi │ │ │ └── __init__.py │ │ ├── PostRecordAudioFull.py │ │ └── PreRecordAudio.py │ ├── record_midi │ │ ├── __init__.py │ │ └── PreRecordMidi.py │ ├── AudioClipSilentEvent.py │ ├── ExtAudioRecordingEndedEvent.py │ └── ExtAudioRecordingStartedEvent.py │ ├── recording_bar_length │ ├── __init__.py │ ├── SelectedRecordingBarLengthUpdatedEvent.py │ ├── RecordingBarLengthEnum.py │ └── RecordingBarLengthScroller.py │ ├── AbstractRecorderFactory.py │ ├── RecordProcessorInterface.py │ └── RecordTypeEnum.py ├── infra ├── __init__.py ├── midi │ ├── __init__.py │ ├── MidiBytesSentEvent.py │ └── MidiBytesReceivedEvent.py ├── interface │ ├── __init__.py │ ├── session │ │ ├── __init__.py │ │ └── SessionUpdatedEvent.py │ ├── PixelEnum.py │ └── InterfaceClicksService.py ├── logging │ └── __init__.py ├── scheduler │ ├── __init__.py │ ├── BeatSchedulerEvent.py │ └── TickSchedulerEvent.py └── persistence │ ├── __init__.py │ ├── TrackDataEnum.py │ ├── SongDataEnum.py │ └── TrackData.py ├── shared ├── __init__.py ├── logging │ ├── __init__.py │ ├── LogLevelEnum.py │ ├── LoggerServiceInterface.py │ └── StatusBar.py ├── observer │ ├── __init__.py │ ├── Observer.py │ └── Observable.py ├── sequence │ ├── __init__.py │ ├── HasSequenceState.py │ └── SequenceTransition.py ├── types.py ├── UndoFacade.py ├── AbstractEnum.py └── Config.py ├── application ├── __init__.py ├── command │ ├── __init__.py │ ├── LoadRev2Command.py │ ├── ToggleArmCommand.py │ ├── GetSetStateCommand.py │ ├── LoadMinitaurCommand.py │ ├── ReloadScriptCommand.py │ ├── GoToGroupTrackCommand.py │ ├── PlayPauseSongCommand.py │ ├── ResetPlaybackCommand.py │ ├── ShowInstrumentCommand.py │ ├── DrumRackToSimplerCommand.py │ ├── FireSelectedSceneCommand.py │ ├── LoadMatchingTrackCommand.py │ ├── RecordUnlimitedCommand.py │ ├── ToggleSceneLoopCommand.py │ ├── BounceTrackToAudioCommand.py │ ├── UnfoldSelectedSceneCommand.py │ ├── CheckAudioExportValidCommand.py │ ├── ToggleReferenceTrackCommand.py │ ├── LoadDeviceCommand.py │ ├── SelectTrackCommand.py │ ├── ShowMessageCommand.py │ ├── ScrollScenesCommand.py │ ├── ShowAutomationCommand.py │ ├── ScrollSceneTracksCommand.py │ ├── ScrollTrackVolumeCommand.py │ ├── SelectOrLoadDeviceCommand.py │ ├── MidiNoteCommand.py │ ├── EmitBackendEventCommand.py │ ├── FireSceneToPositionCommand.py │ ├── LoadDrumRackCommand.py │ └── ScrollScenePositionCommand.py ├── error │ ├── __init__.py │ └── SentryService.py ├── command_handler │ ├── __init__.py │ ├── ShowMessageCommandHandler.py │ ├── GetSetStateCommandHandler.py │ ├── UnfoldSelectedSceneCommandHandler.py │ ├── ToggleReferenceTrackCommandHandler.py │ ├── ScrollSceneTracksCommandHandler.py │ ├── GoToGroupTrackCommandHandler.py │ ├── CommandHandlerInterface.py │ ├── ScrollTrackVolumeCommandHandler.py │ ├── ResetPlaybackCommandHandler.py │ ├── PlayPauseSongCommandHandler.py │ ├── ScrollScenesCommandHandler.py │ ├── ToggleSceneLoopCommandHandler.py │ ├── ShowAutomationCommandHandler.py │ ├── ToggleArmCommandJHandler.py │ ├── LoadDeviceCommandHandler.py │ ├── LoadRev2CommandHandler.py │ ├── BounceTrackToAudioCommandHandler.py │ ├── DrumRackToSimplerCommandHandler.py │ ├── FireSelectedSceneCommandHandler.py │ ├── LoadMatchingTrackCommandHandler.py │ ├── LoadMinitaurCommandHandler.py │ ├── ShowInstrumentCommandHandler.py │ ├── EmitBackendEventCommandHandler.py │ ├── ScrollScenePositionCommandHandler.py │ ├── SelectTrackCommandHandler.py │ ├── SelectOrLoadDeviceCommandHandler.py │ ├── RecordUnlimitedCommandHandler.py │ ├── LoadDrumRackCommandHandler.py │ ├── ReloadScriptCommandHandler.py │ └── CheckAudioExportValidCommandHandler.py ├── control_surface │ ├── __init__.py │ ├── group │ │ ├── __init__.py │ │ ├── legacy │ │ │ ├── __init__.py │ │ │ └── ActionGroupPreset.py │ │ ├── ActionGroupSample.py │ │ ├── ActionGroupFix.py │ │ ├── ActionGroupSet.py │ │ ├── ActionGroupLog.py │ │ ├── ActionGroupCut.py │ │ └── ActionGroupClip.py │ ├── EncoderMoveEnum.py │ └── ActionGroupFactory.py ├── ScriptDisconnectedEvent.py ├── ScriptResetActivatedEvent.py ├── push2 │ ├── __init__.py │ ├── P0TransportComponent.py │ ├── P0TrackListComponent.py │ └── P0ShowInstrumentMode.py └── ContainerInterface.py ├── tests ├── infra │ ├── __init__.py │ ├── listeners │ │ ├── __init__.py │ │ └── test_subject_slot.py │ └── scheduler │ │ ├── __init__.py │ │ ├── TickSchedulerEventTest.py │ │ └── TickSchedulerTest.py ├── domain │ ├── __init__.py │ ├── clip │ │ ├── __init__.py │ │ └── test_clip_playing_position.py │ ├── fixtures │ │ ├── __init__.py │ │ ├── clip_view.py │ │ ├── container.py │ │ ├── scene.py │ │ ├── song_view.py │ │ ├── device_parameter.py │ │ ├── clip_slot.py │ │ ├── device.py │ │ ├── group_track.py │ │ └── clip.py │ ├── scene │ │ ├── __init__.py │ │ ├── test_scene_clips.py │ │ ├── test_scene_length.py │ │ └── test_scene_playing_position.py │ ├── shared │ │ ├── __init__.py │ │ ├── event │ │ │ ├── __init__.py │ │ │ └── test_domain_event_bus.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ └── test_func.py │ │ ├── test_decorators.py │ │ └── test_live_object_mapping.py │ ├── track │ │ ├── __init__.py │ │ ├── test_audio_to_midi_clip_mapping.py │ │ └── test_instantiation.py │ └── validation │ │ ├── __init__.py │ │ ├── test_callback_validator.py │ │ └── test_property_validator.py ├── shared │ ├── __init__.py │ ├── sequence │ │ └── __init__.py │ └── test_container.py ├── application │ └── __init__.py └── __init__.py ├── .env.json.example ├── requirements.txt ├── .ipynb_checkpoints └── notes-checkpoint.ipynb ├── desktop.ini ├── tox.ini ├── .pre-commit-config.yaml ├── Makefile └── pyproject.toml /domain/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infra/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shared/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infra/midi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/infra/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/audit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/clip/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/loop/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/note/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/scene/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/set/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/song/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/shared/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/shared/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infra/interface/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infra/logging/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infra/scheduler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shared/logging/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shared/observer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shared/sequence/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/domain/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/shared/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/command/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/error/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/audit/stats/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/clip_slot/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/device/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/instrument/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/sample/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/validation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/shared/backend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/shared/errors/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/shared/event/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/shared/script/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/shared/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infra/persistence/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/application/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/domain/clip/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/domain/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/domain/scene/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/domain/shared/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/domain/track/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/infra/listeners/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/infra/scheduler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/shared/sequence/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/clip/automation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/device/Sample/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/song/components/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/routing/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/shared/scheduler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infra/interface/session/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/domain/shared/event/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/domain/shared/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/domain/validation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/command_handler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/control_surface/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/device_parameter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/abstract_track/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/count_in/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/event/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/simple/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/control_surface/group/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/audio/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/midi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/validation/sub_validators/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/control_surface/group/legacy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/ext_track/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/validation/object_validators/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_changer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_importer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/matching_track/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/audio/master/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/audio/special/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/midi/special/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/recording_bar_length/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_initializer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/record_audio/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/record_midi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/validation/object_validators/external_synth_track/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/record_audio/record_multi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/lom/track/TrackAddedEvent.py: -------------------------------------------------------------------------------- 1 | class TrackAddedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/scene/ScenesMappedEvent.py: -------------------------------------------------------------------------------- 1 | class ScenesMappedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/song/SongStartedEvent.py: -------------------------------------------------------------------------------- 1 | class SongStartedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/song/SongStoppedEvent.py: -------------------------------------------------------------------------------- 1 | class SongStoppedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/track/TracksMappedEvent.py: -------------------------------------------------------------------------------- 1 | class TracksMappedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/shared/scheduler/BarEndingEvent.py: -------------------------------------------------------------------------------- 1 | class BarEndingEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/device/DrumRackLoadedEvent.py: -------------------------------------------------------------------------------- 1 | class DrumRackLoadedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/song/SongInitializedEvent.py: -------------------------------------------------------------------------------- 1 | class SongInitializedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/shared/errors/Protocol0Error.py: -------------------------------------------------------------------------------- 1 | class Protocol0Error(RuntimeError): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/shared/scheduler/BarChangedEvent.py: -------------------------------------------------------------------------------- 1 | class BarChangedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/track_recorder/event/RecordEndedEvent.py: -------------------------------------------------------------------------------- 1 | class RecordEndedEvent(object): 2 | pass -------------------------------------------------------------------------------- /application/ScriptDisconnectedEvent.py: -------------------------------------------------------------------------------- 1 | class ScriptDisconnectedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /application/ScriptResetActivatedEvent.py: -------------------------------------------------------------------------------- 1 | class ScriptResetActivatedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /application/push2/__init__.py: -------------------------------------------------------------------------------- 1 | """Classes extending push2 code and modifying functionality""" 2 | -------------------------------------------------------------------------------- /domain/lom/clip/ClipEnvelopeShowedEvent.py: -------------------------------------------------------------------------------- 1 | class ClipEnvelopeShowedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/shared/scheduler/Last16thPassedEvent.py: -------------------------------------------------------------------------------- 1 | class Last16thPassedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/shared/scheduler/Last32thPassedEvent.py: -------------------------------------------------------------------------------- 1 | class Last32thPassedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/shared/scheduler/Last8thPassedEvent.py: -------------------------------------------------------------------------------- 1 | class Last8thPassedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/shared/scheduler/LastBeatPassedEvent.py: -------------------------------------------------------------------------------- 1 | class LastBeatPassedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/scene/PlayingSceneChangedEvent.py: -------------------------------------------------------------------------------- 1 | class PlayingSceneChangedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/track/SelectedTrackChangedEvent.py: -------------------------------------------------------------------------------- 1 | class SelectedTrackChangedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/shared/scheduler/ThirdBeatPassedEvent.py: -------------------------------------------------------------------------------- 1 | class ThirdBeatPassedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /.env.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "SAMPLE_DIRECTORY": "----fill-me----", 3 | "SENTRY_DSN": "----fill-me----", 4 | } -------------------------------------------------------------------------------- /domain/lom/instrument/InstrumentSelectedEvent.py: -------------------------------------------------------------------------------- 1 | class InstrumentSelectedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/scene/ScenePositionScrolledEvent.py: -------------------------------------------------------------------------------- 1 | class ScenePositionScrolledEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/track_recorder/event/RecordCancelledEvent.py: -------------------------------------------------------------------------------- 1 | class RecordCancelledEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/AudioClipSilentEvent.py: -------------------------------------------------------------------------------- 1 | class AudioClipSilentEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lebrunthibault/Protocol0-Ableton-Remote-Script/HEAD/requirements.txt -------------------------------------------------------------------------------- /tests/domain/fixtures/clip_view.py: -------------------------------------------------------------------------------- 1 | class AbletonClipView(object): 2 | def show_loop(self): 3 | pass 4 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/SimpleTrackSaveStartedEvent.py: -------------------------------------------------------------------------------- 1 | class SimpleTrackSaveStartedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /domain/lom/track/abstract_track/AbstractTrackNameUpdatedEvent.py: -------------------------------------------------------------------------------- 1 | class AbstractTrackNameUpdatedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/notes-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 2 6 | } 7 | -------------------------------------------------------------------------------- /desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | IconResource=C:\Users\thiba\OneDrive\Images\icons\icon.ico,0 3 | [ViewState] 4 | Mode= 5 | Vid= 6 | FolderType=Generic 7 | -------------------------------------------------------------------------------- /domain/track_recorder/recording_bar_length/SelectedRecordingBarLengthUpdatedEvent.py: -------------------------------------------------------------------------------- 1 | class SelectedRecordingBarLengthUpdatedEvent(object): 2 | pass 3 | -------------------------------------------------------------------------------- /shared/types.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Callable, Any, Tuple 2 | 3 | T = TypeVar("T") 4 | 5 | Func = Callable[..., Any] 6 | 7 | Coords = Tuple[int, int] 8 | -------------------------------------------------------------------------------- /domain/shared/backend/NotificationColorEnum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class NotificationColorEnum(Enum): 5 | INFO = "INFO" 6 | ERROR = "ERROR" 7 | -------------------------------------------------------------------------------- /domain/shared/InterfaceClicksServiceInterface.py: -------------------------------------------------------------------------------- 1 | class InterfaceClicksServiceInterface(object): 2 | def save_sample(self): 3 | # type: () -> None 4 | pass 5 | -------------------------------------------------------------------------------- /domain/shared/ui/ColorEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class ColorEnum(AbstractEnum): 5 | WARNING = 12 6 | FOCUSED = 26 7 | -------------------------------------------------------------------------------- /infra/persistence/TrackDataEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class TrackDataEnum(AbstractEnum): 5 | CLIP_MAPPING = "CLIP_MAPPING" 6 | -------------------------------------------------------------------------------- /domain/shared/SessionServiceInterface.py: -------------------------------------------------------------------------------- 1 | class SessionServiceInterface(object): 2 | def toggle_session_ring(self): 3 | # type: () -> None 4 | raise NotImplementedError 5 | -------------------------------------------------------------------------------- /application/command/LoadRev2Command.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class LoadRev2Command(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/ToggleArmCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ToggleArmCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /domain/shared/scheduler/TickSchedulerEventInterface.py: -------------------------------------------------------------------------------- 1 | class TickSchedulerEventInterface(object): 2 | def cancel(self): 3 | # type: () -> None 4 | raise NotImplementedError 5 | -------------------------------------------------------------------------------- /application/command/GetSetStateCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class GetSetStateCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/LoadMinitaurCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class LoadMinitaurCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/ReloadScriptCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ReloadScriptCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /domain/lom/track/CurrentMonitoringStateEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class CurrentMonitoringStateEnum(AbstractEnum): 5 | IN = 0 6 | AUTO = 1 7 | -------------------------------------------------------------------------------- /application/command/GoToGroupTrackCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class GoToGroupTrackCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/PlayPauseSongCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class PlayPauseSongCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/ResetPlaybackCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ResetPlaybackCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/ShowInstrumentCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ShowInstrumentCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/SampleSelectedEvent.py: -------------------------------------------------------------------------------- 1 | class SampleSelectedEvent(object): 2 | def __init__(self, sample_name): 3 | # type: (str) -> None 4 | self.sample_name = sample_name 5 | -------------------------------------------------------------------------------- /shared/logging/LogLevelEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class LogLevelEnum(AbstractEnum): 5 | DEV = 1 6 | INFO = 2 7 | WARNING = 3 8 | ERROR = 4 9 | -------------------------------------------------------------------------------- /application/command/DrumRackToSimplerCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class DrumRackToSimplerCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/FireSelectedSceneCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class FireSelectedSceneCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/LoadMatchingTrackCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class LoadMatchingTrackCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/RecordUnlimitedCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class RecordUnlimitedCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/ToggleSceneLoopCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ToggleSceneLoopCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /domain/lom/clip/ClipColorEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class ClipColorEnum(AbstractEnum): 5 | DISABLED = 13 6 | AUDIO_UN_QUANTIZED = 14 7 | BLINK = 28 8 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/VocalsTrack.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.NormalGroupTrack import NormalGroupTrack 2 | 3 | 4 | class VocalsTrack(NormalGroupTrack): 5 | TRACK_NAME = "Vocals" 6 | -------------------------------------------------------------------------------- /application/command/BounceTrackToAudioCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class BounceTrackToAudioCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/UnfoldSelectedSceneCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class UnfoldSelectedSceneCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/CheckAudioExportValidCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class CheckAudioExportValidCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /application/command/ToggleReferenceTrackCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ToggleReferenceTrackCommand(SerializableCommand): 5 | pass 6 | -------------------------------------------------------------------------------- /domain/lom/clip/ClipConfig.py: -------------------------------------------------------------------------------- 1 | class ClipConfig(object): 2 | def __init__(self, color=1, default_note=0): 3 | # type: (int, int) -> None 4 | self.color = color 5 | self.default_note = default_note 6 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/PresetProgramSelectedEvent.py: -------------------------------------------------------------------------------- 1 | class PresetProgramSelectedEvent(object): 2 | def __init__(self, preset_index): 3 | # type: (int) -> None 4 | self.preset_index = preset_index 5 | -------------------------------------------------------------------------------- /domain/lom/track/TrackColorEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class TrackColorEnum(AbstractEnum): 5 | RETURN = 10 6 | DISABLED = 13 7 | DRUMS = 61 8 | VOCALS = 61 9 | -------------------------------------------------------------------------------- /infra/midi/MidiBytesSentEvent.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | 4 | class MidiBytesSentEvent(object): 5 | def __init__(self, midi_bytes): 6 | # type: (Tuple) -> None 7 | self.midi_bytes = midi_bytes 8 | -------------------------------------------------------------------------------- /domain/audit/stats/Stats.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Protocol 2 | from typing import Dict 3 | 4 | 5 | class Stats(Protocol): 6 | def to_dict(self): 7 | # type: () -> Dict 8 | raise NotImplementedError 9 | -------------------------------------------------------------------------------- /domain/lom/clip_slot/ClipSlotHasClipEvent.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | class ClipSlotHasClipEvent(object): 4 | def __init__(self, live_track): 5 | # type: (Live.Track.Track) -> None 6 | self.live_track = live_track 7 | -------------------------------------------------------------------------------- /infra/midi/MidiBytesReceivedEvent.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | 4 | class MidiBytesReceivedEvent(object): 5 | def __init__(self, midi_bytes): 6 | # type: (Tuple) -> None 7 | self.midi_bytes = midi_bytes 8 | -------------------------------------------------------------------------------- /application/control_surface/EncoderMoveEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class EncoderMoveEnum(AbstractEnum): 5 | PRESS = "PRESS" 6 | LONG_PRESS = "LONG_PRESS" 7 | SCROLL = "SCROLL" 8 | -------------------------------------------------------------------------------- /domain/shared/errors/ErrorRaisedEvent.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | 4 | class ErrorRaisedEvent(object): 5 | def __init__(self, context=None): 6 | # type: (Optional[str]) -> None 7 | self.context = context 8 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = W605,W503, E501, E126, E122, E127, E121, E125, E123 3 | exclude = .git,__pycache__,tests,jupyter,vulture_whitelist.py,venv 4 | max-line-length = 100 5 | per-file-ignores = 6 | utils\decorators.py: F811, F401 7 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/PresetDisplayOptionEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class PresetDisplayOptionEnum(AbstractEnum): 5 | NONE = "NONE" 6 | NAME = "NAME" 7 | CATEGORY = "CATEGORY" 8 | -------------------------------------------------------------------------------- /domain/lom/clip/ClipSlotSelectedEvent.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | 4 | class ClipSlotSelectedEvent(object): 5 | def __init__(self, live_clip_slot): 6 | # type: (Live.ClipSlot.ClipSlot) -> None 7 | self.live_clip_slot = live_clip_slot 8 | -------------------------------------------------------------------------------- /domain/lom/scene/SceneFiredEvent.py: -------------------------------------------------------------------------------- 1 | class SceneFiredEvent(object): 2 | """Event emitted **before** the scene starts playing""" 3 | def __init__(self, scene_index): 4 | # type: (int) -> None 5 | self.scene_index = scene_index 6 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/SimpleTrackArmedEvent.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | 4 | class SimpleTrackArmedEvent(object): 5 | def __init__(self, live_track): 6 | # type: (Live.Track.Track) -> None 7 | self.live_track = live_track 8 | -------------------------------------------------------------------------------- /domain/shared/backend/BackendEvent.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | 4 | class BackendEvent(object): 5 | def __init__(self, event, data=None): 6 | # type: (str, Any) -> None 7 | self.event = event 8 | self.data = data 9 | -------------------------------------------------------------------------------- /domain/shared/event/HasEmitter.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Protocol, runtime_checkable 2 | 3 | 4 | @runtime_checkable 5 | class HasEmitter(Protocol): 6 | def target(self): 7 | # type: () -> object 8 | raise NotImplementedError 9 | -------------------------------------------------------------------------------- /domain/lom/track/abstract_track/AbstractTrackSelectedEvent.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | 4 | class AbstractTrackSelectedEvent(object): 5 | def __init__(self, live_track): 6 | # type: (Live.Track.Track) -> None 7 | self.live_track = live_track 8 | -------------------------------------------------------------------------------- /application/ContainerInterface.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from protocol0.shared.types import T 4 | 5 | 6 | class ContainerInterface(object): 7 | def get(self, cls): 8 | # type: (Type[T]) -> T 9 | raise NotImplementedError 10 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.3.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - id: flake8 9 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from protocol0.tests.domain.fixtures.p0 import monkey_patch_static 4 | from protocol0.tests.infra.scheduler.TickSchedulerTest import TickSchedulerTest 5 | 6 | sys.dont_write_bytecode = True # noqa 7 | 8 | monkey_patch_static() 9 | -------------------------------------------------------------------------------- /domain/lom/device/DeviceLoadedEvent.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 2 | 3 | 4 | class DeviceLoadedEvent(object): 5 | def __init__(self, device_enum): 6 | # type: (DeviceEnum) -> None 7 | self.device_enum = device_enum 8 | -------------------------------------------------------------------------------- /domain/lom/track/P0TrackInterface.py: -------------------------------------------------------------------------------- 1 | import Live 2 | from typing_extensions import Protocol 3 | 4 | 5 | class P0TrackInterface(Protocol): 6 | @property 7 | def _track(self): 8 | # type: () -> Live.Track.Track 9 | raise NotImplementedError 10 | -------------------------------------------------------------------------------- /infra/persistence/SongDataEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class SongDataEnum(AbstractEnum): 5 | SELECTED_SCENE_INDEX = "SELECTED_SCENE_INDEX" 6 | SELECTED_SCENE_POSITION = "SELECTED_SCENE_POSITION" 7 | SELECTED_TRACK_INDEX = "SELECTED_TRACK_INDEX" 8 | -------------------------------------------------------------------------------- /infra/interface/session/SessionUpdatedEvent.py: -------------------------------------------------------------------------------- 1 | class SessionUpdatedEvent(object): 2 | """ 3 | Used by push2. Emitted when the session is updated 4 | 5 | Emitted when : 6 | - the track / scene offset changes 7 | - a clip is added to a session scene 8 | """ 9 | 10 | pass 11 | -------------------------------------------------------------------------------- /domain/lom/instrument/InstrumentActivatedEvent.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 2 | 3 | 4 | class InstrumentActivatedEvent(object): 5 | def __init__(self, instrument): 6 | # type: (InstrumentInterface) -> None 7 | self.instrument = instrument 8 | -------------------------------------------------------------------------------- /tests/domain/shared/test_decorators.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.shared.utils.timing import throttle 2 | 3 | 4 | def test_throttle(): 5 | def func(val): 6 | return val + 10 7 | 8 | t = throttle(100)(func) 9 | 10 | for i in range(5): 11 | res = t(i) 12 | assert res == 10 13 | -------------------------------------------------------------------------------- /shared/observer/Observer.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Protocol, TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from protocol0.shared.observer.Observable import Observable 5 | 6 | 7 | class Observer(Protocol): 8 | def update(self, observable): 9 | # type: (Observable) -> None 10 | pass 11 | -------------------------------------------------------------------------------- /domain/lom/track/routing/InputRoutingChannelEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class InputRoutingChannelEnum(AbstractEnum): 5 | # AUDIO 6 | PRE_FX = "Pre FX" 7 | POST_FX = "Post FX" 8 | POST_MIXER = "Post Mixer" 9 | 10 | # MIDI 11 | CHANNEL_1 = "Ch. 1" 12 | -------------------------------------------------------------------------------- /domain/track_recorder/event/RecordStartedEvent.py: -------------------------------------------------------------------------------- 1 | class RecordStartedEvent(object): 2 | def __init__(self, scene_index, full_record, has_count_in): 3 | # type: (int, bool, bool) -> None 4 | self.scene_index = scene_index 5 | self.full_record = full_record 6 | self.has_count_in = has_count_in 7 | -------------------------------------------------------------------------------- /domain/lom/instrument/InstrumentColorEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class InstrumentColorEnum(AbstractEnum): 5 | UNKNOWN = 13 6 | SIMPLER = 2 7 | SERUM = 18 8 | REV2 = 23 9 | OPUS = 23 10 | PLAY = 23 11 | KONTAKT = 26 12 | MINITAUR = 69 13 | 14 | -------------------------------------------------------------------------------- /domain/lom/track/routing/InputRoutingTypeEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class InputRoutingTypeEnum(AbstractEnum): 5 | ALL_INS = "All Ins" 6 | EXT_IN = "Ext. In" 7 | REV2_AUX = "REV2_AUX" 8 | NO_INPUT = "No Input" 9 | COMPUTER_KEYBOARD = "Computer Keyboard" 10 | -------------------------------------------------------------------------------- /tests/domain/fixtures/container.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from protocol0.application.ContainerInterface import ContainerInterface 4 | from protocol0.shared.types import T 5 | 6 | 7 | class TestContainer(ContainerInterface): 8 | def get(self, cls): 9 | # type: (Type[T]) -> T 10 | raise None 11 | -------------------------------------------------------------------------------- /tests/domain/fixtures/scene.py: -------------------------------------------------------------------------------- 1 | from _Framework.SubjectSlot import Subject 2 | 3 | 4 | class AbletonScene(Subject): 5 | __subject_events__ = ("name", "is_triggered", "color") 6 | 7 | def __init__(self): 8 | self._live_ptr = id(self) 9 | self.name = "test scene" 10 | self.clip_slots = [] 11 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/SimpleTrackFlattenedEvent.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.lom.clip.ClipInfo import ClipInfo 4 | 5 | 6 | class SimpleTrackFlattenedEvent(object): 7 | def __init__(self, clip_infos): 8 | # type: (List[ClipInfo]) -> None 9 | self.clip_infos = clip_infos 10 | -------------------------------------------------------------------------------- /domain/shared/utils/list.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Callable, Iterable 2 | 3 | from protocol0.shared.types import T 4 | 5 | 6 | def find_if(predicate, seq): 7 | # type: (Callable[[T], bool], Iterable[T]) -> Optional[T] 8 | for x in seq: 9 | if predicate(x): 10 | return x 11 | return None 12 | -------------------------------------------------------------------------------- /infra/interface/PixelEnum.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | from protocol0.shared.AbstractEnum import AbstractEnum 4 | 5 | 6 | class PixelEnum(AbstractEnum): 7 | SAVE_SAMPLE = (534, 1316) 8 | 9 | @property 10 | def coordinates(self): 11 | # type: () -> Tuple[int, int] 12 | return self.value 13 | -------------------------------------------------------------------------------- /domain/lom/track/routing/OutputRoutingTypeEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class OutputRoutingTypeEnum(AbstractEnum): 5 | MASTER = "Master" 6 | SENDS_ONLY = "Sends Only" 7 | 8 | @property 9 | def label(self): 10 | # type: () -> str 11 | return self.value 12 | -------------------------------------------------------------------------------- /shared/logging/LoggerServiceInterface.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from protocol0.shared.logging.LogLevelEnum import LogLevelEnum 4 | 5 | 6 | class LoggerServiceInterface(object): 7 | def log(self, message, debug=True, level=None): 8 | # type: (Any, bool, Optional[LogLevelEnum]) -> None 9 | pass 10 | -------------------------------------------------------------------------------- /application/command/LoadDeviceCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class LoadDeviceCommand(SerializableCommand): 5 | def __init__(self, enum_name): 6 | # type: (str) -> None 7 | super(LoadDeviceCommand, self).__init__() 8 | self.enum_name = enum_name 9 | -------------------------------------------------------------------------------- /application/command/SelectTrackCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class SelectTrackCommand(SerializableCommand): 5 | def __init__(self, track_name): 6 | # type: (str) -> None 7 | super(SelectTrackCommand, self).__init__() 8 | self.track_name = track_name -------------------------------------------------------------------------------- /application/command/ShowMessageCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ShowMessageCommand(SerializableCommand): 5 | def __init__(self, message): 6 | # type: (str) -> None 7 | super(ShowMessageCommand, self).__init__() 8 | self.message = message 9 | -------------------------------------------------------------------------------- /domain/lom/scene/SceneLastBarPassedEvent.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | 4 | class SceneLastBarPassedEvent(object): 5 | def __init__(self, live_scene): 6 | # type: (Live.Scene.Scene) -> None 7 | self.live_scene = live_scene 8 | 9 | def target(self): 10 | # type: () -> object 11 | return self.live_scene 12 | -------------------------------------------------------------------------------- /domain/lom/set/AbletonSetChangedEvent.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from protocol0.domain.lom.set.AbletonSet import AbletonSet 5 | 6 | 7 | class AbletonSetChangedEvent(object): 8 | def __init__(self, ableton_set): 9 | # type: (AbletonSet) -> None 10 | self.set = ableton_set 11 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/ext_track/ExtArmedEvent.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 2 | 3 | 4 | class ExtArmedEvent(object): 5 | def __init__(self, track, arm=True): 6 | # type: (SimpleAudioTrack, bool) -> None 7 | self.track = track 8 | self.arm = arm 9 | -------------------------------------------------------------------------------- /application/command/ScrollScenesCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ScrollScenesCommand(SerializableCommand): 5 | def __init__(self, go_next=False): 6 | # type: (bool) -> None 7 | super(ScrollScenesCommand, self).__init__() 8 | self.go_next = go_next 9 | -------------------------------------------------------------------------------- /application/command/ShowAutomationCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ShowAutomationCommand(SerializableCommand): 5 | def __init__(self, go_next=False): 6 | # type: (bool) -> None 7 | super(ShowAutomationCommand, self).__init__() 8 | self.go_next = go_next 9 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/InstrumentSampler.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.instrument.InstrumentColorEnum import InstrumentColorEnum 2 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 3 | 4 | 5 | class InstrumentSampler(InstrumentInterface): 6 | NAME = "Sampler" 7 | TRACK_COLOR = InstrumentColorEnum.SIMPLER 8 | -------------------------------------------------------------------------------- /domain/lom/track/TrackDisconnectedEvent.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from protocol0.domain.lom.track.abstract_track.AbstractTrack import AbstractTrack 5 | 6 | 7 | class TrackDisconnectedEvent(object): 8 | def __init__(self, track): 9 | # type: (AbstractTrack) -> None 10 | self.track = track 11 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/ExtAudioRecordingEndedEvent.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.ext_track.ExternalSynthTrack import ( 2 | ExternalSynthTrack, 3 | ) 4 | 5 | 6 | class ExtAudioRecordingEndedEvent(object): 7 | def __init__(self, track): 8 | # type: (ExternalSynthTrack) -> None 9 | self.track = track 10 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/ExtAudioRecordingStartedEvent.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.ext_track.ExternalSynthTrack import ( 2 | ExternalSynthTrack, 3 | ) 4 | 5 | 6 | class ExtAudioRecordingStartedEvent(object): 7 | def __init__(self, track): 8 | # type: (ExternalSynthTrack) -> None 9 | self.track = track 10 | -------------------------------------------------------------------------------- /application/command/ScrollSceneTracksCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ScrollSceneTracksCommand(SerializableCommand): 5 | def __init__(self, go_next=False): 6 | # type: (bool) -> None 7 | super(ScrollSceneTracksCommand, self).__init__() 8 | self.go_next = go_next 9 | -------------------------------------------------------------------------------- /application/command/ScrollTrackVolumeCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ScrollTrackVolumeCommand(SerializableCommand): 5 | def __init__(self, go_next=False): 6 | # type: (bool) -> None 7 | super(ScrollTrackVolumeCommand, self).__init__() 8 | self.go_next = go_next 9 | -------------------------------------------------------------------------------- /domain/lom/clip/ClipCreatedOrDeletedEvent.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | 4 | class ClipCreatedOrDeletedEvent(object): 5 | def __init__(self, live_clip_slot): 6 | # type: (Live.ClipSlot.ClipSlot) -> None 7 | self.live_clip_slot = live_clip_slot 8 | 9 | def target(self): 10 | # type: () -> object 11 | return self.live_clip_slot 12 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/SimpleTrackCreatedEvent.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from protocol0.domain.lom.track.simple_track.SimpleTrack import SimpleTrack 5 | 6 | 7 | class SimpleTrackCreatedEvent(object): 8 | def __init__(self, track): 9 | # type: (SimpleTrack) -> None 10 | self.track = track 11 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/SimpleTrackDeletedEvent.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from protocol0.domain.lom.track.simple_track.SimpleTrack import SimpleTrack 5 | 6 | 7 | class SimpleTrackDeletedEvent(object): 8 | def __init__(self, track): 9 | # type: (SimpleTrack) -> None 10 | self.track = track 11 | -------------------------------------------------------------------------------- /application/command/SelectOrLoadDeviceCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class SelectOrLoadDeviceCommand(SerializableCommand): 5 | def __init__(self, device_name): 6 | # type: (str) -> None 7 | super(SelectOrLoadDeviceCommand, self).__init__() 8 | self.device_name = device_name 9 | -------------------------------------------------------------------------------- /tests/domain/fixtures/song_view.py: -------------------------------------------------------------------------------- 1 | from _Framework.SubjectSlot import Subject 2 | 3 | 4 | class AbletonSongView(Subject): 5 | __subject_events__ = ("selected_track", "selected_scene", "selected_parameter", "detail_clip") 6 | 7 | def __init__(self): 8 | # type: () -> None 9 | self.selected_track = None 10 | self.selected_scene = None 11 | -------------------------------------------------------------------------------- /application/command/MidiNoteCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class MidiNoteCommand(SerializableCommand): 5 | def __init__(self, channel, note): 6 | # type: (int, int) -> None 7 | super(MidiNoteCommand, self).__init__() 8 | self.channel = channel 9 | self.note = note 10 | -------------------------------------------------------------------------------- /domain/shared/scheduler/BeatSchedulerInterface.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | 4 | class BeatSchedulerInterface(object): 5 | def wait_beats(self, beats, callback, execute_on_song_stop): 6 | # type: (float, Callable, bool) -> None 7 | raise NotImplementedError 8 | 9 | def reset(self): 10 | # type: () -> None 11 | raise NotImplementedError 12 | -------------------------------------------------------------------------------- /tests/domain/fixtures/device_parameter.py: -------------------------------------------------------------------------------- 1 | from _Framework.SubjectSlot import Subject 2 | 3 | 4 | class AbletonDeviceParameter(Subject): 5 | def __init__(self, name): 6 | # type: (str) -> None 7 | self._live_ptr = id(self) 8 | self.name = name 9 | self.is_enabled = True 10 | self.default_value = 0 11 | self.min = 0 12 | self.max = 1 13 | -------------------------------------------------------------------------------- /domain/audit/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Any 2 | 3 | from protocol0.domain.shared.backend.Backend import Backend 4 | 5 | 6 | def tail_logs(func): 7 | # type: (Callable) -> Callable 8 | def decorate(*a, **k): 9 | # type: (Any, Any) -> None 10 | res = func(*a, **k) 11 | 12 | Backend.client().tail_logs() 13 | 14 | return res 15 | 16 | return decorate 17 | -------------------------------------------------------------------------------- /domain/lom/device/Sample/SampleNotFoundError.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.shared.errors.Protocol0Error import Protocol0Error 2 | 3 | 4 | class SampleNotFoundError(Protocol0Error): 5 | def __init__(self, name): 6 | # type: (str) -> None 7 | super(SampleNotFoundError, self).__init__( 8 | "Cannot find browser item in the live library: %s\n" % name 9 | ) 10 | self.name = name 11 | -------------------------------------------------------------------------------- /domain/shared/errors/Protocol0Warning.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any 2 | 3 | from protocol0.domain.shared.errors.Protocol0Error import Protocol0Error 4 | 5 | 6 | class Protocol0Warning(Protocol0Error): 7 | def __init__(self, message=None, *a): 8 | # type: (Optional[Any], Any) -> None 9 | message = message or self.__class__.__name__ 10 | super(Protocol0Warning, self).__init__(message, *a) 11 | -------------------------------------------------------------------------------- /application/command/EmitBackendEventCommand.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from protocol0.application.command.SerializableCommand import SerializableCommand 4 | 5 | 6 | class EmitBackendEventCommand(SerializableCommand): 7 | def __init__(self, event, data=None): 8 | # type: (str, Any) -> None 9 | super(EmitBackendEventCommand, self).__init__() 10 | self.event = event 11 | self.data = data 12 | -------------------------------------------------------------------------------- /application/command/FireSceneToPositionCommand.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from protocol0.application.command.SerializableCommand import SerializableCommand 4 | 5 | 6 | class FireSceneToPositionCommand(SerializableCommand): 7 | def __init__(self, bar_length=None): 8 | # type: (Optional[int]) -> None 9 | super(FireSceneToPositionCommand, self).__init__() 10 | self.bar_length = bar_length 11 | -------------------------------------------------------------------------------- /domain/lom/scene/NextSceneStartedEvent.py: -------------------------------------------------------------------------------- 1 | class NextSceneStartedEvent(object): 2 | """ 3 | Only emitted when a Scene.next_scene starts 4 | Keeps the memory of the previously selected scene 5 | (so that firing a scene doesn't change the selected scene) 6 | """ 7 | def __init__(self, selected_scene_index): 8 | # type: (int) -> None 9 | self.selected_scene_index = selected_scene_index 10 | -------------------------------------------------------------------------------- /application/command/LoadDrumRackCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class LoadDrumRackCommand(SerializableCommand): 5 | def __init__(self, sample_category, sample_subcategory): 6 | # type: (str, str) -> None 7 | super(LoadDrumRackCommand, self).__init__() 8 | self.sample_category = sample_category 9 | self.sample_subcategory = sample_subcategory 10 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/InstrumentOpus.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 2 | from protocol0.domain.lom.instrument.InstrumentColorEnum import InstrumentColorEnum 3 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 4 | 5 | 6 | class InstrumentOpus(InstrumentInterface): # noqa 7 | NAME = "Opus" 8 | DEVICE = DeviceEnum.OPUS 9 | TRACK_COLOR = InstrumentColorEnum.OPUS 10 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/InstrumentPlay.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 2 | from protocol0.domain.lom.instrument.InstrumentColorEnum import InstrumentColorEnum 3 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 4 | 5 | 6 | class InstrumentPlay(InstrumentInterface): # noqa 7 | NAME = "Play" 8 | DEVICE = DeviceEnum.PLAY 9 | TRACK_COLOR = InstrumentColorEnum.PLAY 10 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/DrumsTrack.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.NormalGroupTrack import NormalGroupTrack 2 | from protocol0.domain.lom.track.simple_track.SimpleTrack import SimpleTrack 3 | 4 | 5 | class DrumsTrack(NormalGroupTrack): 6 | TRACK_NAME = "Drums" 7 | 8 | @classmethod 9 | def is_track_valid(cls, track): 10 | # type: (SimpleTrack) -> bool 11 | return track.name.split(" ")[0] == cls.TRACK_NAME 12 | -------------------------------------------------------------------------------- /domain/lom/validation/object_validators/AbstractGroupTrackValidator.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.AbstractGroupTrack import AbstractGroupTrack 2 | from protocol0.domain.lom.validation.sub_validators.AggregateValidator import AggregateValidator 3 | 4 | 5 | class AbstractGroupTrackValidator(AggregateValidator): 6 | def __init__(self, _): 7 | # type: (AbstractGroupTrack) -> None 8 | super(AbstractGroupTrackValidator, self).__init__([]) 9 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/InstrumentKontakt.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 2 | from protocol0.domain.lom.instrument.InstrumentColorEnum import InstrumentColorEnum 3 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 4 | 5 | 6 | class InstrumentKontakt(InstrumentInterface): # noqa 7 | NAME = "Kontakt" 8 | DEVICE = DeviceEnum.KONTAKT 9 | TRACK_COLOR = InstrumentColorEnum.KONTAKT 10 | -------------------------------------------------------------------------------- /domain/lom/clip_slot/MidiClipSlot.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from protocol0.domain.lom.clip.MidiClip import MidiClip 4 | from protocol0.domain.lom.clip_slot.ClipSlot import ClipSlot 5 | 6 | 7 | class MidiClipSlot(ClipSlot): 8 | CLIP_CLASS = MidiClip 9 | 10 | def __init__(self, *a, **k): 11 | # type: (Any, Any) -> None 12 | super(MidiClipSlot, self).__init__(*a, **k) 13 | self.clip = self.clip # type: Optional[MidiClip] 14 | -------------------------------------------------------------------------------- /domain/lom/set/MixingService.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.NormalGroupTrack import NormalGroupTrack 2 | from protocol0.shared.Song import Song 3 | 4 | 5 | class MixingService(object): 6 | def scroll_all_tracks_volume(self, go_next): 7 | # type: (bool) -> None 8 | for track in Song.abstract_tracks(): 9 | if isinstance(track, NormalGroupTrack): 10 | continue 11 | track.base_track.scroll_volume(go_next) 12 | -------------------------------------------------------------------------------- /domain/audit/SongStatsService.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from protocol0.domain.audit.stats.SongStats import SongStats 4 | from protocol0.domain.audit.utils import tail_logs 5 | from protocol0.shared.logging.Logger import Logger 6 | 7 | 8 | class SongStatsService(object): 9 | @tail_logs 10 | def display_song_stats(self): 11 | # type: () -> None 12 | stats = SongStats() 13 | # Logger.clear() 14 | Logger.info(json.dumps(stats.to_dict(), indent=4)) 15 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_importer/NullPresetImporter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 4 | from protocol0.domain.lom.instrument.preset.preset_importer.PresetImportInterface import ( 5 | PresetImportInterface, 6 | ) 7 | 8 | 9 | class NullPresetImporter(PresetImportInterface): 10 | def _import_presets(self): 11 | # type: () -> List[InstrumentPreset] 12 | return [] 13 | -------------------------------------------------------------------------------- /domain/track_recorder/count_in/CountInInterface.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.song.components.PlaybackComponent import PlaybackComponent 2 | from protocol0.domain.lom.track.abstract_track.AbstractTrack import AbstractTrack 3 | from protocol0.shared.sequence.Sequence import Sequence 4 | 5 | 6 | class CountInInterface(object): 7 | def launch(self, playback_component, track): 8 | # type: (PlaybackComponent, AbstractTrack) -> Sequence 9 | raise NotImplementedError 10 | -------------------------------------------------------------------------------- /infra/interface/InterfaceClicksService.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.shared.InterfaceClicksServiceInterface import InterfaceClicksServiceInterface 2 | from protocol0.domain.shared.backend.Backend import Backend 3 | from protocol0.infra.interface.PixelEnum import PixelEnum 4 | 5 | 6 | class InterfaceClicksService(InterfaceClicksServiceInterface): 7 | def save_sample(self): 8 | # type: () -> None 9 | Backend.client().click_vertical_zone(*PixelEnum.SAVE_SAMPLE.coordinates) 10 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/audio/special/ResamplingTrack.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 4 | 5 | 6 | class ResamplingTrack(SimpleAudioTrack): 7 | TRACK_NAME = "Resampling" 8 | 9 | def stop(self, *a, **k): 10 | # type: (Any, Any) -> None 11 | if any(clip.is_recording for clip in self.clips): 12 | return 13 | 14 | super(ResamplingTrack, self).stop(*a, **k) 15 | -------------------------------------------------------------------------------- /domain/lom/validation/object_validators/SimpleTrackValidator.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.simple_track.SimpleTrack import SimpleTrack 2 | from protocol0.domain.lom.validation.sub_validators.AggregateValidator import AggregateValidator 3 | 4 | 5 | class SimpleTrackValidator(AggregateValidator): 6 | def __init__(self, track): 7 | # type: (SimpleTrack) -> None 8 | self._track = track 9 | # nothing done here 10 | super(SimpleTrackValidator, self).__init__([]) 11 | -------------------------------------------------------------------------------- /application/command_handler/ShowMessageCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ShowMessageCommand import ShowMessageCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.shared.logging.StatusBar import StatusBar 4 | 5 | 6 | class ShowMessageCommandHandler(CommandHandlerInterface): 7 | def handle(self, command): 8 | # type: (ShowMessageCommand) -> None 9 | StatusBar.show_message(command.message) 10 | -------------------------------------------------------------------------------- /application/command_handler/GetSetStateCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.GetSetStateCommand import GetSetStateCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.set.AbletonSet import AbletonSet 4 | 5 | 6 | class GetSetStateCommandHandler(CommandHandlerInterface): 7 | def handle(self, _): 8 | # type: (GetSetStateCommand) -> None 9 | self._container.get(AbletonSet).notify(force=True) 10 | -------------------------------------------------------------------------------- /application/command_handler/UnfoldSelectedSceneCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.UnfoldSelectedSceneCommand import UnfoldSelectedSceneCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.shared.Song import Song 4 | 5 | 6 | class UnfoldSelectedSceneCommandHandler(CommandHandlerInterface): 7 | def handle(self, _): 8 | # type: (UnfoldSelectedSceneCommand) -> None 9 | Song.selected_scene().unfold() 10 | -------------------------------------------------------------------------------- /application/command_handler/ToggleReferenceTrackCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ToggleReferenceTrackCommand import ToggleReferenceTrackCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.shared.Song import Song 4 | 5 | 6 | class ToggleReferenceTrackCommandHandler(CommandHandlerInterface): 7 | def handle(self, _): 8 | # type: (ToggleReferenceTrackCommand) -> None 9 | Song.reference_track().toggle() 10 | -------------------------------------------------------------------------------- /application/command_handler/ScrollSceneTracksCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ScrollSceneTracksCommand import ScrollSceneTracksCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.shared.Song import Song 4 | 5 | 6 | class ScrollSceneTracksCommandHandler(CommandHandlerInterface): 7 | def handle(self, command): 8 | # type: (ScrollSceneTracksCommand) -> None 9 | Song.selected_scene().scroll_tracks(command.go_next) 10 | -------------------------------------------------------------------------------- /application/command_handler/GoToGroupTrackCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.GoToGroupTrackCommand import GoToGroupTrackCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.track.TrackService import TrackService 4 | 5 | 6 | class GoToGroupTrackCommandHandler(CommandHandlerInterface): 7 | def handle(self, _): 8 | # type: (GoToGroupTrackCommand) -> None 9 | self._container.get(TrackService).go_to_group_track() 10 | -------------------------------------------------------------------------------- /tests/domain/fixtures/clip_slot.py: -------------------------------------------------------------------------------- 1 | from _Framework.SubjectSlot import Subject 2 | 3 | from protocol0.tests.domain.fixtures.clip import AbletonClip 4 | 5 | 6 | class AbletonClipSlot(Subject): 7 | __subject_events__ = ("has_clip", "is_triggered") 8 | 9 | def __init__(self): 10 | self.clip = None 11 | self.has_clip = None 12 | self.has_stop_button = True 13 | self.playing_position = 0 14 | 15 | def add_clip(self): 16 | self.clip = AbletonClip() 17 | self.has_clip = True 18 | -------------------------------------------------------------------------------- /application/command_handler/CommandHandlerInterface.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from protocol0.application.ContainerInterface import ContainerInterface 4 | from protocol0.shared.sequence.Sequence import Sequence 5 | 6 | 7 | class CommandHandlerInterface(object): 8 | def __init__(self, container): 9 | # type: (ContainerInterface) -> None 10 | self._container = container 11 | 12 | def handle(self, message): 13 | # type: (Any) -> Optional[Sequence] 14 | raise NotImplementedError 15 | -------------------------------------------------------------------------------- /application/command_handler/ScrollTrackVolumeCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ScrollTrackVolumeCommand import ScrollTrackVolumeCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.shared.Song import Song 4 | 5 | 6 | class ScrollTrackVolumeCommandHandler(CommandHandlerInterface): 7 | def handle(self, command): 8 | # type: (ScrollTrackVolumeCommand) -> None 9 | Song.current_track().base_track.scroll_volume(command.go_next) 10 | -------------------------------------------------------------------------------- /application/command_handler/ResetPlaybackCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ResetPlaybackCommand import ResetPlaybackCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.song.components.PlaybackComponent import PlaybackComponent 4 | 5 | 6 | class ResetPlaybackCommandHandler(CommandHandlerInterface): 7 | def handle(self, _): 8 | # type: (ResetPlaybackCommand) -> None 9 | self._container.get(PlaybackComponent).reset() 10 | -------------------------------------------------------------------------------- /domain/lom/track/routing/TrackRoutingInterface.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | 4 | class TrackRoutingInterface(object): 5 | def __init__(self, live_track): 6 | # type: (Live.Track.Track) -> None 7 | super(TrackRoutingInterface, self).__init__() 8 | self._live_track = live_track 9 | 10 | def __repr__(self): 11 | # type: () -> str 12 | return self.__class__.__name__ 13 | 14 | @property 15 | def live_track(self): 16 | # type: () -> Live.Track.Track 17 | return self._live_track 18 | -------------------------------------------------------------------------------- /application/command_handler/PlayPauseSongCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.PlayPauseSongCommand import PlayPauseSongCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.song.components.PlaybackComponent import PlaybackComponent 4 | 5 | 6 | class PlayPauseSongCommandHandler(CommandHandlerInterface): 7 | def handle(self, command): 8 | # type: (PlayPauseSongCommand) -> None 9 | return self._container.get(PlaybackComponent).play_pause() 10 | -------------------------------------------------------------------------------- /application/command_handler/ScrollScenesCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ScrollScenesCommand import ScrollScenesCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.song.components.SceneComponent import SceneComponent 4 | 5 | 6 | class ScrollScenesCommandHandler(CommandHandlerInterface): 7 | def handle(self, command): 8 | # type: (ScrollScenesCommand) -> None 9 | self._container.get(SceneComponent).scroll_scenes(command.go_next) 10 | -------------------------------------------------------------------------------- /application/push2/P0TransportComponent.py: -------------------------------------------------------------------------------- 1 | from _Framework.ButtonElement import ButtonElement 2 | 3 | from ableton.v2.control_surface.control import ButtonControl 4 | from pushbase.transport_component import TransportComponent 5 | 6 | 7 | class P0TransportComponent(TransportComponent): 8 | stop_button = ButtonControl() 9 | 10 | @stop_button.released 11 | def _on_stop_button_released(self, _): 12 | # type: (ButtonElement) -> None 13 | self.song.is_playing = False 14 | # adding this 15 | self.song.stop_all_clips() 16 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/record_midi/PreRecordMidi.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.ext_track.ExternalSynthTrack import \ 2 | ExternalSynthTrack 3 | from protocol0.domain.track_recorder.config.RecordConfig import RecordConfig 4 | from protocol0.domain.track_recorder.RecordProcessorInterface import RecordProcessorInterface 5 | 6 | 7 | class PreRecordMidi(RecordProcessorInterface): 8 | def process(self, track, _): 9 | # type: (ExternalSynthTrack, RecordConfig) -> None 10 | track.monitoring_state.monitor_midi() 11 | -------------------------------------------------------------------------------- /application/command_handler/ToggleSceneLoopCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ToggleSceneLoopCommand import ToggleSceneLoopCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.song.components.SceneComponent import SceneComponent 4 | 5 | 6 | class ToggleSceneLoopCommandHandler(CommandHandlerInterface): 7 | def handle(self, _): 8 | # type: (ToggleSceneLoopCommand) -> None 9 | self._container.get(SceneComponent).looping_scene_toggler.toggle() 10 | -------------------------------------------------------------------------------- /application/command_handler/ShowAutomationCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ShowAutomationCommand import ShowAutomationCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.track.TrackAutomationService import TrackAutomationService 4 | 5 | 6 | class ShowAutomationCommandHandler(CommandHandlerInterface): 7 | def handle(self, command): 8 | # type: (ShowAutomationCommand) -> None 9 | self._container.get(TrackAutomationService).show_automation(command.go_next) 10 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/InstrumentDrumRack.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.instrument.InstrumentColorEnum import InstrumentColorEnum 2 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 3 | from protocol0.domain.lom.instrument.preset.PresetDisplayOptionEnum import PresetDisplayOptionEnum 4 | 5 | 6 | class InstrumentDrumRack(InstrumentInterface): 7 | NAME = "Drum Rack" 8 | TRACK_COLOR = InstrumentColorEnum.SIMPLER 9 | CAN_BE_SHOWN = False 10 | PRESET_DISPLAY_OPTION = PresetDisplayOptionEnum.NONE 11 | DEFAULT_NOTE = 36 12 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/InstrumentSerum.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 2 | from protocol0.domain.lom.instrument.InstrumentColorEnum import InstrumentColorEnum 3 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 4 | 5 | 6 | class InstrumentSerum(InstrumentInterface): 7 | NAME = "Serum" 8 | DEVICE = DeviceEnum.SERUM 9 | TRACK_COLOR = InstrumentColorEnum.SERUM 10 | PRESETS_PATH = ( 11 | "C:\\Users\\thiba\\OneDrive\\Documents\\Xfer\\Serum Presets\\System\\ProgramChanges.txt" 12 | ) 13 | -------------------------------------------------------------------------------- /application/command_handler/ToggleArmCommandJHandler.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from protocol0.application.command.ToggleArmCommand import ToggleArmCommand 4 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 5 | from protocol0.shared.Song import Song 6 | from protocol0.shared.sequence.Sequence import Sequence 7 | 8 | 9 | class ToggleArmCommandHandler(CommandHandlerInterface): 10 | def handle(self, _): 11 | # type: (ToggleArmCommand) -> Optional[Sequence] 12 | return Song.current_track().arm_state.toggle() 13 | -------------------------------------------------------------------------------- /application/command_handler/LoadDeviceCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.LoadDeviceCommand import LoadDeviceCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.device.DeviceService import DeviceService 4 | from protocol0.shared.sequence.Sequence import Sequence 5 | 6 | 7 | class LoadDeviceCommandHandler(CommandHandlerInterface): 8 | def handle(self, command): 9 | # type: (LoadDeviceCommand) -> Sequence 10 | return self._container.get(DeviceService).load_device(command.enum_name) 11 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_changer/PresetChangerInterface.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from protocol0.domain.lom.device.Device import Device 4 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 5 | 6 | 7 | class PresetChangerInterface(object): 8 | def __init__(self, device, preset_offset): 9 | # type: (Optional[Device], int) -> None 10 | self._device = device 11 | self._preset_offset = preset_offset 12 | 13 | def load(self, preset): 14 | # type: (InstrumentPreset) -> None 15 | raise NotImplementedError 16 | -------------------------------------------------------------------------------- /tests/infra/scheduler/TickSchedulerEventTest.py: -------------------------------------------------------------------------------- 1 | from threading import Timer 2 | 3 | from typing import Callable 4 | 5 | from protocol0.domain.shared.scheduler.TickSchedulerEventInterface import ( 6 | TickSchedulerEventInterface, 7 | ) 8 | 9 | 10 | class TickSchedulerEventTest(TickSchedulerEventInterface): 11 | def __init__(self, callback, tick_count): 12 | # type: (Callable, int) -> None 13 | self._timer = Timer(float(tick_count) / 100, callback) 14 | self._timer.start() 15 | 16 | def cancel(self): 17 | # type: () -> None 18 | self._timer.cancel() 19 | -------------------------------------------------------------------------------- /application/command/ScrollScenePositionCommand.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SerializableCommand import SerializableCommand 2 | 3 | 4 | class ScrollScenePositionCommand(SerializableCommand): 5 | def __init__(self, go_next=False, use_fine_scrolling=False): 6 | # type: (bool, bool) -> None 7 | """ 8 | go_next == True: go right 9 | use_fine_scrolling True: scroll by beat instead of bars 10 | """ 11 | super(ScrollScenePositionCommand, self).__init__() 12 | self.go_next = go_next 13 | self.use_fine_scrolling = use_fine_scrolling 14 | -------------------------------------------------------------------------------- /application/command_handler/LoadRev2CommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.LoadRev2Command import LoadRev2Command 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.instrument.InstrumentInterface import load_instrument_track 4 | from protocol0.domain.lom.instrument.instrument.InstrumentRev2 import InstrumentRev2 5 | 6 | 7 | class LoadRev2CommandHandler(CommandHandlerInterface): 8 | def handle(self, command): 9 | # type: (LoadRev2Command) -> None 10 | load_instrument_track(InstrumentRev2) 11 | 12 | -------------------------------------------------------------------------------- /application/command_handler/BounceTrackToAudioCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.BounceTrackToAudioCommand import BounceTrackToAudioCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.track.group_track.matching_track.MatchingTrackService import \ 4 | MatchingTrackService 5 | 6 | 7 | class BounceTrackToAudioCommandHandler(CommandHandlerInterface): 8 | def handle(self, command): 9 | # type: (BounceTrackToAudioCommand) -> None 10 | self._container.get(MatchingTrackService).bounce_current_track() 11 | -------------------------------------------------------------------------------- /application/command_handler/DrumRackToSimplerCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.DrumRackToSimplerCommand import DrumRackToSimplerCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.device.DrumRackService import DrumRackService 4 | from protocol0.shared.Song import Song 5 | 6 | 7 | class DrumRackToSimplerCommandHandler(CommandHandlerInterface): 8 | def handle(self, _): 9 | # type: (DrumRackToSimplerCommand) -> None 10 | self._container.get(DrumRackService).drum_rack_to_simpler(Song.selected_track()) 11 | -------------------------------------------------------------------------------- /application/command_handler/FireSelectedSceneCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.FireSelectedSceneCommand import FireSelectedSceneCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.scene.ScenePlaybackService import ScenePlaybackService 4 | from protocol0.shared.Song import Song 5 | 6 | 7 | class FireSelectedSceneCommandHandler(CommandHandlerInterface): 8 | def handle(self, _): 9 | # type: (FireSelectedSceneCommand) -> None 10 | self._container.get(ScenePlaybackService).fire_scene(Song.selected_scene()) 11 | -------------------------------------------------------------------------------- /application/command_handler/LoadMatchingTrackCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.LoadMatchingTrackCommand import LoadMatchingTrackCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 4 | from protocol0.shared.Song import Song 5 | 6 | class LoadMatchingTrackCommandHandler(CommandHandlerInterface): 7 | def handle(self, command): 8 | # type: (LoadMatchingTrackCommand) -> None 9 | Song.selected_track(SimpleAudioTrack).load_full_track() 10 | 11 | 12 | -------------------------------------------------------------------------------- /application/command_handler/LoadMinitaurCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.LoadMinitaurCommand import LoadMinitaurCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.instrument.InstrumentInterface import load_instrument_track 4 | from protocol0.domain.lom.instrument.instrument.InstrumentMinitaur import InstrumentMinitaur 5 | 6 | 7 | class LoadMinitaurCommandHandler(CommandHandlerInterface): 8 | def handle(self, command): 9 | # type: (LoadMinitaurCommand) -> None 10 | load_instrument_track(InstrumentMinitaur) 11 | -------------------------------------------------------------------------------- /application/command_handler/ShowInstrumentCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ShowInstrumentCommand import ShowInstrumentCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.instrument.InstrumentDisplayService import InstrumentDisplayService 4 | from protocol0.shared.Song import Song 5 | 6 | 7 | class ShowInstrumentCommandHandler(CommandHandlerInterface): 8 | def handle(self, _): 9 | # type: (ShowInstrumentCommand) -> None 10 | self._container.get(InstrumentDisplayService).show_instrument(Song.current_track()) 11 | -------------------------------------------------------------------------------- /application/push2/P0TrackListComponent.py: -------------------------------------------------------------------------------- 1 | from protocol0_push2.track_list import TrackListComponent 2 | from typing import Any 3 | 4 | from protocol0.domain.shared.ApplicationView import ApplicationView 5 | from protocol0.domain.shared.scheduler.Scheduler import Scheduler 6 | 7 | 8 | class P0TrackListComponent(TrackListComponent): 9 | def _select_mixable(self, track): 10 | # type: (Any) -> None 11 | """When clicking on a track selection button""" 12 | ApplicationView.show_device() 13 | Scheduler.defer(ApplicationView.focus_current_track) 14 | return super(P0TrackListComponent, self)._select_mixable(track) 15 | -------------------------------------------------------------------------------- /application/command_handler/EmitBackendEventCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.EmitBackendEventCommand import ( 2 | EmitBackendEventCommand, 3 | ) 4 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 5 | from protocol0.domain.shared.backend.BackendEvent import BackendEvent 6 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 7 | 8 | 9 | class EmitBackendEventCommandHandler(CommandHandlerInterface): 10 | def handle(self, command): 11 | # type: (EmitBackendEventCommand) -> None 12 | DomainEventBus.emit(BackendEvent(command.event, command.data)) 13 | -------------------------------------------------------------------------------- /domain/shared/LiveObject.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Protocol 2 | from typing import Any 3 | 4 | 5 | def liveobj_valid(obj): 6 | # type: (Any) -> bool 7 | u""" 8 | Check whether obj represents a valid Live API obj. 9 | 10 | This will return False both if obj represents a lost weakref or is None. 11 | It's important that Live API objects are not checked using "is None", since this 12 | would treat lost weakrefs as valid. 13 | """ 14 | return obj != None # noqa 15 | 16 | 17 | class LiveObject(Protocol): 18 | @property 19 | def _live_ptr(self): 20 | # type: () -> int 21 | raise NotImplementedError 22 | -------------------------------------------------------------------------------- /domain/lom/track/routing/TrackOutputRouting.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.routing.OutputRoutingTypeEnum import OutputRoutingTypeEnum 2 | from protocol0.domain.lom.track.routing.RoutingDisplayNameDescriptor import ( 3 | RoutingDisplayNameDescriptor, 4 | ) 5 | from protocol0.domain.lom.track.routing.RoutingTrackDescriptor import RoutingTrackDescriptor 6 | from protocol0.domain.lom.track.routing.TrackRoutingInterface import TrackRoutingInterface 7 | 8 | 9 | class TrackOutputRouting(TrackRoutingInterface): 10 | type = RoutingDisplayNameDescriptor("output_routing_type", OutputRoutingTypeEnum) 11 | track = RoutingTrackDescriptor("output_routing_type") 12 | -------------------------------------------------------------------------------- /domain/shared/utils/string.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from typing import Any 4 | 5 | 6 | def smart_string(s): 7 | # type: (Any) -> str 8 | if sys.version_info[0] == 3: 9 | return s 10 | 11 | if not isinstance(s, basestring): 12 | s = str(s) 13 | 14 | try: 15 | return s.decode("utf-8").encode("ascii", "ignore") 16 | except UnicodeEncodeError: 17 | return s.encode("utf-8") 18 | 19 | 20 | def title(s): 21 | # type: (str) -> str 22 | # .title is not good because of words starting with numbers 23 | if not s: 24 | return s 25 | 26 | s = s.strip() 27 | 28 | return s[0].capitalize() + s[1:] 29 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_changer/SamplePresetChanger.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 2 | from protocol0.domain.lom.instrument.preset.SampleSelectedEvent import SampleSelectedEvent 3 | from protocol0.domain.lom.instrument.preset.preset_changer.PresetChangerInterface import ( 4 | PresetChangerInterface, 5 | ) 6 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 7 | 8 | 9 | class SamplePresetChanger(PresetChangerInterface): 10 | def load(self, preset): 11 | # type: (InstrumentPreset) -> None 12 | DomainEventBus.emit(SampleSelectedEvent(str(preset.original_name))) 13 | -------------------------------------------------------------------------------- /domain/shared/scheduler/TickSchedulerInterface.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from protocol0.domain.shared.scheduler.TickSchedulerEventInterface import ( 4 | TickSchedulerEventInterface, 5 | ) 6 | 7 | 8 | class TickSchedulerInterface(object): 9 | def schedule(self, tick_count, callback, unique=False): 10 | # type: (int, Callable, bool) -> TickSchedulerEventInterface 11 | """timeout_duration in ms""" 12 | raise NotImplementedError 13 | 14 | def start(self): 15 | # type: () -> None 16 | raise NotImplementedError 17 | 18 | def stop(self): 19 | # type: () -> None 20 | raise NotImplementedError 21 | -------------------------------------------------------------------------------- /tests/domain/scene/test_scene_clips.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | 3 | from protocol0.domain.lom.scene.SceneClips import SceneClips 4 | from protocol0.shared.Song import Song 5 | from protocol0.tests.domain.fixtures.clip_slot import AbletonClipSlot 6 | from protocol0.tests.domain.fixtures.p0 import make_protocol0 7 | 8 | 9 | def test_scene_clips(): 10 | make_protocol0() 11 | clips = SceneClips(0) 12 | assert len(list(clips)) == 0 13 | clip_slot = Song.selected_track().clip_slots[0] 14 | cast(AbletonClipSlot, clip_slot._clip_slot).add_clip() 15 | clip_slot._has_clip_listener() 16 | 17 | clips = SceneClips(0) 18 | assert len(list(clips)) == 1 19 | -------------------------------------------------------------------------------- /tests/shared/test_container.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Type, Dict 2 | 3 | from protocol0.application.Container import Container 4 | 5 | 6 | class ContainerTest(Container): 7 | # noinspection PyMissingConstructor 8 | def __init__(self): 9 | self._registry = {} # type: Dict[Type, Any] 10 | 11 | 12 | class Interface(object): 13 | pass 14 | 15 | 16 | class Implementation(Interface): 17 | pass 18 | 19 | 20 | def test_container(): 21 | container = ContainerTest() 22 | service = Implementation() 23 | container._register(service) 24 | assert container.get(Implementation) == service 25 | assert container.get(Interface) == service 26 | -------------------------------------------------------------------------------- /application/command_handler/ScrollScenePositionCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ScrollScenePositionCommand import ScrollScenePositionCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.shared.Song import Song 4 | 5 | 6 | class ScrollScenePositionCommandHandler(CommandHandlerInterface): 7 | def handle(self, command): 8 | # type: (ScrollScenePositionCommand) -> None 9 | position_scroller = Song.selected_scene().position_scroller 10 | 11 | position_scroller.use_fine_scrolling = command.use_fine_scrolling 12 | position_scroller.scroll(command.go_next) 13 | -------------------------------------------------------------------------------- /domain/lom/validation/ValidatorInterface.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | from typing import Optional 4 | 5 | from protocol0.shared.sequence.Sequence import Sequence 6 | 7 | 8 | class ValidatorInterface(object): 9 | __metaclass__ = ABCMeta 10 | 11 | @abstractmethod 12 | def is_valid(self): 13 | # type: () -> bool 14 | raise NotImplementedError 15 | 16 | @abstractmethod 17 | def get_error_message(self): 18 | # type: () -> Optional[str] 19 | raise NotImplementedError 20 | 21 | @abstractmethod 22 | def fix(self): 23 | # type: () -> Optional[Sequence] 24 | raise NotImplementedError 25 | -------------------------------------------------------------------------------- /shared/UndoFacade.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional 2 | 3 | 4 | class UndoFacade(object): 5 | _INSTANCE = None # type: Optional[UndoFacade] 6 | 7 | def __init__(self, begin_undo_step, end_undo_step): 8 | # type: (Callable, Callable) -> None 9 | UndoFacade._INSTANCE = self 10 | self._begin_undo_step = begin_undo_step 11 | self._end_undo_step = end_undo_step 12 | 13 | 14 | @classmethod 15 | def begin_undo_step(cls): 16 | # type: () -> None 17 | cls._INSTANCE._end_undo_step() 18 | 19 | @classmethod 20 | def end_undo_step(cls): 21 | # type: () -> None 22 | cls._INSTANCE._end_undo_step() 23 | -------------------------------------------------------------------------------- /domain/lom/device/DeviceEnumGroup.py: -------------------------------------------------------------------------------- 1 | from typing import List, TYPE_CHECKING, Dict 2 | 3 | if TYPE_CHECKING: 4 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 5 | 6 | 7 | class DeviceEnumGroup(object): 8 | def __init__(self, name, enums): 9 | # type: (str, List[DeviceEnum]) -> None 10 | self.name = name 11 | self.enums = enums 12 | 13 | def __repr__(self): 14 | # type: () -> str 15 | return "DeviceEnumGroup('%s')" % self.name 16 | 17 | def to_dict(self): 18 | # type: () -> Dict 19 | return { 20 | "name": self.name, 21 | "devices": [d.name for d in self.enums] 22 | } 23 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_initializer/PresetInitializerGroupTrackName.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 4 | from protocol0.domain.lom.instrument.preset.preset_initializer.PresetInitializerInterface import ( 5 | PresetInitializerInterface, 6 | ) 7 | from protocol0.domain.shared.utils.list import find_if 8 | 9 | 10 | class PresetInitializerGroupTrackName(PresetInitializerInterface): 11 | def get_selected_preset(self, presets): 12 | # type: (List[InstrumentPreset]) -> Optional[InstrumentPreset] 13 | return find_if(lambda p: p.name == self._track_name, presets) 14 | -------------------------------------------------------------------------------- /application/control_surface/group/ActionGroupSample.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.control_surface.ActionGroupInterface import ActionGroupInterface 2 | from protocol0.domain.lom.device.DrumRackSampleService import DrumRackSampleService 3 | 4 | 5 | class ActionGroupLog(ActionGroupInterface): 6 | CHANNEL = 16 7 | 8 | def configure(self): 9 | # type: () -> None 10 | # LOG encoder 11 | self.add_encoder( 12 | identifier=1, 13 | name="warp selected pad", 14 | on_press=self._container.get(DrumRackSampleService).warp_selected_sample, 15 | on_long_press=self._container.get(DrumRackSampleService).warp_all, 16 | ) 17 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/InstrumentAddictiveKeys.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 2 | from protocol0.domain.lom.instrument.InstrumentColorEnum import InstrumentColorEnum 3 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 4 | from protocol0.domain.lom.instrument.preset.preset_changer.InstrumentRackPresetChanger import ( 5 | InstrumentRackPresetChanger, 6 | ) 7 | 8 | 9 | class InstrumentAddictiveKeys(InstrumentInterface): # noqa 10 | NAME = "Addictive Keys Piano" 11 | DEVICE = DeviceEnum.ADDICTIVE_KEYS 12 | TRACK_COLOR = InstrumentColorEnum.KONTAKT 13 | PRESET_CHANGER = InstrumentRackPresetChanger 14 | -------------------------------------------------------------------------------- /application/command_handler/SelectTrackCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.SelectTrackCommand import SelectTrackCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.shared.utils.list import find_if 4 | from protocol0.shared.Song import Song 5 | 6 | 7 | class SelectTrackCommandHandler(CommandHandlerInterface): 8 | def handle(self, command): 9 | # type: (SelectTrackCommand) -> None 10 | track = find_if(lambda t: t.name == command.track_name, Song.simple_tracks()) 11 | 12 | assert track is not None, "Couldn't find track %s" % command.track_name 13 | track.select() 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test flake8 mypy vulture black check 2 | 3 | test: 4 | cls||clear 5 | ./venv/scripts/pytest -s . 6 | 7 | flake8: 8 | cls||clear 9 | ./venv/scripts/flake8 . 10 | 11 | mypy: 12 | cls||clear 13 | mypy . 14 | 15 | vulture: 16 | cls||clear 17 | ./venv/scripts/vulture . .\vulture_whitelist.py --make-whitelist --exclude=venv/,command/,command_handler/,push2/,InputRoutingTypeEnum.py,InputRoutingChannelEnum.py,OutputRoutingTypeEnum.py,BarLengthEnum.py --ignore-names=Optional,Tuple,Deque,Union,CollectionsSequence,Iterator,TracebackType,Func,decorate,subject,TRACK_COLOR 18 | 19 | black: 20 | cls||clear 21 | black . 22 | 23 | check: 24 | make mypy 25 | make vulture 26 | make test 27 | -------------------------------------------------------------------------------- /application/command_handler/SelectOrLoadDeviceCommandHandler.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from protocol0.application.command.SelectOrLoadDeviceCommand import SelectOrLoadDeviceCommand 4 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 5 | from protocol0.domain.lom.device.DeviceService import DeviceService 6 | from protocol0.shared.sequence.Sequence import Sequence 7 | 8 | 9 | class SelectOrLoadDeviceCommandHandler(CommandHandlerInterface): 10 | def handle(self, command): 11 | # type: (SelectOrLoadDeviceCommand) -> Optional[Sequence] 12 | return self._container.get(DeviceService).select_or_load_device(command.device_name) 13 | -------------------------------------------------------------------------------- /domain/track_recorder/recording_bar_length/RecordingBarLengthEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.shared.AbstractEnum import AbstractEnum 2 | 3 | 4 | class RecordingBarLengthEnum(AbstractEnum): 5 | _order_ = "ONE, TWO, FOUR, EIGHT, SIXTEEN, TWENTY_FOUR, THIRTY_TWO, SIXTY_FOUR" 6 | 7 | ONE = 1 8 | TWO = 2 9 | FOUR = 4 10 | EIGHT = 8 11 | SIXTEEN = 16 12 | TWENTY_FOUR = 24 13 | THIRTY_TWO = 32 14 | SIXTY_FOUR = 64 15 | 16 | @property 17 | def bar_length_value(self): 18 | # type: () -> int 19 | return self.value 20 | 21 | def __str__(self): 22 | # type: () -> str 23 | return "%s bar%s" % (self.value, "s" if abs(self.value) != 1 else "") 24 | -------------------------------------------------------------------------------- /domain/shared/errors/error_handler.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from typing import Any 4 | 5 | from protocol0.shared.types import Func 6 | 7 | 8 | def handle_error(func): 9 | # type: (Func) -> Func 10 | @wraps(func) 11 | def decorate(*a, **k): 12 | # type: (Any, Any) -> Any 13 | # noinspection PyBroadException 14 | try: 15 | return func(*a, **k) 16 | except Exception: 17 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 18 | from protocol0.domain.shared.errors.ErrorRaisedEvent import ErrorRaisedEvent 19 | 20 | DomainEventBus.emit(ErrorRaisedEvent()) 21 | 22 | return decorate 23 | -------------------------------------------------------------------------------- /domain/track_recorder/count_in/CountInShort.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.song.components.PlaybackComponent import PlaybackComponent 2 | from protocol0.domain.lom.track.abstract_track.AbstractTrack import AbstractTrack 3 | from protocol0.domain.track_recorder.count_in.CountInInterface import CountInInterface 4 | from protocol0.shared.sequence.Sequence import Sequence 5 | 6 | 7 | class CountInShort(CountInInterface): 8 | def launch(self, playback_component, _): 9 | # type: (PlaybackComponent, AbstractTrack) -> Sequence 10 | playback_component.stop_playing() 11 | # self.track.stop(immediate=True) 12 | seq = Sequence() 13 | seq.wait(40) # mini count in 14 | return seq.done() 15 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_initializer/PresetInitializerInterface.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from protocol0.domain.lom.device.Device import Device 4 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 5 | 6 | 7 | class PresetInitializerInterface(object): 8 | def __init__(self, device, track_name): 9 | # type: (Optional[Device], str) -> None 10 | """Fetches the selected preset from the device or track""" 11 | self._device = device 12 | self._track_name = track_name 13 | 14 | def get_selected_preset(self, presets): 15 | # type: (List[InstrumentPreset]) -> Optional[InstrumentPreset] 16 | raise NotImplementedError 17 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_importer/FilePresetImporter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 4 | from protocol0.domain.lom.instrument.preset.preset_importer.PresetImportInterface import ( 5 | PresetImportInterface, 6 | ) 7 | 8 | 9 | class FilePresetImporter(PresetImportInterface): 10 | def __init__(self, path): 11 | # type: (str) -> None 12 | self._path = path 13 | 14 | def _import_presets(self): 15 | # type: () -> List[InstrumentPreset] 16 | return [ 17 | InstrumentPreset(index=i, name=name) 18 | for i, name in enumerate(open(self._path).readlines()[0:128]) 19 | ] 20 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_initializer/PresetInitializerDevicePresetName.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 4 | from protocol0.domain.lom.instrument.preset.preset_initializer.PresetInitializerInterface import ( 5 | PresetInitializerInterface, 6 | ) 7 | from protocol0.domain.shared.utils.list import find_if 8 | 9 | 10 | class PresetInitializerDevicePresetName(PresetInitializerInterface): 11 | def get_selected_preset(self, presets): 12 | # type: (List[InstrumentPreset]) -> Optional[InstrumentPreset] 13 | assert self._device, "no device" 14 | return find_if(lambda p: p.name == self._device.preset_name, presets) 15 | -------------------------------------------------------------------------------- /domain/shared/BrowserServiceInterface.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 2 | from protocol0.shared.sequence.Sequence import Sequence 3 | 4 | 5 | class BrowserServiceInterface(object): 6 | def load_device_from_enum(self, device_enum): 7 | # type: (DeviceEnum) -> Sequence 8 | raise NotImplementedError 9 | 10 | def load_from_user_library(self, name): 11 | # type: (str) -> Sequence 12 | raise NotImplementedError 13 | 14 | def load_sample(self, sample_name): 15 | # type: (str) -> Sequence 16 | raise NotImplementedError 17 | 18 | def load_drum_pad_sample(self, sample_name): 19 | # type: (str) -> Sequence 20 | raise NotImplementedError 21 | -------------------------------------------------------------------------------- /tests/domain/fixtures/device.py: -------------------------------------------------------------------------------- 1 | from _Framework.SubjectSlot import Subject 2 | 3 | from protocol0.domain.lom.device_parameter.DeviceParameterEnum import DeviceParameterEnum 4 | from protocol0.tests.domain.fixtures.device_parameter import AbletonDeviceParameter 5 | 6 | 7 | class AbletonDevice(Subject): 8 | __subject_events__ = ("parameters",) 9 | 10 | def __init__(self, name): 11 | # type: (str) -> None 12 | self._live_ptr = id(self) 13 | self.name = name 14 | self.view = None 15 | self.parameters = [AbletonDeviceParameter(DeviceParameterEnum.DEVICE_ON.parameter_name)] 16 | self.can_have_drum_pads = False 17 | self.can_have_chains = False 18 | self.class_display_name = "" 19 | -------------------------------------------------------------------------------- /tests/domain/shared/utils/test_func.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from protocol0.domain.shared.utils.func import is_func_equal 4 | 5 | 6 | def test_func_equal(): 7 | def f(): 8 | pass 9 | 10 | assert is_func_equal(f, f) 11 | 12 | class T(object): 13 | def m(self): 14 | pass 15 | 16 | t = T() 17 | assert is_func_equal(t.m, t.m) 18 | 19 | p1 = partial(t.m) 20 | p2 = partial(t.m) 21 | 22 | assert is_func_equal(p1, p2) 23 | 24 | 25 | class Test(object): 26 | def m(self): 27 | pass 28 | 29 | 30 | def test_func_equal_method(): 31 | t1, t2 = Test(), Test() 32 | assert not is_func_equal(t1.m, t2.m) 33 | assert is_func_equal(t1.m, t2.m, compare_methods=True) 34 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/audio/SimpleReturnTrack.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from typing import Any 4 | 5 | from protocol0.domain.lom.track.TrackColorEnum import TrackColorEnum 6 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 7 | from protocol0.domain.shared.scheduler.Scheduler import Scheduler 8 | 9 | 10 | class SimpleReturnTrack(SimpleAudioTrack): 11 | IS_ACTIVE = False 12 | 13 | def __init__(self, *a, **k): 14 | # type: (Any, Any) -> None 15 | super(SimpleReturnTrack, self).__init__(*a, **k) 16 | Scheduler.defer( 17 | partial(setattr, self.appearance, "color", TrackColorEnum.RETURN.value) 18 | ) 19 | self.appearance.disconnect() 20 | -------------------------------------------------------------------------------- /application/push2/P0ShowInstrumentMode.py: -------------------------------------------------------------------------------- 1 | from ableton.v2.control_surface.mode import Mode, ModesComponent 2 | from protocol0.domain.lom.instrument.InstrumentSelectedEvent import InstrumentSelectedEvent 3 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 4 | 5 | 6 | class P0ShowInstrumentMode(Mode): 7 | def __init__(self, main_modes): 8 | # type: (ModesComponent) -> None 9 | self._main_modes = main_modes 10 | 11 | def enter_mode(self): 12 | # type: () -> None 13 | """cancel mode switch and dispatch event""" 14 | previous_mode = self._main_modes._mode_stack.clients[0] 15 | self._main_modes.selected_mode = previous_mode 16 | 17 | DomainEventBus.emit(InstrumentSelectedEvent()) 18 | -------------------------------------------------------------------------------- /application/command_handler/RecordUnlimitedCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.RecordUnlimitedCommand import ( 2 | RecordUnlimitedCommand, 3 | ) 4 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 5 | from protocol0.domain.track_recorder.RecordTypeEnum import RecordTypeEnum 6 | from protocol0.domain.track_recorder.RecordService import RecordService 7 | from protocol0.shared.Song import Song 8 | 9 | 10 | class RecordUnlimitedCommandHandler(CommandHandlerInterface): 11 | def handle(self, command): 12 | # type: (RecordUnlimitedCommand) -> None 13 | self._container.get(RecordService).record_track( 14 | Song.current_track(), RecordTypeEnum.MIDI_UNLIMITED 15 | ) 16 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/ext_track/SimpleAudioExtTrack.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, TYPE_CHECKING, Any 2 | 3 | if TYPE_CHECKING: 4 | from protocol0.domain.lom.track.group_track.ext_track.ExternalSynthTrack import ( 5 | ExternalSynthTrack, 6 | ) 7 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 8 | 9 | 10 | class SimpleAudioExtTrack(SimpleAudioTrack): 11 | """Tagging class for the main audio track of and ExternalSynthTrack""" 12 | 13 | def __init__(self, *a, **k): 14 | # type:(Any, Any) -> None 15 | super(SimpleAudioExtTrack, self).__init__(*a, **k) 16 | self.abstract_group_track = None # type: Optional[ExternalSynthTrack] 17 | self.clip_tail.active = False 18 | -------------------------------------------------------------------------------- /domain/track_recorder/AbstractRecorderFactory.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.abstract_track.AbstractTrack import AbstractTrack 2 | from protocol0.domain.track_recorder.config.RecordConfig import RecordConfig 3 | from protocol0.domain.track_recorder.RecordTypeEnum import RecordTypeEnum 4 | from protocol0.domain.track_recorder.config.RecordProcessors import RecordProcessors 5 | 6 | 7 | class AbstractTrackRecorderFactory(object): 8 | def get_record_config(self, track, record_type, recording_bar_length): 9 | # type: (AbstractTrack, RecordTypeEnum, int) -> RecordConfig 10 | raise NotImplementedError 11 | 12 | def get_processors(self, record_type): 13 | # type: (RecordTypeEnum) -> RecordProcessors 14 | raise NotImplementedError 15 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/matching_track/MatchingTrackCreatorInterface.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from protocol0.shared.sequence.Sequence import Sequence 4 | 5 | if TYPE_CHECKING: 6 | from protocol0.domain.lom.track.simple_track.SimpleTrack import SimpleTrack 7 | from protocol0.domain.lom.song.components.TrackCrudComponent import TrackCrudComponent 8 | 9 | 10 | class MatchingTrackCreatorInterface(object): 11 | def __init__(self, track_crud_component, base_track): 12 | # type: (TrackCrudComponent, SimpleTrack) -> None 13 | self._track_crud_component = track_crud_component 14 | self._base_track = base_track 15 | 16 | def bounce(self): 17 | # type: () -> Sequence 18 | raise NotImplementedError 19 | -------------------------------------------------------------------------------- /domain/track_recorder/RecordProcessorInterface.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | from typing import Optional, TYPE_CHECKING 4 | 5 | from protocol0.shared.sequence.Sequence import Sequence 6 | 7 | if TYPE_CHECKING: 8 | from protocol0.domain.lom.track.abstract_track.AbstractTrack import AbstractTrack 9 | from protocol0.domain.track_recorder.config.RecordConfig import RecordConfig 10 | 11 | 12 | class RecordProcessorInterface(object): 13 | __metaclass__ = ABCMeta 14 | 15 | def __repr__(self): 16 | # type: () -> str 17 | return self.__class__.__name__ 18 | 19 | @abstractmethod 20 | def process(self, track, config): 21 | # type: (AbstractTrack, RecordConfig) -> Optional[Sequence] 22 | raise NotImplementedError 23 | -------------------------------------------------------------------------------- /domain/track_recorder/simple/PostRecordSimple.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from protocol0.domain.lom.track.simple_track.SimpleTrack import SimpleTrack 4 | from protocol0.domain.shared.scheduler.Scheduler import Scheduler 5 | from protocol0.domain.track_recorder.RecordProcessorInterface import RecordProcessorInterface 6 | from protocol0.domain.track_recorder.config.RecordConfig import RecordConfig 7 | 8 | 9 | class PostRecordSimple(RecordProcessorInterface): 10 | def process(self, track, config): 11 | # type: (SimpleTrack, RecordConfig) -> None 12 | # deferring because the clip length is not accurate right now 13 | clip = track.clip_slots[config.scene_index].clip 14 | Scheduler.wait_ms(50, partial(clip.post_record, config.bar_length)) 15 | -------------------------------------------------------------------------------- /application/command_handler/LoadDrumRackCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.LoadDrumRackCommand import LoadDrumRackCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.sample.SampleCategoryEnum import SampleCategoryEnum 4 | from protocol0.domain.lom.track.TrackFactory import TrackFactory 5 | from protocol0.shared.sequence.Sequence import Sequence 6 | 7 | 8 | class LoadDrumRackCommandHandler(CommandHandlerInterface): 9 | def handle(self, command): 10 | # type: (LoadDrumRackCommand) -> Sequence 11 | return self._container.get(TrackFactory).add_sample_track( 12 | SampleCategoryEnum.from_value(command.sample_category.upper()), command.sample_subcategory 13 | ) 14 | -------------------------------------------------------------------------------- /shared/observer/Observable.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.shared.observer.Observer import Observer 4 | 5 | 6 | class Observable(object): 7 | def __init__(self): 8 | # type: () -> None 9 | self._observers = [] # type: List[Observer] 10 | 11 | def register_observer(self, observer): 12 | # type: (Observer) -> None 13 | if observer not in self._observers: 14 | self._observers.append(observer) 15 | 16 | def remove_observer(self, observer): 17 | # type: (Observer) -> None 18 | if observer in self._observers: 19 | self._observers.remove(observer) 20 | 21 | def notify_observers(self): 22 | # type: () -> None 23 | for observer in self._observers: 24 | observer.update(self) 25 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_importer/RackDevicePresetImporter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.lom.device.RackDevice import RackDevice 4 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 5 | from protocol0.domain.lom.instrument.preset.preset_importer.PresetImportInterface import ( 6 | PresetImportInterface, 7 | ) 8 | 9 | 10 | class RackDevicePresetImporter(PresetImportInterface): 11 | def __init__(self, device): 12 | # type: (RackDevice) -> None 13 | self._device = device 14 | 15 | def _import_presets(self): 16 | # type: () -> List[InstrumentPreset] 17 | return [ 18 | InstrumentPreset(index=i, name=chain.name) 19 | for i, chain in enumerate(self._device.chains) 20 | ] 21 | -------------------------------------------------------------------------------- /shared/sequence/HasSequenceState.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Protocol, runtime_checkable 2 | from typing import Any 3 | 4 | from protocol0.shared.observer.Observer import Observer 5 | from protocol0.shared.sequence.SequenceState import SequenceState 6 | 7 | 8 | @runtime_checkable 9 | class HasSequenceState(Protocol): 10 | @property 11 | def state(self): 12 | # type: () -> SequenceState 13 | raise NotImplementedError 14 | 15 | @property 16 | def res(self): 17 | # type: () -> Any 18 | raise NotImplementedError 19 | 20 | def register_observer(self, observer): 21 | # type: (Observer) -> None 22 | raise NotImplementedError 23 | 24 | def remove_observer(self, observer): 25 | # type: (Observer) -> None 26 | raise NotImplementedError 27 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_importer/PluginDevicePresetImporter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.lom.device.PluginDevice import PluginDevice 4 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 5 | from protocol0.domain.lom.instrument.preset.preset_importer.PresetImportInterface import ( 6 | PresetImportInterface, 7 | ) 8 | 9 | 10 | class PluginDevicePresetImporter(PresetImportInterface): 11 | def __init__(self, device): 12 | # type: (PluginDevice) -> None 13 | self._device = device 14 | 15 | def _import_presets(self): 16 | # type: () -> List[InstrumentPreset] 17 | return [ 18 | InstrumentPreset(index=i, name=preset) 19 | for i, preset in enumerate(self._device.presets[0:128]) 20 | ] 21 | -------------------------------------------------------------------------------- /shared/logging/StatusBar.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from typing import Optional 4 | 5 | from protocol0.shared.logging.Logger import Logger 6 | 7 | 8 | class StatusBar(object): 9 | """Facade for writing to the status bar""" 10 | 11 | _INSTANCE = None # type: Optional[StatusBar] 12 | 13 | def __init__(self, show_message): 14 | # type: (Callable) -> None 15 | StatusBar._INSTANCE = self 16 | self._show_message = show_message 17 | 18 | @classmethod 19 | def show_message(cls, message): 20 | # type: (str) -> None 21 | Logger.info(message) 22 | # noinspection PyBroadException 23 | try: 24 | cls._INSTANCE._show_message(str(message)) 25 | except Exception: 26 | Logger.warning("Couldn't show message : %s" % message) 27 | -------------------------------------------------------------------------------- /domain/lom/scene/SceneCropScroller.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.lom.scene.SceneLength import SceneLength 4 | from protocol0.domain.shared.ValueScroller import ValueScroller 5 | 6 | 7 | class SceneCropScroller(ValueScroller): 8 | def __init__(self, scene_length): 9 | # type: (SceneLength) -> None 10 | self._scene_length = scene_length 11 | super(SceneCropScroller, self).__init__(1) 12 | 13 | def _get_values(self): 14 | # type: () -> List 15 | bar_lengths = [] 16 | power = 0 17 | while pow(2, power) < self._scene_length.bar_length: 18 | bar_lengths += [-pow(2, power), pow(2, power)] 19 | power += 1 20 | bar_lengths = list(dict.fromkeys(bar_lengths)) 21 | bar_lengths.sort() 22 | return bar_lengths 23 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/InstrumentMinitaur.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.instrument.InstrumentColorEnum import InstrumentColorEnum 2 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 3 | from protocol0.domain.lom.instrument.preset.preset_initializer.PresetInitializerGroupTrackName import ( 4 | PresetInitializerGroupTrackName, 5 | ) 6 | 7 | 8 | class InstrumentMinitaur(InstrumentInterface): 9 | NAME = "Minitaur" 10 | PRESET_EXTENSION = ".syx" 11 | TRACK_COLOR = InstrumentColorEnum.MINITAUR 12 | INSTRUMENT_TRACK_NAME = "Bass" 13 | CAN_BE_SHOWN = False 14 | PRESETS_PATH = ( 15 | "C:\\Users\\thiba\\AppData\\Roaming\\Moog Music Inc\\Minitaur\\Presets Library\\User" 16 | ) 17 | PRESET_OFFSET = 1 18 | PRESET_INITIALIZER = PresetInitializerGroupTrackName 19 | -------------------------------------------------------------------------------- /domain/lom/clip_slot/ClipSlotAppearance.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | 4 | class ClipSlotAppearance(object): 5 | def __init__(self, live_clip_slot): 6 | # type: (Live.ClipSlot.ClipSlot) -> None 7 | self._live_clip_slot = live_clip_slot 8 | 9 | @property 10 | def has_stop_button(self): 11 | # type: () -> bool 12 | return self._live_clip_slot and self._live_clip_slot.has_stop_button 13 | 14 | @has_stop_button.setter 15 | def has_stop_button(self, has_stop_button): 16 | # type: (bool) -> None 17 | if self._live_clip_slot: 18 | self._live_clip_slot.has_stop_button = has_stop_button 19 | 20 | def refresh(self): 21 | # type: () -> None 22 | # we never use clip slot stop buttons 23 | if self.has_stop_button: 24 | self.has_stop_button = False 25 | -------------------------------------------------------------------------------- /domain/lom/track/routing/TrackInputRouting.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.routing.InputRoutingChannelEnum import InputRoutingChannelEnum 2 | from protocol0.domain.lom.track.routing.InputRoutingTypeEnum import InputRoutingTypeEnum 3 | from protocol0.domain.lom.track.routing.RoutingDisplayNameDescriptor import ( 4 | RoutingDisplayNameDescriptor, 5 | ) 6 | from protocol0.domain.lom.track.routing.RoutingTrackDescriptor import RoutingTrackDescriptor 7 | from protocol0.domain.lom.track.routing.TrackRoutingInterface import TrackRoutingInterface 8 | 9 | 10 | class TrackInputRouting(TrackRoutingInterface): 11 | type = RoutingDisplayNameDescriptor("input_routing_type", InputRoutingTypeEnum) 12 | track = RoutingTrackDescriptor("input_routing_type") 13 | channel = RoutingDisplayNameDescriptor("input_routing_channel", InputRoutingChannelEnum) 14 | -------------------------------------------------------------------------------- /application/control_surface/ActionGroupFactory.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | import protocol0.application.control_surface.group as group_package 4 | from protocol0.application.ContainerInterface import ContainerInterface 5 | from protocol0.application.control_surface.ActionGroupInterface import ActionGroupInterface 6 | from protocol0.domain.shared.utils.utils import import_package 7 | 8 | 9 | class ActionGroupFactory(object): 10 | @classmethod 11 | def create_action_groups(cls, container, component_guard): 12 | # type: (ContainerInterface, Callable) -> None 13 | import_package(group_package) 14 | group_classes = ActionGroupInterface.__subclasses__() 15 | 16 | for group_class in group_classes: 17 | if group_class.CHANNEL is not None: 18 | group_class(container, component_guard).configure() 19 | -------------------------------------------------------------------------------- /tests/infra/scheduler/TickSchedulerTest.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from protocol0.domain.shared.scheduler.TickSchedulerEventInterface import ( 4 | TickSchedulerEventInterface, 5 | ) 6 | from protocol0.domain.shared.scheduler.TickSchedulerInterface import TickSchedulerInterface 7 | from protocol0.tests.infra.scheduler.TickSchedulerEventTest import TickSchedulerEventTest 8 | 9 | 10 | class TickSchedulerTest(TickSchedulerInterface): 11 | """use threading instead of Live Timer""" 12 | 13 | def schedule(self, tick_count, callback, unique=False): 14 | # type: (int, Callable, bool) -> TickSchedulerEventInterface 15 | """timeout_duration in ms""" 16 | return TickSchedulerEventTest(callback, tick_count) 17 | 18 | def start(self): 19 | # type: () -> None 20 | pass 21 | 22 | def stop(self): 23 | pass 24 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/ext_track/SimpleMidiExtTrack.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from protocol0.domain.lom.device.Device import Device 4 | from protocol0.domain.lom.track.simple_track.midi.SimpleMidiTrack import SimpleMidiTrack 5 | from protocol0.domain.shared.utils.list import find_if 6 | 7 | 8 | class SimpleMidiExtTrack(SimpleMidiTrack): 9 | """Tagging class for the main midi track of an ExternalSynthTrack""" 10 | 11 | def __init__(self, *a, **k): 12 | # type: (Any, Any) -> None 13 | super(SimpleMidiExtTrack, self).__init__(*a, **k) 14 | self.clip_tail.active = False 15 | 16 | @property 17 | def external_device(self): 18 | # type: () -> Optional[Device] 19 | return find_if( 20 | lambda d: d.enum is not None and d.enum.is_external_device, 21 | list(self.devices) 22 | ) 23 | -------------------------------------------------------------------------------- /application/command_handler/ReloadScriptCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.ReloadScriptCommand import ReloadScriptCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.scene.SceneService import SceneService 4 | from protocol0.domain.lom.song.SongInitService import SongInitService 5 | from protocol0.domain.lom.track.TrackMapperService import TrackMapperService 6 | from protocol0.shared.logging.Logger import Logger 7 | 8 | 9 | class ReloadScriptCommandHandler(CommandHandlerInterface): 10 | def handle(self, _): 11 | # type: (ReloadScriptCommand) -> None 12 | Logger.clear() 13 | self._container.get(TrackMapperService).tracks_listener() 14 | self._container.get(SceneService).scenes_listener() 15 | self._container.get(SongInitService).init_song() 16 | -------------------------------------------------------------------------------- /tests/domain/track/test_audio_to_midi_clip_mapping.py: -------------------------------------------------------------------------------- 1 | from protocol0 import EmptyModule 2 | from protocol0.domain.lom.track.simple_track.AudioToMidiClipMapping import AudioToMidiClipMapping 3 | 4 | class ClipInfoTest(object): 5 | def __init__(self, clip_hash): 6 | self.hash = clip_hash 7 | 8 | 9 | # noinspection PyTypeChecker 10 | def test_audio_to_midi_clip_mapping(): 11 | mapping = AudioToMidiClipMapping(EmptyModule()) # noqa 12 | mapping.register_file_path("path1", ClipInfoTest(1)) 13 | mapping.register_file_path("path1_bis", ClipInfoTest(1)) 14 | mapping.register_file_path("path2", ClipInfoTest(2)) 15 | # modify the midi clip 16 | mapping.register_hash_equivalence(1, 11) 17 | 18 | assert mapping.path_matches_hash("path1", 1, False) 19 | assert not mapping.path_matches_hash("path1", 2, False) 20 | assert mapping.path_matches_hash("path1", 11, False) 21 | -------------------------------------------------------------------------------- /shared/AbstractEnum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import TypeVar, cast, Any, Dict 3 | 4 | from protocol0.domain.shared.errors.Protocol0Error import Protocol0Error 5 | 6 | T = TypeVar("T", bound=Enum) 7 | 8 | 9 | class AbstractEnum(Enum): 10 | def __str__(self): 11 | # type: () -> str 12 | return self.name 13 | 14 | @classmethod 15 | def from_value(cls, value): 16 | # type: (Any) -> T 17 | for _, enum in cls.__members__.items(): 18 | if value == enum.value: 19 | return cast(T, enum) 20 | 21 | raise Protocol0Error("Couldn't find matching enum for value %s" % value) 22 | 23 | def get_value_from_mapping(self, mapping): 24 | # type: (Dict[AbstractEnum, Any]) -> Any 25 | if self not in mapping: 26 | raise Protocol0Error("Couldn't find enum %s in mapping" % self) 27 | return mapping[self] 28 | -------------------------------------------------------------------------------- /application/command_handler/CheckAudioExportValidCommandHandler.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.command.CheckAudioExportValidCommand import CheckAudioExportValidCommand 2 | from protocol0.application.command_handler.CommandHandlerInterface import CommandHandlerInterface 3 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 4 | from protocol0.shared.Song import Song 5 | from protocol0.shared.logging.Logger import Logger 6 | 7 | 8 | class CheckAudioExportValidCommandHandler(CommandHandlerInterface): 9 | def handle(self, command): 10 | # type: (CheckAudioExportValidCommand) -> None 11 | sound_id_device = Song.master_track().devices.get_one_from_enum(DeviceEnum.SOUNDID_REFERENCE_PLUGIN) 12 | if sound_id_device is not None and sound_id_device.is_enabled: 13 | sound_id_device.is_enabled = False 14 | Logger.warning("Deactivating SoundID Reference plugin for export") 15 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_changer/ProgramChangePresetChanger.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.device.PluginDevice import PluginDevice 2 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 3 | from protocol0.domain.lom.instrument.preset.PresetProgramSelectedEvent import ( 4 | PresetProgramSelectedEvent, 5 | ) 6 | from protocol0.domain.lom.instrument.preset.preset_changer.PresetChangerInterface import ( 7 | PresetChangerInterface, 8 | ) 9 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 10 | 11 | 12 | class ProgramChangePresetChanger(PresetChangerInterface): 13 | def load(self, preset): 14 | # type: (InstrumentPreset) -> None 15 | if isinstance(self._device, PluginDevice): 16 | self._device.selected_preset_index = preset.index 17 | DomainEventBus.emit(PresetProgramSelectedEvent(preset.index + self._preset_offset)) 18 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_changer/InstrumentRackPresetChanger.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | 3 | from protocol0.domain.lom.device.RackDevice import RackDevice 4 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 5 | from protocol0.domain.lom.instrument.preset.preset_changer.PresetChangerInterface import ( 6 | PresetChangerInterface, 7 | ) 8 | from protocol0.domain.shared.utils.list import find_if 9 | 10 | 11 | class InstrumentRackPresetChanger(PresetChangerInterface): 12 | def load(self, preset): 13 | # type: (InstrumentPreset) -> None 14 | device = cast(RackDevice, self._device) 15 | chain_selector = find_if( 16 | lambda p: p.original_name.startswith("Chain Selector") and p.is_enabled, 17 | device.parameters, 18 | ) 19 | chain_selector.value = preset.index 20 | device.selected_chain = device.chains[preset.index] 21 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/preset_importer/PresetImportInterface.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict 2 | 3 | from protocol0.domain.lom.instrument.preset.InstrumentPreset import InstrumentPreset 4 | 5 | 6 | class PresetImportInterface(object): 7 | _PRESET_CACHE = {} # type: Dict[str, List[InstrumentPreset]] 8 | 9 | def import_presets(self, use_cache=True): 10 | # type: (bool) -> List[InstrumentPreset] 11 | cache_key = getattr(self, "_path", None) 12 | if use_cache and cache_key is not None and cache_key in self._PRESET_CACHE: 13 | return self._PRESET_CACHE[cache_key] 14 | else: 15 | presets = self._import_presets() 16 | if cache_key is not None: 17 | self._PRESET_CACHE[cache_key] = presets 18 | return presets 19 | 20 | def _import_presets(self): 21 | # type: () -> List[InstrumentPreset] 22 | raise NotImplementedError 23 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/ext_track/SimpleBaseExtTrack.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 2 | from protocol0.domain.shared.backend.Backend import Backend 3 | from protocol0.shared.logging.Logger import Logger 4 | from protocol0.shared.observer.Observable import Observable 5 | 6 | 7 | class SimpleBaseExtTrack(SimpleAudioTrack): 8 | """Tagging class for the main base track of and ExternalSynthTrack""" 9 | 10 | def update(self, observable): 11 | # type: (Observable) -> None 12 | super(SimpleBaseExtTrack, self).update(observable) 13 | 14 | devices_count = len(list(self.devices)) 15 | if devices_count > 0: 16 | Logger.info("Found %s devices on ext base track %s" % (devices_count, self)) 17 | 18 | if devices_count == 1: 19 | Backend.client().show_warning("Please add this device to the audio sub track", centered=True) 20 | -------------------------------------------------------------------------------- /tests/domain/clip/test_clip_playing_position.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | 3 | from protocol0.domain.lom.clip.Clip import Clip 4 | from protocol0.domain.lom.clip.ClipConfig import ClipConfig 5 | from protocol0.shared.Song import Song 6 | from protocol0.tests.domain.fixtures.clip_slot import AbletonClipSlot 7 | from protocol0.tests.domain.fixtures.p0 import make_protocol0 8 | 9 | 10 | def test_clip_playing_position(): 11 | make_protocol0() 12 | clip_slot = Song.selected_track().clip_slots[0] 13 | live_clip_slot = cast(AbletonClipSlot, clip_slot._clip_slot) 14 | live_clip_slot.add_clip() 15 | live_clip_slot.clip.length = 4 16 | clip = Clip(live_clip_slot.clip, 1, ClipConfig(1)) 17 | assert clip.playing_position.position == 0 18 | 19 | live_clip_slot.clip.length = 16 20 | live_clip_slot.clip.playing_position = 5.0 21 | clip = Clip(live_clip_slot.clip, 2, ClipConfig(2)) 22 | assert clip.playing_position.position == 5 23 | -------------------------------------------------------------------------------- /tests/domain/fixtures/group_track.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.device.Device import Device 2 | from protocol0.tests.domain.fixtures.device import AbletonDevice 3 | from protocol0.tests.domain.fixtures.simple_track import add_track, AbletonTrack, TrackType 4 | 5 | 6 | def add_external_synth_track(add_tail=False): 7 | # type: (bool) -> AbletonTrack 8 | group_track = add_track(track_type=TrackType.GROUP) 9 | midi_track = add_track(track_type=TrackType.MIDI) 10 | # add external device 11 | midi_track.devices.append(AbletonDevice("Ext. Audio Effect")) 12 | audio_track = add_track(track_type=TrackType.AUDIO) 13 | midi_track.group_track = group_track 14 | audio_track.group_track = group_track 15 | 16 | Device._get_class = classmethod(lambda _, __: Device) 17 | 18 | if add_tail: 19 | audio_tail_track = add_track(track_type=TrackType.AUDIO) 20 | audio_tail_track.group_track = group_track 21 | return group_track 22 | -------------------------------------------------------------------------------- /infra/scheduler/BeatSchedulerEvent.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from protocol0.infra.scheduler.BeatTime import BeatTime 4 | from protocol0.shared.Song import Song 5 | 6 | 7 | class BeatSchedulerEvent(object): 8 | def __init__(self, callback, beats_song_time, execute_on_song_stop): 9 | # type: (Callable, BeatTime, bool) -> None 10 | self._callback = callback 11 | self._beats_song_execution_time = beats_song_time 12 | self._execute_on_song_stop = execute_on_song_stop 13 | 14 | @property 15 | def should_execute(self): 16 | # type: () -> bool 17 | if Song.is_playing(): 18 | return ( 19 | BeatTime.from_song_beat_time(Song.current_beats_song_time()) 20 | >= self._beats_song_execution_time 21 | ) 22 | else: 23 | return self._execute_on_song_stop 24 | 25 | def execute(self): 26 | # type: () -> None 27 | self._callback() 28 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | 4 | [tool.mypy] 5 | python_version = 2.7 6 | exclude = ['venv', 'tests', 'vulture_whitelist.py'] 7 | pretty = false 8 | ignore_missing_imports = true 9 | follow_imports = 'skip' 10 | # cannot remove misc because of _Framework. union-attr possibly 11 | disable_error_code = ['misc','union-attr', 'has-type'] 12 | show_error_codes = true 13 | #warn_return_any = true # this is not possible because of the Live stub (and xml) doesn't specify types for properties 14 | warn_unreachable = true 15 | strict_equality = true 16 | disallow_any_explicit = false 17 | disallow_untyped_calls = true 18 | disallow_untyped_defs = true 19 | disallow_incomplete_defs = true 20 | disallow_untyped_decorators = true 21 | warn_redundant_casts=true 22 | warn_unused_ignores=true 23 | 24 | 25 | [tool.vulture] 26 | #exclude = ["venv/"] 27 | ignore_names = ["Optional"] 28 | make_whitelist = true 29 | min_confidence = 95 30 | sort_by_size = true 31 | verbose = false 32 | -------------------------------------------------------------------------------- /shared/sequence/SequenceTransition.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, List 2 | 3 | from protocol0.domain.shared.utils.list import find_if 4 | from protocol0.shared.AbstractEnum import AbstractEnum 5 | 6 | 7 | class SequenceStateEnum(AbstractEnum): 8 | UN_STARTED = "UN_STARTED" 9 | STARTED = "STARTED" 10 | TERMINATED = "TERMINATED" 11 | CANCELLED = "CANCELLED" 12 | ERRORED = "ERRORED" 13 | 14 | 15 | class SequenceTransition(object): 16 | def __init__(self, enum, allowed_transitions): 17 | # type: (SequenceStateEnum, List[SequenceTransition]) -> None 18 | self.enum = enum 19 | self._allowed_transitions = allowed_transitions 20 | 21 | def __repr__(self): 22 | # type: () -> str 23 | return self.enum.name 24 | 25 | def get_transition(self, new_state_enum): 26 | # type: (SequenceStateEnum) -> Optional["SequenceTransition"] 27 | return find_if(lambda s: s.enum == new_state_enum, self._allowed_transitions) 28 | -------------------------------------------------------------------------------- /domain/audit/stats/TrackStats.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from typing import Dict 4 | 5 | from protocol0.domain.lom.track.group_track.NormalGroupTrack import NormalGroupTrack 6 | from protocol0.domain.lom.track.simple_track.midi.special.UsamoTrack import UsamoTrack 7 | from protocol0.shared.Song import Song 8 | 9 | 10 | class TrackStats(object): 11 | def __init__(self): 12 | # type: () -> None 13 | excluded_track_classes = (NormalGroupTrack, UsamoTrack) 14 | tracks = [t for t in Song.abstract_tracks() if not isinstance(t, excluded_track_classes)] 15 | tracks_sorted = sorted(tracks, key=lambda track: track.load_time) 16 | self._slow_tracks = [t for t in list(reversed(tracks_sorted))[0:10] if t.load_time] 17 | 18 | def to_dict(self): 19 | # type: () -> Dict 20 | output = collections.OrderedDict() 21 | output["slow tracks"] = ["%s: %s ms" % (t.name, t.load_time) for t in self._slow_tracks] 22 | 23 | return output 24 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/SimpleTrackService.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.simple_track.SimpleTrackFlattenedEvent import \ 2 | SimpleTrackFlattenedEvent 3 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 4 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 5 | from protocol0.shared.Song import Song 6 | 7 | 8 | class SimpleTrackService(object): 9 | def __init__(self): 10 | # type: () -> None 11 | DomainEventBus.subscribe(SimpleTrackFlattenedEvent, self._on_simple_track_flattened_event) 12 | 13 | def _on_simple_track_flattened_event(self, _): 14 | # type: (SimpleTrackFlattenedEvent) -> None 15 | flattened_track = Song.selected_track(SimpleAudioTrack) 16 | 17 | for clip in flattened_track.clips: 18 | clip.looping = True 19 | clip.loop.start = 0 20 | clip.loop.end = clip.loop.end / 2 21 | 22 | flattened_track._needs_flattening = False 23 | -------------------------------------------------------------------------------- /tests/domain/track/test_instantiation.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.CommandBus import CommandBus 2 | from protocol0.application.command.ReloadScriptCommand import ReloadScriptCommand 3 | from protocol0.shared.Song import Song 4 | from protocol0.tests.domain.fixtures.group_track import add_external_synth_track 5 | from protocol0.tests.domain.fixtures.p0 import make_protocol0 6 | from protocol0.tests.domain.fixtures.simple_track import TrackType, add_track 7 | 8 | 9 | def test_instantiation_simple(): 10 | make_protocol0() 11 | add_track(track_type=TrackType.MIDI) 12 | add_track(track_type=TrackType.AUDIO) 13 | CommandBus.dispatch(ReloadScriptCommand()) 14 | assert len(list(Song.simple_tracks())) == 3 15 | 16 | 17 | def test_instantiation_external_synth_track(): 18 | make_protocol0() 19 | add_external_synth_track() 20 | CommandBus.dispatch(ReloadScriptCommand()) 21 | assert len(list(Song.simple_tracks())) == 4 22 | assert len(list(Song.external_synth_tracks())) == 1 23 | -------------------------------------------------------------------------------- /domain/lom/scene/SceneAppearance.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | from protocol0.domain.lom.scene.SceneName import SceneName 4 | from protocol0.domain.shared.scheduler.Scheduler import Scheduler 5 | 6 | 7 | class SceneAppearance(object): 8 | def __init__(self, live_scene, scene_name): 9 | # type: (Live.Scene.Scene, SceneName) -> None 10 | self._live_scene = live_scene 11 | self._scene_name = scene_name 12 | 13 | @property 14 | def name(self): 15 | # type: () -> str 16 | if not self._live_scene: 17 | return self._scene_name._cached_name 18 | else: 19 | return self._live_scene.name 20 | 21 | @name.setter 22 | def name(self, name): 23 | # type: (str) -> None 24 | if self._live_scene and name: 25 | self._scene_name.set_name(name) 26 | self._live_scene.name = str(name).strip() 27 | 28 | def refresh(self): 29 | # type: () -> None 30 | Scheduler.defer(self._scene_name.update) 31 | -------------------------------------------------------------------------------- /application/control_surface/group/ActionGroupFix.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.control_surface.ActionGroupInterface import ActionGroupInterface 2 | from protocol0.domain.audit.SetFixerService import SetFixerService 3 | from protocol0.domain.lom.validation.ValidatorService import ValidatorService 4 | from protocol0.shared.Song import Song 5 | 6 | 7 | class ActionGroupFix(ActionGroupInterface): 8 | CHANNEL = 5 9 | 10 | def configure(self): 11 | # type: () -> None 12 | # SET encoder 13 | self.add_encoder( 14 | identifier=1, 15 | name="fix set", 16 | on_press=self._container.get(SetFixerService).fix_set, 17 | ) 18 | 19 | # TRaCK encoder 20 | self.add_encoder( 21 | identifier=2, 22 | name="fix current track", 23 | filter_active_tracks=True, 24 | on_press=lambda: self._container.get(ValidatorService).fix_object( 25 | Song.current_track() 26 | ), 27 | ) 28 | -------------------------------------------------------------------------------- /domain/track_recorder/recording_bar_length/RecordingBarLengthScroller.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.shared.ValueScroller import ValueScroller 4 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 5 | from protocol0.domain.track_recorder.recording_bar_length.RecordingBarLengthEnum import ( 6 | RecordingBarLengthEnum, 7 | ) 8 | from protocol0.domain.track_recorder.recording_bar_length.SelectedRecordingBarLengthUpdatedEvent import ( 9 | SelectedRecordingBarLengthUpdatedEvent, 10 | ) 11 | from protocol0.shared.logging.StatusBar import StatusBar 12 | 13 | 14 | class RecordingBarLengthScroller(ValueScroller): 15 | def _get_values(self): 16 | # type: () -> List[RecordingBarLengthEnum] 17 | return list(RecordingBarLengthEnum) 18 | 19 | def _value_scrolled(self): 20 | # type: () -> None 21 | StatusBar.show_message("Fixed Recording : %s" % self.current_value) 22 | DomainEventBus.emit(SelectedRecordingBarLengthUpdatedEvent()) 23 | -------------------------------------------------------------------------------- /domain/track_recorder/RecordTypeEnum.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.track_recorder.count_in.CountInInterface import CountInInterface 2 | from protocol0.shared.AbstractEnum import AbstractEnum 3 | 4 | 5 | class RecordTypeEnum(AbstractEnum): 6 | MIDI = "Midi" 7 | MIDI_UNLIMITED = "Midi unlimited" 8 | AUDIO = "Audio" 9 | AUDIO_FULL = "Audio full" 10 | AUDIO_MULTI_SCENE = "Audio multi scene" 11 | 12 | @property 13 | def records_midi(self): 14 | # type: () -> bool 15 | return self in ( 16 | RecordTypeEnum.MIDI, 17 | RecordTypeEnum.MIDI_UNLIMITED, 18 | ) 19 | 20 | def get_count_in(self): 21 | # type: () -> CountInInterface 22 | from protocol0.domain.track_recorder.count_in.CountInOneBar import CountInOneBar 23 | from protocol0.domain.track_recorder.count_in.CountInShort import CountInShort 24 | 25 | if self.records_midi: 26 | return CountInOneBar() 27 | else: 28 | return CountInShort() 29 | 30 | -------------------------------------------------------------------------------- /domain/lom/clip/ClipAppearance.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | from protocol0.domain.lom.clip.ClipColorEnum import ClipColorEnum 4 | from protocol0.domain.lom.clip.ClipName import ClipName 5 | 6 | 7 | class ClipAppearance(object): 8 | def __init__(self, live_clip, clip_name, color): 9 | # type: (Live.Clip.Clip, ClipName, int) -> None 10 | self._live_clip = live_clip 11 | self._clip_name = clip_name 12 | self._color = color 13 | 14 | @property 15 | def color(self): 16 | # type: () -> int 17 | return self._live_clip.color_index if self._live_clip else 0 18 | 19 | @color.setter 20 | def color(self, color_index): 21 | # type: (int) -> None 22 | if self._live_clip: 23 | self._live_clip.color_index = color_index # noqa 24 | 25 | def refresh(self): 26 | # type: () -> None 27 | self._clip_name._name_listener(force=True) 28 | if self.color != ClipColorEnum.AUDIO_UN_QUANTIZED.value: 29 | self.color = self._color 30 | -------------------------------------------------------------------------------- /domain/shared/utils/debug.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from collections import namedtuple 3 | 4 | from typing import Optional, Any 5 | 6 | from protocol0.shared.Config import Config 7 | 8 | 9 | def get_frame_info(frame_count=1): 10 | # type: (int) -> Optional[Any] 11 | call_frame = inspect.currentframe() 12 | for _ in range(frame_count): 13 | next_frame = call_frame.f_back 14 | if not next_frame: 15 | break 16 | call_frame = next_frame 17 | try: 18 | (filename, line, method_name, _, _) = inspect.getframeinfo(call_frame) 19 | except IndexError: 20 | return None 21 | filename = filename.replace(Config.PROJECT_ROOT + "\\", "").replace( 22 | Config.REMOTE_SCRIPTS_ROOT + "\\", "" 23 | ) 24 | class_name = filename.replace(".py", "").split("\\")[-1] 25 | 26 | FrameInfo = namedtuple("FrameInfo", ["filename", "class_name", "line", "method_name"]) 27 | return FrameInfo(filename=filename, class_name=class_name, line=line, method_name=method_name) 28 | -------------------------------------------------------------------------------- /domain/lom/device_parameter/DeviceParameterValue.py: -------------------------------------------------------------------------------- 1 | from typing import Any, TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: # for python 3 import 4 | from protocol0.domain.lom.device.Device import Device 5 | from protocol0.domain.lom.device_parameter.DeviceParameterEnum import DeviceParameterEnum 6 | from protocol0.domain.shared.utils.utils import compare_values 7 | 8 | 9 | class DeviceParameterValue(object): 10 | def __init__(self, device_parameter_enum, value): 11 | # type: (DeviceParameterEnum, Any) -> None 12 | self._device_parameter_enum = device_parameter_enum 13 | self._value = value 14 | 15 | def matches(self, device): 16 | # type: (Device) -> bool 17 | device_parameter = device.get_parameter_by_name( 18 | device_parameter_name=self._device_parameter_enum 19 | ) 20 | return ( 21 | compare_values(device_parameter.value, self._value) 22 | and device_parameter.is_enabled 23 | and not device_parameter.is_automated 24 | ) 25 | -------------------------------------------------------------------------------- /domain/lom/clip/ClipPlayingPosition.py: -------------------------------------------------------------------------------- 1 | import Live 2 | 3 | from protocol0.domain.lom.clip.ClipLoop import ClipLoop 4 | from protocol0.shared.Song import Song 5 | 6 | 7 | class ClipPlayingPosition(object): 8 | def __init__(self, live_clip, clip_loop): 9 | # type: (Live.Clip.Clip, ClipLoop) -> None 10 | self._live_clip = live_clip 11 | self._clip_loop = clip_loop 12 | 13 | def __repr__(self): 14 | # type: () -> str 15 | return "position: %s / %s. beats left: %s" % ( 16 | self.position, 17 | self._clip_loop.length, 18 | self.beats_left 19 | ) 20 | 21 | @property 22 | def position(self): 23 | # type: () -> float 24 | return self._live_clip.playing_position - self._clip_loop.start_marker 25 | 26 | @property 27 | def beats_left(self): 28 | # type: () -> int 29 | bar_offset = int(self._clip_loop.bar_offset) * Song.signature_numerator() 30 | 31 | return int(self._clip_loop.length - self.position + bar_offset) 32 | -------------------------------------------------------------------------------- /domain/lom/validation/object_validators/SceneValidator.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.lom.scene.Scene import Scene 4 | from protocol0.domain.lom.validation.ValidatorInterface import ValidatorInterface 5 | from protocol0.domain.lom.validation.sub_validators.AggregateValidator import AggregateValidator 6 | from protocol0.domain.lom.validation.sub_validators.CallbackValidator import CallbackValidator 7 | 8 | 9 | class SceneValidator(AggregateValidator): 10 | def __init__(self, scene): 11 | # type: (Scene) -> None 12 | validators = [] # type: List[ValidatorInterface] 13 | if scene.bar_length != 0 and scene.bar_length <= 32: 14 | validators.append( 15 | CallbackValidator( 16 | scene, 17 | lambda s: s.bar_length < 2 or s.bar_length % 2 == 0, 18 | None, 19 | "%s bar_length is not a multiple of 2" % scene, 20 | ), 21 | ) 22 | 23 | super(SceneValidator, self).__init__(validators) 24 | -------------------------------------------------------------------------------- /domain/lom/validation/object_validators/SimpleAudioTrackValidator.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 4 | from protocol0.domain.lom.validation.ValidatorInterface import ValidatorInterface 5 | from protocol0.domain.lom.validation.sub_validators.AggregateValidator import AggregateValidator 6 | from protocol0.domain.lom.validation.sub_validators.CallbackValidator import CallbackValidator 7 | 8 | 9 | class SimpleAudioTrackValidator(AggregateValidator): 10 | def __init__(self, track, validators=None): 11 | # type: (SimpleAudioTrack, Optional[List[ValidatorInterface]]) -> None 12 | self._track = track 13 | 14 | if validators is None: 15 | validators = [] 16 | 17 | validators += [ 18 | CallbackValidator( 19 | track, lambda t: t.arm_state.is_armable, None, "%s should be armable" % track 20 | ), 21 | ] 22 | 23 | super(SimpleAudioTrackValidator, self).__init__(validators) 24 | -------------------------------------------------------------------------------- /domain/lom/loop/LoopableInterface.py: -------------------------------------------------------------------------------- 1 | class LoopableInterface(object): 2 | @property 3 | def looping(self): 4 | # type: () -> bool 5 | raise NotImplementedError 6 | 7 | @looping.setter 8 | def looping(self, loop): 9 | # type: (bool) -> None 10 | raise NotImplementedError 11 | 12 | @property 13 | def start(self): 14 | # type: () -> float 15 | raise NotImplementedError 16 | 17 | @start.setter 18 | def start(self, start): 19 | # type: (float) -> None 20 | raise NotImplementedError 21 | 22 | @property 23 | def end(self): 24 | # type: () -> float 25 | raise NotImplementedError 26 | 27 | @end.setter 28 | def end(self, end): 29 | # type: (float) -> None 30 | raise NotImplementedError 31 | 32 | @property 33 | def length(self): 34 | # type: () -> float 35 | raise NotImplementedError 36 | 37 | @length.setter 38 | def length(self, length): 39 | # type: (float) -> None 40 | raise NotImplementedError 41 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/matching_track/MatchingTrackSoloState.py: -------------------------------------------------------------------------------- 1 | import Live 2 | from _Framework.CompoundElement import subject_slot_group 3 | from _Framework.SubjectSlot import SlotManager 4 | 5 | from protocol0.domain.lom.track.simple_track.SimpleTrack import SimpleTrack 6 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 7 | from protocol0.domain.shared.utils.timing import defer 8 | 9 | 10 | class MatchingTrackSoloState(SlotManager): 11 | def __init__(self, base_track, audio_track): 12 | # type: (SimpleTrack, SimpleAudioTrack) -> None 13 | super(MatchingTrackSoloState, self).__init__() 14 | self._base_track = base_track 15 | self._audio_track = audio_track 16 | self._solo_listener.replace_subjects([self._base_track._track, self._audio_track._track]) 17 | 18 | @subject_slot_group("solo") 19 | @defer 20 | def _solo_listener(self, track): 21 | # type: (Live.Track.Track) -> None 22 | self._base_track.solo = track.solo 23 | self._audio_track.solo = track.solo 24 | -------------------------------------------------------------------------------- /domain/lom/instrument/preset/InstrumentPreset.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from typing import Any, Optional 4 | 5 | from protocol0.domain.shared.utils.string import smart_string 6 | 7 | 8 | class InstrumentPreset(object): 9 | def __init__(self, index, name, category=None): 10 | # type: (int, Optional[basestring], Optional[str]) -> None 11 | self.index = index 12 | name = smart_string(name) if name else None 13 | self.original_name = name 14 | self.name = self._format_name(name) 15 | self.category = category.lower() if category else "" 16 | 17 | def __repr__(self, **k): 18 | # type: (Any) -> str 19 | name = "%s (%s)" % (self.name, self.index + 1) 20 | if self.category: 21 | name += "(%s)" % self.category 22 | return name 23 | 24 | def _format_name(self, name): 25 | # type: (Optional[str]) -> str 26 | if name is None: 27 | return "empty" 28 | 29 | base_preset_name = re.sub("\\.[a-z\d]{2,4}", "", name) # remove file extension 30 | return str(base_preset_name) 31 | -------------------------------------------------------------------------------- /tests/domain/shared/test_live_object_mapping.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.shared.LiveObjectMapping import LiveObjectMapping 2 | 3 | 4 | def test_build(): 5 | class WrappedObject(object): 6 | def __init__(self, _): 7 | pass 8 | 9 | class LiveObject(object): 10 | def __init__(self): 11 | self._live_ptr = id(self) 12 | 13 | def factory(obj): 14 | return WrappedObject(obj) 15 | 16 | mapping = LiveObjectMapping(factory) 17 | 18 | live_objects = [LiveObject(), LiveObject()] 19 | mapping.build(live_objects) 20 | assert len(mapping.all) == 2 21 | assert len(mapping.added) == 2 22 | assert len(mapping.removed) == 0 23 | 24 | live_objects.append(LiveObject()) 25 | mapping.build(live_objects) 26 | 27 | assert len(mapping.all) == 3 28 | assert len(mapping.added) == 1 29 | assert len(mapping.removed) == 0 30 | 31 | live_objects = live_objects[:2] 32 | mapping.build(live_objects) 33 | 34 | assert len(mapping.all) == 2 35 | assert len(mapping.added) == 0 36 | assert len(mapping.removed) == 1 37 | -------------------------------------------------------------------------------- /application/control_surface/group/ActionGroupSet.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from protocol0.application.ScriptResetActivatedEvent import ScriptResetActivatedEvent 4 | from protocol0.application.control_surface.ActionGroupInterface import ActionGroupInterface 5 | from protocol0.domain.lom.set.SessionToArrangementService import SessionToArrangementService 6 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 7 | 8 | 9 | class ActionGroupSet(ActionGroupInterface): 10 | CHANNEL = 3 11 | 12 | def configure(self): 13 | # type: () -> None 14 | # INIT song encoder 15 | self.add_encoder( 16 | identifier=4, 17 | name="(re) initialize the script", 18 | on_press=partial(DomainEventBus.emit, ScriptResetActivatedEvent()), 19 | ) 20 | 21 | # Session2ARrangement encoder 22 | self.add_encoder( 23 | identifier=16, 24 | name="bounce session to arrangement", 25 | on_press=self._container.get(SessionToArrangementService).bounce_session_to_arrangement, 26 | ) 27 | -------------------------------------------------------------------------------- /domain/shared/utils/concurrency.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from collections import defaultdict 3 | from functools import wraps, partial 4 | 5 | from typing import Any, Optional 6 | 7 | from protocol0.shared.types import Func 8 | 9 | 10 | def lock(func): 11 | # type: (Func) -> Func 12 | from protocol0.shared.sequence.Sequence import Sequence 13 | 14 | @wraps(func) 15 | def decorate(*a, **k): 16 | # type: (Any, Any) -> Optional[Sequence] 17 | object_source = a[0] if inspect.ismethod(func) else decorate 18 | if decorate.lock[object_source]: # type: ignore[attr-defined] 19 | return None 20 | 21 | decorate.lock[object_source] = True # type: ignore[attr-defined] 22 | 23 | def unlock(): 24 | # type: () -> None 25 | decorate.lock[object_source] = False # type: ignore[attr-defined] 26 | 27 | seq = Sequence() 28 | seq.add(partial(func, *a, **k)) 29 | seq.add(unlock) 30 | return seq.done() 31 | 32 | decorate.lock = defaultdict(int) # type: ignore[attr-defined] 33 | 34 | return decorate 35 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/record_audio/PostRecordAudioFull.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.ext_track.ExternalSynthTrack import \ 2 | ExternalSynthTrack 3 | from protocol0.domain.track_recorder.RecordProcessorInterface import RecordProcessorInterface 4 | from protocol0.domain.track_recorder.config.RecordConfig import RecordConfig 5 | from protocol0.domain.track_recorder.external_synth.record_audio.PostRecordAudio import \ 6 | PostRecordAudio 7 | from protocol0.shared.Song import Song 8 | 9 | 10 | class PostRecordAudioFull(RecordProcessorInterface): 11 | def process(self, track, config): 12 | # type: (ExternalSynthTrack, RecordConfig) -> None 13 | PostRecordAudio().process(track, config) 14 | 15 | audio_clip = track.audio_track.clip_slots[config.scene_index].clip 16 | audio_clip.looping = False 17 | audio_clip.loop.end = config.bar_length * Song.signature_numerator() 18 | audio_clip.loop.start_marker = 0 19 | audio_clip.loop.start = (config.bar_length / 2) * Song.signature_numerator() 20 | audio_clip.looping = True 21 | -------------------------------------------------------------------------------- /domain/audit/stats/SongStats.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from typing import Dict, List 4 | 5 | from protocol0.domain.audit.stats.DeviceStats import DevicesStats 6 | from protocol0.domain.audit.stats.SampleStats import SampleStats 7 | from protocol0.domain.audit.stats.SceneStats import SceneStats 8 | from protocol0.domain.audit.stats.Stats import Stats 9 | from protocol0.domain.audit.stats.TrackStats import TrackStats 10 | 11 | 12 | class SongStats(object): 13 | def __init__(self): 14 | # type: () -> None 15 | self._stats = [ 16 | SceneStats(), 17 | TrackStats(), 18 | # ClipStats(), 19 | SampleStats(), 20 | DevicesStats(), 21 | ] # type: List[Stats] 22 | 23 | def to_dict(self): 24 | # type: () -> Dict 25 | output = collections.OrderedDict() 26 | for stat in self._stats: 27 | title = stat.__class__.__name__.replace("Stats", "").lower() 28 | if not title.endswith("s"): 29 | title += "s" 30 | output[title] = stat.to_dict() 31 | 32 | return output 33 | -------------------------------------------------------------------------------- /domain/lom/instrument/instrument/InstrumentSimpler.py: -------------------------------------------------------------------------------- 1 | from typing import cast, Any 2 | 3 | from protocol0.domain.lom.device.SimplerDevice import SimplerDevice 4 | from protocol0.domain.lom.instrument.InstrumentColorEnum import InstrumentColorEnum 5 | from protocol0.domain.lom.instrument.InstrumentInterface import InstrumentInterface 6 | from protocol0.domain.lom.instrument.preset.PresetDisplayOptionEnum import PresetDisplayOptionEnum 7 | from protocol0.domain.lom.instrument.preset.preset_changer.SamplePresetChanger import ( 8 | SamplePresetChanger, 9 | ) 10 | from protocol0.shared.Config import Config 11 | 12 | 13 | class InstrumentSimpler(InstrumentInterface): 14 | NAME = "Simpler" 15 | TRACK_COLOR = InstrumentColorEnum.SIMPLER 16 | PRESETS_PATH = Config.SAMPLE_DIRECTORY 17 | PRESET_DISPLAY_OPTION = PresetDisplayOptionEnum.CATEGORY 18 | CAN_BE_SHOWN = False 19 | PRESET_CHANGER = SamplePresetChanger 20 | 21 | def __init__(self, *a, **k): 22 | # type: (Any, Any) -> None 23 | super(InstrumentSimpler, self).__init__(*a, **k) 24 | self.device = cast(SimplerDevice, self.device) 25 | -------------------------------------------------------------------------------- /domain/track_recorder/external_synth/record_audio/PreRecordAudio.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.ext_track.ExternalSynthTrack import \ 2 | ExternalSynthTrack 3 | from protocol0.domain.shared.backend.Backend import Backend 4 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 5 | from protocol0.domain.track_recorder.config.RecordConfig import RecordConfig 6 | from protocol0.domain.track_recorder.RecordProcessorInterface import RecordProcessorInterface 7 | from protocol0.domain.track_recorder.external_synth.ExtAudioRecordingStartedEvent import \ 8 | ExtAudioRecordingStartedEvent 9 | 10 | 11 | class PreRecordAudio(RecordProcessorInterface): 12 | def process(self, track, config): 13 | # type: (ExternalSynthTrack, RecordConfig) -> None 14 | track.monitoring_state.monitor_midi() 15 | midi_clip = track.midi_track.clip_slots[config.scene_index].clip 16 | if midi_clip.loop.start != 0: 17 | Backend.client().show_warning("Cropping midi clip") 18 | midi_clip.crop() 19 | DomainEventBus.emit(ExtAudioRecordingStartedEvent(track)) 20 | -------------------------------------------------------------------------------- /domain/lom/track/group_track/NormalGroupMatchingTrack.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from protocol0 import EmptyModule 4 | from protocol0.domain.lom.track.group_track.matching_track.MatchingTrackClipColorManager import \ 5 | MatchingTrackClipColorManager 6 | from protocol0.domain.lom.track.group_track.matching_track.MatchingTrackInterface import ( 7 | MatchingTrackInterface, 8 | ) 9 | from protocol0.domain.shared.backend.Backend import Backend 10 | from protocol0.shared.sequence.Sequence import Sequence 11 | 12 | 13 | class NormalGroupMatchingTrack(MatchingTrackInterface): 14 | @property 15 | def clip_color_manager(self): 16 | # type: () -> MatchingTrackClipColorManager 17 | return EmptyModule() # type: ignore 18 | 19 | def bounce(self): 20 | # type: () -> Sequence 21 | seq = Sequence() 22 | 23 | self._audio_track.input_routing.track = self._base_track 24 | self._audio_track.arm_state.arm() 25 | seq.add(self._base_track.save) 26 | seq.add(partial(Backend.client().show_success, "Please record clips")) 27 | 28 | return seq.done() 29 | -------------------------------------------------------------------------------- /shared/Config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os.path import dirname, realpath 3 | 4 | import Live 5 | 6 | from protocol0.domain.track_recorder.recording_bar_length.RecordingBarLengthEnum import \ 7 | RecordingBarLengthEnum 8 | from protocol0.shared.logging.LogLevelEnum import LogLevelEnum 9 | 10 | 11 | class Config(object): 12 | # DIRECTORIES 13 | PROJECT_ROOT = dirname(dirname(realpath(__file__))) 14 | REMOTE_SCRIPTS_ROOT = dirname(PROJECT_ROOT) 15 | SAMPLE_DIRECTORY = str(os.getenv("SAMPLE_DIRECTORY")) 16 | 17 | # SERVICES 18 | SENTRY_DSN = os.getenv("SENTRY_DSN") 19 | 20 | # MISC 21 | DEFAULT_RECORDING_BAR_LENGTH = RecordingBarLengthEnum.ONE 22 | # DEFAULT_RECORDING_BAR_LENGTH = RecordingBarLengthEnum.EIGHT 23 | 24 | EXPERIMENTAL_FEATURES = False 25 | 26 | LOG_LEVEL = LogLevelEnum.DEV 27 | 28 | TRACK_VOLUME_MONITORING = False 29 | 30 | DEFAULT_WARP_MODE = Live.Clip.WarpMode.beats 31 | 32 | CLIP_MAX_LENGTH = 63072000 33 | 34 | # VOLUME CONSTANTS 35 | ZERO_VOLUME = 0.850000023842 36 | CLIPPING_TRACK_VOLUME = 0.91 37 | 38 | DUMMY_CLIP_NAME = "dummy clip" 39 | -------------------------------------------------------------------------------- /domain/audit/stats/SceneStats.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from typing import Dict, Any 4 | 5 | from protocol0.domain.shared.utils.utils import get_minutes_legend 6 | from protocol0.shared.Song import Song 7 | 8 | 9 | class SceneStats(object): 10 | def __init__(self): 11 | # type: () -> None 12 | beat_duration = float(60) / Song.tempo() 13 | 14 | current_scene = Song.scenes()[0] 15 | scenes = [current_scene] 16 | 17 | while current_scene.next_scene and current_scene.next_scene != current_scene: 18 | current_scene = current_scene.next_scene 19 | scenes.append(current_scene) 20 | 21 | self.count = len(scenes) 22 | self.bar_length = sum([scene.bar_length for scene in scenes]) 23 | self.total_duration = sum([scene.length for scene in scenes]) * beat_duration 24 | 25 | def to_dict(self): 26 | # type: () -> Dict 27 | output = collections.OrderedDict() # type: Dict[str, Any] 28 | output["count"] = self.count 29 | output["total duration"] = get_minutes_legend(self.total_duration) 30 | 31 | return output 32 | -------------------------------------------------------------------------------- /domain/lom/track/TrackService.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.SelectedTrackChangedEvent import SelectedTrackChangedEvent 2 | from protocol0.domain.shared.ApplicationView import ApplicationView 3 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 4 | from protocol0.shared.Song import Song 5 | 6 | 7 | class TrackService(object): 8 | def __init__(self): 9 | # type: () -> None 10 | super(TrackService, self).__init__() 11 | DomainEventBus.subscribe(SelectedTrackChangedEvent, self._on_selected_track_changed_event) 12 | 13 | def _on_selected_track_changed_event(self, _): 14 | # type: (SelectedTrackChangedEvent) -> None 15 | pass 16 | # if Song.selected_track().is_foldable: 17 | # try: 18 | # ApplicationView.show_device() 19 | # except RuntimeError: # can happen on startup 20 | # pass 21 | 22 | def go_to_group_track(self): 23 | # type: () -> None 24 | if Song.selected_track().group_track is not None: 25 | Song.selected_track().group_track.select() 26 | ApplicationView.focus_session() 27 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/SimpleTrackClips.py: -------------------------------------------------------------------------------- 1 | from typing import List, TYPE_CHECKING, Iterator 2 | 3 | from protocol0.domain.lom.clip.Clip import Clip 4 | from protocol0.domain.lom.device.SimpleTrackDevices import SimpleTrackDevices 5 | from protocol0.domain.lom.track.simple_track.SimpleTrackClipColorManager import \ 6 | SimpleTrackClipColorManager 7 | 8 | if TYPE_CHECKING: 9 | from protocol0.domain.lom.track.simple_track.SimpleTrackClipSlots import SimpleTrackClipSlots 10 | 11 | 12 | class SimpleTrackClips(object): 13 | def __init__(self, clip_slots, track_devices, track_color): 14 | # type: (SimpleTrackClipSlots, SimpleTrackDevices, int) -> None 15 | self._clip_slots = clip_slots 16 | self.clip_color_manager = SimpleTrackClipColorManager(self, track_devices, track_color) 17 | 18 | def __iter__(self): 19 | # type: () -> Iterator[Clip] 20 | return iter(self._clips) 21 | 22 | @property 23 | def _clips(self): 24 | # type: () -> List[Clip] 25 | return [ 26 | clip_slot.clip for clip_slot in list(self._clip_slots) if clip_slot.has_clip and clip_slot.clip 27 | ] 28 | -------------------------------------------------------------------------------- /domain/shared/ValueToggler.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Generic 2 | 3 | from protocol0.shared.types import T 4 | 5 | 6 | class ValueToggler(Generic[T]): 7 | def __init__(self, value=None): 8 | # type: (Optional[T]) -> None 9 | self._value = value 10 | 11 | @property 12 | def value(self): 13 | # type: () -> Optional[T] 14 | return self._value 15 | 16 | def toggle(self): 17 | # type: () -> None 18 | old_value = self._value 19 | new_value = self._get_value() 20 | if old_value == new_value: 21 | self._value = None 22 | else: 23 | self._value = new_value 24 | self._value_set(new_value) 25 | 26 | if old_value: 27 | self._value_unset(old_value) 28 | 29 | def reset(self): 30 | # type: () -> None 31 | self._value = None 32 | 33 | def _value_set(self, value): 34 | # type: (T) -> None 35 | pass 36 | 37 | def _value_unset(self, value): 38 | # type: (T) -> None 39 | pass 40 | 41 | def _get_value(self): 42 | # type: () -> T 43 | raise NotImplementedError 44 | -------------------------------------------------------------------------------- /infra/scheduler/TickSchedulerEvent.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from protocol0.domain.shared.scheduler.TickSchedulerEventInterface import ( 4 | TickSchedulerEventInterface, 5 | ) 6 | from protocol0.domain.shared.utils.func import get_callable_repr 7 | 8 | 9 | class TickSchedulerEvent(TickSchedulerEventInterface): 10 | def __init__(self, callback, tick_count): 11 | # type: (Callable, int) -> None 12 | self.callback = callback 13 | self._ticks_left = tick_count 14 | self._cancelled = False 15 | 16 | def __repr__(self): 17 | # type: () -> str 18 | return get_callable_repr(self.callback) 19 | 20 | @property 21 | def should_execute(self): 22 | # type: () -> bool 23 | return self._ticks_left == 0 24 | 25 | def decrement_timeout(self): 26 | # type: () -> None 27 | assert self._ticks_left > 0, "0 ticks left" 28 | self._ticks_left -= 1 29 | 30 | def execute(self): 31 | # type: () -> None 32 | if not self._cancelled: 33 | self.callback() 34 | 35 | def cancel(self): 36 | # type: () -> None 37 | self._cancelled = True 38 | -------------------------------------------------------------------------------- /tests/domain/fixtures/clip.py: -------------------------------------------------------------------------------- 1 | from _Framework.SubjectSlot import Subject 2 | 3 | from protocol0.tests.domain.fixtures.clip_view import AbletonClipView 4 | 5 | 6 | class AbletonClip(Subject): 7 | __subject_events__ = ( 8 | "playing_status", 9 | "loop_start", 10 | "loop_end", 11 | "looping", 12 | "start_marker", 13 | "end_marker", 14 | "name", 15 | "warping", 16 | "muted", 17 | ) 18 | 19 | def __init__(self): 20 | self.name = "test" 21 | self.view = AbletonClipView() 22 | self.is_recording = False 23 | self.length = 4 24 | self.color_index = 0 25 | self.looping = True 26 | self.loop_start = 0 27 | self.loop_end = 4 28 | self.muted = False 29 | self.playing_position = 0 30 | self.start_marker = 0 31 | self.is_audio_clip = False 32 | self.is_playing = False 33 | 34 | # noinspection PyUnusedLocal 35 | def get_notes_extended(self, *a, **k): 36 | return () 37 | 38 | def select_all_notes(self): 39 | pass 40 | 41 | def replace_selected_notes(self, _): 42 | pass 43 | -------------------------------------------------------------------------------- /tests/domain/validation/test_callback_validator.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.validation.sub_validators.CallbackValidator import CallbackValidator 2 | 3 | 4 | def test_callback_validator(): 5 | class Test(object): 6 | def __init__(self): 7 | self._test = "test" 8 | 9 | @property 10 | def test(self): 11 | return self._test 12 | 13 | @test.setter 14 | def test(self, test): 15 | self._test = test 16 | 17 | obj = Test() 18 | 19 | validator = CallbackValidator( 20 | obj, lambda t: t.test == "test", lambda t: setattr(t, "test", "test") 21 | ) 22 | assert validator.is_valid() 23 | assert validator.get_error_message() is None 24 | 25 | validator = CallbackValidator(obj, lambda t: t.test == "test") 26 | assert validator.is_valid() 27 | assert validator.get_error_message() is None 28 | 29 | validator = CallbackValidator( 30 | obj, lambda t: t.test == "toto", lambda t: setattr(t, "test", "toto") 31 | ) 32 | assert not validator.is_valid() 33 | assert validator.get_error_message() is not None 34 | validator.fix() 35 | assert validator.is_valid() 36 | -------------------------------------------------------------------------------- /domain/lom/validation/sub_validators/AggregateValidator.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, List 2 | 3 | from protocol0.shared.sequence.Sequence import Sequence 4 | from protocol0.domain.lom.validation.ValidatorInterface import ValidatorInterface 5 | 6 | 7 | class AggregateValidator(ValidatorInterface): 8 | def __init__(self, validators): 9 | # type: (List[ValidatorInterface]) -> None 10 | self._validators = validators 11 | 12 | def get_error_message(self): 13 | # type: () -> Optional[str] 14 | error_messages = list(filter( 15 | None, [validator.get_error_message() for validator in self._validators] 16 | )) 17 | if len(error_messages) == 0: 18 | return None 19 | else: 20 | return "\n".join(error_messages) 21 | 22 | def is_valid(self): 23 | # type: () -> bool 24 | return all(validator.is_valid() for validator in self._validators) 25 | 26 | def fix(self): 27 | # type: () -> Sequence 28 | seq = Sequence() 29 | for validator in self._validators: 30 | if not validator.is_valid(): 31 | seq.add(validator.fix) 32 | return seq.done() 33 | -------------------------------------------------------------------------------- /application/control_surface/group/legacy/ActionGroupPreset.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from protocol0.application.control_surface.ActionGroupInterface import ActionGroupInterface 4 | from protocol0.domain.lom.instrument.preset.InstrumentPresetScrollerService import ( 5 | InstrumentPresetScrollerService, 6 | ) 7 | from protocol0.shared.Song import Song 8 | 9 | 10 | class ActionGroupPreset(ActionGroupInterface): 11 | def configure(self): 12 | # type: () -> None 13 | # PREset encoder 14 | self.add_encoder( 15 | identifier=1, 16 | name="scroll presets", 17 | filter_active_tracks=True, 18 | on_scroll=lambda: partial( 19 | self._container.get(InstrumentPresetScrollerService).scroll_presets_or_samples, 20 | Song.current_track(), 21 | ), 22 | ) 23 | 24 | # CATegory encoder 25 | self.add_encoder( 26 | identifier=2, 27 | name="scroll preset categories", 28 | on_scroll=lambda: partial( 29 | self._container.get(InstrumentPresetScrollerService).scroll_preset_categories, 30 | Song.current_track(), 31 | ), 32 | ) 33 | -------------------------------------------------------------------------------- /domain/lom/validation/sub_validators/CallbackValidator.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional, Callable 2 | 3 | from protocol0.shared.sequence.Sequence import Sequence 4 | from protocol0.domain.lom.validation.ValidatorInterface import ValidatorInterface 5 | 6 | 7 | class CallbackValidator(ValidatorInterface): 8 | def __init__(self, obj, callback_validator, callback_fixer=None, error_message=None): 9 | # type: (Any, Callable, Optional[Callable], Optional[str]) -> None 10 | self._obj = obj 11 | self._callback_validator = callback_validator 12 | self._callback_fixer = callback_fixer 13 | self._error_message = error_message 14 | 15 | def get_error_message(self): 16 | # type: () -> Optional[str] 17 | if self.is_valid(): 18 | return None 19 | else: 20 | return self._error_message or "callback failed for %s" % self._obj 21 | 22 | def is_valid(self): 23 | # type: () -> bool 24 | return self._callback_validator(self._obj) 25 | 26 | def fix(self): 27 | # type: () -> Optional[Sequence] 28 | if self._callback_fixer is not None: 29 | return self._callback_fixer(self._obj) 30 | else: 31 | return None 32 | -------------------------------------------------------------------------------- /tests/domain/validation/test_property_validator.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from protocol0.domain.lom.validation.sub_validators.PropertyValueValidator import ( 4 | PropertyValueValidator, 5 | ) 6 | from protocol0.domain.shared.errors.Protocol0Error import Protocol0Error 7 | 8 | 9 | def test_property_validator(): 10 | class Test(object): 11 | def __init__(self): 12 | self._test = "test" 13 | 14 | @property 15 | def test(self): 16 | return self._test 17 | 18 | @test.setter 19 | def test(self, test): 20 | self._test = test 21 | 22 | obj = Test() 23 | validator = PropertyValueValidator(obj, "test", "test") 24 | assert validator.is_valid() 25 | assert validator.get_error_message() is None 26 | 27 | validator = PropertyValueValidator(obj, "test", "toto") 28 | assert not validator.is_valid() 29 | assert validator.get_error_message() is not None 30 | validator.fix() 31 | assert validator.is_valid() 32 | 33 | validator = PropertyValueValidator(obj, "toto", "toto") 34 | assert not validator.is_valid() 35 | assert validator.get_error_message() is not None 36 | with pytest.raises(Protocol0Error): 37 | validator.fix() 38 | -------------------------------------------------------------------------------- /domain/lom/song/components/QuantizationComponent.py: -------------------------------------------------------------------------------- 1 | import Live 2 | from _Framework.SubjectSlot import SlotManager 3 | 4 | from protocol0.domain.lom.song.components.TempoComponent import TempoComponent 5 | 6 | 7 | class QuantizationComponent(SlotManager): 8 | def __init__(self, song, tempo_component): 9 | # type: (Live.Song.Song, TempoComponent) -> None 10 | super(QuantizationComponent, self).__init__() 11 | self._song = song 12 | self._tempo_component = tempo_component 13 | 14 | @property 15 | def midi_recording_quantization(self): 16 | # type: () -> int 17 | return self._song.midi_recording_quantization 18 | 19 | @midi_recording_quantization.setter 20 | def midi_recording_quantization(self, midi_recording_quantization): 21 | # type: (int) -> None 22 | if self._song: 23 | self._song.midi_recording_quantization = midi_recording_quantization 24 | 25 | @property 26 | def clip_trigger_quantization(self): 27 | # type: () -> int 28 | return self._song.clip_trigger_quantization 29 | 30 | @clip_trigger_quantization.setter 31 | def clip_trigger_quantization(self, clip_trigger_quantization): 32 | # type: (int) -> None 33 | self._song.clip_trigger_quantization = clip_trigger_quantization 34 | -------------------------------------------------------------------------------- /domain/lom/device/DrumRackDevice.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.lom.device.DrumPad import DrumPad 4 | from protocol0.domain.lom.device.RackDevice import RackDevice 5 | from protocol0.shared.logging.Logger import Logger 6 | 7 | 8 | class DrumRackDevice(RackDevice): 9 | def pad_names_equal(self, names): 10 | # type: (List[str]) -> bool 11 | pad_names = [drum_pad.name for drum_pad in self.filled_drum_pads] 12 | if names != pad_names: 13 | Logger.info("difference: %s" % list(set(names) - set(pad_names))) 14 | return names == pad_names 15 | 16 | @property 17 | def drum_pads(self): 18 | # type: () -> List[DrumPad] 19 | return [DrumPad(drum_pad) for drum_pad in self._device.drum_pads if drum_pad] 20 | 21 | @property 22 | def filled_drum_pads(self): 23 | # type: () -> List[DrumPad] 24 | return [drum_pad for drum_pad in self.drum_pads if not drum_pad.is_empty] 25 | 26 | @property 27 | def selected_drum_pad(self): 28 | # type: () -> DrumPad 29 | return DrumPad(self._device.view.selected_drum_pad) 30 | 31 | @selected_drum_pad.setter 32 | def selected_drum_pad(self, drum_pad): 33 | # type: (DrumPad) -> None 34 | self._device.view.selected_drum_pad = drum_pad._drum_pad 35 | -------------------------------------------------------------------------------- /domain/lom/scene/LoopingSceneToggler.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | 3 | from protocol0.domain.lom.scene.PlayingSceneChangedEvent import PlayingSceneChangedEvent 4 | from protocol0.domain.lom.scene.Scene import Scene 5 | from protocol0.domain.shared.ValueToggler import ValueToggler 6 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 7 | from protocol0.shared.Song import Song 8 | 9 | 10 | class LoopingSceneToggler(ValueToggler): 11 | def __init__(self): 12 | # type: () -> None 13 | super(LoopingSceneToggler, self).__init__() 14 | DomainEventBus.subscribe(PlayingSceneChangedEvent, self._on_playing_scene_changed_event) 15 | 16 | def _on_playing_scene_changed_event(self, _): 17 | # type: (PlayingSceneChangedEvent) -> None 18 | if self.value and self.value != Song.playing_scene(): 19 | self.reset() 20 | 21 | def _get_value(self): 22 | # type: () -> Scene 23 | if Song.is_playing(): 24 | return cast(Scene, Song.playing_scene()) 25 | else: 26 | return Song.selected_scene() 27 | 28 | def _value_set(self, scene): 29 | # type: (Scene) -> None 30 | scene.scene_name.update() 31 | 32 | def _value_unset(self, scene): 33 | # type: (Scene) -> None 34 | scene.scene_name.update() 35 | -------------------------------------------------------------------------------- /application/error/SentryService.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | 3 | from protocol0.shared.Config import Config 4 | from protocol0.shared.logging.Logger import Logger 5 | 6 | 7 | class SentryService(object): 8 | def __init__(self): 9 | # type: () -> None 10 | self.activated = False 11 | 12 | def activate(self): 13 | # type: () -> None 14 | # noinspection PyUnresolvedReferences 15 | import sentry_sdk 16 | 17 | sentry_sdk.init( 18 | dsn=Config.SENTRY_DSN, 19 | before_send=self._before_send, 20 | # Set traces_sample_rate to 1.0 to capture 100% 21 | # of transactions for performance monitoring. 22 | # We recommend adjusting this value in production. 23 | traces_sample_rate=1.0, 24 | ) 25 | self.activated = True 26 | Logger.info("Sentry: activated") 27 | 28 | def _before_send(self, event, _): 29 | # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] 30 | """Rewrite filenames so that GitHub integration works""" 31 | for frame in event["exception"]["values"][0]["stacktrace"]["frames"]: 32 | frame["abs_path"] = frame["abs_path"].replace("\\", "/") 33 | frame["filename"] = frame["filename"].replace("\\", "/") 34 | 35 | return event 36 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/SimpleTrackMonitoringState.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from protocol0.domain.lom.track.CurrentMonitoringStateEnum import CurrentMonitoringStateEnum 4 | from protocol0.domain.lom.track.routing.InputRoutingTypeEnum import InputRoutingTypeEnum 5 | 6 | if TYPE_CHECKING: 7 | from protocol0.domain.lom.track.simple_track.SimpleTrack import SimpleTrack 8 | 9 | 10 | class SimpleTrackMonitoringState(object): 11 | def __init__(self, track): 12 | # type: (SimpleTrack) -> None 13 | self._track = track 14 | 15 | def switch(self): 16 | # type: () -> None 17 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 18 | 19 | if self._track.current_monitoring_state == CurrentMonitoringStateEnum.AUTO: 20 | if isinstance(self._track, SimpleAudioTrack): 21 | self._track.input_routing.type = InputRoutingTypeEnum.EXT_IN # type: ignore[has-type] 22 | self._track.current_monitoring_state = CurrentMonitoringStateEnum.IN 23 | else: 24 | if isinstance(self._track, SimpleAudioTrack): 25 | self._track.input_routing.type = InputRoutingTypeEnum.NO_INPUT # type: ignore[has-type] 26 | self._track.current_monitoring_state = CurrentMonitoringStateEnum.AUTO 27 | -------------------------------------------------------------------------------- /domain/track_recorder/config/RecordProcessors.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.track_recorder.RecordProcessorInterface import RecordProcessorInterface 2 | 3 | 4 | class RecordProcessors(object): 5 | def __init__( 6 | self, 7 | pre_record=None, # type: RecordProcessorInterface 8 | record=None, # type: RecordProcessorInterface 9 | on_record_end=None, # type: RecordProcessorInterface 10 | post_record=None, # type: RecordProcessorInterface 11 | ): 12 | # type: (...) -> None 13 | self.pre_record = pre_record 14 | self.record = record 15 | self.on_record_end = on_record_end 16 | self.post_record = post_record 17 | 18 | def __repr__(self): 19 | # type: () -> str 20 | # noinspection SpellCheckingInspection 21 | return "RecordProcessors(\npre_record=%s,\nrecord=%s,\non_record_end=%s,\npost_record=%s" % ( 22 | self.pre_record, 23 | self.record, 24 | self.on_record_end, 25 | self.post_record 26 | ) 27 | 28 | def copy(self): 29 | # type: () -> RecordProcessors 30 | return RecordProcessors( 31 | pre_record=self.pre_record, 32 | record=self.record, 33 | on_record_end=self.on_record_end, 34 | post_record=self.post_record, 35 | ) -------------------------------------------------------------------------------- /domain/lom/song/components/TempoComponent.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | import Live 4 | from _Framework.SubjectSlot import subject_slot, SlotManager 5 | 6 | from protocol0.domain.shared.utils.timing import debounce 7 | from protocol0.domain.shared.scheduler.Scheduler import Scheduler 8 | 9 | 10 | class TempoComponent(SlotManager): 11 | def __init__(self, song): 12 | # type: (Live.Song.Song) -> None 13 | super(TempoComponent, self).__init__() 14 | self._song = song 15 | self._tempo_listener.subject = self._song 16 | 17 | @subject_slot("tempo") 18 | @debounce(duration=1000) 19 | def _tempo_listener(self): 20 | # type: () -> None 21 | Scheduler.defer(partial(setattr, self, "tempo", round(self.tempo))) 22 | 23 | @property 24 | def tempo(self): 25 | # type: () -> float 26 | return self._song.tempo 27 | 28 | @tempo.setter 29 | def tempo(self, tempo): 30 | # type: (float) -> None 31 | try: 32 | self._song.tempo = tempo 33 | except RuntimeError: 34 | pass 35 | 36 | def tap(self): 37 | # type: () -> None 38 | self._song.tap_tempo() 39 | 40 | def scroll(self, go_next): 41 | # type: (bool) -> None 42 | increment = 1 if go_next else -1 43 | self.tempo += increment 44 | -------------------------------------------------------------------------------- /infra/persistence/TrackData.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Dict 2 | 3 | from protocol0.domain.lom.track.simple_track.AudioToMidiClipMapping import AudioToMidiClipMapping 4 | from protocol0.domain.shared.LiveObject import liveobj_valid 5 | from protocol0.infra.persistence.TrackDataEnum import TrackDataEnum 6 | 7 | if TYPE_CHECKING: 8 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 9 | 10 | 11 | class TrackData(object): 12 | def __init__(self, track): 13 | # type: (SimpleAudioTrack) -> None 14 | self._track = track 15 | 16 | def __repr__(self): 17 | # type: () -> str 18 | return "TrackData(%s)" % self._track 19 | 20 | def save(self): 21 | # type: () -> None 22 | if liveobj_valid(self._track._track): 23 | self._track._track.set_data( 24 | TrackDataEnum.CLIP_MAPPING.value, 25 | self._track.clip_mapping.to_dict(), 26 | ) 27 | 28 | def restore(self): 29 | # type: () -> None 30 | # noinspection PyTypeChecker 31 | mapping_data = self._track._track.get_data( 32 | TrackDataEnum.CLIP_MAPPING.value, None 33 | ) # type: Dict 34 | 35 | if mapping_data is not None: 36 | self._track.clip_mapping = AudioToMidiClipMapping(self, mapping_data) 37 | -------------------------------------------------------------------------------- /tests/domain/scene/test_scene_length.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | 3 | from protocol0.domain.lom.clip.Clip import Clip 4 | from protocol0.domain.lom.clip.ClipConfig import ClipConfig 5 | from protocol0.domain.lom.scene.SceneClips import SceneClips, SceneClipSlot 6 | from protocol0.domain.lom.scene.SceneLength import SceneLength 7 | from protocol0.shared.Song import Song 8 | from protocol0.tests.domain.fixtures.clip import AbletonClip 9 | from protocol0.tests.domain.fixtures.clip_slot import AbletonClipSlot 10 | from protocol0.tests.domain.fixtures.p0 import make_protocol0 11 | 12 | 13 | def test_scene_length(): 14 | make_protocol0() 15 | clips = SceneClips(0) 16 | scene_length = SceneLength(clips, 0) 17 | assert scene_length.length == 0 18 | assert scene_length.bar_length == 0 19 | 20 | clip_slot = Song.selected_track().clip_slots[0] 21 | live_clip_slot = cast(AbletonClipSlot, clip_slot._clip_slot) 22 | live_clip_slot.add_clip() 23 | live_clip_slot.clip.length = 4 24 | 25 | live_clip = AbletonClip() 26 | live_clip.length = 4 27 | clip_slot = AbletonClipSlot() 28 | clip_slot.has_clip = True 29 | clip_slot.clip = Clip(live_clip_slot.clip, 1, ClipConfig(1)) 30 | clips._clip_slot_tracks.append(SceneClipSlot(None, clip_slot)) # noqa 31 | 32 | assert scene_length.length == 4 33 | assert scene_length.bar_length == 1 34 | -------------------------------------------------------------------------------- /application/control_surface/group/ActionGroupLog.py: -------------------------------------------------------------------------------- 1 | from protocol0.application.control_surface.ActionGroupInterface import ActionGroupInterface 2 | from protocol0.domain.audit.LogService import LogService 3 | from protocol0.domain.audit.SongStatsService import SongStatsService 4 | from protocol0.shared.logging.Logger import Logger 5 | 6 | 7 | class ActionGroupLog(ActionGroupInterface): 8 | CHANNEL = 9 9 | 10 | def configure(self): 11 | # type: () -> None 12 | # LOG encoder 13 | self.add_encoder( 14 | identifier=1, name="log current", on_press=self._container.get(LogService).log_current 15 | ) 16 | 17 | # LOGS encoder 18 | self.add_encoder( 19 | identifier=2, name="log set", on_press=self._container.get(LogService).log_set 20 | ) 21 | 22 | # CLR encoder 23 | self.add_encoder(identifier=3, name="clear logs", on_press=Logger.clear) 24 | 25 | # STATs encoder 26 | self.add_encoder( 27 | identifier=4, 28 | name="display song stats", 29 | on_press=self._container.get(SongStatsService).display_song_stats, 30 | ) 31 | 32 | # VSTs missing encoder 33 | self.add_encoder( 34 | identifier=5, 35 | name="log missing vsts", 36 | on_press=self._container.get(LogService).log_missing_vsts, 37 | ) 38 | -------------------------------------------------------------------------------- /domain/lom/sample/SampleCategoryEnum.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from protocol0.domain.lom.instrument.preset.preset_importer.DirectoryPresetImporter import ( 4 | DirectoryPresetImporter, 5 | ) 6 | from protocol0.shared.AbstractEnum import AbstractEnum 7 | from protocol0.shared.Config import Config 8 | 9 | 10 | class SampleCategoryEnum(AbstractEnum): 11 | DRUMS = "DRUMS" 12 | VOCALS = "VOCALS" 13 | 14 | @property 15 | def sample_directory(self): 16 | # type: () -> str 17 | return self.get_value_from_mapping( 18 | { 19 | SampleCategoryEnum.DRUMS: Config.SAMPLE_DIRECTORY, 20 | SampleCategoryEnum.VOCALS: "%s\\_Vocal" % Config.SAMPLE_DIRECTORY, 21 | } 22 | ) 23 | 24 | @property 25 | def drum_rack_prefix(self): 26 | # type: () -> str 27 | return self.get_value_from_mapping( 28 | { 29 | SampleCategoryEnum.DRUMS: "DR", 30 | SampleCategoryEnum.VOCALS: "V", 31 | } 32 | ) 33 | 34 | @property 35 | def subcategories(self): 36 | # type: () -> List[str] 37 | subcategories = set() 38 | 39 | presets = DirectoryPresetImporter(self.sample_directory).import_presets() 40 | for preset in presets: 41 | subcategories.add(preset.category) 42 | 43 | return sorted(subcategories) 44 | -------------------------------------------------------------------------------- /domain/lom/track/simple_track/audio/special/ReferenceTrack.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 4 | from protocol0.domain.lom.track.group_track.AbstractGroupTrack import AbstractGroupTrack 5 | from protocol0.shared.Song import Song 6 | 7 | 8 | class ReferenceTrack(AbstractGroupTrack): 9 | TRACK_NAME = "Reference" 10 | 11 | def __init__(self, *a, **k): 12 | # type: (Any, Any) -> None 13 | super(ReferenceTrack, self).__init__(*a, **k) 14 | mastering_rack = Song.master_track().devices.get_one_from_enum( 15 | DeviceEnum.MASTERING_RACK) 16 | self._mastering_rack_enabled = mastering_rack is not None and mastering_rack.is_enabled 17 | 18 | def toggle(self): 19 | # type: () -> None 20 | mastering_rack = Song.master_track().devices.get_one_from_enum( 21 | DeviceEnum.MASTERING_RACK) 22 | 23 | if self.muted: 24 | self.muted = False 25 | self.solo = True 26 | if mastering_rack is not None: 27 | self._mastering_rack_enabled = mastering_rack.is_enabled 28 | mastering_rack.is_enabled = False 29 | else: 30 | self.muted = True 31 | self.solo = False 32 | 33 | if mastering_rack is not None: 34 | mastering_rack.is_enabled = self._mastering_rack_enabled 35 | -------------------------------------------------------------------------------- /tests/domain/shared/event/test_domain_event_bus.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.shared.event.DomainEventBus import DomainEventBus 2 | from protocol0.domain.shared.scheduler.BarEndingEvent import BarEndingEvent 3 | 4 | 5 | def test_domain_event_bus(): 6 | test_res = [] 7 | 8 | def listener(_): 9 | test_res.append(True) 10 | 11 | DomainEventBus.subscribe(BarEndingEvent, listener) 12 | DomainEventBus.emit(BarEndingEvent()) 13 | 14 | assert test_res == [True] 15 | 16 | 17 | class TestEvent(object): 18 | pass 19 | 20 | 21 | def sub(): 22 | pass 23 | 24 | 25 | def test_domain_event_bus_duplicate(): 26 | DomainEventBus._registry = {} 27 | DomainEventBus.subscribe(TestEvent, sub) 28 | assert DomainEventBus._registry[TestEvent] == [sub] 29 | 30 | # no duplicates 31 | DomainEventBus.subscribe(TestEvent, sub) 32 | assert DomainEventBus._registry[TestEvent] == [sub] 33 | 34 | 35 | class Test(object): 36 | def m(self): 37 | pass 38 | 39 | 40 | def test_domain_event_bus_duplicate_methods(): 41 | DomainEventBus._registry = {} 42 | t1, t2 = Test(), Test() 43 | DomainEventBus.subscribe(TestEvent, t1.m) 44 | assert DomainEventBus._registry[TestEvent] == [t1.m] 45 | 46 | # no duplicates across classes 47 | DomainEventBus.subscribe(TestEvent, t2.m, unique_method=True) 48 | assert DomainEventBus._registry[TestEvent] == [t1.m] 49 | -------------------------------------------------------------------------------- /domain/audit/LOMAnalyzerService.py: -------------------------------------------------------------------------------- 1 | from protocol0.domain.lom.track.group_track.AbstractGroupTrack import AbstractGroupTrack 2 | from protocol0.shared.Song import Song 3 | 4 | 5 | class LOMAnalyzerService(object): 6 | """Audit object model""" 7 | 8 | def check_tracks_tree_consistency(self): 9 | # type: () -> None 10 | for simple_track in Song.simple_tracks(): 11 | # 1st layer checks 12 | if simple_track.group_track: 13 | assert simple_track in simple_track.group_track.sub_tracks, ( 14 | "failed on %s" % simple_track 15 | ) 16 | 17 | if simple_track.is_foldable: 18 | for sub_track in simple_track.sub_tracks: 19 | assert sub_track.group_track == simple_track, "failed on %s" % simple_track 20 | 21 | # 2nd layer checks 22 | abstract_group_track = simple_track.abstract_group_track 23 | if simple_track.is_foldable: 24 | assert len(abstract_group_track.sub_tracks) == len(simple_track.sub_tracks), "sub tracks inconsistency" 25 | for sub_track in abstract_group_track.sub_tracks: 26 | if isinstance(sub_track, AbstractGroupTrack): 27 | assert sub_track.group_track == abstract_group_track, ( 28 | "failed on %s" % simple_track 29 | ) 30 | -------------------------------------------------------------------------------- /application/control_surface/group/ActionGroupCut.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from protocol0.application.control_surface.ActionGroupInterface import ActionGroupInterface 4 | from protocol0.domain.lom.song.components.SceneCrudComponent import SceneCrudComponent 5 | from protocol0.shared.Song import Song 6 | 7 | 8 | class ActionGroupCut(ActionGroupInterface): 9 | CHANNEL = 8 10 | 11 | def configure(self): 12 | # type: () -> None 13 | # SPLiT encoder 14 | self.add_encoder( 15 | identifier=8, 16 | name="split scene", 17 | on_scroll=lambda: Song.selected_scene().crop_scroller.scroll, 18 | on_press=lambda: partial( 19 | self._container.get(SceneCrudComponent).split_scene, Song.selected_scene() 20 | ), 21 | ) 22 | 23 | # CROP encoder 24 | self.add_encoder( 25 | identifier=12, 26 | name="crop scene", 27 | on_scroll=lambda: Song.selected_scene().crop_scroller.scroll, 28 | on_press=lambda: partial( 29 | self._container.get(SceneCrudComponent).crop_scene, Song.selected_scene() 30 | ), 31 | ) 32 | 33 | # TAIL encoder 34 | self.add_encoder( 35 | identifier=14, 36 | name="isolate clip tail", 37 | on_press=lambda: Song.selected_track().isolate_clip_tail, 38 | ) 39 | -------------------------------------------------------------------------------- /domain/lom/device/PluginDevice.py: -------------------------------------------------------------------------------- 1 | import Live 2 | from typing import List, Any, Optional, cast 3 | 4 | from protocol0.domain.lom.device.Device import Device 5 | 6 | 7 | class PluginDevice(Device): 8 | def __init__(self, *a, **k): 9 | # type: (Any, Any) -> None 10 | super(PluginDevice, self).__init__(*a, **k) 11 | self._device = cast( 12 | Live.PluginDevice.PluginDevice, self._device 13 | ) # type: Live.PluginDevice.PluginDevice 14 | 15 | @property 16 | def presets(self): 17 | # type: () -> List[str] 18 | return [str(preset) for preset in list(self._device.presets) if not str(preset) == "empty"] 19 | 20 | @property 21 | def selected_preset_index(self): 22 | # type: () -> int 23 | return self._device.selected_preset_index 24 | 25 | @selected_preset_index.setter 26 | def selected_preset_index(self, selected_preset_index): 27 | # type: (int) -> None 28 | self._device.selected_preset_index = selected_preset_index 29 | 30 | @property 31 | def selected_preset(self): 32 | # type: () -> str 33 | return self.presets[self.selected_preset_index] 34 | 35 | @property 36 | def preset_name(self): 37 | # type: () -> Optional[str] 38 | """overridden""" 39 | return self.selected_preset 40 | 41 | @property 42 | def type_name(self): 43 | # type: () -> str 44 | return self.name 45 | -------------------------------------------------------------------------------- /domain/shared/backend/Backend.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from p0_backend_client import P0BackendClient 4 | from typing import Optional, Callable, Any 5 | 6 | from protocol0.shared.logging.Logger import Logger 7 | from protocol0.shared.types import Func 8 | 9 | 10 | def show_and_log(backend_client_func, log_func): 11 | # type: (Func, Func) -> Func 12 | @wraps(backend_client_func) 13 | def decorate(message, *a, **k): 14 | # type: (str, Any, Any) -> None 15 | log_func(message) 16 | backend_client_func(message, *a, **k) 17 | 18 | return decorate 19 | 20 | 21 | class Backend(object): 22 | """Backend API facade""" 23 | 24 | _INSTANCE = None # type: Optional[Backend] 25 | 26 | def __init__(self, send_midi): 27 | # type: (Callable) -> None 28 | Backend._INSTANCE = self 29 | self._client = P0BackendClient(send_midi) 30 | 31 | # wrap backend notification to also log 32 | self._client.show_info = show_and_log(self._client.show_info, Logger.info) 33 | self._client.show_success = show_and_log(self._client.show_success, Logger.info) 34 | self._client.show_warning = show_and_log(self._client.show_warning, Logger.warning) 35 | self._client.show_error = show_and_log(self._client.show_error, Logger.warning) 36 | 37 | @classmethod 38 | def client(cls): 39 | # type: () -> P0BackendClient 40 | return cls._INSTANCE._client 41 | -------------------------------------------------------------------------------- /tests/domain/scene/test_scene_playing_position.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | 3 | from protocol0.domain.lom.scene.SceneClips import SceneClips 4 | from protocol0.domain.lom.scene.SceneLength import SceneLength 5 | from protocol0.domain.lom.scene.ScenePlayingState import ScenePlayingState 6 | from protocol0.shared.Song import Song 7 | from protocol0.tests.domain.fixtures.clip_slot import AbletonClipSlot 8 | from protocol0.tests.domain.fixtures.p0 import make_protocol0 9 | 10 | 11 | def test_scene_playing_state(): 12 | make_protocol0() 13 | clip_slot = Song.selected_track().clip_slots[0] 14 | live_clip_slot = cast(AbletonClipSlot, clip_slot._clip_slot) 15 | live_clip_slot.add_clip() 16 | live_clip_slot.clip.length = 8 17 | live_clip_slot.clip.is_playing = True 18 | clip_slot._has_clip_listener() 19 | clips = SceneClips(0) 20 | 21 | scene_length = SceneLength(clips, 0) 22 | scene_position = ScenePlayingState(clips, scene_length) 23 | assert scene_position.position == 0 24 | assert scene_position.bar_position == 0 25 | assert scene_position.current_bar == 0 26 | 27 | live_clip_slot.clip.playing_position = 2.0 28 | assert scene_position.position == 2 29 | assert scene_position.bar_position == 0.5 30 | assert scene_position.current_bar == 0 31 | 32 | live_clip_slot.clip.playing_position = 4.0 33 | assert scene_position.position == 4 34 | assert scene_position.bar_position == 1 35 | assert scene_position.current_bar == 1 36 | -------------------------------------------------------------------------------- /tests/infra/listeners/test_subject_slot.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | from _Framework.SubjectSlot import Subject, subject_slot, SlotManager 4 | 5 | 6 | def test_subject_slot_inheritance(): 7 | # type: () -> None 8 | res = [] 9 | 10 | class Emitter(Subject): 11 | __subject_events__ = ("test",) 12 | 13 | def emit(self): 14 | # type: () -> None 15 | # noinspection PyUnresolvedReferences 16 | self.notify_test() 17 | 18 | class Parent(SlotManager): 19 | def __init__(self, source_emitter): 20 | # type: (Emitter) -> None 21 | super(Parent, self).__init__() 22 | self.listener.subject = source_emitter 23 | 24 | @subject_slot("test") 25 | def listener(self): 26 | # type: () -> None 27 | res.append(1) 28 | 29 | class Child(Parent): 30 | def __init__(self, source_emitter): 31 | # type: (Emitter) -> None 32 | super(Child, self).__init__(source_emitter=source_emitter) 33 | self.listener.subject = source_emitter 34 | 35 | @subject_slot("test") 36 | def listener(self): 37 | # type: () -> None 38 | res.append(2) 39 | 40 | emitter = Emitter() 41 | obj = Parent(emitter) 42 | emitter.emit() 43 | 44 | assert res == [1] 45 | 46 | obj.listener.subject = None 47 | _ = Child(emitter) 48 | emitter.emit() 49 | assert res == [1, 2] 50 | -------------------------------------------------------------------------------- /application/control_surface/group/ActionGroupClip.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from protocol0.application.control_surface.ActionGroupInterface import ActionGroupInterface 4 | from protocol0.domain.lom.clip.AudioClip import AudioClip 5 | from protocol0.domain.lom.clip.MidiClip import MidiClip 6 | from protocol0.domain.lom.track.simple_track.audio.SimpleAudioTrack import SimpleAudioTrack 7 | from protocol0.domain.lom.track.simple_track.midi.SimpleMidiTrack import SimpleMidiTrack 8 | from protocol0.shared.Song import Song 9 | 10 | 11 | class ActionGroupClip(ActionGroupInterface): 12 | CHANNEL = 7 13 | 14 | def configure(self): 15 | # type: () -> None 16 | # DUPLicate clip 17 | self.add_encoder( 18 | identifier=1, 19 | name="duplicate clip", 20 | on_press=lambda: Song.selected_track(SimpleMidiTrack).broadcast_selected_clip, 21 | ) 22 | 23 | # midi clip to MONO 24 | self.add_encoder( 25 | identifier=4, 26 | name="midi clip to mono", 27 | on_press=lambda: Song.selected_clip(MidiClip).to_mono, 28 | ) 29 | 30 | # BACK to previous file path 31 | self.add_encoder( 32 | identifier=13, 33 | name="set clip previous file path", 34 | on_press=lambda: partial( 35 | Song.selected_track(SimpleAudioTrack).back_to_previous_clip_file_path, 36 | Song.selected_clip(AudioClip), 37 | ), 38 | ) 39 | -------------------------------------------------------------------------------- /domain/lom/validation/sub_validators/SimpleTrackHasDeviceValidator.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from typing import Optional 4 | 5 | from protocol0.domain.lom.device.DeviceEnum import DeviceEnum 6 | from protocol0.domain.lom.track.simple_track.SimpleTrack import SimpleTrack 7 | from protocol0.domain.lom.validation.ValidatorInterface import ValidatorInterface 8 | from protocol0.domain.shared.BrowserServiceInterface import BrowserServiceInterface 9 | from protocol0.shared.sequence.Sequence import Sequence 10 | 11 | 12 | class SimpleTrackHasDeviceValidator(ValidatorInterface): 13 | def __init__(self, track, device_enum, browser_service): 14 | # type: (SimpleTrack, DeviceEnum, BrowserServiceInterface) -> None 15 | self._track = track 16 | self._device_enum = device_enum 17 | self._browser_service = browser_service 18 | 19 | def get_error_message(self): 20 | # type: () -> Optional[str] 21 | if self.is_valid(): 22 | return None 23 | return "Couldn't find device %s in %s" % (self._device_enum, self._track) 24 | 25 | def is_valid(self): 26 | # type: () -> bool 27 | return self._track.devices.get_one_from_enum(self._device_enum) is not None 28 | 29 | def fix(self): 30 | # type: () -> Sequence 31 | 32 | seq = Sequence() 33 | seq.add(self._track.select) 34 | seq.add(partial(self._browser_service.load_device_from_enum, self._device_enum)) 35 | seq.wait(5) 36 | return seq.done() 37 | -------------------------------------------------------------------------------- /domain/lom/device/MixerDevice.py: -------------------------------------------------------------------------------- 1 | import Live 2 | from _Framework.SubjectSlot import SlotManager 3 | from typing import List, Dict 4 | 5 | from protocol0.domain.lom.device_parameter.DeviceParameter import DeviceParameter 6 | 7 | 8 | class MixerDevice(SlotManager): 9 | def __init__(self, live_mixer_device): 10 | # type: (Live.MixerDevice.MixerDevice) -> None 11 | super(MixerDevice, self).__init__() 12 | 13 | parameters = live_mixer_device.sends + [live_mixer_device.volume, live_mixer_device.panning] 14 | self._parameters = [ 15 | DeviceParameter(parameter) for parameter in parameters 16 | ] 17 | 18 | def to_dict(self): 19 | # type: () -> Dict 20 | return { 21 | "params": [p.value for p in self.parameters] 22 | } 23 | 24 | def update_from_dict(self, mixer_data): 25 | # type: (Dict) -> None 26 | assert len(self.parameters) == len(mixer_data["params"]), "Cannot update mixer device" 27 | for param, value in zip(self.parameters, mixer_data["params"]): 28 | param.value = value 29 | 30 | @property 31 | def parameters(self): 32 | # type: () -> List[DeviceParameter] 33 | return self._parameters 34 | 35 | def reset(self): 36 | # type: () -> None 37 | for param in self.parameters: 38 | param.value = 0 39 | 40 | @property 41 | def is_default(self): 42 | # type: () -> bool 43 | return all(param.value == param.default_value for param in self.parameters) 44 | --------------------------------------------------------------------------------