You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

242 lines
7.4 KiB

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2014-2015 Muges
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import pygame
pygame.mixer.init(frequency=48000)
import os.path
import json
from mutagen.oggvorbis import OggVorbis
class Volume:
"""
Abstract class, used to represent a named object whose volume can
be changed
"""
def __init__(self, name, volume=0):
"""
Set the volume (an integer between 0 and 100) and the name that will be
used in the user interface
"""
self.volume = volume
self.name = name
def get_volume(self):
"""
Return the volume (an integer between 0 and 100)
"""
return self.volume
def _set_volume(self):
"""
Method that should be implemented by the subclasses, which
actually sets the volume (for example set the volume of a pygame
sound)
"""
raise NotImplementedError()
def set_volume(self, volume):
"""
Set the volume
"""
self.volume = min(max(0, int(volume)), 100)
self._set_volume()
def inc_volume(self, step):
"""
Increment the volume (or decrement it, the step may be
negative)
"""
self.set_volume(self.volume+step)
class Sound(Volume):
"""
Sound object, the sound is extracted from an ogg file, and is
played with pygame.
"""
def __init__(self, filename, mastervolume):
"""
Create a volume object from an ogg file. mastervolume is a
reference to a MasterVolume object that will control this
sound.
"""
self.filename = filename
# Read the title in the ogg vorbis tags
tags = OggVorbis(filename)
try:
name = tags["title"][0]
except KeyError:
basename = os.path.basename(filename)
name, ext = os.path.splitext(basename)
Volume.__init__(self, name)
try:
self.index = int(tags["tracknumber"][0])
except KeyError:
self.index = 0
# Link with the MasterVolume object
self.mastervolume = mastervolume
# The pygame.mixer.Sound object (only loaded when necessary)
self.sound = None
def __cmp__(self, other):
"""
Comparison function (used to sort lexicograohically by
(index, name)
"""
if self.index == other.index:
return cmp(self.name, other.name)
else:
return cmp(self.index, other.index)
def _set_volume(self):
"""
Set the volume of the pygame.mixer.Sound object (this method
should not be called directly, it will be called by the
set_volume method.
"""
if self.sound == None:
if self.get_volume() > 0:
# Load the sound and play it
self.sound = pygame.mixer.Sound(self.filename)
self.sound.set_volume((self.mastervolume.get_volume()*self.get_volume())/10000.)
self.sound.play(-1, 0, 2000)
else:
# Set the volume
self.sound.set_volume((self.mastervolume.get_volume()*self.get_volume())/10000.)
class Preset:
"""
Stores volumes for each track
"""
def __init__(self, master, filename):
"""
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.master = master
self.filename = filename
self.volumes = {}
def apply(self):
"""
Apply the preset
"""
for sound in self.master.get_sounds():
if sound.name in self.volumes:
sound.set_volume(self.volumes[sound.name])
else:
sound.set_volume(0)
def save(self):
"""
Save the current settings (from the MasterVolume) to the
preset (without writing it)
"""
for sound in self.master.get_sounds():
volume = sound.get_volume()
if volume == 0:
if sound.name in self.volumes:
self.volumes.pop(sound.name)
else:
self.volumes[sound.name] = volume
def read(self):
"""
Read the preset from the file
"""
# TODO : handle errors
with open(self.filename, "r") as f:
self.volumes = json.load(f)
def write(self):
"""
Write the preset to the file
"""
# TODO : handle errors
if not os.path.exists(os.path.dirname(self.filename)):
os.makedirs(os.path.dirname(self.filename))
with open(self.filename, "w") as f:
json.dump(self.volumes, f)
class MasterVolume(Volume):
"""
Volume object used to control each track's volume
"""
# Configuration directories
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, "Master", 100)
# Get the sounds
self.sounds = []
for sounddir in self.sounddirs:
if os.path.isdir(sounddir):
for filename in os.listdir(sounddir):
if os.path.splitext(filename)[1] == ".ogg":
self.sounds.append(Sound(os.path.join(sounddir, filename), self))
self.sounds.sort(key=lambda sound: sound.name.lower())
pygame.mixer.set_num_channels(len(self.sounds))
# 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):
"""
Returns the list of available tracks
"""
return self.sounds
def get_sound(self, i):
"""
Returns the i-th available track
"""
return self.sounds[i]
def _set_volume(self):
"""
Update the volume of all the sounds
"""
for sound in self.sounds:
sound._set_volume()