From 3caf3739463edbf1b26da1c6501a826b83cf1486 Mon Sep 17 00:00:00 2001 From: yomguy <> Date: Tue, 19 Aug 2008 15:25:35 +0000 Subject: [PATCH] * Add audiolab --- telemeta/visualization/scikits/__init__.py | 1 + .../scikits/audiolab/__init__.py | 35 + .../scikits/audiolab/docs/Makefile | 54 + .../scikits/audiolab/docs/audiolab1.png | Bin 0 -> 19591 bytes .../scikits/audiolab/docs/base.tex | 1182 +++++++++++++++++ .../scikits/audiolab/docs/examples/format1.py | 7 + .../scikits/audiolab/docs/examples/format2.py | 6 + .../scikits/audiolab/docs/examples/matlab1.py | 20 + .../scikits/audiolab/docs/examples/quick1.py | 5 + .../scikits/audiolab/docs/examples/usage1.py | 6 + .../scikits/audiolab/docs/examples/usage2.py | 12 + .../scikits/audiolab/docs/examples/write1.py | 34 + .../scikits/audiolab/docs/index.txt | 264 ++++ .../scikits/audiolab/docs/user.tex | 64 + .../visualization/scikits/audiolab/info.py | 2 + .../visualization/scikits/audiolab/matapi.py | 148 +++ .../scikits/audiolab/misc/Makefile | 23 + .../scikits/audiolab/misc/Sconstruct | 5 + .../scikits/audiolab/misc/badflac.c | 37 + .../scikits/audiolab/misc/badflac.flac | Bin 0 -> 38385 bytes .../scikits/audiolab/misc/winfdopen.c | 82 ++ .../scikits/audiolab/pyaudioio.py | 68 + .../scikits/audiolab/pysndfile.py.in | 886 ++++++++++++ .../scikits/audiolab/soundio/SConstruct | 2 + .../scikits/audiolab/soundio/_alsa.pyx | 198 +++ .../scikits/audiolab/soundio/alsa.py | 35 + .../scikits/audiolab/soundio/alsa_ctypes.py | 440 ++++++ .../scikits/audiolab/soundio/setup.py | 12 + .../scikits/audiolab/soundio/simple.c | 74 ++ .../scikits/audiolab/soundio/simple2.c | 80 ++ .../scikits/audiolab/tests/__init__.py | 2 + .../scikits/audiolab/tests/test_matapi.py | 164 +++ .../scikits/audiolab/tests/test_pysndfile.py | 396 ++++++ .../scikits/audiolab/tests/testcommon.py | 20 + 34 files changed, 4364 insertions(+) create mode 100644 telemeta/visualization/scikits/__init__.py create mode 100644 telemeta/visualization/scikits/audiolab/__init__.py create mode 100644 telemeta/visualization/scikits/audiolab/docs/Makefile create mode 100644 telemeta/visualization/scikits/audiolab/docs/audiolab1.png create mode 100644 telemeta/visualization/scikits/audiolab/docs/base.tex create mode 100644 telemeta/visualization/scikits/audiolab/docs/examples/format1.py create mode 100644 telemeta/visualization/scikits/audiolab/docs/examples/format2.py create mode 100644 telemeta/visualization/scikits/audiolab/docs/examples/matlab1.py create mode 100644 telemeta/visualization/scikits/audiolab/docs/examples/quick1.py create mode 100644 telemeta/visualization/scikits/audiolab/docs/examples/usage1.py create mode 100644 telemeta/visualization/scikits/audiolab/docs/examples/usage2.py create mode 100644 telemeta/visualization/scikits/audiolab/docs/examples/write1.py create mode 100644 telemeta/visualization/scikits/audiolab/docs/index.txt create mode 100644 telemeta/visualization/scikits/audiolab/docs/user.tex create mode 100644 telemeta/visualization/scikits/audiolab/info.py create mode 100644 telemeta/visualization/scikits/audiolab/matapi.py create mode 100644 telemeta/visualization/scikits/audiolab/misc/Makefile create mode 100644 telemeta/visualization/scikits/audiolab/misc/Sconstruct create mode 100644 telemeta/visualization/scikits/audiolab/misc/badflac.c create mode 100644 telemeta/visualization/scikits/audiolab/misc/badflac.flac create mode 100644 telemeta/visualization/scikits/audiolab/misc/winfdopen.c create mode 100644 telemeta/visualization/scikits/audiolab/pyaudioio.py create mode 100644 telemeta/visualization/scikits/audiolab/pysndfile.py.in create mode 100644 telemeta/visualization/scikits/audiolab/soundio/SConstruct create mode 100644 telemeta/visualization/scikits/audiolab/soundio/_alsa.pyx create mode 100644 telemeta/visualization/scikits/audiolab/soundio/alsa.py create mode 100644 telemeta/visualization/scikits/audiolab/soundio/alsa_ctypes.py create mode 100644 telemeta/visualization/scikits/audiolab/soundio/setup.py create mode 100644 telemeta/visualization/scikits/audiolab/soundio/simple.c create mode 100644 telemeta/visualization/scikits/audiolab/soundio/simple2.c create mode 100644 telemeta/visualization/scikits/audiolab/tests/__init__.py create mode 100644 telemeta/visualization/scikits/audiolab/tests/test_matapi.py create mode 100644 telemeta/visualization/scikits/audiolab/tests/test_pysndfile.py create mode 100644 telemeta/visualization/scikits/audiolab/tests/testcommon.py diff --git a/telemeta/visualization/scikits/__init__.py b/telemeta/visualization/scikits/__init__.py new file mode 100644 index 00000000..de40ea7c --- /dev/null +++ b/telemeta/visualization/scikits/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/telemeta/visualization/scikits/audiolab/__init__.py b/telemeta/visualization/scikits/audiolab/__init__.py new file mode 100644 index 00000000..12911595 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/__init__.py @@ -0,0 +1,35 @@ +#! /usr/bin/env python +# Last Change: Mon Sep 10 07:00 PM 2007 J +""" +audiolab: a small toolbox to read, write and play audio to and from +numpy arrays. + +audiolab provides two API: + - one similar to matlab: this gives you wavread, wavwrite functions really + similar to matlab's functions. + - a more complete API, which can be used to read, write to many audio file + (including wav, aiff, flac, au, IRCAM, htk, etc...), with IO capabilities + not available to matlab (seek, append data, etc...) + +It is a thin wrapper around libsndfile from Erik Castro Lopo. + +Copyright (C) 2006-2007 Cournapeau David + +LICENSE: audiolab is licensed under the LGPL, as is libsndfile itself. See +COPYING.txt for details. """ + +from info import VERSION +__version__ = VERSION + +from pysndfile import formatinfo, sndfile +from pysndfile import supported_format, supported_endianness, \ + supported_encoding +#from scikits.audiolab.matapi import wavread, aiffread, flacread, auread, \ +# sdifread, wavwrite, aiffwrite, flacwrite, auwrite, sdifwrite +from matapi import * + +__all__ = filter(lambda s:not s.startswith('_'),dir()) + +from numpy.testing import NumpyTest +def test(): + return NumpyTest().test() diff --git a/telemeta/visualization/scikits/audiolab/docs/Makefile b/telemeta/visualization/scikits/audiolab/docs/Makefile new file mode 100644 index 00000000..5ea6c555 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/Makefile @@ -0,0 +1,54 @@ +py2tex = PYTHONPATH=/home/david/local/lib/python2.5/site-packages pygmentize -l python -f tex +rst2tex = PYTHONPATH=/home/david/local/lib/python2.5/site-packages rst2newlatex.py \ + --stylesheet-path base.tex --user-stylesheet user.tex + +pytexfiles = audiolab.tex quick1.tex \ + usage1.tex \ + usage2.tex \ + format1.tex \ + format2.tex \ + write1.tex \ + matlab1.tex + +SOURCEPATH = $(PWD) + +EXTTOCLEAN=.chk .dvi .log .aux .bbl .blg .blig .ilg .toc .lof .lot .idx .ind .out .bak .ps .pdf .bm + +audiolab.pdf: $(pytexfiles) + pdflatex $< + pdflatex $< + pdflatex $< + +audiolab.tex: index.txt + $(rst2tex) $< > $@ + +quick1.tex: examples/quick1.py + $(py2tex) $< > $@ + +usage1.tex: examples/usage1.py + $(py2tex) $< > $@ + +usage2.tex: examples/usage2.py + $(py2tex) $< > $@ + +format1.tex: examples/format1.py + $(py2tex) $< > $@ + +format2.tex: examples/format2.py + $(py2tex) $< > $@ + +write1.tex: examples/write1.py + $(py2tex) $< > $@ + +matlab1.tex: examples/matlab1.py + $(py2tex) $< > $@ + +clean: + for i in $(pytexfiles); do \ + rm -f `echo $$i`; \ + done; + for i in $(SOURCEPATH); do \ + for j in $(EXTTOCLEAN); do \ + rm -f `echo $$i/*$$j`; \ + done; \ + done; diff --git a/telemeta/visualization/scikits/audiolab/docs/audiolab1.png b/telemeta/visualization/scikits/audiolab/docs/audiolab1.png new file mode 100644 index 0000000000000000000000000000000000000000..c88e46f5413b0b0fba8e6690696d7c0ed57e76c0 GIT binary patch literal 19591 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 LtDnm{r-UW|a>zf3 literal 0 HcmV?d00001 diff --git a/telemeta/visualization/scikits/audiolab/docs/base.tex b/telemeta/visualization/scikits/audiolab/docs/base.tex new file mode 100644 index 00000000..b10ea72c --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/base.tex @@ -0,0 +1,1182 @@ +% System stylesheet for the new LaTeX writer, newlatex2e. + +% Major parts of the rendering are done in this stylesheet and not in the +% Python module. + +% For development notes, see notes.txt. + +% User documentation (in the stylesheet for now; that may change though): + +% Naming conventions: +% All uppercase letters in macro names have a specific meaning. +% \D...: All macros introduced by the Docutils LaTeX writer start with "D". +% \DS: Setup function (called at the bottom of this stylesheet). +% \DN{}: Handler for Docutils document tree node `node`; called by +% the Python module. +% \DEV: External variable, set by the Python module. +% \DEC: External command. It is called by the Python module and must be +% defined in this stylesheet. +% \DNA{}{}{}{}{}: +% Attribute handler for `attribute` set on nodes of type `nodename`. +% See below for a discussion of attribute handlers. +% \DA{}{}{}{}{}: +% Attribute handler for all `attribute`. Called only when no specific +% \DNA handler is defined. +% \DNC{}: +% Handler for `class`, when set on nodes of type `nodename`. +% \DC{}: +% Handler for `class`. Called only when no specific \DNC +% handler is defined. +% \D: Generic variable or function. + +% Attribute handlers: +% TODO + +% --------------------------------------------------------------------------- + +% Having to intersperse code with \makeatletter-\makeatother pairs is very +% annoying, so we call \makeatletter at the top and \makeatother at the +% bottom. Just be aware that you cannot use "@" as a text character inside +% this stylesheet. +\makeatletter + +% Print-mode (as opposed to online mode e.g. with Adobe Reader). +% This causes for example blue hyperlinks. +\providecommand{\Dprinting}{false} + +% \DSearly is called right after \documentclass. +\providecommand{\DSearly}{} +% \DSlate is called at the end of the stylesheet (right before the document +% tree). +\providecommand{\DSlate}{} + +% Use the KOMA script article class. +\providecommand{\Ddocumentclass}{scrartcl} +\providecommand{\Ddocumentoptions}{a4paper} +\providecommand{\DSdocumentclass}{ + \documentclass[\Ddocumentoptions]{\Ddocumentclass} } + +% Todo: This should be movable to the bottom, but it isn't as long as +% we use \usepackage commands at the top level of this stylesheet +% (which we shouldn't). +\DSdocumentclass + +\providecommand{\DSpackages}{ + % Load miscellaneous packages. + % Note 1: Many of the packages loaded here are used throughout this stylesheet. + % If one of these packages does not work on your system or in your scenario, + % please let us know, so we can consider making the package optional. + % Note 2: It would appear cleaner to load packages where they are used. + % However, since using a wrong package loading order can lead to *very* + % subtle bugs, we centralize the loading of most packages here. + \DSfontencoding % load font encoding packages + \DSlanguage % load babel + % Using \ifthenelse conditionals. + \usepackage{ifthen} % before hyperref (really!) + % There is not support for *not* using hyperref because it's used in many + % places. If this is a problem (e.g. because hyperref doesn't work on your + % system), please let us know. + \usepackage[colorlinks=false,pdfborder={0 0 0}]{hyperref} + % Get color, e.g. for links and system messages. + \usepackage{color} + % Get \textnhtt macro (non-hyphenating type writer). + \usepackage{hyphenat} + % For sidebars. + \usepackage{picins} + % We use longtable to create tables. + \usepackage{longtable} + % Images. + \usepackage{graphicx} + % These packages might be useful (some just add magic pixie dust), so + % evaluate them: + %\usepackage{fixmath} + %\usepackage{amsmath} + % Add some missing symbols like \textonehalf. + \usepackage{textcomp} +} + +\providecommand{\DSfontencoding}{ + % Set up font encoding. Called by \DSpackages. + % AE is a T1 emulation. It provides mostly the same characters and + % features as T1-encoded fonts but doesn't use bitmap fonts (which are + % unsuitable for online reading and subtle for printers). + \usepackage{ae} + % Provide the characters not contained in AE from EC bitmap fonts. + \usepackage{aecompl} + % Guillemets ("<<", ">>") in AE. + \usepackage{aeguill} +} + +\providecommand{\DSsymbols}{% + % Fix up symbols. + % The Euro symbol in Computer Modern looks, um, funny. Let's get a + % proper Euro symbol. + \usepackage{eurosym}% + \renewcommand{\texteuro}{\euro}% +} + +% Taken from +% +% and modified. Used with permission. +\providecommand{\Dprovidelength}[2]{% + \begingroup% + \escapechar\m@ne% + \xdef\@gtempa{{\string#1}}% + \endgroup% + \expandafter\@ifundefined\@gtempa% + {\newlength{#1}\setlength{#1}{#2}}% + {}% +} + +\providecommand{\Dprovidecounter}[2]{% + % Like \newcounter except that it doesn't crash if the counter + % already exists. + \@ifundefined{c@#1}{\newcounter{#1}\setcounter{#1}{#2}}{} +} + +\Dprovidelength{\Dboxparindent}{\parindent} + +\providecommand{\Dmakebox}[1]{% + % Make a centered, frameless box. Useful e.g. for block quotes. + % Do not use minipages here, but create pseudo-lists to allow + % page-breaking. (Don't use KOMA-script's addmargin environment + % because it messes up bullet lists.) + \Dmakelistenvironment{}{}{% + \setlength{\parskip}{0pt}% + \setlength{\parindent}{\Dboxparindent}% + \item{#1}% + }% +} + +\providecommand{\Dmakefbox}[1]{% + % Make a centered, framed box. Useful e.g. for admonitions. + \vspace{0.4\baselineskip}% + \begin{center}% + \fbox{% + \begin{minipage}[t]{0.9\linewidth}% + \setlength{\parindent}{\Dboxparindent}% + #1% + \end{minipage}% + }% + \end{center}% + \vspace{0.4\baselineskip}% +} + +% We do not currently recognize the difference between an end-sentence and a +% mid-sentence period (". " vs. ". " in plain text). So \frenchspacing is +% appropriate. +\providecommand{\DSfrenchspacing}{\frenchspacing} + + +\Dprovidelength{\Dblocklevelvspace}{% + % Space between block-level elements other than paragraphs. + 0.7\baselineskip plus 0.3\baselineskip minus 0.2\baselineskip% +} +\providecommand{\DECauxiliaryspace}{% + \ifthenelse{\equal{\Dneedvspace}{true}}{\vspace{\Dblocklevelvspace}}{}% + \par\noindent% +} +\providecommand{\DECparagraphspace}{\par} +\providecommand{\Dneedvspace}{true} + +\providecommand{\DSlanguage}{% + % Set up babel. + \usepackage[\DEVlanguagebabel]{babel} +} + +\providecommand{\Difdefined}[3]{\@ifundefined{#1}{#3}{#2}} + +% Handler for 'classes' attribute (called for each class attribute). +\providecommand{\DAclasses}[5]{% + % Dispatch to \DNC. + \Difdefined{DN#4C#3}{% + % Pass only contents, nothing else! + \csname DN#4C#3\endcsname{#5}% + }{% + % Otherwise, dispatch to \DC. + \Difdefined{DC#3}{% + \csname DC#3\endcsname{#5}% + }{% + #5% + }% + }% +} + +\providecommand{\DECattr}[5]{% + % Global attribute dispatcher, called inside the document tree. + % Parameters: + % 1. Attribute number. + % 2. Attribute name. + % 3. Attribute value. + % 4. Node name. + % 5. Node contents. + \Difdefined{DN#4A#2}{% + % Dispatch to \DNA. + \csname DN#4A#2\endcsname{#1}{#2}{#3}{#4}{#5}% + }{\Difdefined{DA#2}{% + % Otherwise dispatch to \DA. + \csname DA#2\endcsname{#1}{#2}{#3}{#4}{#5}% + }{% + % Otherwise simply run the contents without calling a handler. + #5% + }}% +} + +% ---------- Link handling ---------- +% Targets and references. + +\providecommand{\Draisedlink}[1]{% + % Anchors are placed on the base line by default. This is a bad thing for + % inline context, so we raise the anchor (normally by \baselineskip). + \Hy@raisedlink{#1}% +} + +% References. +% We're assuming here that the "refid" and "refuri" attributes occur +% only in inline context (in TextElements). +\providecommand{\DArefid}[5]{% + \ifthenelse{\equal{#4}{reference}}{% + \Dexplicitreference{\##3}{#5}% + }{% + % If this is not a target node (targets with refids are + % uninteresting and should be silently dropped). + \ifthenelse{\not\equal{#4}{target}}{% + % If this is a footnote reference, call special macro. + \ifthenelse{\equal{#4}{footnotereference}}{% + \Dimplicitfootnotereference{\##3}{#5}% + }{% + \ifthenelse{\equal{#4}{citationreference}}{% + \Dimplicitcitationreference{\##3}{#5}% + }{% + \Dimplicitreference{\##3}{#5}% + }% + }% + }{}% + }% +} +\providecommand{\DArefuri}[5]{% + \ifthenelse{\equal{#4}{target}}{% + % The node name is 'target', so this is a hyperlink target, like this: + % .. _mytarget: URI + % Hyperlink targets are ignored because they are invisible. + }{% + % If a non-target node has a refuri attribute, it must be an explicit URI + % reference (i.e. node name is 'reference'). + \Durireference{#3}{#5}% + }% +} +% Targets. +\providecommand{\DAids}[5]{% + \label{#3}% + \ifthenelse{\equal{#4}{footnotereference}}{% + {% + \renewcommand{\HyperRaiseLinkDefault}{% + % Dirty hack to make backrefs to footnote references work. + % For some reason, \baselineskip is 0pt in fn references. + 0.5\Doriginalbaselineskip% + }% + \Draisedlink{\hypertarget{#3}{}}#5% + }% + }{% + \Draisedlink{\hypertarget{#3}{}}#5% + }% +} +\providecommand{\Dimplicitreference}[2]{% + % Create implicit reference to ID. Implicit references occur + % e.g. in TOC-backlinks of section titles. Parameters: + % 1. Target. + % 2. Link text. + \href{#1}{#2}% +} +\providecommand{\Dimplicitfootnotereference}[2]{% + % Ditto, but for the special case of footnotes. + % We want them to be rendered like explicit references. + \Dexplicitreference{#1}{#2}% +} +\providecommand{\Dimplicitcitationreference}[2]{% + % Ditto for citation references. + \Dimplicitfootnotereference{#1}{#2}% +} +\providecommand{\Dcolorexplicitreference}{% + \ifthenelse{\equal{\Dprinting}{true}}{\color{black}}{\color{blue}}% +} +\providecommand{\Dexplicitreference}[2]{% + % Create explicit reference to ID, e.g. created with "foo_". + % Parameters: + % 1. Target. + % 2. Link text. + \href{#1}{{\Dcolorexplicitreference#2}}% +} +\providecommand{\Dcolorurireference}{\Dcolorexplicitreference} +\providecommand{\Durireference}[2]{% + % Create reference to URI. Parameters: + % 1. Target. + % 2. Link text. + \href{#1}{{\Dcolorurireference#2}}% +} + +\Dprovidecounter{Dpdfbookmarkid}{0}% +\providecommand{\Dpdfbookmark}[1]{% + % Temporarily decrement Desctionlevel counter. + \addtocounter{Dsectionlevel}{-1}% + %\typeout{\arabic{Dsectionlevel}}% + %\typeout{#1}% + %\typeout{docutils\roman{Dpdfbookmarkid}}% + %\typeout{}% + \pdfbookmark[\arabic{Dsectionlevel}]{#1}{docutils\arabic{Dpdfbookmarkid}}% + \addtocounter{Dsectionlevel}{1}% + \addtocounter{Dpdfbookmarkid}{1}% +} +% ---------- End of Link Handling ---------- + +\providecommand{\DNparagraph}[1]{% + \ifthenelse{\equal{\DEVparagraphindented}{true}}{\indent}{\noindent}% + #1% +} +\providecommand{\Dformatboxtitle}[1]{{\Large\textbf{#1}}} +\providecommand{\Dformatboxsubtitle}[1]{{\large\textbf{#1}}} +\providecommand{\Dtopictitle}[1]{% + \Difinsidetoc{\vspace{1em}\par}{}% + \noindent\Dformatboxtitle{#1}% + \ifthenelse{\equal{\DEVhassubtitle}{false}}{\vspace{1em}}{\vspace{0.5em}}% + \par% +} +\providecommand{\Dadmonitiontitle}[1]{% + \Dtopictitle{#1}% +} +\providecommand{\Dtopicsubtitle}[1]{% + \noindent\Dformatboxsubtitle{#1}% + \vspace{1em}% + \par% +} +\providecommand{\Dsidebartitle}[1]{\Dtopictitle{#1}} +\providecommand{\Dsidebarsubtitle}[1]{\Dtopicsubtitle{#1}} +\providecommand{\Ddocumenttitle}[1]{% + \begin{center}{\Huge#1}\end{center}% + \ifthenelse{\equal{\DEVhassubtitle}{true}}{\vspace{0.1cm}}{\vspace{1cm}}% +} +\providecommand{\Ddocumentsubtitle}[1]{% + \begin{center}{\huge#1}\end{center}% + \vspace{1cm}% +} +% Can be overwritten by user stylesheet. +\providecommand{\Dformatsectiontitle}[1]{#1} +\providecommand{\Dformatsectionsubtitle}[1]{\Dformatsectiontitle{#1}} +\providecommand{\Dbookmarksectiontitle}[1]{% + % Return text suitable for use in \section*, \subsection*, etc., + % containing a PDF bookmark. Parameter: The title (as node tree). + \Draisedlink{\Dpdfbookmark{\DEVtitleastext}}% + #1% +} +\providecommand{\Dsectiontitlehook}[1]{#1} +\providecommand{\Dsectiontitle}[1]{% + \Dsectiontitlehook{% + \Ddispatchsectiontitle{\Dbookmarksectiontitle{\Dformatsectiontitle{#1}}}% + }% +} +\providecommand{\Ddispatchsectiontitle}[1]{% + \@ifundefined{Dsectiontitle\roman{Dsectionlevel}}{% + \Ddeepsectiontitle{#1}% + }{% + \csname Dsectiontitle\roman{Dsectionlevel}\endcsname{#1}% + }% +} +\providecommand{\Ddispatchsectionsubtitle}[1]{% + \Ddispatchsectiontitle{#1}% +} +\providecommand{\Dsectiontitlei}[1]{\section*{#1}} +\providecommand{\Dsectiontitleii}[1]{\subsection*{#1}} +\providecommand{\Ddeepsectiontitle}[1]{% + % Anything below \subsubsection (like \paragraph or \subparagraph) + % is useless because it uses the same font. The only way to + % (visually) distinguish such deeply nested sections is to use + % section numbering. + \subsubsection*{#1}% +} +\providecommand{\Dsectionsubtitlehook}[1]{#1} +\Dprovidelength{\Dsectionsubtitleraisedistance}{0.7em} +\providecommand{\Dsectionsubtitlescaling}{0.85} +\providecommand{\Dsectionsubtitle}[1]{% + \Dsectionsubtitlehook{% + % Move the subtitle nearer to the title. + \vspace{-\Dsectionsubtitleraisedistance}% + % Don't create a PDF bookmark. + \Ddispatchsectionsubtitle{% + \Dformatsectionsubtitle{\scalebox{\Dsectionsubtitlescaling}{#1}}% + }% + }% +} +\providecommand{\DNtitle}[1]{% + % Dispatch to \Dtitle. + \csname D\DEVparent title\endcsname{#1}% +} +\providecommand{\DNsubtitle}[1]{% + % Dispatch to \Dsubtitle. + \csname D\DEVparent subtitle\endcsname{#1}% +} + +\providecommand{\DNliteralblock}[1]{% + \Dmakelistenvironment{}{% + \ifthenelse{\equal{\Dinsidetabular}{true}}{% + \setlength{\leftmargin}{0pt}% + }{}% + \setlength{\rightmargin}{0pt}% + }{% + \raggedright\item\noindent\nohyphens{\textnhtt{#1\Dfinalstrut}}% + }% +} +\providecommand{\DNdoctestblock}[1]{\DNliteralblock{#1}} +\providecommand{\DNliteral}[1]{\textnhtt{#1}} +\providecommand{\DNemphasis}[1]{\emph{#1}} +\providecommand{\DNstrong}[1]{\textbf{#1}} +\providecommand{\DECvisitdocument}{\begin{document}\noindent} +\providecommand{\DECdepartdocument}{\end{document}} +\providecommand{\DNtopic}[1]{% + \ifthenelse{\equal{\DEVcurrentNtopicAcontents}{1}}{% + \addtocounter{Dtoclevel}{1}% + \par\noindent% + #1% + \addtocounter{Dtoclevel}{-1}% + }{% + \par\noindent% + \Dmakebox{#1}% + }% +} +\providecommand{\DNadmonition}[1]{% + \DNtopic{#1}% +} +\providecommand{\Dformatrubric}[1]{\textbf{#1}} +\Dprovidelength{\Dprerubricspace}{0.3em} +\providecommand{\DNrubric}[1]{% + \vspace{\Dprerubricspace}\par\noindent\Dformatrubric{#1}\par% +} + +\providecommand{\Dbullet}{} +\providecommand{\DECsetbullet}[1]{\renewcommand{\Dbullet}{#1}} +\providecommand{\DNbulletlist}[1]{% + \Difinsidetoc{% + \Dtocbulletlist{#1}% + }{% + \Dmakelistenvironment{\Dbullet}{}{#1}% + }% +} +% Todo: So what on earth is @pnumwidth? +\renewcommand{\@pnumwidth}{2.2em} +\providecommand{\DNlistitem}[1]{% + \Difinsidetoc{% + \ifthenelse{\equal{\theDtoclevel}{1}\and\equal{\Dlocaltoc}{false}}{% + {% + \par\addvspace{1em}\noindent% + \sectfont% + #1\hfill\pageref{\DEVcurrentNlistitemAtocrefid}% + }% + }{% + \@dottedtocline{0}{\Dtocindent}{0em}{#1}{% + \pageref{\DEVcurrentNlistitemAtocrefid}% + }% + }% + }{% + \item{#1}% + }% +} +\providecommand{\DNenumeratedlist}[1]{#1} +\Dprovidecounter{Dsectionlevel}{0} +\providecommand{\Dvisitsectionhook}{} +\providecommand{\Ddepartsectionhook}{} +\providecommand{\DECvisitsection}{% + \addtocounter{Dsectionlevel}{1}% + \Dvisitsectionhook% +} +\providecommand{\DECdepartsection}{% + \Ddepartsectionhook% + \addtocounter{Dsectionlevel}{-1}% +} + +% Using \_ will cause hyphenation after _ even in \textnhtt-typewriter +% because the hyphenat package redefines \_. So we use +% \textunderscore here. +\providecommand{\DECtextunderscore}{\textunderscore} + +\providecommand{\Dtextinlineliteralfirstspace}{{ }} +\providecommand{\Dtextinlineliteralsecondspace}{{~}} + +\Dprovidelength{\Dlistspacing}{0.8\baselineskip} + +\providecommand{\Dsetlistrightmargin}{% + \ifthenelse{\lengthtest{\linewidth>12em}}{% + % Equal margins. + \setlength{\rightmargin}{\leftmargin}% + }{% + % If the line is narrower than 10em, we don't remove any further + % space from the right. + \setlength{\rightmargin}{0pt}% + }% +} +\providecommand{\Dresetlistdepth}{false} +\Dprovidelength{\Doriginallabelsep}{\labelsep} +\providecommand{\Dmakelistenvironment}[3]{% + % Make list environment with support for unlimited nesting and with + % reasonable default lengths. Parameters: + % 1. Label (same as in list environment). + % 2. Spacing (same as in list environment). + % 3. List contents (contents of list environment). + \ifthenelse{\equal{\Dinsidetabular}{true}}{% + % Unfortunately, vertical spacing doesn't work correctly when + % using lists inside tabular environments, so we use a minipage. + \begin{minipage}[t]{\linewidth}% + }{}% + {% + \renewcommand{\Dneedvspace}{false}% + % \parsep0.5\baselineskip + \renewcommand{\Dresetlistdepth}{false}% + \ifnum \@listdepth>5% + \protect\renewcommand{\Dresetlistdepth}{true}% + \@listdepth=5% + \fi% + \begin{list}{% + #1% + }{% + \setlength{\itemsep}{0pt}% + \setlength{\partopsep}{0pt}% + \setlength{\topsep}{0pt}% + % List should take 90% of total width. + \setlength{\leftmargin}{0.05\linewidth}% + \ifthenelse{\lengthtest{\leftmargin<1.8em}}{% + \setlength{\leftmargin}{1.8em}% + }{}% + \setlength{\labelsep}{\Doriginallabelsep}% + \Dsetlistrightmargin% + #2% + }{% + #3% + }% + \end{list}% + \ifthenelse{\equal{\Dresetlistdepth}{true}}{\@listdepth=5}{}% + }% + \ifthenelse{\equal{\Dinsidetabular}{true}}{\end{minipage}}{}% +} +\providecommand{\Dfinalstrut}{\@finalstrut\@arstrutbox} +\providecommand{\DAlastitem}[5]{#5\Dfinalstrut} + +\Dprovidelength{\Ditemsep}{0pt} +\providecommand{\DECmakeenumeratedlist}[6]{% + % Make enumerated list. + % Parameters: + % - prefix + % - type (\arabic, \roman, ...) + % - suffix + % - suggested counter name + % - start number - 1 + % - list contents + \newcounter{#4}% + \Dmakelistenvironment{#1#2{#4}#3}{% + % Use as much space as needed for the label. + \setlength{\labelwidth}{10em}% + % Reserve enough space so that the label doesn't go beyond the + % left margin of preceding paragraphs. Like that: + % + % A paragraph. + % + % 1. First item. + \setlength{\leftmargin}{2.5em}% + \Dsetlistrightmargin% + \setlength{\itemsep}{\Ditemsep}% + % Use counter recommended by Python module. + \usecounter{#4}% + % Set start value. + \addtocounter{#4}{#5}% + }{% + % The list contents. + #6% + }% +} + + +% Single quote in literal mode. \textquotesingle from package +% textcomp has wrong width when using package ae, so we use a normal +% single curly quote here. +\providecommand{\DECtextliteralsinglequote}{'} + + +% "Tabular lists" are field lists and options lists (not definition +% lists because there the term always appears on its own line). We'll +% use the terminology of field lists now ("field", "field name", +% "field body"), but the same is also analogously applicable to option +% lists. +% +% We want these lists to be breakable across pages. We cannot +% automatically get the narrowest possible size for the left column +% (i.e. the field names or option groups) because tabularx does not +% support multi-page tables, ltxtable needs to have the table in an +% external file and we don't want to clutter the user's directories +% with auxiliary files created by the filecontents environment, and +% ltablex is not included in teTeX. +% +% Thus we set a fixed length for the left column and use list +% environments. This also has the nice side effect that breaking is +% now possible anywhere, not just between fields. +% +% Note that we are creating a distinct list environment for each +% field. There is no macro for a whole tabular list! +\Dprovidelength{\Dtabularlistfieldnamewidth}{6em} +\Dprovidelength{\Dtabularlistfieldnamesep}{0.5em} +\providecommand{\Dinsidetabular}{false} +\providecommand{\Dsavefieldname}{} +\providecommand{\Dsavefieldbody}{} +\Dprovidelength{\Dusedfieldnamewidth}{0pt} +\Dprovidelength{\Drealfieldnamewidth}{0pt} +\providecommand{\Dtabularlistfieldname}[1]{\renewcommand{\Dsavefieldname}{#1}} +\providecommand{\Dtabularlistfieldbody}[1]{\renewcommand{\Dsavefieldbody}{#1}} +\Dprovidelength{\Dparskiptemp}{0pt} +\providecommand{\Dtabularlistfield}[1]{% + {% + % This only saves field name and field body in \Dsavefieldname and + % \Dsavefieldbody, resp. It does not insert any text into the + % document. + #1% + % Recalculate the real field name width everytime we encounter a + % tabular list field because it may have been changed using a + % "raw" node. + \setlength{\Drealfieldnamewidth}{\Dtabularlistfieldnamewidth}% + \addtolength{\Drealfieldnamewidth}{\Dtabularlistfieldnamesep}% + \Dmakelistenvironment{% + \makebox[\Drealfieldnamewidth][l]{\Dsavefieldname}% + }{% + \setlength{\labelwidth}{\Drealfieldnamewidth}% + \setlength{\leftmargin}{\Drealfieldnamewidth}% + \setlength{\rightmargin}{0pt}% + \setlength{\labelsep}{0pt}% + }{% + \item% + \settowidth{\Dusedfieldnamewidth}{\Dsavefieldname}% + \setlength{\Dparskiptemp}{\parskip}% + \ifthenelse{% + \lengthtest{\Dusedfieldnamewidth>\Dtabularlistfieldnamewidth}% + }{% + \mbox{}\par% + \setlength{\parskip}{0pt}% + }{}% + \Dsavefieldbody% + \setlength{\parskip}{\Dparskiptemp}% + %XXX Why did we need this? + %\@finalstrut\@arstrutbox% + }% + \par% + }% +} + +\providecommand{\Dformatfieldname}[1]{\textbf{#1:}} +\providecommand{\DNfieldlist}[1]{#1} +\providecommand{\DNfield}[1]{\Dtabularlistfield{#1}} +\providecommand{\DNfieldname}[1]{% + \Dtabularlistfieldname{% + \Dformatfieldname{#1}% + }% +} +\providecommand{\DNfieldbody}[1]{\Dtabularlistfieldbody{#1}} + +\providecommand{\Dformatoptiongroup}[1]{% + % Format option group, e.g. "-f file, --input file". + \texttt{#1}% +} +\providecommand{\Dformatoption}[1]{% + % Format option, e.g. "-f file". + % Put into mbox to avoid line-breaking at spaces. + \mbox{#1}% +} +\providecommand{\Dformatoptionstring}[1]{% + % Format option string, e.g. "-f". + #1% +} +\providecommand{\Dformatoptionargument}[1]{% + % Format option argument, e.g. "file". + \textsl{#1}% +} +\providecommand{\Dformatoptiondescription}[1]{% + % Format option description, e.g. + % "\DNparagraph{Read input data from file.}" + #1% +} +\providecommand{\DNoptionlist}[1]{#1} +\providecommand{\Doptiongroupjoiner}{,{ }} +\providecommand{\Disfirstoption}{% + % Auxiliary macro indicating if a given option is the first child + % of its option group (if it's not, it has to preceded by + % \Doptiongroupjoiner). + false% +} +\providecommand{\DNoptionlistitem}[1]{% + \Dtabularlistfield{#1}% +} +\providecommand{\DNoptiongroup}[1]{% + \renewcommand{\Disfirstoption}{true}% + \Dtabularlistfieldname{\Dformatoptiongroup{#1}}% +} +\providecommand{\DNoption}[1]{% + % If this is not the first option in this option group, add a + % joiner. + \ifthenelse{\equal{\Disfirstoption}{true}}{% + \renewcommand{\Disfirstoption}{false}% + }{% + \Doptiongroupjoiner% + }% + \Dformatoption{#1}% +} +\providecommand{\DNoptionstring}[1]{\Dformatoptionstring{#1}} +\providecommand{\DNoptionargument}[1]{{ }\Dformatoptionargument{#1}} +\providecommand{\DNdescription}[1]{% + \Dtabularlistfieldbody{\Dformatoptiondescription{#1}}% +} + +\providecommand{\DNdefinitionlist}[1]{% + \begin{description}% + \parskip0pt% + #1% + \end{description}% +} +\providecommand{\DNdefinitionlistitem}[1]{% + % LaTeX expects the label in square brackets; we provide an empty + % label. + \item[]#1% +} +\providecommand{\Dformatterm}[1]{#1} +\providecommand{\DNterm}[1]{\hspace{-5pt}\Dformatterm{#1}} +% I'm still not sure what's the best rendering for classifiers. The +% colon syntax is used by reStructuredText, so it's at least WYSIWYG. +% Use slanted text because italic would cause too much emphasis. +\providecommand{\Dformatclassifier}[1]{\textsl{#1}} +\providecommand{\DNclassifier}[1]{~:~\Dformatclassifier{#1}} +\providecommand{\Dformatdefinition}[1]{#1} +\providecommand{\DNdefinition}[1]{\par\Dformatdefinition{#1}} + +\providecommand{\Dlineblockindentation}{2.5em} +\providecommand{\DNlineblock}[1]{% + \Dmakelistenvironment{}{% + \ifthenelse{\equal{\DEVparent}{lineblock}}{% + % Parent is a line block, so indent. + \setlength{\leftmargin}{\Dlineblockindentation}% + }{% + % At top level; don't indent. + \setlength{\leftmargin}{0pt}% + }% + \setlength{\rightmargin}{0pt}% + \setlength{\parsep}{0pt}% + }{% + #1% + }% +} +\providecommand{\DNline}[1]{\item#1} + +\providecommand{\DNtransition}{% + \raisebox{0.25em}{\parbox{\linewidth}{\hspace*{\fill}\hrulefill\hrulefill\hspace*{\fill}}}% +} + +\providecommand{\Dformatblockquote}[1]{% + % Format contents of block quote. + % This occurs in block-level context, so we cannot use \textsl. + {\slshape#1}% +} +\providecommand{\Dformatattribution}[1]{---\textup{#1}} +\providecommand{\DNblockquote}[1]{% + \Dmakebox{% + \Dformatblockquote{#1} + }% +} +\providecommand{\DNattribution}[1]{% + \par% + \begin{flushright}\Dformatattribution{#1}\end{flushright}% +} + + +% Sidebars: +% Vertical and horizontal margins. +\Dprovidelength{\Dsidebarvmargin}{0.5em} +\Dprovidelength{\Dsidebarhmargin}{1em} +% Padding (space between contents and frame). +\Dprovidelength{\Dsidebarpadding}{1em} +% Frame width. +\Dprovidelength{\Dsidebarframewidth}{2\fboxrule} +% Position ("l" or "r"). +\providecommand{\Dsidebarposition}{r} +% Width. +\Dprovidelength{\Dsidebarwidth}{0.45\linewidth} +\providecommand{\DNsidebar}[1]{ + \parpic[\Dsidebarposition]{% + \begin{minipage}[t]{\Dsidebarwidth}% + % Doing this with nested minipages is ugly, but I haven't found + % another way to place vertical space before and after the fbox. + \vspace{\Dsidebarvmargin}% + {% + \setlength{\fboxrule}{\Dsidebarframewidth}% + \setlength{\fboxsep}{\Dsidebarpadding}% + \fbox{% + \begin{minipage}[t]{\linewidth}% + \setlength{\parindent}{\Dboxparindent}% + #1% + \end{minipage}% + }% + }% + \vspace{\Dsidebarvmargin}% + \end{minipage}% + }% +} + + +% Citations and footnotes. +\providecommand{\Dformatfootnote}[1]{% + % Format footnote. + {% + \footnotesize#1% + % \par is necessary for LaTeX to adjust baselineskip to the + % changed font size. + \par% + }% +} +\providecommand{\Dformatcitation}[1]{\Dformatfootnote{#1}} +\Dprovidelength{\Doriginalbaselineskip}{0pt} +\providecommand{\DNfootnotereference}[1]{% + {% + % \baselineskip is 0pt in \textsuperscript, so we save it here. + \setlength{\Doriginalbaselineskip}{\baselineskip}% + \textsuperscript{#1}% + }% +} +\providecommand{\DNcitationreference}[1]{{[}#1{]}} +\Dprovidelength{\Dfootnotesep}{3.5pt} +\providecommand{\Dsetfootnotespacing}{% + % Spacing commands executed at the beginning of footnotes. + \setlength{\parindent}{0pt}% + \hspace{1em}% +} +\providecommand{\DNfootnote}[1]{% + % See ltfloat.dtx for details. + {% + \insert\footins{% + % BUG: This is too small if the user adds + % \onehalfspacing or \doublespace. + \vspace{\Dfootnotesep}% + \Dsetfootnotespacing% + \Dformatfootnote{#1}% + }% + }% +} +\providecommand{\DNcitation}[1]{\DNfootnote{#1}} +\providecommand{\Dformatfootnotelabel}[1]{% + % Keep \footnotesize in footnote labels (\textsuperscript would + % reduce the font size even more). + \textsuperscript{\footnotesize#1{ }}% +} +\providecommand{\Dformatcitationlabel}[1]{{[}#1{]}{ }} +\providecommand{\Dformatmultiplebackrefs}[1]{% + % If in printing mode, do not write out multiple backrefs. + \ifthenelse{\equal{\Dprinting}{true}}{}{\textsl{#1}}% +} +\providecommand{\Dthislabel}{} +\providecommand{\DNlabel}[1]{% + % Footnote or citatation label. + \renewcommand{\Dthislabel}{#1}% + \ifthenelse{\not\equal{\DEVsinglebackref}{}}{% + \let\Doriginallabel=\Dthislabel% + \def\Dthislabel{% + \Dsinglefootnotebacklink{\DEVsinglebackref}{\Doriginallabel}% + }% + }{}% + \ifthenelse{\equal{\DEVparent}{footnote}}{% + % Footnote label. + \Dformatfootnotelabel{\Dthislabel}% + }{% + \ifthenelse{\equal{\DEVparent}{citation}}{% + % Citation label. + \Dformatcitationlabel{\Dthislabel}% + }{}% + }% + % If there are multiple backrefs, add them now. + \Dformatmultiplebackrefs{\DEVmultiplebackrefs}% +} +\providecommand{\Dsinglefootnotebacklink}[2]{% + % Create normal backlink of a footnote label. Parameters: + % 1. ID. + % 2. Link text. + % Treat like a footnote reference. + \Dimplicitfootnotereference{\##1}{#2}% +} +\providecommand{\DECmultifootnotebacklink}[2]{% + % Create generated backlink, as in (1, 2). Parameters: + % 1. ID. + % 2. Link text. + % Treat like a footnote reference. + \Dimplicitfootnotereference{\##1}{#2}% +} +\providecommand{\Dsinglecitationbacklink}[2]{\Dsinglefootnotebacklink{#1}{#2}} +\providecommand{\DECmulticitationbacklink}[2]{\DECmultifootnotebacklink{#1}{#2}} + + +\providecommand{\DECmaketable}[2]{% + % Make table. Parameters: + % 1. Table spec (like "|p|p|"). + % 2. Table contents. + {% + \ifthenelse{\equal{\Dinsidetabular}{true}}{% + % Inside longtable; we cannot have nested longtables. + \begin{tabular}{#1}% + \hline% + #2% + \end{tabular}% + }{% + \renewcommand{\Dinsidetabular}{true}% + \begin{longtable}{#1}% + \hline% + #2% + \end{longtable}% + }% + }% +} +\providecommand{\DNthead}[1]{% + #1% + \endhead% +} +\providecommand{\DNrow}[1]{% + #1\tabularnewline% + \hline% +} +\providecommand{\Dinsidemulticolumn}{false} +\providecommand{\Dcompensatingmulticol}[3]{% + \multicolumn{#1}{#2}{% + {% + \renewcommand{\Dinsidemulticolumn}{true}% + % Compensate for weird missing vertical space at top of paragraph. + \raisebox{-2.5pt}{#3}% + }% + }% +} +\providecommand{\DECcolspan}[2]{% + % Take care of the morecols attribute (but incremented by 1). + &% + \Dcompensatingmulticol{#1}{l|}{#2}% +} +\providecommand{\DECcolspanleft}[2]{% + % Like \Dmorecols, but called for the leftmost entries in a table + % row. + \Dcompensatingmulticol{#1}{|l|}{#2}% +} +\providecommand{\DECsubsequententry}[1]{% + % +} +\providecommand{\DNentry}[1]{% + % The following sequence adds minimal vertical space above the top + % lines of the first cell paragraph, so that vertical space is + % balanced at the top and bottom of table cells. + \ifthenelse{\equal{\Dinsidemulticolumn}{false}}{% + \vspace{-1em}\vspace{-\parskip}\par% + }{}% + #1% + % No need to add an ampersand ("&"); that's done by \DECsubsequententry. +} +\providecommand{\DAtableheaderentry}[5]{\Dformattableheaderentry{#5}} +\providecommand{\Dformattableheaderentry}[1]{{\bfseries#1}} + + +\providecommand{\DNsystemmessage}[1]{% + {% + \ifthenelse{\equal{\Dprinting}{false}}{\color{red}}{}% + \bfseries% + #1% + }% +} + + +\providecommand{\Dinsidehalign}{false} +\newsavebox{\Dalignedimagebox} +\Dprovidelength{\Dalignedimagewidth}{0pt} +\providecommand{\Dhalign}[2]{% + % Horizontally align the contents to the left or right so that the + % text flows around it. + % Parameters: + % 1. l or r + % 2. Contents. + \renewcommand{\Dinsidehalign}{true}% + % For some obscure reason \parpic consumes some vertical space. + \vspace{-3pt}% + % Now we do something *really* ugly, but this enables us to wrap the + % image in a minipage while still allowing tight frames when + % class=border (see \DNimageCborder). + \sbox{\Dalignedimagebox}{#2}% + \settowidth{\Dalignedimagewidth}{\usebox{\Dalignedimagebox}}% + \parpic[#1]{% + \begin{minipage}[b]{\Dalignedimagewidth}% + % Compensate for previously added space, but not entirely. + \vspace*{2.0pt}% + \vspace*{\Dfloatimagetopmargin}% + \usebox{\Dalignedimagebox}% + \vspace*{1.5pt}% + \vspace*{\Dfloatimagebottommargin}% + \end{minipage}% + }% + \renewcommand{\Dinsidehalign}{false}% +} + + +% Maximum width of an image. +\providecommand{\Dimagemaxwidth}{\linewidth} +\providecommand{\Dfloatimagemaxwidth}{0.5\linewidth} +% Auxiliary variable. +\Dprovidelength{\Dcurrentimagewidth}{0pt} +\providecommand{\DNimageAalign}[5]{% + \ifthenelse{\equal{#3}{left}}{% + \Dhalign{l}{#5}% + }{% + \ifthenelse{\equal{#3}{right}}{% + \Dhalign{r}{#5}% + }{% + \ifthenelse{\equal{#3}{center}}{% + % Text floating around centered figures is a bad idea. Thus + % we use a center environment. Note that no extra space is + % added by the writer, so the space added by the center + % environment is fine. + \begin{center}#5\end{center}% + }{% + #5% + }% + }% + }% +} +% Base path for images. +\providecommand{\Dimagebase}{} +% Auxiliary command. Current image path. +\providecommand{\Dimagepath}{} +\providecommand{\DNimageAuri}[5]{% + % Insert image. We treat the URI like a path here. + \renewcommand{\Dimagepath}{\Dimagebase#3}% + \Difdefined{DcurrentNimageAwidth}{% + \Dwidthimage{\DEVcurrentNimageAwidth}{\Dimagepath}% + }{% + \Dsimpleimage{\Dimagepath}% + }% +} +\Dprovidelength{\Dfloatimagevmargin}{0pt} +\providecommand{\Dfloatimagetopmargin}{\Dfloatimagevmargin} +\providecommand{\Dfloatimagebottommargin}{\Dfloatimagevmargin} +\providecommand{\Dwidthimage}[2]{% + % Image with specified width. + % Parameters: + % 1. Image width. + % 2. Image path. + % Need to make bottom-alignment dependent on align attribute (add + % functional test first). Need to observe height attribute. + %\begin{minipage}[b]{#1}% + \includegraphics[width=#1,height=\textheight,keepaspectratio]{#2}% + %\end{minipage}% +} +\providecommand{\Dcurrentimagemaxwidth}{} +\providecommand{\Dsimpleimage}[1]{% + % Insert image, without much parametrization. + \settowidth{\Dcurrentimagewidth}{\includegraphics{#1}}% + \ifthenelse{\equal{\Dinsidehalign}{true}}{% + \renewcommand{\Dcurrentimagemaxwidth}{\Dfloatimagemaxwidth}% + }{% + \renewcommand{\Dcurrentimagemaxwidth}{\Dimagemaxwidth}% + }% + \ifthenelse{\lengthtest{\Dcurrentimagewidth>\Dcurrentimagemaxwidth}}{% + \Dwidthimage{\Dcurrentimagemaxwidth}{#1}% + }{% + \Dwidthimage{\Dcurrentimagewidth}{#1}% + }% +} +\providecommand{\Dwidthimage}[2]{% + % Image with specified width. + % Parameters: + % 1. Image width. + % 2. Image path. + \Dwidthimage{#1}{#2}% +} + +% Figures. +\providecommand{\DNfigureAalign}[5]{% + % Hack to make it work Right Now. + %\def\DEVcurrentNimageAwidth{\DEVcurrentNfigureAwidth}% + % + %\def\DEVcurrentNimageAwidth{\linewidth}% + \DNimageAalign{#1}{#2}{#3}{#4}{% + \begin{minipage}[b]{0.4\linewidth}#5\end{minipage}}% + %\let\DEVcurrentNimageAwidth=\relax% + % + %\let\DEVcurrentNimageAwidth=\relax% +} +\providecommand{\DNcaption}[1]{\par\noindent{\slshape#1}} +\providecommand{\DNlegend}[1]{\DECauxiliaryspace#1} + +\providecommand{\DCborder}[1]{\fbox{#1}} +% No padding between image and border. +\providecommand{\DNimageCborder}[1]{\frame{#1}} + + +% Need to replace with language-specific stuff. Maybe look at +% csquotes.sty and ask the author for permission to use parts of it. +\providecommand{\DECtextleftdblquote}{``} +\providecommand{\DECtextrightdblquote}{''} + +% Table of contents: +\Dprovidelength{\Dtocininitialsectnumwidth}{2.4em} +\Dprovidelength{\Dtocadditionalsectnumwidth}{0.7em} +% Level inside a table of contents. While this is at -1, we are not +% inside a TOC. +\Dprovidecounter{Dtoclevel}{-1}% +\providecommand{\Dlocaltoc}{false}% +\providecommand{\DNtopicClocal}[1]{% + \renewcommand{\Dlocaltoc}{true}% + \addtolength{\Dtocsectnumwidth}{2\Dtocadditionalsectnumwidth}% + \addtolength{\Dtocindent}{-2\Dtocadditionalsectnumwidth}% + #1% + \addtolength{\Dtocindent}{2\Dtocadditionalsectnumwidth}% + \addtolength{\Dtocsectnumwidth}{-2\Dtocadditionalsectnumwidth}% + \renewcommand{\Dlocaltoc}{false}% +} +\Dprovidelength{\Dtocindent}{0pt}% +\Dprovidelength{\Dtocsectnumwidth}{\Dtocininitialsectnumwidth} +% Compensate for one additional TOC indentation space so that the +% top-level is unindented. +\addtolength{\Dtocsectnumwidth}{-\Dtocadditionalsectnumwidth} +\addtolength{\Dtocindent}{-\Dtocsectnumwidth} +\providecommand{\Difinsidetoc}[2]{% + \ifthenelse{\not\equal{\theDtoclevel}{-1}}{#1}{#2}% +} +\providecommand{\DNgeneratedCsectnum}[1]{% + \Difinsidetoc{% + % Section number inside TOC. + \makebox[\Dtocsectnumwidth][l]{#1}% + }{% + % Section number inside section title. + #1\quad% + }% +} +\providecommand{\Dtocbulletlist}[1]{% + \addtocounter{Dtoclevel}{1}% + \addtolength{\Dtocindent}{\Dtocsectnumwidth}% + \addtolength{\Dtocsectnumwidth}{\Dtocadditionalsectnumwidth}% + #1% + \addtolength{\Dtocsectnumwidth}{-\Dtocadditionalsectnumwidth}% + \addtolength{\Dtocindent}{-\Dtocsectnumwidth}% + \addtocounter{Dtoclevel}{-1}% +} + + +% For \DECpixelunit, the length value is pre-multiplied with 0.75, so by +% specifying "pt" we get the same notion of "pixel" as graphicx. +\providecommand{\DECpixelunit}{pt} +% Normally lengths are relative to the current linewidth. +\providecommand{\DECrelativeunit}{\linewidth} + + +% ACTION: These commands actually *do* something. +% Ultimately, everything should be done here, and no active content should be +% above (not even \usepackage). + +\DSearly +\DSpackages +\DSfrenchspacing +\DSsymbols +\DSlate + +\makeatother + + \usepackage{fancyvrb} diff --git a/telemeta/visualization/scikits/audiolab/docs/examples/format1.py b/telemeta/visualization/scikits/audiolab/docs/examples/format1.py new file mode 100644 index 00000000..f9166f98 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/examples/format1.py @@ -0,0 +1,7 @@ +from scikits.audiolab import formatinfo as format + +f = format('aiff', 'ulaw') +print f + +f = format('ircam', 'float32') +print f diff --git a/telemeta/visualization/scikits/audiolab/docs/examples/format2.py b/telemeta/visualization/scikits/audiolab/docs/examples/format2.py new file mode 100644 index 00000000..6b176a65 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/examples/format2.py @@ -0,0 +1,6 @@ +from scikits.audiolab import supported_format, supported_encoding, \ + supported_endianness + +print supported_format() +print supported_encoding() +print supported_endianness() diff --git a/telemeta/visualization/scikits/audiolab/docs/examples/matlab1.py b/telemeta/visualization/scikits/audiolab/docs/examples/matlab1.py new file mode 100644 index 00000000..fe1e8fac --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/examples/matlab1.py @@ -0,0 +1,20 @@ +from tempfile import mkstemp +from os.path import join, dirname +from os import remove + +from scikits.audiolab import wavread, wavwrite + +(tmp, fs, enc) = wavread('test.wav') +if tmp.ndim < 2: + nc = 1 +else: + nc = tmp.shape[1] + +print "The file has %d frames, %d channel(s)" % (tmp.shape[0], nc) +print "FS is %f, encoding is %s" % (fs, enc) + +fd, cfilename = mkstemp('pysndfiletest.wav') +try: + wavwrite(tmp, cfilename, fs = 16000, enc = 'pcm24') +finally: + remove(cfilename) diff --git a/telemeta/visualization/scikits/audiolab/docs/examples/quick1.py b/telemeta/visualization/scikits/audiolab/docs/examples/quick1.py new file mode 100644 index 00000000..1d3c4739 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/examples/quick1.py @@ -0,0 +1,5 @@ +import scikits.audiolab as audiolab + +a = audiolab.sndfile('test.flac', 'read') +data = a.read_frames(1000) +a.close() diff --git a/telemeta/visualization/scikits/audiolab/docs/examples/usage1.py b/telemeta/visualization/scikits/audiolab/docs/examples/usage1.py new file mode 100644 index 00000000..a689a01f --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/examples/usage1.py @@ -0,0 +1,6 @@ +import scikits.audiolab as audiolab + +filename = 'test.flac' +a = audiolab.sndfile(filename, 'read') + +print a diff --git a/telemeta/visualization/scikits/audiolab/docs/examples/usage2.py b/telemeta/visualization/scikits/audiolab/docs/examples/usage2.py new file mode 100644 index 00000000..0117e264 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/examples/usage2.py @@ -0,0 +1,12 @@ +import numpy as N + +import scikits.audiolab as audiolab + +filename = 'test.flac' +a = audiolab.sndfile(filename, 'read') + +tmp = a.read_frames(1e4) +float_tmp = a.read_frames(1e4, dtype = N.float32) + +import pylab as P +P.plot(tmp[:]) diff --git a/telemeta/visualization/scikits/audiolab/docs/examples/write1.py b/telemeta/visualization/scikits/audiolab/docs/examples/write1.py new file mode 100644 index 00000000..b0fd1368 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/examples/write1.py @@ -0,0 +1,34 @@ +from tempfile import mkstemp +from os import remove + +import numpy as N +from scikits.audiolab import formatinfo as format +import scikits.audiolab as audiolab + +# Create a temp file in the system temporary dir, and always remove +# it at the end +cd, filename = mkstemp('tmptest.wav') +try: + fmt = format('wav', 'pcm24') + nchannels = 2 + fs = 44100 + + afile = audiolab.sndfile(filename, 'write', fmt, nchannels, fs) + + # Create a stereo white noise, with Gaussian distribution + tmp = 0.1 * N.random.randn(1000, nchannels) + + # Write the first 500 frames of the signal + # Note that the write_frames method uses tmp's numpy dtype to determine how + # to write to the file; sndfile also converts the data on the fly if necessary + afile.write_frames(tmp, 500) + + afile.close() + + # Let's check that the written file has the expected meta data + afile = audiolab.sndfile(filename, 'read') + assert(afile.get_samplerate() == fs) + assert(afile.get_channels() == nchannels) + assert(afile.get_nframes() == 500) +finally: + remove(filename) diff --git a/telemeta/visualization/scikits/audiolab/docs/index.txt b/telemeta/visualization/scikits/audiolab/docs/index.txt new file mode 100644 index 00000000..54110172 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/index.txt @@ -0,0 +1,264 @@ +.. + restindex + page-title: audiolab + crumb: audiolab + link-title: audiolab + encoding: utf-8 + output-encoding: None + file: audiolab1.png + file: quick1.py + file: usage1.py + file: usage2.py + file: format1.py + file: format2.py + file: write1.py + file: matlab1.py + file: audiolab.pdf + /restindex + +.. vim:syntax=rest +.. Last Change: Tue Jul 17 11:00 AM 2007 J + +=============================================================== + Pyaudiolab, a python package to make noise with numpy arrays +=============================================================== + +Introduction +============ + +.. _scipy: http://www.scipy.org +.. _libsndfile: http://www.mega-nerd.com/libsndfile/ + +For people doing audio processing, it is useful to be able to import data from +audio files, and export them back, as well as listening to the results of some +processing; matlab have functions such as wavread, wavwrite, soundsc, etc... +for that purposes. The goal of audiolab is to give those capabilities to the +`scipy`_ environment by wrapping the excellent library `libsndfile`_ from Erik +Castro de Lopo. Pyaudio supports all format supported by libsndfile, including +wav, aiff, ircam files, and flac (an open source lossless compressed format); +see `here `_ for a complete +list. + + **Note**: The library is still in beta stage: reading and writing + data is possible, but only in frames, not per item. + Also, the ability to play data on the system's soundcard is not there yet. + I have never encountered any data corruption, except when using the buggy + ctypes included in Ubuntu's python 2.5 (bug which was solved recently). + + **Note**: The online version of this document is not always up to date. The + pdf included in the package is the reference, and always in sync with the + package. If something does not work, please refer first to the pdf included in + the package. + +.. contents:: Tables of contents + +Download and installation +========================= + +Download +-------- + +audiolab is part of scikits: its source can be downloaded directly from the +scikits svn repository: svn co http://svn.scipy.org/svn/scikits/trunk/audiolab + +Requirements +------------ + +audiolab requires the following softwares: + + - a python interpreter. + - libsndfile (including the header sndfile.h, which means linux users should + download the libsndfile-dev package). + - numpy (any version >= 1.0 should work). + - ctypes (version >= 1.0.1) + +Starting with version 2.5, python include ctypes in its standart library, so you +don't need to install ctypes separately in this case. + +It has been run succesfully on the following platforms: + + - linux ubuntu (32 and 64 bits) + - windows XP + +I would be interested to hear anyone who succeesfully used it on other +plateforms (Mac Os X, solaris, etc...). + + **Note**: the ctypes used in python2.5 package in ubuntu (and maybe debian + as well) *had* a nasty bug which makes it impossible to use 64 bits integers. You + should NOT use this package with audiolab (importing audiolab should fail, + but if the version is not correctly detected, you will have file corruption when + writing data to audio files). Run the test to check everything is working (a + test case tests this issue). + + ``_ + + +Installation +------------ + +For unix users, if libsndfile is installed in standart location (eg /usr/lib, +/usr/local/lib), the installer should be able to find them automatically, and +you only need to do a "python setup.py install". In other cases, you need to +create a file site.cfg to set the location of libsndfile and its header (there +are site.cfg examples which should give you an idea how to use them on your +platform). + +For windows users: the library distributed by Erik Castro de Lopo cannot be +used directly; you need to follow the instructions given in libsndfile +distribution in the file README-precompiled-dll.txt. See also site.cfg.win32. + +License +------- + +audiolab is released under the LGPL, which forces you to release back the +modifications you may make in the version of audiolab you are distributing, +but you can still use it in closed softwares. + +Quick view +========== + +The following code shows you how to open a file for read, reading the first +1000 frames, and closing it: + +.. raw:: html + + {mycolorize;input/softwares/audiolab/quick1.py} + +.. raw:: latex + + \input{quick1.tex} + +Usage +===== + +Opening a file and getting its parameters +----------------------------------------- + +Once imported, audiolab gives you access the sndfile class, which is the +class of audiolab use to open audio files. +You create a sndfile instance when you want +to open a file for reading or writing (the file test.flac is included +in the audiolab package, in the test_data directory): + +.. raw:: html + + {mycolorize;input/softwares/audiolab/usage1.py} + +.. raw:: latex + + \input{usage1.tex} + +Prints you the informations related to the file, like its sampling rate, +the number of frames, etc... You can of course get each parameter +individually by using the corresponding sndfile.get* accessors. + +Importing audio data +-------------------- + +Now that we've opened a file, we would like to read its audio content, +right ? For now, you can only import the data as floating point data, +float (32 bits) or double (64 bits). The function +sndfile.read_frames read n frames, +where a frame contains a sample of each channel (one in mono, 2 in stereo, +etc...): + +.. raw:: html + + {mycolorize;input/softwares/audiolab/usage2.py} + +.. raw:: latex + + \input{usage2.tex} + +The above code import 10000 frames, and plot the first channel using matplotlib +(see below). A frame holds one sample from each channel: 1000 frames of a stereo +file is 2000 samples. Each channel is one column of the numpy array. The read +functions follow numpy conventions, that is by default, the data are read as +double, but you can give a dtype argument to the function. + +.. image:: audiolab1.png + :width: 500 + :height: 400 + +The format class +---------------- + +When opening a file for writing, you need to give various parameters related to +the format such as the file format, the encoding. The format class is used to +create valid formats from those parameters By default, the format class creates +a format object with file type wav, and 16 bits pcm encoding: + +.. raw:: html + + {mycolorize;input/softwares/audiolab/format1.py} + +.. raw:: latex + + \input{format1.tex} + +prints back "Major Format: AIFF (Apple/SGI), Encoding Format: U-Law" +and "Major Format: SF (Berkeley/IRCAM/CARL), Encoding Format: 32 bit float". + +To get a list of all possible file format and encoding, the function +supported_* are available: + +.. raw:: html + + {mycolorize;input/softwares/audiolab/format2.py} + +.. raw:: latex + + \input{format2.tex} + + **Note**: not all combination of encoding, endianness and format are possible. + If you try to create a format with incompatible values, you will get an error + while creating an instance of format. + +Writing data to a file +---------------------- + +Opening a file for writing is a bit more complicated than reading; you need to +say which format you are requesting, the number of channels and the sampling +rate (in Hz) you are requesting; all thoses information are mandatory ! The +class format is used to build a format understable by libsndfile from +'user-friendly' values. Let's see how it works. + +.. raw:: html + + {mycolorize;input/softwares/audiolab/write1.py} + +.. raw:: latex + + \input{write1.tex} + +Matlab-like API +--------------- + +audiolab also have a matlab-like API for audio IO. Its usage is as similar as it +can get using python: + +.. raw:: html + + {mycolorize;input/softwares/audiolab/matlab1.py} + +.. raw:: latex + + \input{matlab1.tex} + +Known bugs: +=========== + + - there seems to be a problem when using libsndfile fseek facilities with flac + files (which are necessary for the functions flacread/flacwrite). The + problem seems to be with libFLAC; for this reason, seek in flac files is not + enabled by default for now. See FLAC_SUPPORT.txt for more informations. + +TODO +==== + +audiolab is still in early stages. Before a release, I would like to implement the +follwings: + + - support (at least some) meta-data embedded in some audio files format. + - support the libsndfile's error system + - player on all major plateforms (at least linux/windows/max OS X) diff --git a/telemeta/visualization/scikits/audiolab/docs/user.tex b/telemeta/visualization/scikits/audiolab/docs/user.tex new file mode 100644 index 00000000..6ea70994 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/docs/user.tex @@ -0,0 +1,64 @@ +% Last Change: Wed Jan 31 08:00 PM 2007 J +% vim:syntax=tex + +\newcommand\at{@} +\newcommand\lb{[} +\newcommand\rb{]} +\newcommand\Cba[1]{\textcolor[rgb]{0.67,0.13,1.00}{\textbf{#1}}} +\newcommand\Caz[1]{\textcolor[rgb]{0.00,0.25,0.82}{#1}} +\newcommand\Cay[1]{\textcolor[rgb]{0.67,0.13,1.00}{#1}} +\newcommand\Cax[1]{\textcolor[rgb]{0.00,0.63,0.00}{#1}} +\newcommand\Cbc[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\newcommand\Cas[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\newcommand\Car[1]{\textcolor[rgb]{0.72,0.53,0.04}{#1}} +\newcommand\Caq[1]{\textcolor[rgb]{0.73,0.27,0.27}{\textit{#1}}} +\newcommand\Cap[1]{\textcolor[rgb]{0.72,0.53,0.04}{#1}} +\newcommand\Caw[1]{\textcolor[rgb]{0.67,0.13,1.00}{\textbf{#1}}} +\newcommand\Cav[1]{\textcolor[rgb]{0.60,0.60,0.60}{\textbf{#1}}} +\newcommand\Cau[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\newcommand\Cat[1]{\textcolor[rgb]{0.67,0.13,1.00}{\textbf{#1}}} +\newcommand\Cak[1]{\textbf{#1}} +\newcommand\Caj[1]{\textcolor[rgb]{0.73,0.40,0.53}{#1}} +\newcommand\Cai[1]{\textcolor[rgb]{0.72,0.53,0.04}{#1}} +\newcommand\Cah[1]{\textcolor[rgb]{0.63,0.63,0.00}{#1}} +\newcommand\Cao[1]{\textcolor[rgb]{0.53,0.00,0.00}{#1}} +\newcommand\Can[1]{\textcolor[rgb]{0.00,0.50,0.00}{#1}} +\newcommand\Cam[1]{\textcolor[rgb]{0.73,0.40,0.13}{\textbf{#1}}} +\newcommand\Cal[1]{\textcolor[rgb]{0.67,0.13,1.00}{\textbf{#1}}} +\newcommand\Cac[1]{\textcolor[rgb]{0.73,0.27,0.27}{#1}} +\newcommand\Cab[1]{\textit{#1}} +\newcommand\Caa[1]{\textcolor[rgb]{0.50,0.50,0.50}{#1}} +\newcommand\Cag[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\newcommand\Caf[1]{\textcolor[rgb]{0.00,0.53,0.00}{\textit{#1}}} +\newcommand\Cae[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\newcommand\Cad[1]{\textcolor[rgb]{0.73,0.27,0.27}{#1}} +\newcommand\Cbb[1]{\textcolor[rgb]{0.73,0.27,0.27}{#1}} +\newcommand\CaZ[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\newcommand\CaY[1]{\textcolor[rgb]{0.00,0.00,0.50}{\textbf{#1}}} +\newcommand\CaX[1]{\textcolor[rgb]{0.00,0.50,0.00}{\textbf{#1}}} +\newcommand\Cbd[1]{\textcolor[rgb]{0.73,0.40,0.53}{\textbf{#1}}} +\newcommand\Cbe[1]{\textcolor[rgb]{0.67,0.13,1.00}{\textbf{#1}}} +\newcommand\CaS[1]{\textcolor[rgb]{0.50,0.00,0.50}{\textbf{#1}}} +\newcommand\CaR[1]{\textcolor[rgb]{0.00,0.53,0.00}{\textit{#1}}} +\newcommand\CaQ[1]{\textcolor[rgb]{0.72,0.53,0.04}{#1}} +\newcommand\CaP[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\newcommand\CaW[1]{\textcolor[rgb]{0.73,0.27,0.27}{#1}} +\newcommand\CaV[1]{\textcolor[rgb]{0.67,0.13,1.00}{#1}} +\newcommand\CaU[1]{\textcolor[rgb]{0.73,0.27,0.27}{#1}} +\newcommand\CaT[1]{\textcolor[rgb]{0.00,0.00,1.00}{\textbf{#1}}} +\newcommand\CaK[1]{\textcolor[rgb]{0.67,0.13,1.00}{#1}} +\newcommand\CaJ[1]{\textcolor[rgb]{0.00,0.63,0.00}{#1}} +\newcommand\CaI[1]{\textcolor[rgb]{0.73,0.27,0.27}{#1}} +\newcommand\CaH[1]{\textcolor[rgb]{0.67,0.13,1.00}{\textbf{#1}}} +\newcommand\CaO[1]{\textcolor[rgb]{0.73,0.27,0.27}{#1}} +\newcommand\CaN[1]{\textcolor[rgb]{0.00,0.00,0.50}{\textbf{#1}}} +\newcommand\CaM[1]{\textcolor[rgb]{0.00,0.00,1.00}{#1}} +\newcommand\CaL[1]{\textcolor[rgb]{0.00,0.53,0.00}{#1}} +\newcommand\CaC[1]{\textcolor[rgb]{0.00,0.53,0.00}{\textit{#1}}} +\newcommand\CaB[1]{\textcolor[rgb]{0.82,0.25,0.23}{\textbf{#1}}} +\newcommand\CaA[1]{\textcolor[rgb]{0.67,0.13,1.00}{#1}} +\newcommand\CaG[1]{\fcolorbox[rgb]{1.00,0.00,0.00}{1,1,1}{#1}} +\newcommand\CaF[1]{\textcolor[rgb]{0.72,0.53,0.04}{#1}} +\newcommand\CaE[1]{\textcolor[rgb]{1.00,0.00,0.00}{#1}} +\newcommand\CaD[1]{\textcolor[rgb]{0.63,0.00,0.00}{#1}} + diff --git a/telemeta/visualization/scikits/audiolab/info.py b/telemeta/visualization/scikits/audiolab/info.py new file mode 100644 index 00000000..47dd67f6 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/info.py @@ -0,0 +1,2 @@ +VERSION = '0.8dev' +ignore = False diff --git a/telemeta/visualization/scikits/audiolab/matapi.py b/telemeta/visualization/scikits/audiolab/matapi.py new file mode 100644 index 00000000..1568fe9c --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/matapi.py @@ -0,0 +1,148 @@ +#! /usr/bin/env python +# Last Change: Mon Sep 10 07:00 PM 2007 J + +# Copyright (C) 2006-2007 Cournapeau David +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +"""This module implements functions to read and write to audio files easily +(ala matlab: wavread, etc...).""" + +import numpy as N + +from pysndfile import formatinfo, sndfile +from pysndfile import PyaudioException, FlacUnsupported + +__all__ = [] +_MATAPI_FORMAT = ['wav', 'aiff', 'au', 'sdif', 'flac'] +for i in _MATAPI_FORMAT: + __all__.extend(['%sread' % i, '%swrite' % i]) + +# writer function factory +def _writer_factory(name, format, def_fs, descr): + """ Create a writer function with fileformat described by format, default + sampling rate def_fs, and docstring descr.""" + def basic_writer(data, filename, fs = def_fs, enc = format.get_encoding()): + """Common "template" to all write functions.""" + if N.ndim(data) <= 1: + nc = 1 + nframes = N.size(data) + elif N.ndim(data) == 2: + nc = data.shape[1] + nframes = data.shape[0] + else: + RuntimeError("Only rank 0, 1, and 2 arrays supported as audio data") + + hdl = sndfile(filename, 'write', format, nc, fs) + try: + hdl.write_frames(data, nframes) + finally: + hdl.close() + doc = \ + """ wrapper around pysndfile to write %s file, + in a similar manner to matlab's wavwrite/auwrite and the likes. + + OVERWRITES EXISTING FILE ! + + Args: + - data: a rank 0, 1 (mono) or 2 (one channel per col) numpy array + - filename: a string for the audio file name + - fs: the sampling rate in Hz (%d Hz by default). + - enc: a string for the encoding such as 'pcm16', etc...(%s by + default). Not supported yet ! + + For a total control over options, such as endianness, append data to an + existing file, etc... you should use sndfile class instances instead""" \ + % (str(descr), def_fs, format.get_encoding()) + basic_writer.__doc__ = doc + basic_writer.__name__ = name + return basic_writer + +# template for reader functions +def _reader_factory(name, filetype, descr): + """Factory for reader functions ala matlab.""" + def basic_reader(filename, last = None, first = 0): + """Common "template" to all read functions.""" + hdl = sndfile(filename, 'read') + try: + if not hdl.get_file_format() == filetype: + raise PyaudioException("%s is not a %s file (is %s)" \ + % (filename, filetype, hdl.get_file_format())) + + fs = hdl.get_samplerate() + enc = hdl.get_encoding() + # Set the pointer to start position + nf = hdl.seek(first, 1) + if not nf == first: + raise IOError("Error while seeking at starting position") + + if last is None: + nframes = hdl.get_nframes() - first + data = hdl.read_frames(nframes) + else: + data = hdl.read_frames(last) + finally: + hdl.close() + + return data, fs, enc + doc = \ + """ wrapper around pysndfile to read a %s file in float64, + in a similar manner to matlab wavread/auread/etc... + + Returns a tuple (data, fs, enc), where : + - data are the read data (one column per channel) + - fs, the sampling rate + - enc, a string which is the encoding of the file, such as 'pcm16', + 'float32', etc... + + For a total control over options, such as output's dtype, etc..., + you should use sndfile class instances instead""" % (str(descr),) + basic_reader.__doc__ = doc + basic_reader.__name__ = name + return basic_reader + +wavread = _reader_factory('wavread', 'wav', + formatinfo('wav', 'pcm16').get_major_str()) +auread = _reader_factory('auread', 'au', + formatinfo('au', 'pcm16').get_major_str()) +aiffread = _reader_factory('aiffread', 'aiff', + formatinfo('aiff', 'pcm16').get_major_str()) +sdifread = _reader_factory('sdifread', 'ircam', + formatinfo('ircam', 'pcm16').get_major_str()) + +_f1 = formatinfo('wav', 'pcm16') +wavwrite = _writer_factory('wavwrite', _f1, 8000, _f1.get_major_str()) + +_f2 = formatinfo('au', 'ulaw') +auwrite = _writer_factory('auwrite', _f2, 8000, _f2.get_major_str()) + +_f3 = formatinfo('aiff', 'pcm16') +aiffwrite = _writer_factory('aiffwrite', _f3, 8000, _f3.get_major_str()) + +_f4 = formatinfo('ircam', 'pcm16') +sdifwrite = _writer_factory('sdifwrite', _f4, 44100, _f4.get_major_str()) + +try: + flacread = _reader_factory('flacread', 'flac', + formatinfo('flac', 'pcm16').get_major_str()) + _f5 = formatinfo('flac', 'pcm16') + flacwrite = _writer_factory('flacwrite', _f5, 44100, _f5.get_major_str()) +except FlacUnsupported,e: + print e + print "Matlab API for FLAC is disabled" + def missing_flacread(*args): + raise UnimplementedError("Matlab API for FLAC is disabled on your "\ + "installation") + flacread = missing_flacread + flacwrite = missing_flacread diff --git a/telemeta/visualization/scikits/audiolab/misc/Makefile b/telemeta/visualization/scikits/audiolab/misc/Makefile new file mode 100644 index 00000000..2fcbde26 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/misc/Makefile @@ -0,0 +1,23 @@ +CC = colorgcc +LD = gcc + +CFLAGS = -Wall -W + +winfdopen: winfdopen.o + $(LD) $< -o $@ -lsndfile + +winfdopen.o: winfdopen.c + $(CC) $(CFLAGS) -c $< -o $@ + +badflac: badflac.o + $(LD) $< -o $@ -lsndfile + +badflac.o: badflac.c + $(CC) $(CFLAGS) -c $< -o $@ + +test_badflac: badflac badflac.flac + ./badflac badflac.flac + +clean: + rm -f *.o + rm -f badflac diff --git a/telemeta/visualization/scikits/audiolab/misc/Sconstruct b/telemeta/visualization/scikits/audiolab/misc/Sconstruct new file mode 100644 index 00000000..6c29ab33 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/misc/Sconstruct @@ -0,0 +1,5 @@ +# vim:syntax=python +# Last Change: Fri Jun 01 12:00 PM 2007 J +# +mainobj = Object('winfdopen.c') +winfd = Program(mainobj, LIBS = ['sndfile']) diff --git a/telemeta/visualization/scikits/audiolab/misc/badflac.c b/telemeta/visualization/scikits/audiolab/misc/badflac.c new file mode 100644 index 00000000..906ad44f --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/misc/badflac.c @@ -0,0 +1,37 @@ +#include +#include + +#include + +int main(int argc, char *argv[]) +{ + SF_INFO info; + SNDFILE* file; + sf_count_t nf; + + char buffer [2048] ; + if (argc < 2) { + fprintf(stderr, "usage: badflac filename \n"); + exit(EXIT_FAILURE); + } + + info.format = 0; + file = sf_open(argv[1], SFM_READ, &info); + if (file == NULL) { + fprintf(stderr, "%s:%s failed opening file %s\n", __FILE__, __func__, argv[1]); + sf_command (file, SFC_GET_LOG_INFO, buffer, sizeof (buffer)) ; + fprintf(stderr, "sndfile error is %s:\n", buffer); + exit(EXIT_FAILURE); + } + + fprintf(stderr, "Values of seek are on this platform: SET %d, CUR %d, END %d\n", + SEEK_SET, SEEK_CUR, SEEK_END); + + fprintf(stderr, "trying to seek %lld frames\n", (long long)1); + nf = sf_seek(file, 1, SEEK_CUR); + fprintf(stderr, "seeked through %lld frames\n", nf); + + sf_close(file); + + return 0; +} diff --git a/telemeta/visualization/scikits/audiolab/misc/badflac.flac b/telemeta/visualization/scikits/audiolab/misc/badflac.flac new file mode 100644 index 0000000000000000000000000000000000000000..70d0a657092c55b85f36faabf54a6f1a62f2a41b GIT binary patch literal 38385 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�G<2-Bs?@fSjRKfcPc`{ZX9$wB-;F`K5)AfZ}YiJ@tsUfngbK)L~f(iuX z2s+eg)g130nRL-N(zIQXZ#BS;GOjU z?EWt^!zO#yqCk*-Jfy$Ru^%ylqsaGLGIW!1cN4U#btcZ~3(|-X2_D$CU6dKJ^aQw~ z*Ggmz^c_FpPY}57Q?@7Rpr`^zC*~nTRuJU0SKsIj-N2+->(kJ%++j+XK3b&&O2`c+ z8^u_*3F=5A9R||jv*Xp1NgSN=n;)f;(7*LLaVWj(B_;UgF!Z`<|v!%EN zyC+|q7A=@lquhtDwOMt9a&Wrpq5py)xS6?-fRR=`xMrZ=}@M~LXh1uLwjQ#?&-p;oIOo3a5|@7G?>`k2LlW@lq8Xyadc|z=tJ8`4#wct#CE4^1W5{$PpNh%fd zNip+gVUnNVUZQAu&8H%byj-Y@x@Wk+|kVsx_9s|5Z1!lUy>?6pd5Pj(rM!U@je z_0z#8`-iymYm}c)M^PA +#include +#include + +#include + +#ifdef _MSC_VER +#define __func__ __FUNCTION__ +#else +#include +#endif + +int test(const char* filename, int byfd); + +int main(int argc, char *argv[]) +{ + int st; + + if (argc < 2) { + fprintf(stderr, "usage: %s filename \n", argv[0]); + exit(EXIT_FAILURE); + } + + st = test(argv[1], 0); + if (st) { + fprintf(stderr, "Error while opening directly\n"); + } else { + fprintf(stderr, "Opening directly is fine\n"); + } + + st = test(argv[1], 1); + if (st) { + fprintf(stderr, "Error while opening by fd\n"); + } else { + fprintf(stderr, "Opening by fd is fine\n"); + } + + return 0; +} + +/* If byfd is true, try to open the file with sf_open_fd */ +int test(const char* filename, int byfd) +{ + SF_INFO info; + SNDFILE* file; + int fid, flags, st; + char buffer [2048]; + + st = 0; + + flags = O_RDONLY; +#if (defined (WIN32) || defined (_WIN32)) + flags |= O_BINARY; +#endif + + info.format = 0; + if (byfd) { + fid = open(filename, flags); + if (fid < 0) { + fprintf(stderr, "%s:%s failed opening file %s\n", __FILE__, __func__, filename); + return -1; + } + + file = sf_open_fd(fid, SFM_READ, &info, SF_TRUE); + } else { + file = sf_open(filename, SFM_READ, &info); + } + + if (file == NULL) { + fprintf(stderr, "%s:%s failed opening file %s\n", __FILE__, __func__, filename); + sf_command (file, SFC_GET_LOG_INFO, buffer, sizeof (buffer)) ; + fprintf(stderr, "sndfile error is %s:\n", buffer); + close(fid); + exit(EXIT_FAILURE); + } else { + fprintf(stderr, "%s:%s file %s has %d frames \n", __FILE__, __func__, filename, info.frames); + } + + sf_close(file); + + return st; +} diff --git a/telemeta/visualization/scikits/audiolab/pyaudioio.py b/telemeta/visualization/scikits/audiolab/pyaudioio.py new file mode 100644 index 00000000..94c1e9fc --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/pyaudioio.py @@ -0,0 +1,68 @@ +#! /usr/bin/env python +# Last Change: Tue May 22 10:00 AM 2007 J + +# Copyright (C) 2006-2007 Cournapeau David +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) any +# later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with this library; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# TODO: +# - find out why finally does not work with KeyboardInterrupt instances + +from tempfile import mkstemp +from os import remove, popen + +from pysndfile import sndfile, formatinfo as format + +def play(input, sr = 22050): + """play(input, sr = 22050): "play" a numpy array input + to the audio device using aplay command, @ sampling rate + sr. + + Warning: This is really crude, as it copies the numpy array + into an audio file before writing: I am too lazy to write + interfaces to also, windows and co...""" + # Check inputs are OK + if input.ndim == 1: + nc = 1 + nframes = input.size + else: + (nframes, nc) = input.shape + + # Create tmp file + fd, filename = mkstemp('py_player') + + # Copy the data into it + b = sndfile(filename, 'write', format('wav', 'pcm16'), nc, sr) + b.write_frames(input, nframes) + b.close() + + # Play using an audio command + try: + cmd = "aplay %s" % filename + popen(cmd) + remove(filename) + except KeyboardInterrupt, inst: + remove(filename) + raise inst + +if __name__ == '__main__': + # Read the content of a file into numpy array, and play the numpy + # array using the play command + import numpy as N + sr = 22050 + # Play a really small noise to avoid screaming in loudspeakers + # or headphones. + noise = 0.0001 * N.random.randn((sr)) + play(noise, sr) diff --git a/telemeta/visualization/scikits/audiolab/pysndfile.py.in b/telemeta/visualization/scikits/audiolab/pysndfile.py.in new file mode 100644 index 00000000..574e5bb1 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/pysndfile.py.in @@ -0,0 +1,886 @@ +#! /usr/bin/env python +# Last Change: Wed Oct 03 05:00 PM 2007 J + +# Copyright (C) 2006-2007 Cournapeau David +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# vim:syntax=python + +# TODO: +# - import format classes so that we get meaningful information from an +# existing format +# - better API for reader/writer, including integer formats and partial +# reading +# - ability to get log of sndfile ? +# - check how to play sound under windows, OS X and other UNIX + +"""This module implements the wrappers around libsndfile.""" + +__docformat__ = 'restructuredtext' + +#__all__ = ['sndfile', 'formatinfo'] + +import copy +import warnings + +#================ +# Load libsndfile +#================ +import ctypes +from ctypes import cdll, Structure, c_int, pointer, POINTER, \ + create_string_buffer, c_char_p, sizeof, string_at +try: + from ctypes import c_int64 +except ImportError, e: + print "Cannot import c_int64 from ctypes: if you are on ubuntu/debian," +\ + " this is likely because ctypes was compiled with libffi. see" +\ + " https://launchpad.net/ubuntu/+source/python2.5/+bug/71914" + raise e + +from numpy.ctypeslib import ndpointer +CTYPES_MAJOR = int(ctypes.__version__.split('.')[0]) +CTYPES_MINOR = int(ctypes.__version__.split('.')[1]) +CTYPES_MICRO = int(ctypes.__version__.split('.')[2]) +if CTYPES_MAJOR < 1 or (CTYPES_MINOR == 0 and CTYPES_MICRO < 1): + raise ImportError("version of ctypes is %s, expected at least %s" \ + % (ctypes.__version__, '1.0.1')) +import numpy as N + +_SND = cdll.LoadLibrary('%SHARED_LOCATION%') + +#========================= +# Definition of constants +#========================= +# READ/WRITE Mode +%SFM% + +# SF BOOL +%SF_BOOL% + +# Format +%SF_FORMAT% + +# ENDIANESS +%SF_ENDIAN% + +# Commands +%SF_COMMAND% + +%SF_ERR% + +# format equivalence: dic used to create internally +# the right enum values from user friendly strings +py_to_snd_encoding_dic = { + 'pcms8' : SF_FORMAT['SF_FORMAT_PCM_S8'], + 'pcm16' : SF_FORMAT['SF_FORMAT_PCM_16'], + 'pcm24' : SF_FORMAT['SF_FORMAT_PCM_24'], + 'pcm32' : SF_FORMAT['SF_FORMAT_PCM_32'], + + 'pcmu8' : SF_FORMAT['SF_FORMAT_PCM_U8'], + + 'float32' : SF_FORMAT['SF_FORMAT_FLOAT'], + 'float64' : SF_FORMAT['SF_FORMAT_DOUBLE'], + + 'ulaw' : SF_FORMAT['SF_FORMAT_ULAW'], + 'alaw' : SF_FORMAT['SF_FORMAT_ALAW'], + 'ima_adpcm' : SF_FORMAT['SF_FORMAT_IMA_ADPCM'], + 'ms_adpcm' : SF_FORMAT['SF_FORMAT_MS_ADPCM'], + + 'gsm610' : SF_FORMAT['SF_FORMAT_GSM610'], + 'vox_adpcm' : SF_FORMAT['SF_FORMAT_VOX_ADPCM'], + + 'g721_32' : SF_FORMAT['SF_FORMAT_G721_32'], + 'g723_24' : SF_FORMAT['SF_FORMAT_G723_24'], + 'g723_40' : SF_FORMAT['SF_FORMAT_G723_40'], + + 'dww12' : SF_FORMAT['SF_FORMAT_DWVW_12'], + 'dww16' : SF_FORMAT['SF_FORMAT_DWVW_16'], + 'dww24' : SF_FORMAT['SF_FORMAT_DWVW_24'], + 'dwwN' : SF_FORMAT['SF_FORMAT_DWVW_N'], + + 'dpcm8' : SF_FORMAT['SF_FORMAT_DPCM_8'], + 'dpcm16': SF_FORMAT['SF_FORMAT_DPCM_16'] +} + +py_to_snd_file_format_dic = { + 'wav' : SF_FORMAT['SF_FORMAT_WAV'], + 'aiff' : SF_FORMAT['SF_FORMAT_AIFF'], + 'au' : SF_FORMAT['SF_FORMAT_AU'], + 'raw' : SF_FORMAT['SF_FORMAT_RAW'], + 'paf' : SF_FORMAT['SF_FORMAT_PAF'], + 'svx' : SF_FORMAT['SF_FORMAT_SVX'], + 'nist' : SF_FORMAT['SF_FORMAT_NIST'], + 'voc' : SF_FORMAT['SF_FORMAT_VOC'], + 'ircam' : SF_FORMAT['SF_FORMAT_IRCAM'], + 'wav64' : SF_FORMAT['SF_FORMAT_W64'], + 'mat4' : SF_FORMAT['SF_FORMAT_MAT4'], + 'mat5' : SF_FORMAT['SF_FORMAT_MAT5'], + 'pvf' : SF_FORMAT['SF_FORMAT_PVF'], + 'xi' : SF_FORMAT['SF_FORMAT_XI'], + 'htk' : SF_FORMAT['SF_FORMAT_HTK'], + 'sds' : SF_FORMAT['SF_FORMAT_SDS'], + 'avr' : SF_FORMAT['SF_FORMAT_AVR'], + 'wavex' : SF_FORMAT['SF_FORMAT_WAVEX'], + 'sd2' : SF_FORMAT['SF_FORMAT_SD2'], + 'flac' : SF_FORMAT['SF_FORMAT_FLAC'], + 'caf' : SF_FORMAT['SF_FORMAT_CAF'] +} + +py_to_snd_endianness_dic = { + 'file' : SF_ENDIAN['SF_ENDIAN_FILE'], + 'little' : SF_ENDIAN['SF_ENDIAN_LITTLE'], + 'big' : SF_ENDIAN['SF_ENDIAN_BIG'], + 'cpu' : SF_ENDIAN['SF_ENDIAN_CPU'] +} + +# Those following dic are used internally to get user-friendly values from +# sndfile enum +SND_TO_PY_ENCODING = \ + dict([(i, j) for j, i in py_to_snd_encoding_dic.items()]) +SND_TO_PY_FILE_FORMAT = \ + dict([(i, j) for j, i in py_to_snd_file_format_dic.items()]) +SND_TO_PY_ENDIANNESS = \ + dict([(i, j) for j, i in py_to_snd_endianness_dic.items()]) + +#========================================== +# Check that libsndfile is expected version +#========================================== +def get_libsndfile_version(): + nverbuff = 256 + verbuff = create_string_buffer(nverbuff) + n = _SND.sf_command(c_int(0), c_int(SF_COMMAND['SFC_GET_LIB_VERSION']), + verbuff, nverbuff) + if n < 1: + raise Exception("Error while getting version of libsndfile") + + # Transform the buffer into a string + ver = "" + for i in range(n): + ver += verbuff[i] + + # Get major, minor and micro from version + version = ver.split('-')[1] + prerelease = 0 + major, minor, micro = [i for i in version.split('.')] + try: + micro = int(micro) + except ValueError,e: + print "micro is " + str(micro) + micro, prerelease = micro.split('pre') + + return int(major), int(minor), int(micro), prerelease + +MAJOR, MINOR, MICRO, PRERELEASE = get_libsndfile_version() +if not(MAJOR == 1): + raise Exception("audiolab expects major version %d of libsndfile" % 1) +if not(MICRO == 17): + if PRERELEASE == 0: + prestr = "No" + else: + prestr = "%s" % PRERELEASE + print "WARNING libsndfile-%d.%d.%d (prerelease: %s) "\ + "this has only been tested with libsndfile 1.0.17 for now, "\ + "use at your own risk !" % (MAJOR, MINOR, MICRO, prestr) + +#================ +# Python wrappers +#================ + +#+++++++++++++++++ +# Public exception +#+++++++++++++++++ +class PyaudioException(Exception): + pass + +class InvalidFormat(PyaudioException): + pass + +class PyaudioIOError(PyaudioException, IOError): + pass + +class WrappingError(PyaudioException): + pass + +class FlacUnsupported(RuntimeError, PyaudioException): + pass + +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Private classes/function (Should not be used outside this file) +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +class _sf_info(Structure): + """Structure representing C structure SF_INFO""" + _fields_ = [('frames', c_int64), + ('samplerate', c_int), + ('channels', c_int), + ('format', c_int), + ('sections', c_int), + ('seekable', c_int)] + def __str__(self): + return "%d frames, sr = %d Hz, %d channels, format is %d" % \ + (self.frames, self.samplerate, self.channels, self.format) + +class _sf_format_info(Structure): + """Structure representing C structure SF_FORMAT_INFO (useful for + sf_command )""" + _fields_ = [('format', c_int), + ('name', c_char_p), + ('extension', c_char_p)] + def __str__(self): + return "format hex is %#010x, name is %s, extension is %s" % \ + (self.format, self.name, self.extension) + + def __repr__(self): + print self.__str__() + +class _sndfile(Structure): + pass + +sf_info_p = POINTER(_sf_info) +sndfile_p = POINTER(_sndfile) + +# functions args +# TODO: is there a way to ensure that arg1 is the right kind of pointer ? +arg1 = c_char_p +arg2 = c_int +arg3 = sf_info_p +_SND.sf_open.argtypes = [arg1, arg2, arg3] +_SND.sf_open.restype = sndfile_p + +arg1 = sndfile_p +_SND.sf_close.argtypes = [arg1] +_SND.sf_close.restype = c_int + +arg1 = c_int +arg2 = c_int +arg3 = sf_info_p +arg4 = c_int +_SND.sf_open_fd.argtypes = [arg1, arg2, arg3, arg4] +_SND.sf_open_fd.restype = sndfile_p + +arg1 = sndfile_p +arg2 = ndpointer(dtype=N.float64) +arg3 = c_int64 + +# double function +_SND.sf_readf_double.argtypes = [arg1, arg2, arg3] +_SND.sf_readf_double.restype = c_int64 + +_SND.sf_writef_double.argtypes = [arg1, arg2, arg3] +_SND.sf_writef_double.restype = c_int64 + +# float function +arg1 = sndfile_p +arg2 = ndpointer(dtype=N.float32) +arg3 = c_int64 +_SND.sf_readf_float.argtypes = [arg1, arg2, arg3] +_SND.sf_readf_float.restype = c_int64 + +_SND.sf_writef_float.argtypes = [arg1, arg2, arg3] +_SND.sf_writef_float.restype = c_int64 + +# int function +arg1 = sndfile_p +arg2 = ndpointer(dtype=N.int32) +arg3 = c_int64 +_SND.sf_readf_int.argtypes = [arg1, arg2, arg3] +_SND.sf_readf_int.restype = c_int64 + +_SND.sf_writef_int.argtypes = [arg1, arg2, arg3] +_SND.sf_writef_int.restype = c_int64 + +# short function +arg1 = sndfile_p +arg2 = ndpointer(dtype=N.int16) +arg3 = c_int64 +_SND.sf_readf_short.argtypes = [arg1, arg2, arg3] +_SND.sf_readf_short.restype = c_int64 + +_SND.sf_writef_short.argtypes = [arg1, arg2, arg3] +_SND.sf_writef_short.restype = c_int64 + +# Error functions +arg1 = sndfile_p +_SND.sf_strerror.argtypes = [arg1] +_SND.sf_strerror.restype = c_char_p + +# Function to sync data to file +arg1 = sndfile_p +_SND.sf_write_sync.argtypes = [arg1] + +# Function to seek +arg1 = sndfile_p +arg2 = c_int64 +arg3 = c_int +_SND.sf_seek.argtypes = [arg1, arg2, arg3] +_SND.sf_seek.restype = c_int64 + +# To pass when a C function needs a NULL arg +_cNULL = POINTER(c_int)() + +class _format_from_internal: + """Class to handle audio format with sndfile. + + DO NOT USE THIS CLASS OUTSIDE pysndfile.py MODULE: YOU MAY CRASH YOUR + INTERPRETER ! + + Basically, we have 3 classes of parameters: + - the main format: (major format), like wav, aiff, etc... + - the subtype format: pcm, bits resolution + - endianness: little, big, as the cpu, default of the format + + This class encapsulates those parameters, and can build a representation of + them from the format integer of sf_info. This should *NOT* be used, use + format instead, which inherits this class to build a valid format from user + friendly arguments. """ + def __init__(self, format_integer): + # Get the internal values which corresponds to the values libsndfile + # can understand + self._int_type = format_integer & SF_FORMAT['SF_FORMAT_TYPEMASK'] + self._int_encoding = format_integer & SF_FORMAT['SF_FORMAT_SUBMASK'] + self._int_endianness = format_integer & SF_FORMAT['SF_FORMAT_ENDMASK'] + + assert format_integer == self._int_type | self._int_encoding |\ + self._int_endianness + self._format = format_integer + + # Now, we need to test if the combination of format, encoding and + # endianness is valid. sf_format_check needs also samplerate and + # channel information, so just give a fake samplerate and channel + # number. Looking at sndfile.c, it looks like samplerate is never + # actually checked, and that when channels is checked, it is only + # checked against values different than 1 or 2, so giving a value of + # 1 to channel should be ok. + self._sfinfo = _sf_info() + self._sfinfo.channels = 1 + self._sfinfo.samplerate = 8000 + self._sfinfo.format = self._format + + ret = _SND.sf_format_check(pointer(self._sfinfo)) + if ret is not SF_BOOL['SF_TRUE']: + raise InvalidFormat() + + # Get the sndfile string description of the format type + blop = _sf_format_info() + blop.format = self._int_type + st = _SND.sf_command(_cNULL, SF_COMMAND['SFC_GET_FORMAT_INFO'], \ + pointer(blop), sizeof(blop)) + if st is not 0: + if SND_TO_PY_FILE_FORMAT[self._int_type] == 'flac': + raise FlacUnsupported("Flac is not supported by your version"\ + " of libsndfile") + else: + raise WrappingError("Could not get format string for format "\ + "%d, " % blop.format + "please report this problem "\ + "to the maintainer") + + self.format_str = blop.name + + # Get the sndfile string description of the format subtype + blop.format = self._int_encoding + st = _SND.sf_command(_cNULL, SF_COMMAND['SFC_GET_FORMAT_INFO'], \ + pointer(blop), sizeof(blop)) + if st is not 0: + raise WrappingError() + + self.encoding_str = blop.name + + def get_format_raw(self): + """Do not use this function !""" + return self._format + + def get_major_str(self): + """Do not use this function !""" + return self.format_str + + def get_encoding_str(self): + """Do not use this function !""" + return self.encoding_str + + def get_file_format(self): + """return user friendly file format string""" + return SND_TO_PY_FILE_FORMAT[self._int_type] + + def get_encoding(self): + """return user friendly encoding string""" + return SND_TO_PY_ENCODING[self._int_encoding] + + def get_endianness(self): + """return user friendly file format string""" + return SND_TO_PY_ENDIANNESS[self._int_endianness] + + # Various function + def is_type(self, t): + return (self._format & SF_FORMAT['SF_FORMAT_TYPEMASK']) \ + == py_to_snd_file_format_dic[t] + + # Syntactic sugar + def __str__(self): + return "Major Format: %s, Encoding Format: %s" % \ + (self.format_str, self.encoding_str) + + def __repr__(self): + return self.__str__() + +#+++++++++++ +# Public API +#+++++++++++ + +class formatinfo(_format_from_internal): + def __init__(self, type = 'wav', encoding = 'pcm16', endianness = 'file'): + """Build a valid format usable by the sndfile class when opening an + audio file for writing. + + Blah blah + + :Parameters: + type : string + represents the major file format (wav, etc...). + encoding : string + represents the encoding (pcm16, etc..). + endianness : string + represents the endianess. + + Notes + ----- + + Valid type strings are listed by file_format_dic.keys() Valid encoding + strings are listed by encoding_dic.keys() Valid endianness strings are + listed by endianness_dic.keys() """ + # Keep the arguments + self.type = type + self.encoding = encoding + self.endianness = endianness + + # Get the internal values which corresponds to the values libsndfile + # can understand + self._int_type = py_to_snd_file_format_dic[type] + self._int_encoding = py_to_snd_encoding_dic[encoding] + self._int_endianness = py_to_snd_endianness_dic[endianness] + + # Build the internal integer from parameters, and pass it to the super + # class, which will do all the work + format = self._int_type | self._int_encoding | self._int_endianness + + _format_from_internal.__init__(self, format) + +class sndfile: + """Main class to open, read and write audio files""" + def __init__(self, filename, mode = 'read', format = None, channels = 0, \ + samplerate = 0): + """Create an instance of sndfile. + + :Parameters: + filename : string or int + name of the file to open (string), or file descriptor (integer) + mode : string + 'read' for read, 'write' for write, or 'rwrite' for read and + write. + format : formatinfo + when opening a new file for writing, give the format to write + in. + channels : int + number of channels. + samplerate : int + sampling rate. + + :Returns: + sndfile: a valid sndfile object + + Notes + ----- + + format, channels and samplerate need to be given only in the write + modes and for raw files. """ + # Check the mode is one of the expected values + if mode == 'read': + sfmode = SFM['SFM_READ'] + elif mode == 'write': + sfmode = SFM['SFM_WRITE'] + if format == None: + raise Exception("For write mode, you should provide"\ + "a format argument !") + elif mode == 'rwrite': + sfmode = SFM['SFM_RDWR'] + if format == None: + raise Exception("For write mode, you should provide"\ + "a format argument !") + else: + raise Exception("mode %s not recognized" % str(mode)) + + sfinfo = _sf_info() + sfinfo_p = pointer(sfinfo) + + # Fill the sfinfo struct + sfinfo.frames = c_int64(0) + if type(channels) is not int: + print "Warning, channels is converted to int, was %s" % \ + str(type(channels)) + sfinfo.channels = int(channels) + else: + sfinfo.channels = channels + + if type(samplerate) is not int: + print "Warning, sampling rate is converted to int, was %s" % \ + str(type(samplerate)) + sfinfo.samplerate = int(samplerate) + else: + sfinfo.samplerate = samplerate + + sfinfo.sections = 0 + sfinfo.seekable = False + if mode == 'read' and format == None: + sfinfo.format = 0 + else: + if sfinfo.channels > 256 or sfinfo.channels < 1: + msg = "number of channels is %d, expected " \ + "between 1 and 256" % sfinfo.channels + raise RuntimeError(msg) + sfinfo.format = format.get_format_raw() + if not _SND.sf_format_check(sfinfo_p): + msg = "unknown error in format specification ?" +\ + " Please report this to the author" + raise WrappingError() + + sfinfo_p = pointer(sfinfo) + self._sfmode = sfmode + self.hdl = 0 + + if type(filename) == int: + res = _SND.sf_open_fd(filename, self._sfmode, sfinfo_p, + SF_BOOL['SF_FALSE']) + self._byfd = True + self.fd = filename + self.filename = "" + else: + res = _SND.sf_open(filename, self._sfmode, sfinfo_p) + self._byfd = False + self.filename = filename + + try: + # If res is NULL, this statement will raise a ValueError exception + a = res[0] + except ValueError: + if self._byfd: + msg = "error while opening file descriptor %d\n\t->" % self.fd + else: + msg = "error while opening file %s\n\t-> " % self.filename + msg += _SND.sf_strerror(res) + if self._byfd: + msg += """ +(Check that the mode argument passed to sndfile is the same than the one used +when getting the file descriptor, eg do not pass 'read' to sndfile if you +passed 'write' to open to get the file descriptor. If you are on win32, you are +out of luck, because its implementation of POSIX open is broken)""" + raise IOError("error while opening %s\n\t->%s" % (filename, msg)) + + if mode == 'read': + tmp = _format_from_internal(sfinfo.format) + self._format = formatinfo(tmp.get_file_format(), \ + tmp.get_encoding(), tmp.get_endianness()) + else: + self._format = format + + self._sfinfo = sfinfo + self.hdl = res + + if self.get_file_format() == 'flac': + def SeekNotEnabled(self, *args): + raise FlacUnsupported("seek not supported on Flac by default,"\ + " because\n some version of FLAC libraries are buggy."\ + " Read FLAC_SUPPORT.txt") + self.seek = SeekNotEnabled + else: + self.seek = self._seek + + def __del__(self, close_func = _SND.sf_close): + # Stupid python needs the close_func, otherwise + # it may clean ctypes before calling here + if hasattr(self,'hdl'): + if not(self.hdl == 0): + close_func(self.hdl) + self.hdl = 0 + + def close(self): + """close the file.""" + self.__del__() + + def sync(self): + """call the operating system's function to force the writing of all + file cache buffers to disk the file. + + No effect if file is open as read""" + _SND.sf_write_sync(self.hdl) + + def _seek(self, offset, whence = 0, mode = 'rw'): + """similar to python seek function, taking only in account audio data. + + :Parameters: + offset : int + the number of frames (eg two samples for stereo files) to move + relatively to position set by whence. + whence : int + only 0 (beginning), 1 (current) and 2 (end of the file) are + valid. + mode : string + If set to 'rw', both read and write pointers are updated. If + 'r' is given, only read pointer is updated, if 'w', only the + write one is (this may of course make sense only if you open + the file in a certain mode). + + Notes + ----- + + - one only takes into accound audio data. + - if an invalid seek is given (beyond or before the file), a + PyaudioIOError is launched.""" + c_offset = _num2int64(offset) + if mode == 'rw': + # Update both read and write pointers + st = _SND.sf_seek(self.hdl, c_offset, whence) + elif mode == 'r': + whence = whence | SFM['SFM_READ'] + st = _SND.sf_seek(self.hdl, c_offset, whence) + elif mode == 'w': + whence = whence | SFM['SFM_WRITE'] + st = _SND.sf_seek(self.hdl, c_offset, whence) + else: + raise ValueError("mode should be one of 'r', 'w' or 'rw' only") + + if st == -1: + msg = "Error while seeking, libsndfile error is %s" \ + % (_SND.sf_strerror(self.hdl)) + raise PyaudioIOError(msg) + return st + + # Functions to get informations about the file + def get_nframes(self): + """ Return the number of frames of the file""" + if self._sfmode == SFM['SFM_READ']: + # XXX: is this reliable for any file (think pipe and co ?) + return self._sfinfo.frames + else: + # In write/rwrite mode, the only reliable way to get the number of + # frames is to use seek. + raise NotImplementedError("Sorry, getting the current number of" + "frames in write modes is not supported yet") + + def get_samplerate(self): + """ Return the samplerate in Hz of the file""" + return self._sfinfo.samplerate + + def get_channels(self): + """ Return the number of channels of the file""" + return self._sfinfo.channels + + def get_file_format(self): + """return user friendly file format string""" + return SND_TO_PY_FILE_FORMAT[self._format._int_type] + + def get_encoding(self): + """return user friendly encoding string""" + return SND_TO_PY_ENCODING[self._format._int_encoding] + + def get_endianness(self): + """return user friendly file format string""" + return SND_TO_PY_ENDIANNESS[self._format._int_endianness] + + #------------------ + # Functions to read + #------------------ + def read_frames(self, nframes, dtype = N.float64): + """Read nframes frames of the file. + + :Parameters: + nframes : int + number of frames to read. + dtype : numpy dtype + dtype of the returned array containing read data (see note). + + Notes + ----- + + - read_frames updates the read pointer. + - One column is one channel. + - if float are requested when the file contains integer data, you will + get normalized data (that is the max possible integer will be 1.0, + and the minimal possible value -1.0). + - if integers are requested when the file contains floating point data, + it may give wrong results because there is an ambiguity: if the + floating data are normalized, you can get a file with only 0 ! + Getting integer data from files encoded in normalized floating point + is not supported (yet: sndfile supports it).""" + c_nframes = _num2int64(nframes) + if c_nframes < 0: + raise ValueError("number of frames has to be >= 0") + + # XXX: inout argument + if self._sfinfo.channels > 1: + y = N.zeros((nframes, self._sfinfo.channels), dtype) + else: + y = N.zeros(nframes, dtype) + + if dtype == N.float64: + res = _SND.sf_readf_double(self.hdl, y, c_nframes) + elif dtype == N.float32: + res = _SND.sf_readf_float(self.hdl, y, c_nframes) + elif dtype == N.int32: + res = _SND.sf_readf_int(self.hdl, y, c_nframes) + elif dtype == N.int16: + res = _SND.sf_readf_short(self.hdl, y, c_nframes) + else: + RuntimeError("Sorry, only float, double, int and short read " + \ + "supported for now") + + if not(res == nframes): + msg = "Read %d frames, expected to read %d" % (res, nframes) + msg += ", libsndfile last msg is \n\t%s" \ + % _SND.sf_strerror(self.hdl) + raise IOError(msg) + + return y + + #------------------- + # Functions to write + #------------------- + # TODO: Think about overflow vs type of input, etc... + def write_frames(self, input, nframes = -1): + """write data to file. + + :Parameters: + input : ndarray + array containing data to write. + nframes : int + number of frames to write. + + Notes + ----- + + - one channel is one column + - updates the write pointer. + - if float are given when the file contains integer data, you should + put normalized data (that is the range [-1..1] will be written as the + maximum range allowed by the integer bitwidth).""" + # First, get the number of channels and frames from input + if input.ndim == 1: + nc = 1 + else: + if input.ndim > 2: + raise Exception("Expect array of rank <= 2, got %d" \ + % input.ndim) + nc = input.shape[1] + + if nframes == -1: + nframes = N.size(input) + # Number of channels should be the one expected + if not(nc == self._sfinfo.channels): + raise Exception("Expected %d channels, got %d" % \ + (self._sfinfo.channels, nc)) + + # Writing to the file + c_nframes = _num2int64(nframes) + if c_nframes < 0: + raise ValueError("number of frames has to be >= 0") + + input = N.require(input, requirements = 'C') + + if input.dtype == N.float32: + if self._check_overflow(input): + warnings.warn("Warning, overflow detected when writing.") + res = _SND.sf_writef_float(self.hdl, input, c_nframes) + elif input.dtype == N.float64: + self._check_overflow(input) + if self._check_overflow(input): + warnings.warn("Warning, overflow detected when writing.") + res = _SND.sf_writef_double(self.hdl, input, c_nframes) + elif input.dtype == N.int32: + res = _SND.sf_writef_int(self.hdl, input, c_nframes) + elif input.dtype == N.int16: + res = _SND.sf_writef_short(self.hdl, input, c_nframes) + else: + raise Exception("type of input not understood: input should" + " be float64 or float32""") + + if not(res == nframes): + raise IOError("write %d frames, expected to write %d" \ + % res, nframes) + + def _check_overflow(self, data): + if N.max(data ** 2) >= 1.: + return True + return False + + # Syntactic sugar + def __repr__(self): + return self.__str__() + + def __str__(self): + repstr = "----------------------------------------\n" + if self._byfd: + repstr += "File : %d (opened by file descriptor)\n" % self.fd + else: + repstr += "File : %s\n" % self.filename + repstr += "Channels : %d\n" % self._sfinfo.channels + repstr += "Sample rate : %d\n" % self._sfinfo.samplerate + repstr += "Frames : %d\n" % self._sfinfo.frames + repstr += "Raw Format : %#010x -> %s\n" % \ + (self._format.get_format_raw(), self._format.get_major_str()) + repstr += "File format : %s\n" % self.get_file_format() + repstr += "Encoding : %s\n" % self.get_encoding() + repstr += "Endianness : %s\n" % self.get_endianness() + repstr += "Sections : %d\n" % self._sfinfo.sections + if self._sfinfo.seekable: + seek = 'True' + else: + seek = 'False' + repstr += "Seekable : %s\n" % seek + repstr += "Duration : %s\n" % self._generate_duration_str() + return repstr + + def _generate_duration_str(self): + if self._sfinfo.samplerate < 1: + return None + tsec = self._sfinfo.frames / self._sfinfo.samplerate + hrs = tsec / 60 / 60 + tsec = tsec % (60 ** 2) + mins = tsec / 60 + tsec = tsec % 60 + secs = tsec + ms = 1000 * self._sfinfo.frames / self._sfinfo.samplerate % 1000 + + return "%02d:%02d:%02d.%3d" % (hrs, mins, secs, ms) + +def supported_format(): + # XXX: broken + return py_to_snd_file_format_dic.keys() + +def supported_endianness(): + # XXX: broken + return py_to_snd_endianness_dic.keys() + +def supported_encoding(): + # XXX: broken + return py_to_snd_encoding_dic.keys() + +def _num2int64(value): + """ Convert a python objet to a c_int64, safely.""" + if not (type(value) == int or type(value) == long): + value = long(value) + print "Warning, converting %s to long" % str(value) + c_value = c_int64(value) + if not c_value.value == value: + raise RuntimeError("Error while converting %s to a c_int64"\ + ", maybe %s is too big ?" % str(value)) + return c_value diff --git a/telemeta/visualization/scikits/audiolab/soundio/SConstruct b/telemeta/visualization/scikits/audiolab/soundio/SConstruct new file mode 100644 index 00000000..1d1b221e --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/soundio/SConstruct @@ -0,0 +1,2 @@ +Program('main', source = ['simple.c'], LIBS = ['asound']) +Program('main2', source = ['simple2.c'], LIBS = ['asound']) diff --git a/telemeta/visualization/scikits/audiolab/soundio/_alsa.pyx b/telemeta/visualization/scikits/audiolab/soundio/_alsa.pyx new file mode 100644 index 00000000..5fb6a117 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/soundio/_alsa.pyx @@ -0,0 +1,198 @@ +cdef extern from "alsa/asoundlib.h": + ctypedef enum snd_pcm_stream_t: + SND_PCM_STREAM_PLAYBACK + SND_PCM_STREAM_CAPTURE + ctypedef enum snd_pcm_access_t : + SND_PCM_ACCESS_MMAP_INTERLEAVED + SND_PCM_ACCESS_MMAP_NONINTERLEAVED + SND_PCM_ACCESS_MMAP_COMPLEX + SND_PCM_ACCESS_RW_INTERLEAVED + SND_PCM_ACCESS_RW_NONINTERLEAVED + ctypedef enum snd_pcm_format_t : + SND_PCM_FORMAT_UNKNOWN + SND_PCM_FORMAT_S8 + SND_PCM_FORMAT_U8 + SND_PCM_FORMAT_S16_LE + SND_PCM_FORMAT_S16_BE + SND_PCM_FORMAT_U16_LE + SND_PCM_FORMAT_U16_BE + SND_PCM_FORMAT_S24_LE + SND_PCM_FORMAT_S24_BE + SND_PCM_FORMAT_U24_LE + SND_PCM_FORMAT_U24_BE + SND_PCM_FORMAT_S32_LE + SND_PCM_FORMAT_S32_BE + SND_PCM_FORMAT_U32_LE + SND_PCM_FORMAT_U32_BE + SND_PCM_FORMAT_FLOAT_LE + SND_PCM_FORMAT_FLOAT_BE + SND_PCM_FORMAT_FLOAT64_LE + SND_PCM_FORMAT_FLOAT64_BE + SND_PCM_FORMAT_IEC958_SUBFRAME_LE + SND_PCM_FORMAT_IEC958_SUBFRAME_BE + SND_PCM_FORMAT_MU_LAW + SND_PCM_FORMAT_A_LAW + SND_PCM_FORMAT_IMA_ADPCM + SND_PCM_FORMAT_MPEG + SND_PCM_FORMAT_GSM + SND_PCM_FORMAT_SPECIAL + SND_PCM_FORMAT_S24_3LE + SND_PCM_FORMAT_S24_3BE + SND_PCM_FORMAT_U24_3LE + SND_PCM_FORMAT_U24_3BE + SND_PCM_FORMAT_S20_3LE + SND_PCM_FORMAT_S20_3BE + SND_PCM_FORMAT_U20_3LE + SND_PCM_FORMAT_U20_3BE + SND_PCM_FORMAT_S18_3LE + SND_PCM_FORMAT_S18_3BE + SND_PCM_FORMAT_U18_3LE + SND_PCM_FORMAT_U18_3BE + SND_PCM_FORMAT_S16 + SND_PCM_FORMAT_U16 + SND_PCM_FORMAT_S24 + SND_PCM_FORMAT_U24 + SND_PCM_FORMAT_S32 + SND_PCM_FORMAT_U32 + SND_PCM_FORMAT_FLOAT + SND_PCM_FORMAT_FLOAT64 + SND_PCM_FORMAT_IEC958_SUBFRAME + + ctypedef struct snd_pcm_t + # XXX: how to make sure the typedef is OK ? + ctypedef unsigned long snd_pcm_uframes_t + + int snd_pcm_open(snd_pcm_t **, char*, int, int) + int snd_pcm_close(snd_pcm_t *) + int snd_pcm_drain(snd_pcm_t *) + + int snd_pcm_set_params(snd_pcm_t *, snd_pcm_format_t, + snd_pcm_access_t, unsigned int, + unsigned int, int, unsigned int) + + int snd_pcm_writei(snd_pcm_t *, void*, snd_pcm_uframes_t) + + char* snd_strerror(int error) + + int snd_card_next(int *icard) + int snd_card_get_name(int icard, char** name) + char* snd_asoundlib_version() + +cdef extern from "numpy/arrayobject.h": + ctypedef int intp + ctypedef extern class numpy.ndarray [object PyArrayObject]: + cdef char *data + cdef int nd + cdef intp *dimensions + cdef intp *strides + cdef int flags + +cdef extern from "stdlib.h": + ctypedef unsigned long size_t + void free(void *ptr) + void *malloc(size_t size) + void *realloc(void *ptr, size_t size) + size_t strlen(char *s) + char *strcpy(char *dest, char *src) + +cdef extern from "stdint.h": + ctypedef unsigned short int16_t + +cdef extern from "Python.h": + object PyString_FromStringAndSize(char *v, int len) + +class AlsaException(Exception): + pass + +def asoundlib_version(): + return snd_asoundlib_version() + +def card_indexes(): + """Returns a list containing index of cards recognized by alsa.""" + cdef int icur = -1 + + cards = [] + while 1: + st = snd_card_next(&icur) + if st < 0: + raise AlsaException("Could not get next card") + if icur < 0: + break + cards.append(icur) + return tuple(cards) + +def card_name(index): + """Get the name of the card corresponding to the given index.""" + cdef char* sptr + st = snd_card_get_name(index, &sptr) + if st < 0: + raise AlsaException("Error while getting card name %d: alsa error "\ + "was %s" % (index, snd_strerror(st))) + else: + cardname = PyString_FromStringAndSize(sptr, len(sptr)) + free(sptr) + return cardname + +cdef class _PCM: + cdef snd_pcm_t* pcmhdl + cdef public char* name + + def __new__(self, device = "default", stream = SND_PCM_STREAM_PLAYBACK): + self.pcmhdl = NULL + + st = snd_pcm_open(&self.pcmhdl, device, stream, 0) + if st < 0: + raise AlsaException("Cannot open device %s: %s" % (device, snd_strerror(st))) + + def __init__(self, device = "default", stream = SND_PCM_STREAM_PLAYBACK): + self.name = device + + def __dealloc__(self): + if self.pcmhdl: + snd_pcm_close(self.pcmhdl) + +cdef class Device: + cdef _PCM pcm + cdef unsigned int samplerate + # XXX: set property instead + cdef public unsigned int channels + cdef snd_pcm_format_t format + cdef snd_pcm_access_t access + def __init__(self, samplerate = 48000, channels = 1, + format = SND_PCM_FORMAT_S16, + access = SND_PCM_ACCESS_RW_INTERLEAVED): + self.pcm = _PCM() + + self.samplerate = samplerate + self.channels = channels + + self.access = access + self.format = format + + st = snd_pcm_set_params(self.pcm.pcmhdl, format, access, channels, + samplerate, 1, 1000000) + if st < 0: + raise AlsaException() + + def play_short(self, ndarray input): + cdef int16_t* p = input.data + cdef int16_t* buff = NULL + #cdef int n = input.dimensions[0] + cdef int n = input.dimensions[0] + cdef int nb = n / 1024 / 16 + cdef snd_pcm_uframes_t bufsz = 1024 * 16 + + assert self.format == SND_PCM_FORMAT_S16 + assert self.access == SND_PCM_ACCESS_RW_INTERLEAVED + + for i from 0 <= i < nb: + st = snd_pcm_writei(self.pcm.pcmhdl, &p[i * bufsz], bufsz) + if not st == bufsz: + print "Error while writing to device", st + print snd_strerror(st) + + snd_pcm_drain(self.pcm.pcmhdl) + + def _get_name(self): + return self.pcm.name + name = property(_get_name) diff --git a/telemeta/visualization/scikits/audiolab/soundio/alsa.py b/telemeta/visualization/scikits/audiolab/soundio/alsa.py new file mode 100644 index 00000000..15754deb --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/soundio/alsa.py @@ -0,0 +1,35 @@ +import numpy as np + +from _alsa import card_name, card_indexes, asoundlib_version +from _alsa import Device, AlsaException + +def play(input, samplerate = 48000): + if input.ndim == 1: + n = input.size + nc = 1 + elif input.ndim == 2: + n, nc = input.shape + else: + raise ValueError("Only ndim 1 or 2 supported") + + if not input.dtype in (np.float32, np.float64): + raise ValueError("input should be array of float32 or float64 !") + + try: + dev = Device(samplerate = samplerate, channels = nc) + dev.play_short((16384 * input).astype(np.int16)) + except AlsaException, e: + raise IOError(str(e)) + +if __name__ == '__main__': + print "Asoundlib version is", asoundlib_version() + for i in card_indexes(): + print card_name(i) + + dev = Device() + print "Device name:", dev.name + + a = 0.2 * np.random.randn(4e4) + play(a, 16000) + play(a, 8000) + play(a, 22050) diff --git a/telemeta/visualization/scikits/audiolab/soundio/alsa_ctypes.py b/telemeta/visualization/scikits/audiolab/soundio/alsa_ctypes.py new file mode 100644 index 00000000..d1c65fc8 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/soundio/alsa_ctypes.py @@ -0,0 +1,440 @@ +#! /usr/bin/env python +# Last Change: Fri Sep 07 02:00 PM 2007 J + +# Copyright (C) 2006-2007 Cournapeau David +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# vim:syntax=python + +"""This is a small module to wrap basic functionalities of alsa: it uses ctypes +for the wrapping.""" + +__docformat__ = 'restructuredtext' + +__all__ = ['asoundlib_version', 'asoundlib_version_numbers', 'card_indexes', \ + 'card_name', 'card_longname'] +#================ +# Load alsa lib +#================ +import ctypes +from ctypes import cdll, Structure, c_int, pointer, POINTER, c_uint,\ + create_string_buffer, c_char_p, sizeof, byref, string_at, c_size_t,\ + c_void_p, c_ulong, c_uint16, c_long, c_uint8, c_int16, c_float +try: + from ctypes import c_int64 +except ImportError, e: + print "Cannot import c_int64 from ctypes: if you are on ubuntu/debian," +\ + " this is likely because ctypes was compiled with libffi. see" +\ + " https://launchpad.net/ubuntu/+source/python2.5/+bug/71914" + raise e + +from numpy.ctypeslib import ndpointer +CTYPES_MAJOR = int(ctypes.__version__.split('.')[0]) +CTYPES_MINOR = int(ctypes.__version__.split('.')[1]) +CTYPES_MICRO = int(ctypes.__version__.split('.')[2]) +if CTYPES_MAJOR < 1 or (CTYPES_MINOR == 0 and CTYPES_MICRO < 1): + raise ImportError("version of ctypes is %s, expected at least %s" \ + % (ctypes.__version__, '1.0.1')) +import numpy as N + +ALSALOC = 'libasound.so.2' +_ALSA = cdll.LoadLibrary(ALSALOC) +# XXX: alsa means linux, normally. How to locate the glibc ? +_C = cdll.LoadLibrary("libc.so.6") + +# Check version +_ALSA.snd_asoundlib_version.args = [] +_ALSA.snd_asoundlib_version.restype = c_char_p + +def asoundlib_version(): + """Return the version of asoundlib as a string""" + version = _ALSA.snd_asoundlib_version() + return version + +def asoundlib_version_numbers(): + """Return the version of asoundlib as a tuple(major, minor, micro).""" + version = asoundlib_version() + return tuple(int(i) for i in version.split('.')) + +major, minor, micro = asoundlib_version_numbers() +if not (major == 1 and minor == 0): + raise RuntimeError("Expected 1.0.x version, got %s instead" % asoundlib_version()) + +# Define pointers to opaque structures +class snd_pcm_t(Structure): + pass + +class snd_pcm_hw_params_t(Structure): + pass + +class snd_pcm_sw_params_t(Structure): + pass + +class snd_output_t(Structure): + pass + +snd_pcm_t_p = POINTER(snd_pcm_t) +snd_pcm_hw_params_t_p = POINTER(snd_pcm_hw_params_t) +snd_pcm_sw_params_t_p = POINTER(snd_pcm_sw_params_t) +snd_output_t_p = POINTER(snd_output_t) + +#------------ +# Define enum +#------------ +SND_PCM_STREAM = {'SND_PCM_STREAM_PLAYBACK' : 0, + 'SND_PCM_STREAM_CAPTURE' : 1} + +#----------------------------------------------- +# Define all the function we need from asoundlib +#----------------------------------------------- +arg1 = c_int +arg2 = POINTER(c_char_p) +_ALSA.snd_card_get_name.argtypes = [arg1, arg2] +_ALSA.snd_card_get_name.restype = c_int + +arg1 = c_int +arg2 = POINTER(c_char_p) +_ALSA.snd_card_get_longname.argtypes = [arg1, arg2] +_ALSA.snd_card_get_longname.restype = c_int + +arg1 = POINTER(c_int) +_ALSA.snd_card_next.argtypes = [arg1] +_ALSA.snd_card_next.restype = c_int + +arg1 = c_int +_ALSA.snd_strerror.argtypes = [arg1] +_ALSA.snd_strerror.restype = c_char_p + +# output related functions +arg1 = POINTER(snd_output_t_p) +_ALSA.snd_output_buffer_open.argtypes = [arg1] +_ALSA.snd_output_buffer_open.restype = c_int + +arg1 = snd_output_t_p +_ALSA.snd_output_close.argtypes = [arg1] +_ALSA.snd_output_close.restype = c_int + +arg1 = snd_output_t_p +arg2 = POINTER(c_char_p) +_ALSA.snd_output_buffer_string.argtypes = [arg1, arg2] +_ALSA.snd_output_buffer_string.restype = c_size_t + +arg1 = snd_pcm_t_p +arg2 = snd_output_t_p +_ALSA.snd_pcm_dump.argtypes = [arg1, arg2] +_ALSA.snd_pcm_dump.restype = c_int + +# pcm related functions +arg1 = POINTER(POINTER(snd_pcm_t)) +arg2 = c_char_p +arg3 = c_int +arg4 = c_int +_ALSA.snd_pcm_open.argtypes = [arg1, arg2, arg3, arg4] +_ALSA.snd_pcm_open.restype = c_int + +arg1 = POINTER(snd_pcm_t) +_ALSA.snd_pcm_close.argtypes = [arg1] +_ALSA.snd_pcm_close.restype = c_int + +arg1 = POINTER(snd_pcm_t) +_ALSA.snd_pcm_reset.argtypes = [arg1] +_ALSA.snd_pcm_reset.restype = c_int + +arg1 = POINTER(snd_pcm_t) +_ALSA.snd_pcm_drain.argtypes = [arg1] +_ALSA.snd_pcm_drain.restype = c_int + +arg1 = POINTER(snd_pcm_hw_params_t_p) +_ALSA.snd_pcm_hw_params_malloc.argtypes = [arg1] +_ALSA.snd_pcm_hw_params_malloc.restype = c_int + +arg1 = snd_pcm_hw_params_t_p +_ALSA.snd_pcm_hw_params_free.argtypes = [arg1] +_ALSA.snd_pcm_hw_params_free.restype = c_int + +arg1 = snd_pcm_t_p +arg2 = snd_pcm_hw_params_t_p +_ALSA.snd_pcm_hw_params_any.argtypes = [arg1, arg2] +_ALSA.snd_pcm_hw_params_any.restype = c_int + +arg1 = snd_pcm_t_p +arg2 = snd_pcm_hw_params_t_p +_ALSA.snd_pcm_hw_params.argtypes = [arg1, arg2] +_ALSA.snd_pcm_hw_params.restype = c_int + +arg1 = snd_pcm_t_p +arg2 = snd_pcm_hw_params_t_p +arg3 = c_int +_ALSA.snd_pcm_hw_params_set_access.argtypes = [arg1, arg2, arg3] +_ALSA.snd_pcm_hw_params_set_access.restype = c_int + +arg1 = snd_pcm_t_p +arg2 = snd_pcm_hw_params_t_p +arg3 = POINTER(c_uint) +arg4 = c_int +_ALSA.snd_pcm_hw_params_set_rate_near.argtypes = [arg1, arg2, arg3, arg4] +_ALSA.snd_pcm_hw_params_set_rate_near.restype = c_int + +arg1 = snd_pcm_t_p +arg2 = snd_pcm_hw_params_t_p +arg3 = c_uint +_ALSA.snd_pcm_hw_params_set_channels.argtypes = [arg1, arg2, arg3] +_ALSA.snd_pcm_hw_params_set_channels.restype = c_int + +arg1 = snd_pcm_t_p +arg2 = snd_pcm_hw_params_t_p +arg3 = c_int +_ALSA.snd_pcm_hw_params_set_format.argtypes = [arg1, arg2, arg3] +_ALSA.snd_pcm_hw_params_set_format.restype = c_int + +arg1 = snd_pcm_t_p +arg2 = snd_pcm_hw_params_t_p +arg3 = POINTER(c_ulong) +_ALSA.snd_pcm_hw_params_set_buffer_size_near.argtypes = [arg1, arg2, arg3] +_ALSA.snd_pcm_hw_params_set_buffer_size_near.restype = c_int + +arg1 = snd_pcm_t_p +arg2 = snd_pcm_hw_params_t_p +arg3 = POINTER(c_ulong) +arg4 = c_int +_ALSA.snd_pcm_hw_params_set_period_size_near.argtypes = [arg1, arg2, arg3, arg4] +_ALSA.snd_pcm_hw_params_set_period_size_near.restype = c_int + +# write +arg1 = snd_pcm_t_p +arg2 = c_void_p +arg3 = c_ulong +_ALSA.snd_pcm_writei.argtypes = [arg1, arg2, arg3] +_ALSA.snd_pcm_writei.restype = c_ulong + +#================== +# General functions +#================== +class AlsaException(Exception): + pass + +class OutputBuffer(): + def __init__(self): + ohdl = snd_output_t_p() + st = _ALSA.snd_output_buffer_open(byref(ohdl)) + if st: + raise AlsaException("Error creating output buffer") + self._hdl = ohdl + + def __del__(self, close_func = _ALSA.snd_output_close): + if hasattr(self, '_hdl'): + if not(self._hdl == 0): + close_func(self._hdl) + self._hdl = 0 + + +OHDL = OutputBuffer() + +#======================== +# Cards related functions +#======================== +def card_indexes(): + """Returns a list containing index of cards recognized by alsa.""" + cards = [] + cur = c_int(-1) + while 1: + st = _ALSA.snd_card_next(byref(cur)) + if st < 0: + raise AlsaException("Could not get next card") + if cur.value < 0: + break + cards.append(cur.value) + return tuple(cards) + +def card_name(index): + """Get the name of the card corresponding to the given index.""" + sptr = c_char_p(0) + st = _ALSA.snd_card_get_name(index, byref(sptr)) + if st < 0: + raise AlsaException("Error while getting card name %d: alsa error "\ + "was %s" % (index, _ALSA.snd_strerror(st))) + cardname = string_at(sptr) + _C.free(sptr) + return cardname + +def card_longname(index): + """Get the long name of the card corresponding to the given index.""" + sptr = c_char_p(0) + st = _ALSA.snd_card_get_longname(index, byref(sptr)) + if st < 0: + raise AlsaException("Error while getting card longname %d: alsa error "\ + "was %s" % (index, _ALSA.snd_strerror(st))) + cardname = string_at(sptr) + _C.free(sptr) + return cardname + +#======================= +# Pcm related functions +#======================= +class _HwParams: + """Small class to assure that the hw parmas are always freed.""" + def __init__(self): + self._hdl = snd_pcm_hw_params_t_p() + st = _ALSA.snd_pcm_hw_params_malloc(byref(self._hdl)) + if st: + raise AlsaException("error while creating hw params %s" \ + % _ALSA.snd_strerror(st)) + + def __del__(self, close_func = _ALSA.snd_pcm_hw_params_free): + if hasattr(self, '_hdl'): + if not(self._hdl == 0): + close_func(self._hdl) + self._hdl = 0 + +class Pcm: + def __init__(self, device = 'default', samplerate = 48000, channels = 1): + self._pcmhdl = POINTER(snd_pcm_t)() + + # Open the pcm device + st = _ALSA.snd_pcm_open(byref(self._pcmhdl), device, + SND_PCM_STREAM['SND_PCM_STREAM_PLAYBACK'], 0) + if st: + raise AlsaException("error while opening pcm device %s; alsa error is %s"\ + % (device, _ALSA.snd_strerror(st))) + + # Set hw params + self._set_hw_params(samplerate, channels) + + # Reset the pcm device + st = _ALSA.snd_pcm_reset(self._pcmhdl) + if st: + raise AlsaException("error while resetting pcm : %s" \ + % _ALSA.snd_strerror(st)) + + self.nc = channels + self.samplerate = samplerate + + def _set_hw_params(self, samplerate, channels): + # XXX: Parameters copied from sndfile-play.c from libsndfile. Check the + # meaning + alsa_period_size = c_ulong(1024) + alsa_buffer_frames = c_ulong(alsa_period_size.value * 4) + + hw = _HwParams() + hwhdl = hw._hdl + st = _ALSA.snd_pcm_hw_params_any(self._pcmhdl, hwhdl) + if st < 0: + raise AlsaException("cannot initialize hw st: %s" \ + % _ALSA.snd_strerror(st)) + + st = _ALSA.snd_pcm_hw_params_set_access(self._pcmhdl, hwhdl, 3) + if st < 0: + raise AlsaException("cannot initialize hw st: %s" \ + % _ALSA.snd_strerror(st)) + + rrate = c_uint(samplerate) + st = _ALSA.snd_pcm_hw_params_set_rate_near(self._pcmhdl, hwhdl, byref(rrate), 0) + if st < 0: + raise AlsaException("cannot set samplerate : %s" \ + % _ALSA.snd_strerror(st)) + + st = _ALSA.snd_pcm_hw_params_set_format(self._pcmhdl, hwhdl, 14) + if st < 0: + raise AlsaException("cannot set format : %s" \ + % _ALSA.snd_strerror(st)) + + st = _ALSA.snd_pcm_hw_params_set_channels(self._pcmhdl, hwhdl, channels) + if st < 0: + raise AlsaException("cannot set number of channels : %s" \ + % _ALSA.snd_strerror(st)) + + st = _ALSA.snd_pcm_hw_params_set_buffer_size_near(self._pcmhdl, hwhdl, + byref(alsa_buffer_frames)) + if st < 0: + raise AlsaException("cannot set buffer size: %s" \ + % _ALSA.snd_strerror(st)) + + st = _ALSA.snd_pcm_hw_params_set_period_size_near(self._pcmhdl, hwhdl, + byref(alsa_period_size), 0) + if st < 0: + raise AlsaException("cannot set period size: %s" \ + % _ALSA.snd_strerror(st)) + + st = _ALSA.snd_pcm_hw_params(self._pcmhdl, hwhdl) + if st < 0: + raise AlsaException("cannot set params : %s" \ + % _ALSA.snd_strerror(st)) + + def __del__(self, close_func = _ALSA.snd_pcm_close): + if hasattr(self, '_pcmhdl'): + try: + self._pcmhdl[0] + close_func(self._pcmhdl) + self._pcmhdl = POINTER(snd_pcm_t)() + except ValueError: + pass + + def __str__(self): + buf = c_char_p() + msg = "Pcm device: " + if self._pcmhdl > 0: + msg += " Opened\n" + # XXX error checking + st = _ALSA.snd_pcm_dump(self._pcmhdl, OHDL._hdl) + st = _ALSA.snd_output_buffer_string(OHDL._hdl, byref(buf)) + if st > 0: + msg += string_at(buf) + return msg + + def __repr__(self): + return self.__str__() + + def write_float(self, data): + bufsz = 1024 * 2 + nb = data.size / bufsz / self.nc + for i in range(nb): + b = a[i * bufsz: (i+1) * bufsz] + st = _ALSA.snd_pcm_writei(self._pcmhdl, + b.ctypes.data_as(POINTER(c_float)), bufsz) + if not st == bufsz: + print "Error while writing to device" + b = a[nb * bufsz: -1] + reframes = b.size / self.nc + st = _ALSA.snd_pcm_writei(self._pcmhdl, + b.ctypes.data_as(POINTER(c_float)), reframes) + if not st == reframes: + print "Error while writing to device" + + _ALSA.snd_pcm_drain(self._pcmhdl) +#class AlsaPlayer: +# def __init__(self, device = "default"): +# pcm = _Pcm(device) +# hw = _HwParams() +# err = _ALSA.snd_pcm_hw_params_any(pcm.pcmhdl, hw.hwparamshdl) +# if err: +# raise AlsaException(" + +if __name__ == '__main__': + print card_indexes() + print [card_name(i) for i in card_indexes()] + print [card_longname(i) for i in card_indexes()] + print asoundlib_version() + print asoundlib_version_numbers() + from scikits.audiolab import wavread + a, fs = wavread('test.wav')[:2] + if a.ndim > 1: + nc = a.shape[1] + else: + nc = 1 + a = a.astype(N.float32) + pcm = Pcm("default:1", 22050, channels = nc) + print pcm + pcm.write_float(a) diff --git a/telemeta/visualization/scikits/audiolab/soundio/setup.py b/telemeta/visualization/scikits/audiolab/soundio/setup.py new file mode 100644 index 00000000..a2234513 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/soundio/setup.py @@ -0,0 +1,12 @@ +from distutils.core import setup +from distutils.extension import Extension +from Cython.Distutils import build_ext +from numpy.distutils.misc_util import get_numpy_include_dirs + +setup(name = "PyrexGuide", + ext_modules=[ + Extension("_alsa", ["_alsa.pyx"], + libraries = ['asound'], + include_dirs = get_numpy_include_dirs())], + cmdclass = {'build_ext': build_ext}) + diff --git a/telemeta/visualization/scikits/audiolab/soundio/simple.c b/telemeta/visualization/scikits/audiolab/soundio/simple.c new file mode 100644 index 00000000..96e1c37f --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/soundio/simple.c @@ -0,0 +1,74 @@ +#include + +#include + +int main() +{ + snd_pcm_t *pcm; + snd_pcm_hw_params_t *hw; + + int st; + unsigned int rrate = 44100; + snd_output_t *output; + + st = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (st) { + fprintf(stderr, "Error opening pcm\n"); + return -1; + } + + st = snd_pcm_hw_params_malloc(&hw); + if (st) { + fprintf(stderr, "Error while allocating hw\n"); + return -1; + } + + st = snd_pcm_hw_params_any(pcm, hw); + if (st) { + fprintf(stderr, "Error while allocating hw\n"); + return -1; + } + + st = snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_RW_INTERLEAVED); + if (st) { + fprintf(stderr, "Error while allocating hw\n"); + return -1; + } + + st = snd_pcm_hw_params_set_rate_near(pcm, hw, &rrate, 0); + if (st) { + fprintf(stderr, "Error while allocating hw\n"); + return -1; + } + fprintf(stdout, "rate is %d\n", rrate); + + st = snd_pcm_hw_params(pcm, hw); + if (st) { + fprintf(stderr, "Error while allocating hw\n"); + return -1; + } + + //st = snd_output_stdio_attach(&output, stdout, 0); + //if (st < 0) { + // printf("Output failed: %s\n", snd_strerror(st)); + // return 0; + //} + st = snd_output_buffer_open(&output); + if (st < 0) { + printf("Output buffer open failed: %s\n", snd_strerror(st)); + return 0; + } + char* buf; + snd_pcm_dump(pcm, output); + st = snd_output_buffer_string(output, &buf); + fprintf(stdout, "%d chars\n", st); + fprintf(stdout, "%s", buf); + + //snd_output_close(output); + //snd_pcm_hw_params_free(hw); + snd_pcm_close(pcm); + + snd_config_update_free_global(); + + return 0; +} diff --git a/telemeta/visualization/scikits/audiolab/soundio/simple2.c b/telemeta/visualization/scikits/audiolab/soundio/simple2.c new file mode 100644 index 00000000..401d78dd --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/soundio/simple2.c @@ -0,0 +1,80 @@ +#include +#include + +#include + +int play(snd_pcm_t *pcm) +{ + int16_t buff[1024 * 16]; + int i, j; + snd_pcm_uframes_t frames; + + for (i = 0; i < 4; ++i) { + for (j = 0; j < 1024 * 16; ++j) { + buff[j] = random() & 0xff; + } + frames = snd_pcm_writei(pcm, buff, 1024 * 16); + fprintf(stderr, "%lu\n", frames); + } + + return 0; +} + +int set(snd_pcm_t *pcm, snd_pcm_format_t format, snd_pcm_access_t access, + unsigned int channels, unsigned int samplerate) +{ + int st; + + st = snd_pcm_set_params(pcm, format, access, channels, samplerate, + 1, 1000000); + if (st < 0) { + printf("Output failed: %s\n", snd_strerror(st)); + return st; + } + + return 0; +} + +int main() +{ + snd_pcm_t *pcm; + snd_pcm_hw_params_t *hw; + + int st; + unsigned int rrate = 48000; + unsigned int nc = 1; + snd_output_t *output; + + int i, j; + snd_pcm_sframes_t frames; + + st = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (st) { + fprintf(stderr, "Error opening pcm\n"); + return -1; + } + + st = snd_output_stdio_attach(&output, stdout, 0); + if (st < 0) { + printf("Output failed: %s\n", snd_strerror(st)); + return -1; + } + + set(pcm, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 44100); + + play(pcm); + snd_pcm_drain(pcm); + + set(pcm, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 8000); + + play(pcm); + + //snd_pcm_dump(pcm, output); + + snd_output_close(output); + snd_pcm_close(pcm); + + snd_config_update_free_global(); + + return 0; +} diff --git a/telemeta/visualization/scikits/audiolab/tests/__init__.py b/telemeta/visualization/scikits/audiolab/tests/__init__.py new file mode 100644 index 00000000..7f2fbc0c --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/tests/__init__.py @@ -0,0 +1,2 @@ +#! /usr/bin/env python +# Last Change: Mon May 21 12:00 PM 2007 J diff --git a/telemeta/visualization/scikits/audiolab/tests/test_matapi.py b/telemeta/visualization/scikits/audiolab/tests/test_matapi.py new file mode 100644 index 00000000..fe17b8e9 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/tests/test_matapi.py @@ -0,0 +1,164 @@ +#! /usr/bin/env python +# Last Change: Tue Jul 17 11:00 AM 2007 J +from os.path import join, dirname +from os import remove +from tempfile import mkstemp + +from numpy.testing import * +import numpy as N + +set_package_path() +from audiolab.matapi import wavread, auread, aiffread, sdifread, flacread +from audiolab.matapi import wavwrite, auwrite, aiffwrite, sdifwrite, flacwrite +from audiolab.pysndfile import PyaudioException, sndfile, formatinfo as audio_format +from audiolab.pysndfile import FlacUnsupported +restore_path() + +#Optional: +set_local_path() +# import modules that are located in the same directory as this file. +from testcommon import open_tmp_file, close_tmp_file +restore_path() + +class test_audiolab(NumpyTestCase): + def _test_read(self, func, format, filext): + # Create a tmp audio file, write some random data into it, and check it + # is the expected data when read from a function from the matapi. + rfd, fd, cfilename = open_tmp_file('pysndfiletest.' + filext) + try: + nbuff = 22050 + noise = 0.1 * N.random.randn(nbuff) + + # Open the copy file for writing + b = sndfile(cfilename, 'write', format, 1, nbuff) + b.write_frames(noise, nbuff) + b.close() + + # Reread the data + b = sndfile(cfilename, 'read') + rcnoise = b.read_frames(nbuff) + b.close() + + rnoise = func(cfilename)[0] + + assert_array_equal(rnoise, rcnoise) + finally: + close_tmp_file(rfd, cfilename) + + def test_wavread(self): + """ Check wavread """ + self._test_read(wavread, audio_format('wav', 'pcm16', 'file'), 'wav') + + def test_flacread(self): + """ Check flacread """ + try: + self._test_read(flacread, audio_format('flac', 'pcm16', 'file'), 'flac') + except FlacUnsupported: + print "Flac unsupported, flacread not tested" + + def test_auread(self): + """ Check auread """ + self._test_read(auread, audio_format('au', 'ulaw', 'file'), 'au') + + def test_aiffread(self): + """ Check aiffread """ + self._test_read(aiffread, audio_format('aiff', 'pcm16', 'file'), 'aiff') + + def test_sdifread(self): + """ Check sdifread (ircam format) """ + self._test_read(sdifread, audio_format('ircam', 'pcm16', 'file'), 'sdif') + + def test_bad_wavread(self): + """ Check wavread on bad file""" + # Create a tmp audio file with non wav format, write some random data into it, + # and check it can not be opened by wavread + rfd, fd, cfilename = open_tmp_file('pysndfiletest.wav') + try: + nbuff = 22050 + noise = 0.1 * N.random.randn(nbuff) + + # Open the copy file for writing + format = audio_format('aiff', 'pcm16') + b = sndfile(cfilename, 'write', format, 1, nbuff) + + b.write_frames(noise, nbuff) + + b.close() + + b = sndfile(cfilename, 'read') + rcnoise = b.read_frames(nbuff) + b.close() + + try: + rnoise = wavread(cfilename)[0] + raise Exception("wavread on non wav file succeded, expected to fail") + except PyaudioException, e: + pass + #print str(e) + ", as expected" + + finally: + close_tmp_file(rfd, cfilename) + + def _test_write(self, func, format, filext): + """ Check *write functions from matpi """ + rfd1, fd1, cfilename1 = open_tmp_file('pysndfiletest.' + filext) + rfd2, fd2, cfilename2 = open_tmp_file('pysndfiletest.' + filext) + try: + nbuff = 22050 + fs = nbuff + noise = 0.1 * N.random.randn(nbuff) + + # Open the first file for writing with sndfile + b = sndfile(cfilename1, 'write', format, 1, fs) + + b.write_frames(noise, nbuff) + + b.close() + + # Write same data with wavwrite + func(noise, cfilename2, fs) + + # Compare if both files have same hash + f1 = open(cfilename1) + f2 = open(cfilename2) + + import md5 + + m1 = md5.new() + m2 = md5.new() + + m1.update(f1.read()) + m2.update(f2.read()) + + f1.close() + f2.close() + assert m1.hexdigest() == m2.hexdigest() + finally: + close_tmp_file(rfd1, cfilename1) + close_tmp_file(rfd2, cfilename2) + + def test_wavwrite(self): + """ Check wavwrite """ + self._test_write(wavwrite, audio_format('wav', 'pcm16', 'file'), 'wav') + + def test_aiffwrite(self): + """ Check aiffwrite """ + self._test_write(aiffwrite, audio_format('aiff', 'pcm16', 'file'), 'aiff') + + def test_auwrite(self): + """ Check wavwrite """ + self._test_write(auwrite, audio_format('au', 'ulaw', 'file'), 'au') + + def test_sdifwrite(self): + """ Check wavwrite """ + self._test_write(sdifwrite, audio_format('ircam', 'pcm16', 'file'), 'sdif') + + def test_flacwrite(self): + """ Check flacwrite """ + try: + self._test_write(flacwrite, audio_format('flac', 'pcm16', 'file'), 'flac') + except FlacUnsupported: + print "Flac unsupported, flacwrite not tested" + +if __name__ == "__main__": + NumpyTest().run() diff --git a/telemeta/visualization/scikits/audiolab/tests/test_pysndfile.py b/telemeta/visualization/scikits/audiolab/tests/test_pysndfile.py new file mode 100644 index 00000000..632a4bc8 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/tests/test_pysndfile.py @@ -0,0 +1,396 @@ +#! /usr/bin/env python +# Last Change: Tue Jul 17 11:00 AM 2007 J +"""Test for the sndfile class.""" +from os.path import join, dirname +import os +import sys + +from numpy.testing import NumpyTestCase, assert_array_equal, NumpyTest, \ + assert_array_almost_equal, set_package_path, restore_path, set_local_path +import numpy as N + +set_package_path() +from audiolab import pysndfile +from audiolab.pysndfile import sndfile, formatinfo as audio_format +restore_path() + +set_local_path() +from testcommon import open_tmp_file, close_tmp_file +restore_path() + +# XXX: there is a lot to refactor here +class test_pysndfile(NumpyTestCase): + def test_basic_io(self): + """ Check open, close and basic read/write""" + # dirty ! + ofilename = join(dirname(pysndfile.__file__), 'test_data', 'test.wav') + rfd, fd, cfilename = open_tmp_file('pysndfiletest.wav') + try: + nbuff = 22050 + + # Open the test file for reading + a = sndfile(ofilename, 'read') + nframes = a.get_nframes() + + # Open the copy file for writing + format = audio_format('wav', 'pcm16') + b = sndfile(fd, 'write', format, a.get_channels(), + a.get_samplerate()) + + # Copy the data + for i in range(nframes / nbuff): + tmpa = a.read_frames(nbuff) + assert tmpa.dtype == N.float + b.write_frames(tmpa, nbuff) + nrem = nframes % nbuff + tmpa = a.read_frames(nrem) + assert tmpa.dtype == N.float + b.write_frames(tmpa, nrem) + + a.close() + b.close() + finally: + close_tmp_file(rfd, cfilename) + + + def test_basic_io_fd(self): + """ Check open from fd works""" + # dirty ! + if sys.platform == 'win32': + print "Not testing opening by fd because does not work on win32" + else: + ofilename = join(dirname(pysndfile.__file__), 'test_data', 'test.wav') + fd = os.open(ofilename, os.O_RDONLY) + hdl = sndfile(fd, 'read') + hdl.close() + + def test_raw(self): + rawname = join(dirname(pysndfile.__file__), 'test_data', 'test.raw') + format = audio_format('raw', 'pcm16', 'little') + a = sndfile(rawname, 'read', format, 1, 11025) + assert a.get_nframes() == 11290 + a.close() + + def test_float64(self): + """Check float64 write/read works""" + # dirty ! + ofilename = join(dirname(pysndfile.__file__), 'test_data', 'test.wav') + rfd, fd, cfilename = open_tmp_file('pysndfiletest.wav') + try: + nbuff = 22050 + + # Open the test file for reading + a = sndfile(ofilename, 'read') + nframes = a.get_nframes() + + # Open the copy file for writing + format = audio_format('wav', 'float64') + b = sndfile(fd, 'write', format, a.get_channels(), + a.get_samplerate()) + + # Copy the data in the wav file + for i in range(nframes / nbuff): + tmpa = a.read_frames(nbuff, dtype = N.float64) + assert tmpa.dtype == N.float64 + b.write_frames(tmpa, nbuff) + nrem = nframes % nbuff + tmpa = a.read_frames(nrem) + b.write_frames(tmpa, nrem) + + a.close() + b.close() + + # Now, reopen both files in for reading, and check data are + # the same + a = sndfile(ofilename, 'read') + b = sndfile(cfilename, 'read') + for i in range(nframes / nbuff): + tmpa = a.read_frames(nbuff, dtype = N.float64) + tmpb = b.read_frames(nbuff, dtype = N.float64) + assert_array_equal(tmpa, tmpb) + + a.close() + b.close() + + finally: + close_tmp_file(rfd, cfilename) + + def test_float32(self): + """Check float write/read works""" + # dirty ! + ofilename = join(dirname(pysndfile.__file__), 'test_data', 'test.wav') + rfd, fd, cfilename = open_tmp_file('pysndfiletest.wav') + try: + nbuff = 22050 + + # Open the test file for reading + a = sndfile(ofilename, 'read') + nframes = a.get_nframes() + + # Open the copy file for writing + format = audio_format('wav', 'float32') + b = sndfile(fd, 'write', format, a.get_channels(), + a.get_samplerate()) + + # Copy the data in the wav file + for i in range(nframes / nbuff): + tmpa = a.read_frames(nbuff, dtype = N.float32) + assert tmpa.dtype == N.float32 + b.write_frames(tmpa, nbuff) + nrem = nframes % nbuff + tmpa = a.read_frames(nrem) + b.write_frames(tmpa, nrem) + + a.close() + b.close() + + # Now, reopen both files in for reading, and check data are + # the same + a = sndfile(ofilename, 'read') + b = sndfile(cfilename, 'read') + for i in range(nframes / nbuff): + tmpa = a.read_frames(nbuff, dtype = N.float32) + tmpb = b.read_frames(nbuff, dtype = N.float32) + assert_array_equal(tmpa, tmpb) + + a.close() + b.close() + + finally: + close_tmp_file(rfd, cfilename) + + def test_supported_features(self): + msg = "\nsupported file format are : this test is broken FIXME" + #for i in pysndfile.supported_format(): + # msg += str(i) + ', ' + #print msg + #msg = "supported encoding format are : " + #for i in pysndfile.supported_encoding(): + # msg += str(i) + ', ' + #print msg + #msg = "supported endianness are : " + #for i in pysndfile.supported_endianness(): + # msg += str(i) + ', ' + print msg + + def test_short_io(self): + # TODO: check if neg or pos value is the highest in abs + rfd, fd, cfilename = open_tmp_file('pysndfiletest.wav') + try: + nb = 2 ** 14 + nbuff = 22050 + fs = 22050 + a = N.random.random_integers(-nb, nb, nbuff) + a = a.astype(N.short) + + # Open the file for writing + format = audio_format('wav', 'pcm16') + b = sndfile(fd, 'write', format, 1, fs) + + b.write_frames(a, nbuff) + b.close() + + b = sndfile(cfilename, 'read') + + read_a = b.read_frames(nbuff, dtype = N.short) + b.close() + + assert_array_equal(a, read_a) + + finally: + close_tmp_file(rfd, cfilename) + + def test_int_io(self): + # TODO: check if neg or pos value is the highest in abs + rfd, fd, cfilename = open_tmp_file('pysndfiletest.wav') + try: + nb = 2 ** 25 + nbuff = 22050 + fs = 22050 + a = N.random.random_integers(-nb, nb, nbuff) + a = a.astype(N.int32) + + # Open the file for writing + format = audio_format('wav', 'pcm32') + b = sndfile(fd, 'write', format, 1, fs) + + b.write_frames(a, nbuff) + b.close() + + b = sndfile(cfilename, 'read') + + read_a = b.read_frames(nbuff, dtype = N.int32) + b.close() + + assert_array_equal(a, read_a) + + finally: + close_tmp_file(rfd, cfilename) + + def test_mismatch(self): + # This test open a file for writing, but with bad args (channels and + # nframes inverted) + rfd, fd, cfilename = open_tmp_file('pysndfiletest.wav') + try: + # Open the file for writing + format = audio_format('wav', 'pcm16') + try: + b = sndfile(fd, 'write', \ + format, channels = 22000, samplerate = 1) + raise Exception("Try to open a file with more than 256 "\ + "channels, this should not succeed !") + except RuntimeError, e: + #print "Gave %d channels, error detected is \"%s\"" % (22000, e) + pass + + finally: + close_tmp_file(rfd, cfilename) + + def test_bigframes(self): + """ Try to seek really far""" + rawname = join(dirname(pysndfile.__file__), 'test_data', 'test.wav') + a = sndfile(rawname, 'read') + try: + try: + a.seek(2 ** 60) + raise Exception("Seek really succeded ! This should not happen") + except pysndfile.PyaudioIOError, e: + pass + finally: + a.close() + + def test_float_frames(self): + """ Check nframes can be a float""" + rfd, fd, cfilename = open_tmp_file('pysndfiletest.wav') + try: + # Open the file for writing + format = audio_format('wav', 'pcm16') + a = sndfile(fd, 'rwrite', format, channels = 1, + samplerate = 22050) + tmp = N.random.random_integers(-100, 100, 1000) + tmp = tmp.astype(N.short) + a.write_frames(tmp, tmp.size) + a.seek(0) + a.sync() + ctmp = a.read_frames(1e2, dtype = N.short) + a.close() + + finally: + close_tmp_file(rfd, cfilename) + + def test_nofile(self): + """ Check the failure when opening a non existing file.""" + try: + f = sndfile("floupi.wav", "read") + raise AssertionError("call to non existing file should not succeed") + except IOError: + pass + except Exception, e: + raise AssertionError("opening non existing file should raise a IOError exception, got %s instead" % e.__class__) + +class test_seek(NumpyTestCase): + def test_simple(self): + ofilename = join(dirname(pysndfile.__file__), 'test_data', 'test.wav') + # Open the test file for reading + a = sndfile(ofilename, 'read') + nframes = a.get_nframes() + + buffsize = 1024 + buffsize = min(nframes, buffsize) + + # First, read some frames, go back, and compare buffers + buff = a.read_frames(buffsize) + a.seek(0) + buff2 = a.read_frames(buffsize) + assert_array_equal(buff, buff2) + + a.close() + + # Now, read some frames, go back, and compare buffers + # (check whence == 1 == SEEK_CUR) + a = sndfile(ofilename, 'read') + a.read_frames(buffsize) + buff = a.read_frames(buffsize) + a.seek(-buffsize, 1) + buff2 = a.read_frames(buffsize) + assert_array_equal(buff, buff2) + + a.close() + + # Now, read some frames, go back, and compare buffers + # (check whence == 2 == SEEK_END) + a = sndfile(ofilename, 'read') + buff = a.read_frames(nframes) + a.seek(-buffsize, 2) + buff2 = a.read_frames(buffsize) + assert_array_equal(buff[-buffsize:], buff2) + + def test_rw(self): + """Test read/write pointers for seek.""" + ofilename = join(dirname(pysndfile.__file__), 'test_data', 'test.wav') + rfd, fd, cfilename = open_tmp_file('rwseektest.wav') + try: + ref = sndfile(ofilename, 'read') + test = sndfile(fd, 'rwrite', format = ref._format, channels = + ref.get_channels(), samplerate = ref.get_samplerate()) + n = 1024 + + rbuff = ref.read_frames(n, dtype = N.int16) + test.write_frames(rbuff) + tbuff = test.read_frames(n, dtype = N.int16) + + assert_array_equal(rbuff, tbuff) + + # Test seeking both read and write pointers + test.seek(0, 0) + test.write_frames(rbuff) + tbuff = test.read_frames(n, dtype = N.int16) + assert_array_equal(rbuff, tbuff) + + # Test seeking only read pointer + rbuff1 = rbuff.copy() + rbuff2 = rbuff1 * 2 + 1 + rbuff2.clip(-30000, 30000) + test.seek(0, 0, 'r') + test.write_frames(rbuff2) + tbuff1 = test.read_frames(n, dtype = N.int16) + try: + tbuff2 = test.read_frames(n, dtype = N.int16) + except IOError, e: + msg = "write pointer was updated in read seek !" + msg += "\n(msg is %s)" % e + raise AssertionError(msg) + + assert_array_equal(rbuff1, tbuff1) + assert_array_equal(rbuff2, tbuff2) + if N.all(rbuff2 == tbuff1): + raise AssertionError("write pointer was updated"\ + " in read seek !") + + # Test seeking only write pointer + rbuff3 = rbuff1 * 2 - 1 + rbuff3.clip(-30000, 30000) + test.seek(0, 0, 'rw') + test.seek(n, 0, 'w') + test.write_frames(rbuff3) + tbuff1 = test.read_frames(n, N.int16) + try: + assert_array_equal(tbuff1, rbuff1) + except AssertionError: + raise AssertionError("read pointer was updated in write seek !") + + try: + tbuff3 = test.read_frames(n, N.int16) + except IOError, e: + msg = "read pointer was updated in write seek !" + msg += "\n(msg is %s)" % e + raise AssertionError(msg) + + assert_array_equal(tbuff3, rbuff3) + test.close() + + finally: + close_tmp_file(rfd, cfilename) + +if __name__ == "__main__": + NumpyTest().run() diff --git a/telemeta/visualization/scikits/audiolab/tests/testcommon.py b/telemeta/visualization/scikits/audiolab/tests/testcommon.py new file mode 100644 index 00000000..dd8c4a42 --- /dev/null +++ b/telemeta/visualization/scikits/audiolab/tests/testcommon.py @@ -0,0 +1,20 @@ +import os +from tempfile import mkstemp +import sys + +def open_tmp_file(name): + """On any sane platforms, return a fd on a tmp file. On windows, returns + the filename, and as such, is not secure (someone else can reopen the file + in between).""" + fd, cfilename = mkstemp('pysndfiletest.wav') + if sys.platform == 'win32': + return fd, cfilename, cfilename + else: + return fd, fd, cfilename + +def close_tmp_file(fd, filename): + """On any sane platforms, remove the file . On windows, only close the + file.""" + os.close(fd) + os.remove(filename) + -- 2.39.5