You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

151 lines
8.3 KiB

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
This 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 <http://www.gnu.org/licenses/>.
"""
import logging; logger = logging.getLogger(__name__); logger.info("import")
#Standard Library Modules
6 years ago
from typing import List, Set, Dict, Tuple
#Third Party Modules
#Template Modules
import template.engine.sequencer
#Our modules
6 years ago
from .pattern import Pattern
class Track(object): #injection at the bottom of this file!
6 years ago
"""The pattern is same as the track, even if the GUI does not represent it that way
6 years ago
Init parameters are for cloned and copied tracks, but not for loading from json.
"""
def __init__(self, parentData,
6 years ago
name:str="",
structure:Set[int]=None,
scale:Tuple[int]=None,
color:str=None,
whichPatternsAreScaleTransposed:Dict[int,int]=None,
whichPatternsAreHalftoneTransposed:Dict[int,int]=None,
6 years ago
simpleNoteNames:List[str]=None):
self.parentData = parentData
self.sequencerInterface = template.engine.sequencer.SequencerInterface(parentTrack=self, name=name) #needs parentData
self.color = color if color else "#00FFFF" # "#rrggbb" in hex. no alpha. a convenience slot for the GUI to save a color.
#2.0
#The distinction between Track and Pattern in our code is artificial, it is the same thing,
#we take inspiration from the GUI that presents the Track on its own.
#The following setting is most likely to be found in the track sub-window:
self.patternLengthMultiplicator = 1 #int. >= 1 the multiplicator is added after all other calculations, like subdivions. We can't integrate this into howManyUnits because that is the global score value
6 years ago
self.pattern = Pattern(parentTrack=self, scale=scale, simpleNoteNames=simpleNoteNames)
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.
6 years ago
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()
6 years ago
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.
This is called after every small change.
"""
#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.parentData.howManyUnits * self.parentData.whatTypeOfUnit) / self.parentData.subdivisions #subdivisions is 1 by default. bigger values mean shorter values, which is compensated by the user setting bigger howManyUnits manually.
oneMeasureInTicks = int(oneMeasureInTicks) * self.patternLengthMultiplicator
filteredStructure = [index for index in sorted(self.structure) if index < self.parentData.numberOfMeasures] #not <= because we compare count with range
6 years ago
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.parentData.howManyUnits, self.parentData.whatTypeOfUnit, self.parentData.subdivisions, self.patternLengthMultiplicator)
6 years ago
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.
6 years ago
# cboxclips = [o.clip for o in self.parentTrack.sequencerInterface.calfboxTrack.status().clips]
# for cboxclip in cboxclips:
# cboxclip.set_pattern(self.cboxPattern[cboxclip.patroneoScaleTransposed])
6 years ago
#Save / Load / Export
def serialize(self)->dict:
6 years ago
return {
"sequencerInterface" : self.sequencerInterface.serialize(),
"color" : self.color,
"structure" : list(self.structure),
6 years ago
"pattern" : self.pattern.serialize(),
"whichPatternsAreScaleTransposed" : self.whichPatternsAreScaleTransposed,
"whichPatternsAreHalftoneTransposed" : self.whichPatternsAreHalftoneTransposed,
"patternLengthMultiplicator" : self.patternLengthMultiplicator,
6 years ago
}
@classmethod
def instanceFromSerializedData(cls, parentData, serializedData):
self = cls.__new__(cls)
self.parentData = parentData
6 years ago
self.sequencerInterface = template.engine.sequencer.SequencerInterface.instanceFromSerializedData(self, serializedData["sequencerInterface"])
if "patternLengthMultiplicator" in serializedData: #Version 1.8
self.patternLengthMultiplicator = serializedData["patternLengthMultiplicator"]
else:
self.patternLengthMultiplicator = 1
6 years ago
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:
6 years ago
return {
"id" : id(self),
"sequencerInterface" : self.sequencerInterface.export(),
"color" : self.color,
"structure" : sorted(self.structure),
"patternBaseLength" : self.parentData.howManyUnits, #for convenient access
"patternLengthMultiplicator" : self.patternLengthMultiplicator, #int
"pattern": self.pattern.exportCache,
"scale": self.pattern.scale,
"numberOfSteps": self.pattern.numberOfSteps, #pitches. Convenience for len(scale)
6 years ago
"simpleNoteNames": self.pattern.simpleNoteNames,
"numberOfMeasures": self.parentData.numberOfMeasures,
"whichPatternsAreScaleTransposed": self.whichPatternsAreScaleTransposed,
"whichPatternsAreHalftoneTransposed": self.whichPatternsAreHalftoneTransposed,
6 years ago
}
#Dependency Injections.
template.engine.sequencer.Score.TrackClass = Track #Score will look for Track in its module.