#THIS FILE IS PART OF THE JOKOSHER PROJECT AND LICENSED UNDER THE GPL. SEE # THE 'COPYING' FILE FOR DETAILS # Copyright (c) 2007, Laszlo Pandy """ These methods were taked from Jokosher r1321, just before the release of version 0.9. After that revision these methods were rewritten to be much more efficient and less complicated algorithms that are specifically tuned to ladspa elements. However should the time come that we need to support many effects with different caps which will require audioconverts between each of them to convert the stream, these methods will need to be used again. Since they took a lot of hard work an debugging, and their author does not feel like doing that again, they are kept here for the future when someone can make use of them. Specifically, these methods perform the necessary pad blocking and ghostpad switching to make a bin with many elements with audioconverts between them. The bin has to start with a single audioconvert in connectes to both sides using ghostpads. If add is called, the arrangement will then be ghostpad->audioconvert->effect->audioconvert->ghostpad. Calling remove for the same effect will return the bin to the previous configuration: ghostpad->audioconvert->ghostpad. Note that there is still a bug in the remove effect function that will stop the audio when playing and cause gstreamer to send an error to the bus. """ def AddEffect(self, effectName): """ Adds an effect to the pipeline for this Instrument. Considerations: The effect is always placed in the pipeline after any other effects that were previously added. Parameters: effectName -- GStreamer element name of the effect to add. Returns: the added effect element. """ #make the new effect and an audioconvert to go with it convert = gst.element_factory_make("audioconvert") effectElement = gst.element_factory_make(effectName) self.effects.append(effectElement) #add both elements to effects bin self.effectsBin.add(convert) self.effectsBin.add(effectElement) # The sink pad on the first element to the right of the bin externalSinkPad = self.effectsBinSrc.get_peer() # The src pad on the last element in the bin endSrcPad = self.effectsBinSrc.get_target() state = self.playbackbin.get_state(0)[1] if state == gst.STATE_PAUSED or state == gst.STATE_PLAYING: endSrcPad.set_blocked(True) # Unlink the bin from the external element so we can put in a new ghostpad self.effectsBinSrc.unlink(externalSinkPad) # Remove the old ghostpad self.effectsBin.remove_pad(self.effectsBinSrc) newEffectPad = effectElement.sink_pads().next() # Link the last element in the bin with the new effect endSrcPad.link(newEffectPad) # Link the new element to the new convert effectElement.link(convert) # Make the audioconvert the new ghostpad self.effectsBinSrc = gst.GhostPad("src", convert.get_pad("src")) self.effectsBin.add_pad(self.effectsBinSrc) self.effectsBinSrc.link(externalSinkPad) # make the elements' state match the bin's state convert.set_state(state) effectElement.set_state(state) #give it a lambda for a callback that does nothing, so we don't have to wait endSrcPad.set_blocked_async(False, lambda x,y: False) self.StateChanged("effects") return effectElement #_____________________________________________________________________ def RemoveEffect(self, effect): """ Remove the given GStreamer element from the effects bin. Parameters: effect -- GStreamer effect to be removed from this Instrument. """ if effect not in self.effects: Globals.debug("Error: trying to remove an element that is not in the list") return previousConvert = None for pad in effect.sink_pads(): if pad.is_linked(): previousConvert = pad.get_peer().get_parent() break nextConvert = None for pad in effect.src_pads(): if pad.is_linked(): nextConvert = pad.get_peer().get_parent() break state = self.playbackbin.get_state(0)[1] if state == gst.STATE_PAUSED or state == gst.STATE_PLAYING: previousConvert.get_pad("src").set_blocked(True) # If we have to remove from the end if self.effects[-1] == effect: # The sink pad on the first element to the right of the bin externalSinkPad = self.effectsBinSrc.get_peer() # Unlink the bin from the external element so we can put in a new ghostpad self.effectsBinSrc.unlink(externalSinkPad) # Remove the old ghostpad self.effectsBin.remove_pad(self.effectsBinSrc) previousConvert.unlink(effect) # Make the audioconvert the new ghostpad self.effectsBinSrc = gst.GhostPad("src", previousConvert.get_pad("src")) self.effectsBin.add_pad(self.effectsBinSrc) self.effectsBinSrc.link(externalSinkPad) # Else we are removing from the middle or beginning of the list else: nextEffect = nextConvert.get_pad("src").get_peer().get_parent() nextConvert.unlink(nextEffect) previousConvert.unlink(effect) previousConvert.link(nextEffect) # Remove and dispose of the two elements effect.unlink(nextConvert) self.effectsBin.remove(effect) self.effectsBin.remove(nextConvert) effect.set_state(gst.STATE_NULL) nextConvert.set_state(gst.STATE_NULL) #remove the effect from our own list self.effects.remove(effect) #give it a lambda for a callback that does nothing, so we don't have to wait previousConvert.get_pad("src").set_blocked_async(False, lambda x,y: False) self.StateChanged("effects") #_____________________________________________________________________ def ChangeEffectOrder(self, effect, newPosition): """ Move a given GStreamer element inside the effects bin. This method does not swap the element into its new position, it simply shifts all the elements between the effect's current position and the new position down by one. Since a Gstreamer pipeline is essentially a linked list, this "shift" implementation is the fastest way to make one element move to a new position without changing the order of any of the others. For example if this instrument has five effects which are ordered A, B, C, D, E, and I call this method with effect D and a new position of 2 the new order will be: A, D, B, C, E. Parameters: effect -- GStreamer effect to be moved. newPosition -- value of the new position inside the effects bin the effect will have (with 0 as the first position). """ if effect not in self.effects: Globals.debug("Error: trying to remove an element that is not in the list") return if newPosition >= len(self.effects): Globals.debug("Error: trying to move effect to position past the end of the list") return oldPosition = self.effects.index(effect) if oldPosition == newPosition: #the effect is already in the proper position return # The effect currently at the position we want to move the given effect to newPositionEffect = self.effects[newPosition] previousConvert = None for pad in effect.sink_pads(): if pad.is_linked(): previousConvert = pad.get_peer().get_parent() break nextConvert = None for pad in effect.src_pads(): if pad.is_linked(): nextConvert = pad.get_peer().get_parent() break # The src pad on the last element in the bin endSrcPad = self.effectsBinSrc.get_target() # check the state and block if we have to state = self.playbackbin.get_state(0)[1] if state == gst.STATE_PAUSED or state == gst.STATE_PLAYING: endSrcPad.set_blocked(True) #here's where we unlink everything previousConvert.unlink(effect) effect.unlink(nextConvert) if oldPosition > newPosition: newPositionPreviousConvert = None for pad in newPositionEffect.sink_pads(): if pad.is_linked(): newPositionPreviousConvert = pad.get_peer().get_parent() break newPositionPreviousConvert.unlink(newPositionEffect) previousConvertSink = previousConvert.get_pad("sink") # the "src" pad on the end of the chain of events that is being shifted over chainEndPad = previousConvertSink.get_peer() chainEndPad.unlink(previousConvertSink) #here's where we link everything back together in the new order newPositionPreviousConvert.link(effect) effect.link(previousConvert) previousConvert.link(newPositionEffect) chainEndPad.link(nextConvert.get_pad("sink")) else: newPositionNextConvert = None for pad in newPositionEffect.src_pads(): if pad.is_linked(): newPositionNextConvert = pad.get_peer().get_parent() break newPositionEffect.unlink(newPositionNextConvert) nextConvertSrc = nextConvert.get_pad("src") chainBeginningPad = nextConvertSrc.get_peer() nextConvertSrc.unlink(chainBeginningPad) previousConvert.get_pad("src").link(chainBeginningPad) newPositionEffect.link(nextConvert) nextConvert.link(effect) effect.link(newPositionNextConvert) # remove and insert to our own llst so it matches the changes just made del self.effects[oldPosition] self.effects.insert(newPosition, effect) #give it a lambda for a callback that does nothing, so we don't have to wait endSrcPad.set_blocked_async(False, lambda x,y: False) self.StateChanged("effects") #_____________________________________________________________________