Compare commits

...

43 Commits

Author SHA1 Message Date
lawl 1aa277446b Add PipeWire support to README 3 years ago
lawl a2a45bc88b UI: Add confirm screen for unload/reload when device in use
Reloading/Unloading destroys the virtual device that an application
is currently using. Add a warning that this may cause strange behaviour.
3 years ago
lawl cb2ee053b6 Handle non-semver versions in audio client gracefully-ish 3 years ago
lawl d1aa44c3c7 pulseaudio: update module with fixed cookie lookup 3 years ago
lawl 077b4763fe CLI: Cleanup temp files on exit
Fixes #135
3 years ago
Andreas Schneider 809f184e87 c:ladspa: Fix linking with libm
Fixes #140
3 years ago
Andreas Schneider a32f73509f Revert "Temporarily force clang if available"
This reverts commit c2bfe0202f.
3 years ago
lawl c2bfe0202f Temporarily force clang if available
GCC10 seems to refuse to link libm. GCC11 and clang seem to work fine.
3 years ago
lawl a57cc20110 display error screen if module load fails 3 years ago
lawl fad3e1d4f4 Add support for PipeWire
Warning: Alpha quality. Please manage your expectations.
3 years ago
Sefa Eyeoglu 9b651eb701
Implement distribution-specific builds
Make more things configurable at compile time so building binaries for distributions is easier while complying with the license.
3 years ago
lawl a0620ae92c Remove 3rd party package warning from README
The distribution that shall not be named (btw) has decided to
comply with the license. (For now?)
3 years ago
lawl 8b11b649ee Don't log to a file anymore. Log to stdout if called with -log
Additionally refactor the CLI a bit more to make that possible
3 years ago
lawl 0ef8229e6f License: Use convey instead of distribute for wording 3 years ago
lawl fb46112181 Add link of a demo to readme
Closes #19
3 years ago
Lorenz 4853c5c8d7
Fix not printing errror message
The CLI should print errors to stderr, and not write to the log file.
3 years ago
lawl 95e82fcbfd CLI: Factor CLI out of main
Our CLI shared some data structures, like the pulseaudio connection with the GUI.
This could lead to issues where the UI would break when code on the CLI was changed
because the the GUI must handle things differently, by nature of working differently.

Add more separation between those, so changes on the CLI are less likely to break the GUI.
3 years ago
lawl 6ea788a0c0 Refactor: Remove old method of removing rlimit for pulseaudio
Previously we had the capability to remove the rlimit on pulseaudio
by calling ourselves via pkexec to elevate privileges.
A while ago we switched to fully using capabilities.
We now remove the old legacy codepath.
3 years ago
lawl 245455d508 Refactor UI: Rename "screen" to "view" 3 years ago
lawl 6f901716ed Refactor UI: Use a stack of views to handle what to display
Previously we dispatched calls to different drawing function with
a bunch of if statements in the main draw function.

Use a stack of drawing functions instead.
3 years ago
lawl fe14585a37 make troubleshooting section more visible 3 years ago
lawl 8b054c20a9 Update License 3 years ago
lawl ee91fea993 Use -O2 for LADSPA module 3 years ago
lawl 3119fc5997 Use go:embed instead of scripts/embedbinary.go 3 years ago
lawl 5a55f0538a Update 'Special thanks to' 3 years ago
lawl 77a8c73436 Update build instructions 3 years ago
lawl fcb4db042c Add information about potentially having to relog for PATH changes 3 years ago
lawl 7172888478 Convert fmt print to log print 3 years ago
lawl 335b8a2567 Fix unloading via CLI (-u)
Squashed commit of the following:

commit 19532d64a7f7337da7f844d52198417ceaf60e80
Author: lawl <github@dumbinter.net>
Date:   Thu Feb 4 10:40:27 2021 +0100

    Fix unloading paClient for UI

commit 66ece81e17589988f92059b44b2659fdca0c4ca1
Merge: 1c97cfe 60d06f2
Author: lawl <github@dumbinter.net>
Date:   Thu Feb 4 10:36:13 2021 +0100

    Merge branch 'fix_cli_unload' of git://github.com/hador/NoiseTorch into hador-fix_cli_unload

commit 60d06f25aa
Author: andreafa <andreafa@amazon.com>
Date:   Thu Feb 4 08:51:48 2021 +0000

    Fix cli unload (-u).

    The findModule method in module.go requires a valid paClient to be
    populated in the context object.
    Added logging if unloadSupressor fails when called via the -u CLI flag.
3 years ago
Andrea Fagiani 1c97cfe43c
Use 24-hour clock when adding timestamps to log file names. (#92)
Co-authored-by: andreafa <andreafa@amazon.com>
3 years ago
lawl 47d8188b3a Add warning if FS capabilities don't match process capabilities 3 years ago
lawl febccb207b Tune values for fixed latency devices 3 years ago
lawl 54f9e012e3 Make sure we remove rlimit for unloading too 3 years ago
lawl 730dc35d94 Require capabilities on startup 3 years ago
lawl 4513db1baf use different params for fixed latency devices to hopefully correct latency faster 3 years ago
lawl e7072b2bf9 Add -Wall, -Werr 3 years ago
lawl b747084bb0 Replace noise-suppression-for-voice with own implementation
In preparation for PipeWire support we required our own implementation anyways.
To have a shared code base and potentially enable some other tighter integration
between the LADSPA module and NoiseTorch, we move to our own implementation.
3 years ago
lawl 4f7ac325ab
Update README.md 3 years ago
lawl f3c11be919 remove more commented code (yuck) 3 years ago
lawl 313aca54a2 remove commented out code 3 years ago
lawl da9370e983 fix noop updater 3 years ago
lawl 37692a873a Implement output filtering 3 years ago
lawl d8be27d86e Make patreon call to action look nicer 3 years ago

4
.gitignore vendored

@ -1,4 +1,2 @@
bin/
librnnoise.go
version.go
licenses.go
licenses.go

4
.gitmodules vendored

@ -1,4 +0,0 @@
[submodule "librnnoise_ladspa"]
path = librnnoise_ladspa
url = https://github.com/werman/noise-suppression-for-voice
ignore = dirty

@ -1,5 +1,15 @@
NoiseTorch (c) 2020 lawl (github.com/lawl)
SPDX-License-Identifier: GPL-3.0-or-later
NoiseTorch (c) 2020-2021 lawl (github.com/lawl)
This software is distributed under the GNU General Public License Version 3 ("GPLv3").
In accordance with Section 7, subsection `c` of the GPLv3 the following additional term(s) apply:
* Conveying modified versions of this program ("NoiseTorch") must be marked as modified in a reasonable way.
Modified versions may not be conveyed to others under same name as the original program.
Package names, source code, user interfaces and other visible appearances of the program name should make it obvious for users
and potential users that the modified version differs from the original version of NoiseTorch it is based upon.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

@ -1,7 +1,11 @@
UPDATE_URL=https://noisetorch.epicgamer.org
UPDATE_PUBKEY=3mL+rBi4yBZ1wGimQ/oSQCjxELzgTh+673H4JdzQBOk=
VERSION := $(shell git describe --tags)
dev: rnnoise
mkdir -p bin/
go generate
go build -o bin/noisetorch
go build -ldflags '-X main.version=${VERSION}' -o bin/noisetorch
release: rnnoise
mkdir -p bin/
mkdir -p tmp/
@ -14,7 +18,7 @@ release: rnnoise
mkdir -p tmp/.local/bin/
go generate
CGO_ENABLED=0 GOOS=linux go build -tags release -a -ldflags '-s -w -extldflags "-static"' .
CGO_ENABLED=0 GOOS=linux go build -tags release -a -ldflags '-s -w -extldflags "-static" -X main.version=${VERSION} -X main.distribution=official -X main.updateURL=${UPDATE_URL} -X main.publicKeyString=${UPDATE_PUBKEY}' .
upx noisetorch
mv noisetorch tmp/.local/bin/
cd tmp/; \
@ -23,7 +27,6 @@ release: rnnoise
go run scripts/signer.go -s
git describe --tags > bin/version.txt
rnnoise:
cd librnnoise_ladspa/; \
cmake . -DBUILD_VST_PLUGIN=OFF -DBUILD_LV2_PLUGIN=OFF -DBUILD_LADSPA_PLUGIN=ON; \
cd c/ladspa; \
make

@ -5,7 +5,7 @@
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Last Release](https://img.shields.io/github/v/release/lawl/NoiseTorch?label=latest&style=flat-square)](https://github.com/lawl/NoiseTorch/releases)
NoiseTorch is an easy to use open source application for Linux with PulseAudio. It creates a virtual microphone that suppresses noise, in any application. Use whichever conferencing or VOIP application you like and simply select the NoiseTorch Virtual Microphone as input to torch the sound of your mechanical keyboard, computer fans, trains and the likes.
NoiseTorch is an easy to use open source application for Linux with PulseAudio or PipeWire. It creates a virtual microphone that suppresses noise, in any application. Use whichever conferencing or VOIP application you like and simply select the NoiseTorch Virtual Microphone as input to torch the sound of your mechanical keyboard, computer fans, trains and the likes.
Don't forget to ~~like, comment and subscribe~~ leave a star ⭐ if this sounds useful to you!
@ -17,6 +17,10 @@ Then simply select NoiseTorch as your microphone in any application. OBS, Mumble
![](https://i.imgur.com/nimi7Ne.png)
## Demo
Linux For Everyone has a good demo video [here](https://www.youtube.com/watch?v=DzN9rYNeeIU).
## Features
* Two click setup of your virtual denoising microphone
* A single, small, statically linked, self-contained binary
@ -38,7 +42,11 @@ With gnome this can be done with:
You now have a `noisetorch` binary and desktop entry on your system.
If noisetorch doesn't start after installation, you may also have to make sure that `.local/bin` is in your PATH. On most distributions e.g. Ubuntu, this should be the case by default. If it's not, make sure to append
Give it the required permissions with `setcap`:
sudo setcap 'CAP_SYS_RESOURCE=+ep' ~/.local/bin/noisetorch
If noisetorch doesn't start after installation, you may also have to make sure that `~/.local/bin` is in your PATH. On most distributions e.g. Ubuntu, this should be the case by default. If it's not, make sure to append
```
if [ -d "$HOME/.local/bin" ] ; then
@ -46,7 +54,7 @@ if [ -d "$HOME/.local/bin" ] ; then
fi
```
to your `~/.profile`.
to your `~/.profile`. If you do already have that, you may have to log in and out for it to actually apply if this is the first time you're using `~/.local/bin`.
#### Uninstall
@ -54,15 +62,13 @@ to your `~/.profile`.
rm ~/.local/share/applications/noisetorch.desktop
rm ~/.local/share/icons/hicolor/256x256/apps/noisetorch.png
## Third party packages (like AUR)
Please do not use them. Some are known to mess with configuration files and will break things even if you did install a NoiseTorch package from an official source later on. If you had ever installed NoiseTorch from a non-official source, make sure to *completely* remove every trace, including deleting your `~/.config/noisetorch` directory, before reporting an issue.
## Troubleshooting
If you have been maintaining a third party package for a while and would like it to be blessed as supported, please send me an e-mail.
Please see the [Troubleshooting](https://github.com/lawl/NoiseTorch/wiki/Troubleshooting) section in the wiki.
## Usage
Select the microphone you want to denoise, and click "Load NoiseTorch", NoiseTorch will create a virtual microphone called "NoiseTorch Microphone" that you can select in any application.
Select the microphone you want to denoise, and click "Load NoiseTorch", NoiseTorch will create a virtual microphone called "NoiseTorch Microphone" that you can select in any application. Output filtering works the same way, simply output the applications you want to filter to "NoiseTorch Headphones".
When you're done using it, simply click "Unload NoiseTorch" to remove it again, until you need it next time.
@ -74,13 +80,11 @@ Please keep in mind that you will need to reload NoiseTorch for these changes to
Once NoiseTorch has been loaded, feel free to close the window, the virtual microphone will continue working until you explicitly unload it. The NoiseTorch process is not required anymore once it has been loaded.
## Troubleshooting
Please see the [Troubleshooting](https://github.com/lawl/NoiseTorch/wiki/Troubleshooting) section in the wiki.
## Latency
NoiseTorch may introduce a small amount of latency. The amount of inherent latency introduced by noise supression is 10ms, this is very low and should not be a problem. Additionally PulseAudio currently introduces a variable amount of latency that depends on your system. Lowering this latency [requires a change in PulseAudio](https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/120).
NoiseTorch may introduce a small amount of latency for microphone filtering. The amount of inherent latency introduced by noise supression is 10ms, this is very low and should not be a problem. Additionally PulseAudio currently introduces a variable amount of latency that depends on your system. Lowering this latency [requires a change in PulseAudio](https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/120).
Output filtering currently introduces something on the order of ~100ms with pulseaudio. This should still be fine for regular conferences, VOIPing and gaming. Maybe not for competitive gaming teams.
## Building from source
@ -89,14 +93,12 @@ Install the Go compiler from [golang.org](https://golang.org/). And make sure yo
```shell
git clone https://github.com/lawl/NoiseTorch # Clone the repository
cd NoiseTorch # cd into the cloned repository
git submodule init # Tell git to look at submodules
git submodule update # Update submodules
make # build it
```
## Special thanks to
* [xiph.org](https://xiph.org)/[Mozilla's](https://mozilla.org) excellent [RNNoise](https://jmvalin.ca/demo/rnnoise/).
* [@werman](https://github.com/werman/)'s [LADSPA/VST wrapper](https://github.com/werman/noise-suppression-for-voice/) allowing us to load RNNoise into PulseAudio.
* [@werman](https://github.com/werman/)'s [noise-suppression-for-voice](https://github.com/werman/noise-suppression-for-voice/) for the inspiration
* [@aarzilli](https://github.com/aarzilli/)'s [nucular](https://github.com/aarzilli/nucular) GUI toolkit for Go.
* [Salee Design](https://www.salleedesign.com) (info@salleedesign.com)'s Microphone Icon under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
* [Sallee Design](https://www.salleedesign.com) (info@salleedesign.com)'s Microphone Icon under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

@ -0,0 +1,2 @@
*.o
rnnoise_ladspa.so

@ -0,0 +1,3 @@
default:
$(CC) -Wall -Werror -O2 -c -fPIC ../ringbuf.c ../rnnoise/*.c module.c
$(CC) -o rnnoise_ladspa.so *.o -shared -Wl,--version-script=export.txt -lm

@ -0,0 +1,4 @@
{
global: *ladspa*;
local: *;
};

@ -0,0 +1,603 @@
/* ladspa.h
Linux Audio Developer's Simple Plugin API Version 1.1[LGPL].
Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis,
Stefan Westerfeld.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA. */
#ifndef LADSPA_INCLUDED
#define LADSPA_INCLUDED
#define LADSPA_VERSION "1.1"
#define LADSPA_VERSION_MAJOR 1
#define LADSPA_VERSION_MINOR 1
#ifdef __cplusplus
extern "C" {
#endif
/*****************************************************************************/
/* Overview:
There is a large number of synthesis packages in use or development
on the Linux platform at this time. This API (`The Linux Audio
Developer's Simple Plugin API') attempts to give programmers the
ability to write simple `plugin' audio processors in C/C++ and link
them dynamically (`plug') into a range of these packages (`hosts').
It should be possible for any host and any plugin to communicate
completely through this interface.
This API is deliberately short and simple. To achieve compatibility
with a range of promising Linux sound synthesis packages it
attempts to find the `greatest common divisor' in their logical
behaviour. Having said this, certain limiting decisions are
implicit, notably the use of a fixed type (LADSPA_Data) for all
data transfer and absence of a parameterised `initialisation'
phase. See below for the LADSPA_Data typedef.
Plugins are expected to distinguish between control and audio
data. Plugins have `ports' that are inputs or outputs for audio or
control data and each plugin is `run' for a `block' corresponding
to a short time interval measured in samples. Audio data is
communicated using arrays of LADSPA_Data, allowing a block of audio
to be processed by the plugin in a single pass. Control data is
communicated using single LADSPA_Data values. Control data has a
single value at the start of a call to the `run()' or `run_adding()'
function, and may be considered to remain this value for its
duration. The plugin may assume that all its input and output ports
have been connected to the relevant data location (see the
`connect_port()' function below) before it is asked to run.
Plugins will reside in shared object files suitable for dynamic
linking by dlopen() and family. The file will provide a number of
`plugin types' that can be used to instantiate actual plugins
(sometimes known as `plugin instances') that can be connected
together to perform tasks.
This API contains very limited error-handling. */
/*****************************************************************************/
/* Fundamental data type passed in and out of plugin. This data type
is used to communicate audio samples and control values. It is
assumed that the plugin will work sensibly given any numeric input
value although it may have a preferred range (see hints below).
For audio it is generally assumed that 1.0f is the `0dB' reference
amplitude and is a `normal' signal level. */
typedef float LADSPA_Data;
/*****************************************************************************/
/* Special Plugin Properties:
Optional features of the plugin type are encapsulated in the
LADSPA_Properties type. This is assembled by ORing individual
properties together. */
typedef int LADSPA_Properties;
/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a
real-time dependency (e.g. listens to a MIDI device) and so its
output must not be cached or subject to significant latency. */
#define LADSPA_PROPERTY_REALTIME 0x1
/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin
may cease to work correctly if the host elects to use the same data
location for both input and output (see connect_port()). This
should be avoided as enabling this flag makes it impossible for
hosts to use the plugin to process audio `in-place.' */
#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2
/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin
is capable of running not only in a conventional host but also in a
`hard real-time' environment. To qualify for this the plugin must
satisfy all of the following:
(1) The plugin must not use malloc(), free() or other heap memory
management within its run() or run_adding() functions. All new
memory used in run() must be managed via the stack. These
restrictions only apply to the run() function.
(2) The plugin will not attempt to make use of any library
functions with the exceptions of functions in the ANSI standard C
and C maths libraries, which the host is expected to provide.
(3) The plugin will not access files, devices, pipes, sockets, IPC
or any other mechanism that might result in process or thread
blocking.
(4) The plugin will take an amount of time to execute a run() or
run_adding() call approximately of form (A+B*SampleCount) where A
and B depend on the machine and host in use. This amount of time
may not depend on input signals or plugin state. The host is left
the responsibility to perform timings to estimate upper bounds for
A and B. */
#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4
#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME)
#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN)
#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE)
/*****************************************************************************/
/* Plugin Ports:
Plugins have `ports' that are inputs or outputs for audio or
data. Ports can communicate arrays of LADSPA_Data (for audio
inputs/outputs) or single LADSPA_Data values (for control
input/outputs). This information is encapsulated in the
LADSPA_PortDescriptor type which is assembled by ORing individual
properties together.
Note that a port must be an input or an output port but not both
and that a port must be a control or audio port but not both. */
typedef int LADSPA_PortDescriptor;
/* Property LADSPA_PORT_INPUT indicates that the port is an input. */
#define LADSPA_PORT_INPUT 0x1
/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */
#define LADSPA_PORT_OUTPUT 0x2
/* Property LADSPA_PORT_CONTROL indicates that the port is a control
port. */
#define LADSPA_PORT_CONTROL 0x4
/* Property LADSPA_PORT_AUDIO indicates that the port is a audio
port. */
#define LADSPA_PORT_AUDIO 0x8
#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT)
#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT)
#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL)
#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO)
/*****************************************************************************/
/* Plugin Port Range Hints:
The host may wish to provide a representation of data entering or
leaving a plugin (e.g. to generate a GUI automatically). To make
this more meaningful, the plugin should provide `hints' to the host
describing the usual values taken by the data.
Note that these are only hints. The host may ignore them and the
plugin must not assume that data supplied to it is meaningful. If
the plugin receives invalid input data it is expected to continue
to run without failure and, where possible, produce a sensible
output (e.g. a high-pass filter given a negative cutoff frequency
might switch to an all-pass mode).
Hints are meaningful for all input and output ports but hints for
input control ports are expected to be particularly useful.
More hint information is encapsulated in the
LADSPA_PortRangeHintDescriptor type which is assembled by ORing
individual hint types together. Hints may require further
LowerBound and UpperBound information.
All the hint information for a particular port is aggregated in the
LADSPA_PortRangeHint structure. */
typedef int LADSPA_PortRangeHintDescriptor;
/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field
of the LADSPA_PortRangeHint should be considered meaningful. The
value in this field should be considered the (inclusive) lower
bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
specified then the value of LowerBound should be multiplied by the
sample rate. */
#define LADSPA_HINT_BOUNDED_BELOW 0x1
/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field
of the LADSPA_PortRangeHint should be considered meaningful. The
value in this field should be considered the (inclusive) upper
bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
specified then the value of UpperBound should be multiplied by the
sample rate. */
#define LADSPA_HINT_BOUNDED_ABOVE 0x2
/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be
considered a Boolean toggle. Data less than or equal to zero should
be considered `off' or `false,' and data above zero should be
considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in
conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or
LADSPA_HINT_DEFAULT_1. */
#define LADSPA_HINT_TOGGLED 0x4
/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified
should be interpreted as multiples of the sample rate. For
instance, a frequency range from 0Hz to the Nyquist frequency (half
the sample rate) could be requested by this hint in conjunction
with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds
at all must support this hint to retain meaning. */
#define LADSPA_HINT_SAMPLE_RATE 0x8
/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the
user will find it more intuitive to view values using a logarithmic
scale. This is particularly useful for frequencies and gains. */
#define LADSPA_HINT_LOGARITHMIC 0x10
/* Hint LADSPA_HINT_INTEGER indicates that a user interface would
probably wish to provide a stepped control taking only integer
values. Any bounds set should be slightly wider than the actual
integer range required to avoid floating point rounding errors. For
instance, the integer set {0,1,2,3} might be described as [-0.1,
3.1]. */
#define LADSPA_HINT_INTEGER 0x20
/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal'
value for the port that is sensible as a default. For instance,
this value is suitable for use as an initial value in a user
interface or as a value the host might assign to a control port
when the user has not provided one. Defaults are encoded using a
mask so only one default may be specified for a port. Some of the
hints make use of lower and upper bounds, in which case the
relevant bound or bounds must be available and
LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting
default must be rounded if LADSPA_HINT_INTEGER is present. Default
values were introduced in LADSPA v1.1. */
#define LADSPA_HINT_DEFAULT_MASK 0x3C0
/* This default values indicates that no default is provided. */
#define LADSPA_HINT_DEFAULT_NONE 0x0
/* This default hint indicates that the suggested lower bound for the
port should be used. */
#define LADSPA_HINT_DEFAULT_MINIMUM 0x40
/* This default hint indicates that a low value between the suggested
lower and upper bounds should be chosen. For ports with
LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 +
log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper
* 0.25). */
#define LADSPA_HINT_DEFAULT_LOW 0x80
/* This default hint indicates that a middle value between the
suggested lower and upper bounds should be chosen. For ports with
LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 +
log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper *
0.5). */
#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0
/* This default hint indicates that a high value between the suggested
lower and upper bounds should be chosen. For ports with
LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 +
log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper
* 0.75). */
#define LADSPA_HINT_DEFAULT_HIGH 0x100
/* This default hint indicates that the suggested upper bound for the
port should be used. */
#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140
/* This default hint indicates that the number 0 should be used. Note
that this default may be used in conjunction with
LADSPA_HINT_TOGGLED. */
#define LADSPA_HINT_DEFAULT_0 0x200
/* This default hint indicates that the number 1 should be used. Note
that this default may be used in conjunction with
LADSPA_HINT_TOGGLED. */
#define LADSPA_HINT_DEFAULT_1 0x240
/* This default hint indicates that the number 100 should be used. */
#define LADSPA_HINT_DEFAULT_100 0x280
/* This default hint indicates that the Hz frequency of `concert A'
should be used. This will be 440 unless the host uses an unusual
tuning convention, in which case it may be within a few Hz. */
#define LADSPA_HINT_DEFAULT_440 0x2C0
#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW)
#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE)
#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED)
#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE)
#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC)
#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER)
#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK)
#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_MINIMUM)
#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_LOW)
#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_MIDDLE)
#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_HIGH)
#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_MAXIMUM)
#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_0)
#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_1)
#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_100)
#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
== LADSPA_HINT_DEFAULT_440)
typedef struct _LADSPA_PortRangeHint {
/* Hints about the port. */
LADSPA_PortRangeHintDescriptor HintDescriptor;
/* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When
LADSPA_HINT_SAMPLE_RATE is also active then this value should be
multiplied by the relevant sample rate. */
LADSPA_Data LowerBound;
/* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When
LADSPA_HINT_SAMPLE_RATE is also active then this value should be
multiplied by the relevant sample rate. */
LADSPA_Data UpperBound;
} LADSPA_PortRangeHint;
/*****************************************************************************/
/* Plugin Handles:
This plugin handle indicates a particular instance of the plugin
concerned. It is valid to compare this to NULL (0 for C++) but
otherwise the host should not attempt to interpret it. The plugin
may use it to reference internal instance data. */
typedef void * LADSPA_Handle;
/*****************************************************************************/
/* Descriptor for a Type of Plugin:
This structure is used to describe a plugin type. It provides a
number of functions to examine the type, instantiate it, link it to
buffers and workspaces and to run it. */
typedef struct _LADSPA_Descriptor {
/* This numeric identifier indicates the plugin type
uniquely. Plugin programmers may reserve ranges of IDs from a
central body to avoid clashes. Hosts may assume that IDs are
below 0x1000000. */
unsigned long UniqueID;
/* This identifier can be used as a unique, case-sensitive
identifier for the plugin type within the plugin file. Plugin
types should be identified by file and label rather than by index
or plugin name, which may be changed in new plugin
versions. Labels must not contain white-space characters. */
const char * Label;
/* This indicates a number of properties of the plugin. */
LADSPA_Properties Properties;
/* This member points to the null-terminated name of the plugin
(e.g. "Sine Oscillator"). */
const char * Name;
/* This member points to the null-terminated string indicating the
maker of the plugin. This can be an empty string but not NULL. */
const char * Maker;
/* This member points to the null-terminated string indicating any
copyright applying to the plugin. If no Copyright applies the
string "None" should be used. */
const char * Copyright;
/* This indicates the number of ports (input AND output) present on
the plugin. */
unsigned long PortCount;
/* This member indicates an array of port descriptors. Valid indices
vary from 0 to PortCount-1. */
const LADSPA_PortDescriptor * PortDescriptors;
/* This member indicates an array of null-terminated strings
describing ports (e.g. "Frequency (Hz)"). Valid indices vary from
0 to PortCount-1. */
const char * const * PortNames;
/* This member indicates an array of range hints for each port (see
above). Valid indices vary from 0 to PortCount-1. */
const LADSPA_PortRangeHint * PortRangeHints;
/* This may be used by the plugin developer to pass any custom
implementation data into an instantiate call. It must not be used
or interpreted by the host. It is expected that most plugin
writers will not use this facility as LADSPA_Handle should be
used to hold instance data. */
void * ImplementationData;
/* This member is a function pointer that instantiates a plugin. A
handle is returned indicating the new plugin instance. The
instantiation function accepts a sample rate as a parameter. The
plugin descriptor from which this instantiate function was found
must also be passed. This function must return NULL if
instantiation fails.
Note that instance initialisation should generally occur in
activate() rather than here. */
LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor,
unsigned long SampleRate);
/* This member is a function pointer that connects a port on an
instantiated plugin to a memory location at which a block of data
for the port will be read/written. The data location is expected
to be an array of LADSPA_Data for audio ports or a single
LADSPA_Data value for control ports. Memory issues will be
managed by the host. The plugin must read/write the data at these
locations every time run() or run_adding() is called and the data
present at the time of this connection call should not be
considered meaningful.
connect_port() may be called more than once for a plugin instance
to allow the host to change the buffers that the plugin is
reading or writing. These calls may be made before or after
activate() or deactivate() calls.
connect_port() must be called at least once for each port before
run() or run_adding() is called. When working with blocks of
LADSPA_Data the plugin should pay careful attention to the block
size passed to the run function as the block allocated may only
just be large enough to contain the block of samples.
Plugin writers should be aware that the host may elect to use the
same buffer for more than one port and even use the same buffer
for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN).
However, overlapped buffers or use of a single buffer for both
audio and control data may result in unexpected behaviour. */
void (*connect_port)(LADSPA_Handle Instance,
unsigned long Port,
LADSPA_Data * DataLocation);
/* This member is a function pointer that initialises a plugin
instance and activates it for use. This is separated from
instantiate() to aid real-time support and so that hosts can
reinitialise a plugin instance by calling deactivate() and then
activate(). In this case the plugin instance must reset all state
information dependent on the history of the plugin instance
except for any data locations provided by connect_port() and any
gain set by set_run_adding_gain(). If there is nothing for
activate() to do then the plugin writer may provide a NULL rather
than an empty function.
When present, hosts must call this function once before run() (or
run_adding()) is called for the first time. This call should be
made as close to the run() call as possible and indicates to
real-time plugins that they are now live. Plugins should not rely
on a prompt call to run() after activate(). activate() may not be
called again unless deactivate() is called first. Note that
connect_port() may be called before or after a call to
activate(). */
void (*activate)(LADSPA_Handle Instance);
/* This method is a function pointer that runs an instance of a
plugin for a block. Two parameters are required: the first is a
handle to the particular instance to be run and the second
indicates the block size (in samples) for which the plugin
instance may run.
Note that if an activate() function exists then it must be called
before run() or run_adding(). If deactivate() is called for a
plugin instance then the plugin instance may not be reused until
activate() has been called again.
If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE
then there are various things that the plugin should not do
within the run() or run_adding() functions (see above). */
void (*run)(LADSPA_Handle Instance,
unsigned long SampleCount);
/* This method is a function pointer that runs an instance of a
plugin for a block. This has identical behaviour to run() except
in the way data is output from the plugin. When run() is used,
values are written directly to the memory areas associated with
the output ports. However when run_adding() is called, values
must be added to the values already present in the memory
areas. Furthermore, output values written must be scaled by the
current gain set by set_run_adding_gain() (see below) before
addition.
run_adding() is optional. When it is not provided by a plugin,
this function pointer must be set to NULL. When it is provided,
the function set_run_adding_gain() must be provided also. */
void (*run_adding)(LADSPA_Handle Instance,
unsigned long SampleCount);
/* This method is a function pointer that sets the output gain for
use when run_adding() is called (see above). If this function is
never called the gain is assumed to default to 1. Gain
information should be retained when activate() or deactivate()
are called.
This function should be provided by the plugin if and only if the
run_adding() function is provided. When it is absent this
function pointer must be set to NULL. */
void (*set_run_adding_gain)(LADSPA_Handle Instance,
LADSPA_Data Gain);
/* This is the counterpart to activate() (see above). If there is
nothing for deactivate() to do then the plugin writer may provide
a NULL rather than an empty function.
Hosts must deactivate all activated units after they have been
run() (or run_adding()) for the last time. This call should be
made as close to the last run() call as possible and indicates to
real-time plugins that they are no longer live. Plugins should
not rely on prompt deactivation. Note that connect_port() may be
called before or after a call to deactivate().
Deactivation is not similar to pausing as the plugin instance
will be reinitialised when activate() is called to reuse it. */
void (*deactivate)(LADSPA_Handle Instance);
/* Once an instance of a plugin has been finished with it can be
deleted using the following function. The instance handle passed
ceases to be valid after this call.
If activate() was called for a plugin instance then a
corresponding call to deactivate() must be made before cleanup()
is called. */
void (*cleanup)(LADSPA_Handle Instance);
} LADSPA_Descriptor;
/**********************************************************************/
/* Accessing a Plugin: */
/* The exact mechanism by which plugins are loaded is host-dependent,
however all most hosts will need to know is the name of shared
object file containing the plugin types. To allow multiple hosts to
share plugin types, hosts may wish to check for environment
variable LADSPA_PATH. If present, this should contain a
colon-separated path indicating directories that should be searched
(in order) when loading plugin types.
A plugin programmer must include a function called
"ladspa_descriptor" with the following function prototype within
the shared object file. This function will have C-style linkage (if
you are using C++ this is taken care of by the `extern "C"' clause
at the top of the file).
A host will find the plugin shared object file by one means or
another, find the ladspa_descriptor() function, call it, and
proceed from there.
Plugin types are accessed by index (not ID) using values from 0
upwards. Out of range indexes must result in this function
returning NULL, so the plugin count can be determined by checking
for the least index that results in NULL being returned. */
const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index);
/* Datatype corresponding to the ladspa_descriptor() function. */
typedef const LADSPA_Descriptor *
(*LADSPA_Descriptor_Function)(unsigned long Index);
/**********************************************************************/
#ifdef __cplusplus
}
#endif
#endif /* LADSPA_INCLUDED */
/* EOF */

@ -0,0 +1,233 @@
/*
(c) Copyright 2021 github.com/lawl GPL3+
Free software by Richard W.E. Furse. Do with as you will. No
warranty.
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "ladspa.h"
#include "utils.h"
#include "../ringbuf.h"
#include "../rnnoise/rnnoise.h"
#define SF_INPUT 0
#define SF_OUTPUT 1
#define SF_VAD 2
#define FRAMESIZE_NSAMPLES 480
#define FRAMESIZE_BYTES (480 * sizeof(float))
#define VAD_GRACE_PERIOD 20
typedef struct {
DenoiseState *st;
ringbuf_t in_buf;
ringbuf_t out_buf;
int32_t remaining_grace_period;
int init;
LADSPA_Data *m_pfVAD;
LADSPA_Data *m_pfInput;
LADSPA_Data *m_pfOutput;
} rnnoiseFilter;
static LADSPA_Handle
instantiateSimpleFilter(const LADSPA_Descriptor *Descriptor,
unsigned long SampleRate) {
rnnoiseFilter *psFilter;
psFilter = (rnnoiseFilter *)malloc(sizeof(rnnoiseFilter));
if (psFilter) {
psFilter->in_buf = ringbuf_new(FRAMESIZE_BYTES * 100);
psFilter->out_buf = ringbuf_new(FRAMESIZE_BYTES * 100);
psFilter->init = 0;
psFilter->remaining_grace_period = VAD_GRACE_PERIOD;
psFilter->st = rnnoise_create(NULL);
}
return psFilter;
}
static void activateSimpleFilter(LADSPA_Handle Instance) {
}
static void connectPortToSimpleFilter(LADSPA_Handle Instance,
unsigned long Port,
LADSPA_Data *DataLocation) {
rnnoiseFilter *psFilter;
psFilter = (rnnoiseFilter *)Instance;
switch (Port) {
case SF_VAD:
psFilter->m_pfVAD = DataLocation;
break;
case SF_INPUT:
psFilter->m_pfInput = DataLocation;
break;
case SF_OUTPUT:
psFilter->m_pfOutput = DataLocation;
break;
}
}
static void runFilter(LADSPA_Handle Instance, unsigned long n_samples) {
rnnoiseFilter *psFilter;
psFilter = (rnnoiseFilter *)Instance;
ringbuf_t in_buf = psFilter->in_buf;
ringbuf_t out_buf = psFilter->out_buf;
float *in, *out, vad_thresh;
in = psFilter->m_pfInput;
out = psFilter->m_pfOutput;
vad_thresh = *psFilter->m_pfVAD / 100;
for (int i = 0; i < n_samples; i++) {
in[i] = in[i] * 32767;
}
ringbuf_memcpy_into(in_buf, in, n_samples * sizeof(float));
const size_t n_frames = ringbuf_bytes_used(in_buf) / FRAMESIZE_BYTES;
float tmpin[n_frames * FRAMESIZE_NSAMPLES];
ringbuf_memcpy_from(tmpin, in_buf, FRAMESIZE_BYTES * n_frames);
for (int i = 0; i < n_frames; i++) {
float tmp[FRAMESIZE_NSAMPLES];
float vad_prob = rnnoise_process_frame(psFilter->st, tmp,
tmpin + (i * FRAMESIZE_NSAMPLES));
if (vad_prob > vad_thresh) {
psFilter->remaining_grace_period = VAD_GRACE_PERIOD;
}
if (psFilter->remaining_grace_period >= 0) {
psFilter->remaining_grace_period--;
} else {
for (int i = 0; i < FRAMESIZE_NSAMPLES; i++) {
tmp[i] = 0.f;
}
}
ringbuf_memcpy_into(out_buf, tmp, FRAMESIZE_BYTES);
}
int frames_avail = ringbuf_bytes_used(out_buf) / FRAMESIZE_BYTES;
int samples_avail = frames_avail * FRAMESIZE_NSAMPLES;
if (samples_avail < n_samples) {
int skip = n_samples - samples_avail;
for (int i = 0; i < skip; i++) {
out[i] = 0.f;
}
ringbuf_memcpy_from(out + skip, out_buf, samples_avail * sizeof(float));
} else {
ringbuf_memcpy_from(out, out_buf, n_samples * sizeof(float));
}
for (int i = 0; i < n_samples; i++) {
out[i] = out[i] / 32767;
}
}
static void cleanupFilter(LADSPA_Handle Instance) {
rnnoiseFilter *psFilter = (rnnoiseFilter *)Instance;
rnnoise_destroy(psFilter->st);
ringbuf_free(&(psFilter->in_buf));
ringbuf_free(&(psFilter->out_buf));
free(Instance);
}
static LADSPA_Descriptor *g_psDescriptor = NULL;
ON_LOAD_ROUTINE {
char **pcPortNames;
LADSPA_PortDescriptor *piPortDescriptors;
LADSPA_PortRangeHint *psPortRangeHints;
g_psDescriptor = (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor));
if (g_psDescriptor != NULL) {
g_psDescriptor->UniqueID = 16682994;
g_psDescriptor->Label = strdup("noisetorch");
g_psDescriptor->Properties = LADSPA_PROPERTY_HARD_RT_CAPABLE;
g_psDescriptor->Name = strdup("NoiseTorch rnnoise ladspa module");
g_psDescriptor->Maker = strdup("lawl");
g_psDescriptor->Copyright = strdup("GPL3+");
g_psDescriptor->PortCount = 3;
piPortDescriptors =
(LADSPA_PortDescriptor *)calloc(3, sizeof(LADSPA_PortDescriptor));
g_psDescriptor->PortDescriptors =
(const LADSPA_PortDescriptor *)piPortDescriptors;
piPortDescriptors[SF_VAD] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
piPortDescriptors[SF_INPUT] = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
piPortDescriptors[SF_OUTPUT] = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
pcPortNames = (char **)calloc(3, sizeof(char *));
g_psDescriptor->PortNames = (const char **)pcPortNames;
pcPortNames[SF_VAD] = strdup("VAD %%");
pcPortNames[SF_INPUT] = strdup("Input");
pcPortNames[SF_OUTPUT] = strdup("Output");
psPortRangeHints =
((LADSPA_PortRangeHint *)calloc(3, sizeof(LADSPA_PortRangeHint)));
g_psDescriptor->PortRangeHints =
(const LADSPA_PortRangeHint *)psPortRangeHints;
psPortRangeHints[SF_VAD].HintDescriptor =
(LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE);
psPortRangeHints[SF_VAD].LowerBound = 0;
psPortRangeHints[SF_VAD].UpperBound = 95;
psPortRangeHints[SF_INPUT].HintDescriptor = 0;
psPortRangeHints[SF_OUTPUT].HintDescriptor = 0;
g_psDescriptor->instantiate = instantiateSimpleFilter;
g_psDescriptor->connect_port = connectPortToSimpleFilter;
g_psDescriptor->activate = activateSimpleFilter;
g_psDescriptor->run = runFilter;
g_psDescriptor->run_adding = NULL;
g_psDescriptor->set_run_adding_gain = NULL;
g_psDescriptor->deactivate = NULL;
g_psDescriptor->cleanup = cleanupFilter;
}
}
static void deleteDescriptor(LADSPA_Descriptor *psDescriptor) {
unsigned long lIndex;
if (psDescriptor) {
free((char *)psDescriptor->Label);
free((char *)psDescriptor->Name);
free((char *)psDescriptor->Maker);
free((char *)psDescriptor->Copyright);
free((LADSPA_PortDescriptor *)psDescriptor->PortDescriptors);
for (lIndex = 0; lIndex < psDescriptor->PortCount; lIndex++)
free((char *)(psDescriptor->PortNames[lIndex]));
free((char **)psDescriptor->PortNames);
free((LADSPA_PortRangeHint *)psDescriptor->PortRangeHints);
free(psDescriptor);
}
}
ON_UNLOAD_ROUTINE { deleteDescriptor(g_psDescriptor); }
const LADSPA_Descriptor *ladspa_descriptor(unsigned long Index) {
/* Return the requested descriptor or null if the index is out of
range. */
switch (Index) {
case 0:
return g_psDescriptor;
default:
return NULL;
}
}

@ -0,0 +1,105 @@
/* utils.h
Free software by Richard W.E. Furse. Do with as you will. No
warranty. */
#ifndef LADSPA_SDK_LOAD_PLUGIN_LIB
#define LADSPA_SDK_LOAD_PLUGIN_LIB
/*****************************************************************************/
#include "ladspa.h"
/*****************************************************************************/
/* Functions in load.c: */
/* This function call takes a plugin library filename, searches for
the library along the LADSPA_PATH, loads it with dlopen() and
returns a plugin handle for use with findPluginDescriptor() or
unloadLADSPAPluginLibrary(). Errors are handled by writing a
message to stderr and calling exit(1). It is alright (although
inefficient) to call this more than once for the same file. */
void * loadLADSPAPluginLibrary(const char * pcPluginFilename);
/* This function unloads a LADSPA plugin library. */
void unloadLADSPAPluginLibrary(void * pvLADSPAPluginLibrary);
/* This function locates a LADSPA plugin within a plugin library
loaded with loadLADSPAPluginLibrary(). Errors are handled by
writing a message to stderr and calling exit(1). Note that the
plugin library filename is only included to help provide
informative error messages. */
const LADSPA_Descriptor *
findLADSPAPluginDescriptor(void * pvLADSPAPluginLibrary,
const char * pcPluginLibraryFilename,
const char * pcPluginLabel);
/*****************************************************************************/
/* Functions in search.c: */
/* Callback function for use with LADSPAPluginSearch(). The callback
function passes the filename (full path), a plugin handle (dlopen()
style) and a LADSPA_DescriptorFunction (from which
LADSPA_Descriptors can be acquired). */
typedef void LADSPAPluginSearchCallbackFunction
(const char * pcFullFilename,
void * pvPluginHandle,
LADSPA_Descriptor_Function fDescriptorFunction);
/* Search through the $(LADSPA_PATH) (or a default path) for any
LADSPA plugin libraries. Each plugin library is tested using
dlopen() and dlsym(,"ladspa_descriptor"). After loading each
library, the callback function is called to process it. This
function leaves items passed to the callback function open. */
void LADSPAPluginSearch(LADSPAPluginSearchCallbackFunction fCallbackFunction);
/*****************************************************************************/
/* Function in default.c: */
/* Find the default value for a port. Return 0 if a default is found
and -1 if not. */
int getLADSPADefault(const LADSPA_PortRangeHint * psPortRangeHint,
const unsigned long lSampleRate,
LADSPA_Data * pfResult);
/*****************************************************************************/
/* During C pre-processing, take a string (passed in from the
Makefile) and put quote marks around it. */
#define RAW_STRINGIFY(x) #x
#define EXPAND_AND_STRINGIFY(x) RAW_STRINGIFY(x)
/*****************************************************************************/
#ifndef __cplusplus
/* In C, special incantations are needed to trigger initialisation and
cleanup routines when a dynamic plugin library is loaded or
unloaded (e.g. with dlopen() or dlclose()). _init() and _fini() are
classic exported symbols to achieve this, but these days GNU C
likes to do things a different way. Ideally we would check the GNU
version as older ones will probably expect the classic behaviour,
but for now... */
# if __GNUC__
/* Modern GNU C incantations: */
# define ON_LOAD_ROUTINE static void __attribute__ ((constructor)) init()
# define ON_UNLOAD_ROUTINE static void __attribute__ ((destructor)) fini()
# else
/* Classic incantations: */
# define ON_LOAD_ROUTINE void _init()
# define ON_UNLOAD_ROUTINE void _fini()
# endif
#else
/* In C++, we use the constructor/destructor of a static object to
manage initialisation and cleanup, so we don't need these
routines. */
#endif
/*****************************************************************************/
#endif
/* EOF */

@ -0,0 +1,347 @@
/*
* ringbuf.c - C ring buffer (FIFO) implementation.
*
* Written in 2011 by Drew Hess <dhess-src@bothan.net>.
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to
* the public domain worldwide. This software is distributed without
* any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "ringbuf.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/param.h>
#include <assert.h>
/*
* The code is written for clarity, not cleverness or performance, and
* contains many assert()s to enforce invariant assumptions and catch
* bugs. Feel free to optimize the code and to remove asserts for use
* in your own projects, once you're comfortable that it functions as
* intended.
*/
struct ringbuf_t
{
uint8_t *buf;
uint8_t *head, *tail;
size_t size;
};
ringbuf_t
ringbuf_new(size_t capacity)
{
ringbuf_t rb = malloc(sizeof(struct ringbuf_t));
if (rb) {
/* One byte is used for detecting the full condition. */
rb->size = capacity + 1;
rb->buf = malloc(rb->size);
if (rb->buf)
ringbuf_reset(rb);
else {
free(rb);
return 0;
}
}
return rb;
}
size_t
ringbuf_buffer_size(const struct ringbuf_t *rb)
{
return rb->size;
}
void
ringbuf_reset(ringbuf_t rb)
{
rb->head = rb->tail = rb->buf;
}
void
ringbuf_free(ringbuf_t *rb)
{
assert(rb && *rb);
free((*rb)->buf);
free(*rb);
*rb = 0;
}
size_t
ringbuf_capacity(const struct ringbuf_t *rb)
{
return ringbuf_buffer_size(rb) - 1;
}
/*
* Return a pointer to one-past-the-end of the ring buffer's
* contiguous buffer. You shouldn't normally need to use this function
* unless you're writing a new ringbuf_* function.
*/
static const uint8_t *
ringbuf_end(const struct ringbuf_t *rb)
{
return rb->buf + ringbuf_buffer_size(rb);
}
size_t
ringbuf_bytes_free(const struct ringbuf_t *rb)
{
if (rb->head >= rb->tail)
return ringbuf_capacity(rb) - (rb->head - rb->tail);
else
return rb->tail - rb->head - 1;
}
size_t
ringbuf_bytes_used(const struct ringbuf_t *rb)
{
return ringbuf_capacity(rb) - ringbuf_bytes_free(rb);
}
int
ringbuf_is_full(const struct ringbuf_t *rb)
{
return ringbuf_bytes_free(rb) == 0;
}
int
ringbuf_is_empty(const struct ringbuf_t *rb)
{
return ringbuf_bytes_free(rb) == ringbuf_capacity(rb);
}
const void *
ringbuf_tail(const struct ringbuf_t *rb)
{
return rb->tail;
}
const void *
ringbuf_head(const struct ringbuf_t *rb)
{
return rb->head;
}
/*
* Given a ring buffer rb and a pointer to a location within its
* contiguous buffer, return the a pointer to the next logical
* location in the ring buffer.
*/
static uint8_t *
ringbuf_nextp(ringbuf_t rb, const uint8_t *p)
{
/*
* The assert guarantees the expression (++p - rb->buf) is
* non-negative; therefore, the modulus operation is safe and
* portable.
*/
assert((p >= rb->buf) && (p < ringbuf_end(rb)));
return rb->buf + ((++p - rb->buf) % ringbuf_buffer_size(rb));
}
size_t
ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset)
{
const uint8_t *bufend = ringbuf_end(rb);
size_t bytes_used = ringbuf_bytes_used(rb);
if (offset >= bytes_used)
return bytes_used;
const uint8_t *start = rb->buf +
(((rb->tail - rb->buf) + offset) % ringbuf_buffer_size(rb));
assert(bufend > start);
size_t n = MIN(bufend - start, bytes_used - offset);
const uint8_t *found = memchr(start, c, n);
if (found)
return offset + (found - start);
else
return ringbuf_findchr(rb, c, offset + n);
}
size_t
ringbuf_memset(ringbuf_t dst, int c, size_t len)
{
const uint8_t *bufend = ringbuf_end(dst);
size_t nwritten = 0;
size_t count = MIN(len, ringbuf_buffer_size(dst));
int overflow = count > ringbuf_bytes_free(dst);
while (nwritten != count) {
/* don't copy beyond the end of the buffer */
assert(bufend > dst->head);
size_t n = MIN(bufend - dst->head, count - nwritten);
memset(dst->head, c, n);
dst->head += n;
nwritten += n;
/* wrap? */
if (dst->head == bufend)
dst->head = dst->buf;
}
if (overflow) {
dst->tail = ringbuf_nextp(dst, dst->head);
assert(ringbuf_is_full(dst));
}
return nwritten;
}
void *
ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count)
{
const uint8_t *u8src = src;
const uint8_t *bufend = ringbuf_end(dst);
int overflow = count > ringbuf_bytes_free(dst);
size_t nread = 0;
while (nread != count) {
/* don't copy beyond the end of the buffer */
assert(bufend > dst->head);
size_t n = MIN(bufend - dst->head, count - nread);
memcpy(dst->head, u8src + nread, n);
dst->head += n;
nread += n;
/* wrap? */
if (dst->head == bufend)
dst->head = dst->buf;
}
if (overflow) {
dst->tail = ringbuf_nextp(dst, dst->head);
assert(ringbuf_is_full(dst));
}
return dst->head;
}
ssize_t
ringbuf_read(int fd, ringbuf_t rb, size_t count)
{
const uint8_t *bufend = ringbuf_end(rb);
size_t nfree = ringbuf_bytes_free(rb);
/* don't write beyond the end of the buffer */
assert(bufend > rb->head);
count = MIN(bufend - rb->head, count);
ssize_t n = read(fd, rb->head, count);
if (n > 0) {
assert(rb->head + n <= bufend);
rb->head += n;
/* wrap? */
if (rb->head == bufend)
rb->head = rb->buf;
/* fix up the tail pointer if an overflow occurred */
if (n > nfree) {
rb->tail = ringbuf_nextp(rb, rb->head);
assert(ringbuf_is_full(rb));
}
}
return n;
}
void *
ringbuf_memcpy_from(void *dst, ringbuf_t src, size_t count)
{
size_t bytes_used = ringbuf_bytes_used(src);
if (count > bytes_used)
return 0;
uint8_t *u8dst = dst;
const uint8_t *bufend = ringbuf_end(src);
size_t nwritten = 0;
while (nwritten != count) {
assert(bufend > src->tail);
size_t n = MIN(bufend - src->tail, count - nwritten);
memcpy(u8dst + nwritten, src->tail, n);
src->tail += n;
nwritten += n;
/* wrap ? */
if (src->tail == bufend)
src->tail = src->buf;
}
assert(count + ringbuf_bytes_used(src) == bytes_used);
return src->tail;
}
ssize_t
ringbuf_write(int fd, ringbuf_t rb, size_t count)
{
size_t bytes_used = ringbuf_bytes_used(rb);
if (count > bytes_used)
return 0;
const uint8_t *bufend = ringbuf_end(rb);
assert(bufend > rb->head);
count = MIN(bufend - rb->tail, count);
ssize_t n = write(fd, rb->tail, count);
if (n > 0) {
assert(rb->tail + n <= bufend);
rb->tail += n;
/* wrap? */
if (rb->tail == bufend)
rb->tail = rb->buf;
assert(n + ringbuf_bytes_used(rb) == bytes_used);
}
return n;
}
void *
ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count)
{
size_t src_bytes_used = ringbuf_bytes_used(src);
if (count > src_bytes_used)
return 0;
int overflow = count > ringbuf_bytes_free(dst);
const uint8_t *src_bufend = ringbuf_end(src);
const uint8_t *dst_bufend = ringbuf_end(dst);
size_t ncopied = 0;
while (ncopied != count) {
assert(src_bufend > src->tail);
size_t nsrc = MIN(src_bufend - src->tail, count - ncopied);
assert(dst_bufend > dst->head);
size_t n = MIN(dst_bufend - dst->head, nsrc);
memcpy(dst->head, src->tail, n);
src->tail += n;
dst->head += n;
ncopied += n;
/* wrap ? */
if (src->tail == src_bufend)
src->tail = src->buf;
if (dst->head == dst_bufend)
dst->head = dst->buf;
}
assert(count + ringbuf_bytes_used(src) == src_bytes_used);
if (overflow) {
dst->tail = ringbuf_nextp(dst, dst->head);
assert(ringbuf_is_full(dst));
}
return dst->head;
}

@ -0,0 +1,243 @@
#ifndef INCLUDED_RINGBUF_H
#define INCLUDED_RINGBUF_H
/*
* ringbuf.h - C ring buffer (FIFO) interface.
*
* Written in 2011 by Drew Hess <dhess-src@bothan.net>.
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to
* the public domain worldwide. This software is distributed without
* any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
/*
* A byte-addressable ring buffer FIFO implementation.
*
* The ring buffer's head pointer points to the starting location
* where data should be written when copying data *into* the buffer
* (e.g., with ringbuf_read). The ring buffer's tail pointer points to
* the starting location where data should be read when copying data
* *from* the buffer (e.g., with ringbuf_write).
*/
#include <stddef.h>
#include <sys/types.h>
typedef struct ringbuf_t *ringbuf_t;
/*
* Create a new ring buffer with the given capacity (usable
* bytes). Note that the actual internal buffer size may be one or
* more bytes larger than the usable capacity, for bookkeeping.
*
* Returns the new ring buffer object, or 0 if there's not enough
* memory to fulfill the request for the given capacity.
*/
ringbuf_t
ringbuf_new(size_t capacity);
/*
* The size of the internal buffer, in bytes. One or more bytes may be
* unusable in order to distinguish the "buffer full" state from the
* "buffer empty" state.
*
* For the usable capacity of the ring buffer, use the
* ringbuf_capacity function.
*/
size_t
ringbuf_buffer_size(const struct ringbuf_t *rb);
/*
* Deallocate a ring buffer, and, as a side effect, set the pointer to
* 0.
*/
void
ringbuf_free(ringbuf_t *rb);
/*
* Reset a ring buffer to its initial state (empty).
*/
void
ringbuf_reset(ringbuf_t rb);
/*
* The usable capacity of the ring buffer, in bytes. Note that this
* value may be less than the ring buffer's internal buffer size, as
* returned by ringbuf_buffer_size.
*/
size_t
ringbuf_capacity(const struct ringbuf_t *rb);
/*
* The number of free/available bytes in the ring buffer. This value
* is never larger than the ring buffer's usable capacity.
*/
size_t
ringbuf_bytes_free(const struct ringbuf_t *rb);
/*
* The number of bytes currently being used in the ring buffer. This
* value is never larger than the ring buffer's usable capacity.
*/
size_t
ringbuf_bytes_used(const struct ringbuf_t *rb);
int
ringbuf_is_full(const struct ringbuf_t *rb);
int
ringbuf_is_empty(const struct ringbuf_t *rb);
/*
* Const access to the head and tail pointers of the ring buffer.
*/
const void *
ringbuf_tail(const struct ringbuf_t *rb);
const void *
ringbuf_head(const struct ringbuf_t *rb);
/*
* Locate the first occurrence of character c (converted to an
* unsigned char) in ring buffer rb, beginning the search at offset
* bytes from the ring buffer's tail pointer. The function returns the
* offset of the character from the ring buffer's tail pointer, if
* found. If c does not occur in the ring buffer, the function returns
* the number of bytes used in the ring buffer.
*
* Note that the offset parameter and the returned offset are logical
* offsets from the tail pointer, not necessarily linear offsets.
*/
size_t
ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset);
/*
* Beginning at ring buffer dst's head pointer, fill the ring buffer
* with a repeating sequence of len bytes, each of value c (converted
* to an unsigned char). len can be as large as you like, but the
* function will never write more than ringbuf_buffer_size(dst) bytes
* in a single invocation, since that size will cause all bytes in the
* ring buffer to be written exactly once each.
*
* Note that if len is greater than the number of free bytes in dst,
* the ring buffer will overflow. When an overflow occurs, the state
* of the ring buffer is guaranteed to be consistent, including the
* head and tail pointers; old data will simply be overwritten in FIFO
* fashion, as needed. However, note that, if calling the function
* results in an overflow, the value of the ring buffer's tail pointer
* may be different than it was before the function was called.
*
* Returns the actual number of bytes written to dst: len, if
* len < ringbuf_buffer_size(dst), else ringbuf_buffer_size(dst).
*/
size_t
ringbuf_memset(ringbuf_t dst, int c, size_t len);
/*
* Copy n bytes from a contiguous memory area src into the ring buffer
* dst. Returns the ring buffer's new head pointer.
*
* It is possible to copy more data from src than is available in the
* buffer; i.e., it's possible to overflow the ring buffer using this
* function. When an overflow occurs, the state of the ring buffer is
* guaranteed to be consistent, including the head and tail pointers;
* old data will simply be overwritten in FIFO fashion, as
* needed. However, note that, if calling the function results in an
* overflow, the value of the ring buffer's tail pointer may be
* different than it was before the function was called.
*/
void *
ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count);
/*
* This convenience function calls read(2) on the file descriptor fd,
* using the ring buffer rb as the destination buffer for the read,
* and returns the value returned by read(2). It will only call
* read(2) once, and may return a short count.
*
* It is possible to read more data from the file descriptor than is
* available in the buffer; i.e., it's possible to overflow the ring
* buffer using this function. When an overflow occurs, the state of
* the ring buffer is guaranteed to be consistent, including the head
* and tail pointers: old data will simply be overwritten in FIFO
* fashion, as needed. However, note that, if calling the function
* results in an overflow, the value of the ring buffer's tail pointer
* may be different than it was before the function was called.
*/
ssize_t
ringbuf_read(int fd, ringbuf_t rb, size_t count);
/*
* Copy n bytes from the ring buffer src, starting from its tail
* pointer, into a contiguous memory area dst. Returns the value of
* src's tail pointer after the copy is finished.
*
* Note that this copy is destructive with respect to the ring buffer:
* the n bytes copied from the ring buffer are no longer available in
* the ring buffer after the copy is complete, and the ring buffer
* will have n more free bytes than it did before the function was
* called.
*
* This function will *not* allow the ring buffer to underflow. If
* count is greater than the number of bytes used in the ring buffer,
* no bytes are copied, and the function will return 0.
*/
void *
ringbuf_memcpy_from(void *dst, ringbuf_t src, size_t count);
/*
* This convenience function calls write(2) on the file descriptor fd,
* using the ring buffer rb as the source buffer for writing (starting
* at the ring buffer's tail pointer), and returns the value returned
* by write(2). It will only call write(2) once, and may return a
* short count.
*
* Note that this copy is destructive with respect to the ring buffer:
* any bytes written from the ring buffer to the file descriptor are
* no longer available in the ring buffer after the copy is complete,
* and the ring buffer will have N more free bytes than it did before
* the function was called, where N is the value returned by the
* function (unless N is < 0, in which case an error occurred and no
* bytes were written).
*
* This function will *not* allow the ring buffer to underflow. If
* count is greater than the number of bytes used in the ring buffer,
* no bytes are written to the file descriptor, and the function will
* return 0.
*/
ssize_t
ringbuf_write(int fd, ringbuf_t rb, size_t count);
/*
* Copy count bytes from ring buffer src, starting from its tail
* pointer, into ring buffer dst. Returns dst's new head pointer after
* the copy is finished.
*
* Note that this copy is destructive with respect to the ring buffer
* src: any bytes copied from src into dst are no longer available in
* src after the copy is complete, and src will have 'count' more free
* bytes than it did before the function was called.
*
* It is possible to copy more data from src than is available in dst;
* i.e., it's possible to overflow dst using this function. When an
* overflow occurs, the state of dst is guaranteed to be consistent,
* including the head and tail pointers; old data will simply be
* overwritten in FIFO fashion, as needed. However, note that, if
* calling the function results in an overflow, the value dst's tail
* pointer may be different than it was before the function was
* called.
*
* It is *not* possible to underflow src; if count is greater than the
* number of bytes used in src, no bytes are copied, and the function
* returns 0.
*/
void *
ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count);
#endif /* INCLUDED_RINGBUF_H */

@ -0,0 +1,31 @@
Copyright (c) 2017, Mozilla
Copyright (c) 2007-2017, Jean-Marc Valin
Copyright (c) 2005-2017, Xiph.Org Foundation
Copyright (c) 2003-2004, Mark Borgerding
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.Org Foundation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,182 @@
/*Copyright (c) 2003-2004, Mark Borgerding
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.*/
#ifndef KISS_FFT_GUTS_H
#define KISS_FFT_GUTS_H
#define MIN(a,b) ((a)<(b) ? (a):(b))
#define MAX(a,b) ((a)>(b) ? (a):(b))
/* kiss_fft.h
defines kiss_fft_scalar as either short or a float type
and defines
typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */
#include "kiss_fft.h"
/*
Explanation of macros dealing with complex math:
C_MUL(m,a,b) : m = a*b
C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise
C_SUB( res, a,b) : res = a - b
C_SUBFROM( res , a) : res -= a
C_ADDTO( res , a) : res += a
* */
#ifdef FIXED_POINT
#include "arch.h"
#define SAMP_MAX 2147483647
#define TWID_MAX 32767
#define TRIG_UPSCALE 1
#define SAMP_MIN -SAMP_MAX
# define S_MUL(a,b) MULT16_32_Q15(b, a)
# define C_MUL(m,a,b) \
do{ (m).r = SUB32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
(m).i = ADD32_ovflw(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0)
# define C_MULC(m,a,b) \
do{ (m).r = ADD32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \
(m).i = SUB32_ovflw(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0)
# define C_MULBYSCALAR( c, s ) \
do{ (c).r = S_MUL( (c).r , s ) ;\
(c).i = S_MUL( (c).i , s ) ; }while(0)
# define DIVSCALAR(x,k) \
(x) = S_MUL( x, (TWID_MAX-((k)>>1))/(k)+1 )
# define C_FIXDIV(c,div) \
do { DIVSCALAR( (c).r , div); \
DIVSCALAR( (c).i , div); }while (0)
#define C_ADD( res, a,b)\
do {(res).r=ADD32_ovflw((a).r,(b).r); (res).i=ADD32_ovflw((a).i,(b).i); \
}while(0)
#define C_SUB( res, a,b)\
do {(res).r=SUB32_ovflw((a).r,(b).r); (res).i=SUB32_ovflw((a).i,(b).i); \
}while(0)
#define C_ADDTO( res , a)\
do {(res).r = ADD32_ovflw((res).r, (a).r); (res).i = ADD32_ovflw((res).i,(a).i);\
}while(0)
#define C_SUBFROM( res , a)\
do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \
}while(0)
#if defined(OPUS_ARM_INLINE_ASM)
#include "arm/kiss_fft_armv4.h"
#endif
#if defined(OPUS_ARM_INLINE_EDSP)
#include "arm/kiss_fft_armv5e.h"
#endif
#if defined(MIPSr1_ASM)
#include "mips/kiss_fft_mipsr1.h"
#endif
#else /* not FIXED_POINT*/
# define S_MUL(a,b) ( (a)*(b) )
#define C_MUL(m,a,b) \
do{ (m).r = (a).r*(b).r - (a).i*(b).i;\
(m).i = (a).r*(b).i + (a).i*(b).r; }while(0)
#define C_MULC(m,a,b) \
do{ (m).r = (a).r*(b).r + (a).i*(b).i;\
(m).i = (a).i*(b).r - (a).r*(b).i; }while(0)
#define C_MUL4(m,a,b) C_MUL(m,a,b)
# define C_FIXDIV(c,div) /* NOOP */
# define C_MULBYSCALAR( c, s ) \
do{ (c).r *= (s);\
(c).i *= (s); }while(0)
#endif
#ifndef CHECK_OVERFLOW_OP
# define CHECK_OVERFLOW_OP(a,op,b) /* noop */
#endif
#ifndef C_ADD
#define C_ADD( res, a,b)\
do { \
CHECK_OVERFLOW_OP((a).r,+,(b).r)\
CHECK_OVERFLOW_OP((a).i,+,(b).i)\
(res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \
}while(0)
#define C_SUB( res, a,b)\
do { \
CHECK_OVERFLOW_OP((a).r,-,(b).r)\
CHECK_OVERFLOW_OP((a).i,-,(b).i)\
(res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \
}while(0)
#define C_ADDTO( res , a)\
do { \
CHECK_OVERFLOW_OP((res).r,+,(a).r)\
CHECK_OVERFLOW_OP((res).i,+,(a).i)\
(res).r += (a).r; (res).i += (a).i;\
}while(0)
#define C_SUBFROM( res , a)\
do {\
CHECK_OVERFLOW_OP((res).r,-,(a).r)\
CHECK_OVERFLOW_OP((res).i,-,(a).i)\
(res).r -= (a).r; (res).i -= (a).i; \
}while(0)
#endif /* C_ADD defined */
#ifdef FIXED_POINT
/*# define KISS_FFT_COS(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase))))
# define KISS_FFT_SIN(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase))))*/
# define KISS_FFT_COS(phase) floor(.5+TWID_MAX*cos (phase))
# define KISS_FFT_SIN(phase) floor(.5+TWID_MAX*sin (phase))
# define HALF_OF(x) ((x)>>1)
#elif defined(USE_SIMD)
# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) )
# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) )
# define HALF_OF(x) ((x)*_mm_set1_ps(.5f))
#else
# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)
# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)
# define HALF_OF(x) ((x)*.5f)
#endif
#define kf_cexp(x,phase) \
do{ \
(x)->r = KISS_FFT_COS(phase);\
(x)->i = KISS_FFT_SIN(phase);\
}while(0)
#define kf_cexp2(x,phase) \
do{ \
(x)->r = TRIG_UPSCALE*celt_cos_norm((phase));\
(x)->i = TRIG_UPSCALE*celt_cos_norm((phase)-32768);\
}while(0)
#endif /* KISS_FFT_GUTS_H */

@ -0,0 +1,261 @@
/* Copyright (c) 2003-2008 Jean-Marc Valin
Copyright (c) 2007-2008 CSIRO
Copyright (c) 2007-2009 Xiph.Org Foundation
Written by Jean-Marc Valin */
/**
@file arch.h
@brief Various architecture definitions for CELT
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ARCH_H
#define ARCH_H
#include "opus_types.h"
#include "common.h"
# if !defined(__GNUC_PREREQ)
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
# define __GNUC_PREREQ(_maj,_min) \
((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
# else
# define __GNUC_PREREQ(_maj,_min) 0
# endif
# endif
#define CELT_SIG_SCALE 32768.f
#define celt_fatal(str) _celt_fatal(str, __FILE__, __LINE__);
#ifdef ENABLE_ASSERTIONS
#include <stdio.h>
#include <stdlib.h>
#ifdef __GNUC__
__attribute__((noreturn))
#endif
static OPUS_INLINE void _celt_fatal(const char *str, const char *file, int line)
{
fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str);
abort();
}
#define celt_assert(cond) {if (!(cond)) {celt_fatal("assertion failed: " #cond);}}
#define celt_assert2(cond, message) {if (!(cond)) {celt_fatal("assertion failed: " #cond "\n" message);}}
#else
#define celt_assert(cond)
#define celt_assert2(cond, message)
#endif
#define IMUL32(a,b) ((a)*(b))
#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 16-bit value. */
#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */
#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 32-bit value. */
#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */
#define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */
#define IMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum int value. */
#define UADD32(a,b) ((a)+(b))
#define USUB32(a,b) ((a)-(b))
/* Set this if opus_int64 is a native type of the CPU. */
/* Assume that all LP64 architectures have fast 64-bit types; also x86_64
(which can be ILP32 for x32) and Win64 (which is LLP64). */
#if defined(__x86_64__) || defined(__LP64__) || defined(_WIN64)
#define OPUS_FAST_INT64 1
#else
#define OPUS_FAST_INT64 0
#endif
#define PRINT_MIPS(file)
#ifdef FIXED_POINT
typedef opus_int16 opus_val16;
typedef opus_int32 opus_val32;
typedef opus_int64 opus_val64;
typedef opus_val32 celt_sig;
typedef opus_val16 celt_norm;
typedef opus_val32 celt_ener;
#define Q15ONE 32767
#define SIG_SHIFT 12
/* Safe saturation value for 32-bit signals. Should be less than
2^31*(1-0.85) to avoid blowing up on DC at deemphasis.*/
#define SIG_SAT (300000000)
#define NORM_SCALING 16384
#define DB_SHIFT 10
#define EPSILON 1
#define VERY_SMALL 0
#define VERY_LARGE16 ((opus_val16)32767)
#define Q15_ONE ((opus_val16)32767)
#define SCALEIN(a) (a)
#define SCALEOUT(a) (a)
#define ABS16(x) ((x) < 0 ? (-(x)) : (x))
#define ABS32(x) ((x) < 0 ? (-(x)) : (x))
static OPUS_INLINE opus_int16 SAT16(opus_int32 x) {
return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x;
}
#ifdef FIXED_DEBUG
#include "fixed_debug.h"
#else
#include "fixed_generic.h"
#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR
#include "arm/fixed_arm64.h"
#elif OPUS_ARM_INLINE_EDSP
#include "arm/fixed_armv5e.h"
#elif defined (OPUS_ARM_INLINE_ASM)
#include "arm/fixed_armv4.h"
#elif defined (BFIN_ASM)
#include "fixed_bfin.h"
#elif defined (TI_C5X_ASM)
#include "fixed_c5x.h"
#elif defined (TI_C6X_ASM)
#include "fixed_c6x.h"
#endif
#endif
#else /* FIXED_POINT */
typedef float opus_val16;
typedef float opus_val32;
typedef float opus_val64;
typedef float celt_sig;
typedef float celt_norm;
typedef float celt_ener;
#ifdef FLOAT_APPROX
/* This code should reliably detect NaN/inf even when -ffast-math is used.
Assumes IEEE 754 format. */
static OPUS_INLINE int celt_isnan(float x)
{
union {float f; opus_uint32 i;} in;
in.f = x;
return ((in.i>>23)&0xFF)==0xFF && (in.i&0x007FFFFF)!=0;
}
#else
#ifdef __FAST_MATH__
#error Cannot build libopus with -ffast-math unless FLOAT_APPROX is defined. This could result in crashes on extreme (e.g. NaN) input
#endif
#define celt_isnan(x) ((x)!=(x))
#endif
#define Q15ONE 1.0f
#define NORM_SCALING 1.f
#define EPSILON 1e-15f
#define VERY_SMALL 1e-30f
#define VERY_LARGE16 1e15f
#define Q15_ONE ((opus_val16)1.f)
/* This appears to be the same speed as C99's fabsf() but it's more portable. */
#define ABS16(x) ((float)fabs(x))
#define ABS32(x) ((float)fabs(x))
#define QCONST16(x,bits) (x)
#define QCONST32(x,bits) (x)
#define NEG16(x) (-(x))
#define NEG32(x) (-(x))
#define NEG32_ovflw(x) (-(x))
#define EXTRACT16(x) (x)
#define EXTEND32(x) (x)
#define SHR16(a,shift) (a)
#define SHL16(a,shift) (a)
#define SHR32(a,shift) (a)
#define SHL32(a,shift) (a)
#define PSHR32(a,shift) (a)
#define VSHR32(a,shift) (a)
#define PSHR(a,shift) (a)
#define SHR(a,shift) (a)
#define SHL(a,shift) (a)
#define SATURATE(x,a) (x)
#define SATURATE16(x) (x)
#define ROUND16(a,shift) (a)
#define SROUND16(a,shift) (a)
#define HALF16(x) (.5f*(x))
#define HALF32(x) (.5f*(x))
#define ADD16(a,b) ((a)+(b))
#define SUB16(a,b) ((a)-(b))
#define ADD32(a,b) ((a)+(b))
#define SUB32(a,b) ((a)-(b))
#define ADD32_ovflw(a,b) ((a)+(b))
#define SUB32_ovflw(a,b) ((a)-(b))
#define MULT16_16_16(a,b) ((a)*(b))
#define MULT16_16(a,b) ((opus_val32)(a)*(opus_val32)(b))
#define MAC16_16(c,a,b) ((c)+(opus_val32)(a)*(opus_val32)(b))
#define MULT16_32_Q15(a,b) ((a)*(b))
#define MULT16_32_Q16(a,b) ((a)*(b))
#define MULT32_32_Q31(a,b) ((a)*(b))
#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b))
#define MAC16_32_Q16(c,a,b) ((c)+(a)*(b))
#define MULT16_16_Q11_32(a,b) ((a)*(b))
#define MULT16_16_Q11(a,b) ((a)*(b))
#define MULT16_16_Q13(a,b) ((a)*(b))
#define MULT16_16_Q14(a,b) ((a)*(b))
#define MULT16_16_Q15(a,b) ((a)*(b))
#define MULT16_16_P15(a,b) ((a)*(b))
#define MULT16_16_P13(a,b) ((a)*(b))
#define MULT16_16_P14(a,b) ((a)*(b))
#define MULT16_32_P16(a,b) ((a)*(b))
#define DIV32_16(a,b) (((opus_val32)(a))/(opus_val16)(b))
#define DIV32(a,b) (((opus_val32)(a))/(opus_val32)(b))
#define SCALEIN(a) ((a)*CELT_SIG_SCALE)
#define SCALEOUT(a) ((a)*(1/CELT_SIG_SCALE))
#define SIG2WORD16(x) (x)
#endif /* !FIXED_POINT */
#ifndef GLOBAL_STACK_SIZE
#ifdef FIXED_POINT
#define GLOBAL_STACK_SIZE 120000
#else
#define GLOBAL_STACK_SIZE 120000
#endif
#endif
#endif /* ARCH_H */

@ -0,0 +1,279 @@
/* Copyright (c) 2009-2010 Xiph.Org Foundation
Written by Jean-Marc Valin */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "celt_lpc.h"
#include "arch.h"
#include "common.h"
#include "pitch.h"
void _celt_lpc(
opus_val16 *_lpc, /* out: [0...p-1] LPC coefficients */
const opus_val32 *ac, /* in: [0...p] autocorrelation values */
int p
)
{
int i, j;
opus_val32 r;
opus_val32 error = ac[0];
#ifdef FIXED_POINT
opus_val32 lpc[LPC_ORDER];
#else
float *lpc = _lpc;
#endif
RNN_CLEAR(lpc, p);
if (ac[0] != 0)
{
for (i = 0; i < p; i++) {
/* Sum up this iteration's reflection coefficient */
opus_val32 rr = 0;
for (j = 0; j < i; j++)
rr += MULT32_32_Q31(lpc[j],ac[i - j]);
rr += SHR32(ac[i + 1],3);
r = -SHL32(rr,3)/error;
/* Update LPC coefficients and total error */
lpc[i] = SHR32(r,3);
for (j = 0; j < (i+1)>>1; j++)
{
opus_val32 tmp1, tmp2;
tmp1 = lpc[j];
tmp2 = lpc[i-1-j];
lpc[j] = tmp1 + MULT32_32_Q31(r,tmp2);
lpc[i-1-j] = tmp2 + MULT32_32_Q31(r,tmp1);
}
error = error - MULT32_32_Q31(MULT32_32_Q31(r,r),error);
/* Bail out once we get 30 dB gain */
#ifdef FIXED_POINT
if (error<SHR32(ac[0],10))
break;
#else
if (error<.001f*ac[0])
break;
#endif
}
}
#ifdef FIXED_POINT
for (i=0;i<p;i++)
_lpc[i] = ROUND16(lpc[i],16);
#endif
}
void celt_fir(
const opus_val16 *x,
const opus_val16 *num,
opus_val16 *y,
int N,
int ord)
{
int i,j;
opus_val16 rnum[ord];
for(i=0;i<ord;i++)
rnum[i] = num[ord-i-1];
for (i=0;i<N-3;i+=4)
{
opus_val32 sum[4];
sum[0] = SHL32(EXTEND32(x[i ]), SIG_SHIFT);
sum[1] = SHL32(EXTEND32(x[i+1]), SIG_SHIFT);
sum[2] = SHL32(EXTEND32(x[i+2]), SIG_SHIFT);
sum[3] = SHL32(EXTEND32(x[i+3]), SIG_SHIFT);
xcorr_kernel(rnum, x+i-ord, sum, ord);
y[i ] = ROUND16(sum[0], SIG_SHIFT);
y[i+1] = ROUND16(sum[1], SIG_SHIFT);
y[i+2] = ROUND16(sum[2], SIG_SHIFT);
y[i+3] = ROUND16(sum[3], SIG_SHIFT);
}
for (;i<N;i++)
{
opus_val32 sum = SHL32(EXTEND32(x[i]), SIG_SHIFT);
for (j=0;j<ord;j++)
sum = MAC16_16(sum,rnum[j],x[i+j-ord]);
y[i] = ROUND16(sum, SIG_SHIFT);
}
}
void celt_iir(const opus_val32 *_x,
const opus_val16 *den,
opus_val32 *_y,
int N,
int ord,
opus_val16 *mem)
{
#ifdef SMALL_FOOTPRINT
int i,j;
for (i=0;i<N;i++)
{
opus_val32 sum = _x[i];
for (j=0;j<ord;j++)
{
sum -= MULT16_16(den[j],mem[j]);
}
for (j=ord-1;j>=1;j--)
{
mem[j]=mem[j-1];
}
mem[0] = SROUND16(sum, SIG_SHIFT);
_y[i] = sum;
}
#else
int i,j;
celt_assert((ord&3)==0);
opus_val16 rden[ord];
opus_val16 y[N+ord];
for(i=0;i<ord;i++)
rden[i] = den[ord-i-1];
for(i=0;i<ord;i++)
y[i] = -mem[ord-i-1];
for(;i<N+ord;i++)
y[i]=0;
for (i=0;i<N-3;i+=4)
{
/* Unroll by 4 as if it were an FIR filter */
opus_val32 sum[4];
sum[0]=_x[i];
sum[1]=_x[i+1];
sum[2]=_x[i+2];
sum[3]=_x[i+3];
xcorr_kernel(rden, y+i, sum, ord);
/* Patch up the result to compensate for the fact that this is an IIR */
y[i+ord ] = -SROUND16(sum[0],SIG_SHIFT);
_y[i ] = sum[0];
sum[1] = MAC16_16(sum[1], y[i+ord ], den[0]);
y[i+ord+1] = -SROUND16(sum[1],SIG_SHIFT);
_y[i+1] = sum[1];
sum[2] = MAC16_16(sum[2], y[i+ord+1], den[0]);
sum[2] = MAC16_16(sum[2], y[i+ord ], den[1]);
y[i+ord+2] = -SROUND16(sum[2],SIG_SHIFT);
_y[i+2] = sum[2];
sum[3] = MAC16_16(sum[3], y[i+ord+2], den[0]);
sum[3] = MAC16_16(sum[3], y[i+ord+1], den[1]);
sum[3] = MAC16_16(sum[3], y[i+ord ], den[2]);
y[i+ord+3] = -SROUND16(sum[3],SIG_SHIFT);
_y[i+3] = sum[3];
}
for (;i<N;i++)
{
opus_val32 sum = _x[i];
for (j=0;j<ord;j++)
sum -= MULT16_16(rden[j],y[i+j]);
y[i+ord] = SROUND16(sum,SIG_SHIFT);
_y[i] = sum;
}
for(i=0;i<ord;i++)
mem[i] = _y[N-i-1];
#endif
}
int _celt_autocorr(
const opus_val16 *x, /* in: [0...n-1] samples x */
opus_val32 *ac, /* out: [0...lag-1] ac values */
const opus_val16 *window,
int overlap,
int lag,
int n)
{
opus_val32 d;
int i, k;
int fastN=n-lag;
int shift;
const opus_val16 *xptr;
opus_val16 xx[n];
celt_assert(n>0);
celt_assert(overlap>=0);
if (overlap == 0)
{
xptr = x;
} else {
for (i=0;i<n;i++)
xx[i] = x[i];
for (i=0;i<overlap;i++)
{
xx[i] = MULT16_16_Q15(x[i],window[i]);
xx[n-i-1] = MULT16_16_Q15(x[n-i-1],window[i]);
}
xptr = xx;
}
shift=0;
#ifdef FIXED_POINT
{
opus_val32 ac0;
ac0 = 1+(n<<7);
if (n&1) ac0 += SHR32(MULT16_16(xptr[0],xptr[0]),9);
for(i=(n&1);i<n;i+=2)
{
ac0 += SHR32(MULT16_16(xptr[i],xptr[i]),9);
ac0 += SHR32(MULT16_16(xptr[i+1],xptr[i+1]),9);
}
shift = celt_ilog2(ac0)-30+10;
shift = (shift)/2;
if (shift>0)
{
for(i=0;i<n;i++)
xx[i] = PSHR32(xptr[i], shift);
xptr = xx;
} else
shift = 0;
}
#endif
celt_pitch_xcorr(xptr, xptr, ac, fastN, lag+1);
for (k=0;k<=lag;k++)
{
for (i = k+fastN, d = 0; i < n; i++)
d = MAC16_16(d, xptr[i], xptr[i-k]);
ac[k] += d;
}
#ifdef FIXED_POINT
shift = 2*shift;
if (shift<=0)
ac[0] += SHL32((opus_int32)1, -shift);
if (ac[0] < 268435456)
{
int shift2 = 29 - EC_ILOG(ac[0]);
for (i=0;i<=lag;i++)
ac[i] = SHL32(ac[i], shift2);
shift -= shift2;
} else if (ac[0] >= 536870912)
{
int shift2=1;
if (ac[0] >= 1073741824)
shift2++;
for (i=0;i<=lag;i++)
ac[i] = SHR32(ac[i], shift2);
shift += shift2;
}
#endif
return shift;
}

@ -0,0 +1,59 @@
/* Copyright (c) 2009-2010 Xiph.Org Foundation
Written by Jean-Marc Valin */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef PLC_H
#define PLC_H
#include "arch.h"
#include "common.h"
#if defined(OPUS_X86_MAY_HAVE_SSE4_1)
#include "x86/celt_lpc_sse.h"
#endif
#define LPC_ORDER 24
void _celt_lpc(opus_val16 *_lpc, const opus_val32 *ac, int p);
void celt_fir(
const opus_val16 *x,
const opus_val16 *num,
opus_val16 *y,
int N,
int ord);
void celt_iir(const opus_val32 *x,
const opus_val16 *den,
opus_val32 *y,
int N,
int ord,
opus_val16 *mem);
int _celt_autocorr(const opus_val16 *x, opus_val32 *ac,
const opus_val16 *window, int overlap, int lag, int n);
#endif /* PLC_H */

@ -0,0 +1,48 @@
#ifndef COMMON_H
#define COMMON_H
#include "stdlib.h"
#include "string.h"
#define RNN_INLINE inline
#define OPUS_INLINE inline
/** RNNoise wrapper for malloc(). To do your own dynamic allocation, all you need t
o do is replace this function and rnnoise_free */
#ifndef OVERRIDE_RNNOISE_ALLOC
static RNN_INLINE void *rnnoise_alloc (size_t size)
{
return malloc(size);
}
#endif
/** RNNoise wrapper for free(). To do your own dynamic allocation, all you need to do is replace this function and rnnoise_alloc */
#ifndef OVERRIDE_RNNOISE_FREE
static RNN_INLINE void rnnoise_free (void *ptr)
{
free(ptr);
}
#endif
/** Copy n elements from src to dst. The 0* term provides compile-time type checking */
#ifndef OVERRIDE_RNN_COPY
#define RNN_COPY(dst, src, n) (memcpy((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) ))
#endif
/** Copy n elements from src to dst, allowing overlapping regions. The 0* term
provides compile-time type checking */
#ifndef OVERRIDE_RNN_MOVE
#define RNN_MOVE(dst, src, n) (memmove((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) ))
#endif
/** Set n elements of dst to zero */
#ifndef OVERRIDE_RNN_CLEAR
#define RNN_CLEAR(dst, n) (memset((dst), 0, (n)*sizeof(*(dst))))
#endif
#endif

@ -0,0 +1,3 @@
#!/bin/sh
gcc -DTRAINING=1 -Wall -W -O3 -g -I../include denoise.c kiss_fft.c pitch.c celt_lpc.c rnn.c rnn_data.c -o denoise_training -lm

@ -0,0 +1,642 @@
/* Copyright (c) 2018 Gregor Richards
* Copyright (c) 2017 Mozilla */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "kiss_fft.h"
#include "common.h"
#include <math.h>
#include "rnnoise.h"
#include "pitch.h"
#include "arch.h"
#include "rnn.h"
#include "rnn_data.h"
#define FRAME_SIZE_SHIFT 2
#define FRAME_SIZE (120<<FRAME_SIZE_SHIFT)
#define WINDOW_SIZE (2*FRAME_SIZE)
#define FREQ_SIZE (FRAME_SIZE + 1)
#define PITCH_MIN_PERIOD 60
#define PITCH_MAX_PERIOD 768
#define PITCH_FRAME_SIZE 960
#define PITCH_BUF_SIZE (PITCH_MAX_PERIOD+PITCH_FRAME_SIZE)
#define SQUARE(x) ((x)*(x))
#define NB_BANDS 22
#define CEPS_MEM 8
#define NB_DELTA_CEPS 6
#define NB_FEATURES (NB_BANDS+3*NB_DELTA_CEPS+2)
#ifndef TRAINING
#define TRAINING 0
#endif
/* The built-in model, used if no file is given as input */
extern const struct RNNModel rnnoise_model_orig;
static const opus_int16 eband5ms[] = {
/*0 200 400 600 800 1k 1.2 1.4 1.6 2k 2.4 2.8 3.2 4k 4.8 5.6 6.8 8k 9.6 12k 15.6 20k*/
0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 34, 40, 48, 60, 78, 100
};
typedef struct {
int init;
kiss_fft_state *kfft;
float half_window[FRAME_SIZE];
float dct_table[NB_BANDS*NB_BANDS];
} CommonState;
struct DenoiseState {
float analysis_mem[FRAME_SIZE];
float cepstral_mem[CEPS_MEM][NB_BANDS];
int memid;
float synthesis_mem[FRAME_SIZE];
float pitch_buf[PITCH_BUF_SIZE];
float pitch_enh_buf[PITCH_BUF_SIZE];
float last_gain;
int last_period;
float mem_hp_x[2];
float lastg[NB_BANDS];
RNNState rnn;
};
void compute_band_energy(float *bandE, const kiss_fft_cpx *X) {
int i;
float sum[NB_BANDS] = {0};
for (i=0;i<NB_BANDS-1;i++)
{
int j;
int band_size;
band_size = (eband5ms[i+1]-eband5ms[i])<<FRAME_SIZE_SHIFT;
for (j=0;j<band_size;j++) {
float tmp;
float frac = (float)j/band_size;
tmp = SQUARE(X[(eband5ms[i]<<FRAME_SIZE_SHIFT) + j].r);
tmp += SQUARE(X[(eband5ms[i]<<FRAME_SIZE_SHIFT) + j].i);
sum[i] += (1-frac)*tmp;
sum[i+1] += frac*tmp;
}
}
sum[0] *= 2;
sum[NB_BANDS-1] *= 2;
for (i=0;i<NB_BANDS;i++)
{
bandE[i] = sum[i];
}
}
void compute_band_corr(float *bandE, const kiss_fft_cpx *X, const kiss_fft_cpx *P) {
int i;
float sum[NB_BANDS] = {0};
for (i=0;i<NB_BANDS-1;i++)
{
int j;
int band_size;
band_size = (eband5ms[i+1]-eband5ms[i])<<FRAME_SIZE_SHIFT;
for (j=0;j<band_size;j++) {
float tmp;
float frac = (float)j/band_size;
tmp = X[(eband5ms[i]<<FRAME_SIZE_SHIFT) + j].r * P[(eband5ms[i]<<FRAME_SIZE_SHIFT) + j].r;
tmp += X[(eband5ms[i]<<FRAME_SIZE_SHIFT) + j].i * P[(eband5ms[i]<<FRAME_SIZE_SHIFT) + j].i;
sum[i] += (1-frac)*tmp;
sum[i+1] += frac*tmp;
}
}
sum[0] *= 2;
sum[NB_BANDS-1] *= 2;
for (i=0;i<NB_BANDS;i++)
{
bandE[i] = sum[i];
}
}
void interp_band_gain(float *g, const float *bandE) {
int i;
memset(g, 0, FREQ_SIZE);
for (i=0;i<NB_BANDS-1;i++)
{
int j;
int band_size;
band_size = (eband5ms[i+1]-eband5ms[i])<<FRAME_SIZE_SHIFT;
for (j=0;j<band_size;j++) {
float frac = (float)j/band_size;
g[(eband5ms[i]<<FRAME_SIZE_SHIFT) + j] = (1-frac)*bandE[i] + frac*bandE[i+1];
}
}
}
CommonState common;
static void check_init() {
int i;
if (common.init) return;
common.kfft = opus_fft_alloc_twiddles(2*FRAME_SIZE, NULL, NULL, NULL, 0);
for (i=0;i<FRAME_SIZE;i++)
common.half_window[i] = sin(.5*M_PI*sin(.5*M_PI*(i+.5)/FRAME_SIZE) * sin(.5*M_PI*(i+.5)/FRAME_SIZE));
for (i=0;i<NB_BANDS;i++) {
int j;
for (j=0;j<NB_BANDS;j++) {
common.dct_table[i*NB_BANDS + j] = cos((i+.5)*j*M_PI/NB_BANDS);
if (j==0) common.dct_table[i*NB_BANDS + j] *= sqrt(.5);
}
}
common.init = 1;
}
static void dct(float *out, const float *in) {
int i;
check_init();
for (i=0;i<NB_BANDS;i++) {
int j;
float sum = 0;
for (j=0;j<NB_BANDS;j++) {
sum += in[j] * common.dct_table[j*NB_BANDS + i];
}
out[i] = sum*sqrt(2./22);
}
}
#if 0
static void idct(float *out, const float *in) {
int i;
check_init();
for (i=0;i<NB_BANDS;i++) {
int j;
float sum = 0;
for (j=0;j<NB_BANDS;j++) {
sum += in[j] * common.dct_table[i*NB_BANDS + j];
}
out[i] = sum*sqrt(2./22);
}
}
#endif
static void forward_transform(kiss_fft_cpx *out, const float *in) {
int i;
kiss_fft_cpx x[WINDOW_SIZE];
kiss_fft_cpx y[WINDOW_SIZE];
check_init();
for (i=0;i<WINDOW_SIZE;i++) {
x[i].r = in[i];
x[i].i = 0;
}
opus_fft(common.kfft, x, y, 0);
for (i=0;i<FREQ_SIZE;i++) {
out[i] = y[i];
}
}
static void inverse_transform(float *out, const kiss_fft_cpx *in) {
int i;
kiss_fft_cpx x[WINDOW_SIZE];
kiss_fft_cpx y[WINDOW_SIZE];
check_init();
for (i=0;i<FREQ_SIZE;i++) {
x[i] = in[i];
}
for (;i<WINDOW_SIZE;i++) {
x[i].r = x[WINDOW_SIZE - i].r;
x[i].i = -x[WINDOW_SIZE - i].i;
}
opus_fft(common.kfft, x, y, 0);
/* output in reverse order for IFFT. */
out[0] = WINDOW_SIZE*y[0].r;
for (i=1;i<WINDOW_SIZE;i++) {
out[i] = WINDOW_SIZE*y[WINDOW_SIZE - i].r;
}
}
static void apply_window(float *x) {
int i;
check_init();
for (i=0;i<FRAME_SIZE;i++) {
x[i] *= common.half_window[i];
x[WINDOW_SIZE - 1 - i] *= common.half_window[i];
}
}
int rnnoise_get_size() {
return sizeof(DenoiseState);
}
int rnnoise_init(DenoiseState *st, RNNModel *model) {
memset(st, 0, sizeof(*st));
if (model)
st->rnn.model = model;
else
st->rnn.model = &rnnoise_model_orig;
st->rnn.vad_gru_state = calloc(sizeof(float), st->rnn.model->vad_gru_size);
st->rnn.noise_gru_state = calloc(sizeof(float), st->rnn.model->noise_gru_size);
st->rnn.denoise_gru_state = calloc(sizeof(float), st->rnn.model->denoise_gru_size);
return 0;
}
DenoiseState *rnnoise_create(RNNModel *model) {
DenoiseState *st;
st = malloc(rnnoise_get_size());
rnnoise_init(st, model);
return st;
}
void rnnoise_destroy(DenoiseState *st) {
free(st->rnn.vad_gru_state);
free(st->rnn.noise_gru_state);
free(st->rnn.denoise_gru_state);
free(st);
}
#if TRAINING
int lowpass = FREQ_SIZE;
int band_lp = NB_BANDS;
#endif
static void frame_analysis(DenoiseState *st, kiss_fft_cpx *X, float *Ex, const float *in) {
int i;
float x[WINDOW_SIZE];
RNN_COPY(x, st->analysis_mem, FRAME_SIZE);
for (i=0;i<FRAME_SIZE;i++) x[FRAME_SIZE + i] = in[i];
RNN_COPY(st->analysis_mem, in, FRAME_SIZE);
apply_window(x);
forward_transform(X, x);
#if TRAINING
for (i=lowpass;i<FREQ_SIZE;i++)
X[i].r = X[i].i = 0;
#endif
compute_band_energy(Ex, X);
}
static int compute_frame_features(DenoiseState *st, kiss_fft_cpx *X, kiss_fft_cpx *P,
float *Ex, float *Ep, float *Exp, float *features, const float *in) {
int i;
float E = 0;
float *ceps_0, *ceps_1, *ceps_2;
float spec_variability = 0;
float Ly[NB_BANDS];
float p[WINDOW_SIZE];
float pitch_buf[PITCH_BUF_SIZE>>1];
int pitch_index;
float gain;
float *(pre[1]);
float tmp[NB_BANDS];
float follow, logMax;
frame_analysis(st, X, Ex, in);
RNN_MOVE(st->pitch_buf, &st->pitch_buf[FRAME_SIZE], PITCH_BUF_SIZE-FRAME_SIZE);
RNN_COPY(&st->pitch_buf[PITCH_BUF_SIZE-FRAME_SIZE], in, FRAME_SIZE);
pre[0] = &st->pitch_buf[0];
pitch_downsample(pre, pitch_buf, PITCH_BUF_SIZE, 1);
pitch_search(pitch_buf+(PITCH_MAX_PERIOD>>1), pitch_buf, PITCH_FRAME_SIZE,
PITCH_MAX_PERIOD-3*PITCH_MIN_PERIOD, &pitch_index);
pitch_index = PITCH_MAX_PERIOD-pitch_index;
gain = remove_doubling(pitch_buf, PITCH_MAX_PERIOD, PITCH_MIN_PERIOD,
PITCH_FRAME_SIZE, &pitch_index, st->last_period, st->last_gain);
st->last_period = pitch_index;
st->last_gain = gain;
for (i=0;i<WINDOW_SIZE;i++)
p[i] = st->pitch_buf[PITCH_BUF_SIZE-WINDOW_SIZE-pitch_index+i];
apply_window(p);
forward_transform(P, p);
compute_band_energy(Ep, P);
compute_band_corr(Exp, X, P);
for (i=0;i<NB_BANDS;i++) Exp[i] = Exp[i]/sqrt(.001+Ex[i]*Ep[i]);
dct(tmp, Exp);
for (i=0;i<NB_DELTA_CEPS;i++) features[NB_BANDS+2*NB_DELTA_CEPS+i] = tmp[i];
features[NB_BANDS+2*NB_DELTA_CEPS] -= 1.3;
features[NB_BANDS+2*NB_DELTA_CEPS+1] -= 0.9;
features[NB_BANDS+3*NB_DELTA_CEPS] = .01*(pitch_index-300);
logMax = -2;
follow = -2;
for (i=0;i<NB_BANDS;i++) {
Ly[i] = log10(1e-2+Ex[i]);
Ly[i] = MAX16(logMax-7, MAX16(follow-1.5, Ly[i]));
logMax = MAX16(logMax, Ly[i]);
follow = MAX16(follow-1.5, Ly[i]);
E += Ex[i];
}
if (!TRAINING && E < 0.04) {
/* If there's no audio, avoid messing up the state. */
RNN_CLEAR(features, NB_FEATURES);
return 1;
}
dct(features, Ly);
features[0] -= 12;
features[1] -= 4;
ceps_0 = st->cepstral_mem[st->memid];
ceps_1 = (st->memid < 1) ? st->cepstral_mem[CEPS_MEM+st->memid-1] : st->cepstral_mem[st->memid-1];
ceps_2 = (st->memid < 2) ? st->cepstral_mem[CEPS_MEM+st->memid-2] : st->cepstral_mem[st->memid-2];
for (i=0;i<NB_BANDS;i++) ceps_0[i] = features[i];
st->memid++;
for (i=0;i<NB_DELTA_CEPS;i++) {
features[i] = ceps_0[i] + ceps_1[i] + ceps_2[i];
features[NB_BANDS+i] = ceps_0[i] - ceps_2[i];
features[NB_BANDS+NB_DELTA_CEPS+i] = ceps_0[i] - 2*ceps_1[i] + ceps_2[i];
}
/* Spectral variability features. */
if (st->memid == CEPS_MEM) st->memid = 0;
for (i=0;i<CEPS_MEM;i++)
{
int j;
float mindist = 1e15f;
for (j=0;j<CEPS_MEM;j++)
{
int k;
float dist=0;
for (k=0;k<NB_BANDS;k++)
{
float tmp;
tmp = st->cepstral_mem[i][k] - st->cepstral_mem[j][k];
dist += tmp*tmp;
}
if (j!=i)
mindist = MIN32(mindist, dist);
}
spec_variability += mindist;
}
features[NB_BANDS+3*NB_DELTA_CEPS+1] = spec_variability/CEPS_MEM-2.1;
return TRAINING && E < 0.1;
}
static void frame_synthesis(DenoiseState *st, float *out, const kiss_fft_cpx *y) {
float x[WINDOW_SIZE];
int i;
inverse_transform(x, y);
apply_window(x);
for (i=0;i<FRAME_SIZE;i++) out[i] = x[i] + st->synthesis_mem[i];
RNN_COPY(st->synthesis_mem, &x[FRAME_SIZE], FRAME_SIZE);
}
static void biquad(float *y, float mem[2], const float *x, const float *b, const float *a, int N) {
int i;
for (i=0;i<N;i++) {
float xi, yi;
xi = x[i];
yi = x[i] + mem[0];
mem[0] = mem[1] + (b[0]*(double)xi - a[0]*(double)yi);
mem[1] = (b[1]*(double)xi - a[1]*(double)yi);
y[i] = yi;
}
}
void pitch_filter(kiss_fft_cpx *X, const kiss_fft_cpx *P, const float *Ex, const float *Ep,
const float *Exp, const float *g) {
int i;
float r[NB_BANDS];
float rf[FREQ_SIZE] = {0};
for (i=0;i<NB_BANDS;i++) {
#if 0
if (Exp[i]>g[i]) r[i] = 1;
else r[i] = Exp[i]*(1-g[i])/(.001 + g[i]*(1-Exp[i]));
r[i] = MIN16(1, MAX16(0, r[i]));
#else
if (Exp[i]>g[i]) r[i] = 1;
else r[i] = SQUARE(Exp[i])*(1-SQUARE(g[i]))/(.001 + SQUARE(g[i])*(1-SQUARE(Exp[i])));
r[i] = sqrt(MIN16(1, MAX16(0, r[i])));
#endif
r[i] *= sqrt(Ex[i]/(1e-8+Ep[i]));
}
interp_band_gain(rf, r);
for (i=0;i<FREQ_SIZE;i++) {
X[i].r += rf[i]*P[i].r;
X[i].i += rf[i]*P[i].i;
}
float newE[NB_BANDS];
compute_band_energy(newE, X);
float norm[NB_BANDS];
float normf[FREQ_SIZE]={0};
for (i=0;i<NB_BANDS;i++) {
norm[i] = sqrt(Ex[i]/(1e-8+newE[i]));
}
interp_band_gain(normf, norm);
for (i=0;i<FREQ_SIZE;i++) {
X[i].r *= normf[i];
X[i].i *= normf[i];
}
}
float rnnoise_process_frame(DenoiseState *st, float *out, const float *in) {
int i;
kiss_fft_cpx X[FREQ_SIZE];
kiss_fft_cpx P[WINDOW_SIZE];
float x[FRAME_SIZE];
float Ex[NB_BANDS], Ep[NB_BANDS];
float Exp[NB_BANDS];
float features[NB_FEATURES];
float g[NB_BANDS];
float gf[FREQ_SIZE]={1};
float vad_prob = 0;
int silence;
static const float a_hp[2] = {-1.99599, 0.99600};
static const float b_hp[2] = {-2, 1};
biquad(x, st->mem_hp_x, in, b_hp, a_hp, FRAME_SIZE);
silence = compute_frame_features(st, X, P, Ex, Ep, Exp, features, x);
if (!silence) {
compute_rnn(&st->rnn, g, &vad_prob, features);
pitch_filter(X, P, Ex, Ep, Exp, g);
for (i=0;i<NB_BANDS;i++) {
float alpha = .6f;
g[i] = MAX16(g[i], alpha*st->lastg[i]);
st->lastg[i] = g[i];
}
interp_band_gain(gf, g);
#if 1
for (i=0;i<FREQ_SIZE;i++) {
X[i].r *= gf[i];
X[i].i *= gf[i];
}
#endif
}
frame_synthesis(st, out, X);
return vad_prob;
}
#if TRAINING
static float uni_rand() {
return rand()/(double)RAND_MAX-.5;
}
static void rand_resp(float *a, float *b) {
a[0] = .75*uni_rand();
a[1] = .75*uni_rand();
b[0] = .75*uni_rand();
b[1] = .75*uni_rand();
}
int main(int argc, char **argv) {
int i;
int count=0;
static const float a_hp[2] = {-1.99599, 0.99600};
static const float b_hp[2] = {-2, 1};
float a_noise[2] = {0};
float b_noise[2] = {0};
float a_sig[2] = {0};
float b_sig[2] = {0};
float mem_hp_x[2]={0};
float mem_hp_n[2]={0};
float mem_resp_x[2]={0};
float mem_resp_n[2]={0};
float x[FRAME_SIZE];
float n[FRAME_SIZE];
float xn[FRAME_SIZE];
int vad_cnt=0;
int gain_change_count=0;
float speech_gain = 1, noise_gain = 1;
FILE *f1, *f2;
int maxCount;
DenoiseState *st;
DenoiseState *noise_state;
DenoiseState *noisy;
st = rnnoise_create(NULL);
noise_state = rnnoise_create(NULL);
noisy = rnnoise_create(NULL);
if (argc!=4) {
fprintf(stderr, "usage: %s <speech> <noise> <count>\n", argv[0]);
return 1;
}
f1 = fopen(argv[1], "r");
f2 = fopen(argv[2], "r");
maxCount = atoi(argv[3]);
for(i=0;i<150;i++) {
short tmp[FRAME_SIZE];
fread(tmp, sizeof(short), FRAME_SIZE, f2);
}
while (1) {
kiss_fft_cpx X[FREQ_SIZE], Y[FREQ_SIZE], N[FREQ_SIZE], P[WINDOW_SIZE];
float Ex[NB_BANDS], Ey[NB_BANDS], En[NB_BANDS], Ep[NB_BANDS];
float Exp[NB_BANDS];
float Ln[NB_BANDS];
float features[NB_FEATURES];
float g[NB_BANDS];
short tmp[FRAME_SIZE];
float vad=0;
float E=0;
if (count==maxCount) break;
if ((count%1000)==0) fprintf(stderr, "%d\r", count);
if (++gain_change_count > 2821) {
speech_gain = pow(10., (-40+(rand()%60))/20.);
noise_gain = pow(10., (-30+(rand()%50))/20.);
if (rand()%10==0) noise_gain = 0;
noise_gain *= speech_gain;
if (rand()%10==0) speech_gain = 0;
gain_change_count = 0;
rand_resp(a_noise, b_noise);
rand_resp(a_sig, b_sig);
lowpass = FREQ_SIZE * 3000./24000. * pow(50., rand()/(double)RAND_MAX);
for (i=0;i<NB_BANDS;i++) {
if (eband5ms[i]<<FRAME_SIZE_SHIFT > lowpass) {
band_lp = i;
break;
}
}
}
if (speech_gain != 0) {
fread(tmp, sizeof(short), FRAME_SIZE, f1);
if (feof(f1)) {
rewind(f1);
fread(tmp, sizeof(short), FRAME_SIZE, f1);
}
for (i=0;i<FRAME_SIZE;i++) x[i] = speech_gain*tmp[i];
for (i=0;i<FRAME_SIZE;i++) E += tmp[i]*(float)tmp[i];
} else {
for (i=0;i<FRAME_SIZE;i++) x[i] = 0;
E = 0;
}
if (noise_gain!=0) {
fread(tmp, sizeof(short), FRAME_SIZE, f2);
if (feof(f2)) {
rewind(f2);
fread(tmp, sizeof(short), FRAME_SIZE, f2);
}
for (i=0;i<FRAME_SIZE;i++) n[i] = noise_gain*tmp[i];
} else {
for (i=0;i<FRAME_SIZE;i++) n[i] = 0;
}
biquad(x, mem_hp_x, x, b_hp, a_hp, FRAME_SIZE);
biquad(x, mem_resp_x, x, b_sig, a_sig, FRAME_SIZE);
biquad(n, mem_hp_n, n, b_hp, a_hp, FRAME_SIZE);
biquad(n, mem_resp_n, n, b_noise, a_noise, FRAME_SIZE);
for (i=0;i<FRAME_SIZE;i++) xn[i] = x[i] + n[i];
if (E > 1e9f) {
vad_cnt=0;
} else if (E > 1e8f) {
vad_cnt -= 5;
} else if (E > 1e7f) {
vad_cnt++;
} else {
vad_cnt+=2;
}
if (vad_cnt < 0) vad_cnt = 0;
if (vad_cnt > 15) vad_cnt = 15;
if (vad_cnt >= 10) vad = 0;
else if (vad_cnt > 0) vad = 0.5f;
else vad = 1.f;
frame_analysis(st, Y, Ey, x);
frame_analysis(noise_state, N, En, n);
for (i=0;i<NB_BANDS;i++) Ln[i] = log10(1e-2+En[i]);
int silence = compute_frame_features(noisy, X, P, Ex, Ep, Exp, features, xn);
pitch_filter(X, P, Ex, Ep, Exp, g);
//printf("%f %d\n", noisy->last_gain, noisy->last_period);
for (i=0;i<NB_BANDS;i++) {
g[i] = sqrt((Ey[i]+1e-3)/(Ex[i]+1e-3));
if (g[i] > 1) g[i] = 1;
if (silence || i > band_lp) g[i] = -1;
if (Ey[i] < 5e-2 && Ex[i] < 5e-2) g[i] = -1;
if (vad==0 && noise_gain==0) g[i] = -1;
}
count++;
#if 1
fwrite(features, sizeof(float), NB_FEATURES, stdout);
fwrite(g, sizeof(float), NB_BANDS, stdout);
fwrite(Ln, sizeof(float), NB_BANDS, stdout);
fwrite(&vad, sizeof(float), 1, stdout);
#endif
}
fprintf(stderr, "matrix size: %d x %d\n", count, NB_FEATURES + 2*NB_BANDS + 1);
fclose(f1);
fclose(f2);
return 0;
}
#endif

@ -0,0 +1,601 @@
/*Copyright (c) 2003-2004, Mark Borgerding
Lots of modifications by Jean-Marc Valin
Copyright (c) 2005-2007, Xiph.Org Foundation
Copyright (c) 2008, Xiph.Org Foundation, CSIRO
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.*/
/* This code is originally from Mark Borgerding's KISS-FFT but has been
heavily modified to better suit Opus */
#ifndef SKIP_CONFIG_H
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
#endif
#include "_kiss_fft_guts.h"
#define CUSTOM_MODES
/* The guts header contains all the multiplication and addition macros that are defined for
complex numbers. It also delares the kf_ internal functions.
*/
static void kf_bfly2(
kiss_fft_cpx * Fout,
int m,
int N
)
{
kiss_fft_cpx * Fout2;
int i;
(void)m;
#ifdef CUSTOM_MODES
if (m==1)
{
celt_assert(m==1);
for (i=0;i<N;i++)
{
kiss_fft_cpx t;
Fout2 = Fout + 1;
t = *Fout2;
C_SUB( *Fout2 , *Fout , t );
C_ADDTO( *Fout , t );
Fout += 2;
}
} else
#endif
{
opus_val16 tw;
tw = QCONST16(0.7071067812f, 15);
/* We know that m==4 here because the radix-2 is just after a radix-4 */
celt_assert(m==4);
for (i=0;i<N;i++)
{
kiss_fft_cpx t;
Fout2 = Fout + 4;
t = Fout2[0];
C_SUB( Fout2[0] , Fout[0] , t );
C_ADDTO( Fout[0] , t );
t.r = S_MUL(ADD32_ovflw(Fout2[1].r, Fout2[1].i), tw);
t.i = S_MUL(SUB32_ovflw(Fout2[1].i, Fout2[1].r), tw);
C_SUB( Fout2[1] , Fout[1] , t );
C_ADDTO( Fout[1] , t );
t.r = Fout2[2].i;
t.i = -Fout2[2].r;
C_SUB( Fout2[2] , Fout[2] , t );
C_ADDTO( Fout[2] , t );
t.r = S_MUL(SUB32_ovflw(Fout2[3].i, Fout2[3].r), tw);
t.i = S_MUL(NEG32_ovflw(ADD32_ovflw(Fout2[3].i, Fout2[3].r)), tw);
C_SUB( Fout2[3] , Fout[3] , t );
C_ADDTO( Fout[3] , t );
Fout += 8;
}
}
}
static void kf_bfly4(
kiss_fft_cpx * Fout,
const size_t fstride,
const kiss_fft_state *st,
int m,
int N,
int mm
)
{
int i;
if (m==1)
{
/* Degenerate case where all the twiddles are 1. */
for (i=0;i<N;i++)
{
kiss_fft_cpx scratch0, scratch1;
C_SUB( scratch0 , *Fout, Fout[2] );
C_ADDTO(*Fout, Fout[2]);
C_ADD( scratch1 , Fout[1] , Fout[3] );
C_SUB( Fout[2], *Fout, scratch1 );
C_ADDTO( *Fout , scratch1 );
C_SUB( scratch1 , Fout[1] , Fout[3] );
Fout[1].r = ADD32_ovflw(scratch0.r, scratch1.i);
Fout[1].i = SUB32_ovflw(scratch0.i, scratch1.r);
Fout[3].r = SUB32_ovflw(scratch0.r, scratch1.i);
Fout[3].i = ADD32_ovflw(scratch0.i, scratch1.r);
Fout+=4;
}
} else {
int j;
kiss_fft_cpx scratch[6];
const kiss_twiddle_cpx *tw1,*tw2,*tw3;
const int m2=2*m;
const int m3=3*m;
kiss_fft_cpx * Fout_beg = Fout;
for (i=0;i<N;i++)
{
Fout = Fout_beg + i*mm;
tw3 = tw2 = tw1 = st->twiddles;
/* m is guaranteed to be a multiple of 4. */
for (j=0;j<m;j++)
{
C_MUL(scratch[0],Fout[m] , *tw1 );
C_MUL(scratch[1],Fout[m2] , *tw2 );
C_MUL(scratch[2],Fout[m3] , *tw3 );
C_SUB( scratch[5] , *Fout, scratch[1] );
C_ADDTO(*Fout, scratch[1]);
C_ADD( scratch[3] , scratch[0] , scratch[2] );
C_SUB( scratch[4] , scratch[0] , scratch[2] );
C_SUB( Fout[m2], *Fout, scratch[3] );
tw1 += fstride;
tw2 += fstride*2;
tw3 += fstride*3;
C_ADDTO( *Fout , scratch[3] );
Fout[m].r = ADD32_ovflw(scratch[5].r, scratch[4].i);
Fout[m].i = SUB32_ovflw(scratch[5].i, scratch[4].r);
Fout[m3].r = SUB32_ovflw(scratch[5].r, scratch[4].i);
Fout[m3].i = ADD32_ovflw(scratch[5].i, scratch[4].r);
++Fout;
}
}
}
}
#ifndef RADIX_TWO_ONLY
static void kf_bfly3(
kiss_fft_cpx * Fout,
const size_t fstride,
const kiss_fft_state *st,
int m,
int N,
int mm
)
{
int i;
size_t k;
const size_t m2 = 2*m;
const kiss_twiddle_cpx *tw1,*tw2;
kiss_fft_cpx scratch[5];
kiss_twiddle_cpx epi3;
kiss_fft_cpx * Fout_beg = Fout;
#ifdef FIXED_POINT
/*epi3.r = -16384;*/ /* Unused */
epi3.i = -28378;
#else
epi3 = st->twiddles[fstride*m];
#endif
for (i=0;i<N;i++)
{
Fout = Fout_beg + i*mm;
tw1=tw2=st->twiddles;
/* For non-custom modes, m is guaranteed to be a multiple of 4. */
k=m;
do {
C_MUL(scratch[1],Fout[m] , *tw1);
C_MUL(scratch[2],Fout[m2] , *tw2);
C_ADD(scratch[3],scratch[1],scratch[2]);
C_SUB(scratch[0],scratch[1],scratch[2]);
tw1 += fstride;
tw2 += fstride*2;
Fout[m].r = SUB32_ovflw(Fout->r, HALF_OF(scratch[3].r));
Fout[m].i = SUB32_ovflw(Fout->i, HALF_OF(scratch[3].i));
C_MULBYSCALAR( scratch[0] , epi3.i );
C_ADDTO(*Fout,scratch[3]);
Fout[m2].r = ADD32_ovflw(Fout[m].r, scratch[0].i);
Fout[m2].i = SUB32_ovflw(Fout[m].i, scratch[0].r);
Fout[m].r = SUB32_ovflw(Fout[m].r, scratch[0].i);
Fout[m].i = ADD32_ovflw(Fout[m].i, scratch[0].r);
++Fout;
} while(--k);
}
}
#ifndef OVERRIDE_kf_bfly5
static void kf_bfly5(
kiss_fft_cpx * Fout,
const size_t fstride,
const kiss_fft_state *st,
int m,
int N,
int mm
)
{
kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4;
int i, u;
kiss_fft_cpx scratch[13];
const kiss_twiddle_cpx *tw;
kiss_twiddle_cpx ya,yb;
kiss_fft_cpx * Fout_beg = Fout;
#ifdef FIXED_POINT
ya.r = 10126;
ya.i = -31164;
yb.r = -26510;
yb.i = -19261;
#else
ya = st->twiddles[fstride*m];
yb = st->twiddles[fstride*2*m];
#endif
tw=st->twiddles;
for (i=0;i<N;i++)
{
Fout = Fout_beg + i*mm;
Fout0=Fout;
Fout1=Fout0+m;
Fout2=Fout0+2*m;
Fout3=Fout0+3*m;
Fout4=Fout0+4*m;
/* For non-custom modes, m is guaranteed to be a multiple of 4. */
for ( u=0; u<m; ++u ) {
scratch[0] = *Fout0;
C_MUL(scratch[1] ,*Fout1, tw[u*fstride]);
C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]);
C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]);
C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]);
C_ADD( scratch[7],scratch[1],scratch[4]);
C_SUB( scratch[10],scratch[1],scratch[4]);
C_ADD( scratch[8],scratch[2],scratch[3]);
C_SUB( scratch[9],scratch[2],scratch[3]);
Fout0->r = ADD32_ovflw(Fout0->r, ADD32_ovflw(scratch[7].r, scratch[8].r));
Fout0->i = ADD32_ovflw(Fout0->i, ADD32_ovflw(scratch[7].i, scratch[8].i));
scratch[5].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,ya.r), S_MUL(scratch[8].r,yb.r)));
scratch[5].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,ya.r), S_MUL(scratch[8].i,yb.r)));
scratch[6].r = ADD32_ovflw(S_MUL(scratch[10].i,ya.i), S_MUL(scratch[9].i,yb.i));
scratch[6].i = NEG32_ovflw(ADD32_ovflw(S_MUL(scratch[10].r,ya.i), S_MUL(scratch[9].r,yb.i)));
C_SUB(*Fout1,scratch[5],scratch[6]);
C_ADD(*Fout4,scratch[5],scratch[6]);
scratch[11].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,yb.r), S_MUL(scratch[8].r,ya.r)));
scratch[11].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,yb.r), S_MUL(scratch[8].i,ya.r)));
scratch[12].r = SUB32_ovflw(S_MUL(scratch[9].i,ya.i), S_MUL(scratch[10].i,yb.i));
scratch[12].i = SUB32_ovflw(S_MUL(scratch[10].r,yb.i), S_MUL(scratch[9].r,ya.i));
C_ADD(*Fout2,scratch[11],scratch[12]);
C_SUB(*Fout3,scratch[11],scratch[12]);
++Fout0;++Fout1;++Fout2;++Fout3;++Fout4;
}
}
}
#endif /* OVERRIDE_kf_bfly5 */
#endif
#ifdef CUSTOM_MODES
static
void compute_bitrev_table(
int Fout,
opus_int16 *f,
const size_t fstride,
int in_stride,
opus_int16 * factors,
const kiss_fft_state *st
)
{
const int p=*factors++; /* the radix */
const int m=*factors++; /* stage's fft length/p */
/*printf ("fft %d %d %d %d %d %d\n", p*m, m, p, s2, fstride*in_stride, N);*/
if (m==1)
{
int j;
for (j=0;j<p;j++)
{
*f = Fout+j;
f += fstride*in_stride;
}
} else {
int j;
for (j=0;j<p;j++)
{
compute_bitrev_table( Fout , f, fstride*p, in_stride, factors,st);
f += fstride*in_stride;
Fout += m;
}
}
}
/* facbuf is populated by p1,m1,p2,m2, ...
where
p[i] * m[i] = m[i-1]
m0 = n */
static
int kf_factor(int n,opus_int16 * facbuf)
{
int p=4;
int i;
int stages=0;
int nbak = n;
/*factor out powers of 4, powers of 2, then any remaining primes */
do {
while (n % p) {
switch (p) {
case 4: p = 2; break;
case 2: p = 3; break;
default: p += 2; break;
}
if (p>32000 || (opus_int32)p*(opus_int32)p > n)
p = n; /* no more factors, skip to end */
}
n /= p;
#ifdef RADIX_TWO_ONLY
if (p!=2 && p != 4)
#else
if (p>5)
#endif
{
return 0;
}
facbuf[2*stages] = p;
if (p==2 && stages > 1)
{
facbuf[2*stages] = 4;
facbuf[2] = 2;
}
stages++;
} while (n > 1);
n = nbak;
/* Reverse the order to get the radix 4 at the end, so we can use the
fast degenerate case. It turns out that reversing the order also
improves the noise behaviour. */
for (i=0;i<stages/2;i++)
{
int tmp;
tmp = facbuf[2*i];
facbuf[2*i] = facbuf[2*(stages-i-1)];
facbuf[2*(stages-i-1)] = tmp;
}
for (i=0;i<stages;i++)
{
n /= facbuf[2*i];
facbuf[2*i+1] = n;
}
return 1;
}
static void compute_twiddles(kiss_twiddle_cpx *twiddles, int nfft)
{
int i;
#ifdef FIXED_POINT
for (i=0;i<nfft;++i) {
opus_val32 phase = -i;
kf_cexp2(twiddles+i, DIV32(SHL32(phase,17),nfft));
}
#else
for (i=0;i<nfft;++i) {
const double pi=3.14159265358979323846264338327;
double phase = ( -2*pi /nfft ) * i;
kf_cexp(twiddles+i, phase );
}
#endif
}
int opus_fft_alloc_arch_c(kiss_fft_state *st) {
(void)st;
return 0;
}
/*
*
* Allocates all necessary storage space for the fft and ifft.
* The return value is a contiguous block of memory. As such,
* It can be freed with free().
* */
kiss_fft_state *opus_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem,
const kiss_fft_state *base, int arch)
{
kiss_fft_state *st=NULL;
size_t memneeded = sizeof(struct kiss_fft_state); /* twiddle factors*/
if ( lenmem==NULL ) {
st = ( kiss_fft_state*)KISS_FFT_MALLOC( memneeded );
}else{
if (mem != NULL && *lenmem >= memneeded)
st = (kiss_fft_state*)mem;
*lenmem = memneeded;
}
if (st) {
opus_int16 *bitrev;
kiss_twiddle_cpx *twiddles;
st->nfft=nfft;
#ifdef FIXED_POINT
st->scale_shift = celt_ilog2(st->nfft);
if (st->nfft == 1<<st->scale_shift)
st->scale = Q15ONE;
else
st->scale = (1073741824+st->nfft/2)/st->nfft>>(15-st->scale_shift);
#else
st->scale = 1.f/nfft;
#endif
if (base != NULL)
{
st->twiddles = base->twiddles;
st->shift = 0;
while (st->shift < 32 && nfft<<st->shift != base->nfft)
st->shift++;
if (st->shift>=32)
goto fail;
} else {
st->twiddles = twiddles = (kiss_twiddle_cpx*)KISS_FFT_MALLOC(sizeof(kiss_twiddle_cpx)*nfft);
compute_twiddles(twiddles, nfft);
st->shift = -1;
}
if (!kf_factor(nfft,st->factors))
{
goto fail;
}
/* bitrev */
st->bitrev = bitrev = (opus_int16*)KISS_FFT_MALLOC(sizeof(opus_int16)*nfft);
if (st->bitrev==NULL)
goto fail;
compute_bitrev_table(0, bitrev, 1,1, st->factors,st);
/* Initialize architecture specific fft parameters */
if (opus_fft_alloc_arch(st, arch))
goto fail;
}
return st;
fail:
opus_fft_free(st, arch);
return NULL;
}
kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch)
{
return opus_fft_alloc_twiddles(nfft, mem, lenmem, NULL, arch);
}
void opus_fft_free_arch_c(kiss_fft_state *st) {
(void)st;
}
void opus_fft_free(const kiss_fft_state *cfg, int arch)
{
if (cfg)
{
opus_fft_free_arch((kiss_fft_state *)cfg, arch);
opus_free((opus_int16*)cfg->bitrev);
if (cfg->shift < 0)
opus_free((kiss_twiddle_cpx*)cfg->twiddles);
opus_free((kiss_fft_state*)cfg);
}
}
#endif /* CUSTOM_MODES */
void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout)
{
int m2, m;
int p;
int L;
int fstride[MAXFACTORS];
int i;
int shift;
/* st->shift can be -1 */
shift = st->shift>0 ? st->shift : 0;
fstride[0] = 1;
L=0;
do {
p = st->factors[2*L];
m = st->factors[2*L+1];
fstride[L+1] = fstride[L]*p;
L++;
} while(m!=1);
m = st->factors[2*L-1];
for (i=L-1;i>=0;i--)
{
if (i!=0)
m2 = st->factors[2*i-1];
else
m2 = 1;
switch (st->factors[2*i])
{
case 2:
kf_bfly2(fout, m, fstride[i]);
break;
case 4:
kf_bfly4(fout,fstride[i]<<shift,st,m, fstride[i], m2);
break;
#ifndef RADIX_TWO_ONLY
case 3:
kf_bfly3(fout,fstride[i]<<shift,st,m, fstride[i], m2);
break;
case 5:
kf_bfly5(fout,fstride[i]<<shift,st,m, fstride[i], m2);
break;
#endif
}
m = m2;
}
}
void opus_fft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout)
{
int i;
opus_val16 scale;
#ifdef FIXED_POINT
/* Allows us to scale with MULT16_32_Q16(), which is faster than
MULT16_32_Q15() on ARM. */
int scale_shift = st->scale_shift-1;
#endif
scale = st->scale;
celt_assert2 (fin != fout, "In-place FFT not supported");
/* Bit-reverse the input */
for (i=0;i<st->nfft;i++)
{
kiss_fft_cpx x = fin[i];
fout[st->bitrev[i]].r = SHR32(MULT16_32_Q16(scale, x.r), scale_shift);
fout[st->bitrev[i]].i = SHR32(MULT16_32_Q16(scale, x.i), scale_shift);
}
opus_fft_impl(st, fout);
}
void opus_ifft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout)
{
int i;
celt_assert2 (fin != fout, "In-place FFT not supported");
/* Bit-reverse the input */
for (i=0;i<st->nfft;i++)
fout[st->bitrev[i]] = fin[i];
for (i=0;i<st->nfft;i++)
fout[i].i = -fout[i].i;
opus_fft_impl(st, fout);
for (i=0;i<st->nfft;i++)
fout[i].i = -fout[i].i;
}

@ -0,0 +1,203 @@
/*Copyright (c) 2003-2004, Mark Borgerding
Lots of modifications by Jean-Marc Valin
Copyright (c) 2005-2007, Xiph.Org Foundation
Copyright (c) 2008, Xiph.Org Foundation, CSIRO
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.*/
#ifndef KISS_FFT_H
#define KISS_FFT_H
#include <stdlib.h>
#include <math.h>
#include "arch.h"
#include <stdlib.h>
#define opus_alloc(x) malloc(x)
#define opus_free(x) free(x)
#ifdef __cplusplus
extern "C" {
#endif
#ifdef USE_SIMD
# include <xmmintrin.h>
# define kiss_fft_scalar __m128
#define KISS_FFT_MALLOC(nbytes) memalign(16,nbytes)
#else
#define KISS_FFT_MALLOC opus_alloc
#endif
#ifdef FIXED_POINT
#include "arch.h"
# define kiss_fft_scalar opus_int32
# define kiss_twiddle_scalar opus_int16
#else
# ifndef kiss_fft_scalar
/* default is float */
# define kiss_fft_scalar float
# define kiss_twiddle_scalar float
# define KF_SUFFIX _celt_single
# endif
#endif
typedef struct {
kiss_fft_scalar r;
kiss_fft_scalar i;
}kiss_fft_cpx;
typedef struct {
kiss_twiddle_scalar r;
kiss_twiddle_scalar i;
}kiss_twiddle_cpx;
#define MAXFACTORS 8
/* e.g. an fft of length 128 has 4 factors
as far as kissfft is concerned
4*4*4*2
*/
typedef struct arch_fft_state{
int is_supported;
void *priv;
} arch_fft_state;
typedef struct kiss_fft_state{
int nfft;
opus_val16 scale;
#ifdef FIXED_POINT
int scale_shift;
#endif
int shift;
opus_int16 factors[2*MAXFACTORS];
const opus_int16 *bitrev;
const kiss_twiddle_cpx *twiddles;
arch_fft_state *arch_fft;
} kiss_fft_state;
#if defined(HAVE_ARM_NE10)
#include "arm/fft_arm.h"
#endif
/*typedef struct kiss_fft_state* kiss_fft_cfg;*/
/**
* opus_fft_alloc
*
* Initialize a FFT (or IFFT) algorithm's cfg/state buffer.
*
* typical usage: kiss_fft_cfg mycfg=opus_fft_alloc(1024,0,NULL,NULL);
*
* The return value from fft_alloc is a cfg buffer used internally
* by the fft routine or NULL.
*
* If lenmem is NULL, then opus_fft_alloc will allocate a cfg buffer using malloc.
* The returned value should be free()d when done to avoid memory leaks.
*
* The state can be placed in a user supplied buffer 'mem':
* If lenmem is not NULL and mem is not NULL and *lenmem is large enough,
* then the function places the cfg in mem and the size used in *lenmem
* and returns mem.
*
* If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough),
* then the function returns NULL and places the minimum cfg
* buffer size in *lenmem.
* */
kiss_fft_state *opus_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem, const kiss_fft_state *base, int arch);
kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch);
/**
* opus_fft(cfg,in_out_buf)
*
* Perform an FFT on a complex input buffer.
* for a forward FFT,
* fin should be f[0] , f[1] , ... ,f[nfft-1]
* fout will be F[0] , F[1] , ... ,F[nfft-1]
* Note that each element is complex and can be accessed like
f[k].r and f[k].i
* */
void opus_fft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
void opus_ifft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout);
void opus_ifft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout);
void opus_fft_free(const kiss_fft_state *cfg, int arch);
void opus_fft_free_arch_c(kiss_fft_state *st);
int opus_fft_alloc_arch_c(kiss_fft_state *st);
#if !defined(OVERRIDE_OPUS_FFT)
/* Is run-time CPU detection enabled on this platform? */
#if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10))
extern int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])(
kiss_fft_state *st);
#define opus_fft_alloc_arch(_st, arch) \
((*OPUS_FFT_ALLOC_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st))
extern void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])(
kiss_fft_state *st);
#define opus_fft_free_arch(_st, arch) \
((*OPUS_FFT_FREE_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st))
extern void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
const kiss_fft_cpx *fin, kiss_fft_cpx *fout);
#define opus_fft(_cfg, _fin, _fout, arch) \
((*OPUS_FFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout))
extern void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
const kiss_fft_cpx *fin, kiss_fft_cpx *fout);
#define opus_ifft(_cfg, _fin, _fout, arch) \
((*OPUS_IFFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout))
#else /* else for if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */
#define opus_fft_alloc_arch(_st, arch) \
((void)(arch), opus_fft_alloc_arch_c(_st))
#define opus_fft_free_arch(_st, arch) \
((void)(arch), opus_fft_free_arch_c(_st))
#define opus_fft(_cfg, _fin, _fout, arch) \
((void)(arch), opus_fft_c(_cfg, _fin, _fout))
#define opus_ifft(_cfg, _fin, _fout, arch) \
((void)(arch), opus_ifft_c(_cfg, _fin, _fout))
#endif /* end if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */
#endif /* end if !defined(OVERRIDE_OPUS_FFT) */
#ifdef __cplusplus
}
#endif
#endif

@ -0,0 +1,159 @@
/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */
/* Modified by Jean-Marc Valin */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* opus_types.h based on ogg_types.h from libogg */
/**
@file opus_types.h
@brief Opus reference implementation types
*/
#ifndef OPUS_TYPES_H
#define OPUS_TYPES_H
/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */
#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H))
#include <stdint.h>
typedef int16_t opus_int16;
typedef uint16_t opus_uint16;
typedef int32_t opus_int32;
typedef uint32_t opus_uint32;
#elif defined(_WIN32)
# if defined(__CYGWIN__)
# include <_G_config.h>
typedef _G_int32_t opus_int32;
typedef _G_uint32_t opus_uint32;
typedef _G_int16 opus_int16;
typedef _G_uint16 opus_uint16;
# elif defined(__MINGW32__)
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
# elif defined(__MWERKS__)
typedef int opus_int32;
typedef unsigned int opus_uint32;
typedef short opus_int16;
typedef unsigned short opus_uint16;
# else
/* MSVC/Borland */
typedef __int32 opus_int32;
typedef unsigned __int32 opus_uint32;
typedef __int16 opus_int16;
typedef unsigned __int16 opus_uint16;
# endif
#elif defined(__MACOS__)
# include <sys/types.h>
typedef SInt16 opus_int16;
typedef UInt16 opus_uint16;
typedef SInt32 opus_int32;
typedef UInt32 opus_uint32;
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
# include <sys/types.h>
typedef int16_t opus_int16;
typedef u_int16_t opus_uint16;
typedef int32_t opus_int32;
typedef u_int32_t opus_uint32;
#elif defined(__BEOS__)
/* Be */
# include <inttypes.h>
typedef int16 opus_int16;
typedef u_int16 opus_uint16;
typedef int32_t opus_int32;
typedef u_int32_t opus_uint32;
#elif defined (__EMX__)
/* OS/2 GCC */
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#elif defined (DJGPP)
/* DJGPP */
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#elif defined(R5900)
/* PS2 EE */
typedef int opus_int32;
typedef unsigned opus_uint32;
typedef short opus_int16;
typedef unsigned short opus_uint16;
#elif defined(__SYMBIAN32__)
/* Symbian GCC */
typedef signed short opus_int16;
typedef unsigned short opus_uint16;
typedef signed int opus_int32;
typedef unsigned int opus_uint32;
#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef long opus_int32;
typedef unsigned long opus_uint32;
#elif defined(CONFIG_TI_C6X)
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#else
/* Give up, take a reasonable guess */
typedef short opus_int16;
typedef unsigned short opus_uint16;
typedef int opus_int32;
typedef unsigned int opus_uint32;
#endif
#define opus_int int /* used for counters etc; at least 16 bits */
#define opus_int64 long long
#define opus_int8 signed char
#define opus_uint unsigned int /* used for counters etc; at least 16 bits */
#define opus_uint64 unsigned long long
#define opus_uint8 unsigned char
#endif /* OPUS_TYPES_H */

@ -0,0 +1,526 @@
/* Copyright (c) 2007-2008 CSIRO
Copyright (c) 2007-2009 Xiph.Org Foundation
Written by Jean-Marc Valin */
/**
@file pitch.c
@brief Pitch analysis
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "pitch.h"
#include "common.h"
//#include "modes.h"
//#include "stack_alloc.h"
//#include "mathops.h"
#include "celt_lpc.h"
#include "math.h"
static void find_best_pitch(opus_val32 *xcorr, opus_val16 *y, int len,
int max_pitch, int *best_pitch
#ifdef FIXED_POINT
, int yshift, opus_val32 maxcorr
#endif
)
{
int i, j;
opus_val32 Syy=1;
opus_val16 best_num[2];
opus_val32 best_den[2];
#ifdef FIXED_POINT
int xshift;
xshift = celt_ilog2(maxcorr)-14;
#endif
best_num[0] = -1;
best_num[1] = -1;
best_den[0] = 0;
best_den[1] = 0;
best_pitch[0] = 0;
best_pitch[1] = 1;
for (j=0;j<len;j++)
Syy = ADD32(Syy, SHR32(MULT16_16(y[j],y[j]), yshift));
for (i=0;i<max_pitch;i++)
{
if (xcorr[i]>0)
{
opus_val16 num;
opus_val32 xcorr16;
xcorr16 = EXTRACT16(VSHR32(xcorr[i], xshift));
#ifndef FIXED_POINT
/* Considering the range of xcorr16, this should avoid both underflows
and overflows (inf) when squaring xcorr16 */
xcorr16 *= 1e-12f;
#endif
num = MULT16_16_Q15(xcorr16,xcorr16);
if (MULT16_32_Q15(num,best_den[1]) > MULT16_32_Q15(best_num[1],Syy))
{
if (MULT16_32_Q15(num,best_den[0]) > MULT16_32_Q15(best_num[0],Syy))
{
best_num[1] = best_num[0];
best_den[1] = best_den[0];
best_pitch[1] = best_pitch[0];
best_num[0] = num;
best_den[0] = Syy;
best_pitch[0] = i;
} else {
best_num[1] = num;
best_den[1] = Syy;
best_pitch[1] = i;
}
}
}
Syy += SHR32(MULT16_16(y[i+len],y[i+len]),yshift) - SHR32(MULT16_16(y[i],y[i]),yshift);
Syy = MAX32(1, Syy);
}
}
static void celt_fir5(const opus_val16 *x,
const opus_val16 *num,
opus_val16 *y,
int N,
opus_val16 *mem)
{
int i;
opus_val16 num0, num1, num2, num3, num4;
opus_val32 mem0, mem1, mem2, mem3, mem4;
num0=num[0];
num1=num[1];
num2=num[2];
num3=num[3];
num4=num[4];
mem0=mem[0];
mem1=mem[1];
mem2=mem[2];
mem3=mem[3];
mem4=mem[4];
for (i=0;i<N;i++)
{
opus_val32 sum = SHL32(EXTEND32(x[i]), SIG_SHIFT);
sum = MAC16_16(sum,num0,mem0);
sum = MAC16_16(sum,num1,mem1);
sum = MAC16_16(sum,num2,mem2);
sum = MAC16_16(sum,num3,mem3);
sum = MAC16_16(sum,num4,mem4);
mem4 = mem3;
mem3 = mem2;
mem2 = mem1;
mem1 = mem0;
mem0 = x[i];
y[i] = ROUND16(sum, SIG_SHIFT);
}
mem[0]=mem0;
mem[1]=mem1;
mem[2]=mem2;
mem[3]=mem3;
mem[4]=mem4;
}
void pitch_downsample(celt_sig *x[], opus_val16 *x_lp,
int len, int C)
{
int i;
opus_val32 ac[5];
opus_val16 tmp=Q15ONE;
opus_val16 lpc[4], mem[5]={0,0,0,0,0};
opus_val16 lpc2[5];
opus_val16 c1 = QCONST16(.8f,15);
#ifdef FIXED_POINT
int shift;
opus_val32 maxabs = celt_maxabs32(x[0], len);
if (C==2)
{
opus_val32 maxabs_1 = celt_maxabs32(x[1], len);
maxabs = MAX32(maxabs, maxabs_1);
}
if (maxabs<1)
maxabs=1;
shift = celt_ilog2(maxabs)-10;
if (shift<0)
shift=0;
if (C==2)
shift++;
#endif
for (i=1;i<len>>1;i++)
x_lp[i] = SHR32(HALF32(HALF32(x[0][(2*i-1)]+x[0][(2*i+1)])+x[0][2*i]), shift);
x_lp[0] = SHR32(HALF32(HALF32(x[0][1])+x[0][0]), shift);
if (C==2)
{
for (i=1;i<len>>1;i++)
x_lp[i] += SHR32(HALF32(HALF32(x[1][(2*i-1)]+x[1][(2*i+1)])+x[1][2*i]), shift);
x_lp[0] += SHR32(HALF32(HALF32(x[1][1])+x[1][0]), shift);
}
_celt_autocorr(x_lp, ac, NULL, 0,
4, len>>1);
/* Noise floor -40 dB */
#ifdef FIXED_POINT
ac[0] += SHR32(ac[0],13);
#else
ac[0] *= 1.0001f;
#endif
/* Lag windowing */
for (i=1;i<=4;i++)
{
/*ac[i] *= exp(-.5*(2*M_PI*.002*i)*(2*M_PI*.002*i));*/
#ifdef FIXED_POINT
ac[i] -= MULT16_32_Q15(2*i*i, ac[i]);
#else
ac[i] -= ac[i]*(.008f*i)*(.008f*i);
#endif
}
_celt_lpc(lpc, ac, 4);
for (i=0;i<4;i++)
{
tmp = MULT16_16_Q15(QCONST16(.9f,15), tmp);
lpc[i] = MULT16_16_Q15(lpc[i], tmp);
}
/* Add a zero */
lpc2[0] = lpc[0] + QCONST16(.8f,SIG_SHIFT);
lpc2[1] = lpc[1] + MULT16_16_Q15(c1,lpc[0]);
lpc2[2] = lpc[2] + MULT16_16_Q15(c1,lpc[1]);
lpc2[3] = lpc[3] + MULT16_16_Q15(c1,lpc[2]);
lpc2[4] = MULT16_16_Q15(c1,lpc[3]);
celt_fir5(x_lp, lpc2, x_lp, len>>1, mem);
}
void celt_pitch_xcorr(const opus_val16 *_x, const opus_val16 *_y,
opus_val32 *xcorr, int len, int max_pitch)
{
#if 0 /* This is a simple version of the pitch correlation that should work
well on DSPs like Blackfin and TI C5x/C6x */
int i, j;
#ifdef FIXED_POINT
opus_val32 maxcorr=1;
#endif
for (i=0;i<max_pitch;i++)
{
opus_val32 sum = 0;
for (j=0;j<len;j++)
sum = MAC16_16(sum, _x[j], _y[i+j]);
xcorr[i] = sum;
#ifdef FIXED_POINT
maxcorr = MAX32(maxcorr, sum);
#endif
}
#ifdef FIXED_POINT
return maxcorr;
#endif
#else /* Unrolled version of the pitch correlation -- runs faster on x86 and ARM */
int i;
/*The EDSP version requires that max_pitch is at least 1, and that _x is
32-bit aligned.
Since it's hard to put asserts in assembly, put them here.*/
#ifdef FIXED_POINT
opus_val32 maxcorr=1;
#endif
celt_assert(max_pitch>0);
celt_assert((((unsigned char *)_x-(unsigned char *)NULL)&3)==0);
for (i=0;i<max_pitch-3;i+=4)
{
opus_val32 sum[4]={0,0,0,0};
xcorr_kernel(_x, _y+i, sum, len);
xcorr[i]=sum[0];
xcorr[i+1]=sum[1];
xcorr[i+2]=sum[2];
xcorr[i+3]=sum[3];
#ifdef FIXED_POINT
sum[0] = MAX32(sum[0], sum[1]);
sum[2] = MAX32(sum[2], sum[3]);
sum[0] = MAX32(sum[0], sum[2]);
maxcorr = MAX32(maxcorr, sum[0]);
#endif
}
/* In case max_pitch isn't a multiple of 4, do non-unrolled version. */
for (;i<max_pitch;i++)
{
opus_val32 sum;
sum = celt_inner_prod(_x, _y+i, len);
xcorr[i] = sum;
#ifdef FIXED_POINT
maxcorr = MAX32(maxcorr, sum);
#endif
}
#ifdef FIXED_POINT
return maxcorr;
#endif
#endif
}
void pitch_search(const opus_val16 *x_lp, opus_val16 *y,
int len, int max_pitch, int *pitch)
{
int i, j;
int lag;
int best_pitch[2]={0,0};
#ifdef FIXED_POINT
opus_val32 maxcorr;
opus_val32 xmax, ymax;
int shift=0;
#endif
int offset;
celt_assert(len>0);
celt_assert(max_pitch>0);
lag = len+max_pitch;
opus_val16 x_lp4[len>>2];
opus_val16 y_lp4[lag>>2];
opus_val32 xcorr[max_pitch>>1];
/* Downsample by 2 again */
for (j=0;j<len>>2;j++)
x_lp4[j] = x_lp[2*j];
for (j=0;j<lag>>2;j++)
y_lp4[j] = y[2*j];
#ifdef FIXED_POINT
xmax = celt_maxabs16(x_lp4, len>>2);
ymax = celt_maxabs16(y_lp4, lag>>2);
shift = celt_ilog2(MAX32(1, MAX32(xmax, ymax)))-11;
if (shift>0)
{
for (j=0;j<len>>2;j++)
x_lp4[j] = SHR16(x_lp4[j], shift);
for (j=0;j<lag>>2;j++)
y_lp4[j] = SHR16(y_lp4[j], shift);
/* Use double the shift for a MAC */
shift *= 2;
} else {
shift = 0;
}
#endif
/* Coarse search with 4x decimation */
#ifdef FIXED_POINT
maxcorr =
#endif
celt_pitch_xcorr(x_lp4, y_lp4, xcorr, len>>2, max_pitch>>2);
find_best_pitch(xcorr, y_lp4, len>>2, max_pitch>>2, best_pitch
#ifdef FIXED_POINT
, 0, maxcorr
#endif
);
/* Finer search with 2x decimation */
#ifdef FIXED_POINT
maxcorr=1;
#endif
for (i=0;i<max_pitch>>1;i++)
{
opus_val32 sum;
xcorr[i] = 0;
if (abs(i-2*best_pitch[0])>2 && abs(i-2*best_pitch[1])>2)
continue;
#ifdef FIXED_POINT
sum = 0;
for (j=0;j<len>>1;j++)
sum += SHR32(MULT16_16(x_lp[j],y[i+j]), shift);
#else
sum = celt_inner_prod(x_lp, y+i, len>>1);
#endif
xcorr[i] = MAX32(-1, sum);
#ifdef FIXED_POINT
maxcorr = MAX32(maxcorr, sum);
#endif
}
find_best_pitch(xcorr, y, len>>1, max_pitch>>1, best_pitch
#ifdef FIXED_POINT
, shift+1, maxcorr
#endif
);
/* Refine by pseudo-interpolation */
if (best_pitch[0]>0 && best_pitch[0]<(max_pitch>>1)-1)
{
opus_val32 a, b, c;
a = xcorr[best_pitch[0]-1];
b = xcorr[best_pitch[0]];
c = xcorr[best_pitch[0]+1];
if ((c-a) > MULT16_32_Q15(QCONST16(.7f,15),b-a))
offset = 1;
else if ((a-c) > MULT16_32_Q15(QCONST16(.7f,15),b-c))
offset = -1;
else
offset = 0;
} else {
offset = 0;
}
*pitch = 2*best_pitch[0]-offset;
}
#ifdef FIXED_POINT
static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy)
{
opus_val32 x2y2;
int sx, sy, shift;
opus_val32 g;
opus_val16 den;
if (xy == 0 || xx == 0 || yy == 0)
return 0;
sx = celt_ilog2(xx)-14;
sy = celt_ilog2(yy)-14;
shift = sx + sy;
x2y2 = SHR32(MULT16_16(VSHR32(xx, sx), VSHR32(yy, sy)), 14);
if (shift & 1) {
if (x2y2 < 32768)
{
x2y2 <<= 1;
shift--;
} else {
x2y2 >>= 1;
shift++;
}
}
den = celt_rsqrt_norm(x2y2);
g = MULT16_32_Q15(den, xy);
g = VSHR32(g, (shift>>1)-1);
return EXTRACT16(MIN32(g, Q15ONE));
}
#else
static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy)
{
return xy/sqrt(1+xx*yy);
}
#endif
static const int second_check[16] = {0, 0, 3, 2, 3, 2, 5, 2, 3, 2, 3, 2, 5, 2, 3, 2};
opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod,
int N, int *T0_, int prev_period, opus_val16 prev_gain)
{
int k, i, T, T0;
opus_val16 g, g0;
opus_val16 pg;
opus_val32 xy,xx,yy,xy2;
opus_val32 xcorr[3];
opus_val32 best_xy, best_yy;
int offset;
int minperiod0;
minperiod0 = minperiod;
maxperiod /= 2;
minperiod /= 2;
*T0_ /= 2;
prev_period /= 2;
N /= 2;
x += maxperiod;
if (*T0_>=maxperiod)
*T0_=maxperiod-1;
T = T0 = *T0_;
opus_val32 yy_lookup[maxperiod+1];
dual_inner_prod(x, x, x-T0, N, &xx, &xy);
yy_lookup[0] = xx;
yy=xx;
for (i=1;i<=maxperiod;i++)
{
yy = yy+MULT16_16(x[-i],x[-i])-MULT16_16(x[N-i],x[N-i]);
yy_lookup[i] = MAX32(0, yy);
}
yy = yy_lookup[T0];
best_xy = xy;
best_yy = yy;
g = g0 = compute_pitch_gain(xy, xx, yy);
/* Look for any pitch at T/k */
for (k=2;k<=15;k++)
{
int T1, T1b;
opus_val16 g1;
opus_val16 cont=0;
opus_val16 thresh;
T1 = (2*T0+k)/(2*k);
if (T1 < minperiod)
break;
/* Look for another strong correlation at T1b */
if (k==2)
{
if (T1+T0>maxperiod)
T1b = T0;
else
T1b = T0+T1;
} else
{
T1b = (2*second_check[k]*T0+k)/(2*k);
}
dual_inner_prod(x, &x[-T1], &x[-T1b], N, &xy, &xy2);
xy = HALF32(xy + xy2);
yy = HALF32(yy_lookup[T1] + yy_lookup[T1b]);
g1 = compute_pitch_gain(xy, xx, yy);
if (abs(T1-prev_period)<=1)
cont = prev_gain;
else if (abs(T1-prev_period)<=2 && 5*k*k < T0)
cont = HALF16(prev_gain);
else
cont = 0;
thresh = MAX16(QCONST16(.3f,15), MULT16_16_Q15(QCONST16(.7f,15),g0)-cont);
/* Bias against very high pitch (very short period) to avoid false-positives
due to short-term correlation */
if (T1<3*minperiod)
thresh = MAX16(QCONST16(.4f,15), MULT16_16_Q15(QCONST16(.85f,15),g0)-cont);
else if (T1<2*minperiod)
thresh = MAX16(QCONST16(.5f,15), MULT16_16_Q15(QCONST16(.9f,15),g0)-cont);
if (g1 > thresh)
{
best_xy = xy;
best_yy = yy;
T = T1;
g = g1;
}
}
best_xy = MAX32(0, best_xy);
if (best_yy <= best_xy)
pg = Q15ONE;
else
pg = best_xy/(best_yy+1);
for (k=0;k<3;k++)
xcorr[k] = celt_inner_prod(x, x-(T+k-1), N);
if ((xcorr[2]-xcorr[0]) > MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[0]))
offset = 1;
else if ((xcorr[0]-xcorr[2]) > MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[2]))
offset = -1;
else
offset = 0;
if (pg > g)
pg = g;
*T0_ = 2*T+offset;
if (*T0_<minperiod0)
*T0_=minperiod0;
return pg;
}

@ -0,0 +1,149 @@
/* Copyright (c) 2007-2008 CSIRO
Copyright (c) 2007-2009 Xiph.Org Foundation
Written by Jean-Marc Valin */
/**
@file pitch.h
@brief Pitch analysis
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef PITCH_H
#define PITCH_H
//#include "modes.h"
//#include "cpu_support.h"
#include "arch.h"
void pitch_downsample(celt_sig *x[], opus_val16 *x_lp,
int len, int C);
void pitch_search(const opus_val16 *x_lp, opus_val16 *y,
int len, int max_pitch, int *pitch);
opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod,
int N, int *T0, int prev_period, opus_val16 prev_gain);
/* OPT: This is the kernel you really want to optimize. It gets used a lot
by the prefilter and by the PLC. */
static OPUS_INLINE void xcorr_kernel(const opus_val16 * x, const opus_val16 * y, opus_val32 sum[4], int len)
{
int j;
opus_val16 y_0, y_1, y_2, y_3;
celt_assert(len>=3);
y_3=0; /* gcc doesn't realize that y_3 can't be used uninitialized */
y_0=*y++;
y_1=*y++;
y_2=*y++;
for (j=0;j<len-3;j+=4)
{
opus_val16 tmp;
tmp = *x++;
y_3=*y++;
sum[0] = MAC16_16(sum[0],tmp,y_0);
sum[1] = MAC16_16(sum[1],tmp,y_1);
sum[2] = MAC16_16(sum[2],tmp,y_2);
sum[3] = MAC16_16(sum[3],tmp,y_3);
tmp=*x++;
y_0=*y++;
sum[0] = MAC16_16(sum[0],tmp,y_1);
sum[1] = MAC16_16(sum[1],tmp,y_2);
sum[2] = MAC16_16(sum[2],tmp,y_3);
sum[3] = MAC16_16(sum[3],tmp,y_0);
tmp=*x++;
y_1=*y++;
sum[0] = MAC16_16(sum[0],tmp,y_2);
sum[1] = MAC16_16(sum[1],tmp,y_3);
sum[2] = MAC16_16(sum[2],tmp,y_0);
sum[3] = MAC16_16(sum[3],tmp,y_1);
tmp=*x++;
y_2=*y++;
sum[0] = MAC16_16(sum[0],tmp,y_3);
sum[1] = MAC16_16(sum[1],tmp,y_0);
sum[2] = MAC16_16(sum[2],tmp,y_1);
sum[3] = MAC16_16(sum[3],tmp,y_2);
}
if (j++<len)
{
opus_val16 tmp = *x++;
y_3=*y++;
sum[0] = MAC16_16(sum[0],tmp,y_0);
sum[1] = MAC16_16(sum[1],tmp,y_1);
sum[2] = MAC16_16(sum[2],tmp,y_2);
sum[3] = MAC16_16(sum[3],tmp,y_3);
}
if (j++<len)
{
opus_val16 tmp=*x++;
y_0=*y++;
sum[0] = MAC16_16(sum[0],tmp,y_1);
sum[1] = MAC16_16(sum[1],tmp,y_2);
sum[2] = MAC16_16(sum[2],tmp,y_3);
sum[3] = MAC16_16(sum[3],tmp,y_0);
}
if (j<len)
{
opus_val16 tmp=*x++;
y_1=*y++;
sum[0] = MAC16_16(sum[0],tmp,y_2);
sum[1] = MAC16_16(sum[1],tmp,y_3);
sum[2] = MAC16_16(sum[2],tmp,y_0);
sum[3] = MAC16_16(sum[3],tmp,y_1);
}
}
static OPUS_INLINE void dual_inner_prod(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02,
int N, opus_val32 *xy1, opus_val32 *xy2)
{
int i;
opus_val32 xy01=0;
opus_val32 xy02=0;
for (i=0;i<N;i++)
{
xy01 = MAC16_16(xy01, x[i], y01[i]);
xy02 = MAC16_16(xy02, x[i], y02[i]);
}
*xy1 = xy01;
*xy2 = xy02;
}
/*We make sure a C version is always available for cases where the overhead of
vectorization and passing around an arch flag aren't worth it.*/
static OPUS_INLINE opus_val32 celt_inner_prod(const opus_val16 *x,
const opus_val16 *y, int N)
{
int i;
opus_val32 xy=0;
for (i=0;i<N;i++)
xy = MAC16_16(xy, x[i], y[i]);
return xy;
}
void celt_pitch_xcorr(const opus_val16 *_x, const opus_val16 *_y,
opus_val32 *xcorr, int len, int max_pitch);
#endif

@ -0,0 +1,178 @@
/* Copyright (c) 2008-2011 Octasic Inc.
2012-2017 Jean-Marc Valin */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <math.h>
#include "opus_types.h"
#include "common.h"
#include "arch.h"
#include "tansig_table.h"
#include "rnn.h"
#include "rnn_data.h"
#include <stdio.h>
static OPUS_INLINE float tansig_approx(float x)
{
int i;
float y, dy;
float sign=1;
/* Tests are reversed to catch NaNs */
if (!(x<8))
return 1;
if (!(x>-8))
return -1;
#ifndef FIXED_POINT
/* Another check in case of -ffast-math */
if (celt_isnan(x))
return 0;
#endif
if (x<0)
{
x=-x;
sign=-1;
}
i = (int)floor(.5f+25*x);
x -= .04f*i;
y = tansig_table[i];
dy = 1-y*y;
y = y + x*dy*(1 - y*x);
return sign*y;
}
static OPUS_INLINE float sigmoid_approx(float x)
{
return .5 + .5*tansig_approx(.5*x);
}
static OPUS_INLINE float relu(float x)
{
return x < 0 ? 0 : x;
}
void compute_dense(const DenseLayer *layer, float *output, const float *input)
{
int i, j;
int N, M;
int stride;
M = layer->nb_inputs;
N = layer->nb_neurons;
stride = N;
for (i=0;i<N;i++)
{
/* Compute update gate. */
float sum = layer->bias[i];
for (j=0;j<M;j++)
sum += layer->input_weights[j*stride + i]*input[j];
output[i] = WEIGHTS_SCALE*sum;
}
if (layer->activation == ACTIVATION_SIGMOID) {
for (i=0;i<N;i++)
output[i] = sigmoid_approx(output[i]);
} else if (layer->activation == ACTIVATION_TANH) {
for (i=0;i<N;i++)
output[i] = tansig_approx(output[i]);
} else if (layer->activation == ACTIVATION_RELU) {
for (i=0;i<N;i++)
output[i] = relu(output[i]);
} else {
*(int*)0=0;
}
}
void compute_gru(const GRULayer *gru, float *state, const float *input)
{
int i, j;
int N, M;
int stride;
float z[MAX_NEURONS];
float r[MAX_NEURONS];
float h[MAX_NEURONS];
M = gru->nb_inputs;
N = gru->nb_neurons;
stride = 3*N;
for (i=0;i<N;i++)
{
/* Compute update gate. */
float sum = gru->bias[i];
for (j=0;j<M;j++)
sum += gru->input_weights[j*stride + i]*input[j];
for (j=0;j<N;j++)
sum += gru->recurrent_weights[j*stride + i]*state[j];
z[i] = sigmoid_approx(WEIGHTS_SCALE*sum);
}
for (i=0;i<N;i++)
{
/* Compute reset gate. */
float sum = gru->bias[N + i];
for (j=0;j<M;j++)
sum += gru->input_weights[N + j*stride + i]*input[j];
for (j=0;j<N;j++)
sum += gru->recurrent_weights[N + j*stride + i]*state[j];
r[i] = sigmoid_approx(WEIGHTS_SCALE*sum);
}
for (i=0;i<N;i++)
{
/* Compute output. */
float sum = gru->bias[2*N + i];
for (j=0;j<M;j++)
sum += gru->input_weights[2*N + j*stride + i]*input[j];
for (j=0;j<N;j++)
sum += gru->recurrent_weights[2*N + j*stride + i]*state[j]*r[j];
if (gru->activation == ACTIVATION_SIGMOID) sum = sigmoid_approx(WEIGHTS_SCALE*sum);
else if (gru->activation == ACTIVATION_TANH) sum = tansig_approx(WEIGHTS_SCALE*sum);
else if (gru->activation == ACTIVATION_RELU) sum = relu(WEIGHTS_SCALE*sum);
else *(int*)0=0;
h[i] = z[i]*state[i] + (1-z[i])*sum;
}
for (i=0;i<N;i++)
state[i] = h[i];
}
#define INPUT_SIZE 42
void compute_rnn(RNNState *rnn, float *gains, float *vad, const float *input) {
int i;
float dense_out[MAX_NEURONS];
float noise_input[MAX_NEURONS*3];
float denoise_input[MAX_NEURONS*3];
compute_dense(rnn->model->input_dense, dense_out, input);
compute_gru(rnn->model->vad_gru, rnn->vad_gru_state, dense_out);
compute_dense(rnn->model->vad_output, vad, rnn->vad_gru_state);
for (i=0;i<rnn->model->input_dense_size;i++) noise_input[i] = dense_out[i];
for (i=0;i<rnn->model->vad_gru_size;i++) noise_input[i+rnn->model->input_dense_size] = rnn->vad_gru_state[i];
for (i=0;i<INPUT_SIZE;i++) noise_input[i+rnn->model->input_dense_size+rnn->model->vad_gru_size] = input[i];
compute_gru(rnn->model->noise_gru, rnn->noise_gru_state, noise_input);
for (i=0;i<rnn->model->vad_gru_size;i++) denoise_input[i] = rnn->vad_gru_state[i];
for (i=0;i<rnn->model->noise_gru_size;i++) denoise_input[i+rnn->model->vad_gru_size] = rnn->noise_gru_state[i];
for (i=0;i<INPUT_SIZE;i++) denoise_input[i+rnn->model->vad_gru_size+rnn->model->noise_gru_size] = input[i];
compute_gru(rnn->model->denoise_gru, rnn->denoise_gru_state, denoise_input);
compute_dense(rnn->model->denoise_output, gains, rnn->denoise_gru_state);
}

@ -0,0 +1,69 @@
/* Copyright (c) 2017 Jean-Marc Valin */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef RNN_H_
#define RNN_H_
#include "rnnoise.h"
#include "opus_types.h"
#define WEIGHTS_SCALE (1.f/256)
#define MAX_NEURONS 128
#define ACTIVATION_TANH 0
#define ACTIVATION_SIGMOID 1
#define ACTIVATION_RELU 2
typedef signed char rnn_weight;
typedef struct {
const rnn_weight *bias;
const rnn_weight *input_weights;
int nb_inputs;
int nb_neurons;
int activation;
} DenseLayer;
typedef struct {
const rnn_weight *bias;
const rnn_weight *input_weights;
const rnn_weight *recurrent_weights;
int nb_inputs;
int nb_neurons;
int activation;
} GRULayer;
typedef struct RNNState RNNState;
void compute_dense(const DenseLayer *layer, float *output, const float *input);
void compute_gru(const GRULayer *gru, float *state, const float *input);
void compute_rnn(RNNState *rnn, float *gains, float *vad, const float *input);
#endif /* _MLP_H_ */

File diff suppressed because it is too large Load Diff

@ -0,0 +1,34 @@
#ifndef RNN_DATA_H
#define RNN_DATA_H
#include "rnn.h"
struct RNNModel {
int input_dense_size;
const DenseLayer *input_dense;
int vad_gru_size;
const GRULayer *vad_gru;
int noise_gru_size;
const GRULayer *noise_gru;
int denoise_gru_size;
const GRULayer *denoise_gru;
int denoise_output_size;
const DenseLayer *denoise_output;
int vad_output_size;
const DenseLayer *vad_output;
};
struct RNNState {
const RNNModel *model;
float *vad_gru_state;
float *noise_gru_state;
float *denoise_gru_state;
};
#endif

@ -0,0 +1,168 @@
/* Copyright (c) 2018 Gregor Richards */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include "rnn.h"
#include "rnn_data.h"
#include "rnnoise.h"
/* Although these values are the same as in rnn.h, we make them separate to
* avoid accidentally burning internal values into a file format */
#define F_ACTIVATION_TANH 0
#define F_ACTIVATION_SIGMOID 1
#define F_ACTIVATION_RELU 2
RNNModel *rnnoise_model_from_file(FILE *f)
{
int i, in;
if (fscanf(f, "rnnoise-nu model file version %d\n", &in) != 1 || in != 1)
return NULL;
RNNModel *ret = calloc(1, sizeof(RNNModel));
if (!ret)
return NULL;
#define ALLOC_LAYER(type, name) \
type *name; \
name = calloc(1, sizeof(type)); \
if (!name) { \
rnnoise_model_free(ret); \
return NULL; \
} \
ret->name = name
ALLOC_LAYER(DenseLayer, input_dense);
ALLOC_LAYER(GRULayer, vad_gru);
ALLOC_LAYER(GRULayer, noise_gru);
ALLOC_LAYER(GRULayer, denoise_gru);
ALLOC_LAYER(DenseLayer, denoise_output);
ALLOC_LAYER(DenseLayer, vad_output);
#define INPUT_VAL(name) do { \
if (fscanf(f, "%d", &in) != 1 || in < 0 || in > 128) { \
rnnoise_model_free(ret); \
return NULL; \
} \
name = in; \
} while (0)
#define INPUT_ACTIVATION(name) do { \
int activation; \
INPUT_VAL(activation); \
switch (activation) { \
case F_ACTIVATION_SIGMOID: \
name = ACTIVATION_SIGMOID; \
break; \
case F_ACTIVATION_RELU: \
name = ACTIVATION_RELU; \
break; \
default: \
name = ACTIVATION_TANH; \
} \
} while (0)
#define INPUT_ARRAY(name, len) do { \
rnn_weight *values = malloc((len) * sizeof(rnn_weight)); \
if (!values) { \
rnnoise_model_free(ret); \
return NULL; \
} \
name = values; \
for (i = 0; i < (len); i++) { \
if (fscanf(f, "%d", &in) != 1) { \
rnnoise_model_free(ret); \
return NULL; \
} \
values[i] = in; \
} \
} while (0)
#define INPUT_DENSE(name) do { \
INPUT_VAL(name->nb_inputs); \
INPUT_VAL(name->nb_neurons); \
ret->name ## _size = name->nb_neurons; \
INPUT_ACTIVATION(name->activation); \
INPUT_ARRAY(name->input_weights, name->nb_inputs * name->nb_neurons); \
INPUT_ARRAY(name->bias, name->nb_neurons); \
} while (0)
#define INPUT_GRU(name) do { \
INPUT_VAL(name->nb_inputs); \
INPUT_VAL(name->nb_neurons); \
ret->name ## _size = name->nb_neurons; \
INPUT_ACTIVATION(name->activation); \
INPUT_ARRAY(name->input_weights, name->nb_inputs * name->nb_neurons * 3); \
INPUT_ARRAY(name->recurrent_weights, name->nb_neurons * name->nb_neurons * 3); \
INPUT_ARRAY(name->bias, name->nb_neurons * 3); \
} while (0)
INPUT_DENSE(input_dense);
INPUT_GRU(vad_gru);
INPUT_GRU(noise_gru);
INPUT_GRU(denoise_gru);
INPUT_DENSE(denoise_output);
INPUT_DENSE(vad_output);
return ret;
}
void rnnoise_model_free(RNNModel *model)
{
#define FREE_MAYBE(ptr) do { if (ptr) free(ptr); } while (0)
#define FREE_DENSE(name) do { \
if (model->name) { \
free((void *) model->name->input_weights); \
free((void *) model->name->bias); \
free((void *) model->name); \
} \
} while (0)
#define FREE_GRU(name) do { \
if (model->name) { \
free((void *) model->name->input_weights); \
free((void *) model->name->recurrent_weights); \
free((void *) model->name->bias); \
free((void *) model->name); \
} \
} while (0)
if (!model)
return;
FREE_DENSE(input_dense);
FREE_GRU(vad_gru);
FREE_GRU(noise_gru);
FREE_GRU(denoise_gru);
FREE_DENSE(denoise_output);
FREE_DENSE(vad_output);
free(model);
}

@ -0,0 +1,66 @@
#!/usr/bin/python
from __future__ import print_function
from keras.models import Sequential
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense
from keras.layers import LSTM
from keras.layers import GRU
from keras.layers import SimpleRNN
from keras.layers import Dropout
from keras import losses
import h5py
from keras import backend as K
import numpy as np
print('Build model...')
main_input = Input(shape=(None, 22), name='main_input')
#x = Dense(44, activation='relu')(main_input)
#x = GRU(44, dropout=0.0, recurrent_dropout=0.0, activation='tanh', recurrent_activation='sigmoid', return_sequences=True)(x)
x=main_input
x = GRU(128, activation='tanh', recurrent_activation='sigmoid', return_sequences=True)(x)
#x = GRU(128, return_sequences=True)(x)
#x = GRU(22, activation='relu', return_sequences=True)(x)
x = Dense(22, activation='sigmoid')(x)
#x = Dense(22, activation='softplus')(x)
model = Model(inputs=main_input, outputs=x)
batch_size = 32
print('Loading data...')
with h5py.File('denoise_data.h5', 'r') as hf:
all_data = hf['denoise_data'][:]
print('done.')
window_size = 500
nb_sequences = len(all_data)//window_size
print(nb_sequences, ' sequences')
x_train = all_data[:nb_sequences*window_size, :-22]
x_train = np.reshape(x_train, (nb_sequences, window_size, 22))
y_train = np.copy(all_data[:nb_sequences*window_size, -22:])
y_train = np.reshape(y_train, (nb_sequences, window_size, 22))
#y_train = -20*np.log10(np.add(y_train, .03));
all_data = 0;
x_train = x_train.astype('float32')
y_train = y_train.astype('float32')
print(len(x_train), 'train sequences. x shape =', x_train.shape, 'y shape = ', y_train.shape)
# try using different optimizers and different optimizer configs
model.compile(loss='mean_squared_error',
optimizer='adam',
metrics=['binary_accuracy'])
print('Train...')
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=200,
validation_data=(x_train, y_train))
model.save("newweights.hdf5")

@ -0,0 +1,65 @@
/* Copyright (c) 2018 Gregor Richards
* Copyright (c) 2017 Mozilla */
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef RNNOISE_H
#define RNNOISE_H 1
#include <stdio.h>
#ifndef RNNOISE_EXPORT
# if defined(WIN32)
# if defined(RNNOISE_BUILD) && defined(DLL_EXPORT)
# define RNNOISE_EXPORT __declspec(dllexport)
# else
# define RNNOISE_EXPORT
# endif
# elif defined(__GNUC__) && defined(RNNOISE_BUILD)
# define RNNOISE_EXPORT __attribute__ ((visibility ("default")))
# else
# define RNNOISE_EXPORT
# endif
#endif
typedef struct DenoiseState DenoiseState;
typedef struct RNNModel RNNModel;
RNNOISE_EXPORT int rnnoise_get_size();
RNNOISE_EXPORT int rnnoise_init(DenoiseState *st, RNNModel *model);
RNNOISE_EXPORT DenoiseState *rnnoise_create(RNNModel *model);
RNNOISE_EXPORT void rnnoise_destroy(DenoiseState *st);
RNNOISE_EXPORT float rnnoise_process_frame(DenoiseState *st, float *out, const float *in);
RNNOISE_EXPORT RNNModel *rnnoise_model_from_file(FILE *f);
RNNOISE_EXPORT void rnnoise_model_free(RNNModel *model);
#endif

@ -0,0 +1,45 @@
/* This file is auto-generated by gen_tables */
static const float tansig_table[201] = {
0.000000f, 0.039979f, 0.079830f, 0.119427f, 0.158649f,
0.197375f, 0.235496f, 0.272905f, 0.309507f, 0.345214f,
0.379949f, 0.413644f, 0.446244f, 0.477700f, 0.507977f,
0.537050f, 0.564900f, 0.591519f, 0.616909f, 0.641077f,
0.664037f, 0.685809f, 0.706419f, 0.725897f, 0.744277f,
0.761594f, 0.777888f, 0.793199f, 0.807569f, 0.821040f,
0.833655f, 0.845456f, 0.856485f, 0.866784f, 0.876393f,
0.885352f, 0.893698f, 0.901468f, 0.908698f, 0.915420f,
0.921669f, 0.927473f, 0.932862f, 0.937863f, 0.942503f,
0.946806f, 0.950795f, 0.954492f, 0.957917f, 0.961090f,
0.964028f, 0.966747f, 0.969265f, 0.971594f, 0.973749f,
0.975743f, 0.977587f, 0.979293f, 0.980869f, 0.982327f,
0.983675f, 0.984921f, 0.986072f, 0.987136f, 0.988119f,
0.989027f, 0.989867f, 0.990642f, 0.991359f, 0.992020f,
0.992631f, 0.993196f, 0.993718f, 0.994199f, 0.994644f,
0.995055f, 0.995434f, 0.995784f, 0.996108f, 0.996407f,
0.996682f, 0.996937f, 0.997172f, 0.997389f, 0.997590f,
0.997775f, 0.997946f, 0.998104f, 0.998249f, 0.998384f,
0.998508f, 0.998623f, 0.998728f, 0.998826f, 0.998916f,
0.999000f, 0.999076f, 0.999147f, 0.999213f, 0.999273f,
0.999329f, 0.999381f, 0.999428f, 0.999472f, 0.999513f,
0.999550f, 0.999585f, 0.999617f, 0.999646f, 0.999673f,
0.999699f, 0.999722f, 0.999743f, 0.999763f, 0.999781f,
0.999798f, 0.999813f, 0.999828f, 0.999841f, 0.999853f,
0.999865f, 0.999875f, 0.999885f, 0.999893f, 0.999902f,
0.999909f, 0.999916f, 0.999923f, 0.999929f, 0.999934f,
0.999939f, 0.999944f, 0.999948f, 0.999952f, 0.999956f,
0.999959f, 0.999962f, 0.999965f, 0.999968f, 0.999970f,
0.999973f, 0.999975f, 0.999977f, 0.999978f, 0.999980f,
0.999982f, 0.999983f, 0.999984f, 0.999986f, 0.999987f,
0.999988f, 0.999989f, 0.999990f, 0.999990f, 0.999991f,
0.999992f, 0.999992f, 0.999993f, 0.999994f, 0.999994f,
0.999994f, 0.999995f, 0.999995f, 0.999996f, 0.999996f,
0.999996f, 0.999997f, 0.999997f, 0.999997f, 0.999997f,
0.999997f, 0.999998f, 0.999998f, 0.999998f, 0.999998f,
0.999998f, 0.999998f, 0.999999f, 0.999999f, 0.999999f,
0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f,
0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f,
1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f,
1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f,
1.000000f,
};

@ -1,7 +1,6 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
@ -25,7 +24,7 @@ func getCurrentCaps() *capability.Capabilities {
func getSelfFileCaps() *capability.Capabilities {
self, err := os.Executable()
fmt.Printf("Getting caps for: %s\n", self)
log.Printf("Getting caps for: %s\n", self)
if err != nil {
log.Fatalf("Could not get path to own executable: %+v\n", err)
}
@ -58,10 +57,11 @@ func makeBinarySetcapped() error {
return nil
}
func pkexecSetcapSelf() {
func pkexecSetcapSelf() error {
self, err := os.Executable()
if err != nil {
log.Fatalf("Couldn't find path to own binary\n")
return err
}
cmd := exec.Command("pkexec", self, "-setcap")
@ -69,5 +69,8 @@ func pkexecSetcapSelf() {
err = cmd.Run()
if err != nil {
log.Printf("Couldn't setcap self as root: %v\n", err)
return err
}
return nil
}

158
cli.go

@ -0,0 +1,158 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/lawl/pulseaudio"
)
type CLIOpts struct {
doLog bool
setcap bool
sinkName string
unload bool
loadInput bool
loadOutput bool
threshold int
list bool
}
func parseCLIOpts() CLIOpts {
var opt CLIOpts
flag.BoolVar(&opt.doLog, "log", false, "Print debugging output to stdout")
flag.BoolVar(&opt.setcap, "setcap", false, "for internal use only")
flag.StringVar(&opt.sinkName, "s", "", "Use the specified source/sink device ID")
flag.BoolVar(&opt.loadInput, "i", false, "Load supressor for input. If no source device ID is specified the default pulse audio source is used.")
flag.BoolVar(&opt.loadOutput, "o", false, "Load supressor for output. If no source device ID is specified the default pulse audio source is used.")
flag.BoolVar(&opt.unload, "u", false, "Unload supressor")
flag.IntVar(&opt.threshold, "t", -1, "Voice activation threshold")
flag.BoolVar(&opt.list, "l", false, "List available PulseAudio devices")
flag.Parse()
return opt
}
func doCLI(opt CLIOpts, config *config, librnnoise string) {
if opt.setcap {
err := makeBinarySetcapped()
if err != nil {
cleanupExit(librnnoise, 1)
}
cleanupExit(librnnoise, 0)
}
paClient, err := pulseaudio.NewClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't create pulseaudio client: %v\n", err)
cleanupExit(librnnoise, 1)
}
defer paClient.Close()
ctx := ntcontext{}
info, err := serverInfo(paClient)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't fetch audio server info: %s\n", err)
}
ctx.serverInfo = info
ctx.config = config
ctx.librnnoise = librnnoise
ctx.paClient = paClient
if opt.list {
fmt.Println("Sources:")
sources := getSources(paClient)
for i := range sources {
fmt.Printf("\tDevice Name: %s\n\tDevice ID: %s\n\n", sources[i].Name, sources[i].ID)
}
fmt.Println("Sinks:")
sinks := getSinks(paClient)
for i := range sinks {
fmt.Printf("\tDevice Name: %s\n\tDevice ID: %s\n\n", sinks[i].Name, sinks[i].ID)
}
cleanupExit(librnnoise, 0)
}
if opt.threshold > 0 {
if opt.threshold > 95 {
fmt.Fprintf(os.Stderr, "Threshold of '%d' too high, setting to maximum of 95.\n", opt.threshold)
ctx.config.Threshold = 95
} else {
ctx.config.Threshold = opt.threshold
}
}
if opt.unload {
err := unloadSupressor(&ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Error unloading PulseAudio Module: %+v\n", err)
cleanupExit(librnnoise, 1)
}
cleanupExit(librnnoise, 0)
}
if opt.loadInput {
sources := getSources(paClient)
if opt.sinkName == "" {
defaultSource, err := getDefaultSourceID(paClient)
if err != nil {
fmt.Fprintf(os.Stderr, "No source specified to load and failed to load default source: %+v\n", err)
cleanupExit(librnnoise, 1)
}
opt.sinkName = defaultSource
}
for i := range sources {
if sources[i].ID == opt.sinkName {
sources[i].checked = true
err := loadSupressor(&ctx, &sources[i], &device{})
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading PulseAudio Module: %+v\n", err)
cleanupExit(librnnoise, 1)
}
cleanupExit(librnnoise, 0)
}
}
fmt.Fprintf(os.Stderr, "PulseAudio source not found: %s\n", opt.sinkName)
cleanupExit(librnnoise, 1)
}
if opt.loadOutput {
sinks := getSinks(paClient)
if opt.sinkName == "" {
defaultSink, err := getDefaultSinkID(paClient)
if err != nil {
fmt.Fprintf(os.Stderr, "No sink specified to load and failed to load default sink: %+v\n", err)
cleanupExit(librnnoise, 1)
}
opt.sinkName = defaultSink
}
for i := range sinks {
if sinks[i].ID == opt.sinkName {
sinks[i].checked = true
err := loadSupressor(&ctx, &device{}, &sinks[i])
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading PulseAudio Module: %+v\n", err)
cleanupExit(librnnoise, 1)
}
cleanupExit(librnnoise, 0)
}
}
fmt.Fprintf(os.Stderr, "PulseAudio sink not found: %s\n", opt.sinkName)
cleanupExit(librnnoise, 1)
}
}
func cleanupExit(librnnoise string, exitCode int) {
removeLib(librnnoise)
os.Exit(exitCode)
}

@ -14,16 +14,30 @@ type config struct {
Threshold int
DisplayMonitorSources bool
EnableUpdates bool
GuiltTripped bool
FilterInput bool
FilterOutput bool
LastUsedInput string
LastUsedOutput string
}
const configFile = "config.toml"
func initializeConfigIfNot() {
log.Println("Checking if config needs to be initialized")
conf := config{Threshold: 95, DisplayMonitorSources: false, EnableUpdates: true, LastUsedInput: ""} // if you're a package maintainer and you mess with this, we have a problem.
// Unless you set -tags release on the build the updater is *not* compiled in any. DO NOT MESS WITH THIS!
// if you're a package maintainer and you mess with this, we have a problem.
// Unless you set -tags release on the build the updater is *not* compiled in anymore. DO NOT MESS WITH THIS!
// This isn't and never was the proper location to disable the updater.
conf := config{
Threshold: 95,
DisplayMonitorSources: false,
EnableUpdates: true,
GuiltTripped: false,
FilterInput: true,
FilterOutput: false,
LastUsedInput: "",
LastUsedOutput: ""}
configdir := configDir()
ok, err := exists(configdir)

@ -1,12 +1,13 @@
module noisetorch
go 1.14
go 1.16
require (
gioui.org v0.0.0-20200630184602-223f8fd40ae4 // indirect
github.com/BurntSushi/toml v0.3.1
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046
github.com/aarzilli/nucular v0.0.0-20200615134801-81910c722bba
github.com/lawl/pulseaudio v0.0.0-20200802093727-ab0735955fd0
github.com/lawl/pulseaudio v0.0.0-20210604102109-cb2596d6a8ef
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
)

@ -7,6 +7,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA=
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k=
github.com/aarzilli/nucular v0.0.0-20200615134801-81910c722bba h1:7OBB0+T/f0gGMdqTwoXF872nDKosq4dIK/H2cRlrnWI=
github.com/aarzilli/nucular v0.0.0-20200615134801-81910c722bba/go.mod h1:TsFEH0qn2Uu3C3guJjfIaoCqgpoCvU+laq0SSK2TOyY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
@ -19,6 +21,8 @@ github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad h1:eMxs9EL0Pv
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/lawl/pulseaudio v0.0.0-20200802093727-ab0735955fd0 h1:JrvOwrr1teFiqsp0EQxgEPJsm0pet+YLTL+HdYmnMx0=
github.com/lawl/pulseaudio v0.0.0-20200802093727-ab0735955fd0/go.mod h1:9h36x4KH7r2V8DOCKoPMt87IXZ++X90y8D5nnuwq290=
github.com/lawl/pulseaudio v0.0.0-20210604102109-cb2596d6a8ef h1:mpCJg3O6C+B8mh5xoO147NG3Z70GBcFNgqgz2DH/rLQ=
github.com/lawl/pulseaudio v0.0.0-20210604102109-cb2596d6a8ef/go.mod h1:9h36x4KH7r2V8DOCKoPMt87IXZ++X90y8D5nnuwq290=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

@ -1 +0,0 @@
Subproject commit 453a8af82a31a5361f6a13bf95c97686f0a2acd1

@ -1,87 +1,62 @@
package main
import (
"flag"
"fmt"
"image"
"io/ioutil"
"log"
"os"
"path/filepath"
"syscall"
"regexp"
"strconv"
"strings"
"time"
"github.com/BurntSushi/xgbutil"
"github.com/BurntSushi/xgbutil/ewmh"
"github.com/BurntSushi/xgbutil/icccm"
"github.com/aarzilli/nucular/font"
"github.com/lawl/pulseaudio"
_ "embed"
"github.com/aarzilli/nucular"
"github.com/aarzilli/nucular/style"
)
//go:generate go run scripts/embedlibrnnoise.go
//go:generate go run scripts/embedversion.go
//go:generate go run scripts/embedlicenses.go
type input struct {
//go:embed c/ladspa/rnnoise_ladspa.so
var libRNNoise []byte
//go:embed assets/patreon.png
var patreonPNG []byte
type device struct {
ID string
Name string
isMonitor bool
checked bool
dynamicLatency bool
rate uint32
}
func main() {
const appName = "NoiseTorch"
var pulsepid int
var setcap bool
var sourceName string
var unload bool
var load bool
var threshold int
var list bool
flag.IntVar(&pulsepid, "removerlimit", -1, "for internal use only")
flag.BoolVar(&setcap, "setcap", false, "for internal use only")
flag.StringVar(&sourceName, "s", "", "Use the specified source device ID")
flag.BoolVar(&load, "i", false, "Load supressor for input. If no source device ID is specified the default pulse audio source is used.")
flag.BoolVar(&unload, "u", false, "Unload supressor")
flag.IntVar(&threshold, "t", -1, "Voice activation threshold")
flag.BoolVar(&list, "l", false, "List available PulseAudio sources")
flag.Parse()
// we also execute this opportunistically on pulsepid since that's also called as root, but need to do so silently, so no os.Exit()'s
if setcap || pulsepid > 0 {
err := makeBinarySetcapped()
if err != nil && !(pulsepid > 0) {
os.Exit(1)
}
if !(pulsepid > 0) {
os.Exit(0)
}
}
var version = "unknown" // will be changed by build
var distribution = "custom" // ditto
var updateURL = "" // ditto
var publicKeyString = "" // ditto
if pulsepid > 0 {
const MaxUint = ^uint64(0)
new := syscall.Rlimit{Cur: MaxUint, Max: MaxUint}
err := setRlimit(pulsepid, &new)
if err != nil {
os.Exit(1)
}
os.Exit(0)
}
func main() {
opt := parseCLIOpts()
date := time.Now().Format("2006_01_02_03_04_05")
tmpdir := os.TempDir()
f, err := os.OpenFile(filepath.Join(tmpdir, fmt.Sprintf("noisetorch-%s.log", date)), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatalf("error opening file: %v\n", err)
if opt.doLog {
log.SetOutput(os.Stdout)
} else {
log.SetOutput(ioutil.Discard)
}
defer f.Close()
log.SetOutput(f)
log.Printf("Application starting. Version: %s\n", version)
log.Printf("Application starting. Version: %s (%s)\n", version, distribution)
log.Printf("CAP_SYS_RESOURCE: %t\n", hasCapSysResource(getCurrentCaps()))
initializeConfigIfNot()
@ -92,81 +67,34 @@ func main() {
ctx.config = readConfig()
ctx.librnnoise = rnnoisefile
paClient, err := pulseaudio.NewClient()
if err != nil {
log.Printf("Couldn't create pulseaudio client: %v\n", err)
os.Exit(1)
}
if list {
sources := getSources(paClient)
for i := range sources {
fmt.Printf("Device Name: %s\nDevice ID: %s\n\n", sources[i].Name, sources[i].ID)
}
os.Exit(0)
}
if threshold > 0 {
if threshold > 95 {
fmt.Fprintf(os.Stderr, "Threshold of '%d' too high, setting to maximum of 95.\n", threshold)
ctx.config.Threshold = 95
} else {
ctx.config.Threshold = threshold
}
}
if unload {
unloadSupressor(paClient)
os.Exit(0)
}
if load {
ctx.paClient = paClient
sources := getSources(paClient)
if supressorState(paClient) != unloaded {
fmt.Fprintf(os.Stderr, "Supressor is already loaded.\n")
os.Exit(1)
}
if sourceName == "" {
defaultSource, err := getDefaultSourceID(paClient)
if err != nil {
fmt.Fprintf(os.Stderr, "No source specified to load and failed to load default source: %+v\n", err)
os.Exit(1)
}
sourceName = defaultSource
}
for i := range sources {
if sources[i].ID == sourceName {
err := loadSupressor(&ctx, sources[i])
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading PulseAudio Module: %+v\n", err)
os.Exit(1)
}
os.Exit(0)
}
}
fmt.Fprintf(os.Stderr, "PulseAudio source not found: %s\n", sourceName)
os.Exit(1)
}
doCLI(opt, ctx.config, ctx.librnnoise)
if ctx.config.EnableUpdates {
go updateCheck(&ctx)
}
go paConnectionWatchdog(&ctx)
ctx.haveCapabilities = hasCapSysResource(getCurrentCaps())
ctx.capsMismatch = hasCapSysResource(getCurrentCaps()) != hasCapSysResource(getSelfFileCaps())
resetUI(&ctx)
wnd := nucular.NewMasterWindowSize(0, "NoiseTorch", image.Point{550, 300}, func(w *nucular.Window) {
wnd := nucular.NewMasterWindowSize(0, appName, image.Point{600, 400}, func(w *nucular.Window) {
updatefn(&ctx, w)
})
ctx.masterWindow = &wnd
(*ctx.masterWindow).Changed()
go paConnectionWatchdog(&ctx)
style := style.FromTheme(style.DarkTheme, 2.0)
style.Font = font.DefaultFont(16, 1)
wnd.SetStyle(style)
//this is a disgusting hack that searches for the noisetorch window
//and then fixes up the WM_CLASS attribute so it displays
//properly in the taskbar
go fixWindowClass()
wnd.Main()
}
@ -189,29 +117,57 @@ func removeLib(file string) {
log.Printf("Deleted temp librnnoise: %s\n", file)
}
func getSources(client *pulseaudio.Client) []input {
func getSources(client *pulseaudio.Client) []device {
sources, err := client.Sources()
if err != nil {
log.Printf("Couldn't fetch sources from pulseaudio\n")
}
inputs := make([]input, 0)
outputs := make([]device, 0)
for i := range sources {
if sources[i].Name == "nui_mic_remap" {
if strings.Contains(sources[i].Name, "nui_") || strings.Contains(sources[i].Name, "NoiseTorch") {
continue
}
log.Printf("Input %s, %+v\n", sources[i].Name, sources[i])
var inp input
var inp device
inp.ID = sources[i].Name
inp.Name = sources[i].PropList["device.description"]
inp.isMonitor = (sources[i].MonitorSourceIndex != 0xffffffff)
inp.rate = sources[i].SampleSpec.Rate
//PA_SOURCE_DYNAMIC_LATENCY = 0x0040U
inp.dynamicLatency = sources[i].Flags&uint32(0x0040) != 0
outputs = append(outputs, inp)
}
return outputs
}
func getSinks(client *pulseaudio.Client) []device {
sources, err := client.Sinks()
if err != nil {
log.Printf("Couldn't fetch sources from pulseaudio\n")
}
inputs := make([]device, 0)
for i := range sources {
if strings.Contains(sources[i].Name, "nui_") || strings.Contains(sources[i].Name, "NoiseTorch") {
continue
}
log.Printf("Output %s, %+v\n", sources[i].Name, sources[i])
var inp device
inp.ID = sources[i].Name
inp.Name = sources[i].PropList["device.description"]
inp.rate = sources[i].SampleSpec.Rate
// PA_SINK_DYNAMIC_LATENCY = 0x0080U
inp.dynamicLatency = sources[i].Flags&uint32(0x0080) != 0
inputs = append(inputs, inp)
}
@ -225,49 +181,123 @@ func paConnectionWatchdog(ctx *ntcontext) {
continue
}
ctx.views.Push(connectView)
(*ctx.masterWindow).Changed()
paClient, err := pulseaudio.NewClient()
if err != nil {
log.Printf("Couldn't create pulseaudio client: %v\n", err)
fmt.Fprintf(os.Stderr, "Couldn't create pulseaudio client: %v\n", err)
}
info, err := serverInfo(paClient)
if err != nil {
log.Printf("Couldn't fetch audio server info: %s\n", err)
}
ctx.serverInfo = info
log.Printf("Connected to audio server. Server name '%s'\n", info.name)
ctx.paClient = paClient
go updateNoiseSupressorLoaded(paClient, &ctx.noiseSupressorState)
go updateNoiseSupressorLoaded(ctx)
ctx.inputList = getSourcesWithPreSelectedInput(ctx)
ctx.inputList = preselectDevice(ctx, getSources(ctx.paClient), ctx.config.LastUsedInput, getDefaultSourceID)
ctx.outputList = preselectDevice(ctx, getSinks(paClient), ctx.config.LastUsedOutput, getDefaultSinkID)
resetUI(ctx)
(*ctx.masterWindow).Changed()
time.Sleep(500 * time.Millisecond)
}
}
func getSourcesWithPreSelectedInput(ctx *ntcontext) []input {
inputs := getSources(ctx.paClient)
preselectedInputID := &ctx.config.LastUsedInput
inputExists := false
if preselectedInputID != nil {
for _, input := range inputs {
inputExists = inputExists || input.ID == *preselectedInputID
}
func serverInfo(paClient *pulseaudio.Client) (audioserverinfo, error) {
info, err := paClient.ServerInfo()
if err != nil {
log.Printf("Couldn't fetch pulse server info: %v\n", err)
fmt.Fprintf(os.Stderr, "Couldn't fetch pulse server info: %v\n", err)
}
if !inputExists {
defaultSource, err := getDefaultSourceID(ctx.paClient)
pkgname := info.PackageName
log.Printf("Audioserver package name: %s\n", pkgname)
log.Printf("Audioserver package version: %s\n", info.PackageVersion)
isPipewire := strings.Contains(pkgname, "PipeWire")
var servername string
var servertype uint
var major, minor, patch int
var versionRegex *regexp.Regexp
var versionString string
var outdatedPipeWire bool
if isPipewire {
servername = "PipeWire"
servertype = servertype_pipewire
versionRegex = regexp.MustCompile(`.*?on PipeWire (\d+)\.(\d+)\.(\d+).*?`)
versionString = pkgname
log.Printf("Detected PipeWire\n")
} else {
servername = "PulseAudio"
servertype = servertype_pulse
versionRegex = regexp.MustCompile(`.*?(\d+)\.(\d+)\.(\d+).*?`)
versionString = info.PackageVersion
log.Printf("Detected PulseAudio\n")
}
res := versionRegex.FindStringSubmatch(versionString)
if len(res) != 4 {
log.Printf("couldn't parse server version, regexp didn't match version: %s\n", versionString)
return audioserverinfo{servertype: servertype}, nil
}
major, err = strconv.Atoi(res[1])
if err != nil {
return audioserverinfo{servertype: servertype}, err
}
minor, err = strconv.Atoi(res[2])
if err != nil {
return audioserverinfo{servertype: servertype}, err
}
patch, err = strconv.Atoi(res[3])
if err != nil {
return audioserverinfo{servertype: servertype}, err
}
if isPipewire && major <= 0 && minor <= 3 && patch < 28 {
log.Printf("pipewire version %d.%d.%d too old.\n", major, minor, patch)
outdatedPipeWire = true
}
return audioserverinfo{
servertype: servertype,
name: servername,
major: major,
minor: minor,
patch: patch,
outdatedPipeWire: outdatedPipeWire}, nil
}
func preselectDevice(ctx *ntcontext, devices []device, preselectID string,
fallbackFunc func(client *pulseaudio.Client) (string, error)) []device {
deviceExists := false
for _, input := range devices {
deviceExists = deviceExists || input.ID == preselectID
}
if !deviceExists {
defaultDevice, err := fallbackFunc(ctx.paClient)
if err != nil {
log.Printf("Failed to load default source: %+v\n", err)
log.Printf("Failed to load default device: %+v\n", err)
} else {
preselectedInputID = &defaultSource
preselectID = defaultDevice
}
}
if preselectedInputID != nil {
for i := range inputs {
if inputs[i].ID == *preselectedInputID {
inputs[i].checked = true
}
for i := range devices {
if devices[i].ID == preselectID {
devices[i].checked = true
}
}
return inputs
return devices
}
func getDefaultSourceID(client *pulseaudio.Client) (string, error) {
@ -277,3 +307,43 @@ func getDefaultSourceID(client *pulseaudio.Client) (string, error) {
}
return server.DefaultSource, nil
}
func getDefaultSinkID(client *pulseaudio.Client) (string, error) {
server, err := client.ServerInfo()
if err != nil {
return "", err
}
return server.DefaultSink, nil
}
//this is disgusting
func fixWindowClass() {
xu, err := xgbutil.NewConn()
defer xu.Conn().Close()
if err != nil {
log.Printf("Couldn't create XU xdg conn: %+v\n", err)
return
}
for i := 0; i < 100; i++ {
wnds, _ := ewmh.ClientListGet(xu)
for _, w := range wnds {
n, _ := ewmh.WmNameGet(xu, w)
if n == appName {
_, err := icccm.WmClassGet(xu, w)
//if we have *NO* WM_CLASS, then the above call errors. We *want* to make sure this errors
if err == nil {
continue
}
class := icccm.WmClass{}
class.Class = appName
class.Instance = appName
icccm.WmClassSet(xu, w, &class)
return
}
}
time.Sleep(100 * time.Millisecond)
}
}

@ -4,7 +4,6 @@ import (
"fmt"
"log"
"strings"
"time"
"github.com/lawl/pulseaudio"
)
@ -17,16 +16,15 @@ const (
// the ugly and (partially) repeated strings are unforunately difficult to avoid, as it's what pulse audio expects
func updateNoiseSupressorLoaded(c *pulseaudio.Client, b *int) {
func updateNoiseSupressorLoaded(ctx *ntcontext) {
c := ctx.paClient
upd, err := c.Updates()
if err != nil {
fmt.Printf("Error listening for updates: %v\n", err)
}
for {
*b = supressorState(c)
ctx.noiseSupressorState, ctx.virtualDeviceInUse = supressorState(ctx)
if !c.Connected() {
break
}
@ -35,117 +33,316 @@ func updateNoiseSupressorLoaded(c *pulseaudio.Client, b *int) {
}
}
func supressorState(c *pulseaudio.Client) int {
func supressorState(ctx *ntcontext) (int, bool) {
//perform some checks to see if it looks like the noise supressor is loaded
_, nullsink, err := findModule(c, "module-null-sink", "sink_name=nui_mic_denoised_out")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-null-sink: %v\n", err)
c := ctx.paClient
var inpLoaded, outLoaded, inputInc, outputInc bool
var virtualDeviceInUse bool = false
if ctx.config.FilterInput {
if ctx.serverInfo.servertype == servertype_pipewire {
module, ladspasource, err := findModule(c, "module-ladspa-source", "source_name='NoiseTorch Microphone'")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-ladspa-source: %v\n", err)
}
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
inpLoaded = ladspasource
inputInc = false
} else {
_, nullsink, err := findModule(c, "module-null-sink", "sink_name=nui_mic_denoised_out")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-null-sink: %v\n", err)
}
_, ladspasink, err := findModule(c, "module-ladspa-sink", "sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err)
}
_, loopback, err := findModule(c, "module-loopback", "sink=nui_mic_raw_in")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-loopback: %v\n", err)
}
module, remap, err := findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-remap-source: %v\n", err)
}
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
if nullsink && ladspasink && loopback && remap {
inpLoaded = true
} else if nullsink || ladspasink || loopback || remap {
inputInc = true
}
}
} else {
inpLoaded = true
}
_, ladspasink, err := findModule(c, "module-ladspa-sink", "sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err)
if ctx.config.FilterOutput {
if ctx.serverInfo.servertype == servertype_pipewire {
module, ladspasink, err := findModule(c, "module-ladspa-sink", "sink_name='NoiseTorch Headphones'")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err)
}
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
outLoaded = ladspasink
outputInc = false
} else {
_, out, err := findModule(c, "module-null-sink", "sink_name=nui_out_out_sink")
if err != nil {
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
}
_, lad, err := findModule(c, "module-ladspa-sink", "sink_name=nui_out_ladspa")
if err != nil {
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
}
_, loop, err := findModule(c, "module-loopback", "source=nui_out_out_sink.monitor")
if err != nil {
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
}
module, outin, err := findModule(c, "module-null-sink", "sink_name=nui_out_in_sink")
if err != nil {
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
}
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
_, loop2, err := findModule(c, "module-loopback", "source=nui_out_in_sink.monitor")
if err != nil {
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
}
outLoaded = out && lad && loop && outin && loop2
outputInc = out || lad || loop || outin || loop2
}
} else {
outLoaded = true
}
_, loopback, err := findModule(c, "module-loopback", "sink=nui_mic_raw_in")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-loopback: %v\n", err)
if (inpLoaded || !ctx.config.FilterInput) && (outLoaded || !ctx.config.FilterOutput) && !inputInc {
return loaded, virtualDeviceInUse
}
_, remap, err := findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap")
if err != nil {
log.Printf("Couldn't fetch module list to check for module-remap-source: %v\n", err)
if (inpLoaded && ctx.config.FilterInput) || (outLoaded && ctx.config.FilterOutput) || inputInc || outputInc {
return inconsistent, virtualDeviceInUse
}
return unloaded, virtualDeviceInUse
}
func loadSupressor(ctx *ntcontext, inp *device, out *device) error {
if ctx.serverInfo.servertype == servertype_pulse {
log.Printf("Querying pulse rlimit\n")
pid, err := getPulsePid()
if err != nil {
return err
}
lim, err := getRlimit(pid)
if err != nil {
return err
}
log.Printf("Rlimit: %+v. Trying to remove.\n", lim)
removeRlimit(pid)
defer setRlimit(pid, &lim) // lowering RLIMIT doesn't require root
newLim, err := getRlimit(pid)
if err != nil {
return err
}
log.Printf("Rlimit: %+v\n", newLim)
}
if inp.checked {
var err error
if ctx.serverInfo.servertype == servertype_pipewire {
err = loadPipeWireInput(ctx, inp)
} else {
err = loadPulseInput(ctx, inp)
}
if err != nil {
log.Printf("Error loading input: %v\n", err)
return err
}
}
if nullsink && ladspasink && loopback && remap {
return loaded
if out.checked {
var err error
if ctx.serverInfo.servertype == servertype_pipewire {
err = loadPipeWireOutput(ctx, out)
} else {
err = loadPulseOutput(ctx, out)
}
if err != nil {
log.Printf("Error loading output: %v\n", err)
return err
}
}
if nullsink || ladspasink || loopback || remap {
return inconsistent
return nil
}
func loadModule(ctx *ntcontext, module, args string) (uint32, error) {
idx, err := ctx.paClient.LoadModule(module, args)
//14 = module initialisation failed
if paErr, ok := err.(*pulseaudio.Error); ok && paErr.Code == 14 {
resetUI(ctx)
ctx.views.Push(makeErrorView(ctx, fmt.Sprintf("Could not load module '%s'. This is likely a problem with your system or distribution.", module)))
}
return unloaded
return idx, err
}
func loadSupressor(ctx *ntcontext, inp input) error {
c := ctx.paClient
func loadPipeWireInput(ctx *ntcontext, inp *device) error {
log.Printf("Loading supressor for pipewire\n")
idx, err := loadModule(ctx, "module-ladspa-source",
fmt.Sprintf("source_name='NoiseTorch Microphone' master=%s "+
"rate=48000 channels=1 "+
"label=noisetorch plugin=%s control=%d", inp.ID, ctx.librnnoise, ctx.config.Threshold))
log.Printf("Querying pulse rlimit\n")
if err != nil {
return err
}
log.Printf("Loaded ladspa source as idx: %d\n", idx)
return nil
}
func loadPipeWireOutput(ctx *ntcontext, out *device) error {
log.Printf("Loading supressor for pipewire\n")
idx, err := loadModule(ctx, "module-ladspa-sink",
fmt.Sprintf("sink_name='NoiseTorch Headphones' master=%s "+
"rate=48000 channels=1 "+
"label=noisetorch plugin=%s control=%d", out.ID, ctx.librnnoise, ctx.config.Threshold))
pid, err := getPulsePid()
if err != nil {
return err
}
log.Printf("Loaded ladspa source as idx: %d\n", idx)
return nil
}
lim, err := getRlimit(pid)
func loadPulseInput(ctx *ntcontext, inp *device) error {
log.Printf("Loading supressor for pulse\n")
idx, err := loadModule(ctx, "module-null-sink", "sink_name=nui_mic_denoised_out rate=48000")
if err != nil {
return err
}
log.Printf("Rlimit: %+v. Trying to remove.\n", lim)
if hasCapSysResource(getCurrentCaps()) {
log.Printf("Have capabilities\n")
removeRlimit(pid)
log.Printf("Loaded null sink as idx: %d\n", idx)
idx, err = loadModule(ctx, "module-ladspa-sink",
fmt.Sprintf("sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out "+
"label=noisetorch plugin=%s control=%d", ctx.librnnoise, ctx.config.Threshold))
if err != nil {
return err
}
log.Printf("Loaded ladspa sink as idx: %d\n", idx)
if inp.dynamicLatency {
idx, err = loadModule(ctx, "module-loopback",
fmt.Sprintf("source=%s sink=nui_mic_raw_in channels=1 latency_msec=1 source_dont_move=true sink_dont_move=true", inp.ID))
if err != nil {
return err
}
log.Printf("Loaded loopback as idx: %d\n", idx)
} else {
log.Printf("Capabilities missing, removing via pkexec\n")
removeRlimitAsRoot(pid)
idx, err = loadModule(ctx, "module-loopback",
fmt.Sprintf("source=%s sink=nui_mic_raw_in channels=1 latency_msec=50 source_dont_move=true sink_dont_move=true adjust_time=1", inp.ID))
if err != nil {
return err
}
log.Printf("Loaded fixed latency loopback as idx: %d\n", idx)
}
defer setRlimit(pid, &lim) // lowering RLIMIT doesn't require root
newLim, err := getRlimit(pid)
idx, err = loadModule(ctx, "module-remap-source", `master=nui_mic_denoised_out.monitor `+
`source_name=nui_mic_remap source_properties="device.description='NoiseTorch Microphone'"`)
if err != nil {
return err
}
log.Printf("Rlimit: %+v\n", newLim)
log.Printf("Loaded remap source as idx: %d\n", idx)
return nil
}
log.Printf("Loading supressor\n")
idx, err := c.LoadModule("module-null-sink", "sink_name=nui_mic_denoised_out rate=48000")
func loadPulseOutput(ctx *ntcontext, out *device) error {
_, err := loadModule(ctx, "module-null-sink", `sink_name=nui_out_out_sink`)
if err != nil {
return err
}
log.Printf("Loaded null sink as idx: %d\n", idx)
time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast
_, err = loadModule(ctx, "module-null-sink", `sink_name=nui_out_in_sink sink_properties="device.description='NoiseTorch Headphones'"`)
if err != nil {
return err
}
idx, err = c.LoadModule("module-ladspa-sink",
fmt.Sprintf("sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out "+
"label=noise_suppressor_mono plugin=%s control=%d", ctx.librnnoise, ctx.config.Threshold))
_, err = loadModule(ctx, "module-ladspa-sink", fmt.Sprintf(`sink_name=nui_out_ladspa sink_master=nui_out_out_sink `+
`label=noisetorch channels=1 plugin=%s control=%d rate=%d`,
ctx.librnnoise, ctx.config.Threshold, 48000))
if err != nil {
return err
}
log.Printf("Loaded ladspa sink as idx: %d\n", idx)
time.Sleep(time.Millisecond * 1000) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast
_, err = loadModule(ctx, "module-loopback",
fmt.Sprintf("source=nui_out_out_sink.monitor sink=%s channels=2 latency_msec=50 source_dont_move=true sink_dont_move=true", out.ID))
if err != nil {
return err
}
idx, err = c.LoadModule("module-loopback",
fmt.Sprintf("source=%s sink=nui_mic_raw_in channels=1 latency_msec=1 source_dont_move=true sink_dont_move=true", inp.ID))
_, err = loadModule(ctx, "module-loopback",
fmt.Sprintf("source=nui_out_in_sink.monitor sink=nui_out_ladspa channels=1 latency_msec=50 source_dont_move=true sink_dont_move=true"))
if err != nil {
return err
}
log.Printf("Loaded loopback as idx: %d\n", idx)
return nil
}
time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast
func unloadSupressor(ctx *ntcontext) error {
if ctx.serverInfo.servertype == servertype_pipewire {
return unloadSupressorPipeWire(ctx)
} else {
return unloadSupressorPulse(ctx)
}
}
idx, err = c.LoadModule("module-remap-source", `master=nui_mic_denoised_out.monitor `+
`source_name=nui_mic_remap source_properties="device.description='NoiseTorch Microphone'"`)
func unloadSupressorPipeWire(ctx *ntcontext) error {
log.Printf("Unloading modules for pipewire\n")
log.Printf("Searching for module-ladspa-source\n")
c := ctx.paClient
m, found, err := findModule(c, "module-ladspa-source", "source_name='NoiseTorch Microphone'")
if err != nil {
return err
}
log.Printf("Loaded ladspa sink as idx: %d\n", idx)
if found {
log.Printf("Found module-ladspa-source at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
log.Printf("Searching for module-ladspa-sink\n")
m, found, err = findModule(c, "module-ladspa-sink", "sink_name='NoiseTorch Headphones'")
if err != nil {
return err
}
if found {
log.Printf("Found module-ladspa-sink at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
return nil
}
func unloadSupressor(c *pulseaudio.Client) error {
log.Printf("Unloading pulseaudio modules\n")
func unloadSupressorPulse(ctx *ntcontext) error {
log.Printf("Unloading modules for pulseaudio\n")
// we ignore errors here on purpose, since NT didn't use to do this for unloading anyways
// and we don't want to prompt for root again
// so this only suceeds with CAP_SYS_RESOURCE, which we want to make the new default anyways.
if pid, err := getPulsePid(); err == nil {
if lim, err := getRlimit(pid); err == nil {
log.Printf("Trying to remove rlimit. Limit is: %+v\n", lim)
removeRlimit(pid)
newLim, _ := getRlimit(pid)
log.Printf("Rlimit: %+v\n", newLim)
defer setRlimit(pid, &lim)
}
}
log.Printf("Searching for null-sink\n")
c := ctx.paClient
m, found, err := findModule(c, "module-null-sink", "sink_name=nui_mic_denoised_out")
if err != nil {
return err
@ -154,7 +351,7 @@ func unloadSupressor(c *pulseaudio.Client) error {
log.Printf("Found null-sink at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast
log.Printf("Searching for ladspa-sink\n")
m, found, err = findModule(c, "module-ladspa-sink", "sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out")
if err != nil {
@ -164,7 +361,7 @@ func unloadSupressor(c *pulseaudio.Client) error {
log.Printf("Found ladspa-sink at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast
log.Printf("Searching for loopback\n")
m, found, err = findModule(c, "module-loopback", "sink=nui_mic_raw_in")
if err != nil {
@ -174,7 +371,7 @@ func unloadSupressor(c *pulseaudio.Client) error {
log.Printf("Found loopback at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
time.Sleep(time.Millisecond * 500) // pulseaudio gets SIGKILL'd because of RLIMITS if we send these too fast
log.Printf("Searching for remap-source\n")
m, found, err = findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap")
if err != nil {
@ -184,12 +381,64 @@ func unloadSupressor(c *pulseaudio.Client) error {
log.Printf("Found remap source at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
log.Printf("Searching for output module-null-sink\n")
m, found, err = findModule(c, "module-null-sink", "sink_name=nui_out_out_sink")
if err != nil {
return err
}
if found {
log.Printf("Found output null sink at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
log.Printf("Searching for output module-null-sink\n")
m, found, err = findModule(c, "module-null-sink", "sink_name=nui_out_in_sink")
if err != nil {
return err
}
if found {
log.Printf("Found output null sink at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
log.Printf("Searching for output module-ladspa-sink\n")
m, found, err = findModule(c, "module-ladspa-sink", "sink_name=nui_out_ladspa")
if err != nil {
return err
}
if found {
log.Printf("Found output ladspa sink at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
log.Printf("Searching for output module-loopback\n")
m, found, err = findModule(c, "module-loopback", "source=nui_out_out_sink.monitor")
if err != nil {
return err
}
if found {
log.Printf("Found output loopback at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
log.Printf("Searching for output module-loopback\n")
m, found, err = findModule(c, "module-loopback", "source=nui_out_in_sink.monitor")
if err != nil {
return err
}
if found {
log.Printf("Found output loopback at id [%d], sending unload command\n", m.Index)
c.UnloadModule(m.Index)
}
return nil
}
// Finds a module by exactly matching the module name, and checking if the second string is a substring of the argument
func findModule(c *pulseaudio.Client, name string, argMatch string) (module pulseaudio.Module, found bool, err error) {
lst, err := c.ModuleList()
if err != nil {
return pulseaudio.Module{}, false, err
}

@ -3,8 +3,6 @@ package main
import (
"io/ioutil"
"log"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
@ -52,20 +50,6 @@ func removeRlimit(pid int) {
}
}
func removeRlimitAsRoot(pid int) {
self, err := os.Executable()
if err != nil {
log.Fatalf("Couldn't find path to own binary\n")
}
cmd := exec.Command("pkexec", self, "-removerlimit", strconv.Itoa(pid))
log.Printf("Calling: %s\n", cmd.String())
err = cmd.Run()
if err != nil {
log.Printf("Couldn't remove rlimit as root: %v\n", err)
}
}
func pRlimit(pid int, limit uintptr, new *syscall.Rlimit, old *syscall.Rlimit) error {
_, _, errno := syscall.RawSyscall6(syscall.SYS_PRLIMIT64,
uintptr(pid),

@ -1,28 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"strconv"
)
func main() {
b, err := ioutil.ReadFile("librnnoise_ladspa/bin/ladspa/librnnoise_ladspa.so")
if err != nil {
fmt.Printf("Couldn't read librnnoise_ladspa.so: %v\n", err)
os.Exit(1)
}
out, _ := os.Create("librnnoise.go")
defer out.Close()
out.Write([]byte("package main \n\n//THIS FILE IS AUTOMATICALLY GENERATED BY `go generate` DO NOT EDIT!\n\nvar libRNNoise = []byte{\n"))
for i, c := range b {
out.Write([]byte(strconv.Itoa(int(c))))
out.Write([]byte(","))
if i%32 == 0 && i != 0 {
out.Write([]byte("\n"))
}
}
out.Write([]byte("}\n"))
}

@ -1,24 +0,0 @@
package main
import (
"os"
"os/exec"
"strings"
)
func main() {
cmd := exec.Command("git", "describe", "--tags")
ret, err := cmd.Output()
if err != nil {
panic("Couldn't read git tags to embed version number")
}
version := strings.TrimSpace(string(ret))
out, _ := os.Create("version.go")
defer out.Close()
out.Write([]byte("package main\n\n//THIS FILE IS AUTOMATICALLY GENERATED BY `go generate` DO NOT EDIT!\n\nvar version=\""))
out.Write([]byte(version))
out.Write([]byte("\"\n"))
}

@ -22,8 +22,8 @@ func main() {
var doSign bool
flag.BoolVar(&doSign, "s", false, "Sign the release tar")
var doVerify bool
flag.BoolVar(&doVerify, "v", false, "Verify the signature of the tar")
var publicKeyString string
flag.StringVar(&publicKeyString, "k", "", "Public key to verify against (runs verifier if set)")
flag.Parse()
@ -56,9 +56,8 @@ func main() {
os.Exit(0)
}
if doVerify {
pubStr := "3mL+rBi4yBZ1wGimQ/oSQCjxELzgTh+673H4JdzQBOk="
pub, err := base64.StdEncoding.DecodeString(pubStr)
if publicKeyString != "" {
pub, err := base64.StdEncoding.DecodeString(publicKeyString)
if err != nil {
panic(err)
}

440
ui.go

@ -1,10 +1,16 @@
package main
import (
"bytes"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"log"
"os"
"os/exec"
"syscall"
"time"
"github.com/aarzilli/nucular"
@ -13,71 +19,79 @@ import (
)
type ntcontext struct {
inputList []input
inputList []device
outputList []device
noiseSupressorState int
paClient *pulseaudio.Client
librnnoise string
sourceListColdWidthIndex int
useBuiltinRNNoise bool
config *config
loadingScreen bool
licenseScreen bool
versionScreen bool
licenseTextArea nucular.TextEditor
masterWindow *nucular.MasterWindow
update updateui
reloadRequired bool
haveCapabilities bool
capsMismatch bool
views *ViewStack
serverInfo audioserverinfo
virtualDeviceInUse bool
}
//TODO pull some of these strucs out of UI, they don't belong here
type audioserverinfo struct {
servertype uint
name string
major int
minor int
patch int
outdatedPipeWire bool
}
const (
servertype_pulse = iota
servertype_pipewire
)
var green = color.RGBA{34, 187, 69, 255}
var red = color.RGBA{255, 70, 70, 255}
var orange = color.RGBA{255, 140, 0, 255}
func updatefn(ctx *ntcontext, w *nucular.Window) {
if !ctx.paClient.Connected() {
connectScreen(ctx, w)
return
}
if ctx.loadingScreen {
loadingScreen(ctx, w)
return
}
var patreonImg *image.RGBA
if ctx.licenseScreen {
licenseScreen(ctx, w)
return
}
func updatefn(ctx *ntcontext, w *nucular.Window) {
currView := ctx.views.Peek()
currView(ctx, w)
}
if ctx.versionScreen {
versionScreen(ctx, w)
return
}
func mainView(ctx *ntcontext, w *nucular.Window) {
w.MenubarBegin()
w.Row(10).Dynamic(2)
w.Row(10).Dynamic(1)
if w := w.Menu(label.TA("About", "LC"), 120, nil); w != nil {
w.Row(10).Dynamic(1)
if w.MenuItem(label.T("Licenses")) {
ctx.licenseScreen = true
ctx.views.Push(licenseView)
}
w.Row(10).Dynamic(1)
if w.MenuItem(label.T("Source code")) {
exec.Command("xdg-open", "https://github.com/lawl/NoiseTorch").Run()
}
if w.MenuItem(label.T("Version")) {
ctx.versionScreen = true
ctx.views.Push(versionView)
}
}
if w := w.Menu(label.TA("Support NoiseTorch on PATREON", "RC"), 120, nil); w != nil {
exec.Command("xdg-open", "https://patreon.com/lawl").Run()
w.Close()
}
w.MenubarEnd()
w.Row(15).Dynamic(1)
w.Row(25).Dynamic(2)
if patreonImg == nil {
patreonImg = loadPatreonImg()
}
if imageButton(w, patreonImg) {
exec.Command("xdg-open", "https://patreon.com/lawl").Run()
}
if ctx.noiseSupressorState == loaded {
w.LabelColored("NoiseTorch active", "RC", green)
@ -87,6 +101,11 @@ func updatefn(ctx *ntcontext, w *nucular.Window) {
w.LabelColored("Inconsistent state, please unload first.", "RC", orange)
}
if ctx.serverInfo.servertype == servertype_pipewire {
w.Row(20).Dynamic(1)
w.Label("Running in PipeWire mode. PipeWire support is currently alpha quality. Please report bugs.", "LC")
}
if ctx.update.available && !ctx.update.triggered {
w.Row(20).Ratio(0.9, 0.1)
w.LabelColored("Update available! Click to install version: "+ctx.update.serverVersion, "LC", green)
@ -127,9 +146,22 @@ func updatefn(ctx *ntcontext, w *nucular.Window) {
w.LabelColored("Reloading NoiseTorch is required to apply these changes.", "LC", orange)
}
w.Row(15).Dynamic(2)
if w.CheckboxText("Filter Microphone", &ctx.config.FilterInput) {
ctx.sourceListColdWidthIndex++ //recompute the with because of new elements
go writeConfig(ctx.config)
go (func() { ctx.noiseSupressorState, _ = supressorState(ctx) })()
}
if w.CheckboxText("Filter Headphones", &ctx.config.FilterOutput) {
ctx.sourceListColdWidthIndex++ //recompute the with because of new elements
go writeConfig(ctx.config)
go (func() { ctx.noiseSupressorState, _ = supressorState(ctx) })()
}
w.TreePop()
}
if w.TreePush(nucular.TreeTab, "Select Device", true) {
if ctx.config.FilterInput && w.TreePush(nucular.TreeTab, "Select Microphone", true) {
w.Row(15).Dynamic(1)
w.Label("Select an input device below:", "LC")
@ -153,69 +185,152 @@ func updatefn(ctx *ntcontext, w *nucular.Window) {
}
}
w.Row(30).Dynamic(1)
w.Spacing(1)
w.TreePop()
}
if ctx.config.FilterOutput && w.TreePush(nucular.TreeTab, "Select Headphones", true) {
if ctx.config.GuiltTripped {
w.Row(15).Dynamic(1)
w.Label("Select an output device below:", "LC")
for i := range ctx.outputList {
el := &ctx.outputList[i]
if el.isMonitor && !ctx.config.DisplayMonitorSources {
continue
}
w.Row(15).Static()
w.LayoutFitWidth(0, 0)
if w.CheckboxText("", &el.checked) {
ensureOnlyOneInputSelected(&ctx.outputList, el)
}
w.LayoutFitWidth(ctx.sourceListColdWidthIndex, 0)
if el.dynamicLatency {
w.Label(el.Name, "LC")
} else {
w.LabelColored("(incompatible?) "+el.Name, "LC", orange)
}
w.Row(25).Dynamic(2)
if ctx.noiseSupressorState != unloaded {
if w.ButtonText("Unload NoiseTorch") {
ctx.loadingScreen = true
ctx.reloadRequired = false
go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads
if err := unloadSupressor(ctx.paClient); err != nil {
log.Println(err)
}
//wait until PA reports it has actually loaded it, timeout at 10s
for i := 0; i < 20; i++ {
if supressorState(ctx.paClient) != unloaded {
time.Sleep(time.Millisecond * 500)
}
}
ctx.loadingScreen = false
(*ctx.masterWindow).Changed()
}()
}
} else {
w.Spacing(1)
w.Row(15).Dynamic(1)
w.Label("This feature is only for patrons.", "LC")
w.Row(15).Dynamic(1)
w.Label("You can still use it eitherway, but you are legally required to feel bad.", "LC")
w.Row(25).Dynamic(2)
if w.ButtonText("Become a patron") {
exec.Command("xdg-open", "https://patreon.com/lawl").Run()
ctx.config.GuiltTripped = true
go writeConfig(ctx.config)
}
if w.ButtonText("Feel bad") {
ctx.config.GuiltTripped = true
go writeConfig(ctx.config)
}
}
txt := "Load NoiseTorch"
if ctx.noiseSupressorState == loaded {
txt = "Reload NoiseTorch"
w.TreePop()
}
w.Row(15).Dynamic(1)
w.Spacing(1)
w.Row(25).Dynamic(2)
if ctx.noiseSupressorState != unloaded {
if w.ButtonText("Unload NoiseTorch") {
ctx.reloadRequired = false
if ctx.virtualDeviceInUse {
confirm := makeConfirmView(ctx,
"Virtual Device in Use",
"Some applications may behave weirdly when you remove a device they're currently using",
"Unload",
"Go back",
func() { uiUnloadNoisetorch(ctx) },
func() {})
ctx.views.Push(confirm)
} else {
go uiUnloadNoisetorch(ctx)
}
}
if inp, ok := inputSelection(ctx); ok && ctx.noiseSupressorState != inconsistent {
if w.ButtonText(txt) {
ctx.loadingScreen = true
ctx.reloadRequired = false
go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads
if ctx.noiseSupressorState == loaded {
if err := unloadSupressor(ctx.paClient); err != nil {
log.Println(err)
}
}
if err := loadSupressor(ctx, inp); err != nil {
log.Println(err)
}
//wait until PA reports it has actually loaded it, timeout at 10s
for i := 0; i < 20; i++ {
if supressorState(ctx.paClient) != loaded {
time.Sleep(time.Millisecond * 500)
}
}
ctx.config.LastUsedInput = inp.ID
go writeConfig(ctx.config)
ctx.loadingScreen = false
(*ctx.masterWindow).Changed()
}()
} else {
w.Spacing(1)
}
txt := "Load NoiseTorch"
if ctx.noiseSupressorState == loaded {
txt = "Reload NoiseTorch"
}
inp, inpOk := inputSelection(ctx)
out, outOk := outputSelection(ctx)
if (!ctx.config.FilterInput || (ctx.config.FilterInput && inpOk)) &&
(!ctx.config.FilterOutput || (ctx.config.FilterOutput && outOk)) &&
(ctx.config.FilterInput || ctx.config.FilterOutput) &&
((ctx.config.FilterOutput && ctx.config.GuiltTripped) || !ctx.config.FilterOutput) &&
ctx.noiseSupressorState != inconsistent {
if w.ButtonText(txt) {
ctx.reloadRequired = false
if ctx.virtualDeviceInUse {
confirm := makeConfirmView(ctx,
"Virtual Device in Use",
"Some applications may behave weirdly when you reload a device they're currently using",
"Reload",
"Go back",
func() { uiReloadNoisetorch(ctx, inp, out) },
func() {})
ctx.views.Push(confirm)
} else {
go uiReloadNoisetorch(ctx, inp, out)
}
} else {
w.Spacing(1)
}
w.TreePop()
} else {
w.Spacing(1)
}
}
func uiUnloadNoisetorch(ctx *ntcontext) {
ctx.views.Push(loadingView)
if err := unloadSupressor(ctx); err != nil {
log.Println(err)
}
//wait until PA reports it has actually loaded it, timeout at 10s
for i := 0; i < 20; i++ {
if state, _ := supressorState(ctx); state != unloaded {
time.Sleep(time.Millisecond * 500)
}
}
ctx.views.Pop()
(*ctx.masterWindow).Changed()
}
func uiReloadNoisetorch(ctx *ntcontext, inp, out device) {
ctx.views.Push(loadingView)
if ctx.noiseSupressorState == loaded {
if err := unloadSupressor(ctx); err != nil {
log.Println(err)
}
}
if err := loadSupressor(ctx, &inp, &out); err != nil {
log.Println(err)
}
//wait until PA reports it has actually loaded it, timeout at 10s
for i := 0; i < 20; i++ {
if state, _ := supressorState(ctx); state != loaded {
time.Sleep(time.Millisecond * 500)
}
}
ctx.config.LastUsedInput = inp.ID
ctx.config.LastUsedOutput = out.ID
go writeConfig(ctx.config)
ctx.views.Pop()
(*ctx.masterWindow).Changed()
}
func ensureOnlyOneInputSelected(inps *[]input, current *input) {
func ensureOnlyOneInputSelected(inps *[]device, current *device) {
if current.checked != true {
return
}
@ -226,23 +341,40 @@ func ensureOnlyOneInputSelected(inps *[]input, current *input) {
current.checked = true
}
func inputSelection(ctx *ntcontext) (input, bool) {
func inputSelection(ctx *ntcontext) (device, bool) {
if !ctx.config.FilterInput {
return device{}, false
}
for _, in := range ctx.inputList {
if in.checked {
return in, true
}
}
return input{}, false
return device{}, false
}
func outputSelection(ctx *ntcontext) (device, bool) {
if !ctx.config.FilterOutput {
return device{}, false
}
for _, out := range ctx.outputList {
if out.checked {
return out, true
}
}
return device{}, false
}
func loadingScreen(ctx *ntcontext, w *nucular.Window) {
func loadingView(ctx *ntcontext, w *nucular.Window) {
w.Row(50).Dynamic(1)
w.Label("Working...", "CB")
w.Row(50).Dynamic(1)
w.Label("(this may take a few seconds)", "CB")
}
func licenseScreen(ctx *ntcontext, w *nucular.Window) {
func licenseView(ctx *ntcontext, w *nucular.Window) {
w.Row(255).Dynamic(1)
field := &ctx.licenseTextArea
field.Flags |= nucular.EditMultiline
@ -254,33 +386,145 @@ func licenseScreen(ctx *ntcontext, w *nucular.Window) {
w.Row(20).Dynamic(2)
w.Spacing(1)
if w.ButtonText("OK") {
ctx.licenseScreen = false
ctx.views.Pop()
}
}
func versionScreen(ctx *ntcontext, w *nucular.Window) {
func versionView(ctx *ntcontext, w *nucular.Window) {
w.Row(50).Dynamic(1)
w.Label("Version", "CB")
w.Row(50).Dynamic(1)
w.Label(version, "CB")
w.Label(fmt.Sprintf("%s (%s)", version, distribution), "CB")
w.Row(50).Dynamic(1)
w.Spacing(1)
w.Row(20).Dynamic(2)
w.Spacing(1)
if w.ButtonText("OK") {
ctx.versionScreen = false
ctx.views.Pop()
}
}
func connectScreen(ctx *ntcontext, w *nucular.Window) {
func connectView(ctx *ntcontext, w *nucular.Window) {
w.Row(50).Dynamic(1)
w.Label("Connecting to pulseaudio...", "CB")
}
func capabilitiesView(ctx *ntcontext, w *nucular.Window) {
w.Row(15).Dynamic(1)
w.Label("NoiseTorch currently does not have the capabilities to function properly.", "CB")
w.Row(15).Dynamic(1)
w.Label("We require CAP_SYS_RESOURCE. If that doesn't mean anything to you, don't worry. I'll fix it for you.", "CB")
if ctx.capsMismatch {
w.Row(15).Dynamic(1)
w.LabelColored("Warning: File has CAP_SYS_RESOURCE but our process doesn't.", "CB", orange)
w.Row(15).Dynamic(1)
w.LabelColored("Check if your filesystem has nosuid set or check the troubleshooting page.", "CB", orange)
}
w.Row(40).Dynamic(1)
w.Row(25).Dynamic(1)
if w.ButtonText("Grant capability (requires root)") {
err := pkexecSetcapSelf()
if err != nil {
ctx.views.Push(makeErrorView(ctx, err.Error()))
return
}
self, err := os.Executable()
if err != nil {
ctx.views.Push(makeErrorView(ctx, err.Error()))
return
}
err = syscall.Exec(self, []string{""}, os.Environ())
if err != nil {
ctx.views.Push(makeErrorView(ctx, err.Error()))
return
}
}
}
func makeErrorView(ctx *ntcontext, errorMsg string) ViewFunc {
return func(ctx *ntcontext, w *nucular.Window) {
w.Row(15).Dynamic(1)
w.Label("Error", "CB")
w.Row(15).Dynamic(1)
w.Label(errorMsg, "CB")
w.Row(40).Dynamic(1)
w.Row(25).Dynamic(1)
if w.ButtonText("OK") {
ctx.views.Pop()
return
}
}
}
func makeFatalErrorView(ctx *ntcontext, errorMsg string) ViewFunc {
return func(ctx *ntcontext, w *nucular.Window) {
w.Row(15).Dynamic(1)
w.Label("Fatal Error", "CB")
w.Row(15).Dynamic(1)
w.Label(errorMsg, "CB")
w.Row(40).Dynamic(1)
w.Row(25).Dynamic(1)
if w.ButtonText("Quit") {
os.Exit(1)
return
}
}
}
func makeConfirmView(ctx *ntcontext, title, text, confirmText, denyText string, confirmfunc, denyfunc func()) ViewFunc {
return func(ctx *ntcontext, w *nucular.Window) {
w.Row(15).Dynamic(1)
w.Label(title, "CB")
w.Row(15).Dynamic(1)
w.Label(text, "CB")
w.Row(40).Dynamic(1)
w.Row(25).Dynamic(2)
if w.ButtonText(denyText) {
ctx.views.Pop()
go denyfunc()
return
}
if w.ButtonText(confirmText) {
ctx.views.Pop()
go confirmfunc()
return
}
}
}
func resetUI(ctx *ntcontext) {
ctx.loadingScreen = false
ctx.views = NewViewStack()
ctx.views.Push(mainView)
if !ctx.haveCapabilities {
ctx.views.Push(capabilitiesView)
}
if ctx.masterWindow != nil {
(*ctx.masterWindow).Changed()
if ctx.serverInfo.outdatedPipeWire {
ctx.views.Push(makeFatalErrorView(ctx,
fmt.Sprintf("Your PipeWire version is too old. Detected %d.%d.%d. Require at least 0.3.28.",
ctx.serverInfo.major, ctx.serverInfo.minor, ctx.serverInfo.patch)))
}
}
func loadPatreonImg() *image.RGBA {
var pat *image.RGBA
img, _ := png.Decode(bytes.NewReader(patreonPNG))
pat = image.NewRGBA(img.Bounds())
draw.Draw(pat, img.Bounds(), img, image.Point{}, draw.Src)
return pat
}
func imageButton(w *nucular.Window, img *image.RGBA) bool {
style := w.Master().Style()
origButtonStyle := style.Button
style.Button.Border = 0
style.Button.Normal.Data.Color = style.NormalWindow.Background
style.Button.Hover.Data.Color = style.NormalWindow.Background
style.Button.Active.Data.Color = style.NormalWindow.Background
defer (func() { style.Button = origButtonStyle })()
return w.Button(label.I(patreonImg), false)
}

@ -1,5 +1,3 @@
// +build release
package main
import (
@ -14,9 +12,6 @@ import (
"strings"
)
var updateURL = "https://noisetorch.epicgamer.org"
var publicKeyString = "3mL+rBi4yBZ1wGimQ/oSQCjxELzgTh+673H4JdzQBOk="
type updateui struct {
serverVersion string
available bool
@ -24,7 +19,14 @@ type updateui struct {
updatingText string
}
func updateable() bool {
return updateURL != "" && publicKeyString != ""
}
func updateCheck(ctx *ntcontext) {
if !updateable() {
return
}
log.Println("Checking for updates")
bodybuf, err := fetchFile("version.txt")
if err != nil {
@ -41,6 +43,9 @@ func updateCheck(ctx *ntcontext) {
}
func update(ctx *ntcontext) {
if !updateable() {
return
}
sig, err := fetchFile("NoiseTorch_x64.tgz.sig")
if err != nil {
log.Println("Couldn't fetch signature", err)
@ -94,8 +99,9 @@ func fetchFile(file string) ([]byte, error) {
func publickey() []byte {
pub, err := base64.StdEncoding.DecodeString(publicKeyString)
if err != nil {
panic(err) // it's hardcoded, we should never hit this, panic if we do
if err != nil { // Should only happen when distributor ships an invalid public key
log.Fatalf("Error while reading public key: %s\nContact the distribution '%s' about this error.\n", err, distribution)
os.Exit(1)
}
return pub
}

@ -1,28 +0,0 @@
// +build !release
package main
import "errors"
type updateui struct {
serverVersion string
available bool
triggered bool
updatingText string
}
func updateCheck(ctx *ntcontext) {
}
func update(ctx *ntcontext) {
}
func fetchFile(file string) ([]byte, error) {
return make([]byte, 0), errors.New("Disabled by build flags")
}
func publickey() []byte {
return make([]byte, 0)
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,718 @@
// Package xinerama is the X client API for the XINERAMA extension.
package xinerama
// This file is automatically generated from xinerama.xml. Edit at your peril!
import (
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/xproto"
)
// Init must be called before using the XINERAMA extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 8, "XINERAMA").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named XINERAMA could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["XINERAMA"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["XINERAMA"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["XINERAMA"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["XINERAMA"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["XINERAMA"] = make(map[int]xgb.NewErrorFun)
}
type ScreenInfo struct {
XOrg int16
YOrg int16
Width uint16
Height uint16
}
// ScreenInfoRead reads a byte slice into a ScreenInfo value.
func ScreenInfoRead(buf []byte, v *ScreenInfo) int {
b := 0
v.XOrg = int16(xgb.Get16(buf[b:]))
b += 2
v.YOrg = int16(xgb.Get16(buf[b:]))
b += 2
v.Width = xgb.Get16(buf[b:])
b += 2
v.Height = xgb.Get16(buf[b:])
b += 2
return b
}
// ScreenInfoReadList reads a byte slice into a list of ScreenInfo values.
func ScreenInfoReadList(buf []byte, dest []ScreenInfo) int {
b := 0
for i := 0; i < len(dest); i++ {
dest[i] = ScreenInfo{}
b += ScreenInfoRead(buf[b:], &dest[i])
}
return xgb.Pad(b)
}
// Bytes writes a ScreenInfo value to a byte slice.
func (v ScreenInfo) Bytes() []byte {
buf := make([]byte, 8)
b := 0
xgb.Put16(buf[b:], uint16(v.XOrg))
b += 2
xgb.Put16(buf[b:], uint16(v.YOrg))
b += 2
xgb.Put16(buf[b:], v.Width)
b += 2
xgb.Put16(buf[b:], v.Height)
b += 2
return buf[:b]
}
// ScreenInfoListBytes writes a list of ScreenInfo values to a byte slice.
func ScreenInfoListBytes(buf []byte, list []ScreenInfo) int {
b := 0
var structBytes []byte
for _, item := range list {
structBytes = item.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
return xgb.Pad(b)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// GetScreenCountCookie is a cookie used only for GetScreenCount requests.
type GetScreenCountCookie struct {
*xgb.Cookie
}
// GetScreenCount sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetScreenCountCookie.Reply()
func GetScreenCount(c *xgb.Conn, Window xproto.Window) GetScreenCountCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetScreenCount' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getScreenCountRequest(c, Window), cookie)
return GetScreenCountCookie{cookie}
}
// GetScreenCountUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetScreenCountUnchecked(c *xgb.Conn, Window xproto.Window) GetScreenCountCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetScreenCount' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getScreenCountRequest(c, Window), cookie)
return GetScreenCountCookie{cookie}
}
// GetScreenCountReply represents the data returned from a GetScreenCount request.
type GetScreenCountReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
ScreenCount byte
Window xproto.Window
}
// Reply blocks and returns the reply data for a GetScreenCount request.
func (cook GetScreenCountCookie) Reply() (*GetScreenCountReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getScreenCountReply(buf), nil
}
// getScreenCountReply reads a byte slice into a GetScreenCountReply value.
func getScreenCountReply(buf []byte) *GetScreenCountReply {
v := new(GetScreenCountReply)
b := 1 // skip reply determinant
v.ScreenCount = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Window = xproto.Window(xgb.Get32(buf[b:]))
b += 4
return v
}
// Write request to wire for GetScreenCount
// getScreenCountRequest writes a GetScreenCount request to a byte slice.
func getScreenCountRequest(c *xgb.Conn, Window xproto.Window) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// GetScreenSizeCookie is a cookie used only for GetScreenSize requests.
type GetScreenSizeCookie struct {
*xgb.Cookie
}
// GetScreenSize sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetScreenSizeCookie.Reply()
func GetScreenSize(c *xgb.Conn, Window xproto.Window, Screen uint32) GetScreenSizeCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetScreenSize' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getScreenSizeRequest(c, Window, Screen), cookie)
return GetScreenSizeCookie{cookie}
}
// GetScreenSizeUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetScreenSizeUnchecked(c *xgb.Conn, Window xproto.Window, Screen uint32) GetScreenSizeCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetScreenSize' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getScreenSizeRequest(c, Window, Screen), cookie)
return GetScreenSizeCookie{cookie}
}
// GetScreenSizeReply represents the data returned from a GetScreenSize request.
type GetScreenSizeReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
Width uint32
Height uint32
Window xproto.Window
Screen uint32
}
// Reply blocks and returns the reply data for a GetScreenSize request.
func (cook GetScreenSizeCookie) Reply() (*GetScreenSizeReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getScreenSizeReply(buf), nil
}
// getScreenSizeReply reads a byte slice into a GetScreenSizeReply value.
func getScreenSizeReply(buf []byte) *GetScreenSizeReply {
v := new(GetScreenSizeReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Width = xgb.Get32(buf[b:])
b += 4
v.Height = xgb.Get32(buf[b:])
b += 4
v.Window = xproto.Window(xgb.Get32(buf[b:]))
b += 4
v.Screen = xgb.Get32(buf[b:])
b += 4
return v
}
// Write request to wire for GetScreenSize
// getScreenSizeRequest writes a GetScreenSize request to a byte slice.
func getScreenSizeRequest(c *xgb.Conn, Window xproto.Window, Screen uint32) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
xgb.Put32(buf[b:], Screen)
b += 4
return buf
}
// GetStateCookie is a cookie used only for GetState requests.
type GetStateCookie struct {
*xgb.Cookie
}
// GetState sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetStateCookie.Reply()
func GetState(c *xgb.Conn, Window xproto.Window) GetStateCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetState' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getStateRequest(c, Window), cookie)
return GetStateCookie{cookie}
}
// GetStateUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetStateUnchecked(c *xgb.Conn, Window xproto.Window) GetStateCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetState' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getStateRequest(c, Window), cookie)
return GetStateCookie{cookie}
}
// GetStateReply represents the data returned from a GetState request.
type GetStateReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
State byte
Window xproto.Window
}
// Reply blocks and returns the reply data for a GetState request.
func (cook GetStateCookie) Reply() (*GetStateReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getStateReply(buf), nil
}
// getStateReply reads a byte slice into a GetStateReply value.
func getStateReply(buf []byte) *GetStateReply {
v := new(GetStateReply)
b := 1 // skip reply determinant
v.State = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Window = xproto.Window(xgb.Get32(buf[b:]))
b += 4
return v
}
// Write request to wire for GetState
// getStateRequest writes a GetState request to a byte slice.
func getStateRequest(c *xgb.Conn, Window xproto.Window) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// IsActiveCookie is a cookie used only for IsActive requests.
type IsActiveCookie struct {
*xgb.Cookie
}
// IsActive sends a checked request.
// If an error occurs, it will be returned with the reply by calling IsActiveCookie.Reply()
func IsActive(c *xgb.Conn) IsActiveCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'IsActive' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(isActiveRequest(c), cookie)
return IsActiveCookie{cookie}
}
// IsActiveUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func IsActiveUnchecked(c *xgb.Conn) IsActiveCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'IsActive' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(isActiveRequest(c), cookie)
return IsActiveCookie{cookie}
}
// IsActiveReply represents the data returned from a IsActive request.
type IsActiveReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
State uint32
}
// Reply blocks and returns the reply data for a IsActive request.
func (cook IsActiveCookie) Reply() (*IsActiveReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return isActiveReply(buf), nil
}
// isActiveReply reads a byte slice into a IsActiveReply value.
func isActiveReply(buf []byte) *IsActiveReply {
v := new(IsActiveReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.State = xgb.Get32(buf[b:])
b += 4
return v
}
// Write request to wire for IsActive
// isActiveRequest writes a IsActive request to a byte slice.
func isActiveRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// QueryScreensCookie is a cookie used only for QueryScreens requests.
type QueryScreensCookie struct {
*xgb.Cookie
}
// QueryScreens sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryScreensCookie.Reply()
func QueryScreens(c *xgb.Conn) QueryScreensCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'QueryScreens' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryScreensRequest(c), cookie)
return QueryScreensCookie{cookie}
}
// QueryScreensUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryScreensUnchecked(c *xgb.Conn) QueryScreensCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'QueryScreens' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryScreensRequest(c), cookie)
return QueryScreensCookie{cookie}
}
// QueryScreensReply represents the data returned from a QueryScreens request.
type QueryScreensReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
Number uint32
// padding: 20 bytes
ScreenInfo []ScreenInfo // size: xgb.Pad((int(Number) * 8))
}
// Reply blocks and returns the reply data for a QueryScreens request.
func (cook QueryScreensCookie) Reply() (*QueryScreensReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryScreensReply(buf), nil
}
// queryScreensReply reads a byte slice into a QueryScreensReply value.
func queryScreensReply(buf []byte) *QueryScreensReply {
v := new(QueryScreensReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Number = xgb.Get32(buf[b:])
b += 4
b += 20 // padding
v.ScreenInfo = make([]ScreenInfo, v.Number)
b += ScreenInfoReadList(buf[b:], v.ScreenInfo)
return v
}
// Write request to wire for QueryScreens
// queryScreensRequest writes a QueryScreens request to a byte slice.
func queryScreensRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply()
func QueryVersion(c *xgb.Conn, Major byte, Minor byte) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, Major, Minor), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, Major byte, Minor byte) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, Major, Minor), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
Major uint16
Minor uint16
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Major = xgb.Get16(buf[b:])
b += 2
v.Minor = xgb.Get16(buf[b:])
b += 2
return v
}
// Write request to wire for QueryVersion
// queryVersionRequest writes a QueryVersion request to a byte slice.
func queryVersionRequest(c *xgb.Conn, Major byte, Minor byte) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
buf[b] = Major
b += 1
buf[b] = Minor
b += 1
return buf
}

@ -0,0 +1,6 @@
*.swp
*.png
tst_first
tst_graphics
TAGS

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

@ -0,0 +1,36 @@
all: callback.go types_auto.go gofmt
install:
go install -p 6 . ./ewmh ./gopher ./icccm ./keybind ./motif ./mousebind \
./xcursor ./xevent ./xgraphics ./xinerama ./xprop ./xrect ./xwindow
push:
git push origin master
git push github master
build-ex:
find ./_examples/ -type d -wholename './_examples/[a-z]*' -print0 \
| xargs -0 go build -p 6
gofmt:
gofmt -w *.go */*.go _examples/*/*.go
colcheck *.go */*.go _examples/*/*.go
callback.go:
scripts/write-events callbacks > xevent/callback.go
types_auto.go:
scripts/write-events evtypes > xevent/types_auto.go
tags:
find ./ \( -name '*.go' -and -not -wholename './tests/*' -and -not -wholename './_examples/*' \) -print0 | xargs -0 gotags > TAGS
loc:
find ./ -name '*.go' -and -not -wholename './tests*' -and -not -name '*keysymdef.go' -and -not -name '*gopher.go' -print | sort | xargs wc -l
ex-%:
go run _examples/$*/main.go
gopherimg:
go-bindata -f GopherPng -p gopher -i gopher/gophercolor-small.png -o gopher/gopher.go

@ -0,0 +1,55 @@
xgbutil is a utility library designed to work with the X Go Binding. This
project's main goal is to make various X related tasks easier. For example,
binding keys, using the EWMH or ICCCM specs with the window manager,
moving/resizing windows, assigning function callbacks to particular events,
drawing images to a window, etc.
xgbutil attempts to be thread safe, but it has not been completely tested in
this regard. In general, the X event loop implemented in the xevent package is
sequential. The idea is to be sequential by default, and let the user spawn
concurrent code at their discretion. (i.e., the complexity of making the main
event loop generally concurrent is vast.)
You may sleep safely at night by assuming that XGB is thread safe, though.
To start using xgbutil, you should have at least a passing familiarity with X.
Your first stop should be the examples directory.
Installation
============
go get github.com/BurntSushi/xgbutil
Dependencies
============
XGB is the main dependency. Use of the xgraphics packages requires graphics-go
and freetype-go.
XGB project URL: https://github.com/BurntSushi/xgb
Quick Example
=============
go get github.com/BurntSushi/xgbutil/_examples/window-name-sizes
"$GOPATH"/bin/window-name-sizes
The output will be a list of names of all top-level windows and their geometry
including window manager decorations. (Assuming your window manager supports
some basic EWMH properties.)
Documentation
=============
https://godoc.org/github.com/BurntSushi/xgbutil
Examples
========
There are several examples in the examples directory covering common use cases.
They are heavily documented and should run out of the box.
Python
======
An older project of mine, xpybutil, served as inspiration for xgbutil. If you
want to use Python, xpybutil should help quite a bit. Please note though, that
at this point, xgbutil provides a lot more functionality and is much better
documented.
xpybutil project URL: https://github.com/BurntSushi/xpybutil

@ -0,0 +1,29 @@
I like to keep all my code to 80 columns or less. I have plenty of screen real
estate, but enjoy 80 columns so that I can have multiple code windows open side
to side and not be plagued by the ugly auto-wrapping of a text editor.
If you don't oblige me, I will fix any patch you submit to abide 80 columns.
Note that this style restriction does not preclude gofmt, but introduces a few
peculiarities. The first is that gofmt will occasionally add spacing (typically
to comments) that ends up going over 80 columns. Either shorten the comment or
put it on its own line.
The second and more common hiccup is when a function definition extends beyond
80 columns. If one adds line breaks to keep it below 80 columns, gofmt will
indent all subsequent lines in a function definition to the same indentation
level of the function body. This results in a less-than-ideal separation
between function definition and function body. To remedy this, simply add a
line break like so:
func RestackWindowExtra(xu *xgbutil.XUtil, win xproto.Window, stackMode int,
sibling xproto.Window, source int) error {
return ClientEvent(xu, win, "_NET_RESTACK_WINDOW", source, int(sibling),
stackMode)
}
Something similar should also be applied to long 'if' or 'for' conditionals,
although it would probably be preferrable to break up the conditional to
smaller chunks with a few helper variables.

@ -0,0 +1,67 @@
/*
Package xgbutil is a utility library designed to make common tasks with the X
server easier. The central design choice that has driven development is to hide
the complexity of X wherever possible but expose it when necessary.
For example, the xevent package provides an implementation of an X event loop
that acts as a dispatcher to event handlers set up with the xevent, keybind and
mousebind packages. At the same time, the event queue is exposed and can be
modified using xevent.Peek and xevent.DequeueAt.
Sub-packages
The xgbutil package is considerably small, and only contains some type
definitions and the initial setup for an X connection. Much of the
functionality of xgbutil comes from its sub-packages. Each sub-package is
appropriately documented.
Installation
xgbutil is go-gettable:
go get github.com/BurntSushi/xgbutil
Dependencies
XGB is the main dependency, and is required for all packages inside xgbutil.
graphics-go and freetype-go are also required if using the xgraphics package.
Quick Example
A quick example to demonstrate that xgbutil is working correctly:
go get github.com/BurntSushi/xgbutil/examples/window-name-sizes
GO/PATH/bin/window-name-sizes
The output will be a list of names of all top-level windows and their geometry
including window manager decorations. (Assuming your window manager supports
some basic EWMH properties.)
Examples
The examples directory contains a sizable number of examples demonstrating
common tasks with X. They are intended to demonstrate a single thing each,
although a few that require setup are necessarily long. Each example is
heavily documented.
The examples directory should be your first stop when learning how to use
xgbutil.
xgbutil is also used heavily throughout my window manager, Wingo. It may be
useful reference material.
Wingo project page: https://github.com/BurntSushi/wingo
Thread Safety
While I am fairly confident that XGB is thread safe, I am only somewhat
confident that xgbutil is thread safe. It simply has not been tested enough for
my confidence to be higher.
Note that the xevent package's X event loop is not concurrent. Namely,
designing a generally concurrent X event loop is extremely complex. Instead,
the onus is on you, the user, to design concurrent callback functions if
concurrency is desired.
*/
package xgbutil

@ -0,0 +1,56 @@
/*
Package ewmh provides a comprehensive API to get and set properties specified
by the EWMH spec, as well as perform actions specified by the EWMH spec.
Since there are so many functions and they adhere to an existing spec,
this package file does not contain much documentation. Indeed, each
method has only a single comment associated with it: the EWMH property name.
The idea is to
provide a consistent interface to use all facilities described in the EWMH
spec: http://standards.freedesktop.org/wm-spec/wm-spec-latest.html.
Naming scheme
Using "_NET_ACTIVE_WINDOW" as an example,
functions "ActiveWindowGet" and "ActiveWindowSet" get and set the
property, respectively. Both of these functions exist for most EWMH
properties. Additionally, some EWMH properties support sending a client
message event to request the window manager to perform some action. In the
case of "_NET_ACTIVE_WINDOW", this request is used to set the active
window.
These sorts of functions end in "Req". So for "_NET_ACTIVE_WINDOW",
the method name is "ActiveWindowReq". Moreover, most requests include
various parameters that don't need to be changed often (like the source
indication). Thus, by default, functions ending in "Req" force these to
sensible defaults. If you need access to all of the parameters, use the
corresponding "ReqExtra" method. So for "_NET_ACTIVE_WINDOW", that would
be "ActiveWindowReqExtra". (If no "ReqExtra" method exists, then the
"Req" method covers all available parameters.)
This naming scheme has one exception: if a property's only use is through
sending an event (like "_NET_CLOSE_WINDOW"), then the name will be
"CloseWindow" for the short-hand version and "CloseWindowExtra"
for access to all of the parameters. (Since there is no "_NET_CLOSE_WINDOW"
property, there is no need for "CloseWindowGet" and "CloseWindowSet"
functions.)
For properties that store more than just a simple integer, name or list
of integers, structs have been created and exposed to organize the
information returned in a sensible manner. For example, the
"_NET_DESKTOP_GEOMETRY" property would typically return a slice of integers
of length 2, where the first integer is the width and the second is the
height. Xgbutil will wrap this in a struct with the obvious members. These
structs are documented.
Finally, functions ending in "*Set" are typically only used when setting
properties on clients *you've* created or when the window manager sets
properties. Thus, it's unlikely that you should use them unless you're
creating a top-level client or building a window manager.
Functions ending in "Get" or "Req[Extra]" are commonly used.
N.B. Not all properties have "*Req" functions.
*/
package ewmh

File diff suppressed because it is too large Load Diff

@ -0,0 +1,31 @@
package ewmh
import (
"fmt"
"github.com/BurntSushi/xgbutil"
)
// GetEwmhWM uses the EWMH spec to find if a conforming window manager
// is currently running or not. If it is, then its name will be returned.
// Otherwise, an error will be returned explaining why one couldn't be found.
func GetEwmhWM(xu *xgbutil.XUtil) (string, error) {
childCheck, err := SupportingWmCheckGet(xu, xu.RootWin())
if err != nil {
return "", fmt.Errorf("GetEwmhWM: Failed because: %s", err)
}
childCheck2, err := SupportingWmCheckGet(xu, childCheck)
if err != nil {
return "", fmt.Errorf("GetEwmhWM: Failed because: %s", err)
}
if childCheck != childCheck2 {
return "", fmt.Errorf(
"GetEwmhWM: _NET_SUPPORTING_WM_CHECK value on the root window "+
"(%x) does not match _NET_SUPPORTING_WM_CHECK value "+
"on the child window (%x).", childCheck, childCheck2)
}
return WmNameGet(xu, childCheck)
}

@ -0,0 +1,61 @@
/*
Package icccm provides an API for a portion of the ICCCM, namely, getters
and setters for many of the properties specified in the ICCCM. There is also a
smattering of support for other protocols specified by ICCCM. For example, to
satisfy the WM_DELETE_WINDOW protocol, package icccm provides 'IsDeleteProtocol'
which returns whether a ClientMessage event satisfies the WM_DELETE_WINDOW
protocol.
If a property has values that aren't simple strings or integers, struct types
are provided to organize the data. In particular, WM_NORMAL_HINTS and WM_HINTS.
Also note that properties like WM_NORMAL_HINTS and WM_HINTS contain a 'Flags'
field (a bit mask) that specifies which values are "active". This is of
importance when setting and reading WM_NORMAL_HINTS and WM_HINTS; one must make
sure the appropriate bit is set in Flags.
For example, you might want to check if a window has specified a resize
increment in the WM_NORMAL_HINTS property. The values in the corresponding
NormalHints struct are WidthInc and HeightInc. So to check if such values exist
*and* should be used:
normalHints, err := icccm.WmNormalHintsGet(XUtilValue, window-id)
if err != nil {
// handle error
}
if normalHints.Flags&icccm.SizeHintPResizeInc > 0 {
// Use normalHints.WidthInc and normalHints.HeightInc
}
When you should use icccm
Although the ICCCM is extremely old, a lot of it is still used. In fact, the
EWMH spec itself specifically states that the ICCCM should still be used unless
otherwise noted by the EWMH. For example, WM_HINTS and WM_NORMAL_HINTS are
still used, but _NET_WM_NAME replaces WM_NAME.
With that said, many applications (like xterm or LibreOffice) have not been
updated to be fully EWMH compliant. Therefore, code that finds a window's name
often looks like this:
winName, err := ewmh.WmNameGet(XUtilValue, window-id)
if err != nil || winName == "" {
winName, err = icccm.WmNameGet(XUtilValue, window-id)
if err != nill || winName == "" {
winName = "N/A"
}
}
Something similar can be said for the _NET_WM_ICON and the IconPixmap field
in WM_HINTS.
Naming scheme
The naming scheme is precisely the same as the one found in the ewmh package.
The documentation for the ewmh package describes the naming scheme in more
detail. The only difference (currently) is that the icccm package only contains
functions ending in "Get" and "Set". It is planned to add "Req" functions. (An
example of a Req function would be to send a ClientMessage implementing the
WM_DELETE_WINDOW protocol to a client window.)
*/
package icccm

@ -0,0 +1,358 @@
package icccm
import (
"fmt"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
"github.com/BurntSushi/xgbutil/xprop"
)
const (
HintInput = (1 << iota)
HintState
HintIconPixmap
HintIconWindow
HintIconPosition
HintIconMask
HintWindowGroup
HintMessage
HintUrgency
)
const (
SizeHintUSPosition = (1 << iota)
SizeHintUSSize
SizeHintPPosition
SizeHintPSize
SizeHintPMinSize
SizeHintPMaxSize
SizeHintPResizeInc
SizeHintPAspect
SizeHintPBaseSize
SizeHintPWinGravity
)
const (
StateWithdrawn = iota
StateNormal
StateZoomed
StateIconic
StateInactive
)
// WM_NAME get
func WmNameGet(xu *xgbutil.XUtil, win xproto.Window) (string, error) {
return xprop.PropValStr(xprop.GetProperty(xu, win, "WM_NAME"))
}
// WM_NAME set
func WmNameSet(xu *xgbutil.XUtil, win xproto.Window, name string) error {
return xprop.ChangeProp(xu, win, 8, "WM_NAME", "STRING", ([]byte)(name))
}
// WM_ICON_NAME get
func WmIconNameGet(xu *xgbutil.XUtil, win xproto.Window) (string, error) {
return xprop.PropValStr(xprop.GetProperty(xu, win, "WM_ICON_NAME"))
}
// WM_ICON_NAME set
func WmIconNameSet(xu *xgbutil.XUtil, win xproto.Window, name string) error {
return xprop.ChangeProp(xu, win, 8, "WM_ICON_NAME", "STRING",
([]byte)(name))
}
// NormalHints is a struct that organizes the information related to the
// WM_NORMAL_HINTS property. Please see the ICCCM spec for more details.
type NormalHints struct {
Flags uint
X, Y int
Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight uint
WidthInc, HeightInc uint
MinAspectNum, MinAspectDen, MaxAspectNum, MaxAspectDen uint
BaseWidth, BaseHeight, WinGravity uint
}
// WM_NORMAL_HINTS get
func WmNormalHintsGet(xu *xgbutil.XUtil,
win xproto.Window) (nh *NormalHints, err error) {
lenExpect := 18
hints, err := xprop.PropValNums(xprop.GetProperty(xu, win,
"WM_NORMAL_HINTS"))
if err != nil {
return nil, err
}
if len(hints) != lenExpect {
return nil,
fmt.Errorf("WmNormalHint: There are %d fields in WM_NORMAL_HINTS, "+
"but xgbutil expects %d.", len(hints), lenExpect)
}
nh = &NormalHints{}
nh.Flags = hints[0]
nh.X = int(hints[1])
nh.Y = int(hints[2])
nh.Width = hints[3]
nh.Height = hints[4]
nh.MinWidth = hints[5]
nh.MinHeight = hints[6]
nh.MaxWidth = hints[7]
nh.MaxHeight = hints[8]
nh.WidthInc = hints[9]
nh.HeightInc = hints[10]
nh.MinAspectNum = hints[11]
nh.MinAspectDen = hints[12]
nh.MaxAspectNum = hints[13]
nh.MaxAspectDen = hints[14]
nh.BaseWidth = hints[15]
nh.BaseHeight = hints[16]
nh.WinGravity = hints[17]
if nh.WinGravity <= 0 {
nh.WinGravity = xproto.GravityNorthWest
}
return nh, nil
}
// WM_NORMAL_HINTS set
// Make sure to set the flags in the NormalHints struct correctly!
func WmNormalHintsSet(xu *xgbutil.XUtil, win xproto.Window,
nh *NormalHints) error {
raw := []uint{
nh.Flags,
uint(nh.X), uint(nh.Y), nh.Width, nh.Height,
nh.MinWidth, nh.MinHeight,
nh.MaxWidth, nh.MaxHeight,
nh.WidthInc, nh.HeightInc,
nh.MinAspectNum, nh.MinAspectDen,
nh.MaxAspectNum, nh.MaxAspectDen,
nh.BaseWidth, nh.BaseHeight,
nh.WinGravity,
}
return xprop.ChangeProp32(xu, win, "WM_NORMAL_HINTS", "WM_SIZE_HINTS",
raw...)
}
// Hints is a struct that organizes information related to the WM_HINTS
// property. Once again, I refer you to the ICCCM spec for documentation.
type Hints struct {
Flags uint
Input, InitialState uint
IconX, IconY int
IconPixmap, IconMask xproto.Pixmap
WindowGroup, IconWindow xproto.Window
}
// WM_HINTS get
func WmHintsGet(xu *xgbutil.XUtil,
win xproto.Window) (hints *Hints, err error) {
lenExpect := 9
raw, err := xprop.PropValNums(xprop.GetProperty(xu, win, "WM_HINTS"))
if err != nil {
return nil, err
}
if len(raw) != lenExpect {
return nil,
fmt.Errorf("WmHints: There are %d fields in "+
"WM_HINTS, but xgbutil expects %d.", len(raw), lenExpect)
}
hints = &Hints{}
hints.Flags = raw[0]
hints.Input = raw[1]
hints.InitialState = raw[2]
hints.IconPixmap = xproto.Pixmap(raw[3])
hints.IconWindow = xproto.Window(raw[4])
hints.IconX = int(raw[5])
hints.IconY = int(raw[6])
hints.IconMask = xproto.Pixmap(raw[7])
hints.WindowGroup = xproto.Window(raw[8])
return hints, nil
}
// WM_HINTS set
// Make sure to set the flags in the Hints struct correctly!
func WmHintsSet(xu *xgbutil.XUtil, win xproto.Window, hints *Hints) error {
raw := []uint{
hints.Flags, hints.Input, hints.InitialState,
uint(hints.IconPixmap), uint(hints.IconWindow),
uint(hints.IconX), uint(hints.IconY),
uint(hints.IconMask),
uint(hints.WindowGroup),
}
return xprop.ChangeProp32(xu, win, "WM_HINTS", "WM_HINTS", raw...)
}
// WmClass struct contains two data points:
// the instance and a class of a window.
type WmClass struct {
Instance, Class string
}
// WM_CLASS get
func WmClassGet(xu *xgbutil.XUtil, win xproto.Window) (*WmClass, error) {
raw, err := xprop.PropValStrs(xprop.GetProperty(xu, win, "WM_CLASS"))
if err != nil {
return nil, err
}
if len(raw) != 2 {
return nil,
fmt.Errorf("WmClass: Two string make up WM_CLASS, but "+
"xgbutil found %d in '%v'.", len(raw), raw)
}
return &WmClass{
Instance: raw[0],
Class: raw[1],
}, nil
}
// WM_CLASS set
func WmClassSet(xu *xgbutil.XUtil, win xproto.Window, class *WmClass) error {
raw := make([]byte, len(class.Instance)+len(class.Class)+2)
copy(raw, class.Instance)
copy(raw[(len(class.Instance)+1):], class.Class)
return xprop.ChangeProp(xu, win, 8, "WM_CLASS", "STRING", raw)
}
// WM_TRANSIENT_FOR get
func WmTransientForGet(xu *xgbutil.XUtil,
win xproto.Window) (xproto.Window, error) {
return xprop.PropValWindow(xprop.GetProperty(xu, win, "WM_TRANSIENT_FOR"))
}
// WM_TRANSIENT_FOR set
func WmTransientForSet(xu *xgbutil.XUtil, win xproto.Window,
transient xproto.Window) error {
return xprop.ChangeProp32(xu, win, "WM_TRANSIENT_FOR", "WINDOW",
uint(transient))
}
// WM_PROTOCOLS get
func WmProtocolsGet(xu *xgbutil.XUtil, win xproto.Window) ([]string, error) {
raw, err := xprop.GetProperty(xu, win, "WM_PROTOCOLS")
return xprop.PropValAtoms(xu, raw, err)
}
// WM_PROTOCOLS set
func WmProtocolsSet(xu *xgbutil.XUtil, win xproto.Window,
atomNames []string) error {
atoms, err := xprop.StrToAtoms(xu, atomNames)
if err != nil {
return err
}
return xprop.ChangeProp32(xu, win, "WM_PROTOCOLS", "ATOM", atoms...)
}
// WM_COLORMAP_WINDOWS get
func WmColormapWindowsGet(xu *xgbutil.XUtil,
win xproto.Window) ([]xproto.Window, error) {
return xprop.PropValWindows(xprop.GetProperty(xu, win,
"WM_COLORMAP_WINDOWS"))
}
// WM_COLORMAP_WINDOWS set
func WmColormapWindowsSet(xu *xgbutil.XUtil, win xproto.Window,
windows []xproto.Window) error {
return xprop.ChangeProp32(xu, win, "WM_COLORMAP_WINDOWS", "WINDOW",
xprop.WindowToInt(windows)...)
}
// WM_CLIENT_MACHINE get
func WmClientMachineGet(xu *xgbutil.XUtil, win xproto.Window) (string, error) {
return xprop.PropValStr(xprop.GetProperty(xu, win, "WM_CLIENT_MACHINE"))
}
// WM_CLIENT_MACHINE set
func WmClientMachineSet(xu *xgbutil.XUtil, win xproto.Window,
client string) error {
return xprop.ChangeProp(xu, win, 8, "WM_CLIENT_MACHINE", "STRING",
([]byte)(client))
}
// WmState is a struct that organizes information related to the WM_STATE
// property. Namely, the state (corresponding to a State* constant in this file)
// and the icon window (probably not used).
type WmState struct {
State uint
Icon xproto.Window
}
// WM_STATE get
func WmStateGet(xu *xgbutil.XUtil, win xproto.Window) (*WmState, error) {
raw, err := xprop.PropValNums(xprop.GetProperty(xu, win, "WM_STATE"))
if err != nil {
return nil, err
}
if len(raw) != 2 {
return nil,
fmt.Errorf("WmState: Expected two integers in WM_STATE property "+
"but xgbutil found %d in '%v'.", len(raw), raw)
}
return &WmState{
State: raw[0],
Icon: xproto.Window(raw[1]),
}, nil
}
// WM_STATE set
func WmStateSet(xu *xgbutil.XUtil, win xproto.Window, state *WmState) error {
raw := []uint{
state.State,
uint(state.Icon),
}
return xprop.ChangeProp32(xu, win, "WM_STATE", "WM_STATE", raw...)
}
// IconSize is a struct the organizes information related to the WM_ICON_SIZE
// property. Mostly info about its dimensions.
type IconSize struct {
MinWidth, MinHeight, MaxWidth, MaxHeight, WidthInc, HeightInc uint
}
// WM_ICON_SIZE get
func WmIconSizeGet(xu *xgbutil.XUtil, win xproto.Window) (*IconSize, error) {
raw, err := xprop.PropValNums(xprop.GetProperty(xu, win, "WM_ICON_SIZE"))
if err != nil {
return nil, err
}
if len(raw) != 6 {
return nil,
fmt.Errorf("WmIconSize: Expected six integers in WM_ICON_SIZE "+
"property, but xgbutil found "+"%d in '%v'.", len(raw), raw)
}
return &IconSize{
MinWidth: raw[0], MinHeight: raw[1],
MaxWidth: raw[2], MaxHeight: raw[3],
WidthInc: raw[4], HeightInc: raw[5],
}, nil
}
// WM_ICON_SIZE set
func WmIconSizeSet(xu *xgbutil.XUtil, win xproto.Window,
icondim *IconSize) error {
raw := []uint{
icondim.MinWidth, icondim.MinHeight,
icondim.MaxWidth, icondim.MaxHeight,
icondim.WidthInc, icondim.HeightInc,
}
return xprop.ChangeProp32(xu, win, "WM_ICON_SIZE", "WM_ICON_SIZE", raw...)
}

@ -0,0 +1,64 @@
package icccm
import (
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
"github.com/BurntSushi/xgbutil/xevent"
"github.com/BurntSushi/xgbutil/xprop"
)
// IsDeleteProtocol checks whether a ClientMessage event satisfies the
// WM_DELETE_WINDOW protocol. Namely, the format must be 32, the type must
// be the WM_PROTOCOLS atom, and the first data item must be the atom
// WM_DELETE_WINDOW.
//
// Note that if you're using the xwindow package, you should use the
// WMGracefulClose method instead of directly using IsDeleteProtocol.
func IsDeleteProtocol(X *xgbutil.XUtil, ev xevent.ClientMessageEvent) bool {
// Make sure the Format is 32. (Meaning that each data item is
// 32 bits.)
if ev.Format != 32 {
return false
}
// Check to make sure the Type atom is WM_PROTOCOLS.
typeName, err := xprop.AtomName(X, ev.Type)
if err != nil || typeName != "WM_PROTOCOLS" { // not what we want
return false
}
// Check to make sure the first data item is WM_DELETE_WINDOW.
protocolType, err := xprop.AtomName(X,
xproto.Atom(ev.Data.Data32[0]))
if err != nil || protocolType != "WM_DELETE_WINDOW" {
return false
}
return true
}
// IsFocusProtocol checks whether a ClientMessage event satisfies the
// WM_TAKE_FOCUS protocol.
func IsFocusProtocol(X *xgbutil.XUtil, ev xevent.ClientMessageEvent) bool {
// Make sure the Format is 32. (Meaning that each data item is
// 32 bits.)
if ev.Format != 32 {
return false
}
// Check to make sure the Type atom is WM_PROTOCOLS.
typeName, err := xprop.AtomName(X, ev.Type)
if err != nil || typeName != "WM_PROTOCOLS" { // not what we want
return false
}
// Check to make sure the first data item is WM_TAKE_FOCUS.
protocolType, err := xprop.AtomName(X,
xproto.Atom(ev.Data.Data32[0]))
if err != nil || protocolType != "WM_TAKE_FOCUS" {
return false
}
return true
}

@ -0,0 +1 @@
au BufWritePost *.go silent!make tags > /dev/null 2>&1

@ -0,0 +1,167 @@
package xgbutil
/*
types.go contains several types used in the XUtil structure. In an ideal world,
they would be defined in their appropriate packages, but must be defined here
(and exported) for use in some sub-packages. (Namely, xevent, keybind and
mousebind.)
*/
import (
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/xproto"
)
// Callback is an interface that should be implemented by event callback
// functions. Namely, to assign a function to a particular event/window
// combination, simply define a function with type 'SomeEventFun' (pre-defined
// in xevent/callback.go), and call the 'Connect' method.
// The 'Run' method is used inside the Main event loop, and shouldn't be used
// by the user.
// Also, it is perfectly legitimate to connect to events that don't specify
// a window (like MappingNotify and KeymapNotify). In this case, simply
// use 'xgbutil.NoWindow' as the window id.
//
// Example to respond to ConfigureNotify events on window 0x1
//
// xevent.ConfigureNotifyFun(
// func(X *xgbutil.XUtil, e xevent.ConfigureNotifyEvent) {
// fmt.Printf("(%d, %d) %dx%d\n", e.X, e.Y, e.Width, e.Height)
// }).Connect(X, 0x1)
type Callback interface {
// Connect modifies XUtil's state to attach an event handler to a
// particular event.
Connect(xu *XUtil, win xproto.Window)
// Run is exported for use in the xevent package but should not be
// used by the user. (It is used to run the callback function in the
// main event loop.)
Run(xu *XUtil, ev interface{})
}
// CallbackHook works similarly to the more general Callback, but it is
// for hooks into the main xevent loop. As such it does not get attached
// to a window.
type CallbackHook interface {
// Connect connects this hook to the main loop of the passed XUtil
// instance.
Connect(xu *XUtil)
// Run is exported for use in the xevent package, but should not be
// used by the user. It should return true if it's ok to process
// the event as usual, or false if it should be suppressed.
Run(xu *XUtil, ev interface{}) bool
}
// CallbackKey works similarly to the more general Callback, but it adds
// parameters specific to key bindings.
type CallbackKey interface {
// Connect modifies XUtil's state to attach an event handler to a
// particular key press. If grab is true, connect will request a passive
// grab.
Connect(xu *XUtil, win xproto.Window, keyStr string, grab bool) error
// Run is exported for use in the keybind package but should not be
// used by the user. (It is used to run the callback function in the
// main event loop.
Run(xu *XUtil, ev interface{})
}
// CallbackMouse works similarly to the more general Callback, but it adds
// parameters specific to mouse bindings.
type CallbackMouse interface {
// Connect modifies XUtil's state to attach an event handler to a
// particular button press.
// If sync is true, the grab will be synchronous. (This will require a
// call to xproto.AllowEvents in response, otherwise no further events
// will be processed and your program will lock.)
// If grab is true, connect will request a passive grab.
Connect(xu *XUtil, win xproto.Window, buttonStr string,
sync bool, grab bool) error
// Run is exported for use in the mousebind package but should not be
// used by the user. (It is used to run the callback function in the
// main event loop.)
Run(xu *XUtil, ev interface{})
}
// KeyKey is the type of the key in the map of keybindings.
// It essentially represents the tuple
// (event type, window id, modifier, keycode).
// It is exported for use in the keybind package. It should not be used.
type KeyKey struct {
Evtype int
Win xproto.Window
Mod uint16
Code xproto.Keycode
}
// KeyString is the type of a key binding string used to connect to particular
// key combinations. A list of all such key strings is maintained in order to
// rebind keys when the keyboard mapping has been changed.
type KeyString struct {
Str string
Callback CallbackKey
Evtype int
Win xproto.Window
Grab bool
}
// MouseKey is the type of the key in the map of mouse bindings.
// It essentially represents the tuple
// (event type, window id, modifier, button).
// It is exported for use in the mousebind package. It should not be used.
type MouseKey struct {
Evtype int
Win xproto.Window
Mod uint16
Button xproto.Button
}
// KeyboardMapping embeds a keyboard mapping reply from XGB.
// It should be retrieved using keybind.KeyMapGet, if necessary.
// xgbutil tries quite hard to absolve you from ever having to use this.
// A keyboard mapping is a table that maps keycodes to one or more keysyms.
type KeyboardMapping struct {
*xproto.GetKeyboardMappingReply
}
// ModifierMapping embeds a modifier mapping reply from XGB.
// It should be retrieved using keybind.ModMapGet, if necessary.
// xgbutil tries quite hard to absolve you from ever having to use this.
// A modifier mapping is a table that maps modifiers to one or more keycodes.
type ModifierMapping struct {
*xproto.GetModifierMappingReply
}
// ErrorHandlerFun is the type of function required to handle errors that
// come in through the main event loop.
// For example, to set a new error handler, use:
//
// xevent.ErrorHandlerSet(xgbutil.ErrorHandlerFun(
// func(err xgb.Error) {
// // do something with err
// }))
type ErrorHandlerFun func(err xgb.Error)
// EventOrError is a struct that contains either an event value or an error
// value. It is an error to contain both. Containing neither indicates an
// error too.
// This is exported for use in the xevent package. You shouldn't have any
// direct contact with values of this type, unless you need to inspect the
// queue directly with xevent.Peek.
type EventOrError struct {
Event xgb.Event
Err xgb.Error
}
// MouseDragFun is the kind of function used on each dragging step
// and at the end of a drag.
type MouseDragFun func(xu *XUtil, rootX, rootY, eventX, eventY int)
// MouseDragBeginFun is the kind of function used to initialize a drag.
// The difference between this and MouseDragFun is that the begin function
// returns a bool (of whether or not to cancel the drag) and an X resource
// identifier corresponding to a cursor.
type MouseDragBeginFun func(xu *XUtil, rootX, rootY,
eventX, eventY int) (bool, xproto.Cursor)

@ -0,0 +1,389 @@
package xevent
/*
Does all the plumbing to allow a simple callback interface for users.
This file is automatically generated using `scripts/write-events callbacks`.
Edit it at your peril.
*/
import (
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
)
type KeyPressFun func(xu *xgbutil.XUtil, event KeyPressEvent)
func (callback KeyPressFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, KeyPress, win, callback)
}
func (callback KeyPressFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(KeyPressEvent))
}
type KeyReleaseFun func(xu *xgbutil.XUtil, event KeyReleaseEvent)
func (callback KeyReleaseFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, KeyRelease, win, callback)
}
func (callback KeyReleaseFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(KeyReleaseEvent))
}
type ButtonPressFun func(xu *xgbutil.XUtil, event ButtonPressEvent)
func (callback ButtonPressFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, ButtonPress, win, callback)
}
func (callback ButtonPressFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ButtonPressEvent))
}
type ButtonReleaseFun func(xu *xgbutil.XUtil, event ButtonReleaseEvent)
func (callback ButtonReleaseFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, ButtonRelease, win, callback)
}
func (callback ButtonReleaseFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ButtonReleaseEvent))
}
type MotionNotifyFun func(xu *xgbutil.XUtil, event MotionNotifyEvent)
func (callback MotionNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, MotionNotify, win, callback)
}
func (callback MotionNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(MotionNotifyEvent))
}
type EnterNotifyFun func(xu *xgbutil.XUtil, event EnterNotifyEvent)
func (callback EnterNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, EnterNotify, win, callback)
}
func (callback EnterNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(EnterNotifyEvent))
}
type LeaveNotifyFun func(xu *xgbutil.XUtil, event LeaveNotifyEvent)
func (callback LeaveNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, LeaveNotify, win, callback)
}
func (callback LeaveNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(LeaveNotifyEvent))
}
type FocusInFun func(xu *xgbutil.XUtil, event FocusInEvent)
func (callback FocusInFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, FocusIn, win, callback)
}
func (callback FocusInFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(FocusInEvent))
}
type FocusOutFun func(xu *xgbutil.XUtil, event FocusOutEvent)
func (callback FocusOutFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, FocusOut, win, callback)
}
func (callback FocusOutFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(FocusOutEvent))
}
type KeymapNotifyFun func(xu *xgbutil.XUtil, event KeymapNotifyEvent)
func (callback KeymapNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, KeymapNotify, win, callback)
}
func (callback KeymapNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(KeymapNotifyEvent))
}
type ExposeFun func(xu *xgbutil.XUtil, event ExposeEvent)
func (callback ExposeFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, Expose, win, callback)
}
func (callback ExposeFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ExposeEvent))
}
type GraphicsExposureFun func(xu *xgbutil.XUtil, event GraphicsExposureEvent)
func (callback GraphicsExposureFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, GraphicsExposure, win, callback)
}
func (callback GraphicsExposureFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(GraphicsExposureEvent))
}
type NoExposureFun func(xu *xgbutil.XUtil, event NoExposureEvent)
func (callback NoExposureFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, NoExposure, win, callback)
}
func (callback NoExposureFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(NoExposureEvent))
}
type VisibilityNotifyFun func(xu *xgbutil.XUtil, event VisibilityNotifyEvent)
func (callback VisibilityNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, VisibilityNotify, win, callback)
}
func (callback VisibilityNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(VisibilityNotifyEvent))
}
type CreateNotifyFun func(xu *xgbutil.XUtil, event CreateNotifyEvent)
func (callback CreateNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, CreateNotify, win, callback)
}
func (callback CreateNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(CreateNotifyEvent))
}
type DestroyNotifyFun func(xu *xgbutil.XUtil, event DestroyNotifyEvent)
func (callback DestroyNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, DestroyNotify, win, callback)
}
func (callback DestroyNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(DestroyNotifyEvent))
}
type UnmapNotifyFun func(xu *xgbutil.XUtil, event UnmapNotifyEvent)
func (callback UnmapNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, UnmapNotify, win, callback)
}
func (callback UnmapNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(UnmapNotifyEvent))
}
type MapNotifyFun func(xu *xgbutil.XUtil, event MapNotifyEvent)
func (callback MapNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, MapNotify, win, callback)
}
func (callback MapNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(MapNotifyEvent))
}
type MapRequestFun func(xu *xgbutil.XUtil, event MapRequestEvent)
func (callback MapRequestFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, MapRequest, win, callback)
}
func (callback MapRequestFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(MapRequestEvent))
}
type ReparentNotifyFun func(xu *xgbutil.XUtil, event ReparentNotifyEvent)
func (callback ReparentNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, ReparentNotify, win, callback)
}
func (callback ReparentNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ReparentNotifyEvent))
}
type ConfigureNotifyFun func(xu *xgbutil.XUtil, event ConfigureNotifyEvent)
func (callback ConfigureNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, ConfigureNotify, win, callback)
}
func (callback ConfigureNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ConfigureNotifyEvent))
}
type ConfigureRequestFun func(xu *xgbutil.XUtil, event ConfigureRequestEvent)
func (callback ConfigureRequestFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, ConfigureRequest, win, callback)
}
func (callback ConfigureRequestFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ConfigureRequestEvent))
}
type GravityNotifyFun func(xu *xgbutil.XUtil, event GravityNotifyEvent)
func (callback GravityNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, GravityNotify, win, callback)
}
func (callback GravityNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(GravityNotifyEvent))
}
type ResizeRequestFun func(xu *xgbutil.XUtil, event ResizeRequestEvent)
func (callback ResizeRequestFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, ResizeRequest, win, callback)
}
func (callback ResizeRequestFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ResizeRequestEvent))
}
type CirculateNotifyFun func(xu *xgbutil.XUtil, event CirculateNotifyEvent)
func (callback CirculateNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, CirculateNotify, win, callback)
}
func (callback CirculateNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(CirculateNotifyEvent))
}
type CirculateRequestFun func(xu *xgbutil.XUtil, event CirculateRequestEvent)
func (callback CirculateRequestFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, CirculateRequest, win, callback)
}
func (callback CirculateRequestFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(CirculateRequestEvent))
}
type PropertyNotifyFun func(xu *xgbutil.XUtil, event PropertyNotifyEvent)
func (callback PropertyNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, PropertyNotify, win, callback)
}
func (callback PropertyNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(PropertyNotifyEvent))
}
type SelectionClearFun func(xu *xgbutil.XUtil, event SelectionClearEvent)
func (callback SelectionClearFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, SelectionClear, win, callback)
}
func (callback SelectionClearFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(SelectionClearEvent))
}
type SelectionRequestFun func(xu *xgbutil.XUtil, event SelectionRequestEvent)
func (callback SelectionRequestFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, SelectionRequest, win, callback)
}
func (callback SelectionRequestFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(SelectionRequestEvent))
}
type SelectionNotifyFun func(xu *xgbutil.XUtil, event SelectionNotifyEvent)
func (callback SelectionNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, SelectionNotify, win, callback)
}
func (callback SelectionNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(SelectionNotifyEvent))
}
type ColormapNotifyFun func(xu *xgbutil.XUtil, event ColormapNotifyEvent)
func (callback ColormapNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, ColormapNotify, win, callback)
}
func (callback ColormapNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ColormapNotifyEvent))
}
type ClientMessageFun func(xu *xgbutil.XUtil, event ClientMessageEvent)
func (callback ClientMessageFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, ClientMessage, win, callback)
}
func (callback ClientMessageFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ClientMessageEvent))
}
type MappingNotifyFun func(xu *xgbutil.XUtil, event MappingNotifyEvent)
func (callback MappingNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, MappingNotify, win, callback)
}
func (callback MappingNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(MappingNotifyEvent))
}
type ShapeNotifyFun func(xu *xgbutil.XUtil, event ShapeNotifyEvent)
func (callback ShapeNotifyFun) Connect(xu *xgbutil.XUtil,
win xproto.Window) {
attachCallback(xu, ShapeNotify, win, callback)
}
func (callback ShapeNotifyFun) Run(xu *xgbutil.XUtil, event interface{}) {
callback(xu, event.(ShapeNotifyEvent))
}

@ -0,0 +1,76 @@
/*
Package xevent provides an event handler interface for attaching callback
functions to X events, and an implementation of an X event loop.
The X event loop
One of the biggest conveniences offered by xgbutil is its event handler system.
That is, the ability to attach an arbitrary callback function to any X event.
In order for such things to work, xgbutil needs to control the main X event
loop and act as a dispatcher for all event handlers created by you.
To run the X event loop, use xevent.Main or xevent.MainPing. The former
runs a normal event loop in the current goroutine and processes events. The
latter runs the event loop in a new goroutine and returns a pingBefore and
a pingAfter channel. The pingBefore channel is sent a benign value right before
an event is dequeued, and the pingAfter channel is sent a benign value right
after after all callbacks for that event have finished execution. These
synchronization points in the main event loop can be combined with a 'select'
statement to process data from other input sources. An example of this is given
in the documentation for the MainPing function. A complete example called
multiple-source-event-loop can also be found in the examples directory of the
xgbutil package.
To quit the main event loop, you may use xevent.Quit, but there is nothing
inherently wrong with stopping dead using os.Exit. xevent.Quit is provided for
your convenience should you need to run any clean-up code after the main event
loop returns.
The X event queue
xgbutil's event queue contains values that are either events or errors. (Never
both and never neither.) Namely, errors are received in the event loop from
unchecked requests. (Errors generated by checked requests are guaranteed to be
returned to the caller and are never received in the event loop.) Also, a
default error handler function can be set with xevent.ErrorHandlerSet.
To this end, xgbutil's event queue can be inspected. This is advantageous when
information about what events will be processed in the future could be helpful
(i.e., if there is an UnmapNotify event waiting to be processed.) The event
queue can also be manipulated to facilitate event compression. (Two events that
are common candidates for compression are ConfigureNotify and MotionNotify.)
Detach events
Whenever a window can no longer receive events (i.e., when it is destroyed),
all event handlers related to that window should be detached. (If this is
omitted, then Go's garbage collector will not be able to reuse memory occupied
by the now-unused event handlers for that window.) Moreover, its possible that
a window id can be reused after it has been discarded, which could result in
odd behavior in your application.
To detach a window from all event handlers in the xevent package, use
xevent.Detach. If you're also using the keybind and mousebind packages, you'll
need to call keybind.Detach and mousebind.Detach too. So to detach your window
from all possible event handlers in xgbutil, use something like:
xevent.Detach(XUtilValue, your-window-id)
keybind.Detach(XUtilValue, your-window-id)
mousebind.Detach(XUtilValue, your-window-id)
Quick example
A small example that shows how to respond to ConfigureNotify events sent to
your-window-id.
xevent.ConfigureNotifyFun(
func(X *xgbutil.XUtil, e xevent.ConfigureNotifyEvent) {
fmt.Printf("(%d, %d) %dx%d\n", e.X, e.Y, e.Width, e.Height)
}).Connect(XUtilValue, your-window-id)
More examples
The xevent package is used in several of the examples in the examples directory
in the xgbutil package.
*/
package xevent

@ -0,0 +1,291 @@
package xevent
/*
xevent/eventloop.go contains code that implements a main X event loop.
Namely, it provides facilities to read new events into xevent's event queue,
run a normal main event loop and run a main event loop that pings a channel
each time an event is about to be dequeued. The latter facility allows one to
easily include other input sources for processing in a program's main event
loop.
*/
import (
"github.com/BurntSushi/xgb/shape"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
)
// Read reads one or more events and queues them in XUtil.
// If 'block' is True, then call 'WaitForEvent' before sucking up
// all events that have been queued by XGB.
func Read(xu *xgbutil.XUtil, block bool) {
if block {
ev, err := xu.Conn().WaitForEvent()
if ev == nil && err == nil {
xgbutil.Logger.Fatal("BUG: Could not read an event or an error.")
}
Enqueue(xu, ev, err)
}
// Clean up anything that's in the queue
for {
ev, err := xu.Conn().PollForEvent()
// No events left...
if ev == nil && err == nil {
break
}
// We're good, queue it up
Enqueue(xu, ev, err)
}
}
// Main starts the main X event loop. It will read events and call appropriate
// callback functions.
// N.B. If you have multiple X connections in the same program, you should be
// able to run this in different goroutines concurrently. However, only
// *one* of these should run for *each* connection.
func Main(xu *xgbutil.XUtil) {
mainEventLoop(xu, nil, nil, nil)
}
// MainPing starts the main X event loop, and returns three "ping" channels:
// the first is pinged before an event is dequeued, the second is pinged
// after all callbacks for a particular event have been called and the last
// is pinged when the event loop stops (e.g., after a call to xevent.Quit).
// pingAfter channel.
//
// This is useful if your event loop needs to draw from other sources. e.g.,
//
// pingBefore, pingAfter, pingQuit := xevent.MainPing()
// for {
// select {
// case <-pingBefore:
// // Wait for event processing to finish.
// <-pingAfter
// case val <- someOtherChannel:
// // do some work with val
// case <-pingQuit:
// fmt.Printf("xevent loop has quit")
// return
// }
// }
//
// Note that an unbuffered channel is returned, which implies that any work
// done with 'val' will delay further X event processing.
//
// A complete example using MainPing can be found in the examples directory in
// the xgbutil package under the name multiple-source-event-loop.
func MainPing(xu *xgbutil.XUtil) (chan struct{}, chan struct{}, chan struct{}) {
pingBefore := make(chan struct{}, 0)
pingAfter := make(chan struct{}, 0)
pingQuit := make(chan struct{}, 0)
go func() {
mainEventLoop(xu, pingBefore, pingAfter, pingQuit)
}()
return pingBefore, pingAfter, pingQuit
}
// mainEventLoop runs the main event loop with an optional ping channel.
func mainEventLoop(xu *xgbutil.XUtil,
pingBefore, pingAfter, pingQuit chan struct{}) {
for {
if Quitting(xu) {
if pingQuit != nil {
pingQuit <- struct{}{}
}
break
}
// Gobble up as many events as possible (into the queue).
// If there are no events, we block.
Read(xu, true)
// Now process every event/error in the queue.
processEventQueue(xu, pingBefore, pingAfter)
}
}
// processEventQueue processes every item in the event/error queue.
func processEventQueue(xu *xgbutil.XUtil, pingBefore, pingAfter chan struct{}) {
for !Empty(xu) {
if Quitting(xu) {
return
}
// We send the ping *before* the next event is dequeued.
// This is so the queue doesn't present a misrepresentation of which
// events haven't been processed yet.
if pingBefore != nil && pingAfter != nil {
pingBefore <- struct{}{}
}
ev, err := Dequeue(xu)
// If we gobbled up an error, send it to the error event handler
// and move on the next event/error.
if err != nil {
ErrorHandlerGet(xu)(err)
if pingBefore != nil && pingAfter != nil {
pingAfter <- struct{}{}
}
continue
}
// We know there isn't an error. If there isn't an event either,
// then there's a bug somewhere.
if ev == nil {
xgbutil.Logger.Fatal("BUG: Expected an event but got nil.")
}
hooks := getHooks(xu)
for _, hook := range hooks {
if !hook.Run(xu, ev) {
goto END
}
}
switch event := ev.(type) {
case xproto.KeyPressEvent:
e := KeyPressEvent{&event}
// If we're redirecting key events, this is the place to do it!
if wid := RedirectKeyGet(xu); wid > 0 {
e.Event = wid
}
xu.TimeSet(e.Time)
runCallbacks(xu, e, KeyPress, e.Event)
case xproto.KeyReleaseEvent:
e := KeyReleaseEvent{&event}
// If we're redirecting key events, this is the place to do it!
if wid := RedirectKeyGet(xu); wid > 0 {
e.Event = wid
}
xu.TimeSet(e.Time)
runCallbacks(xu, e, KeyRelease, e.Event)
case xproto.ButtonPressEvent:
e := ButtonPressEvent{&event}
xu.TimeSet(e.Time)
runCallbacks(xu, e, ButtonPress, e.Event)
case xproto.ButtonReleaseEvent:
e := ButtonReleaseEvent{&event}
xu.TimeSet(e.Time)
runCallbacks(xu, e, ButtonRelease, e.Event)
case xproto.MotionNotifyEvent:
e := MotionNotifyEvent{&event}
xu.TimeSet(e.Time)
runCallbacks(xu, e, MotionNotify, e.Event)
case xproto.EnterNotifyEvent:
e := EnterNotifyEvent{&event}
xu.TimeSet(e.Time)
runCallbacks(xu, e, EnterNotify, e.Event)
case xproto.LeaveNotifyEvent:
e := LeaveNotifyEvent{&event}
xu.TimeSet(e.Time)
runCallbacks(xu, e, LeaveNotify, e.Event)
case xproto.FocusInEvent:
e := FocusInEvent{&event}
runCallbacks(xu, e, FocusIn, e.Event)
case xproto.FocusOutEvent:
e := FocusOutEvent{&event}
runCallbacks(xu, e, FocusOut, e.Event)
case xproto.KeymapNotifyEvent:
e := KeymapNotifyEvent{&event}
runCallbacks(xu, e, KeymapNotify, NoWindow)
case xproto.ExposeEvent:
e := ExposeEvent{&event}
runCallbacks(xu, e, Expose, e.Window)
case xproto.GraphicsExposureEvent:
e := GraphicsExposureEvent{&event}
runCallbacks(xu, e, GraphicsExposure, xproto.Window(e.Drawable))
case xproto.NoExposureEvent:
e := NoExposureEvent{&event}
runCallbacks(xu, e, NoExposure, xproto.Window(e.Drawable))
case xproto.VisibilityNotifyEvent:
e := VisibilityNotifyEvent{&event}
runCallbacks(xu, e, VisibilityNotify, e.Window)
case xproto.CreateNotifyEvent:
e := CreateNotifyEvent{&event}
runCallbacks(xu, e, CreateNotify, e.Parent)
case xproto.DestroyNotifyEvent:
e := DestroyNotifyEvent{&event}
runCallbacks(xu, e, DestroyNotify, e.Window)
case xproto.UnmapNotifyEvent:
e := UnmapNotifyEvent{&event}
runCallbacks(xu, e, UnmapNotify, e.Window)
case xproto.MapNotifyEvent:
e := MapNotifyEvent{&event}
runCallbacks(xu, e, MapNotify, e.Event)
case xproto.MapRequestEvent:
e := MapRequestEvent{&event}
runCallbacks(xu, e, MapRequest, e.Window)
runCallbacks(xu, e, MapRequest, e.Parent)
case xproto.ReparentNotifyEvent:
e := ReparentNotifyEvent{&event}
runCallbacks(xu, e, ReparentNotify, e.Window)
case xproto.ConfigureNotifyEvent:
e := ConfigureNotifyEvent{&event}
runCallbacks(xu, e, ConfigureNotify, e.Window)
case xproto.ConfigureRequestEvent:
e := ConfigureRequestEvent{&event}
runCallbacks(xu, e, ConfigureRequest, e.Window)
runCallbacks(xu, e, ConfigureRequest, e.Parent)
case xproto.GravityNotifyEvent:
e := GravityNotifyEvent{&event}
runCallbacks(xu, e, GravityNotify, e.Window)
case xproto.ResizeRequestEvent:
e := ResizeRequestEvent{&event}
runCallbacks(xu, e, ResizeRequest, e.Window)
case xproto.CirculateNotifyEvent:
e := CirculateNotifyEvent{&event}
runCallbacks(xu, e, CirculateNotify, e.Window)
case xproto.CirculateRequestEvent:
e := CirculateRequestEvent{&event}
runCallbacks(xu, e, CirculateRequest, e.Window)
case xproto.PropertyNotifyEvent:
e := PropertyNotifyEvent{&event}
xu.TimeSet(e.Time)
runCallbacks(xu, e, PropertyNotify, e.Window)
case xproto.SelectionClearEvent:
e := SelectionClearEvent{&event}
xu.TimeSet(e.Time)
runCallbacks(xu, e, SelectionClear, e.Owner)
case xproto.SelectionRequestEvent:
e := SelectionRequestEvent{&event}
xu.TimeSet(e.Time)
runCallbacks(xu, e, SelectionRequest, e.Requestor)
case xproto.SelectionNotifyEvent:
e := SelectionNotifyEvent{&event}
xu.TimeSet(e.Time)
runCallbacks(xu, e, SelectionNotify, e.Requestor)
case xproto.ColormapNotifyEvent:
e := ColormapNotifyEvent{&event}
runCallbacks(xu, e, ColormapNotify, e.Window)
case xproto.ClientMessageEvent:
e := ClientMessageEvent{&event}
runCallbacks(xu, e, ClientMessage, e.Window)
case xproto.MappingNotifyEvent:
e := MappingNotifyEvent{&event}
runCallbacks(xu, e, MappingNotify, NoWindow)
case shape.NotifyEvent:
e := ShapeNotifyEvent{&event}
runCallbacks(xu, e, ShapeNotify, e.AffectedWindow)
default:
if event != nil {
xgbutil.Logger.Printf("ERROR: UNSUPPORTED EVENT TYPE: %T",
event)
}
}
END:
if pingBefore != nil && pingAfter != nil {
pingAfter <- struct{}{}
}
}
}

@ -0,0 +1,336 @@
package xevent
/*
Defines event types and their associated methods automatically.
This file is automatically generated using `scripts/write-events evtypes`.
Edit it at your peril.
*/
import (
"fmt"
"github.com/BurntSushi/xgb/shape"
"github.com/BurntSushi/xgb/xproto"
)
type KeyPressEvent struct {
*xproto.KeyPressEvent
}
const KeyPress = xproto.KeyPress
func (ev KeyPressEvent) String() string {
return fmt.Sprintf("%v", ev.KeyPressEvent)
}
type KeyReleaseEvent struct {
*xproto.KeyReleaseEvent
}
const KeyRelease = xproto.KeyRelease
func (ev KeyReleaseEvent) String() string {
return fmt.Sprintf("%v", ev.KeyReleaseEvent)
}
type ButtonPressEvent struct {
*xproto.ButtonPressEvent
}
const ButtonPress = xproto.ButtonPress
func (ev ButtonPressEvent) String() string {
return fmt.Sprintf("%v", ev.ButtonPressEvent)
}
type ButtonReleaseEvent struct {
*xproto.ButtonReleaseEvent
}
const ButtonRelease = xproto.ButtonRelease
func (ev ButtonReleaseEvent) String() string {
return fmt.Sprintf("%v", ev.ButtonReleaseEvent)
}
type MotionNotifyEvent struct {
*xproto.MotionNotifyEvent
}
const MotionNotify = xproto.MotionNotify
func (ev MotionNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.MotionNotifyEvent)
}
type EnterNotifyEvent struct {
*xproto.EnterNotifyEvent
}
const EnterNotify = xproto.EnterNotify
func (ev EnterNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.EnterNotifyEvent)
}
type LeaveNotifyEvent struct {
*xproto.LeaveNotifyEvent
}
const LeaveNotify = xproto.LeaveNotify
func (ev LeaveNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.LeaveNotifyEvent)
}
type FocusInEvent struct {
*xproto.FocusInEvent
}
const FocusIn = xproto.FocusIn
func (ev FocusInEvent) String() string {
return fmt.Sprintf("%v", ev.FocusInEvent)
}
type FocusOutEvent struct {
*xproto.FocusOutEvent
}
const FocusOut = xproto.FocusOut
func (ev FocusOutEvent) String() string {
return fmt.Sprintf("%v", ev.FocusOutEvent)
}
type KeymapNotifyEvent struct {
*xproto.KeymapNotifyEvent
}
const KeymapNotify = xproto.KeymapNotify
func (ev KeymapNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.KeymapNotifyEvent)
}
type ExposeEvent struct {
*xproto.ExposeEvent
}
const Expose = xproto.Expose
func (ev ExposeEvent) String() string {
return fmt.Sprintf("%v", ev.ExposeEvent)
}
type GraphicsExposureEvent struct {
*xproto.GraphicsExposureEvent
}
const GraphicsExposure = xproto.GraphicsExposure
func (ev GraphicsExposureEvent) String() string {
return fmt.Sprintf("%v", ev.GraphicsExposureEvent)
}
type NoExposureEvent struct {
*xproto.NoExposureEvent
}
const NoExposure = xproto.NoExposure
func (ev NoExposureEvent) String() string {
return fmt.Sprintf("%v", ev.NoExposureEvent)
}
type VisibilityNotifyEvent struct {
*xproto.VisibilityNotifyEvent
}
const VisibilityNotify = xproto.VisibilityNotify
func (ev VisibilityNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.VisibilityNotifyEvent)
}
type CreateNotifyEvent struct {
*xproto.CreateNotifyEvent
}
const CreateNotify = xproto.CreateNotify
func (ev CreateNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.CreateNotifyEvent)
}
type DestroyNotifyEvent struct {
*xproto.DestroyNotifyEvent
}
const DestroyNotify = xproto.DestroyNotify
func (ev DestroyNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.DestroyNotifyEvent)
}
type UnmapNotifyEvent struct {
*xproto.UnmapNotifyEvent
}
const UnmapNotify = xproto.UnmapNotify
func (ev UnmapNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.UnmapNotifyEvent)
}
type MapNotifyEvent struct {
*xproto.MapNotifyEvent
}
const MapNotify = xproto.MapNotify
func (ev MapNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.MapNotifyEvent)
}
type MapRequestEvent struct {
*xproto.MapRequestEvent
}
const MapRequest = xproto.MapRequest
func (ev MapRequestEvent) String() string {
return fmt.Sprintf("%v", ev.MapRequestEvent)
}
type ReparentNotifyEvent struct {
*xproto.ReparentNotifyEvent
}
const ReparentNotify = xproto.ReparentNotify
func (ev ReparentNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.ReparentNotifyEvent)
}
type ConfigureRequestEvent struct {
*xproto.ConfigureRequestEvent
}
const ConfigureRequest = xproto.ConfigureRequest
func (ev ConfigureRequestEvent) String() string {
return fmt.Sprintf("%v", ev.ConfigureRequestEvent)
}
type GravityNotifyEvent struct {
*xproto.GravityNotifyEvent
}
const GravityNotify = xproto.GravityNotify
func (ev GravityNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.GravityNotifyEvent)
}
type ResizeRequestEvent struct {
*xproto.ResizeRequestEvent
}
const ResizeRequest = xproto.ResizeRequest
func (ev ResizeRequestEvent) String() string {
return fmt.Sprintf("%v", ev.ResizeRequestEvent)
}
type CirculateNotifyEvent struct {
*xproto.CirculateNotifyEvent
}
const CirculateNotify = xproto.CirculateNotify
func (ev CirculateNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.CirculateNotifyEvent)
}
type CirculateRequestEvent struct {
*xproto.CirculateRequestEvent
}
const CirculateRequest = xproto.CirculateRequest
func (ev CirculateRequestEvent) String() string {
return fmt.Sprintf("%v", ev.CirculateRequestEvent)
}
type PropertyNotifyEvent struct {
*xproto.PropertyNotifyEvent
}
const PropertyNotify = xproto.PropertyNotify
func (ev PropertyNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.PropertyNotifyEvent)
}
type SelectionClearEvent struct {
*xproto.SelectionClearEvent
}
const SelectionClear = xproto.SelectionClear
func (ev SelectionClearEvent) String() string {
return fmt.Sprintf("%v", ev.SelectionClearEvent)
}
type SelectionRequestEvent struct {
*xproto.SelectionRequestEvent
}
const SelectionRequest = xproto.SelectionRequest
func (ev SelectionRequestEvent) String() string {
return fmt.Sprintf("%v", ev.SelectionRequestEvent)
}
type SelectionNotifyEvent struct {
*xproto.SelectionNotifyEvent
}
const SelectionNotify = xproto.SelectionNotify
func (ev SelectionNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.SelectionNotifyEvent)
}
type ColormapNotifyEvent struct {
*xproto.ColormapNotifyEvent
}
const ColormapNotify = xproto.ColormapNotify
func (ev ColormapNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.ColormapNotifyEvent)
}
type MappingNotifyEvent struct {
*xproto.MappingNotifyEvent
}
const MappingNotify = xproto.MappingNotify
func (ev MappingNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.MappingNotifyEvent)
}
type ShapeNotifyEvent struct {
*shape.NotifyEvent
}
const ShapeNotify = shape.Notify
func (ev ShapeNotifyEvent) String() string {
return fmt.Sprintf("%v", ev.NotifyEvent)
}

@ -0,0 +1,90 @@
package xevent
import (
"fmt"
"github.com/BurntSushi/xgb/xproto"
)
// ClientMessageEvent embeds the struct by the same name from the xgb library.
type ClientMessageEvent struct {
*xproto.ClientMessageEvent
}
const ClientMessage = xproto.ClientMessage
// NewClientMessage takes all arguments required to build a ClientMessageEvent
// struct and hides the messy details.
// The variadic parameters coincide with the "data" part of a client message.
// The type of the variadic parameters depends upon the value of Format.
// If Format is 8, 'data' should have type byte.
// If Format is 16, 'data' should have type int16.
// If Format is 32, 'data' should have type int.
// Any other value of Format returns an error.
func NewClientMessage(Format byte, Window xproto.Window, Type xproto.Atom,
data ...interface{}) (*ClientMessageEvent, error) {
// Create the client data list first
var clientData xproto.ClientMessageDataUnion
// Don't support formats 8 or 16 yet. They aren't used in EWMH anyway.
switch Format {
case 8:
buf := make([]byte, 20)
for i := 0; i < 20; i++ {
if i >= len(data) {
break
}
buf[i] = data[i].(byte)
}
clientData = xproto.ClientMessageDataUnionData8New(buf)
case 16:
buf := make([]uint16, 10)
for i := 0; i < 10; i++ {
if i >= len(data) {
break
}
buf[i] = uint16(data[i].(int16))
}
clientData = xproto.ClientMessageDataUnionData16New(buf)
case 32:
buf := make([]uint32, 5)
for i := 0; i < 5; i++ {
if i >= len(data) {
break
}
buf[i] = uint32(data[i].(int))
}
clientData = xproto.ClientMessageDataUnionData32New(buf)
default:
return nil, fmt.Errorf("NewClientMessage: Unsupported format '%d'.",
Format)
}
return &ClientMessageEvent{&xproto.ClientMessageEvent{
Format: Format,
Window: Window,
Type: Type,
Data: clientData,
}}, nil
}
// ConfigureNotifyEvent embeds the struct by the same name in XGB.
type ConfigureNotifyEvent struct {
*xproto.ConfigureNotifyEvent
}
const ConfigureNotify = xproto.ConfigureNotify
// NewConfigureNotify takes all arguments required to build a
// ConfigureNotifyEvent struct and returns a ConfigureNotifyEvent value.
func NewConfigureNotify(Event, Window, AboveSibling xproto.Window,
X, Y, Width, Height int, BorderWidth uint16,
OverrideRedirect bool) *ConfigureNotifyEvent {
return &ConfigureNotifyEvent{&xproto.ConfigureNotifyEvent{
Event: Event, Window: Window, AboveSibling: AboveSibling,
X: int16(X), Y: int16(Y), Width: uint16(Width), Height: uint16(Height),
BorderWidth: BorderWidth, OverrideRedirect: OverrideRedirect,
}}
}

@ -0,0 +1,237 @@
package xevent
import (
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
)
// Sometimes we need to specify NO WINDOW when a window is typically
// expected. (Like connecting to MappingNotify or KeymapNotify events.)
// Use this value to do that.
var NoWindow xproto.Window = 0
// IgnoreMods is a list of X modifiers that we don't want interfering
// with our mouse or key bindings. In particular, for each mouse or key binding
// issued, there is a seperate mouse or key binding made for each of the
// modifiers specified.
//
// You may modify this slice to add (or remove) modifiers, but it should be
// done before *any* key or mouse bindings are attached with the keybind and
// mousebind packages. It should not be modified afterwards.
//
// TODO: We're assuming numlock is in the 'mod2' modifier, which is a pretty
// common setup, but by no means guaranteed. This should be modified to actually
// inspect the modifiers table and look for the special Num_Lock keysym.
var IgnoreMods []uint16 = []uint16{
0,
xproto.ModMaskLock, // Caps lock
xproto.ModMask2, // Num lock
xproto.ModMaskLock | xproto.ModMask2, // Caps and Num lock
}
// Enqueue queues up an event read from X.
// Note that an event read may return an error, in which case, this queue
// entry will be an error and not an event.
//
// ev, err := XUtilValue.Conn().WaitForEvent()
// xevent.Enqueue(XUtilValue, ev, err)
//
// You probably shouldn't have to enqueue events yourself. This is done
// automatically if you're using xevent.Main{Ping} and/or xevent.Read.
func Enqueue(xu *xgbutil.XUtil, ev xgb.Event, err xgb.Error) {
xu.EvqueueLck.Lock()
defer xu.EvqueueLck.Unlock()
xu.Evqueue = append(xu.Evqueue, xgbutil.EventOrError{
Event: ev,
Err: err,
})
}
// Dequeue pops an event/error from the queue and returns it.
// The queue item is unwrapped and returned as multiple return values.
// Only one of the return values can be nil.
func Dequeue(xu *xgbutil.XUtil) (xgb.Event, xgb.Error) {
xu.EvqueueLck.Lock()
defer xu.EvqueueLck.Unlock()
everr := xu.Evqueue[0]
xu.Evqueue = xu.Evqueue[1:]
return everr.Event, everr.Err
}
// DequeueAt removes a particular item from the queue.
// This is primarily useful when attempting to compress events.
func DequeueAt(xu *xgbutil.XUtil, i int) {
xu.EvqueueLck.Lock()
defer xu.EvqueueLck.Unlock()
xu.Evqueue = append(xu.Evqueue[:i], xu.Evqueue[i+1:]...)
}
// Empty returns whether the event queue is empty or not.
func Empty(xu *xgbutil.XUtil) bool {
xu.EvqueueLck.RLock()
defer xu.EvqueueLck.RUnlock()
return len(xu.Evqueue) == 0
}
// Peek returns a *copy* of the current queue so we can examine it.
// This can be useful when trying to determine if a particular kind of
// event will be processed in the future.
func Peek(xu *xgbutil.XUtil) []xgbutil.EventOrError {
xu.EvqueueLck.RLock()
defer xu.EvqueueLck.RUnlock()
cpy := make([]xgbutil.EventOrError, len(xu.Evqueue))
copy(cpy, xu.Evqueue)
return cpy
}
// ErrorHandlerSet sets the default error handler for errors that come
// into the main event loop. (This may be removed in the future in favor
// of a particular callback interface like events, but these sorts of errors
// aren't handled often in practice, so maybe not.)
// This is only called for errors returned from unchecked (asynchronous error
// handling) requests.
// The default error handler just emits them to stderr.
func ErrorHandlerSet(xu *xgbutil.XUtil, fun xgbutil.ErrorHandlerFun) {
xu.ErrorHandler = fun
}
// ErrorHandlerGet retrieves the default error handler.
func ErrorHandlerGet(xu *xgbutil.XUtil) xgbutil.ErrorHandlerFun {
return xu.ErrorHandler
}
type HookFun func(xu *xgbutil.XUtil, event interface{}) bool
func (callback HookFun) Connect(xu *xgbutil.XUtil) {
xu.HooksLck.Lock()
defer xu.HooksLck.Unlock()
// COW
newHooks := make([]xgbutil.CallbackHook, len(xu.Hooks))
copy(newHooks, xu.Hooks)
newHooks = append(newHooks, callback)
xu.Hooks = newHooks
}
func (callback HookFun) Run(xu *xgbutil.XUtil, event interface{}) bool {
return callback(xu, event)
}
func getHooks(xu *xgbutil.XUtil) []xgbutil.CallbackHook {
xu.HooksLck.RLock()
defer xu.HooksLck.RUnlock()
return xu.Hooks
}
// RedirectKeyEvents, when set to a window id (greater than 0), will force
// *all* Key{Press,Release} to callbacks attached to the specified window.
// This is close to emulating a Keyboard grab without the racing.
// To stop redirecting key events, use window identifier '0'.
func RedirectKeyEvents(xu *xgbutil.XUtil, wid xproto.Window) {
xu.KeyRedirect = wid
}
// RedirectKeyGet gets the window that key events are being redirected to.
// If 0, then no redirection occurs.
func RedirectKeyGet(xu *xgbutil.XUtil) xproto.Window {
return xu.KeyRedirect
}
// Quit elegantly exits out of the main event loop.
// "Elegantly" in this case means that it finishes processing the current
// event, and breaks out of the loop afterwards.
// There is no particular reason to use this instead of something like os.Exit
// other than you might have code to run after the main event loop exits to
// "clean up."
func Quit(xu *xgbutil.XUtil) {
xu.Quit = true
}
// Quitting returns whether it's time to quit.
// This is only used in the main event loop in xevent.
func Quitting(xu *xgbutil.XUtil) bool {
return xu.Quit
}
// attachCallback associates a (event, window) tuple with an event.
// Use copy on write since we run callbacks *a lot* more than attaching them.
// (The copy on write only applies to the slice of callbacks rather than
// the map itself, since the initial allocation is guaranteed to come before
// any use of it.)
func attachCallback(xu *xgbutil.XUtil, evtype int, win xproto.Window,
fun xgbutil.Callback) {
xu.CallbacksLck.Lock()
defer xu.CallbacksLck.Unlock()
if _, ok := xu.Callbacks[evtype]; !ok {
xu.Callbacks[evtype] = make(map[xproto.Window][]xgbutil.Callback, 20)
}
if _, ok := xu.Callbacks[evtype][win]; !ok {
xu.Callbacks[evtype][win] = make([]xgbutil.Callback, 0)
}
// COW
newCallbacks := make([]xgbutil.Callback, len(xu.Callbacks[evtype][win]))
copy(newCallbacks, xu.Callbacks[evtype][win])
newCallbacks = append(newCallbacks, fun)
xu.Callbacks[evtype][win] = newCallbacks
}
// runCallbacks executes every callback corresponding to a
// particular event/window tuple.
func runCallbacks(xu *xgbutil.XUtil, event interface{}, evtype int,
win xproto.Window) {
// The callback slice for a particular (event type, window) tuple uses
// copy on write. So just take a pointer to whatever is there and use that.
// We can be sure that the slice won't change from underneathe us.
xu.CallbacksLck.RLock()
cbs := xu.Callbacks[evtype][win]
xu.CallbacksLck.RUnlock()
for _, cb := range cbs {
cb.Run(xu, event)
}
}
// Detach removes all callbacks associated with a particular window.
// Note that if you're also using the keybind and mousebind packages, a complete
// detachment should look like:
//
// keybind.Detach(XUtilValue, window-id)
// mousebind.Detach(XUtilValue, window-id)
// xevent.Detach(XUtilValue, window-id)
//
// If a window is no longer receiving events, these methods should be called.
// Otherwise, the memory used to store the handler info for that window will
// never be released.
func Detach(xu *xgbutil.XUtil, win xproto.Window) {
xu.CallbacksLck.Lock()
defer xu.CallbacksLck.Unlock()
for evtype, _ := range xu.Callbacks {
delete(xu.Callbacks[evtype], win)
}
}
// SendRootEvent takes a type implementing the xgb.Event interface, converts it
// to raw X bytes, and sends it to the root window using the SendEvent request.
func SendRootEvent(xu *xgbutil.XUtil, ev xgb.Event, evMask uint32) error {
return xproto.SendEventChecked(xu.Conn(), false, xu.RootWin(), evMask,
string(ev.Bytes())).Check()
}
// ReplayPointer is a quick alias to AllowEvents with 'ReplayPointer' mode.
func ReplayPointer(xu *xgbutil.XUtil) {
xproto.AllowEvents(xu.Conn(), xproto.AllowReplayPointer, 0)
}

@ -0,0 +1,348 @@
package xgbutil
import (
"log"
"os"
"sync"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/xinerama"
"github.com/BurntSushi/xgb/xproto"
)
// Logger is used through xgbutil when messages need to be emitted to stderr.
var Logger = log.New(os.Stderr, "[xgbutil] ", log.Lshortfile)
// The current maximum request size. I think we can expand this with
// BigReq, but it probably isn't worth it at the moment.
const MaxReqSize = (1 << 16) * 4
// An XUtil represents the state of xgbutil. It keeps track of the current
// X connection, the root window, event callbacks, key/mouse bindings, etc.
// Regrettably, many of the members are exported, even though they should not
// be used directly by the user. They are exported for use in sub-packages.
// (Namely, xevent, keybind and mousebind.) In fact, there should *never*
// be a reason to access any members of an XUtil value directly. Any
// interaction with an XUtil value should be through its methods.
type XUtil struct {
// conn is the XGB connection object used to issue protocol requests.
conn *xgb.Conn
// Quit can be set to true, and the main event loop will finish processing
// the current event, and gracefully quit afterwards.
// This is exported for use in the xevent package. Please us xevent.Quit
// to set this value.
Quit bool // when true, the main event loop will stop gracefully
// setup contains all the setup information retrieved at connection time.
setup *xproto.SetupInfo
// screen is a simple alias to the default screen info.
screen *xproto.ScreenInfo
// root is an alias to the default root window.
root xproto.Window
// Atoms is a cache of atom names to resource identifiers. This minimizes
// round trips to the X server, since atom identifiers never change.
// It is exported for use in the xprop package. It should not be used.
Atoms map[string]xproto.Atom
AtomsLck *sync.RWMutex
// AtomNames is a cache just like 'atoms', but in the reverse direction.
// It is exported for use in the xprop package. It should not be used.
AtomNames map[xproto.Atom]string
AtomNamesLck *sync.RWMutex
// Evqueue is the queue that stores the results of xgb.WaitForEvent.
// Namely, each value is either an Event *or* an Error.
// It is exported for use in the xevent package. Do not use it.
// If you need to interact with the event queue, please use the functions
// available in the xevent package: Dequeue, DequeueAt, QueueEmpty
// and QueuePeek.
Evqueue []EventOrError
EvqueueLck *sync.RWMutex
// Callbacks is a map of event numbers to a map of window identifiers
// to callback functions.
// This is the data structure that stores all callback functions, where
// a callback function is always attached to a (event, window) tuple.
// It is exported for use in the xevent package. Do not use it.
Callbacks map[int]map[xproto.Window][]Callback
CallbacksLck *sync.RWMutex
// Hooks are called by the XEvent main loop before processing the event
// itself. These are meant for instances when it's not possible / easy
// to use the normal Hook system. You should not modify this yourself.
Hooks []CallbackHook
HooksLck *sync.RWMutex
// eventTime is the last time recorded by an event. It is automatically
// updated if xgbutil's main event loop is used.
eventTime xproto.Timestamp
// Keymap corresponds to xgbutil's current conception of the keyboard
// mapping. It is automatically kept up-to-date if xgbutil's event loop
// is used.
// It is exported for use in the keybind package. It should not be
// accessed directly. Instead, use keybind.KeyMapGet.
Keymap *KeyboardMapping
// Modmap corresponds to xgbutil's current conception of the modifier key
// mapping. It is automatically kept up-to-date if xgbutil's event loop
// is used.
// It is exported for use in the keybind package. It should not be
// accessed directly. Instead, use keybind.ModMapGet.
Modmap *ModifierMapping
// KeyRedirect corresponds to a window identifier that, when set,
// automatically receives *all* keyboard events. This is a sort-of
// synthetic grab and is helpful in avoiding race conditions.
// It is exported for use in the xevent and keybind packages. Do not use
// it directly. To redirect key events, please use xevent.RedirectKeyEvents.
KeyRedirect xproto.Window
// Keybinds is the data structure storing all callbacks for key bindings.
// This is extremely similar to the general notion of event callbacks,
// but adds extra support to make handling key bindings easier. (Like
// specifying human readable key sequences to bind to.)
// KeyBindKey is a struct representing the 4-tuple
// (event-type, window-id, modifiers, keycode).
// It is exported for use in the keybind package. Do not access it directly.
Keybinds map[KeyKey][]CallbackKey
KeybindsLck *sync.RWMutex
// Keygrabs is a frequency count of the number of callbacks associated
// with a particular KeyBindKey. This is necessary because we can only
// grab a particular key *once*, but we may want to attach several callbacks
// to a single keypress.
// It is exported for use in the keybind package. Do not access it directly.
Keygrabs map[KeyKey]int
// Keystrings is a list of all key strings used to connect keybindings.
// They are used to rebuild key grabs when the keyboard mapping is updated.
// It is exported for use in the keybind package. Do not access it directly.
Keystrings []KeyString
// Mousebinds is the data structure storing all callbacks for mouse
// bindings.This is extremely similar to the general notion of event
// callbacks,but adds extra support to make handling mouse bindings easier.
// (Like specifying human readable mouse sequences to bind to.)
// MouseBindKey is a struct representing the 4-tuple
// (event-type, window-id, modifiers, button).
// It is exported for use in the mousebind package. Do not use it.
Mousebinds map[MouseKey][]CallbackMouse
MousebindsLck *sync.RWMutex
// Mousegrabs is a frequency count of the number of callbacks associated
// with a particular MouseBindKey. This is necessary because we can only
// grab a particular mouse button *once*, but we may want to attach
// several callbacks to a single button press.
// It is exported for use in the mousebind package. Do not use it.
Mousegrabs map[MouseKey]int
// InMouseDrag is true if a drag is currently in progress.
// It is exported for use in the mousebind package. Do not use it.
InMouseDrag bool
// MouseDragStep is the function executed for each step (i.e., pointer
// movement) in the current mouse drag. Note that this is nil when a drag
// is not in progress.
// It is exported for use in the mousebind package. Do not use it.
MouseDragStepFun MouseDragFun
// MouseDragEnd is the function executed at the end of the current
// mouse drag. This is nil when a drag is not in progress.
// It is exported for use in the mousebind package. Do not use it.
MouseDragEndFun MouseDragFun
// gc is a general purpose graphics context; used to paint images.
// Since we don't do any real X drawing, we don't really care about the
// particulars of our graphics context.
gc xproto.Gcontext
// dummy is a dummy window used for mouse/key GRABs.
// Basically, whenever a grab is instituted, mouse and key events are
// redirected to the dummy the window.
dummy xproto.Window
// ErrorHandler is the function that handles errors *in the event loop*.
// By default, it simply emits them to stderr.
// It is exported for use in the xevent package. To set the default error
// handler, please use xevent.ErrorHandlerSet.
ErrorHandler ErrorHandlerFun
}
// NewConn connects to the X server using the DISPLAY environment variable
// and creates a new XUtil. Most environments have the DISPLAY environment
// variable set, so this is probably what you want to use to connect to X.
func NewConn() (*XUtil, error) {
return NewConnDisplay("")
}
// NewConnDisplay connects to the X server and creates a new XUtil.
// If 'display' is empty, the DISPLAY environment variable is used. Otherwise
// there are several different display formats supported:
//
// NewConn(":1") -> net.Dial("unix", "", "/tmp/.X11-unix/X1")
// NewConn("/tmp/launch-12/:0") -> net.Dial("unix", "", "/tmp/launch-12/:0")
// NewConn("hostname:2.1") -> net.Dial("tcp", "", "hostname:6002")
// NewConn("tcp/hostname:1.0") -> net.Dial("tcp", "", "hostname:6001")
func NewConnDisplay(display string) (*XUtil, error) {
c, err := xgb.NewConnDisplay(display)
if err != nil {
return nil, err
}
return NewConnXgb(c)
}
// NewConnXgb use the specific xgb.Conn to create a new XUtil.
//
// NewConn, NewConnDisplay are wrapper of this function.
func NewConnXgb(c *xgb.Conn) (*XUtil, error) {
setup := xproto.Setup(c)
screen := setup.DefaultScreen(c)
// Initialize our central struct that stores everything.
xu := &XUtil{
conn: c,
Quit: false,
Evqueue: make([]EventOrError, 0, 1000),
EvqueueLck: &sync.RWMutex{},
setup: setup,
screen: screen,
root: screen.Root,
eventTime: xproto.Timestamp(0), // last event time
Atoms: make(map[string]xproto.Atom, 50),
AtomsLck: &sync.RWMutex{},
AtomNames: make(map[xproto.Atom]string, 50),
AtomNamesLck: &sync.RWMutex{},
Callbacks: make(map[int]map[xproto.Window][]Callback, 33),
CallbacksLck: &sync.RWMutex{},
Hooks: make([]CallbackHook, 0),
HooksLck: &sync.RWMutex{},
Keymap: nil, // we don't have anything yet
Modmap: nil,
KeyRedirect: 0,
Keybinds: make(map[KeyKey][]CallbackKey, 10),
KeybindsLck: &sync.RWMutex{},
Keygrabs: make(map[KeyKey]int, 10),
Keystrings: make([]KeyString, 0, 10),
Mousebinds: make(map[MouseKey][]CallbackMouse, 10),
MousebindsLck: &sync.RWMutex{},
Mousegrabs: make(map[MouseKey]int, 10),
InMouseDrag: false,
MouseDragStepFun: nil,
MouseDragEndFun: nil,
ErrorHandler: func(err xgb.Error) { Logger.Println(err) },
}
var err error = nil
// Create a general purpose graphics context
xu.gc, err = xproto.NewGcontextId(xu.conn)
if err != nil {
return nil, err
}
xproto.CreateGC(xu.conn, xu.gc, xproto.Drawable(xu.root),
xproto.GcForeground, []uint32{xu.screen.WhitePixel})
// Create a dummy window
xu.dummy, err = xproto.NewWindowId(xu.conn)
if err != nil {
return nil, err
}
xproto.CreateWindow(xu.conn, xu.Screen().RootDepth, xu.dummy, xu.RootWin(),
-1000, -1000, 1, 1, 0,
xproto.WindowClassInputOutput, xu.Screen().RootVisual,
xproto.CwEventMask|xproto.CwOverrideRedirect,
[]uint32{1, xproto.EventMaskPropertyChange})
xproto.MapWindow(xu.conn, xu.dummy)
// Register the Xinerama extension... because it doesn't cost much.
err = xinerama.Init(xu.conn)
// If we can't register Xinerama, that's okay. Output something
// and move on.
if err != nil {
Logger.Printf("WARNING: %s\n", err)
Logger.Printf("MESSAGE: The 'xinerama' package cannot be used " +
"because the XINERAMA extension could not be loaded.")
}
return xu, nil
}
// Conn returns the xgb connection object.
func (xu *XUtil) Conn() *xgb.Conn {
return xu.conn
}
// ExtInitialized returns true if an extension has been initialized.
// This is useful for determining whether an extension is available or not.
func (xu *XUtil) ExtInitialized(extName string) bool {
_, ok := xu.Conn().Extensions[extName]
return ok
}
// Sync forces XGB to catch up with all events/requests and synchronize.
// This is done by issuing a benign round trip request to X.
func (xu *XUtil) Sync() {
xproto.GetInputFocus(xu.Conn()).Reply()
}
// Setup returns the setup information retrieved during connection time.
func (xu *XUtil) Setup() *xproto.SetupInfo {
return xu.setup
}
// Screen returns the default screen
func (xu *XUtil) Screen() *xproto.ScreenInfo {
return xu.screen
}
// RootWin returns the current root window.
func (xu *XUtil) RootWin() xproto.Window {
return xu.root
}
// RootWinSet will change the current root window to the one provided.
// N.B. This probably shouldn't be used unless you're desperately trying
// to support multiple X screens. (This is *not* the same as Xinerama/RandR or
// TwinView. All of those have a single root window.)
func (xu *XUtil) RootWinSet(root xproto.Window) {
xu.root = root
}
// TimeGet gets the most recent time seen by an event.
func (xu *XUtil) TimeGet() xproto.Timestamp {
return xu.eventTime
}
// TimeSet sets the most recent time seen by an event.
func (xu *XUtil) TimeSet(t xproto.Timestamp) {
xu.eventTime = t
}
// GC gets a general purpose graphics context that is typically used to simply
// paint images.
func (xu *XUtil) GC() xproto.Gcontext {
return xu.gc
}
// Dummy gets the id of the dummy window.
func (xu *XUtil) Dummy() xproto.Window {
return xu.dummy
}
// Grabs the server. Everything becomes synchronous.
func (xu *XUtil) Grab() {
xproto.GrabServer(xu.Conn())
}
// Ungrabs the server.
func (xu *XUtil) Ungrab() {
xproto.UngrabServer(xu.Conn())
}

@ -0,0 +1,103 @@
package xprop
/*
xprop/atom.go contains functions related to interning atoms and retrieving
atom names from an atom identifier.
It also manages an atom cache so that once an atom is interned from the X
server, all future atom interns use that value. (So that one and only one
request is sent for interning each atom.)
*/
import (
"fmt"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
)
// Atm is a short alias for Atom in the common case of interning an atom.
// Namely, interning the atom always succeeds. (If the atom does not already
// exist, a new one is created.)
func Atm(xu *xgbutil.XUtil, name string) (xproto.Atom, error) {
aid, err := Atom(xu, name, false)
if err != nil {
return 0, err
}
if aid == 0 {
return 0, fmt.Errorf("Atm: '%s' returned an identifier of 0.", name)
}
return aid, err
}
// Atom interns an atom and panics if there is any error.
func Atom(xu *xgbutil.XUtil, name string,
onlyIfExists bool) (xproto.Atom, error) {
// Check the cache first
if aid, ok := atomGet(xu, name); ok {
return aid, nil
}
reply, err := xproto.InternAtom(xu.Conn(), onlyIfExists,
uint16(len(name)), name).Reply()
if err != nil {
return 0, fmt.Errorf("Atom: Error interning atom '%s': %s", name, err)
}
// If we're here, it means we didn't have this atom cached. So cache it!
cacheAtom(xu, name, reply.Atom)
return reply.Atom, nil
}
// AtomName fetches a string representation of an ATOM given its integer id.
func AtomName(xu *xgbutil.XUtil, aid xproto.Atom) (string, error) {
// Check the cache first
if atomName, ok := atomNameGet(xu, aid); ok {
return string(atomName), nil
}
reply, err := xproto.GetAtomName(xu.Conn(), aid).Reply()
if err != nil {
return "", fmt.Errorf("AtomName: Error fetching name for ATOM "+
"id '%d': %s", aid, err)
}
// If we're here, it means we didn't have ths ATOM id cached. So cache it.
atomName := string(reply.Name)
cacheAtom(xu, atomName, aid)
return atomName, nil
}
// atomGet retrieves an atom identifier from a cache if it exists.
func atomGet(xu *xgbutil.XUtil, name string) (xproto.Atom, bool) {
xu.AtomsLck.RLock()
defer xu.AtomsLck.RUnlock()
aid, ok := xu.Atoms[name]
return aid, ok
}
// atomNameGet retrieves an atom name from a cache if it exists.
func atomNameGet(xu *xgbutil.XUtil, aid xproto.Atom) (string, bool) {
xu.AtomNamesLck.RLock()
defer xu.AtomNamesLck.RUnlock()
name, ok := xu.AtomNames[aid]
return name, ok
}
// cacheAtom puts an atom into the cache.
func cacheAtom(xu *xgbutil.XUtil, name string, aid xproto.Atom) {
xu.AtomsLck.Lock()
xu.AtomNamesLck.Lock()
defer xu.AtomsLck.Unlock()
defer xu.AtomNamesLck.Unlock()
xu.Atoms[name] = aid
xu.AtomNames[aid] = name
}

@ -0,0 +1,40 @@
/*
Package xprop provides a cache for interning atoms and helper functions for
dealing with GetProperty and ChangeProperty X requests.
Atoms
Atoms in X are interned, meaning that strings are assigned unique integer
identifiers. This minimizes the amount of data transmitted over an X connection.
Once atoms have been interned, they are never changed while the X server is
running. xgbutil takes advantage of this invariant and will only issue an
intern atom request once and cache the result.
To use the xprop package to intern an atom, use Atom:
atom, err := xprop.Atom(XUtilValue, "THE_ATOM_NAME", false)
if err == nil {
println("The atom number: ", atom.Atom)
}
The 'false' parameter corresponds to the 'only_if_exists' parameter of the
X InternAtom request. When it's false, the atom being interned always returns
a non-zero atom number---even if the string being interned hasn't been interned
before. If 'only_if_exists' is true, the atom being interned will return a 0
atom number if it hasn't already been interned.
The typical case is to set 'only_if_exists' to false. To this end, xprop.Atm is
an alias that always sets this value to false.
The reverse can also be done: getting an atom string if you have an atom
number. This can be done with the xprop.AtomName function.
Properties
The other facility of xprop is to help with the use of GetProperty and
ChangeProperty. Please see the source code of the ewmh package for plenty of
examples.
*/
package xprop

@ -0,0 +1,270 @@
package xprop
import (
"fmt"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/xproto"
"github.com/BurntSushi/xgbutil"
)
// GetProperty abstracts the messiness of calling xgb.GetProperty.
func GetProperty(xu *xgbutil.XUtil, win xproto.Window, atom string) (
*xproto.GetPropertyReply, error) {
atomId, err := Atm(xu, atom)
if err != nil {
return nil, err
}
reply, err := xproto.GetProperty(xu.Conn(), false, win, atomId,
xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
if err != nil {
return nil, fmt.Errorf("GetProperty: Error retrieving property '%s' "+
"on window %x: %s", atom, win, err)
}
if reply.Format == 0 {
return nil, fmt.Errorf("GetProperty: No such property '%s' on "+
"window %x.", atom, win)
}
return reply, nil
}
// ChangeProperty abstracts the semi-nastiness of xgb.ChangeProperty.
func ChangeProp(xu *xgbutil.XUtil, win xproto.Window, format byte, prop string,
typ string, data []byte) error {
propAtom, err := Atm(xu, prop)
if err != nil {
return err
}
typAtom, err := Atm(xu, typ)
if err != nil {
return err
}
return xproto.ChangePropertyChecked(xu.Conn(), xproto.PropModeReplace, win,
propAtom, typAtom, format,
uint32(len(data)/(int(format)/8)), data).Check()
}
// ChangeProperty32 makes changing 32 bit formatted properties easier
// by constructing the raw X data for you.
func ChangeProp32(xu *xgbutil.XUtil, win xproto.Window, prop string, typ string,
data ...uint) error {
buf := make([]byte, len(data)*4)
for i, datum := range data {
xgb.Put32(buf[(i*4):], uint32(datum))
}
return ChangeProp(xu, win, 32, prop, typ, buf)
}
// WindowToUint is a covenience function for converting []xproto.Window
// to []uint.
func WindowToInt(ids []xproto.Window) []uint {
ids32 := make([]uint, len(ids))
for i, v := range ids {
ids32[i] = uint(v)
}
return ids32
}
// AtomToInt is a covenience function for converting []xproto.Atom
// to []uint.
func AtomToUint(ids []xproto.Atom) []uint {
ids32 := make([]uint, len(ids))
for i, v := range ids {
ids32[i] = uint(v)
}
return ids32
}
// StrToAtoms is a convenience function for converting
// []string to []uint32 atoms.
// NOTE: If an atom name in the list doesn't exist, it will be created.
func StrToAtoms(xu *xgbutil.XUtil, atomNames []string) ([]uint, error) {
var err error
atoms := make([]uint, len(atomNames))
for i, atomName := range atomNames {
a, err := Atom(xu, atomName, false)
if err != nil {
return nil, err
}
atoms[i] = uint(a)
}
return atoms, err
}
// PropValAtom transforms a GetPropertyReply struct into an ATOM name.
// The property reply must be in 32 bit format.
func PropValAtom(xu *xgbutil.XUtil, reply *xproto.GetPropertyReply,
err error) (string, error) {
if err != nil {
return "", err
}
if reply.Format != 32 {
return "", fmt.Errorf("PropValAtom: Expected format 32 but got %d",
reply.Format)
}
return AtomName(xu, xproto.Atom(xgb.Get32(reply.Value)))
}
// PropValAtoms is the same as PropValAtom, except that it returns a slice
// of atom names. Also must be 32 bit format.
// This is a method of an XUtil struct, unlike the other 'PropVal...' functions.
func PropValAtoms(xu *xgbutil.XUtil, reply *xproto.GetPropertyReply,
err error) ([]string, error) {
if err != nil {
return nil, err
}
if reply.Format != 32 {
return nil, fmt.Errorf("PropValAtoms: Expected format 32 but got %d",
reply.Format)
}
ids := make([]string, reply.ValueLen)
vals := reply.Value
for i := 0; len(vals) >= 4; i++ {
ids[i], err = AtomName(xu, xproto.Atom(xgb.Get32(vals)))
if err != nil {
return nil, err
}
vals = vals[4:]
}
return ids, nil
}
// PropValWindow transforms a GetPropertyReply struct into an X resource
// window identifier.
// The property reply must be in 32 bit format.
func PropValWindow(reply *xproto.GetPropertyReply,
err error) (xproto.Window, error) {
if err != nil {
return 0, err
}
if reply.Format != 32 {
return 0, fmt.Errorf("PropValId: Expected format 32 but got %d",
reply.Format)
}
return xproto.Window(xgb.Get32(reply.Value)), nil
}
// PropValWindows is the same as PropValWindow, except that it returns a slice
// of identifiers. Also must be 32 bit format.
func PropValWindows(reply *xproto.GetPropertyReply,
err error) ([]xproto.Window, error) {
if err != nil {
return nil, err
}
if reply.Format != 32 {
return nil, fmt.Errorf("PropValIds: Expected format 32 but got %d",
reply.Format)
}
ids := make([]xproto.Window, reply.ValueLen)
vals := reply.Value
for i := 0; len(vals) >= 4; i++ {
ids[i] = xproto.Window(xgb.Get32(vals))
vals = vals[4:]
}
return ids, nil
}
// PropValNum transforms a GetPropertyReply struct into an unsigned
// integer. Useful when the property value is a single integer.
func PropValNum(reply *xproto.GetPropertyReply, err error) (uint, error) {
if err != nil {
return 0, err
}
if reply.Format != 32 {
return 0, fmt.Errorf("PropValNum: Expected format 32 but got %d",
reply.Format)
}
return uint(xgb.Get32(reply.Value)), nil
}
// PropValNums is the same as PropValNum, except that it returns a slice
// of integers. Also must be 32 bit format.
func PropValNums(reply *xproto.GetPropertyReply, err error) ([]uint, error) {
if err != nil {
return nil, err
}
if reply.Format != 32 {
return nil, fmt.Errorf("PropValIds: Expected format 32 but got %d",
reply.Format)
}
nums := make([]uint, reply.ValueLen)
vals := reply.Value
for i := 0; len(vals) >= 4; i++ {
nums[i] = uint(xgb.Get32(vals))
vals = vals[4:]
}
return nums, nil
}
// PropValNum64 transforms a GetPropertyReply struct into a 64 bit
// integer. Useful when the property value is a single integer.
func PropValNum64(reply *xproto.GetPropertyReply, err error) (int64, error) {
if err != nil {
return 0, err
}
if reply.Format != 32 {
return 0, fmt.Errorf("PropValNum: Expected format 32 but got %d",
reply.Format)
}
return int64(xgb.Get32(reply.Value)), nil
}
// PropValStr transforms a GetPropertyReply struct into a string.
// Useful when the property value is a null terminated string represented
// by integers. Also must be 8 bit format.
func PropValStr(reply *xproto.GetPropertyReply, err error) (string, error) {
if err != nil {
return "", err
}
if reply.Format != 8 {
return "", fmt.Errorf("PropValStr: Expected format 8 but got %d",
reply.Format)
}
return string(reply.Value), nil
}
// PropValStrs is the same as PropValStr, except that it returns a slice
// of strings. The raw byte string is a sequence of null terminated strings,
// which is translated into a slice of strings.
func PropValStrs(reply *xproto.GetPropertyReply, err error) ([]string, error) {
if err != nil {
return nil, err
}
if reply.Format != 8 {
return nil, fmt.Errorf("PropValStrs: Expected format 8 but got %d",
reply.Format)
}
var strs []string
sstart := 0
for i, c := range reply.Value {
if c == 0 {
strs = append(strs, string(reply.Value[sstart:i]))
sstart = i + 1
}
}
if sstart < int(reply.ValueLen) {
strs = append(strs, string(reply.Value[sstart:]))
}
return strs, nil
}

@ -383,7 +383,7 @@ func cookiePath() (string, error) {
return p, nil
}
p = filepath.Join(os.Getenv("HOME"), "/.pulse_cookie")
p = filepath.Join(os.Getenv("HOME"), "/.pulse-cookie")
if exists(p) {
return p, nil
}

11
vendor/modules.txt vendored

@ -41,8 +41,17 @@ github.com/BurntSushi/toml
# github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802
github.com/BurntSushi/xgb
github.com/BurntSushi/xgb/render
github.com/BurntSushi/xgb/shape
github.com/BurntSushi/xgb/shm
github.com/BurntSushi/xgb/xinerama
github.com/BurntSushi/xgb/xproto
# github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046
## explicit
github.com/BurntSushi/xgbutil
github.com/BurntSushi/xgbutil/ewmh
github.com/BurntSushi/xgbutil/icccm
github.com/BurntSushi/xgbutil/xevent
github.com/BurntSushi/xgbutil/xprop
# github.com/aarzilli/nucular v0.0.0-20200615134801-81910c722bba
## explicit
github.com/aarzilli/nucular
@ -62,7 +71,7 @@ github.com/golang/freetype/truetype
# github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad
github.com/hashicorp/golang-lru
github.com/hashicorp/golang-lru/simplelru
# github.com/lawl/pulseaudio v0.0.0-20200802093727-ab0735955fd0
# github.com/lawl/pulseaudio v0.0.0-20210604102109-cb2596d6a8ef
## explicit
github.com/lawl/pulseaudio
# github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635

@ -0,0 +1,50 @@
package main
import (
"fmt"
"sync"
"github.com/aarzilli/nucular"
)
type ViewFunc func(ctx *ntcontext, w *nucular.Window)
type ViewStack struct {
stack [100]ViewFunc
sp int8
mu sync.Mutex
}
func NewViewStack() *ViewStack {
return &ViewStack{sp: -1}
}
func (v *ViewStack) Push(f ViewFunc) {
v.mu.Lock()
defer v.mu.Unlock()
v.stack[v.sp+1] = f
v.sp++
}
func (v *ViewStack) Pop() (ViewFunc, error) {
v.mu.Lock()
if v.sp <= 0 {
return nil, fmt.Errorf("Cannot pop root element from ViewStack")
}
defer (func() {
v.stack[v.sp] = nil
v.sp--
v.mu.Unlock()
})()
return v.stack[v.sp], nil
}
func (v *ViewStack) Peek() ViewFunc {
v.mu.Lock()
defer v.mu.Unlock()
return v.stack[v.sp]
}
Loading…
Cancel
Save