Adding media-sound/boodler - a super fun soundscape tool

pull/4/head
audiodef 6 years ago
parent 06be5e27dc
commit 8448beeb62

@ -0,0 +1,5 @@
AUX boodle-ui-qt.py 12114 SHA256 b2a0ea4dc175e7e175671bfca48bafe44a0bd37801babcd7e23e492ad34130a4 SHA512 c2770590a888c8e6183bd173ed484bf487e678679d60e2b8de3335cdc78e04eae9e381f831c6c66be6f3b7ceb4002cd46d4f4598d55f3f76bc686a54487610cc WHIRLPOOL 9aade76ecaccebe4146851e92015388348d0fdc0c467932e142cefb08b01706784d0d72857889a69418f90547328e184a415473a44ef894d6531c17abf8d7333
AUX boodler 2156 SHA256 b2f5fbc14dc152e78fdaca0b23020a8df03956c1987ce379df8cf082937177a1 SHA512 88b4e9ae3f916c02ce76a1f7db9ade5b0a24558d3167e16de051f8e1e3cf734b3845b30b9ac26baa231d34b2a0e73c28ce613f11ae14f606495e6619e489f499 WHIRLPOOL 9466af082362125d5adf69143e964cba1bebe608bc3ed668d7da84d51f5e580588141126a53561d43ac0cd9f650e8c7cbbf38a738229a00a5549a830ca687c98
DIST Boodler-2.0.4.tar.gz 310801 SHA256 430ca92103203442b1f4c5067ea7f6dc0dbced534bf02a28ed7a8e358aaa55f2 SHA512 d48eb55e552f157e5ec7c456a17f70ec81a7b30fdbe7c7097163c3ee9ae33d726011aecfaecc8c47de8c358eb3b0f46dc0d94fcbe56efa4225e8a2e94e485215 WHIRLPOOL 467dfd1ae8d4d845b5c853cdea5f892c09e7d80c3fb4a571dc5faa509e47586451fb22ed7446537c9363827c89f28343a3831a8f9f02851e4289cc292b1aca5f
EBUILD boodler-2.0.4-r1.ebuild 3191 SHA256 3dc47b0043868b78a1c54259c99b24748de6d58994f163a7ce74970ce75f610f SHA512 7afc523a2aac265254d115eab4886c3e163d5c58dba10e847afe7852f72e5dea9d003411b32a50612d4b319b1e4e161831cd847bc56e2116f7e60cf4a19bf8c2 WHIRLPOOL 43ae7da35a550e0952819a84ecec5a6ea3cc29d85583c2e8db233798c54af5e0c37c6ebde62c473b34fd5b5b1db79233e50a743f961061c0944d6406430c2721
MISC metadata.xml 457 SHA256 dde8c156d6ec0e6f3dbe3625c2e905d72d736e68f2a86160bd1d84eb5f90b72a SHA512 023ae1900518cb71d67045a02fa33e8b66d4ef1de2ebd842919cb3299baf84aad54d8f47ca865351b65ee484e21a2ad90a789b659275d634e5c15f30d24ce3b5 WHIRLPOOL 8343875e6be7e0f54a02cd4cf7dea695922757158bb1540e56411be392db8d9c1a78101ec2c97880d711477a133a5a7666c9e6ee28cbf13aed9d5559210f237b

@ -0,0 +1,111 @@
# Copyright 1999-2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
EAPI=6
PYTHON_COMPAT=( python{2_5,2_6,2_7} )
inherit distutils-r1 bash-completion-r1
DESCRIPTION="Tool for creating soundscapes -- continuous, infinitely varying streams of sound"
HOMEPAGE="http://boodler.org/"
RESTRICT="mirror"
SRC_URI="http://boodler.org/dl/Boodler-${PV}.tar.gz"
LICENSE="LGPL-2 GPL-2 public-domain"
SLOT="0"
KEYWORDS="~amd64 ~x86"
IUSE="alsa bash-completion coreaudio doc intmath jack lame qt4 shout vorbis"
REQUIRED_USE="shout? ( vorbis )"
RDEPEND="alsa? ( >=media-libs/alsa-lib-1.0.17a )
jack? ( >=media-libs/bio2jack-0.9 )
lame? ( >=media-sound/lame-3.98.2-r1 )
shout? ( >=media-libs/libshout-2.1 )
vorbis? ( >=media-libs/libvorbis-1.2.1_rc1-r2 )"
DEPEND="${RDEPEND}
qt4? ( >=dev-python/PyQt4-4.7.3[X] )"
S=${WORKDIR}/Boodler-${PV}
HTML_DOCS=( doc/ )
python_prepare_all() {
# fix bash completion script for new file names without extension in 2.0.4
cp "${FILESDIR}/boodler" "${T}"/boodler || die
sed -i -e s/\.py//g "${T}"/boodler || die
distutils-r1_python_prepare_all
}
python_prepare() {
if use qt4 ; then
# copy to tmp dir so it can be modified later
cp "${FILESDIR}"/boodle-ui-qt.py \
"${T}"/${EPYTHON}/boodle-ui-qt || die
# fix up the command name which was changed in boodler-2.0.4
sed -i -e "s/\"boodler.py\"/\"boodler\"/" \
"${T}"/${EPYTHON}/boodle-ui-qt || die
fi
distutils-r1_python_prepare
}
python_configure_all() {
local defdriver
local with
local without
# set up a default audio driver (not daemon) according to USE flags.
# it does not appear to work in setup.cfg so there is a workaround below.
if use coreaudio ; then
defdriver=macosx
elif use alsa ; then
defdriver=alsa
else
defdriver=oss
fi
# this ugly code enables/disables the output drivers
# oss seems to be needed for boodler.py --list-drivers to work
with="${with}oss,"
use alsa && with="${with}alsa," || without="${without}alsa,"
use coreaudio && with="${with}macosx,osxaq," \
|| without="${without}macosx,osxaq,"
without="${without}esd,"
use jack && with="${with}jackb," || without="${without}jackb,"
use lame && with="${with}lame," || without="${without}lame,"
use shout && with="${with}shout," || without="${without}shout,"
use vorbis && with="${with}vorbis," || without="${without}vorbis,"
# move the original setup.cfg out of the way as a backup to check syntax
mv setup.cfg setup.cfg.orig || die
# fill the setup.cfg with the values
cat > setup.cfg <<-EOF
[build_scripts]
default_driver=${defdriver}
[build_ext]
with-drivers=${with}
without-drivers=${without}
intmath=$(use intmath && echo 1 || echo 0)
EOF
mydistutilargs=( --default-driver ${defdriver} )
}
python_install() {
# a pyqt4 gui addon for boodler downloaded from the official site
# http://boodler.org/dl/etc/boodle-ui-qt.py
if use qt4 ; then
python_doscript "${T}"/${EPYTHON}/boodle-ui-qt
fi
distutils-r1_python_install
}
src_install() {
distutils-r1_src_install
# a bash completion addon script downloaded from the official site
# http://boodler.org/dl/etc/bash_completion.d/boodler
dobashcomp "${T}/boodler"
}

@ -0,0 +1,316 @@
#!/usr/bin/python
# Boodler: a programmable soundscape tool
# Designed by Andrew Plotkin <erkyrath@eblong.com>
# For more information, see <http://boodler.org/>
#
# This Python script ("boodle-ui-qt.py") is licensed under the GNU
# Library General Public License (LGPL).
# Author: Tuukka Hastrup <Tuukka.Hastrup@iki.fi>
#
# You should have received a copy of the GNU Library General Public License
# along with this program. (It should be a document entitled "LGPL".)
# If not, see the web URL above, or write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import sys
import subprocess
import os, signal
from boopak import collect # you'll need Boodler on Python's library path
# same as in boodler.py
if 'darwin' in sys.platform.lower():
Default_Relative_Data_Dir = 'Library/Application Support/Boodler'
else:
Default_Relative_Data_Dir = '.boodler'
basedir = None # XXX opts.basedir
if not basedir:
basedir = os.environ.get('BOODLER_DATA')
if not basedir:
basedir = os.path.join(os.environ.get('HOME'), Default_Relative_Data_Dir)
# collect the list of agents
l = collect.PackageCollection(basedir=basedir)
pkgs = l.list_all_current_packages()
agents = []
for pkgname,_vers in sorted(pkgs):
pkg = l.load(pkgname)
for resname in sorted(pkg.resources.keys()):
res = pkg.resources.get(resname)
if res.get_one("boodler.use") == "agent":
agents += [(pkgname, resname)]
def play(agent):
boodler = ["boodler.py", "%s/%s" % agent]
return boodler
def textplay(agent):
print "Playing %s/%s" % agent,
pkg = l.load(agent[0])
res = pkg.resources.get(agent[1])
print '"%s"' % res.get_one("dc.title"), "... ",
sys.stdout.flush()
boodler = subprocess.Popen(play(agent), stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
boodler.communicate()
if boodler.returncode == 0:
print "Done."
else:
print "Boodler failed with exit code %s." % boodler.returncode
except KeyboardInterrupt:
print "\nInterrupted"
os.kill(boodler.pid, signal.SIGTERM)
try:
boodler.wait()
except KeyboardInterrupt:
os.kill(boodler.pid, signal.SIGKILL)
def textmain():
for agent in agents:
textplay(agent)
def qtmain():
from PyQt4 import QtCore, QtGui # you'll need PyQt4 (python-qt4)
def index2rowdata(index):
rowdata = (index.sibling(index.row(), 0).data().toString(),
index.sibling(index.row(), 1).data().toString(),
index.sibling(index.row(), 2).data().toString(),
)
return rowdata
class Window(QtGui.QWidget):
def __init__(self, app):
self.boodler = None
QtGui.QWidget.__init__(self)
self.connect(app, QtCore.SIGNAL('aboutToQuit()'), self.stopPlay)
# proxy model for sorting and filtering
self.proxyModel = QtGui.QSortFilterProxyModel()
self.proxyModel.setDynamicSortFilter(True)
self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.proxyModel.setFilterKeyColumn(2)
# widgets for now playing agent
self.nowPlayingGroupBox = QtGui.QGroupBox("Now playing")
self.nowPlayingLabel = QtGui.QLabel("Double-click a soundscape to play\n")
self.nowPlayingButton = QtGui.QPushButton(self.style().standardIcon(QtGui.QStyle.SP_MediaStop), "&Stop")
self.nowPlayingButton.setEnabled(False)
# signals for now playing agent
self.connect(self.nowPlayingButton, QtCore.SIGNAL('clicked()'), self.stopPlay)
# widgets for selected agent
self.selectedGroupBox = QtGui.QGroupBox("Selected soundscape")
self.selectedLabel = QtGui.QLabel("Click a soundscape to view details\n")
self.selectedInfoLabel = QtGui.QLabel("Internal name:\nLicense:\nSource:")
self.selectedButton = QtGui.QPushButton(self.style().standardIcon(QtGui.QStyle.SP_MediaPlay), "&Play")
self.selectedButton.setEnabled(False)
# signals for now playing agent
self.connect(self.selectedButton, QtCore.SIGNAL('clicked()'), self.playButtonClicked)
# view widgets
self.proxyGroupBox = QtGui.QGroupBox("Available soundscapes")
self.proxyView = QtGui.QTreeView()
self.proxyView.setRootIsDecorated(False)
self.proxyView.setAlternatingRowColors(True)
self.proxyView.setModel(self.proxyModel)
self.proxyView.setSortingEnabled(True)
self.proxyView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.proxyView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.proxyView.setAllColumnsShowFocus(True)
# filter pattern widgets
self.filterPatternLineEdit = QtGui.QLineEdit()
self.filterPatternLabel = QtGui.QLabel("F&ind:")
self.filterPatternLabel.setBuddy(self.filterPatternLineEdit)
# filter pattern signals
self.connect(self.filterPatternLineEdit,
QtCore.SIGNAL('textChanged(const QString &)'),
self.filterRegExpChanged)
self.connect(self.proxyView.selectionModel(),
QtCore.SIGNAL('currentRowChanged(const QModelIndex &, const QModelIndex &)'),
self.itemSelected)
self.connect(self.proxyView, QtCore.SIGNAL('activated(const QModelIndex &)'), self.itemActivated)
# now playing layout
nowPlayingLayout = QtGui.QHBoxLayout()
nowPlayingLayout.addWidget(self.nowPlayingButton)
nowPlayingLayout.addWidget(self.nowPlayingLabel, 1)
self.nowPlayingGroupBox.setLayout(nowPlayingLayout)
# selected layout
selectedLayout = QtGui.QVBoxLayout()
selectedLayoutH = QtGui.QHBoxLayout()
selectedLayoutH.addWidget(self.selectedButton)
selectedLayoutH.addWidget(self.selectedLabel, 1)
selectedLayout.addLayout(selectedLayoutH)
selectedLayout.addWidget(self.selectedInfoLabel)
self.selectedGroupBox.setLayout(selectedLayout)
# available agents layout
filterPatternLayout = QtGui.QHBoxLayout()
filterPatternLayout.addWidget(self.filterPatternLabel)
filterPatternLayout.addWidget(self.filterPatternLineEdit)
proxyLayout = QtGui.QVBoxLayout()
proxyLayout.addWidget(self.proxyView)
proxyLayout.addLayout(filterPatternLayout)
self.proxyGroupBox.setLayout(proxyLayout)
# main layout
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(self.nowPlayingGroupBox)
mainLayout.addWidget(self.selectedGroupBox)
mainLayout.addWidget(self.proxyGroupBox)
self.setLayout(mainLayout)
# set top-level window properties
self.setWindowTitle("Boodle UI")
self.resize(500, 450)
# set initial state
self.proxyView.sortByColumn(2, QtCore.Qt.AscendingOrder)
self.filterPatternLineEdit.setText("")
def setSourceModel(self, model):
self.proxyModel.setSourceModel(model)
def filterRegExpChanged(self):
regExp = QtCore.QRegExp(self.filterPatternLineEdit.text(), QtCore.Qt.CaseInsensitive, QtCore.QRegExp.Wildcard)
self.proxyModel.setFilterRegExp(regExp)
def itemSelected(self, currentIndex, _previousIndex):
if not currentIndex.isValid():
return # keep the latest selection, if any
row = index2rowdata(currentIndex)
self.lastSelectedRow = row
self.selectedButton.setEnabled(True)
self.updateSelected(row)
def itemActivated(self, index):
row = index2rowdata(index)
self.startPlay((str(row[0]), str(row[1])))
self.updateNowPlaying(row)
def playButtonClicked(self):
row = self.lastSelectedRow
self.startPlay((str(row[0]), str(row[1])))
self.updateNowPlaying(row)
def updateNowPlaying(self, row):
pkg = l.load(str(row[0]))
creator = pkg.metadata.get_one("dc.creator")
pkgtitle = pkg.metadata.get_one("dc.title")
msg = '"%s"\nby %s from "%s"' % (row[2], creator, pkgtitle)
self.nowPlayingLabel.setText(msg)
def updateSelected(self, row):
pkg = l.load(str(row[0]))
creator = pkg.metadata.get_one("dc.creator")
pkgtitle = pkg.metadata.get_one("dc.title")
license = pkg.metadata.get_one("dc.license")
source = pkg.metadata.get_one("dc.source")
msg = '"%s"\nby %s from "%s"' % (row[2], creator, pkgtitle)
self.selectedLabel.setText(msg)
info = 'Internal name: %s/%s' % (row[0], row[1])
info += '\nLicense: %s' % license
info += '\nSource: %s' % source
self.selectedInfoLabel.setText(info)
def startPlay(self, agent):
self.stopPlay()
self.boodler = QtCore.QProcess(self)
self.connect(self.boodler, QtCore.SIGNAL('error(QProcess::ProcessError)'), self.playError)
self.connect(self.boodler, QtCore.SIGNAL('finished(int,QProcess::ExitStatus)'), self.playFinished)
cmd = play(agent)
print "Launching: %s" % ' '.join(cmd)
self.boodler.start(cmd[0], cmd[1:])
self.nowPlayingButton.setEnabled(True)
def stopPlay(self):
if self.boodler:
self.disconnect(self.boodler, QtCore.SIGNAL('error(QProcess::ProcessError)'), self.playError)
self.disconnect(self.boodler, QtCore.SIGNAL('finished(int, QProcess::ExitStatus)'), self.playFinished)
if self.boodler.state() != QtCore.QProcess.NotRunning:
self.boodler.terminate()
if not self.boodler.waitForFinished(5000):
self.boodler.kill()
self.boodler = None
self.nowPlayingButton.setEnabled(False)
def playError(self, error):
if error == QtCore.QProcess.FailedToStart:
print "Boodler failed to start: %s" % self.boodler.errorString()
elif error == QtCore.QProcess.Crashed:
print "Boodler crashed with exit code %s." % self.boodler.exitCode()
else:
print "Boodler failed with error %s." % error
self.stopPlay()
def playFinished(self, exitCode, exitStatus):
if exitCode != 0:
print self.boodler.readAllStandardError()
print "Boodler failed with exit code %s." % exitCode
else:
print "Boodler done playing."
self.stopPlay()
def createAgentModel(parent):
model = QtGui.QStandardItemModel(0, 3, parent)
model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.QVariant("Package"))
model.setHeaderData(1, QtCore.Qt.Horizontal, QtCore.QVariant("Resource"))
model.setHeaderData(2, QtCore.Qt.Horizontal, QtCore.QVariant("Title"))
def standardItem(x):
if x is None:
return QtGui.QStandardItem("")
else:
return QtGui.QStandardItem(x)
for agent in reversed(agents):
pkg = l.load(agent[0])
res = pkg.resources.get(agent[1])
title = res.get_one("dc.title")
model.appendRow(map(standardItem, [agent[0], agent[1], title]))
return model
app = QtGui.QApplication(sys.argv)
window = Window(app)
window.setSourceModel(createAgentModel(window))
window.show()
sys.exit(app.exec_())
qtmain()

@ -0,0 +1,86 @@
# /etc/bash_completion.d/boodler #v3
mgr=boodle-mgr.py
#types="agent" # only agents
types="agent\|sound" #
typefilter=".*\[\(${types}\)\].*"
#20091113 - v3 - typefilter introduction
#20091113 - v2 - cleanup, documentation, bugfix, substring version
#20091113 - v1 - initial working version
# does simple bash completion for boodler.py in via wildcard or a two-step process
# type
# boodler.py <tab><tab> # to get package completion
# boodler.py name.me.example/<tab><tab> # for agent completion
# boodler.py exam*<tab><tab> # get substring matching completion
_boodlelib()
{
# case insensitive matches
if `shopt -q nocasematch`; then
oldnocasematchset=true ; else oldnocasematchset=false ;
fi
shopt -s nocasematch # FIXME - TRAP ME
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
# packages list (have to remove the last line 'xx packages installed...')
packages=`$mgr -- list 2>/dev/null | sed "$ s/.*//"`
case "$cur" in
*\*) # WILDCARD - long, thorough search (package/agent completion)
cur="${cur%\*}" # removing the wildcard
agents=""
for p in $packages; do
ags=`$mgr contents "$p" 2>/dev/null | grep -e "${typefilter}" | awk '{print $1}'`
#echo ags_${ags}_ags
for ag in $ags; do
pag="$p/$ag"
if [[ "$pag" == *${cur}* ]] ; then
#echo "$pag" == *${cur}*
agents+="$pag "
fi
done
done
COMPREPLY=( ${agents} )
;;
*/*) # AGENT completion
p="${cur%\/}" # removing the trailing slash
ags=`$mgr contents "$p" 2>/dev/null | grep -e "${typefilter}" | awk '{print $1}'`
for ag in $ags; do
# echo found $ag
agents+="$p/$ag "
done
opts="$agents"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
;;
*) # PACKAGE completion
# match first on packages (with slashes at the end)
opts=`echo "$packages" | sed "s/\([a-zA-Z0-9]\)$/\1\//"`
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
;;
esac
# clean-up
if ${oldnocasematchset} ; then
shopt -s nocasematch
else
shopt -u nocasematch
fi
return 0
}
complete -o nospace -F _boodlelib boodler.py

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE pkgmetadata SYSTEM "http://www.gentoo.org/dtd/metadata.dtd">
<pkgmetadata>
<maintainer type="person">
<email>webmaster@gentoostudio.org</email>
<name>Gentoo Studio/Damien Moody</name>
</maintainer>
<use>
<flag name="intmath">reduces the number of floating-point operations at the cost of less accurate volume fading</flag>
<flag name="shout">Enable shoutcast support</flag>
</use>
</pkgmetadata>
Loading…
Cancel
Save