Source code for gsapi.GSIO

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function


import logging
import glob
from .GSPitchSpelling import *
from .GSPattern import *
from .GSPatternUtils import *
from . import GSPitchSpelling
import os,sys
import json  
if sys.version_info >= (3,0):
    import pickle
else:
    import cPickle as pickle
gsiolog = logging.getLogger("gsapi.GSIO")
# gsiolog.setLevel(level=logging.INFO)


[docs]def fromMidi(midiPath, NoteToTagsMap="pitchNames", tracksToGet=None, TagsFromTrackNameEvents=False, filterOutNotMapped=True, checkForOverlapped=False): """Loads a midi file as a pattern. Args: midiPath: midi filePath NoteToTagsMap: dictionary converting pitches to tags if only interested in pitch, you can set this to "pitchNames", or optionally set the value to the list of string for pitches from C noteMapping maps classes to a list of possible mappings, a mapping can be either: * a tuple of (note, channel): if one of those doesnt matter it canbe replaced by '*' character * an integer: if only pitch matters for simplicity, one can pass only one integer (i.e not a list) for one to one mappings if midi track contains the name of one element of mapping, it'll be choosed without anyother consideration TagsFromTrackNameEvents: use only track names to resolve mapping, useful for midi containing named tracks filterOutNotMapped: if set to true, don't add event not represented by `NoteToTagsMap` tracksToGet: if not empty, specifies Midi tracks wanted either by name or index checkForOverlapped: if true will check that two consecutiveEvents with exactly same MidiNote are not overlapping """ _NoteToTagsMap = __formatNoteToTags(NoteToTagsMap) return __fromMidiFormatted(midiPath=midiPath, NoteToTagsMap=_NoteToTagsMap, tracksToGet=tracksToGet, TagsFromTrackNameEvents=TagsFromTrackNameEvents, filterOutNotMapped=filterOutNotMapped, checkForOverlapped=checkForOverlapped)
[docs]def fromMidiCollection(midiGlobPath, NoteToTagsMap=defaultPitchNames, tracksToGet=None, TagsFromTrackNameEvents=False, filterOutNotMapped=True, desiredLength=0): """Loads a midi collection. Args: midiGlobPath: midi filePath in glob naming convention (e.g. '/folder/To/Crawl/\*.mid') NoteToTagsMap: tracksToGet: TagsFromTrackNameEvents: filterOutNotMapped: desiredLength: optionally cut patterns in equal length Returns: a list of GSPattern build from Midi folder """ res = [] _NoteToTagsMap = __formatNoteToTags(NoteToTagsMap) for f in glob.glob(midiGlobPath): name = os.path.splitext(os.path.basename(f))[0] gsiolog.info("getting " + name) p = fromMidi(f, _NoteToTagsMap, TagsFromTrackNameEvents=TagsFromTrackNameEvents, filterOutNotMapped=filterOutNotMapped) if desiredLength > 0: res += p.splitInEqualLengthPatterns(desiredLength, makeCopy=False) else: res += [p] return res
[docs]def fromJSONFile(filePath,conserveTuple=False): """Load a pattern to internal JSON Format. Args: filePath: filePath where to load it """ def hinted_tuple_hook(obj): # print obj if isinstance(obj, list):return [hinted_tuple_hook(e) for e in obj] if isinstance(obj, dict): if '__tuple__' in obj:return tuple(obj['items']) return {k:hinted_tuple_hook(e) for k,e in obj.items()} else: return obj with open(filePath, 'r') as f: return GSPattern().fromJSONDict(json.load(f,object_hook=hinted_tuple_hook if conserveTuple else None))
[docs]def toJSONFile(pattern, folderPath,useTagIndexing=True,nameSuffix=None,conserveTuple = False): """Save a pattern to internal JSON Format. Args: pattern: a GSPattern folderPath: folder where to save it, fileName will be pattern.name+nameSuffix+".json" nameSuffix : string to append to name of the file useTagIndexing: if true, tags are stored as indexes from a list of all tags (reduce size of json files) ConserveTuple : useful if some tags can be tuple but much slower """ filePath = os.path.join(folderPath,pattern.name+(nameSuffix or "")+".json") if(not os.path.exists(folderPath)) : os.makedirs(folderPath) class TupleEncoder(json.JSONEncoder): """ encoder conserving tuple type info""" def checkTuple(self,item): if isinstance(item, tuple):return {'__tuple__': True, 'items': item} if isinstance(item, list):return [self.checkTuple(e) for e in item] if isinstance(item, dict):return {k:self.checkTuple(e) for k,e in item.items()} else:return item def iterencode(self,item): return json.JSONEncoder.iterencode(self, self.checkTuple(item)) encoderClass = TupleEncoder if conserveTuple else None; with open(filePath, 'w') as f: json.dump(pattern.toJSONDict(useTagIndexing=useTagIndexing), f,cls=encoderClass,indent=1,separators=(',', ':')) return os.path.abspath(filePath)
[docs]def fromPickleFile(filePath): """Load a pattern from pickle Format. Args: filePath: filePath where to load it """ with open(filePath, 'rb') as f: return pickle.load(f)
[docs]def toPickleFile(pattern,folderPath,nameSuffix=None): """Save a pattern to python's pickle Format. Args: pattern: a GSPattern folderPath: folder where to save it, fileName will be pattern.name+nameSuffix+".json" nameSuffix : string to append to name of the file """ filePath = os.path.join(folderPath,pattern.name+(nameSuffix or "")+".pickle") if(not os.path.exists(folderPath)) : os.makedirs(folderPath) with open(filePath, 'wb') as f: pickle.dump(pattern, f) return os.path.abspath(filePath)
def __formatNoteToTags(_NoteToTags): """Internal conversion for consistent NoteTagMap structure.""" import copy NoteToTags = copy.copy(_NoteToTags) if NoteToTags == "pitchNames": NoteToTags = {"pitchNames": ""} for n in NoteToTags: if n == "pitchNames": if not NoteToTags["pitchNames"]: NoteToTags["pitchNames"] = defaultPitchNames else: if not isinstance(NoteToTags[n], list): NoteToTags[n] = [NoteToTags[n]] for i in range(len(NoteToTags[n])): if isinstance(NoteToTags[n][i], int): NoteToTags[n][i] = (NoteToTags[n][i], "*") return NoteToTags def __fromMidiFormatted(midiPath, NoteToTagsMap, tracksToGet=None, TagsFromTrackNameEvents=False, filterOutNotMapped=True, checkForOverlapped=False): """ Internal function that accepts only consistent NoteTagMap structure as created by __formatNoteToTags. """ import math import midi import os globalMidi = midi.read_midifile(midiPath) globalMidi.make_ticks_abs() pattern = GSPattern() pattern.name = os.path.basename(midiPath) # boolean to avoid useless string creation extremeLog = gsiolog.getEffectiveLevel()<=logging.DEBUG # get time signature first gsiolog.info("start processing %s" % pattern.name) __findTimeInfoFromMidi(pattern, globalMidi) tick_to_quarter_note = 1.0 / globalMidi.resolution pattern.events = [] lastNoteOff = 0 notFoundTags = [] trackIdx = 0 trackDuration = None for tracks in globalMidi: shouldSkipTrack = False for e in tracks: if shouldSkipTrack: continue if not TagsFromTrackNameEvents: noteTag = () if midi.MetaEvent.is_event(e.statusmsg): if e.metacommand == midi.TrackNameEvent.metacommand: if tracksToGet and not((e.text in tracksToGet) or (trackIdx in tracksToGet)): gsiolog.info("skipping track: %i %s" % (trackIdx, e.text)) shouldSkipTrack = True continue else: gsiolog.info(pattern.name + ": getting track: %i %s" % (trackIdx, e.text)) if TagsFromTrackNameEvents: noteTag = __findTagsFromName(e.text, NoteToTagsMap) if midi.EndOfTrackEvent.is_event(e.statusmsg): thisDuration = e.tick * tick_to_quarter_note trackDuration = max(trackDuration,thisDuration) if trackDuration else thisDuration continue isNoteOn = midi.NoteOnEvent.is_event(e.statusmsg) isNoteOff = midi.NoteOffEvent.is_event(e.statusmsg) if isNoteOn or isNoteOff: pitch = e.pitch # optimize pitch property access tick = e.tick velocity = e.get_velocity() if velocity==0: isNoteOff =True isNoteOn = False curBeat = tick * 1.0 * tick_to_quarter_note if not noteTag: if TagsFromTrackNameEvents: continue noteTag = __findTagsFromPitchAndChannel(pitch, e.channel, NoteToTagsMap) if not noteTag: if [e.channel, pitch] not in notFoundTags: gsiolog.info(pattern.name + ": no tags found for " "pitch %d on channel %d" % (pitch, e.channel)) notFoundTags += [[e.channel, pitch]] if filterOutNotMapped: continue if isNoteOn: if extremeLog : gsiolog.debug("on %d %f"%(pitch, curBeat)) pattern.events += [GSPatternEvent(startTime=curBeat, duration=-1, pitch=pitch, velocity=velocity, tag=noteTag)] if isNoteOff: if extremeLog: gsiolog.debug( "off %d %f"%(pitch,curBeat)) foundNoteOn = False isTrueNoteOff = midi.NoteOffEvent.is_event(e.statusmsg) for i in reversed(pattern.events): if (i.pitch == pitch) and (i.tag==noteTag) and ((isTrueNoteOff and(curBeat >= i.startTime))or curBeat>i.startTime) and i.duration<=0.0001: foundNoteOn = True i.duration = max(0.0001,curBeat - i.startTime) lastNoteOff = max(curBeat,lastNoteOff) gsiolog.info("set duration %f at start %f "%(i.duration,i.startTime)) break if not foundNoteOn : gsiolog.warning(pattern.name + ": not found note on\n %s\n%s\n%s , %s "% (e,pattern.events[-1],noteTag,curBeat)) trackIdx += 1 elementSize = 4.0 / pattern.timeSignature[1] barSize = pattern.timeSignature[0] * elementSize lastBarPos = math.ceil(lastNoteOff*1.0/barSize) * barSize pattern.duration = trackDuration or lastBarPos if checkForOverlapped: pattern.removeOverlapped(usePitchValues=True) return pattern def __findTimeInfoFromMidi(pattern, midiFile): import midi foundTimeSignatureEvent = False foundTempo = False pattern.timeSignature = (4, 4) pattern.bpm = 60 pattern.resolution = midiFile.resolution # hide it in pattern to be able to retrieve it when exporting for tracks in midiFile: for e in tracks: if midi.MetaEvent.is_event(e.statusmsg): if e.metacommand == midi.TimeSignatureEvent.metacommand: if foundTimeSignatureEvent and (pattern.timeSignature != (e.numerator, e.denominator)): gsiolog.error(pattern.name + ": multiple time " "signature found, not supported, " "result can be alterated") foundTimeSignatureEvent = True pattern.timeSignature = (e.numerator, e.denominator) # e.metronome = e.thirtyseconds :: do we need that ??? elif e.metacommand == midi.SetTempoEvent.metacommand: if foundTempo: gsiolog.error(pattern.name+": multiple bpm found, not supported") foundTempo = True pattern.bpm = e.bpm if foundTimeSignatureEvent: # pass break if not foundTimeSignatureEvent: gsiolog.info(pattern.name + ": no time signature event found") def __findTagsFromName(name, noteMapping): res = tuple() for l in noteMapping: if l in name: res += [l] return res def __findTagsFromPitchAndChannel(pitch, channel, noteMapping): if "pitchNames" in noteMapping.keys(): return pitch2name(pitch, noteMapping["pitchNames"]) res = tuple() for l in noteMapping: for le in noteMapping[l]: if (le[0] in {"*", pitch}) and (le[1] in {"*", channel}): res += (l,) if len(res)==1: return res[0] return res
[docs]def toMidi(gspattern, midiMap=None, folderPath="output/", name=None): """ Function to write GSPattern instance to MIDI. Args: midiMap: mapping used to translate tags to MIDI pitch folderPath: folder where MIDI file is stored name: name of the file """ import midi # Instantiate a MIDI Pattern (contains a list of tracks) pattern = midi.Pattern(tick_relative=False,format=1) pattern.resolution=getattr(gspattern,'resolution' , 960) # Instantiate a MIDI Track (contains a list of MIDI events) track = midi.Track(tick_relative=False) track.append(midi.TimeSignatureEvent(numerator = gspattern.timeSignature[0],denominator=gspattern.timeSignature[1])) # obscure events # timeSignatureEvent.set_metronome(32) # timeSignatureEvent.set_thirtyseconds(4) track.append(midi.TrackNameEvent(text= gspattern.name)) track.append(midi.SetTempoEvent(bpm=gspattern.bpm)) # Append the track to the pattern pattern.append(track) beatToTick = pattern.resolution for e in gspattern.events: startTick = int(beatToTick * e.startTime) endTick = int(beatToTick * e.getEndTime()) pitch = e.pitch channel=1 if midiMap: pitch = midiMap[e.tag[0]] if midiMap is None: track.append(midi.NoteOnEvent(tick=startTick, velocity=e.velocity, pitch=pitch,channel=channel)) track.append(midi.NoteOffEvent(tick=endTick, velocity=e.velocity, pitch=pitch,channel=channel)) track.append(midi.EndOfTrackEvent(tick=int(gspattern.duration * beatToTick))) # make tick relatives track.sort(key=lambda e:e.tick) track.make_ticks_rel() # Save the pattern to disk if(not os.path.exists(folderPath)) : os.makedirs(folderPath) name = name or gspattern.name name = name or "test" if not ".mid" in name: name+=".mid" exportedPath = os.path.join(folderPath,name) midi.write_midifile(exportedPath, pattern) return exportedPath