~ubuntu-branches/ubuntu/quantal/jokosher/quantal

« back to all changes in this revision

Viewing changes to Jokosher/Project.py

  • Committer: Bazaar Package Importer
  • Author(s): Luca Falavigna, Luca Falavigna, Piotr Ożarowski
  • Date: 2009-05-12 00:37:15 UTC
  • mfrom: (1.3.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20090512003715-3hp2ycoqjlzwfnlv
Tags: 0.11.2-1
[ Luca Falavigna ]
* New upstream release (Closes: #517234).
  - Jokosher now appears under Sound & Video (Closes: #443788).
* New Maintainer (Closes: #523167).
* Add Python Applications Packaging Team to Uploaders.
* Add Vcs-* fields in source stanza.
* Adjust copyright informations:
  - Refresh upstream authors and copyright holders.
  - Link to /usr/share/common-licenses/GPL-2.
  - Adjust copyright holders for Debian packaging.
  - Replace (c) with ©.
* Apply changes from Ubuntu by Daniel Holbach (thanks!):
  - Drop scrollkeeper from Build-Depends.
  - Drop useless dependencies: python-alsaaudio, gstreamer0.10-gnomevfs,
    librsvg2-common, python-gnome2.
  - Bump gstreamer0.10-plugins-good requirement to >= 0.10.9.
  - Drop debian/jokosher.sh, useless.
  - Provide debian/watch file.
* Switch to debhelper 7.
* Install Jokosher module in private directory.
* Unpack egg files to let python-support handle them.
* Drop python-dev from Build-Depends, use python (>= 2.4) instead.
* Depend on python-gobject.
* Switch dependency from python-setuptools to python-pkg-resources
  because of package rename (Closes: #468728).
* debian/patches/10_update_mime_database.dpatch:
  - Refresh for new upstream release.
* debian/patches/20_LevelList_IOError.dpatch:
  - Fix IOError exception trying to add an audio file to a project.
* debian/patches/30_desktop_file.dpatch:
  - Adhere to Freedesktop.org standards by removing deprecated entries.
* debian/patches/50_CreateNewProject_return.dpatch:
  - Return class while creating a new project.
* Provide a simple man page for jokosher.
* Bump Standards-Version to 3.8.1:
  - Provide Homepage field in source stanza.
  - Provide debian/README.source to document dpatch usage.

[ Piotr Ożarowski ]
* Add 40_load_extensions_from_unpacked_eggs patch so that extensions in
  unzipped Eggs are recognized as well

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
import pygst
15
15
pygst.require("0.10")
16
16
import gst
17
 
import os
 
17
import gobject
 
18
import os, os.path
18
19
import gzip
19
20
import re
20
21
 
21
22
import TransportManager
22
 
import UndoSystem
 
23
import UndoSystem, IncrementalSave
23
24
import Globals
24
25
import xml.dom.minidom as xml
25
26
import Instrument, Event
26
 
from Monitored import Monitored
27
27
import Utils
28
 
import AlsaDevices
 
28
import AudioBackend
 
29
import ProjectManager
 
30
import PlatformUtils
29
31
 
30
32
#=========================================================================
31
33
 
32
 
class Project(Monitored):
 
34
class Project(gobject.GObject):
33
35
        """
34
36
        This class maintains all of the information required about single Project. It also
35
37
        saves and loads Project files.
36
38
        """
37
39
        
38
40
        """ The Project structure version. Will be useful for handling old save files. """
39
 
        Globals.VERSION = "0.9"
 
41
        Globals.VERSION = "0.11.1"
40
42
        
41
43
        """ The audio playback state enum values """
42
44
        AUDIO_STOPPED, AUDIO_RECORDING, AUDIO_PLAYING, AUDIO_PAUSED, AUDIO_EXPORTING = range(5)
 
45
        
 
46
        """ String constants for incremental save """
 
47
        INCREMENTAL_SAVE_EXT = ".incremental"
 
48
        INCREMENTAL_SAVE_DELIMITER = "\n<<delimiter>>\n"
 
49
        
 
50
        """
 
51
        Signals:
 
52
                "audio-state" -- The status of the audio system has changed. See below:
 
53
                        "audio-state::play" -- The audio started playing.
 
54
                        "audio-state::pause" -- The audio is paused.
 
55
                        "audio-state::record" -- The audio started recording.
 
56
                        "audio-state::stop" -- The playback or recording was stopped.
 
57
                        "audio-state::export-start" -- The audio is being played to a file.
 
58
                        "audio-state::export-stop" -- The export to a file has completed.
 
59
                "bpm" -- The beats per minute value was changed.
 
60
                "click-track" -- The volume of the click track changed.
 
61
                "gst-bus-error" -- An error message was posted to the pipeline. Two strings are also send with the error details.
 
62
                "incremental-save" -- An action was logged to the .incremental file.
 
63
                "instrument" -- The instruments for this project have changed. The instrument instance will be passed as a parameter. See below:
 
64
                        "instrument::added" -- An instrument was added to this project.
 
65
                        "instrument::removed" -- An instrument was removed from this project.
 
66
                        "instrument::reordered" -- The order of the instruments for this project changed.
 
67
                "time-signature" -- The time signature values were changed.
 
68
                "undo" -- The undo or redo stacks for this project have been changed.
 
69
                "view-start" -- The starting position of the view of this project's timeline has changed.
 
70
                "volume" -- This master volume value for this project has changed.
 
71
                "zoom" -- The zoom level of this project's timeline has changed.
 
72
        """
 
73
        
 
74
        __gsignals__ = {
 
75
                "audio-state"           : ( gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_DETAILED, gobject.TYPE_NONE, () ),
 
76
                "bpm"                   : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
77
                "click-track"           : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_DOUBLE,) ),
 
78
                "gst-bus-error" : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_STRING) ),
 
79
                "incremental-save" : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
80
                "instrument"            : ( gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_DETAILED, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ),
 
81
                "time-signature"        : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
82
                "undo"                  : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
83
                "view-start"            : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
84
                "volume"                        : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ),
 
85
                "zoom"                  : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () )
 
86
        }
43
87
 
44
88
        #_____________________________________________________________________
45
89
 
47
91
                """
48
92
                Creates a new instance of Project with default values.
49
93
                """
50
 
                Monitored.__init__(self)
 
94
                gobject.GObject.__init__(self)
51
95
                
52
 
                self.author = ""                        #the author of this project
 
96
                self.author = ""                        #user specified author of this project
53
97
                self.name = ""                          #the name of this project
 
98
                self.notes = ""                         #user specified notes for the project
54
99
                self.projectfile = ""           #the name of the project file, complete with path
 
100
                self.audio_path = ""
 
101
                self.levels_path = ""
55
102
                self.___id_list = []            #the list of IDs that have already been used, to avoid collisions
56
103
                self.instruments = []           #the list of instruments held by this project
57
104
                self.graveyard = []                     # The place where deleted instruments are kept, to later be retrieved by undo functions
58
105
                #used to delete copied audio files if the event that uses them is not saved in the project file
 
106
                #also contains paths to levels_data files corresponding to those audio files
59
107
                self.deleteOnCloseAudioFiles = []       # WARNING: any paths in this list will be deleted on exit!
60
108
                self.clipboardList = []         #The list containing the events to cut/copy
61
109
                self.viewScale = 25.0           #View scale as pixels per second
68
116
                self.meter_nom = 4              # time signature numerator
69
117
                self.meter_denom = 4            # time signature denominator
70
118
                self.clickbpm = 120                     #the number of beats per minute that the click track will play
71
 
                self.clickEnabled = False       #True is the click track is currently enabled
 
119
                self.clickVolumeValue = 0       #The value of the click track volume between 0.0 and 1.0
72
120
                #Keys are instruments which are recording; values are 3-tuples of the event being recorded, the recording bin and bus handler id
73
121
                self.recordingEvents = {}       #Dict containing recording information for each recording instrument
74
122
                self.volume = 1.0                       #The volume setting for the entire project
75
123
                self.level = 0.0                        #The level of the entire project as reported by the gstreamer element
76
124
                self.currentSinkString = None   #to keep track if the sink changes or not
77
125
 
 
126
                self.hasDoneIncrementalSave = False     # True if we have already written to the .incremental file from this project.
 
127
                self.isDoingIncrementalRestore = False # If we are currently restoring incremental save actions
 
128
 
78
129
                # Variables for the undo/redo command system
79
 
                self.unsavedChanges = False             #This boolean is to indicate if something which is not on the undo/redo stack needs to be saved
 
130
                self.__unsavedChanges = False   #This boolean is to indicate if something which is not on the undo/redo stack needs to be saved
80
131
                self.__undoStack = []                   #not yet saved undo commands
81
132
                self.__redoStack = []                   #not yet saved actions that we're undone
82
133
                self.__savedUndoStack = []              #undo commands that have already been saved in the project file
90
141
                self.mainpipeline = gst.Pipeline("timeline")
91
142
                self.playbackbin = gst.Bin("playbackbin")
92
143
                self.adder = gst.element_factory_make("adder")
 
144
                self.postAdderConvert = gst.element_factory_make("audioconvert")
93
145
                self.masterSink = self.MakeProjectSink()
94
146
                
95
147
                self.levelElement = gst.element_factory_make("level", "MasterLevel")
96
148
                self.levelElement.set_property("interval", gst.SECOND / 50)
97
149
                self.levelElement.set_property("message", True)
98
150
                
99
 
                #Restrict adder's output caps due to adder bug
 
151
                #Restrict adder's output caps due to adder bug 341431
100
152
                self.levelElementCaps = gst.element_factory_make("capsfilter", "levelcaps")
101
 
                caps = gst.caps_from_string("audio/x-raw-int,rate=44100,channels=2,width=16,depth=16,signed=(boolean)true")
 
153
                capsString = "audio/x-raw-float,rate=44100,channels=2,width=32,endianness=1234"
 
154
                caps = gst.caps_from_string(capsString)
102
155
                self.levelElementCaps.set_property("caps", caps)
103
156
                
104
157
                # ADD ELEMENTS TO THE PIPELINE AND/OR THEIR BINS #
105
158
                self.mainpipeline.add(self.playbackbin)
106
159
                Globals.debug("added project playback bin to the pipeline")
107
 
                for element in [self.adder, self.levelElementCaps, self.levelElement, self.masterSink]:
 
160
                for element in [self.adder, self.levelElementCaps, self.postAdderConvert, self.levelElement, self.masterSink]:
108
161
                        self.playbackbin.add(element)
109
162
                        Globals.debug("added %s to project playbackbin" % element.get_name())
110
163
 
111
164
                # LINK GSTREAMER ELEMENTS #
112
165
                self.adder.link(self.levelElementCaps)
113
 
                self.levelElementCaps.link(self.levelElement)
 
166
                self.levelElementCaps.link(self.postAdderConvert)
 
167
                self.postAdderConvert.link(self.levelElement)
114
168
                self.levelElement.link(self.masterSink)
115
169
                
116
170
                # CONSTRUCT CLICK TRACK BIN #
145
199
                self.transportMode = TransportManager.TransportManager.MODE_BARS_BEATS
146
200
                self.transport = TransportManager.TransportManager(self.transportMode, self)
147
201
 
148
 
                self.EOShandlers = [] #extension functions needing EOS notifications
149
 
 
150
202
                self.PrepareClick()
151
203
        
152
204
        #_____________________________________________________________________
183
235
                        self.transport.Play(newAudioState)
184
236
                
185
237
                Globals.debug("just set state to PAUSED")
186
 
 
187
 
                Globals.PrintPipelineDebug("Play Pipeline:", self.mainpipeline)
188
238
                
189
239
        #_____________________________________________________________________
190
240
 
206
256
                Globals.debug("current state:", self.mainpipeline.get_state(0)[1].value_name)
207
257
                
208
258
                #If we've been recording then add new events to instruments
209
 
                for instr, (event, bin, handle) in self.recordingEvents.items():
 
259
                for instr, (event, bin, handle) in self.recordingEvents.iteritems():
210
260
                        instr.FinalizeRecording(event)
211
261
                        self.bus.disconnect(handle)
212
262
 
213
263
                self.TerminateRecording()
214
 
 
215
 
                #If this is due to end of stream then notify those interested
216
 
                if bus:
217
 
                        for function in self.EOShandlers:
218
 
                                function()
219
 
                
220
 
                Globals.PrintPipelineDebug("PIPELINE AFTER STOP:", self.mainpipeline)
221
264
                
222
265
        #_____________________________________________________________________
223
266
 
231
274
                Globals.debug("State just set to READY")
232
275
                
233
276
                #Relink instruments and stop their recording bins
234
 
                for instr, (event, bin, handle) in self.recordingEvents.items():
 
277
                for instr, (event, bin, handle) in self.recordingEvents.iteritems():
235
278
                        try:
236
279
                                Globals.debug("Removing recordingEvents bin")
237
280
                                self.mainpipeline.remove(bin)
255
298
                #Add all instruments to the pipeline
256
299
                self.recordingEvents = {}
257
300
                devices = {}
258
 
                for device in AlsaDevices.GetAlsaList("capture").keys():
 
301
                capture_devices = AudioBackend.ListCaptureDevices(probe_name=False)
 
302
                if not capture_devices:
 
303
                        capture_devices = ((None,None),)
 
304
                
 
305
                default_device = capture_devices[0][0]
 
306
                
 
307
                for device, deviceName in capture_devices:
259
308
                        devices[device] = []
260
309
                        for instr in self.instruments:
261
 
                                if instr.isArmed and instr.input == device:
 
310
                                if instr.isArmed and (instr.input == device or device is None):
262
311
                                        instr.RemoveAndUnlinkPlaybackbin()
263
312
                                        devices[device].append(instr)
 
313
                                elif instr.isArmed and instr.input is None:
 
314
                                        instr.RemoveAndUnlinkPlaybackbin()
 
315
                                        devices[default_device].append(instr)
 
316
                
264
317
 
265
 
                for device, recInstruments in devices.items():
 
318
                for device, recInstruments in devices.iteritems():
266
319
                        if len(recInstruments) == 0:
267
320
                                #Nothing to record on this device
268
321
                                continue
269
322
 
270
 
                        channelsNeeded = AlsaDevices.GetChannelsOffered(device)
271
 
 
272
 
                        if channelsNeeded > 1 and not gst.registry_get_default().find_plugin("chansplit"):
273
 
                                Globals.debug("Channel splitting element not found when trying to record from multi-input device.")
274
 
                                raise AudioInputsError(2)
275
 
 
 
323
                        if device is None:
 
324
                                # assume we are using a backend like JACK which does not allow
 
325
                                #us to do device selection.
 
326
                                channelsNeeded = len(recInstruments)
 
327
                        else:
 
328
                                channelsNeeded = AudioBackend.GetChannelsOffered(device)
 
329
 
 
330
                        
276
331
                        if channelsNeeded > 1: #We're recording from a multi-input device
277
 
                                recordingbin = gst.Bin()
278
 
                                src = gst.element_factory_make("alsasrc")
279
 
                                src.set_property("device", device)
 
332
                                recordingbin = gst.Bin("recording bin")
 
333
                                recordString = Globals.settings.recording["audiosrc"]
 
334
                                srcBin = gst.parse_bin_from_description(recordString, True)
 
335
                                try:
 
336
                                        src_element = recordingbin.iterate_sources().next()
 
337
                                except StopIteration:
 
338
                                        pass
 
339
                                else:
 
340
                                        if hasattr(src_element.props, "device"):
 
341
                                                src_element.set_property("device", device)
280
342
                                
 
343
                                caps = gst.caps_from_string("audio/x-raw-int;audio/x-raw-float")
 
344
 
 
345
                                sampleRate = Globals.settings.recording["samplerate"]
 
346
                                try:
 
347
                                        sampleRate = int(sampleRate)
 
348
                                except ValueError:
 
349
                                        sampleRate = 0
 
350
                                # 0 means for "autodetect", or more technically "don't use any rate caps".
 
351
                                if sampleRate > 0:
 
352
                                        for struct in caps:
 
353
                                                struct.set_value("rate", sampleRate)
 
354
 
 
355
                                for struct in caps:
 
356
                                        struct.set_value("channels", channelsNeeded)
 
357
 
 
358
                                Globals.debug("recording with capsfilter:", caps.to_string())
281
359
                                capsfilter = gst.element_factory_make("capsfilter")
282
 
                                capsString = "audio/x-raw-int,rate=%s" % Globals.settings.recording["samplerate"]
283
 
                                caps = gst.caps_from_string(capsString)
284
360
                                capsfilter.set_property("caps", caps)
285
361
                                
286
 
                                split = gst.element_factory_make("chansplit")
287
 
                                
288
 
                                recordingbin.add(src)
289
 
                                recordingbin.add(capsfilter)
290
 
                                recordingbin.add(split)
291
 
                                src.link(capsfilter)
 
362
                                split = gst.element_factory_make("deinterleave")
 
363
                                convert = gst.element_factory_make("audioconvert")
 
364
                                
 
365
                                recordingbin.add(srcBin, split, convert, capsfilter)
 
366
                                
 
367
                                srcBin.link(convert) 
 
368
                                convert.link(capsfilter)
292
369
                                capsfilter.link(split)
293
370
                                
294
371
                                split.connect("pad-added", self.__RecordingPadAddedCb, recInstruments, recordingbin)
300
377
                                event = instr.GetRecordingEvent()
301
378
                                
302
379
                                encodeString = Globals.settings.recording["fileformat"]
303
 
                                capsString = "audio/x-raw-int,rate=%s" % Globals.settings.recording["samplerate"]
304
 
                                pipe = "alsasrc device=%s ! %s ! audioconvert ! level name=recordlevel interval=%d" +\
305
 
                                                        " ! audioconvert ! %s ! filesink location=%s"
306
 
                                pipe %= (device, capsString, event.LEVEL_INTERVAL * gst.SECOND, encodeString, event.file.replace(" ", "\ "))
 
380
                                recordString = Globals.settings.recording["audiosrc"]
 
381
                                
 
382
                                sampleRate = 0
 
383
                                try:
 
384
                                        sampleRate = int( Globals.settings.recording["samplerate"] )
 
385
                                except ValueError:
 
386
                                        pass
 
387
                                # 0 means for "autodetect", or more technically "don't use any caps".
 
388
                                if sampleRate > 0:
 
389
                                        capsString = "audio/x-raw-int,rate=%s ! audioconvert" % sampleRate
 
390
                                else:
 
391
                                        capsString = "audioconvert"
 
392
                                        
 
393
                                # TODO: get rid of this entire string; do it manually
 
394
                                pipe = "%s ! %s ! level name=recordlevel ! audioconvert ! %s ! filesink name=sink"
 
395
                                pipe %= (recordString, capsString, encodeString)
307
396
                                
308
397
                                Globals.debug("Using pipeline: %s" % pipe)
309
398
                                
310
 
                                recordingbin = gst.parse_launch("bin.( %s )" % pipe)
 
399
                                recordingbin = gst.parse_bin_from_description(pipe, False)
 
400
                                
 
401
                                filesink = recordingbin.get_by_name("sink")
 
402
                                level = recordingbin.get_by_name("recordlevel")
 
403
                                
 
404
                                filesink.set_property("location", event.GetAbsFile())
 
405
                                level.set_property("interval", int(event.LEVEL_INTERVAL * gst.SECOND))
 
406
                                
311
407
                                #update the levels in real time
312
408
                                handle = self.bus.connect("message::element", event.recording_bus_level)
313
409
                                
 
410
                                try:
 
411
                                        src_element = recordingbin.iterate_sources().next()
 
412
                                except StopIteration:
 
413
                                        pass
 
414
                                else:
 
415
                                        if hasattr(src_element.props, "device"):
 
416
                                                src_element.set_property("device", device)
 
417
                                
314
418
                                self.recordingEvents[instr] = (event, recordingbin, handle)
315
419
                                
316
420
                                Globals.debug("Recording in single-input mode")
335
439
                                        for mp3: "lame"
336
440
                                        for wav: "wavenc"
337
441
                """
 
442
                #try to create encoder/muxer first, before modifying the main pipeline.
 
443
                try:
 
444
                        self.encodebin = gst.parse_bin_from_description(encodeBin, True)
 
445
                except gobject.GError, e:
 
446
                        if e.code == gst.PARSE_ERROR_NO_SUCH_ELEMENT:
 
447
                                error_no = ProjectManager.ProjectExportException.MISSING_ELEMENT
 
448
                        else:
 
449
                                error_no = ProjectManager.ProjectExportException.INVALID_ENCODE_BIN
 
450
                        raise ProjectManager.ProjectExportException(error_no, e.message)
 
451
 
338
452
                #stop playback because some elements will be removed from the pipeline
339
453
                self.Stop()
340
454
                
341
455
                #remove and unlink the alsasink
342
456
                self.playbackbin.remove(self.masterSink, self.levelElement)
343
 
                self.levelElementCaps.unlink(self.levelElement)
 
457
                self.postAdderConvert.unlink(self.levelElement)
344
458
                self.levelElement.unlink(self.masterSink)
345
459
                
346
460
                #create filesink
347
461
                self.outfile = gst.element_factory_make("filesink", "export_file")
348
462
                self.outfile.set_property("location", filename)
349
463
                self.playbackbin.add(self.outfile)
350
 
                
351
 
                #create encoder/muxer
352
 
                self.encodebin = gst.gst_parse_bin_from_description("audioconvert ! %s" % encodeBin, True)
 
464
 
353
465
                self.playbackbin.add(self.encodebin)
354
 
                self.levelElementCaps.link(self.encodebin)
 
466
                self.postAdderConvert.link(self.encodebin)
355
467
                self.encodebin.link(self.outfile)
356
468
                        
357
469
                #disconnect the bus message handler so the levels don't change
363
475
                self.exportFilename = filename
364
476
                #start the pipeline!
365
477
                self.Play(newAudioState=self.AUDIO_EXPORTING)
366
 
                self.StateChanged("export-start")
 
478
                self.emit("audio-state::export-start")
367
479
 
368
480
        #_____________________________________________________________________
369
481
        
389
501
                
390
502
                #remove the filesink and encoder
391
503
                self.playbackbin.remove(self.outfile, self.encodebin)           
392
 
                self.levelElementCaps.unlink(self.encodebin)
 
504
                self.postAdderConvert.unlink(self.encodebin)
393
505
                        
394
506
                #dispose of the elements
395
507
                self.outfile.set_state(gst.STATE_NULL)
398
510
                
399
511
                #re-add all the alsa playback elements
400
512
                self.playbackbin.add(self.masterSink, self.levelElement)
401
 
                self.levelElementCaps.link(self.levelElement)
 
513
                self.postAdderConvert.link(self.levelElement)
402
514
                self.levelElement.link(self.masterSink)
403
515
                
404
 
                self.StateChanged("export-stop")
 
516
                self.emit("audio-state::export-stop")
405
517
        
406
518
        #_____________________________________________________________________
407
519
        
462
574
                """
463
575
                self.audioState = newState
464
576
                if newState == self.AUDIO_PAUSED:
465
 
                        self.StateChanged("pause")
 
577
                        self.emit("audio-state::pause")
466
578
                elif newState == self.AUDIO_PLAYING:
467
 
                        self.StateChanged("play")
 
579
                        self.emit("audio-state::play")
468
580
                elif newState == self.AUDIO_STOPPED:
469
 
                        self.StateChanged("stop")
 
581
                        self.emit("audio-state::stop")
470
582
                elif newState == self.AUDIO_RECORDING:
471
 
                        self.StateChanged("record")
 
583
                        self.emit("audio-state::record")
472
584
                elif newState == self.AUDIO_EXPORTING:
473
585
                        self.exportPending = False
474
586
                
486
598
                        recInstruments -- list with all Instruments currently recording.
487
599
                        bin -- the bin that stores all the recording elements.
488
600
                """
489
 
                match = re.search("(\d+)$", pad.get_name())
490
 
                if not match:
 
601
                # SRC template: 'src%d'
 
602
                padname = pad.get_name()
 
603
                try:
 
604
                        index = int(padname[3:])
 
605
                except ValueError:
 
606
                        Globals.debug("Cannot start multichannel record: pad name does not match 'src%d':", padname)
491
607
                        return
492
 
                index = int(match.groups()[0])
 
608
 
493
609
                for instr in recInstruments:
494
610
                        if instr.inTrack == index:
495
611
                                event = instr.GetRecordingEvent()
496
612
                                
 
613
                                # TODO: get rid of string concatentation
497
614
                                encodeString = Globals.settings.recording["fileformat"]
498
 
                                pipe = "audioconvert ! level name=eventlevel interval=%d message=true !" +\
499
 
                                                        "audioconvert ! %s ! filesink location=%s"
500
 
                                pipe %= (event.LEVEL_INTERVAL, encodeString, event.file.replace(" ", "\ "))
 
615
                                pipe = "queue ! audioconvert ! level name=recordlevel ! audioconvert ! %s ! filesink name=sink"
 
616
                                pipe %= encodeString
501
617
                                
502
 
                                encodeBin = gst.gst_parse_bin_from_description(pipe, True)
 
618
                                encodeBin = gst.parse_bin_from_description(pipe, True)
503
619
                                bin.add(encodeBin)
504
620
                                pad.link(encodeBin.get_pad("sink"))
505
621
                                
 
622
                                filesink = bin.get_by_name("sink")
 
623
                                level = bin.get_by_name("recordlevel")
 
624
                                
 
625
                                filesink.set_property("location", event.GetAbsFile())
 
626
                                level.set_property("interval", int(event.LEVEL_INTERVAL * gst.SECOND))
 
627
                                
506
628
                                handle = self.bus.connect("message::element", event.recording_bus_level)
507
629
                                
 
630
                                # since we are adding the encodebin to an already playing pipeline, sync up there states
 
631
                                encodeBin.set_state(gst.STATE_PLAYING)
 
632
 
508
633
                                self.recordingEvents[instr] = (event, bin, handle)
 
634
                                Globals.debug("Linked recording channel: instrument (%s), track %d" % (instr.name, instr.inTrack))
 
635
                                break
509
636
 
510
637
        #_____________________________________________________________________
511
638
        
573
700
                error, debug = message.parse_error()
574
701
                
575
702
                Globals.debug("Gstreamer bus error:", str(error), str(debug))
576
 
                self.StateChanged("gst-bus-error", str(error), str(debug))
 
703
                Globals.debug("Domain: %s, Code: %s" % (error.domain, error.code))
 
704
                Globals.debug("Message:", error.message)
 
705
                
 
706
                if error.domain == gst.STREAM_ERROR and Globals.DEBUG_GST:
 
707
                        self.DumpDotFile()
 
708
                
 
709
                self.emit("gst-bus-error", str(error), str(debug))
577
710
 
578
711
        #_____________________________________________________________________
579
712
        
580
 
        def SaveProjectFile(self, path=None):
 
713
        def DumpDotFile(self):
 
714
                basepath, ext = os.path.splitext(self.projectfile)
 
715
                name = "jokosher-pipeline-" + os.path.basename(basepath)
 
716
                gst.DEBUG_BIN_TO_DOT_FILE_WITH_TS(self.mainpipeline, gst.DEBUG_GRAPH_SHOW_ALL, name)
 
717
                Globals.debug("Dumped pipeline to DOT file:", name)
 
718
                Globals.debug("Command to render DOT file: dot -Tsvg -o pipeline.svg <file>")
 
719
        
 
720
        #_____________________________________________________________________
 
721
        
 
722
        def SaveProjectFile(self, path=None, backup=False):
581
723
                """
582
724
                Saves the Project and its children as an XML file
583
725
                to the path specified by file.
588
730
                
589
731
                if not path:
590
732
                        if not self.projectfile:
591
 
                                raise "No save path specified!"
 
733
                                raise Exception("No save path specified!")
592
734
                        path = self.projectfile
 
735
                
 
736
                if not self.audio_path:
 
737
                        self.audio_path = os.path.join(os.path.dirname(path), "audio")
 
738
                if not self.levels_path:
 
739
                        self.levels_path = os.path.join(os.path.dirname(path), "levels")
593
740
                        
 
741
                if os.path.exists(self.audio_path):
 
742
                        if not os.path.isdir(self.audio_path):
 
743
                                raise Exception("Audio save location is not a directory")
 
744
                else:
 
745
                        os.mkdir(self.audio_path)
 
746
                
 
747
                if os.path.exists(self.levels_path):
 
748
                        if not os.path.isdir(self.levels_path):
 
749
                                raise Exception("Levels save location is not a directory")
 
750
                else:
 
751
                        os.mkdir(self.levels_path)
 
752
                
594
753
                if not path.endswith(".jokosher"):
595
754
                        path = path + ".jokosher"
596
755
                        
597
756
                #sync the transport's mode with the one which will be saved
598
757
                self.transportMode = self.transport.mode
599
758
                
600
 
                self.unsavedChanges = False
601
 
                #purge main undo stack so that it will not prompt to save on exit
602
 
                self.__savedUndoStack.extend(self.__undoStack)
603
 
                self.__undoStack = []
604
 
                #purge savedRedoStack so that it will not prompt to save on exit
605
 
                self.__redoStack.extend(self.__savedRedoStack)
606
 
                self.__savedRedoStack = []
 
759
                if not backup:
 
760
                        self.__unsavedChanges = False
 
761
                        #purge main undo stack so that it will not prompt to save on exit
 
762
                        self.__savedUndoStack.extend(self.__undoStack)
 
763
                        self.__undoStack = []
 
764
                        #purge savedRedoStack so that it will not prompt to save on exit
 
765
                        self.__redoStack.extend(self.__savedRedoStack)
 
766
                        self.__savedRedoStack = []
 
767
                        
 
768
                        # delete the incremental file since its all safe on disk now
 
769
                        basepath, ext = os.path.splitext(self.projectfile)
 
770
                        incr_path = basepath + self.INCREMENTAL_SAVE_EXT
 
771
                        try:
 
772
                                if os.path.exists(incr_path):
 
773
                                        os.remove(incr_path)
 
774
                        except OSError:
 
775
                                Globals.debug("Removal of .incremental failed! Next load we will try to restore unrestorable state!")
607
776
                
608
777
                doc = xml.Document()
609
778
                head = doc.createElement("JokosherProject")
614
783
                params = doc.createElement("Parameters")
615
784
                head.appendChild(params)
616
785
                
617
 
                items = ["viewScale", "viewStart", "name", "author", "transportMode", "bpm", "meter_nom", "meter_denom"]
 
786
                items = ["viewScale", "viewStart", "name", "author",
 
787
                         "transportMode", "bpm", "meter_nom", "meter_denom", "projectfile"]
618
788
                
619
789
                Utils.StoreParametersToXML(self, doc, params, items)
 
790
                
 
791
                notesNode = doc.createElement("Notes")
 
792
                head.appendChild(notesNode)
 
793
                
 
794
                # use repr() because XML will not preserve whitespace charaters such as \n and \t.
 
795
                notesNode.setAttribute("text", repr(self.notes))
620
796
                        
621
797
                undo = doc.createElement("Undo")
622
798
                head.appendChild(undo)
647
823
                        os.remove(path + "~")
648
824
                else:
649
825
                        #if the saving doesn't fail, move it to the proper location
 
826
                        if os.path.exists(path):
 
827
                                os.remove(path)
650
828
                        os.rename(path + "~", path)             
651
829
                
652
 
                self.StateChanged("undo")
653
 
        
 
830
                self.emit("undo")
 
831
        
 
832
        #_____________________________________________________________________
 
833
        
 
834
        def SaveIncrementalAction(self, action):
 
835
                if self.isDoingIncrementalRestore:
 
836
                        return
 
837
                
 
838
                if self.__performingUndo or self.__performingRedo:
 
839
                        return
 
840
                
 
841
                path, ext = os.path.splitext(self.projectfile)
 
842
                filename = path + self.INCREMENTAL_SAVE_EXT
 
843
                
 
844
                if self.hasDoneIncrementalSave:
 
845
                        incr_file = open(filename, "a")
 
846
                else:
 
847
                        # if we haven't performed an incremental save yet,
 
848
                        # the existing .incremental file is old, so overwrite it.
 
849
                        incr_file = open(filename, "w")
 
850
                        self.hasDoneIncrementalSave = True
 
851
                
 
852
                incr_file.write(action.StoreToString())
 
853
                incr_file.write(self.INCREMENTAL_SAVE_DELIMITER)
 
854
                
 
855
                incr_file.close()
 
856
                
 
857
                self.SetUnsavedChanges()
 
858
                self.emit("incremental-save")
 
859
        
 
860
        #_____________________________________________________________________
 
861
        
 
862
        def CanDoIncrementalRestore(self):
 
863
                path, ext = os.path.splitext(self.projectfile)
 
864
                filename = path + self.INCREMENTAL_SAVE_EXT
 
865
                return os.path.exists(filename) 
 
866
        
 
867
        #_____________________________________________________________________
 
868
        
 
869
        def DoIncrementalRestore(self):
 
870
                """
 
871
                Loads all the actions from the .incremental file and executes them
 
872
                to restore the project's state.
 
873
                """
 
874
                
 
875
                if self.hasDoneIncrementalSave:
 
876
                        Globals.debug("Cannot do incremental restore after incremental save.")
 
877
                        return False
 
878
                
 
879
                path, ext = os.path.splitext(self.projectfile)
 
880
                filename = path + self.INCREMENTAL_SAVE_EXT
 
881
                
 
882
                save_action_list = []
 
883
                
 
884
                if os.path.isfile(filename):
 
885
                        incr_file = open(filename, "r")
 
886
                        filetext = incr_file.read()
 
887
                        incr_file.close()
 
888
                        for incr_xml in filetext.split(self.INCREMENTAL_SAVE_DELIMITER):
 
889
                                incr_xml = incr_xml.strip()
 
890
                                if incr_xml:
 
891
                                        incr_action = IncrementalSave.LoadFromString(incr_xml)
 
892
                                        save_action_list.append(incr_action)
 
893
                
 
894
                self.isDoingIncrementalRestore = True
 
895
                try:
 
896
                        IncrementalSave.FilterAndExecuteAll(save_action_list, self)
 
897
                except:
 
898
                        Globals.debug("Exception while restoring incremental save.",
 
899
                                                "Project state is surely out of sync with .incremental file")
 
900
                        raise
 
901
                
 
902
                # set hasDoneIncrementSave to True because project is now in sync with .incremental file
 
903
                # i.e. we don't have to destory the .incremental file because the states match up.
 
904
                self.hasDoneIncrementalSave = True
 
905
                self.isDoingIncrementalRestore = False
 
906
                return True
 
907
                
654
908
        #_____________________________________________________________________
655
909
 
656
910
        def CloseProject(self):
657
911
                """
658
912
                Closes down this Project.
659
913
                """
 
914
                
 
915
                # when closing the file, the user chooses to either save, or discard
 
916
                # in either case, we don't need the incremental save file anymore
 
917
                path, ext = os.path.splitext(self.projectfile)
 
918
                filename = path + self.INCREMENTAL_SAVE_EXT
 
919
                try:
 
920
                        if os.path.exists(filename):
 
921
                                os.remove(filename)
 
922
                except OSError:
 
923
                        Globals.debug("Removal of .incremental failed! Next load we will try to restore unrestorable state!")
 
924
                
660
925
                for file in self.deleteOnCloseAudioFiles:
661
926
                        if os.path.exists(file):
662
927
                                Globals.debug("Deleting copied audio file:", file)
663
928
                                os.remove(file)
664
929
                self.deleteOnCloseAudioFiles = []
665
930
                
666
 
                self.ClearListeners()
667
 
                self.transport.ClearListeners()
668
931
                self.mainpipeline.set_state(gst.STATE_NULL)
669
932
                
670
933
        #_____________________________________________________________________
687
950
                        self.__savedUndo = False
688
951
                        
689
952
                self.__performingUndo = False
 
953
                
 
954
                # __performingUndo must be False for project to log
 
955
                inc = IncrementalSave.Undo()
 
956
                self.SaveIncrementalAction(inc)
690
957
        
691
958
        #_____________________________________________________________________
692
959
        
707
974
                        self.ExecuteAction(cmd)
708
975
                        
709
976
                self.__performingRedo = False
 
977
                
 
978
                # __performingRedo must be False for project to log
 
979
                inc = IncrementalSave.Redo()
 
980
                self.SaveIncrementalAction(inc)
710
981
 
711
982
        #_____________________________________________________________________
712
983
        
734
1005
                                self.__savedRedoStack = []
735
1006
                                #since there is no other record that something has 
736
1007
                                #changed after savedRedoStack is purged
737
 
                                self.unsavedChanges = True
738
 
                self.StateChanged("undo")
 
1008
                                self.__unsavedChanges = True
 
1009
                self.emit("undo")
739
1010
        
740
1011
        #_____________________________________________________________________
741
1012
        
755
1026
        
756
1027
        def CheckUnsavedChanges(self):
757
1028
                """
758
 
                Uses boolean self.unsavedChanges and Undo/Redo to 
 
1029
                Uses boolean self.__unsavedChanges and Undo/Redo to 
759
1030
                determine if the program needs to save anything on exit.
760
1031
                
761
1032
                Return:
762
1033
                        True -- there's unsaved changes, undoes or redoes
763
1034
                        False -- the Project can be safely closed.
764
1035
                """
765
 
                return self.unsavedChanges or \
 
1036
                return self.__unsavedChanges or \
766
1037
                        len(self.__undoStack) > 0 or \
767
1038
                        len(self.__savedRedoStack) > 0
768
1039
        
769
1040
        #_____________________________________________________________________
770
1041
        
 
1042
        def SetUnsavedChanges(self):
 
1043
                self.__unsavedChanges = True
 
1044
                self.emit("undo") 
 
1045
                
 
1046
        #_____________________________________________________________________
 
1047
        
771
1048
        def CanPerformUndo(self):
772
1049
                """
773
1050
                Whether it's possible to perform an undo operation.
803
1080
                newUndoAction = self.NewAtomicUndoAction()
804
1081
                for cmdList in reversed(undoAction.GetUndoCommands()):
805
1082
                        obj = cmdList[0]
806
 
                        target_object = None
807
 
                        if obj[0] == "P":               # Check if the object is a Project
808
 
                                target_object = self
809
 
                        elif obj[0] == "I":             # Check if the object is an Instrument
810
 
                                id = int(obj[1:])
811
 
                                target_object = [x for x in self.instruments if x.id==id][0]
812
 
                        elif obj[0] == "E":             # Check if the object is an Event
813
 
                                id = int(obj[1:])
814
 
                                for instr in self.instruments:
815
 
                                        # First of all see if it's alive on an instrument
816
 
                                        n = [x for x in instr.events if x.id==id]
817
 
                                        if not n:
818
 
                                                # If not, check the graveyard on each instrument
819
 
                                                n = [x for x in instr.graveyard if x.id==id]
820
 
                                        if n:
821
 
                                                target_object = n[0]
822
 
                                                break
 
1083
                        target_object = self.JokosherObjectFromString(obj)
823
1084
                        
824
1085
                        getattr(target_object, cmdList[1])(_undoAction_=newUndoAction, *cmdList[2])
825
1086
 
826
1087
        #_____________________________________________________________________
827
1088
        
 
1089
        def JokosherObjectFromString(self, string):
 
1090
                """
 
1091
                Converts a string used to serialize references to Project, Instrument
 
1092
                and Event instances into a reference to the actual object.
 
1093
                
 
1094
                Parameters:
 
1095
                        string -- The string to convert such as "P" for project or "I2" for instrument with ID equal to 2.
 
1096
                """
 
1097
                if string[0] == "P":            # Check if the object is a Project
 
1098
                        return self
 
1099
                elif string[0] == "I":          # Check if the object is an Instrument
 
1100
                        id = int(string[1:])
 
1101
                        for instr in self.instruments:
 
1102
                                if instr.id == id:
 
1103
                                        return instr
 
1104
                elif string[0] == "E":          # Check if the object is an Event
 
1105
                        id = int(string[1:])
 
1106
                        for instr in self.instruments:
 
1107
                                for event in instr.events:
 
1108
                                        if event.id == id:
 
1109
                                                return event
 
1110
                                for event in instr.graveyard:
 
1111
                                        if event.id == id:
 
1112
                                                return event
 
1113
                                
 
1114
        #_____________________________________________________________________
 
1115
        
828
1116
        @UndoSystem.UndoCommand("SetBPM", "temp")
829
1117
        def SetBPM(self, bpm):
830
1118
                """
836
1124
                self.temp = self.bpm
837
1125
                if self.bpm != bpm:
838
1126
                        self.bpm = bpm
839
 
                        self.StateChanged("bpm")
 
1127
                        #FIXME: find a better way to do project.PrepareClick() it doesn't take a really long time with large bpm
 
1128
                        self.PrepareClick()
 
1129
                        self.emit("bpm")
840
1130
        
841
1131
        #_____________________________________________________________________
842
1132
 
862
1152
                if self.meter_nom != nom or self.meter_denom != denom:
863
1153
                        self.meter_nom = nom
864
1154
                        self.meter_denom = denom
865
 
                        self.StateChanged("time-signature")
 
1155
                        self.emit("time-signature")
866
1156
                        
867
1157
        #_____________________________________________________________________
868
1158
        
872
1162
                they are all appended to the undo stack as a single atomic action.
873
1163
                
874
1164
                Parameters:
875
 
                        instrTuples -- a list of tuples containing name, type and pixbuf
 
1165
                        instrTuples -- a list of tuples containing name and type
876
1166
                                        that will be passed to AddInstrument().
877
1167
                        
878
1168
                Returns:
879
 
                        A list of IDs of the added Instruments.
 
1169
                        A list of the added Instruments.
880
1170
                """
881
1171
                
882
1172
                undoAction = self.NewAtomicUndoAction()
883
 
                for name, type, pixbuf, path in instrTuples:
884
 
                        self.AddInstrument(name, type, pixbuf, _undoAction_=undoAction)
 
1173
                instrList = []
 
1174
                for name, type in instrTuples:
 
1175
                        instr = self.AddInstrument(name, type, _undoAction_=undoAction)
 
1176
                        instrList.append(instr)
 
1177
                return instrList
885
1178
        
886
1179
        #_____________________________________________________________________
887
1180
        
902
1195
        #_____________________________________________________________________
903
1196
        
904
1197
        @UndoSystem.UndoCommand("DeleteInstrument", "temp")
905
 
        def AddInstrument(self, name, type, pixbuf):
 
1198
        def AddInstrument(self, name, type):
906
1199
                """
907
1200
                Adds a new instrument to the Project and returns the ID for that instrument.
908
1201
                
913
1206
                Parameters:
914
1207
                        name -- name of the instrument.
915
1208
                        type -- type of the instrument.
916
 
                        pixbuf -- image object corresponding to the instrument.
917
1209
                        
918
1210
                Returns:
919
 
                        ID of the added Instrument.
 
1211
                        The created Instrument object.
920
1212
                """
921
 
                        
 
1213
                pixbuf = Globals.getCachedInstrumentPixbuf(type)
922
1214
                instr = Instrument.Instrument(self, name, type, pixbuf)
923
1215
                if len(self.instruments) == 0:
924
1216
                        #If this is the first instrument, arm it by default
925
1217
                        instr.isArmed = True
926
 
                audio_dir = os.path.join(os.path.split(self.projectfile)[0], "audio")
927
 
                instr.path = os.path.join(audio_dir)
928
1218
                
929
1219
                self.temp = instr.id
930
1220
                self.instruments.append(instr)
931
1221
                
 
1222
                self.emit("instrument::added", instr)
932
1223
                return instr
933
1224
                
934
1225
        #_____________________________________________________________________  
963
1254
                        event.StopGenerateWaveform(False)
964
1255
                        
965
1256
                self.temp = id
 
1257
                self.emit("instrument::removed", instr)
966
1258
        
967
1259
        #_____________________________________________________________________
968
1260
        
990
1282
                instr.isVisible = True
991
1283
                self.graveyard.remove(instr)
992
1284
                self.temp = id
 
1285
                self.emit("instrument::added", instr)
993
1286
                
994
1287
        #_____________________________________________________________________
995
1288
        
1009
1302
                self.temp1 = self.instruments.index(instr)
1010
1303
                
1011
1304
                self.instruments.remove(instr)
1012
 
                self.instruments.insert(position, instr)                
 
1305
                self.instruments.insert(position, instr)
 
1306
                self.emit("instrument::reordered", instr)
1013
1307
        
1014
1308
        #_____________________________________________________________________
1015
1309
        
1025
1319
                """
1026
1320
                if not undoAction:
1027
1321
                        undoAction = self.NewAtomicUndoAction()
1028
 
                
 
1322
        
 
1323
                uris = [PlatformUtils.pathname2url(filename) for filename in fileList]
 
1324
 
1029
1325
                name, type, pixbuf, path = [x for x in Globals.getCachedInstruments() if x[1] == "audiofile"][0]
1030
 
                instr = self.AddInstrument(name, type, pixbuf, _undoAction_=undoAction)
1031
 
                instr.AddEventsFromList(0, fileList, copyFile, undoAction)
 
1326
                instr = self.AddInstrument(name, type, _undoAction_=undoAction)
 
1327
                instr.AddEventsFromList(0, uris, copyFile, undoAction)
1032
1328
        
1033
1329
        #_____________________________________________________________________
1034
1330
        
1067
1363
                start = max(0, min(self.GetProjectLength(), start))
1068
1364
                if self.viewStart != start:
1069
1365
                        self.viewStart = start
1070
 
                        self.StateChanged("view-start")
 
1366
                        self.emit("view-start")
1071
1367
                
1072
1368
        #_____________________________________________________________________
1073
1369
        
1079
1375
                        scale -- view scale in pixels per second.
1080
1376
                """
1081
1377
                self.viewScale = scale
1082
 
                self.StateChanged("zoom")
 
1378
                self.emit("zoom")
1083
1379
                
1084
1380
        #_____________________________________________________________________
1085
1381
 
1108
1404
                        
1109
1405
        #_____________________________________________________________________
1110
1406
        
1111
 
        def GenerateUniqueID(self, id = None):
 
1407
        def GenerateUniqueID(self, id = None,  reserve=True):
1112
1408
                """
1113
1409
                Creates a new unique ID which can be assigned to an new Project object.
1114
1410
                
1115
1411
                Parameters:
1116
1412
                        id -- an unique ID proposal. If it's already taken, a new one is generated.
 
1413
                        reserve -- if True, the ID will be recorded and never returned again.
1117
1414
                        
1118
1415
                Returns:
1119
1416
                        an unique ID suitable for a new Project.
1122
1419
                        if id in self.___id_list:
1123
1420
                                Globals.debug("Error: id", id, "already taken")
1124
1421
                        else:
1125
 
                                self.___id_list.append(id)
 
1422
                                if reserve:
 
1423
                                        self.___id_list.append(id)
1126
1424
                                return id
1127
1425
                                
1128
1426
                counter = 0
1129
1427
                while True:
1130
1428
                        if not counter in self.___id_list:
1131
 
                                self.___id_list.append(counter)
 
1429
                                if reserve:
 
1430
                                        self.___id_list.append(counter)
1132
1431
                                return counter
1133
1432
                        counter += 1
1134
1433
        
1144
1443
                self.volume = volume
1145
1444
                for instr in self.instruments:
1146
1445
                        instr.UpdateVolume()
1147
 
                self.StateChanged("volume")
 
1446
                self.emit("volume")
1148
1447
 
1149
1448
        #_____________________________________________________________________
1150
1449
 
1200
1499
 
1201
1500
        #_____________________________________________________________________
1202
1501
 
1203
 
        def EnableClick(self):
 
1502
        def SetClickTrackVolume(self, value):
1204
1503
                """
1205
1504
                Unmutes and enables the click track.
1206
 
                """
1207
 
        
1208
 
                self.clickTrackVolume.set_property("mute", False)
1209
 
                self.clickEnabled = True
1210
 
 
1211
 
        #_____________________________________________________________________
1212
 
 
1213
 
        def DisableClick(self):
1214
 
                """
1215
 
                Mutes and disables the click track.
1216
 
                """
1217
 
        
1218
 
                self.clickTrackVolume.set_property("mute", True)
1219
 
                self.clickEnabled = False
 
1505
                
 
1506
                Parameters:
 
1507
                        value -- The volume of the click track between 0.0 and 1.0
 
1508
                """
 
1509
                if self.clickVolumeValue != value:
 
1510
                        self.clickTrackVolume.set_property("mute", (value < 0.01))
 
1511
                        # convert the 0.0 to 1.0 range to 0.0 to 2.0 range (to let the user make it twice as loud)
 
1512
                        self.clickTrackVolume.set_property("volume", value * 2)
 
1513
                        self.clickVolumeValue = value
 
1514
                        self.emit("click-track", value)
1220
1515
 
1221
1516
        #_____________________________________________________________________
1222
1517
 
1266
1561
                        return self.masterSink
1267
1562
                
1268
1563
                self.currentSinkString = sinkString
1269
 
                sinkElement = None
1270
 
                
1271
 
                if sinkString == "alsasink":
1272
 
                        sinkElement = gst.element_factory_make("alsasink")
1273
 
                        #Set the alsa device for audio output
1274
 
                        outdevice = Globals.settings.playback["devicecardnum"]
1275
 
                        if outdevice == "default":
1276
 
                                try:
1277
 
                                        # Select first output device as default to avoid a GStreamer bug which causes
1278
 
                                        # large amounts of latency with the ALSA 'default' device.
1279
 
                                        outdevice = AlsaDevices.GetAlsaList("playback").keys()[1]
1280
 
                                except:
1281
 
                                        pass
1282
 
                        Globals.debug("Output device: %s" % outdevice)
1283
 
                        sinkElement.set_property("device", outdevice)
1284
 
                        Globals.debug("Using alsasink for audio output")
1285
 
                
1286
 
                elif sinkString != "autoaudiosink":
1287
 
                        try:
1288
 
                                sinkElement = gst.gst_parse_bin_from_description(sinkString, True)
1289
 
                        except gobject.GError:
1290
 
                                Globals.debug("Parsing failed: %s" % sinkString)
1291
 
                        else:
1292
 
                                Globals.debug("Using custom pipeline for audio sink: %s" % sinkString)
1293
 
                
1294
 
                if not sinkElement:
1295
 
                        # if a sink element has not yet been created, autoaudiosink is our last resort
1296
 
                        sinkElement = gst.element_factory_make("autoaudiosink")
 
1564
                sinkBin = None
 
1565
                
 
1566
                try:
 
1567
                        sinkBin = gst.parse_bin_from_description(sinkString, True)
 
1568
                except gobject.GError:
 
1569
                        Globals.debug("Parsing failed: %s" % sinkString)
 
1570
                        # autoaudiosink is our last resort
 
1571
                        sinkBin = gst.element_factory_make("autoaudiosink")
1297
1572
                        Globals.debug("Using autoaudiosink for audio output")
 
1573
                else:
 
1574
                        Globals.debug("Using custom pipeline for audio sink: %s" % sinkString)
1298
1575
                        
1299
 
                return sinkElement
1300
 
        
1301
 
        #_____________________________________________________________________
1302
 
        
1303
 
        def AddEndOfStreamHandler(self, function):
1304
 
                """
1305
 
                Adds a function to the list of functions that need notification of 
1306
 
                end-of-stream messages from gstreamer
1307
 
                
1308
 
                Parameters:
1309
 
                        function -- the function to be added
1310
 
                """
1311
 
                self.EOShandlers.append(function)
1312
 
                
1313
 
        #_____________________________________________________________________
1314
 
        
1315
 
        def RemoveEndOfStreamHandler(self, function):
1316
 
                """
1317
 
                Removes a function from the list of functions that need notification of 
1318
 
                end-of-stream messages from gstreamer
1319
 
                
1320
 
                Parameters:
1321
 
                        function -- the function to be removed
1322
 
                """
1323
 
                self.EOShandlers.remove(function)
1324
 
                
1325
 
        #____________________________________________________________________   
 
1576
                        sinkElement = sinkBin.sinks().next()
 
1577
                        if hasattr(sinkElement.props, "device"):
 
1578
                                outdevice = Globals.settings.playback["device"]
 
1579
                                Globals.debug("Output device: %s" % outdevice)
 
1580
                                sinkElement.set_property("device", outdevice)
 
1581
                
 
1582
                return sinkBin
 
1583
                
 
1584
        #____________________________________________________________________   
 
1585
        
 
1586
        def OnCaptureBackendChange(self):
 
1587
                for instr in self.instruments:
 
1588
                        instr.input = None
 
1589
                        instr.inTrack = -1
 
1590
        
 
1591
        #____________________________________________________________________   
 
1592
        
1326
1593
        def GetInputFilenames(self):
1327
1594
                """
1328
1595
                Obtains a list of  all filenames that are to be input to
1334
1601
                fileList = []
1335
1602
                for instrument in self.instruments:
1336
1603
                        for event in instrument.events:
1337
 
                                fileList.append(event.file)
 
1604
                                fileList.append(event.GetAbsFile())
1338
1605
                return fileList
1339
1606
                
1340
1607
        #____________________________________________________________________   
 
1608
        
 
1609
        def GetLocalAudioFilenames(self):
 
1610
                fileList = []
 
1611
                for instrument in self.instruments:
 
1612
                        for event in instrument.events:
 
1613
                                if not os.path.isabs(event.file):
 
1614
                                        fileList.append(event.file)
 
1615
                return fileList
 
1616
        
 
1617
        #____________________________________________________________________   
 
1618
        
 
1619
        def GetLevelsFilenames(self):
 
1620
                fileList = []
 
1621
                for instrument in self.instruments:
 
1622
                        for event in instrument.events:
 
1623
                                fileList.append(event.levels_file)
 
1624
                return fileList
 
1625
        
 
1626
        #____________________________________________________________________   
 
1627
        
1341
1628
 
1342
 
        
 
1629
        def SetName(self, name):
 
1630
                if self.name != name:
 
1631
                        self.name = name
 
1632
                        inc = IncrementalSave.SetName(name)
 
1633
                        self.SaveIncrementalAction(inc)
 
1634
                                
 
1635
        #____________________________________________________________________   
 
1636
        
 
1637
        def SetAuthor(self, author):
 
1638
                if self.author != author:
 
1639
                        self.author = author
 
1640
                        inc = IncrementalSave.SetAuthor(author)
 
1641
                        self.SaveIncrementalAction(inc)
 
1642
        
 
1643
        #____________________________________________________________________   
 
1644
        
 
1645
        def SetNotes(self, notes):
 
1646
                if self.notes != notes:
 
1647
                        self.notes = notes
 
1648
                        inc = IncrementalSave.SetNotes(notes)
 
1649
                        self.SaveIncrementalAction(inc)
 
1650
        
 
1651
        #____________________________________________________________________           
1343
1652
#=========================================================================