Source code for guitarpro.gp5

import attr

from . import models as gp
from . import gp4


[docs]class GP5File(gp4.GP4File): """A reader for GuitarPro 5 files.""" # Reading # =======
[docs] def readSong(self): """Read the song. A song consists of score information, triplet feel, lyrics, tempo, song key, MIDI channels, measure and track count, measure headers, tracks, measures. - Version: :ref:`byte-size-string` of size 30. - Score information. See :meth:`readInfo`. - Lyrics. See :meth:`readLyrics`. - RSE master effect. See :meth:`readRSEInstrument`. - Tempo name: :ref:`int-byte-size-string`. - Tempo: :ref:`int`. - Hide tempo: :ref:`bool`. Don't display tempo on the sheet if set. - Key: :ref:`int`. Key signature of the song. - Octave: :ref:`int`. Octave of the song. - MIDI channels. See :meth:`readMidiChannels`. - Directions. See :meth:`readDirections`. - Master reverb. See :meth:`readMasterReverb`. - Number of measures: :ref:`int`. - Number of tracks: :ref:`int`. - Measure headers. See :meth:`readMeasureHeaders`. - Tracks. See :meth:`readTracks`. - Measures. See :meth:`readMeasures`. """ song = gp.Song(tracks=[], measureHeaders=[]) song.version = self.readVersion() song.versionTuple = self.versionTuple if self.isClipboard(): song.clipboard = self.readClipboard() self.readInfo(song) song.lyrics = self.readLyrics() song.masterEffect = self.readRSEMasterEffect() song.pageSetup = self.readPageSetup() song.tempoName = self.readIntByteSizeString() song.tempo = self.readInt() song.hideTempo = self.readBool() if self.versionTuple > (5, 0, 0) else False song.key = gp.KeySignature((self.readSignedByte(), 0)) self.readInt() # octave channels = self.readMidiChannels() directions = self.readDirections() song.masterEffect.reverb = self.readInt() measureCount = self.readInt() trackCount = self.readInt() with self.annotateErrors('reading'): self.readMeasureHeaders(song, measureCount, directions) self.readTracks(song, trackCount, channels) self.readMeasures(song) return song
def readClipboard(self): clipboard = super().readClipboard() if clipboard is None: return clipboard.startBeat = self.readInt() clipboard.stopBeat = self.readInt() clipboard.subBarCopy = bool(self.readInt()) return clipboard
[docs] def readInfo(self, song): """Read score information. Score information consists of sequence of :ref:`IntByteSizeStrings <int-byte-size-string>`: - title - subtitle - artist - album - words - music - copyright - tabbed by - instructions The sequence if followed by notice. Notice starts with the number of notice lines stored in :ref:`int`. Each line is encoded in :ref:`int-byte-size-string`. """ song.title = self.readIntByteSizeString() song.subtitle = self.readIntByteSizeString() song.artist = self.readIntByteSizeString() song.album = self.readIntByteSizeString() song.words = self.readIntByteSizeString() song.music = self.readIntByteSizeString() song.copyright = self.readIntByteSizeString() song.tab = self.readIntByteSizeString() song.instructions = self.readIntByteSizeString() notesCount = self.readInt() song.notice = [] for _ in range(notesCount): song.notice.append(self.readIntByteSizeString())
[docs] def readRSEMasterEffect(self): """Read RSE master effect. Persistence of RSE master effect was introduced in Guitar Pro 5.1. It is read as: - Master volume: :ref:`int`. Values are in range from 0 to 200. - 10-band equalizer. See :meth:`readEqualizer`. """ masterEffect = gp.RSEMasterEffect() if self.versionTuple > (5, 0, 0): masterEffect.volume = self.readInt() self.readInt() # ??? masterEffect.equalizer = self.readEqualizer(11) return masterEffect
[docs] def readEqualizer(self, knobsNumber): """Read equalizer values. Equalizers are used in RSE master effect and Track RSE. They consist of *n* :ref:`SignedBytes <signed-byte>` for each *n* bands and one :ref:`signed-byte` for gain (PRE) fader. Volume values are stored as opposite to actual value. See :meth:`unpackVolumeValue`. """ knobs = list(map(self.unpackVolumeValue, self.readSignedByte(count=knobsNumber))) return gp.RSEEqualizer(knobs=knobs[:-1], gain=knobs[-1])
[docs] def unpackVolumeValue(self, value): """Unpack equalizer volume value. Equalizer volumes are float but stored as :ref:`SignedBytes <signed-byte>`. """ return -value / 10
[docs] def readPageSetup(self): """Read page setup. Page setup is read as follows: - Page size: 2 :ref:`Ints <int>`. Width and height of the page. - Page padding: 4 :ref:`Ints <int>`. Left, right, top, bottom padding of the page. - Score size proportion: :ref:`int`. - Header and footer elements: :ref:`short`. See :class:`guitarpro.models.HeaderFooterElements` for value mapping. - List of placeholders: * title * subtitle * artist * album * words * music * wordsAndMusic * copyright1, e.g. *"Copyright %copyright%"* * copyright2, e.g. *"All Rights Reserved - International Copyright Secured"* * pageNumber """ setup = gp.PageSetup() setup.pageSize = gp.Point(self.readInt(), self.readInt()) left = self.readInt() right = self.readInt() top = self.readInt() bottom = self.readInt() setup.pageMargin = gp.Padding(left, top, right, bottom) setup.scoreSizeProportion = self.readInt() / 100 setup.headerAndFooter = self.readShort() setup.title = self.readIntByteSizeString() setup.subtitle = self.readIntByteSizeString() setup.artist = self.readIntByteSizeString() setup.album = self.readIntByteSizeString() setup.words = self.readIntByteSizeString() setup.music = self.readIntByteSizeString() setup.wordsAndMusic = self.readIntByteSizeString() setup.copyright = self.readIntByteSizeString() + '\n' + self.readIntByteSizeString() setup.pageNumber = self.readIntByteSizeString() return setup
[docs] def readDirections(self): """Read directions. Directions is a list of 19 :ref:`ShortInts <short>` each pointing at the number of measure. Directions are read in the following order. - Coda - Double Coda - Segno - Segno Segno - Fine - Da Capo - Da Capo al Coda - Da Capo al Double Coda - Da Capo al Fine - Da Segno - Da Segno al Coda - Da Segno al Double Coda - Da Segno al Fine - Da Segno Segno - Da Segno Segno al Coda - Da Segno Segno al Double Coda - Da Segno Segno al Fine - Da Coda - Da Double Coda """ signs = { gp.DirectionSign('Coda'): self.readShort(), gp.DirectionSign('Double Coda'): self.readShort(), gp.DirectionSign('Segno'): self.readShort(), gp.DirectionSign('Segno Segno'): self.readShort(), gp.DirectionSign('Fine'): self.readShort() } fromSigns = { gp.DirectionSign('Da Capo'): self.readShort(), gp.DirectionSign('Da Capo al Coda'): self.readShort(), gp.DirectionSign('Da Capo al Double Coda'): self.readShort(), gp.DirectionSign('Da Capo al Fine'): self.readShort(), gp.DirectionSign('Da Segno'): self.readShort(), gp.DirectionSign('Da Segno al Coda'): self.readShort(), gp.DirectionSign('Da Segno al Double Coda'): self.readShort(), gp.DirectionSign('Da Segno al Fine'): self.readShort(), gp.DirectionSign('Da Segno Segno'): self.readShort(), gp.DirectionSign('Da Segno Segno al Coda'): self.readShort(), gp.DirectionSign('Da Segno Segno al Double Coda'): self.readShort(), gp.DirectionSign('Da Segno Segno al Fine'): self.readShort(), gp.DirectionSign('Da Coda'): self.readShort(), gp.DirectionSign('Da Double Coda'): self.readShort() } return signs, fromSigns
[docs] def readMeasureHeaders(self, song, measureCount, directions): super().readMeasureHeaders(song, measureCount) signs, fromSigns = directions for sign, number in signs.items(): if number > -1: song.measureHeaders[number - 1].direction = sign for sign, number in fromSigns.items(): if number > -1: song.measureHeaders[number - 1].fromDirection = sign
[docs] def readMeasureHeader(self, number, song, previous=None): """Read measure header. Measure header format in Guitar Pro 5 differs from one in Guitar Pro 3. First, there is a blank byte if measure is not first. Then measure header is read as in GP3's :meth:`guitarpro.gp3.readMeasureHeader`. Then measure header is read as follows: - Time signature beams: 4 :ref:`Bytes <byte>`. Appears If time signature was set, i.e. flags *0x01* and *0x02* are both set. - Blank :ref:`byte` if flag at *0x10* is set. - Triplet feel: :ref:`byte`. See :class:`guitarpro.models.TripletFeel`. """ if previous is not None: # Always 0 self.skip(1) flags = self.readByte() header = gp.MeasureHeader() header.number = number header.start = 0 header.tripletFeel = self._tripletFeel if flags & 0x01: header.timeSignature.numerator = self.readSignedByte() else: header.timeSignature.numerator = previous.timeSignature.numerator if flags & 0x02: header.timeSignature.denominator.value = self.readSignedByte() else: header.timeSignature.denominator.value = previous.timeSignature.denominator.value header.isRepeatOpen = bool(flags & 0x04) if flags & 0x08: header.repeatClose = self.readSignedByte() if flags & 0x20: header.marker = self.readMarker(header) if flags & 0x40: root = self.readSignedByte() type_ = self.readSignedByte() header.keySignature = gp.KeySignature((root, type_)) elif header.number > 1: header.keySignature = previous.keySignature if flags & 0x10: header.repeatAlternative = self.readRepeatAlternative(song.measureHeaders) header.hasDoubleBar = bool(flags & 0x80) if header.repeatClose > -1: header.repeatClose -= 1 if flags & 0x03: header.timeSignature.beams = self.readByte(4) else: header.timeSignature.beams = previous.timeSignature.beams if flags & 0x10 == 0: # Always 0 self.skip(1) header.tripletFeel = gp.TripletFeel(self.readByte()) return header, flags
def readRepeatAlternative(self, measureHeaders): return self.readByte()
[docs] def readTracks(self, song, trackCount, channels): """Read tracks. Tracks in Guitar Pro 5 have almost the same format as in Guitar Pro 3. If it's Guitar Pro 5.0 then 2 blank bytes are read after :meth:`guitarpro.gp3.readTracks`. If format version is higher than 5.0, 1 blank byte is read. """ super().readTracks(song, trackCount, channels) self.skip(2 if self.versionTuple == (5, 0, 0) else 1) # Always 0
[docs] def readTrack(self, track, channels): """Read track. If it's Guitar Pro 5.0 format and track is first then one blank byte is read. Then go track's flags. It presides the track's attributes: - *0x01*: drums track - *0x02*: 12 stringed guitar track - *0x04*: banjo track - *0x08*: track visibility - *0x10*: track is soloed - *0x20*: track is muted - *0x40*: RSE is enabled - *0x80*: show tuning in the header of the sheet. Flags are followed by: - Name: `String`. A 40 characters long string containing the track's name. - Number of strings: :ref:`int`. An integer equal to the number of strings of the track. - Tuning of the strings: `Table of integers`. The tuning of the strings is stored as a 7-integers table, the "Number of strings" first integers being really used. The strings are stored from the highest to the lowest. - Port: :ref:`int`. The number of the MIDI port used. - Channel. See :meth:`GP3File.readChannel`. - Number of frets: :ref:`int`. The number of frets of the instrument. - Height of the capo: :ref:`int`. The number of the fret on which a capo is set. If no capo is used, the value is 0. - Track's color. The track's displayed color in Guitar Pro. The properties are followed by second set of flags stored in a :ref:`short`. - *0x0001*: show tablature - *0x0002*: show standard notation - *0x0004*: chord diagrams are below standard notation - *0x0008*: show rhythm with tab - *0x0010*: force horizontal beams - *0x0020*: force channels 11 to 16 - *0x0040*: diagram list on top of the score - *0x0080*: diagrams in the score - *0x0200*: auto let-ring - *0x0400*: auto brush - *0x0800*: extend rhythmic inside the tab Then follow: - Auto accentuation: :ref:`byte`. See :class:`guitarpro.models.Accentuation`. - MIDI bank: :ref:`byte`. - Track RSE. See :meth:`readTrackRSE`. """ if track.number == 1 or self.versionTuple == (5, 0, 0): # Always 0 self.skip(1) flags1 = self.readByte() track.isPercussionTrack = bool(flags1 & 0x01) track.is12StringedGuitarTrack = bool(flags1 & 0x02) track.isBanjoTrack = bool(flags1 & 0x04) track.isVisible = bool(flags1 & 0x08) track.isSolo = bool(flags1 & 0x10) track.isMute = bool(flags1 & 0x20) track.useRSE = bool(flags1 & 0x40) track.indicateTuning = bool(flags1 & 0x80) track.name = self.readByteSizeString(40) stringCount = self.readInt() for i in range(7): iTuning = self.readInt() if stringCount > i: oString = gp.GuitarString(i + 1, iTuning) track.strings.append(oString) track.port = self.readInt() track.channel = self.readChannel(channels) if track.channel.channel == 9: track.isPercussionTrack = True track.fretCount = self.readInt() track.offset = self.readInt() track.color = self.readColor() flags2 = self.readShort() track.settings = gp.TrackSettings() track.settings.tablature = bool(flags2 & 0x0001) track.settings.notation = bool(flags2 & 0x0002) track.settings.diagramsAreBelow = bool(flags2 & 0x0004) track.settings.showRhythm = bool(flags2 & 0x0008) track.settings.forceHorizontal = bool(flags2 & 0x0010) track.settings.forceChannels = bool(flags2 & 0x0020) track.settings.diagramList = bool(flags2 & 0x0040) track.settings.diagramsInScore = bool(flags2 & 0x0080) # 0x0100: ??? track.settings.autoLetRing = bool(flags2 & 0x0200) track.settings.autoBrush = bool(flags2 & 0x0400) track.settings.extendRhythmic = bool(flags2 & 0x0800) track.rse = gp.TrackRSE() track.rse.autoAccentuation = gp.Accentuation(self.readByte()) track.channel.bank = self.readByte() self.readTrackRSE(track.rse)
[docs] def readTrackRSE(self, trackRSE): """Read track RSE. In GuitarPro 5.1 track RSE is read as follows: - Humanize: :ref:`byte`. - Unknown space: 6 :ref:`Ints <int>`. - RSE instrument. See :meth:`readRSEInstrument`. - 3-band track equalizer. See :meth:`readEqualizer`. - RSE instrument effect. See :meth:`readRSEInstrumentEffect`. """ trackRSE.humanize = self.readByte() self.readInt(3) # ??? self.skip(12) # ??? trackRSE.instrument = self.readRSEInstrument() if self.versionTuple > (5, 0, 0): trackRSE.equalizer = self.readEqualizer(4) self.readRSEInstrumentEffect(trackRSE.instrument) return trackRSE
[docs] def readRSEInstrument(self): """Read RSE instrument. - MIDI instrument number: :ref:`int`. - Unknown :ref:`int`. - Sound bank: :ref:`int`. - Effect number: :ref:`int`. Vestige of Guitar Pro 5.0 format. """ instrument = gp.RSEInstrument() instrument.instrument = self.readInt() instrument.unknown = self.readInt() # ??? mostly 1 instrument.soundBank = self.readInt() if self.versionTuple == (5, 0, 0): instrument.effectNumber = self.readShort() self.skip(1) else: instrument.effectNumber = self.readInt() return instrument
[docs] def readRSEInstrumentEffect(self, rseInstrument): """Read RSE instrument effect name. This feature was introduced in Guitar Pro 5.1. - Effect name: :ref:`int-byte-size-string`. - Effect category: :ref:`int-byte-size-string`. """ if self.versionTuple > (5, 0, 0): effect = self.readIntByteSizeString() effectCategory = self.readIntByteSizeString() if rseInstrument is not None: rseInstrument.effect = effect rseInstrument.effectCategory = effectCategory return rseInstrument
[docs] def readMeasure(self, measure): """Read measure. Guitar Pro 5 stores twice more measures compared to Guitar Pro 3. One measure consists of two sub-measures for each of two voices. Sub-measures are followed by a :class:`~guitarpro.models.LineBreak` stored in :ref:`byte`. """ start = measure.start for number, voice in enumerate(measure.voices[:gp.Measure.maxVoices]): self._currentVoiceNumber = number + 1 self.readVoice(start, voice) self._currentVoiceNumber = None measure.lineBreak = gp.LineBreak(self.readByte(default=0))
[docs] def readBeat(self, start, voice): """Read beat. First, beat is read is in Guitar Pro 3 :meth:`guitarpro.gp3.readBeat`. Then it is followed by set of flags stored in :ref:`short`. - *0x0001*: break beams - *0x0002*: direct beams down - *0x0004*: force beams - *0x0008*: direct beams up - *0x0010*: ottava (8va) - *0x0020*: ottava bassa (8vb) - *0x0040*: quindicesima (15ma) - *0x0100*: quindicesima bassa (15mb) - *0x0200*: start tuplet bracket here - *0x0400*: end tuplet bracket here - *0x0800*: break secondary beams - *0x1000*: break secondary tuplet - *0x2000*: force tuplet bracket - Break secondary beams: :ref:`byte`. Appears if flag at *0x0800* is set. Signifies how much beams should be broken. """ duration = super().readBeat(start, voice) beat = self.getBeat(voice, start) flags2 = self.readShort() if flags2 & 0x0010: beat.octave = gp.Octave.ottava if flags2 & 0x0020: beat.octave = gp.Octave.ottavaBassa if flags2 & 0x0040: beat.octave = gp.Octave.quindicesima if flags2 & 0x0100: beat.octave = gp.Octave.quindicesimaBassa display = gp.BeatDisplay() display.breakBeam = bool(flags2 & 0x0001) display.forceBeam = bool(flags2 & 0x0004) display.forceBracket = bool(flags2 & 0x2000) display.breakSecondaryTuplet = bool(flags2 & 0x1000) if flags2 & 0x0002: display.beamDirection = gp.VoiceDirection.down if flags2 & 0x0008: display.beamDirection = gp.VoiceDirection.up if flags2 & 0x0200: display.tupletBracket = gp.TupletBracket.start if flags2 & 0x0400: display.tupletBracket = gp.TupletBracket.end if flags2 & 0x0800: display.breakSecondary = self.readByte() beat.display = display return duration
[docs] def readBeatStroke(self): """Read beat stroke. Beat stroke consists of two :ref:`Bytes <byte>` which correspond to stroke down and stroke up speed. See :class:`guitarpro.models.BeatStroke` for value mapping. """ stroke = super().readBeatStroke() return stroke.swapDirection()
[docs] def readMixTableChange(self, measure): """Read mix table change. Mix table change was modified to support RSE instruments. It is read as in Guitar Pro 3 and is followed by: - Wah effect. See :meth:`readWahEffect`. - RSE instrument effect. See :meth:`readRSEInstrumentEffect`. """ tableChange = super(gp4.GP4File, self).readMixTableChange(measure) flags = self.readMixTableChangeFlags(tableChange) tableChange.wah = self.readWahEffect(flags) self.readRSEInstrumentEffect(tableChange.rse) return tableChange
[docs] def readMixTableChangeValues(self, tableChange, measure): """Read mix table change values. Mix table change values consist of: - Instrument: :ref:`signed-byte`. - RSE instrument. See `readRSEInstrument`. - Volume: :ref:`signed-byte`. - Balance: :ref:`signed-byte`. - Chorus: :ref:`signed-byte`. - Reverb: :ref:`signed-byte`. - Phaser: :ref:`signed-byte`. - Tremolo: :ref:`signed-byte`. - Tempo name: :ref:`int-byte-size-string`. - Tempo: :ref:`int`. If the value is -1 then corresponding parameter hasn't changed. """ instrument = self.readSignedByte() rse = self.readRSEInstrument() if self.versionTuple == (5, 0, 0): self.skip(1) volume = self.readSignedByte() balance = self.readSignedByte() chorus = self.readSignedByte() reverb = self.readSignedByte() phaser = self.readSignedByte() tremolo = self.readSignedByte() tempoName = self.readIntByteSizeString() tempo = self.readInt() if instrument >= 0: tableChange.instrument = gp.MixTableItem(instrument) tableChange.rse = rse if volume >= 0: tableChange.volume = gp.MixTableItem(volume) if balance >= 0: tableChange.balance = gp.MixTableItem(balance) if chorus >= 0: tableChange.chorus = gp.MixTableItem(chorus) if reverb >= 0: tableChange.reverb = gp.MixTableItem(reverb) if phaser >= 0: tableChange.phaser = gp.MixTableItem(phaser) if tremolo >= 0: tableChange.tremolo = gp.MixTableItem(tremolo) if tempo >= 0: tableChange.tempo = gp.MixTableItem(tempo) tableChange.tempoName = tempoName
[docs] def readMixTableChangeDurations(self, tableChange): """Read mix table change durations. Durations are read for each non-null :class:`~guitarpro.models.MixTableItem`. Durations are encoded in :ref:`signed-byte`. If tempo did change, then one :ref:`bool` is read. If it's true, then tempo change won't be displayed on the score. """ if tableChange.volume is not None: tableChange.volume.duration = self.readSignedByte() if tableChange.balance is not None: tableChange.balance.duration = self.readSignedByte() if tableChange.chorus is not None: tableChange.chorus.duration = self.readSignedByte() if tableChange.reverb is not None: tableChange.reverb.duration = self.readSignedByte() if tableChange.phaser is not None: tableChange.phaser.duration = self.readSignedByte() if tableChange.tremolo is not None: tableChange.tremolo.duration = self.readSignedByte() if tableChange.tempo is not None: tableChange.tempo.duration = self.readSignedByte() tableChange.hideTempo = self.versionTuple > (5, 0, 0) and self.readBool()
[docs] def readMixTableChangeFlags(self, tableChange): """Read mix table change flags. Mix table change flags are read as in Guitar Pro 4 :meth:`guitarpro.gp4.readMixTableChangeFlags`, with one additional flag: - *0x40*: use RSE - *0x80*: show wah-wah """ flags = super().readMixTableChangeFlags(tableChange) tableChange.useRSE = bool(flags & 0x40) return flags
[docs] def readWahEffect(self, flags): """Read wah-wah. - Wah value: :ref:`signed-byte`. See :class:`guitarpro.models.WahEffect` for value mapping. """ return gp.WahEffect(value=self.readSignedByte(), display=bool(flags & 0x80))
[docs] def readNote(self, note, guitarString, track): """Read note. The first byte is note flags: - *0x01*: duration percent - *0x02*: heavy accentuated note - *0x04*: ghost note - *0x08*: presence of note effects - *0x10*: dynamics - *0x20*: fret - *0x40*: accentuated note - *0x80*: right hand or left hand fingering Flags are followed by: - Note type: :ref:`byte`. Note is normal if values is 1, tied if value is 2, dead if value is 3. - Note dynamics: :ref:`signed-byte`. See :meth:`unpackVelocity`. - Fret number: :ref:`signed-byte`. If flag at *0x20* is set then read fret number. - Fingering: 2 :ref:`SignedBytes <signed-byte>`. See :class:`guitarpro.models.Fingering`. - Duration percent: :ref:`double`. - Second set of flags: :ref:`byte`. - *0x02*: swap accidentals. - Note effects. See :meth:`guitarpro.gp4.readNoteEffects`. """ flags = self.readByte() note.string = guitarString.number note.effect.heavyAccentuatedNote = bool(flags & 0x02) note.effect.ghostNote = bool(flags & 0x04) note.effect.accentuatedNote = bool(flags & 0x40) if flags & 0x20: note.type = gp.NoteType(self.readByte()) if flags & 0x10: dyn = self.readSignedByte() note.velocity = self.unpackVelocity(dyn) if flags & 0x20: fret = self.readSignedByte() if note.type == gp.NoteType.tie: value = self.getTiedNoteValue(guitarString.number, track) else: value = fret note.value = value if 0 <= value < 100 else 0 if flags & 0x80: note.effect.leftHandFinger = gp.Fingering(self.readSignedByte()) note.effect.rightHandFinger = gp.Fingering(self.readSignedByte()) if flags & 0x01: note.durationPercent = self.readDouble() flags2 = self.readByte() note.swapAccidentals = bool(flags2 & 0x02) if flags & 0x08: note.effect = self.readNoteEffects(note) return note
[docs] def readGrace(self): """Read grace note effect. - Fret: :ref:`signed-byte`. Number of fret. - Dynamic: :ref:`byte`. Dynamic of a grace note, as in :attr:`guitarpro.models.Note.velocity`. - Transition: :ref:`byte`. See :class:`guitarpro.models.GraceEffectTransition`. - Duration: :ref:`byte`. Values are: - *1*: Thirty-second note. - *2*: Twenty-fourth note. - *3*: Sixteenth note. - Flags: :ref:`byte`. - *0x01*: grace note is muted (dead) - *0x02*: grace note is on beat """ grace = gp.GraceEffect() grace.fret = self.readByte() grace.velocity = self.unpackVelocity(self.readByte()) grace.transition = gp.GraceEffectTransition(self.readByte()) grace.duration = 1 << (7 - self.readByte()) flags = self.readByte() grace.isDead = bool(flags & 0x01) grace.isOnBeat = bool(flags & 0x02) return grace
[docs] def readSlides(self): """Read slides. First :ref:`byte` stores slide types: - *0x01*: shift slide - *0x02*: legato slide - *0x04*: slide out downwards - *0x08*: slide out upwards - *0x10*: slide into from below - *0x20*: slide into from above """ slideType = self.readByte() slides = [] if slideType & 0x01: slides.append(gp.SlideType.shiftSlideTo) if slideType & 0x02: slides.append(gp.SlideType.legatoSlideTo) if slideType & 0x04: slides.append(gp.SlideType.outDownwards) if slideType & 0x08: slides.append(gp.SlideType.outUpwards) if slideType & 0x10: slides.append(gp.SlideType.intoFromBelow) if slideType & 0x20: slides.append(gp.SlideType.intoFromAbove) return slides
[docs] def readHarmonic(self, note): """Read harmonic. First :ref:`byte` is harmonic type: - *1*: natural harmonic - *2*: artificial harmonic - *3*: tapped harmonic - *4*: pinch harmonic - *5*: semi-harmonic In case harmonic types is artificial, following data is read: - Note: :ref:`byte`. - Accidental: :ref:`signed-byte`. - Octave: :ref:`byte`. If harmonic type is tapped: - Fret: :ref:`byte`. """ harmonicType = self.readSignedByte() if harmonicType == 1: harmonic = gp.NaturalHarmonic() elif harmonicType == 2: # C = 0, D = 2, E = 4, F = 5... # b = -1, # = 1 # loco = 0, 8va = 1, 15ma = 2 semitone = self.readByte() accidental = self.readSignedByte() pitchClass = gp.PitchClass(semitone, accidental) octave = gp.Octave(self.readByte()) harmonic = gp.ArtificialHarmonic(pitchClass, octave) elif harmonicType == 3: fret = self.readByte() harmonic = gp.TappedHarmonic(fret) elif harmonicType == 4: harmonic = gp.PinchHarmonic() elif harmonicType == 5: harmonic = gp.SemiHarmonic() return harmonic
# Writing # ======= def writeSong(self, song): self.writeVersion() self.writeClipboard(song.clipboard) self.writeInfo(song) self.writeLyrics(song.lyrics) self.writeRSEMasterEffect(song.masterEffect) self.writePageSetup(song.pageSetup) self.writeIntByteSizeString(song.tempoName) self.writeInt(song.tempo) if self.versionTuple > (5, 0, 0): self.writeBool(song.hideTempo) self.writeSignedByte(song.key.value[0]) self.writeInt(0) # octave self.writeMidiChannels(song.tracks) self.writeDirections(song.measureHeaders) self.writeMasterReverb(song.masterEffect) measureCount = len(song.tracks[0].measures) trackCount = len(song.tracks) self.writeInt(measureCount) self.writeInt(trackCount) with self.annotateErrors('writing'): self.writeMeasureHeaders(song.tracks[0].measures) self.writeTracks(song.tracks) self.writeMeasures(song.tracks) def writeClipboard(self, clipboard): if clipboard is None: return super().writeClipboard(clipboard) self.writeInt(clipboard.startBeat) self.writeInt(clipboard.stopBeat) self.writeInt(int(clipboard.subBarCopy)) def writeInfo(self, song): self.writeIntByteSizeString(song.title) self.writeIntByteSizeString(song.subtitle) self.writeIntByteSizeString(song.artist) self.writeIntByteSizeString(song.album) self.writeIntByteSizeString(song.words) self.writeIntByteSizeString(song.music) self.writeIntByteSizeString(song.copyright) self.writeIntByteSizeString(song.tab) self.writeIntByteSizeString(song.instructions) self.writeInt(len(song.notice)) for line in song.notice: self.writeIntByteSizeString(line) def writeRSEMasterEffect(self, masterEffect): if self.versionTuple > (5, 0, 0): masterEffect.volume = masterEffect.volume or 100 masterEffect.reverb = masterEffect.reverb or 0 masterEffect.equalizer = masterEffect.equalizer or gp.RSEEqualizer(knobs=[0] * 10, gain=0) self.writeInt(masterEffect.volume) self.writeInt(0) self.writeEqualizer(masterEffect.equalizer) def writeEqualizer(self, equalizer): for knob in equalizer.knobs: self.writeSignedByte(self.packVolumeValue(knob)) self.writeSignedByte(self.packVolumeValue(equalizer.gain)) def packVolumeValue(self, value): return int(-round(value, 1) * 10) def writePageSetup(self, setup): self.writeInt(setup.pageSize.x) self.writeInt(setup.pageSize.y) self.writeInt(setup.pageMargin.left) self.writeInt(setup.pageMargin.right) self.writeInt(setup.pageMargin.top) self.writeInt(setup.pageMargin.bottom) self.writeInt(int(setup.scoreSizeProportion * 100)) self.writeByte(setup.headerAndFooter & 0xff) flags2 = 0x00 if setup.headerAndFooter and gp.HeaderFooterElements.pageNumber != 0: flags2 |= 0x01 self.writeByte(flags2) self.writeIntByteSizeString(setup.title) self.writeIntByteSizeString(setup.subtitle) self.writeIntByteSizeString(setup.artist) self.writeIntByteSizeString(setup.album) self.writeIntByteSizeString(setup.words) self.writeIntByteSizeString(setup.music) self.writeIntByteSizeString(setup.wordsAndMusic) copyrighta, copyrightb = setup.copyright.split('\n', 1) self.writeIntByteSizeString(copyrighta) self.writeIntByteSizeString(copyrightb) self.writeIntByteSizeString(setup.pageNumber) def writeDirections(self, measureHeaders): order = ['Coda', 'Double Coda', 'Segno', 'Segno Segno', 'Fine', 'Da Capo', 'Da Capo al Coda', 'Da Capo al Double Coda', 'Da Capo al Fine', 'Da Segno', 'Da Segno al Coda', 'Da Segno al Double Coda', 'Da Segno al Fine', 'Da Segno Segno', 'Da Segno Segno al Coda', 'Da Segno Segno al Double Coda', 'Da Segno Segno al Fine', 'Da Coda', 'Da Double Coda'] signs = {} for number, header in enumerate(measureHeaders, start=1): if header.direction is not None: signs[header.direction.name] = number if header.fromDirection is not None: signs[header.fromDirection.name] = number for name in order: self.writeShort(signs.get(name, -1)) def writeMasterReverb(self, masterEffect): if masterEffect is not None: self.writeInt(masterEffect.reverb) else: self.writeInt(0) def writeMeasureHeader(self, header, previous=None): flags = self.packMeasureHeaderFlags(header, previous) if previous is not None: self.placeholder(1) self.writeMeasureHeaderValues(header, flags) def packMeasureHeaderFlags(self, header, previous=None): flags = super().packMeasureHeaderFlags(header, previous) if previous is not None: if header.timeSignature.beams != previous.timeSignature.beams: flags |= 0x03 return flags def writeMeasureHeaderValues(self, header, flags): header = attr.evolve(header, repeatClose=header.repeatClose+1) self.writeByte(flags) if flags & 0x01: self.writeSignedByte(header.timeSignature.numerator) if flags & 0x02: self.writeSignedByte(header.timeSignature.denominator.value) if flags & 0x08: self.writeSignedByte(header.repeatClose) if flags & 0x20: self.writeMarker(header.marker) if flags & 0x40: self.writeSignedByte(header.keySignature.value[0]) self.writeSignedByte(header.keySignature.value[1]) if flags & 0x10: self.writeRepeatAlternative(header.repeatAlternative) if flags & 0x03: for beam in header.timeSignature.beams: self.writeByte(beam) if flags & 0x10 == 0: self.placeholder(1) self.writeByte(header.tripletFeel.value) def writeRepeatAlternative(self, repeatAlternative): self.writeByte(repeatAlternative & 255) def writeTracks(self, tracks): super().writeTracks(tracks) self.placeholder(2 if self.versionTuple == (5, 0, 0) else 1) def writeTrack(self, track, number): if number == 1 or self.versionTuple == (5, 0, 0): self.placeholder(1) flags1 = 0x00 if track.isPercussionTrack: flags1 |= 0x01 if track.is12StringedGuitarTrack: flags1 |= 0x02 if track.isBanjoTrack: flags1 |= 0x04 if track.isVisible: flags1 |= 0x08 if track.isSolo: flags1 |= 0x10 if track.isMute: flags1 |= 0x20 if track.useRSE: flags1 |= 0x40 if track.indicateTuning: flags1 |= 0x80 self.writeByte(flags1) self.writeByteSizeString(track.name, 40) self.writeInt(len(track.strings)) for i in range(7): if i < len(track.strings): tuning = track.strings[i].value else: tuning = 0 self.writeInt(tuning) self.writeInt(track.port) self.writeChannel(track) self.writeInt(track.fretCount) self.writeInt(track.offset) self.writeColor(track.color) flags2 = 0x0000 if track.settings.tablature: flags2 |= 0x0001 if track.settings.notation: flags2 |= 0x0002 if track.settings.diagramsAreBelow: flags2 |= 0x0004 if track.settings.showRhythm: flags2 |= 0x0008 if track.settings.forceHorizontal: flags2 |= 0x0010 if track.settings.forceChannels: flags2 |= 0x0020 if track.settings.diagramList: flags2 |= 0x0040 if track.settings.diagramsInScore: flags2 |= 0x0080 if track.settings.autoLetRing: flags2 |= 0x0200 if track.settings.autoBrush: flags2 |= 0x0400 if track.settings.extendRhythmic: flags2 |= 0x0800 self.writeShort(flags2) if track.rse is not None and track.rse.autoAccentuation is not None: self.writeByte(track.rse.autoAccentuation.value) else: self.writeByte(0) self.writeByte(track.channel.bank) self.writeTrackRSE(track.rse) def writeTrackRSE(self, trackRSE): self.writeByte(trackRSE.humanize) self.writeInt(0) self.writeInt(0) self.writeInt(100) self.placeholder(12) self.writeRSEInstrument(trackRSE.instrument) if self.versionTuple > (5, 0, 0): self.writeEqualizer(trackRSE.equalizer) self.writeRSEInstrumentEffect(trackRSE.instrument) def writeRSEInstrument(self, instrument): self.writeInt(instrument.instrument) self.writeInt(instrument.unknown) self.writeInt(instrument.soundBank) if self.versionTuple == (5, 0, 0): self.writeShort(instrument.effectNumber) self.placeholder(1) else: self.writeInt(instrument.effectNumber) def writeRSEInstrumentEffect(self, instrument): if self.versionTuple > (5, 0, 0): self.writeIntByteSizeString(instrument.effect) self.writeIntByteSizeString(instrument.effectCategory) def writeMeasure(self, measure): for number, voice in enumerate(measure.voices[:gp.Measure.maxVoices]): self._currentVoiceNumber = number + 1 self.writeVoice(voice) self._currentVoiceNumber = None self.writeByte(measure.lineBreak.value) def writeBeat(self, beat): super().writeBeat(beat) flags2 = 0x0000 if beat.display.breakBeam: flags2 |= 0x0001 if beat.display.beamDirection == gp.VoiceDirection.down: flags2 |= 0x0002 if beat.display.forceBeam: flags2 |= 0x0004 if beat.display.beamDirection == gp.VoiceDirection.up: flags2 |= 0x0008 if beat.octave == gp.Octave.ottava: flags2 |= 0x0010 if beat.octave == gp.Octave.ottavaBassa: flags2 |= 0x0020 if beat.octave == gp.Octave.quindicesima: flags2 |= 0x0040 if beat.octave == gp.Octave.quindicesimaBassa: flags2 |= 0x0100 if beat.display.tupletBracket == gp.TupletBracket.start: flags2 |= 0x0200 if beat.display.tupletBracket == gp.TupletBracket.end: flags2 |= 0x0400 if beat.display.breakSecondary: flags2 |= 0x0800 if beat.display.breakSecondaryTuplet: flags2 |= 0x1000 if beat.display.forceBracket: flags2 |= 0x2000 self.writeShort(flags2) if flags2 & 0x0800: self.writeByte(beat.display.breakSecondary) def writeBeatStroke(self, stroke): super().writeBeatStroke(stroke.swapDirection()) def writeMixTableChange(self, tableChange): super(gp4.GP4File, self).writeMixTableChange(tableChange) self.writeMixTableChangeFlags(tableChange) self.writeWahEffect(tableChange.wah) self.writeRSEInstrumentEffect(tableChange.rse) def writeMixTableChangeValues(self, tableChange): self.writeSignedByte(tableChange.instrument.value if tableChange.instrument is not None else -1) self.writeRSEInstrument(tableChange.rse) if self.versionTuple == (5, 0, 0): self.placeholder(1) self.writeSignedByte(tableChange.volume.value if tableChange.volume is not None else -1) self.writeSignedByte(tableChange.balance.value if tableChange.balance is not None else -1) self.writeSignedByte(tableChange.chorus.value if tableChange.chorus is not None else -1) self.writeSignedByte(tableChange.reverb.value if tableChange.reverb is not None else -1) self.writeSignedByte(tableChange.phaser.value if tableChange.phaser is not None else -1) self.writeSignedByte(tableChange.tremolo.value if tableChange.tremolo is not None else -1) self.writeIntByteSizeString(tableChange.tempoName) self.writeInt(tableChange.tempo.value if tableChange.tempo is not None else -1) def writeMixTableChangeDurations(self, tableChange): if tableChange.volume is not None: self.writeSignedByte(tableChange.volume.duration) if tableChange.balance is not None: self.writeSignedByte(tableChange.balance.duration) if tableChange.chorus is not None: self.writeSignedByte(tableChange.chorus.duration) if tableChange.reverb is not None: self.writeSignedByte(tableChange.reverb.duration) if tableChange.phaser is not None: self.writeSignedByte(tableChange.phaser.duration) if tableChange.tremolo is not None: self.writeSignedByte(tableChange.tremolo.duration) if tableChange.tempo is not None: self.writeSignedByte(tableChange.tempo.duration) if self.versionTuple > (5, 0, 0): self.writeBool(tableChange.hideTempo) def writeMixTableChangeFlags(self, tableChange): flags = 0x00 if tableChange.volume is not None and tableChange.volume.allTracks: flags |= 0x01 if tableChange.balance is not None and tableChange.balance.allTracks: flags |= 0x02 if tableChange.chorus is not None and tableChange.chorus.allTracks: flags |= 0x04 if tableChange.reverb is not None and tableChange.reverb.allTracks: flags |= 0x08 if tableChange.phaser is not None and tableChange.phaser.allTracks: flags |= 0x10 if tableChange.tremolo is not None and tableChange.tremolo.allTracks: flags |= 0x20 if tableChange.useRSE: flags |= 0x40 if tableChange.wah is not None and tableChange.wah.display: flags |= 0x80 self.writeByte(flags) def writeWahEffect(self, wah): if wah is not None: self.writeSignedByte(wah.value) else: self.writeSignedByte(gp.WahEffect.none.value) def writeNote(self, note): flags = self.packNoteFlags(note) self.writeByte(flags) if flags & 0x20: self.writeByte(self.getEnumValue(note.type)) if flags & 0x10: value = self.packVelocity(note.velocity) self.writeSignedByte(value) if flags & 0x20: fret = note.value if note.type != gp.NoteType.tie else 0 self.writeSignedByte(fret) if flags & 0x80: self.writeSignedByte(self.getEnumValue(note.effect.leftHandFinger)) self.writeSignedByte(self.getEnumValue(note.effect.rightHandFinger)) if flags & 0x01: self.writeDouble(note.durationPercent) flags2 = 0x00 if note.swapAccidentals: flags2 |= 0x02 self.writeByte(flags2) if flags & 0x08: self.writeNoteEffects(note) def packNoteFlags(self, note): flags = super().packNoteFlags(note) if abs(note.durationPercent - 1.0) >= 1e-3: flags |= 0x01 return flags def writeGrace(self, grace): self.writeByte(grace.fret) self.writeByte(self.packVelocity(grace.velocity)) self.writeByte(grace.transition.value) self.writeByte(8 - grace.duration.bit_length()) flags = 0x00 if grace.isDead: flags |= 0x01 if grace.isOnBeat: flags |= 0x02 self.writeByte(flags) def writeSlides(self, slides): slideType = 0 for slide in slides: if slide == gp.SlideType.shiftSlideTo: slideType |= 0x01 elif slide == gp.SlideType.legatoSlideTo: slideType |= 0x02 elif slide == gp.SlideType.outDownwards: slideType |= 0x04 elif slide == gp.SlideType.outUpwards: slideType |= 0x08 elif slide == gp.SlideType.intoFromBelow: slideType |= 0x10 elif slide == gp.SlideType.intoFromAbove: slideType |= 0x20 self.writeByte(slideType) def writeHarmonic(self, note, harmonic): self.writeSignedByte(harmonic.type) if isinstance(harmonic, gp.ArtificialHarmonic): if not harmonic.pitch or not harmonic.octave: harmonic.pitch = gp.PitchClass(note.realValue % 12) harmonic.octave = gp.Octave.ottava self.writeByte(harmonic.pitch.just) self.writeSignedByte(harmonic.pitch.accidental) self.writeByte(harmonic.octave.value) elif isinstance(harmonic, gp.TappedHarmonic): self.writeByte(harmonic.fret)