#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2018, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), more specifically its template base application. The Template Base Application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ #Standard Library Modules from typing import List, Set, Dict, Tuple #Third Party Modules #Template Modules import template.engine.sequencer #Our modules from .pattern import Pattern class Track(object): #injection at the bottom of this file! """The pattern is same as the track, even if the GUI does not represent it that way Init parameters are for cloned and copied tracks, but not for loading from json. """ def __init__(self, parentScore, name:str="", structure:Set[int]=None, scale:Tuple[int]=None, color:str=None, whichPatternsAreScaleTransposed:Dict[int,int]=None, whichPatternsAreHalftoneTransposed:Dict[int,int]=None, noteNames:List[str]=None): self.parentScore = parentScore self.sequencerInterface = template.engine.sequencer.SequencerInterface(parentTrack=self, name=name) #needs parentScore self.color = color if color else "#00FFFF" # "#rrggbb" in hex. no alpha. a convenience slot for the GUI to save a color. self.pattern = Pattern(parentTrack=self, scale=scale, noteNames=noteNames) self.structure = structure if structure else set() #see buildTrack(). This is the main track data structure besides the pattern. Just integers (starts at 0) as switches which are positions where to play the patterns. In between are automatic rests. self.whichPatternsAreScaleTransposed = whichPatternsAreScaleTransposed if whichPatternsAreScaleTransposed else {} #position:integers between -7 and 7. Reversed pitch, row based: -7 is higher than 7!! self.whichPatternsAreHalftoneTransposed = whichPatternsAreHalftoneTransposed if whichPatternsAreHalftoneTransposed else {} #position:integers between -7 and 7. Reversed pitch, row based: -7 is higher than 7!! self._processAfterInit() def _processAfterInit(self): pass def buildTrack(self): """The goal is to create a cbox-track, consisting of cbox-clips which hold cbox-pattern, generated with our own note data. The latter happens in structures_pattern. """ #First clean the transpositions of zeroes self.whichPatternsAreScaleTransposed = {k:v for k,v in self.whichPatternsAreScaleTransposed.items() if v!=0 and k in self.structure} self.whichPatternsAreHalftoneTransposed = {k:v for k,v in self.whichPatternsAreHalftoneTransposed.items() if v!=0 and k in self.structure} oneMeasureInTicks = (self.parentScore.howManyUnits * self.parentScore.whatTypeOfUnit) / self.parentScore.subdivisions #subdivisions is 1 by default. bigger values mean shorter values, which is compensated by the user setting bigger howManyUnits manually. oneMeasureInTicks = int(oneMeasureInTicks) filteredStructure = [index for index in sorted(self.structure) if index < self.parentScore.numberOfMeasures] #not <= because we compare count with range cboxclips = [o.clip for o in self.sequencerInterface.calfboxTrack.status().clips] for cboxclip in cboxclips: cboxclip.delete() #removes itself from the track for index in filteredStructure: scaleTransposition = self.whichPatternsAreScaleTransposed[index] if index in self.whichPatternsAreScaleTransposed else 0 halftoneTransposition = self.whichPatternsAreHalftoneTransposed[index] if index in self.whichPatternsAreHalftoneTransposed else 0 cboxPattern = self.pattern.buildPattern(scaleTransposition, halftoneTransposition, self.parentScore.howManyUnits, self.parentScore.whatTypeOfUnit, self.parentScore.subdivisions) r = self.sequencerInterface.calfboxTrack.add_clip(index*oneMeasureInTicks, 0, oneMeasureInTicks, cboxPattern) #pos, pattern-internal offset, length, pattern. ######Old optimisations. Keep for later#### ########################################## #if changeClipsInPlace: #no need for track.buildTrack. Very cheap pattern exchange. # cboxclips = [o.clip for o in self.parentTrack.sequencerInterface.calfboxTrack.status().clips] # for cboxclip in cboxclips: # cboxclip.set_pattern(self.cboxPattern[cboxclip.patroneoScaleTransposed]) #Save / Load / Export def serialize(self)->dict: return { "sequencerInterface" : self.sequencerInterface.serialize(), "color" : self.color, "structure" : list(self.structure), "pattern" : self.pattern.serialize(), "whichPatternsAreScaleTransposed" : self.whichPatternsAreScaleTransposed, "whichPatternsAreHalftoneTransposed" : self.whichPatternsAreHalftoneTransposed, } @classmethod def instanceFromSerializedData(cls, parentScore, serializedData): self = cls.__new__(cls) self.parentScore = parentScore self.sequencerInterface = template.engine.sequencer.SequencerInterface.instanceFromSerializedData(self, serializedData["sequencerInterface"]) self.color = serializedData["color"] self.structure = set(serializedData["structure"]) self.whichPatternsAreHalftoneTransposed = {int(k):int(v) for k,v in serializedData["whichPatternsAreHalftoneTransposed"].items()} #json saves dict keys as strings self.whichPatternsAreScaleTransposed = {int(k):int(v) for k,v in serializedData["whichPatternsAreScaleTransposed"].items()} #json saves dict keys as strings self.pattern = Pattern.instanceFromSerializedData(parentTrack=self, serializedData=serializedData["pattern"] ) self._processAfterInit() return self def export(self)->dict: return { "id" : id(self), "sequencerInterface" : self.sequencerInterface.export(), "color" : self.color, "structure" : sorted(self.structure), "pattern": self.pattern.exportCache, "scale": self.pattern.scale, "noteNames": self.pattern.noteNames, "numberOfMeasures": self.parentScore.numberOfMeasures, "whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed, "whichPatternsAreHalftoneTransposed": self.whichPatternsAreHalftoneTransposed, } #Dependency Injections. template.engine.sequencer.Score.TrackClass = Track #Score will look for Track in its module.