diff --git a/ambientsounds.py b/ambientsounds.py index 134027c..46227e1 100755 --- a/ambientsounds.py +++ b/ambientsounds.py @@ -26,14 +26,17 @@ import traceback from ui import UI from sounds import MasterVolume -ui = UI() +if __name__ == "__main__": + ui = UI() -try: - ui.start() + try: + ui.start() - master = MasterVolume() + master = MasterVolume() - ui.run(master) -except: - ui.end() - traceback.print_exc() + ui.run(master) + except SystemExit: + pass + except: + ui.end() + traceback.print_exc() diff --git a/readme.md b/readme.md index f4e1e17..08196b3 100644 --- a/readme.md +++ b/readme.md @@ -11,10 +11,11 @@ The program depends on : - [pygame](http://www.pygame.org/news.html) to play the tracks - [mutagen](https://bitbucket.org/lazka/mutagen) to read the ogg vorbis tags -## Manual +## Shortcuts -- `Up`/`Down` to select a track or a preset -- `Left`/`Right` to change the volume of the selected track +- `Up` and `Down` to select an item +- `Left` and `Right` to change the volume of the selected track +- `s` to save the current settings - `q` to quit ## Sounds diff --git a/sounds.py b/sounds.py index 8a446ad..17792a4 100644 --- a/sounds.py +++ b/sounds.py @@ -108,7 +108,7 @@ class Sound(Volume): if self.sound == None: # Load the sound and play it self.sound = pygame.mixer.Sound(self.filename) - self.sound.play(-1) + self.sound.play(-1, 0, 2000) # Set the volume self.sound.set_volume((self.mastervolume.get_volume()*self.get_volume())/10000.) @@ -117,17 +117,16 @@ class Preset: """ Stores volumes for each track """ - def __init__(self, filename, master): + def __init__(self, master, filename): """ - Initialise (without reading or creating it) a preset that + Initialize (without reading or creating it) a preset that will be stored in the file `filename`. master is a reference to a mastervolume object. """ - self.filename = filename - self.name = os.path.splitext(os.path.basename(filename))[0] self.master = master + self.filename = filename self.volumes = {} - + def apply(self): """ Apply the preset @@ -146,7 +145,8 @@ class Preset: for sound in self.master.get_sounds(): volume = sound.get_volume() if volume == 0: - self.volumes.pop(sound.name) + if self.volumes.has_key(sound.name): + self.volumes.pop(sound.name) else: self.volumes[sound.name] = volume @@ -172,30 +172,33 @@ class MasterVolume(Volume): """ # Configuration directories - confdirs = [os.path.dirname(os.path.realpath(__file__)), - "/usr/share/ambientsounds/", - os.path.expanduser("~/.config/ambientsounds/")] + sounddirs = [os.path.join(os.path.dirname(os.path.realpath(__file__)), "sounds"), + "/usr/share/ambientsounds/sounds", + os.path.expanduser("~/.config/ambientsounds/sounds")] def __init__(self): Volume.__init__(self, "Volume", 100) # Get the sounds self.sounds = [] - for dirname in self.confdirs: - sounddir = os.path.join(dirname, "sounds") + for sounddir in self.sounddirs: if os.path.isdir(sounddir): for filename in os.listdir(sounddir): self.sounds.append(Sound(os.path.join(sounddir, filename), self)) pygame.mixer.set_num_channels(len(self.sounds)) - # Get the presets - self.presets = [] - for dirname in self.confdirs: - presetdir = os.path.join(dirname, "presets") - if os.path.isdir(presetdir): - for filename in os.listdir(presetdir): - self.presets.append(Preset(os.path.join(presetdir, filename), self)) + # Get the preset + self.presetpath = os.path.expanduser("~/.config/ambientsounds/preset.json") + if os.path.isfile(self.presetpath): + preset = Preset(self, self.presetpath) + preset.read() + preset.apply() + + def save_preset(self): + preset = Preset(self, self.presetpath) + preset.save() + preset.write() def get_sounds(self): """ @@ -209,12 +212,6 @@ class MasterVolume(Volume): """ return self.sounds[i] - def get_presets(self): - """ - Returns the list of available presets - """ - return self.presets - def _set_volume(self): """ Update the volume of all the sounds diff --git a/ui.py b/ui.py index 5af0722..d985cd8 100644 --- a/ui.py +++ b/ui.py @@ -24,6 +24,7 @@ # SOFTWARE. import curses +import sys class OneLineWidget: """ @@ -48,12 +49,12 @@ class OneLineWidget: """ raise NotImplementedError() - def on_key(self, c): + def on_key(self, c, ui): """ Method called when the user types the key c and the widget is selected. """ - return + return False class VolumeWidget(OneLineWidget): """ @@ -95,14 +96,17 @@ class VolumeWidget(OneLineWidget): self.parent.addstr(y, slidex, "#"*slidewleft) self.parent.addstr(y, slidex+slidewleft, "-"*slidewright) - def on_key(self, c): + def on_key(self, c, ui): if c == curses.KEY_LEFT: # Increase the volume self.volume.inc_volume(-1) elif c == curses.KEY_RIGHT: # Decrease the volume self.volume.inc_volume(1) - + else: + return False + return True + class ScrollableList: """ Object representing a list of OneLineWidgets that can be browsed and @@ -125,7 +129,7 @@ class ScrollableList: def get_selection(self): try: return self.widgets[self.selection] - except KeyError: + except IndexError: return None def set_selection(self, selection): @@ -190,7 +194,7 @@ class ScrollableList: width = sright-sleft self.pad.clear() - self.pad.resize(self.height, width) + self.pad.resize(max(1, self.height), max(1, width)) # Draw each widget y = 0 @@ -202,7 +206,7 @@ class ScrollableList: ptop = max(0, min(self.selection - height/2, self.height-height-1)) self.pad.refresh(ptop, 0, stop, sleft, sbottom, sright) - def on_key(self, c): + def on_key(self, c, ui): if c == curses.KEY_DOWN: self.select_next_widget() elif c == curses.KEY_UP: @@ -210,7 +214,10 @@ class ScrollableList: else: selection = self.get_selection() if selection != None: - selection.on_key(c) + return selection.on_key(c, ui) + else: + return False + return True class VolumeList(ScrollableList): """ @@ -218,7 +225,8 @@ class VolumeList(ScrollableList): """ def __init__(self, mastervolume): ScrollableList.__init__(self) - + self.mastervolume = mastervolume + sounds = mastervolume.get_sounds() namesw = max(max([len(s.name) for s in sounds]), len(mastervolume.name)) @@ -229,6 +237,52 @@ class VolumeList(ScrollableList): widgets.append(VolumeWidget(self.pad, sound, namesw)) self.set_widgets(widgets, 2) + + def on_key(self, c, ui): + if not ScrollableList.on_key(self, c, ui): + if c == ord("s"): + self.mastervolume.save_preset() + else: + return False + return True + +class MessageView: + """ + Display a message at the center of the screen + """ + def __init__(self, message): + """ + Initialise the MessageView + + - message is a string + """ + self.message = message.split("\n") + self.pad = curses.newpad(1,1) + + def draw(self, stop, sleft, sbottom, sright): + """ + Draw the message in the portion of the screen delimited by the + coordinates (stop, sleft, sbottom, sright) + """ + height = sbottom-stop + width = sright-sleft + + self.pad.clear() + self.pad.resize(height, width) + + messageheight = len(self.message) + + y = (height-messageheight)/2 + for text in self.message: + x = (width-len(text))/2 + self.pad.addstr(y, x, text) + y += 1 + + self.pad.refresh(0, 0, stop, sleft, sbottom, sright) + +class LoadingView(MessageView): + def __init__(self): + MessageView.__init__(self, "Loading sounds...") class UI: def start(self): @@ -244,12 +298,10 @@ class UI: self.resize() - # Display loading text - text = "Loading sounds..." - x = (self.screenw-len(text))/2 - y = self.screenh/2 - self.screen.addstr(y, x, text) - self.screen.refresh() + self.loadingview = LoadingView() + self.current = self.loadingview + + self.update() def end(self): """ @@ -282,36 +334,43 @@ class UI: """ Update the screen """ + self.screen.clear() self.screen.refresh() + self.current.draw(self.vpadding, self.hpadding, + self.screenh-self.vpadding-1, + self.screenw-self.hpadding-1) - self.volumelist.draw(self.vpadding, self.hpadding, - self.screenh-self.vpadding-1, - self.screenw-self.hpadding-1) - def run(self, mastervolume): """ Start the main loop """ self.volumelist = VolumeList(mastervolume) + self.current = self.volumelist + self.resize() self.update() while True: # Wait for user input and handle it - c = self.screen.getch() - if c == ord('q'): - # Quit - self.end() - break - elif c == curses.KEY_RESIZE: - # The terminal has been resized, update the display - self.resize() - self.update() - else: - # Propagate the key to the VolumeList - self.volumelist.on_key(c) - + self.on_key(self.screen.getch(), self) self.update() + + def on_key(self, c, ui): + """ + Callback called when a key is pressed + """ + if c == ord('q'): + # Quit + self.end() + sys.exit(0) + elif c == curses.KEY_HOME: + self.current = self.volumelist + elif c == curses.KEY_RESIZE: + # The terminal has been resized, update the display + self.resize() + else: + # Propagate the key to the current view + self.current.on_key(c, ui)