+++ /dev/null
-/*
- * Sonitus - AbstractControlledComponent.java - Copyright © 2013 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sonitus.data;
-
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-
-import com.google.common.collect.Lists;
-
-/**
- * Abstract {@link ControlledComponent} implementation that takes care of
- * managing {@link MetadataListener}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public abstract class AbstractControlledComponent implements ControlledComponent {
-
- /** The name of this filter. */
- private final String name;
-
- /** The list of metadata listeners. */
- private final List<MetadataListener> metadataListeners = Lists.newCopyOnWriteArrayList();
-
- /** The current metadata. */
- private final AtomicReference<Metadata> metadata = new AtomicReference<Metadata>();
-
- /**
- * Creates a new abstract controlled component.
- *
- * @param name
- * The name of the component
- */
- protected AbstractControlledComponent(String name) {
- this.name = name;
- }
-
- //
- // LISTENER MANAGEMENT
- //
-
- @Override
- public void addMetadataListener(MetadataListener metadataListener) {
- metadataListeners.add(metadataListener);
- }
-
- @Override
- public void removeMetadataListener(MetadataListener metadataListener) {
- metadataListeners.remove(metadataListener);
- }
-
- //
- // CONTROLLEDCOMPONENT METHODS
- //
-
- @Override
- public String name() {
- return name;
- }
-
- @Override
- public Metadata metadata() {
- return metadata.get();
- }
-
- @Override
- public void metadataUpdated(Metadata metadata) {
- if (metadata.equals(this.metadata.get())) {
- return;
- }
- this.metadata.set(metadata);
- fireMetadataUpdated(metadata);
- }
-
- //
- // EVENT METHODS
- //
-
- /**
- * Notifies all registered metadata listeners that the metadata has changed.
- *
- * @param metadata
- * The new metadata
- */
- protected void fireMetadataUpdated(Metadata metadata) {
- for (MetadataListener metadataListener : metadataListeners) {
- metadataListener.metadataUpdated(this, metadata);
- }
- }
-
-}
--- /dev/null
+/*
+ * Sonitus - AbstractFilter.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sonitus.data;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.google.common.collect.Lists;
+import com.google.common.io.Closeables;
+
+/**
+ * Abstract {@link Filter} implementation that takes care of managing {@link
+ * MetadataListener}s and pipes its input to its output.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class AbstractFilter implements Filter {
+
+ /** The name of this filter. */
+ private final String name;
+
+ /** The list of metadata listeners. */
+ private final List<MetadataListener> metadataListeners = Lists.newCopyOnWriteArrayList();
+
+ /** The current metadata. */
+ private final AtomicReference<Metadata> metadata = new AtomicReference<Metadata>();
+
+ /** The input stream from which to read. */
+ private InputStream inputStream;
+
+ /** The output stream to which to write. */
+ private OutputStream outputStream;
+
+ /**
+ * Creates a new abstract filter.
+ *
+ * @param name
+ * The name of the filter
+ */
+ protected AbstractFilter(String name) {
+ this.name = name;
+ }
+
+ //
+ // LISTENER MANAGEMENT
+ //
+
+ @Override
+ public void addMetadataListener(MetadataListener metadataListener) {
+ metadataListeners.add(metadataListener);
+ }
+
+ @Override
+ public void removeMetadataListener(MetadataListener metadataListener) {
+ metadataListeners.remove(metadataListener);
+ }
+
+ //
+ // FILTER METHODS
+ //
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List<Controller<?>> controllers() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Metadata metadata() {
+ return metadata.get();
+ }
+
+ @Override
+ public void metadataUpdated(Metadata metadata) {
+ if (metadata.equals(this.metadata.get())) {
+ return;
+ }
+ this.metadata.set(metadata);
+ fireMetadataUpdated(metadata);
+ }
+
+ @Override
+ public void open(Metadata metadata) throws IOException {
+ metadataUpdated(metadata);
+ inputStream = createInputStream();
+ outputStream = createOutputStream();
+ }
+
+ @Override
+ public void close() {
+ try {
+ Closeables.close(outputStream, true);
+ Closeables.close(inputStream, true);
+ } catch (IOException e) {
+ /* won’t throw. */
+ }
+ }
+
+ @Override
+ public void process(byte[] buffer) throws IOException {
+ outputStream.write(buffer);
+ outputStream.flush();
+ }
+
+ @Override
+ public byte[] get(int bufferSize) throws IOException {
+ byte[] buffer = new byte[bufferSize];
+ int read = inputStream.read(buffer);
+ if (read == -1) {
+ throw new EOFException();
+ }
+ return Arrays.copyOf(buffer, read);
+ }
+
+ //
+ // EVENT METHODS
+ //
+
+ /**
+ * Notifies all registered metadata listeners that the metadata has changed.
+ *
+ * @param metadata
+ * The new metadata
+ */
+ protected void fireMetadataUpdated(Metadata metadata) {
+ for (MetadataListener metadataListener : metadataListeners) {
+ metadataListener.metadataUpdated(this, metadata);
+ }
+ }
+
+ //
+ // SUBCLASS METHODS
+ //
+
+ /**
+ * Creates the input stream from which {@link net.pterodactylus.sonitus.data.Pipeline}
+ * will read the audio data. If you override this, you have to override {@link
+ * #createOutputStream()}, too!
+ *
+ * @return The input stream to read from
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ protected InputStream createInputStream() throws IOException {
+ return new PipedInputStream();
+ }
+
+ /**
+ * Creates the output stream to which {@link net.pterodactylus.sonitus.data.Pipeline}
+ * will write the audio data. If you override this, you have to override {@link
+ * #createInputStream()}, too!
+ *
+ * @return The output stream to write to
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ protected OutputStream createOutputStream() throws IOException {
+ return new PipedOutputStream((PipedInputStream) inputStream);
+ }
+
+}
+++ /dev/null
-/*
- * Sonitus - Controlled.java - Copyright © 2013 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sonitus.data;
-
-import java.util.List;
-
-/**
- * Interface for components that can be controlled externally in some way.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface ControlledComponent {
-
- /**
- * Adds the given listener to the list of registered listeners.
- *
- * @param metadataListener
- * The metadata listener to add
- */
- void addMetadataListener(MetadataListener metadataListener);
-
- /**
- * Removes the given listener from the list of registered listeners.
- *
- * @param metadataListener
- * The metadata listener to remove
- */
- void removeMetadataListener(MetadataListener metadataListener);
-
- /**
- * Returns the name of this controlled component.
- *
- * @return The name of this controlled component
- */
- public String name();
-
- /**
- * Returns the current metadata of this component.
- *
- * @return The current metadata of this component
- */
- public Metadata metadata();
-
- /**
- * Returns the controllers offered by this component.
- *
- * @return The controllers of this component
- */
- public List<Controller<?>> controllers();
-
- /**
- * Notifies the sink that the metadata of the audio stream has changed. This
- * method should return as fast as possible, i.e. every heavy lifting should be
- * done from another thread.
- *
- * @param metadata
- * The new metadata
- */
- void metadataUpdated(Metadata metadata);
-
-}
package net.pterodactylus.sonitus.data;
+import java.io.IOException;
+import java.util.List;
+
/**
- * A filter is both a {@link Source} and a {@link Sink}. It is used to process
+ * A filter is both a source and a sink for audio data. It is used to process
* the audio date in whatever way seems appropriate.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public interface Filter extends ControlledComponent, Source, Sink {
+public interface Filter {
+
+ /**
+ * Adds the given listener to the list of registered listeners.
+ *
+ * @param metadataListener
+ * The metadata listener to add
+ */
+ void addMetadataListener(MetadataListener metadataListener);
+
+ /**
+ * Removes the given listener from the list of registered listeners.
+ *
+ * @param metadataListener
+ * The metadata listener to remove
+ */
+ void removeMetadataListener(MetadataListener metadataListener);
+
+ /**
+ * Returns the name of this filter.
+ *
+ * @return The name of this filter
+ */
+ String name();
+
+ /**
+ * Returns the controllers offered by this filter.
+ *
+ * @return The controllers of this filter
+ */
+ List<Controller<?>> controllers();
+
+ /**
+ * Returns the metadata of the audio stream.
+ *
+ * @return The metadata of the audio stream
+ */
+ Metadata metadata();
+
+ /**
+ * Notifies the sink that the metadata of the audio stream has changed. This
+ * method should return as fast as possible, i.e. every heavy lifting should be
+ * done from another thread.
+ *
+ * @param metadata
+ * The new metadata
+ */
+ void metadataUpdated(Metadata metadata);
+
+ /**
+ * Retrieves data from the audio stream.
+ *
+ * @param bufferSize
+ * The maximum amount of bytes to retrieve from the audio stream
+ * @return A buffer filled with up to {@code bufferSize} bytes of data; the
+ * returned buffer may contain less data than requested but will not
+ * contain excess elements
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ byte[] get(int bufferSize) throws IOException;
+
+ /**
+ * Opens this sink using the format parameters of the given metadata.
+ *
+ * @param metadata
+ * The metadata of the stream
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ void open(Metadata metadata) throws IOException;
+
+ /** Closes this sink. */
+ void close();
+
+ /**
+ * Processes the given buffer of data.
+ *
+ * @param buffer
+ * The data to process
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ void process(byte[] buffer) throws IOException;
}
public interface MetadataListener {
/**
- * Notifies a listener when the metadata of the given component was updated.
+ * Notifies a listener when the metadata of the given filter was updated.
*
- * @param component
- * The component whose metadata was updated
+ * @param filter
+ * The filter whose metadata was updated
* @param metadata
* The new metadata
*/
- void metadataUpdated(ControlledComponent component, Metadata metadata);
+ void metadataUpdated(Filter filter, Metadata metadata);
}
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.MoreExecutors;
/**
- * A pipeline is responsible for streaming audio data from a {@link Source} to
- * an arbitrary number of connected {@link Filter}s and {@link Sink}s.
+ * A pipeline is responsible for streaming audio data from a {@link Filter} to
+ * an arbitrary number of connected {@link Filter}s.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class Pipeline implements Iterable<ControlledComponent> {
+public class Pipeline implements Iterable<Filter> {
/** The logger. */
private static final Logger logger = Logger.getLogger(Pipeline.class.getName());
/** The source of the audio stream. */
- private final Source source;
+ private final Filter source;
- /** The sinks for each source. */
- private final Multimap<Source, Sink> sinks;
+ /** The filters for each source. */
+ private final ListMultimap<Filter, Filter> filters;
/** All started connections. */
private final List<Connection> connections = Lists.newArrayList();
*
* @param source
* The source of the audio stream
- * @param sinks
- * The sinks for each source
+ * @param filters
+ * The filters for each source
*/
- private Pipeline(Source source, Multimap<Source, Sink> sinks) {
+ private Pipeline(Filter source, Multimap<Filter, Filter> filters) {
this.source = Preconditions.checkNotNull(source, "source must not be null");
- this.sinks = Preconditions.checkNotNull(sinks, "sinks must not be null");
- for (ControlledComponent component : Lists.reverse(components())) {
- logger.finest(String.format("Adding Listener to %s.", component.name()));
- component.addMetadataListener(new MetadataListener() {
+ this.filters = ArrayListMultimap.create(Preconditions.checkNotNull(filters, "filters must not be null"));
+ for (Filter filter : Lists.reverse(filters())) {
+ logger.finest(String.format("Adding Listener to %s.", filter.name()));
+ filter.addMetadataListener(new MetadataListener() {
+
@Override
- public void metadataUpdated(ControlledComponent component, Metadata metadata) {
- if (!(component instanceof Source)) {
- return;
- }
- for (ControlledComponent controlledComponent : sinks((Source) component)) {
- logger.fine(String.format("Updating Metadata from %s to %s as %s.", component.name(), controlledComponent.name(), metadata));
- controlledComponent.metadataUpdated(metadata);
+ public void metadataUpdated(Filter filter, Metadata metadata) {
+ for (Filter sinks : filters(filter)) {
+ logger.fine(String.format("Updating Metadata from %s to %s as %s.", filter.name(), sinks.name(), metadata));
+ sinks.metadataUpdated(metadata);
}
}
});
*
* @return This pipeline’s source
*/
- public Source source() {
+ public Filter source() {
return source;
}
/**
- * Returns all {@link Sink}s (or {@link Filter}s, really) that are connected to
- * the given source.
+ * Returns all {@link Filter}s that are connected to the given filter.
*
- * @param source
- * The source to get the sinks for
- * @return The sinks connected to the given source, or an empty list if the
- * source does not exist in this pipeline
+ * @param filter
+ * The filter to get the connected filters for
+ * @return The filters connected to the given filter, or an empty list if the
+ * filter does not exist in this pipeline, or is not connected to any filters
*/
- public Collection<Sink> sinks(Source source) {
- return sinks.get(source);
+ public List<Filter> filters(Filter filter) {
+ return filters.get(filter);
}
/**
- * Returns the traffic counters of the given controlled component.
+ * Returns the traffic counters of the given filter.
*
- * @param controlledComponent
- * The controlled component to get the traffic counters for
- * @return The traffic counters for the given controlled component
+ * @param filter
+ * The filter to get the traffic counters for
+ * @return The traffic counters for the given filter
*/
- public TrafficCounter trafficCounter(ControlledComponent controlledComponent) {
+ public TrafficCounter trafficCounter(Filter filter) {
long input = -1;
long output = -1;
for (Connection connection : connections) {
/* the connection where the source matches knows the output. */
- if (connection.source.equals(controlledComponent)) {
+ if (connection.source.equals(filter)) {
output = connection.counter();
- } else if (connection.sinks.contains(controlledComponent)) {
+ } else if (connection.sinks.contains(filter)) {
input = connection.counter();
}
}
* Starts the pipeline.
*
* @throws IOException
- * if any of the sinks can not be opened
+ * if any of the filters can not be opened
* @throws IllegalStateException
* if the pipeline is already running
*/
if (!connections.isEmpty()) {
throw new IllegalStateException("Pipeline is already running!");
}
- List<Source> sources = Lists.newArrayList();
- sources.add(source);
+ List<Filter> filters = Lists.newArrayList();
+ filters.add(source);
/* collect all source->sink pairs. */
- while (!sources.isEmpty()) {
- Source source = sources.remove(0);
- Collection<Sink> sinks = this.sinks.get(source);
- connections.add(new Connection(source, sinks));
- for (Sink sink : sinks) {
+ while (!filters.isEmpty()) {
+ Filter filter = filters.remove(0);
+ Collection<Filter> sinks = this.filters.get(filter);
+ connections.add(new Connection(filter, sinks));
+ for (Filter sink : sinks) {
logger.info(String.format("Opening %s with %s...", sink.name(), source.metadata()));
- sink.open(source.metadata());
- if (sink instanceof Filter) {
- sources.add((Source) sink);
- }
+ sink.open(filter.metadata());
+ filters.add(sink);
}
}
for (Connection connection : connections) {
- String threadName = String.format("%s → %s.", connection.source.name(), FluentIterable.from(connection.sinks).transform(new Function<Sink, String>() {
+ String threadName = String.format("%s → %s.", connection.source.name(), FluentIterable.from(connection.sinks).transform(new Function<Filter, String>() {
@Override
- public String apply(Sink sink) {
+ public String apply(Filter sink) {
return sink.name();
}
}));
//
@Override
- public Iterator<ControlledComponent> iterator() {
- return components().iterator();
+ public Iterator<Filter> iterator() {
+ return filters().iterator();
}
//
//
/**
- * Returns all components of this pipeline, listed breadth-first, starting with
+ * Returns all filters of this pipeline, listed breadth-first, starting with
* the source.
*
- * @return All components of this pipeline
+ * @return All filters of this pipeline
*/
- public List<ControlledComponent> components() {
- ImmutableList.Builder<ControlledComponent> components = ImmutableList.builder();
- List<ControlledComponent> currentComponents = Lists.newArrayList();
- components.add(source);
- currentComponents.add(source);
- while (!currentComponents.isEmpty()) {
- Collection<Sink> sinks = this.sinks((Source) currentComponents.remove(0));
- for (Sink sink : sinks) {
- components.add(sink);
- if (sink instanceof Source) {
- currentComponents.add(sink);
- }
+ public List<Filter> filters() {
+ ImmutableList.Builder<Filter> filters = ImmutableList.builder();
+ List<Filter> remainingFilters = Lists.newArrayList();
+ filters.add(source);
+ remainingFilters.add(source);
+ while (!remainingFilters.isEmpty()) {
+ Collection<Filter> sinks = this.filters(remainingFilters.remove(0));
+ for (Filter sink : sinks) {
+ filters.add(sink);
+ remainingFilters.add(sink);
}
}
- return components.build();
+ return filters.build();
}
//
* The source at which to start
* @return A builder for a new pipeline
*/
- public static Builder builder(Source source) {
+ public static Builder builder(Filter source) {
return new Builder(source);
}
public static class Builder {
/** The source of the pipeline. */
- private final Source source;
+ private final Filter source;
- /** The sinks to which each source streams. */
- private Multimap<Source, Sink> nextSinks = ArrayListMultimap.create();
+ /** The filters to which each source streams. */
+ private Multimap<Filter, Filter> nextSinks = ArrayListMultimap.create();
/** The last added source. */
- private Source lastSource;
+ private Filter lastSource;
/**
* Creates a new builder.
* @param source
* The source that starts the pipeline
*/
- private Builder(Source source) {
+ private Builder(Filter source) {
this.source = source;
lastSource = source;
}
/**
- * Adds a {@link Sink} (or {@link Filter} as a recipient for the last added
- * {@link Source}.
+ * Adds a {@link Filter} as a recipient for the last added source.
*
* @param sink
* The sink to add
* @return This builder
- * @throws IllegalStateException
- * if the last added {@link Sink} was not also a {@link Source}
*/
- public Builder to(Sink sink) {
- Preconditions.checkState(lastSource != null, "last added Sink was not a Source");
+ public Builder to(Filter sink) {
nextSinks.put(lastSource, sink);
- lastSource = (sink instanceof Filter) ? (Source) sink : null;
+ lastSource = sink;
return this;
}
/**
* Locates the given source and sets it as the last added node so that the
- * next invocation of {@link #to(Sink)} can “fork” the pipeline.
+ * next invocation of {@link #to(Filter)} can “fork” the pipeline.
*
* @param source
* The source to locate
* @throws IllegalStateException
* if the given source was not previously added as a sink
*/
- public Builder find(Source source) {
+ public Builder find(Filter source) {
Preconditions.checkState(nextSinks.containsValue(source));
lastSource = source;
return this;
}
/**
- * A connection is responsible for streaming audio from one {@link Source} to
- * an arbitrary number of {@link Sink}s it is connected to. A connection is
+ * A connection is responsible for streaming audio from one {@link Filter} to
+ * an arbitrary number of {@link Filter}s it is connected to. A connection is
* started by creating a {@link Thread} wrapping it and starting said thread.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
public class Connection implements Runnable {
/** The source. */
- private final Source source;
+ private final Filter source;
- /** The sinks. */
- private final Collection<Sink> sinks;
+ /** The filters. */
+ private final Collection<Filter> sinks;
/** Whether the feeder was stopped. */
private final AtomicBoolean stopped = new AtomicBoolean(false);
* @param source
* The source of the stream
* @param sinks
- * The sinks to which to stream
+ * The filters to which to stream
*/
- public Connection(Source source, Collection<Sink> sinks) {
+ public Connection(Filter source, Collection<Filter> sinks) {
this.source = source;
this.sinks = sinks;
if (sinks.size() < 2) {
} catch (IOException ioe1) {
throw new IOException(String.format("I/O error while reading from %s.", source.name()), ioe1);
}
- List<Future<Void>> futures = executorService.invokeAll(FluentIterable.from(sinks).transform(new Function<Sink, Callable<Void>>() {
+ List<Future<Void>> futures = executorService.invokeAll(FluentIterable.from(sinks).transform(new Function<Filter, Callable<Void>>() {
@Override
- public Callable<Void> apply(final Sink sink) {
+ public Callable<Void> apply(final Filter sink) {
return new Callable<Void>() {
@Override
* Returns the number of input bytes.
*
* @return The number of input bytes, or {@link Optional#absent()} if the
- * component can not receive input
+ * filter did not receive input
*/
public Optional<Long> input() {
return (input == -1) ? Optional.<Long>absent() : Optional.of(input);
* Returns the number of output bytes.
*
* @return The number of output bytes, or {@link Optional#absent()} if the
- * component can not send output
+ * filter did not send output
*/
public Optional<Long> output() {
return (output == -1) ? Optional.<Long>absent() : Optional.of(output);
+++ /dev/null
-package net.pterodactylus.sonitus.data;
-
-import java.io.IOException;
-
-/**
- * A sink is a destination for audio data. It can be played on speakers, it can
- * be written to a file, or it can be sent to a remote streaming server.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface Sink extends ControlledComponent {
-
- /**
- * Opens this sink using the format parameters of the given metadata.
- *
- * @param metadata
- * The metadata of the stream
- * @throws IOException
- * if an I/O error occurs
- */
- void open(Metadata metadata) throws IOException;
-
- /** Closes this sink. */
- void close();
-
- /**
- * Processes the given buffer of data.
- *
- * @param buffer
- * The data to process
- * @throws IOException
- * if an I/O error occurs
- */
- void process(byte[] buffer) throws IOException;
-
-}
+++ /dev/null
-package net.pterodactylus.sonitus.data;
-
-import java.io.IOException;
-
-/**
- * A source produces an audio stream and accompanying metadata.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface Source extends ControlledComponent {
-
- /**
- * Returns the metadata of the audio stream.
- *
- * @return The metadata of the audio stream
- */
- Metadata metadata();
-
- /**
- * Retrieves data from the audio stream.
- *
- * @param bufferSize
- * The maximum amount of bytes to retrieve from the audio stream
- * @return A buffer filled with up to {@code bufferSize} bytes of data; the
- * returned buffer may contain less data than requested but will not
- * contain excess elements
- * @throws IOException
- * if an I/O error occurs
- */
- byte[] get(int bufferSize) throws IOException;
-
-}
+++ /dev/null
-/*
- * Sonitus - SourceFinishedEvent.java - Copyright © 2013 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sonitus.data.event;
-
-import net.pterodactylus.sonitus.data.Source;
-
-/**
- * Event that is sent to an {@link com.google.common.eventbus.EventBus} when
- * a {@link Source} is no longer in use.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class SourceFinishedEvent {
-
- /** The source that is no longer in use. */
- private final Source source;
-
- /**
- * Creates a new source finished event.
- *
- * @param source
- * The source that is no longer in use
- */
- public SourceFinishedEvent(Source source) {
- this.source = source;
- }
-
- /**
- * Returns the source that is no longer in use
- *
- * @return The source that is no longer in use
- */
- public Source source() {
- return source;
- }
-
-}
import java.io.IOException;
import java.io.OutputStream;
+import net.pterodactylus.sonitus.data.AbstractFilter;
import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.io.ProcessingOutputStream;
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public abstract class AudioProcessingFilter extends BasicFilter {
+public abstract class AudioProcessingFilter extends AbstractFilter implements Filter {
/**
* Creates a new audio processing filter with the given name.
}
//
- // BASICFILTER METHODS
+ // FILTER METHODS
//
@Override
+++ /dev/null
-/*
- * Sonitus - AbstractFilter.java - Copyright © 2013 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sonitus.data.filter;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import net.pterodactylus.sonitus.data.AbstractControlledComponent;
-import net.pterodactylus.sonitus.data.Controller;
-import net.pterodactylus.sonitus.data.Filter;
-import net.pterodactylus.sonitus.data.Metadata;
-
-import com.google.common.io.Closeables;
-
-/**
- * Basic {@link Filter} implementation that pipes its input to its output.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class BasicFilter extends AbstractControlledComponent implements Filter {
-
- /** The input stream from which to read. */
- private InputStream inputStream;
-
- /** The output stream to which to write. */
- private OutputStream outputStream;
-
- /**
- * Creates a new dummy filter with the given name.
- *
- * @param name
- * The name of the filter
- */
- public BasicFilter(String name) {
- super(name);
- }
-
- //
- // CONTROLLED METHODS
- //
-
- @Override
- public List<Controller<?>> controllers() {
- return Collections.emptyList();
- }
-
- //
- // FILTER METHODS
- //
-
- @Override
- public void open(Metadata metadata) throws IOException {
- metadataUpdated(metadata);
- inputStream = createInputStream();
- outputStream = createOutputStream();
- }
-
- @Override
- public void close() {
- try {
- Closeables.close(outputStream, true);
- Closeables.close(inputStream, true);
- } catch (IOException e) {
- /* won’t throw. */
- }
- }
-
- @Override
- public void process(byte[] buffer) throws IOException {
- outputStream.write(buffer);
- outputStream.flush();
- }
-
- @Override
- public byte[] get(int bufferSize) throws IOException {
- byte[] buffer = new byte[bufferSize];
- int read = inputStream.read(buffer);
- if (read == -1) {
- throw new EOFException();
- }
- return Arrays.copyOf(buffer, read);
- }
-
- //
- // SUBCLASS METHODS
- //
-
- /**
- * Creates the input stream from which {@link net.pterodactylus.sonitus.data.Pipeline}
- * will read the audio data. If you override this, you have to override {@link
- * #createOutputStream()}, too!
- *
- * @return The input stream to read from
- * @throws IOException
- * if an I/O error occurs
- */
- protected InputStream createInputStream() throws IOException {
- return new PipedInputStream();
- }
-
- /**
- * Creates the output stream to which {@link net.pterodactylus.sonitus.data.Pipeline}
- * will write the audio data. If you override this, you have to override {@link
- * #createInputStream()}, too!
- *
- * @return The output stream to write to
- * @throws IOException
- * if an I/O error occurs
- */
- protected OutputStream createOutputStream() throws IOException {
- return new PipedOutputStream((PipedInputStream) inputStream);
- }
-
-}
import java.io.OutputStream;
import java.util.logging.Logger;
+import net.pterodactylus.sonitus.data.AbstractFilter;
+import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.data.Metadata;
import net.pterodactylus.sonitus.io.InputStreamDrainer;
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public abstract class ExternalFilter extends BasicFilter {
+public abstract class ExternalFilter extends AbstractFilter implements Filter {
/** The logger. */
private final Logger logger = Logger.getLogger(getClass().getName());
process.destroy();
}
- //
- // BASICFILTER METHODS
- //
-
@Override
protected InputStream createInputStream() throws IOException {
return process.getInputStream();
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
+import net.pterodactylus.sonitus.data.AbstractFilter;
import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.data.Metadata;
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class PredicateFilter extends BasicFilter {
+public class PredicateFilter extends AbstractFilter implements Filter {
/** The predicate. */
private final Predicate<Metadata> metadataPredicate;
import java.io.IOException;
import java.util.logging.Logger;
+import net.pterodactylus.sonitus.data.AbstractFilter;
+import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.data.Metadata;
/**
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class RateLimitingFilter extends BasicFilter {
+public class RateLimitingFilter extends AbstractFilter implements Filter {
/** The logger. */
private static final Logger logger = Logger.getLogger(RateLimitingFilter.class.getName());
}
//
- // CONTROLLED METHODS
+ // FILTER METHODS
//
@Override
return Arrays.<Controller<?>>asList(separationKnob);
}
- //
- // AUDIOPROCESSINGFILTER METHODS
- //
-
@Override
protected int[] processSamples(int[] samples) {
if (samples.length == 1) {
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import net.pterodactylus.sonitus.data.AbstractFilter;
import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.data.Metadata;
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class TimeCounterFilter extends BasicFilter {
+public class TimeCounterFilter extends AbstractFilter implements Filter {
/** The byte counter. */
private final AtomicLong counter = new AtomicLong();
}
//
- // BASICFILTER METHODS
+ // FILTER METHODS
//
@Override
}
//
- // CONTROLLED METHODS
+ // FILTER METHODS
//
@Override
return Arrays.<Controller<?>>asList(volumeFader, muteSwitch);
}
- //
- // AUDIOPROCESSINGFILTER METHODS
- //
-
@Override
protected int[] processSamples(int[] samples) {
int[] processedSamples = new int[samples.length];
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
-import net.pterodactylus.sonitus.data.AbstractControlledComponent;
+import net.pterodactylus.sonitus.data.AbstractFilter;
import net.pterodactylus.sonitus.data.Controller;
+import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.data.Metadata;
-import net.pterodactylus.sonitus.data.Sink;
-import net.pterodactylus.sonitus.data.Source;
import net.pterodactylus.sonitus.data.controller.Fader;
import net.pterodactylus.sonitus.data.controller.Switch;
import net.pterodactylus.sonitus.io.IntegralWriteOutputStream;
import com.google.common.base.Preconditions;
/**
- * {@link Sink} implementation that uses the JDK’s {@link AudioSystem} to play
- * all {@link Source}s.
+ * {@link Filter} implementation that uses the JDK’s {@link AudioSystem} to play
+ * all the audio signal.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class AudioSink extends AbstractControlledComponent implements Sink {
+public class AudioSink extends AbstractFilter {
/** The logger. */
private static final Logger logger = Logger.getLogger(AudioSink.class.getName());
}
//
- // CONTROLLED METHODS
+ // FILTER METHODS
//
@Override
return Arrays.<Controller<?>>asList(volumeFader, muteSwitch);
}
- //
- // SINK METHODS
- //
-
@Override
public void open(Metadata metadata) throws IOException {
Preconditions.checkArgument(metadata.encoding().equalsIgnoreCase("PCM"), "source must be PCM-encoded");
+ super.open(metadata);
AudioFormat audioFormat = new AudioFormat(metadata.frequency(), 16, metadata.channels(), true, false);
try {
sourceDataLine = AudioSystem.getSourceDataLine(audioFormat);
@Override
public void process(byte[] buffer) throws IOException {
sourceDataLineOutputStream.write(buffer);
+ super.process(buffer);
logger.finest(String.format("AudioSink: Wrote %d Bytes.", buffer.length));
}
import java.io.FileOutputStream;
import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
import java.util.logging.Logger;
-import net.pterodactylus.sonitus.data.AbstractControlledComponent;
-import net.pterodactylus.sonitus.data.Controller;
+import net.pterodactylus.sonitus.data.AbstractFilter;
import net.pterodactylus.sonitus.data.Metadata;
-import net.pterodactylus.sonitus.data.Sink;
/**
* {@link net.pterodactylus.sonitus.data.Sink} that writes all received data
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class FileSink extends AbstractControlledComponent implements Sink {
+public class FileSink extends AbstractFilter {
/** The logger. */
private static final Logger logger = Logger.getLogger(FileSink.class.getName());
}
//
- // CONTROLLED METHODS
- //
-
- @Override
- public List<Controller<?>> controllers() {
- return Collections.emptyList();
- }
-
- //
- // SINK METHODS
+ // FILTER METHODS
//
@Override
import java.util.logging.Level;
import java.util.logging.Logger;
-import net.pterodactylus.sonitus.data.AbstractControlledComponent;
+import net.pterodactylus.sonitus.data.AbstractFilter;
import net.pterodactylus.sonitus.data.Controller;
import net.pterodactylus.sonitus.data.Metadata;
-import net.pterodactylus.sonitus.data.Sink;
import net.pterodactylus.sonitus.io.InputStreamDrainer;
import com.google.common.io.BaseEncoding;
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class Icecast2Sink extends AbstractControlledComponent implements Sink {
+public class Icecast2Sink extends AbstractFilter {
/** The logger. */
private static final Logger logger = Logger.getLogger(Icecast2Sink.class.getName());
}
//
- // CONTROLLED METHODS
+ // FILTER METHODS
//
@Override
return Collections.emptyList();
}
- //
- // SINK METHODS
- //
-
@Override
public void open(Metadata metadata) throws IOException {
logger.info(String.format("Connecting to %s:%d...", server, port));
import java.util.Collections;
import java.util.List;
-import net.pterodactylus.sonitus.data.AbstractControlledComponent;
+import net.pterodactylus.sonitus.data.AbstractFilter;
import net.pterodactylus.sonitus.data.Controller;
+import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.data.Metadata;
-import net.pterodactylus.sonitus.data.Source;
import net.pterodactylus.sonitus.io.IdentifyingInputStream;
import com.google.common.base.Optional;
/**
- * A {@link net.pterodactylus.sonitus.data.Source} that is read from the local
- * file system.
+ * A {@link Filter} that reads a file from the local file system and does not
+ * expect any input.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class FileSource extends AbstractControlledComponent implements Source {
+public class FileSource extends AbstractFilter {
/** The path of the file. */
private final String path;
}
//
- // CONTROLLED METHODS
+ // FILTER METHODS
//
@Override
return Collections.emptyList();
}
- //
- // SOURCE METHODS
- //
-
@Override
public byte[] get(int bufferSize) throws IOException {
byte[] buffer = new byte[bufferSize];
import java.util.logging.Logger;
import javax.swing.event.EventListenerList;
-import net.pterodactylus.sonitus.data.AbstractControlledComponent;
+import net.pterodactylus.sonitus.data.AbstractFilter;
import net.pterodactylus.sonitus.data.Controller;
+import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.data.Metadata;
-import net.pterodactylus.sonitus.data.Source;
import com.google.inject.Inject;
/**
- * {@link Source} implementation that simply forwards another source and
- * supports changing the source without letting the {@link
- * net.pterodactylus.sonitus.data.Sink} know.
+ * {@link Filter} implementation that simply forwards data from another filter
+ * and supports changing the source without letting downstream filters know.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class MultiSource extends AbstractControlledComponent implements Source {
+public class MultiSource extends AbstractFilter {
/** The logger. */
private static final Logger logger = Logger.getLogger(MultiSource.class.getName());
private final EventListenerList sourceFinishedListeners = new EventListenerList();
/** The current source. */
- private final AtomicReference<Source> source = new AtomicReference<Source>();
+ private final AtomicReference<Filter> source = new AtomicReference<Filter>();
/** Whether the source was changed. */
private boolean sourceChanged;
* @param source
* The new source to use
*/
- public void setSource(Source source) {
+ public void setSource(Filter source) {
checkNotNull(source, "source must not be null");
- Source oldSource = this.source.getAndSet(source);
+ Filter oldSource = this.source.getAndSet(source);
if (!source.equals(oldSource)) {
synchronized (this.source) {
sourceChanged = true;
/**
* Notifies all registered listeners that the current source finished playing
- * and that a new source should be {@link #setSource(Source) set}.
+ * and that a new source should be {@link #setSource(Filter) set}.
*
* @see SourceFinishedListener
*/
}
//
- // CONTROLLED METHODS
+ // FILTER METHODS
//
@Override
return super.metadata();
}
- //
- // SOURCE METHODS
- //
-
@Override
public byte[] get(int bufferSize) throws EOFException, IOException {
while (true) {
// PRIVATE METHODS
//
- /** Waits for a new source to be {@link #setSource(Source) set}. */
+ /** Waits for a new source to be {@link #setSource(Filter) set}. */
private void waitForNewSource() {
fireSourceFinished();
synchronized (source) {
import java.util.EventListener;
-import net.pterodactylus.sonitus.data.Source;
-
/**
* Interface for {@link MultiSource} notifications if a source is finished
* playing.
import java.util.Map;
import java.util.logging.Logger;
-import net.pterodactylus.sonitus.data.AbstractControlledComponent;
+import net.pterodactylus.sonitus.data.AbstractFilter;
import net.pterodactylus.sonitus.data.ContentMetadata;
import net.pterodactylus.sonitus.data.Controller;
import net.pterodactylus.sonitus.data.FormatMetadata;
import net.pterodactylus.sonitus.data.Metadata;
-import net.pterodactylus.sonitus.data.Source;
import net.pterodactylus.sonitus.io.MetadataStream;
import com.google.common.base.Optional;
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class StreamSource extends AbstractControlledComponent implements Source {
+public class StreamSource extends AbstractFilter {
/** The logger. */
private static final Logger logger = Logger.getLogger(StreamSource.class.getName());
}
//
- // CONTROLLED METHODS
+ // FILTER METHODS
//
@Override
return Collections.emptyList();
}
- //
- // SOURCE METHODS
- //
-
@Override
public Metadata metadata() {
Optional<ContentMetadata> streamMetadata = metadataStream.getContentMetadata();
+++ /dev/null
-/*
- * Sonitus - ComponentInfoPanel.java - Copyright © 2013 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sonitus.gui;
-
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-import java.awt.Insets;
-import javax.swing.BorderFactory;
-import javax.swing.Box;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-
-import net.pterodactylus.sonitus.data.ControlledComponent;
-import net.pterodactylus.sonitus.data.Controller;
-import net.pterodactylus.sonitus.data.FormatMetadata;
-import net.pterodactylus.sonitus.data.controller.Fader;
-import net.pterodactylus.sonitus.data.controller.Knob;
-import net.pterodactylus.sonitus.data.controller.Switch;
-
-import com.google.common.base.Optional;
-
-/**
- * Panel that shows information about a {@link ControlledComponent}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ComponentInfoPanel extends JPanel {
-
- /** The name of the component. */
- private final JLabel headerLabel = new JLabel();
-
- /** The number of received input bytes. */
- private final JLabel inputLabel = new JLabel();
-
- /** The number of sent output bytes. */
- private final JLabel outputLabel = new JLabel();
-
- /** The current format metadata. */
- private final JLabel formatLabel = new JLabel();
-
- /**
- * Creates a new component info panel.
- *
- * @param controlledComponent
- * The component to display
- */
- public ComponentInfoPanel(ControlledComponent controlledComponent) {
- super(new GridBagLayout());
-
- setPreferredSize(new Dimension(300, 0));
- createPanel(controlledComponent);
- }
-
- //
- // ACTIONS
- //
-
- /**
- * Sets the number of received input bytes.
- *
- * @param input
- * The number of received input bytes
- * @return This panel
- */
- public ComponentInfoPanel input(Optional<Long> input) {
- if (input.isPresent()) {
- inputLabel.setText(format(input.get()));
- } else {
- inputLabel.setText("");
- }
- return this;
- }
-
- /**
- * Sets the number of sent output bytes.
- *
- * @param output
- * The number of sent output input bytes
- * @return This panel
- */
- public ComponentInfoPanel output(Optional<Long> output) {
- if (output.isPresent()) {
- outputLabel.setText(format(output.get()));
- } else {
- outputLabel.setText("");
- }
- return this;
- }
-
- /**
- * Sets the current format metadata.
- *
- * @param metadata
- * The format metadata
- * @return This panel
- */
- public ComponentInfoPanel format(Optional<FormatMetadata> metadata) {
- if (metadata.isPresent()) {
- formatLabel.setText(metadata.get().toString());
- } else {
- formatLabel.setText("");
- }
- return this;
- }
-
- //
- // PRIVATE METHODS
- //
-
- /**
- * Creates the panel for the given controlled component.
- *
- * @param controlledComponent
- * The controlled component
- */
- private void createPanel(ControlledComponent controlledComponent) {
- setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
-
- headerLabel.setText(controlledComponent.name());
- headerLabel.setFont(headerLabel.getFont().deriveFont(Font.BOLD));
-
- int line = 0;
- add(headerLabel, new GridBagConstraints(0, line++, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
- add(new JLabel("Input"), new GridBagConstraints(0, line, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(18, 0, 0, 0), 0, 0));
- add(inputLabel, new GridBagConstraints(1, line++, 1, 1, 1.0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(18, 6, 0, 0), 0, 0));
- add(new JLabel("Output"), new GridBagConstraints(0, line, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0));
- add(outputLabel, new GridBagConstraints(1, line++, 1, 1, 1.0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
- add(new JLabel("Format"), new GridBagConstraints(0, line, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0));
- add(formatLabel, new GridBagConstraints(1, line++, 1, 1, 1.0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
-
- /* add the controllers. */
- for (Controller<?> controller : controlledComponent.controllers()) {
- add(new JLabel(controller.name()), new GridBagConstraints(0, line, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(6, 0, 0, 6), 0, 0));
- if (controller instanceof Fader) {
- add(new FaderPanel((Fader) controller), new GridBagConstraints(1, line++, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 0, 0, 0), 0, 0));
- } else if (controller instanceof Switch) {
- add(new SwitchPanel((Switch) controller), new GridBagConstraints(1, line++, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 0, 0, 0), 0, 0));
- } else if (controller instanceof Knob) {
- add(new KnobPanel((Knob) controller), new GridBagConstraints(1, line++, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 0, 0, 0), 0, 0));
- }
- }
-
- add(Box.createVerticalGlue(), new GridBagConstraints(1, line++, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(6, 6, 0, 0), 0, 0));
- }
-
- /**
- * Formats the number using SI prefixes so that a maximum of 3 digits are
- * shown.
- *
- * @param number
- * The number to format
- * @return The formatted number
- */
- private static String format(long number) {
- String[] prefixes = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei" };
- double shortenedNumber = number;
- for (String prefix : prefixes) {
- if (shortenedNumber < 1000) {
- return String.format("%.1f %sB", shortenedNumber, prefix);
- }
- shortenedNumber /= 1024;
- }
- return String.format("%.1e B", (double) number);
- }
-
-}
--- /dev/null
+/*
+ * Sonitus - FilterInfoPanel.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sonitus.gui;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.pterodactylus.sonitus.data.Controller;
+import net.pterodactylus.sonitus.data.Filter;
+import net.pterodactylus.sonitus.data.FormatMetadata;
+import net.pterodactylus.sonitus.data.controller.Fader;
+import net.pterodactylus.sonitus.data.controller.Knob;
+import net.pterodactylus.sonitus.data.controller.Switch;
+
+import com.google.common.base.Optional;
+
+/**
+ * Panel that shows information about a {@link Filter}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class FilterInfoPanel extends JPanel {
+
+ /** The name of the filter. */
+ private final JLabel headerLabel = new JLabel();
+
+ /** The number of received input bytes. */
+ private final JLabel inputLabel = new JLabel();
+
+ /** The number of sent output bytes. */
+ private final JLabel outputLabel = new JLabel();
+
+ /** The current format metadata. */
+ private final JLabel formatLabel = new JLabel();
+
+ /**
+ * Creates a new filter info panel.
+ *
+ * @param filter
+ * The filter to display
+ */
+ public FilterInfoPanel(Filter filter) {
+ super(new GridBagLayout());
+
+ setPreferredSize(new Dimension(300, 0));
+ createPanel(filter);
+ }
+
+ //
+ // ACTIONS
+ //
+
+ /**
+ * Sets the number of received input bytes.
+ *
+ * @param input
+ * The number of received input bytes
+ * @return This panel
+ */
+ public FilterInfoPanel input(Optional<Long> input) {
+ if (input.isPresent()) {
+ inputLabel.setText(format(input.get()));
+ } else {
+ inputLabel.setText("");
+ }
+ return this;
+ }
+
+ /**
+ * Sets the number of sent output bytes.
+ *
+ * @param output
+ * The number of sent output input bytes
+ * @return This panel
+ */
+ public FilterInfoPanel output(Optional<Long> output) {
+ if (output.isPresent()) {
+ outputLabel.setText(format(output.get()));
+ } else {
+ outputLabel.setText("");
+ }
+ return this;
+ }
+
+ /**
+ * Sets the current format metadata.
+ *
+ * @param metadata
+ * The format metadata
+ * @return This panel
+ */
+ public FilterInfoPanel format(Optional<FormatMetadata> metadata) {
+ if (metadata.isPresent()) {
+ formatLabel.setText(metadata.get().toString());
+ } else {
+ formatLabel.setText("");
+ }
+ return this;
+ }
+
+ //
+ // PRIVATE METHODS
+ //
+
+ /**
+ * Creates the panel for the given filter.
+ *
+ * @param filter
+ * The filter to create a panel for
+ */
+ private void createPanel(Filter filter) {
+ setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
+
+ headerLabel.setText(filter.name());
+ headerLabel.setFont(headerLabel.getFont().deriveFont(Font.BOLD));
+
+ int line = 0;
+ add(headerLabel, new GridBagConstraints(0, line++, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+ add(new JLabel("Input"), new GridBagConstraints(0, line, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(18, 0, 0, 0), 0, 0));
+ add(inputLabel, new GridBagConstraints(1, line++, 1, 1, 1.0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(18, 6, 0, 0), 0, 0));
+ add(new JLabel("Output"), new GridBagConstraints(0, line, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0));
+ add(outputLabel, new GridBagConstraints(1, line++, 1, 1, 1.0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
+ add(new JLabel("Format"), new GridBagConstraints(0, line, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0));
+ add(formatLabel, new GridBagConstraints(1, line++, 1, 1, 1.0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
+
+ /* add the controllers. */
+ for (Controller<?> controller : filter.controllers()) {
+ add(new JLabel(controller.name()), new GridBagConstraints(0, line, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(6, 0, 0, 6), 0, 0));
+ if (controller instanceof Fader) {
+ add(new FaderPanel((Fader) controller), new GridBagConstraints(1, line++, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 0, 0, 0), 0, 0));
+ } else if (controller instanceof Switch) {
+ add(new SwitchPanel((Switch) controller), new GridBagConstraints(1, line++, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 0, 0, 0), 0, 0));
+ } else if (controller instanceof Knob) {
+ add(new KnobPanel((Knob) controller), new GridBagConstraints(1, line++, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(6, 0, 0, 0), 0, 0));
+ }
+ }
+
+ add(Box.createVerticalGlue(), new GridBagConstraints(1, line++, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(6, 6, 0, 0), 0, 0));
+ }
+
+ /**
+ * Formats the number using SI prefixes so that a maximum of 3 digits are
+ * shown.
+ *
+ * @param number
+ * The number to format
+ * @return The formatted number
+ */
+ private static String format(long number) {
+ String[] prefixes = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei" };
+ double shortenedNumber = number;
+ for (String prefix : prefixes) {
+ if (shortenedNumber < 1000) {
+ return String.format("%.1f %sB", shortenedNumber, prefix);
+ }
+ shortenedNumber /= 1024;
+ }
+ return String.format("%.1e B", (double) number);
+ }
+
+}
import javax.swing.Timer;
import javax.swing.WindowConstants;
-import net.pterodactylus.sonitus.data.ControlledComponent;
+import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.data.Pipeline;
-import net.pterodactylus.sonitus.gui.PipelinePanel.ComponentSelectionListener;
+import net.pterodactylus.sonitus.gui.PipelinePanel.FilterSelectionListener;
import net.pterodactylus.sonitus.main.Version;
import com.google.common.base.Optional;
/** The pipeline to display. */
private final Pipeline pipeline;
- /** The tabbed pane displaying all controlled components. */
+ /** The tabbed pane displaying all pipelines. */
private final JTabbedPane tabbedPane = new JTabbedPane();
/** The info panel card layout. */
/** The info panel. */
private final JPanel infoPanel = new JPanel(infoPanelCardLayout);
- /** The mapping from controlled components to info panels. */
- private final Map<ControlledComponent, ComponentInfoPanel> controlledInfoPanels = Maps.newHashMap();
+ /** The mapping from filters to info panels. */
+ private final Map<Filter, FilterInfoPanel> filterInfoPanels = Maps.newHashMap();
/**
* Creates a new main window.
tabbedPane.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
final JPanel pipelineInfoPanel = new JPanel(new BorderLayout(12, 12));
PipelinePanel pipelinePanel = new PipelinePanel(pipeline);
- pipelinePanel.addComponentHoverListener(new ComponentSelectionListener() {
+ pipelinePanel.addFilterSelectionListener(new FilterSelectionListener() {
@Override
- public void componentSelected(ControlledComponent controlledComponent) {
- infoPanelCardLayout.show(infoPanel, controlledComponent.name());
+ public void filterSelected(Filter filter) {
+ infoPanelCardLayout.show(infoPanel, filter.name());
}
});
pipelineInfoPanel.add(pipelinePanel, BorderLayout.CENTER);
getContentPane().add(tabbedPane, BorderLayout.CENTER);
setSize(new Dimension(800, 450));
- /* create info panels for all components. */
- for (ControlledComponent controlledComponent : pipeline) {
- ComponentInfoPanel componentInfoPanel = new ComponentInfoPanel(controlledComponent);
- infoPanel.add(componentInfoPanel, controlledComponent.name());
- controlledInfoPanels.put(controlledComponent, componentInfoPanel);
+ /* create info panels for all filters. */
+ for (Filter fliter : pipeline) {
+ FilterInfoPanel filterInfoPanel = new FilterInfoPanel(fliter);
+ infoPanel.add(filterInfoPanel, fliter.name());
+ filterInfoPanels.put(fliter, filterInfoPanel);
}
Timer timer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
/* update all info panels. */
- for (ControlledComponent controlled : MainWindow.this.pipeline) {
- ComponentInfoPanel componentInfoPanel = controlledInfoPanels.get(controlled);
- componentInfoPanel.input(MainWindow.this.pipeline.trafficCounter(controlled).input());
- componentInfoPanel.output(MainWindow.this.pipeline.trafficCounter(controlled).output());
- componentInfoPanel.format(Optional.of(controlled.metadata().format()));
+ for (Filter filter : MainWindow.this.pipeline) {
+ FilterInfoPanel filterInfoPanel = filterInfoPanels.get(filter);
+ filterInfoPanel.input(MainWindow.this.pipeline.trafficCounter(filter).input());
+ filterInfoPanel.output(MainWindow.this.pipeline.trafficCounter(filter).output());
+ filterInfoPanel.format(Optional.of(filter.metadata().format()));
}
}
});
import javax.swing.UIManager;
import javax.swing.event.EventListenerList;
-import net.pterodactylus.sonitus.data.ControlledComponent;
+import net.pterodactylus.sonitus.data.Filter;
import net.pterodactylus.sonitus.data.Metadata;
import net.pterodactylus.sonitus.data.MetadataListener;
import net.pterodactylus.sonitus.data.Pipeline;
-import net.pterodactylus.sonitus.data.Sink;
-import net.pterodactylus.sonitus.data.Source;
/**
- * {@link JPanel} that displays all components of a {@link Pipeline}.
+ * {@link JPanel} that displays all filters of a {@link Pipeline}.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
/** The pipeline being displayed. */
private final Pipeline pipeline;
- /** The component hover listeners. */
- private final EventListenerList componentSelectionListeners = new EventListenerList();
+ /** The filter selection listeners. */
+ private final EventListenerList filterSelectionListeners = new EventListenerList();
- /** The currently selected component. */
- private JComponent selectedComponent;
+ /** The currently selected filter. */
+ private JComponent selectedFilter;
/**
* Creates a new pipeline panel displaying the given pipeline.
//
/**
- * Adds the given component selection listener to this panel.
+ * Adds the given filter selection listener to this panel.
*
- * @param componentSelectionListener
- * The component selection listener to add
+ * @param filterSelectionListener
+ * The filter selection listener to add
*/
- public void addComponentHoverListener(ComponentSelectionListener componentSelectionListener) {
- componentSelectionListeners.add(ComponentSelectionListener.class, componentSelectionListener);
+ public void addFilterSelectionListener(FilterSelectionListener filterSelectionListener) {
+ filterSelectionListeners.add(FilterSelectionListener.class, filterSelectionListener);
}
//
/* clear everything. */
removeAll();
- /* count all sinks. */
+ /* count all filters. */
int sinkCount = 0;
- for (ControlledComponent component : pipeline.components()) {
- if (!(component instanceof Source)) {
- logger.finest(String.format("%s is not a Source, skipping.", component.name()));
- sinkCount++;
- continue;
- }
- Collection<Sink> sinks = pipeline.sinks((Source) component);
- logger.finest(String.format("%s has %d sinks: %s", component.name(), sinks.size(), sinks));
+ for (Filter filter : pipeline.filters()) {
+ Collection<Filter> sinks = pipeline.filters(filter);
+ logger.finest(String.format("%s has %d filters: %s", filter.name(), sinks.size(), sinks));
if (sinks.isEmpty()) {
sinkCount++;
}
gridCellCount *= n;
}
- /* paint all components recursively. */
- addControlled(pipeline.source(), 0, 0, gridCellCount, null);
+ /* paint all filters recursively. */
+ addFilter(pipeline.source(), 0, 0, gridCellCount, null);
}
/**
- * Displays the given component.
+ * Displays the given filter.
*
- * @param controlledComponent
- * The component to add this panel.
+ * @param filter
+ * The filter to add this panel.
* @param level
- * The level at which to show the component (the source is level {@code 0})
+ * The level at which to show the filter (the source is level {@code 0})
* @param position
- * The position at which to display the component
+ * The position at which to display the filter
* @param width
- * The width of the component in grid cells
+ * The width of the filter in grid cells
*/
- private void addControlled(final ControlledComponent controlledComponent, int level, int position, int width, ControlledComponent parentComponent) {
- /* create a GUI component that displays the component. */
- final JPanel componentPanel = createComponentPanel(controlledComponent, parentComponent);
- componentPanel.addMouseListener(new MouseAdapter() {
+ private void addFilter(final Filter filter, int level, int position, int width, Filter parentFilter) {
+ /* create a GUI component that displays the filter. */
+ final JPanel filterPanel = createFilterPanel(filter, parentFilter);
+ filterPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
for (Component component : getComponents()) {
component.setBackground(UIManager.getColor("Panel.background"));
}
- for (ComponentSelectionListener componentSelectionListener : componentSelectionListeners.getListeners(ComponentSelectionListener.class)) {
- componentPanel.setBackground(Color.LIGHT_GRAY);
- componentSelectionListener.componentSelected(controlledComponent);
+ for (FilterSelectionListener filterSelectionListener : filterSelectionListeners.getListeners(FilterSelectionListener.class)) {
+ filterPanel.setBackground(Color.LIGHT_GRAY);
+ filterSelectionListener.filterSelected(filter);
}
- selectedComponent = componentPanel;
+ selectedFilter = filterPanel;
}
@Override
public void mouseEntered(MouseEvent mouseEvent) {
- if (componentPanel != selectedComponent) {
- componentPanel.setBackground(Color.white);
+ if (filterPanel != selectedFilter) {
+ filterPanel.setBackground(Color.white);
}
}
@Override
public void mouseExited(MouseEvent mouseEvent) {
- if (componentPanel != selectedComponent) {
- componentPanel.setBackground(UIManager.getColor("Panel.background"));
+ if (filterPanel != selectedFilter) {
+ filterPanel.setBackground(UIManager.getColor("Panel.background"));
}
}
});
- /* show component. */
- add(componentPanel, new GridBagConstraints(position, level, width, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+ /* show filter. */
+ add(filterPanel, new GridBagConstraints(position, level, width, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
- /* if the component does not have connected sinks, exit here. */
- if (!(controlledComponent instanceof Source)) {
+ /* if the filter does not have connected filters, exit here. */
+ Collection<Filter> sinks = pipeline.filters(filter);
+ if (sinks.isEmpty()) {
add(new JPanel(), new GridBagConstraints(position, 999, width, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
return;
}
- /* iterate over the component’s sinks. */
- Collection<Sink> sinks = pipeline.sinks((Source) controlledComponent);
+ /* iterate over the filter’s connected filters. */
if (!sinks.isEmpty()) {
int sinkWidth = width / sinks.size();
int sinkIndex = 0;
- for (Sink connectedSink : sinks) {
- /* distribute all sinks evenly below this source. */
- addControlled(connectedSink, level + 1, position + sinkIndex * sinkWidth, sinkWidth, controlledComponent);
+ for (Filter connectedSink : sinks) {
+ /* distribute all filters evenly below this source. */
+ addFilter(connectedSink, level + 1, position + sinkIndex * sinkWidth, sinkWidth, filter);
sinkIndex++;
}
}
}
/**
- * Creates a panel displaying a single component.
+ * Creates a panel displaying a single filter.
*
- * @param controlledComponent
- * The component to display
+ * @param filter
+ * The filter to display
* @return The created panel
*/
- private static JPanel createComponentPanel(final ControlledComponent controlledComponent, final ControlledComponent parentComponent) {
- JPanel componentPanel = new JPanel(new BorderLayout(12, 12));
- componentPanel.setBorder(createCompoundBorder(createEtchedBorder(), createEmptyBorder(0, 4, 0, 3)));
- componentPanel.add(new JLabel(controlledComponent.name()), BorderLayout.WEST);
- final JLabel titleLabel = new JLabel(controlledComponent.metadata().fullTitle());
+ private static JPanel createFilterPanel(final Filter filter, final Filter parentFilter) {
+ JPanel filterPanel = new JPanel(new BorderLayout(12, 12));
+ filterPanel.setBorder(createCompoundBorder(createEtchedBorder(), createEmptyBorder(0, 4, 0, 3)));
+ filterPanel.add(new JLabel(filter.name()), BorderLayout.WEST);
+ final JLabel titleLabel = new JLabel(filter.metadata().fullTitle());
titleLabel.setFont(titleLabel.getFont().deriveFont(titleLabel.getFont().getSize2D() * 0.8f));
- componentPanel.add(titleLabel, BorderLayout.EAST);
- if (parentComponent != null) {
- titleLabel.setVisible(!parentComponent.metadata().fullTitle().equals(controlledComponent.metadata().fullTitle()));
+ filterPanel.add(titleLabel, BorderLayout.EAST);
+ if (parentFilter != null) {
+ titleLabel.setVisible(!parentFilter.metadata().fullTitle().equals(filter.metadata().fullTitle()));
}
- controlledComponent.addMetadataListener(new MetadataListener() {
+ filter.addMetadataListener(new MetadataListener() {
@Override
- public void metadataUpdated(ControlledComponent component, Metadata metadata) {
+ public void metadataUpdated(Filter filter, Metadata metadata) {
titleLabel.setText(metadata.fullTitle());
- titleLabel.setVisible((parentComponent == null) || !parentComponent.metadata().fullTitle().equals(metadata.fullTitle()));
+ titleLabel.setVisible((parentFilter == null) || !parentFilter.metadata().fullTitle().equals(metadata.fullTitle()));
}
});
- return componentPanel;
+ return filterPanel;
}
/**
* Interface for objects that want to be notified if the user moves the mouse
- * cursor over a controlled component.
+ * cursor over a filter.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
- public static interface ComponentSelectionListener extends EventListener {
+ public static interface FilterSelectionListener extends EventListener {
/**
- * Notifies the listener that the mouse is now over the given controlled
- * component.
+ * Notifies the listener that the mouse is now over the given filter.
*
- * @param controlledComponent
- * The controlled component now under the mouse
+ * @param filter
+ * The filter now under the mouse
*/
- void componentSelected(ControlledComponent controlledComponent);
+ void filterSelected(Filter filter);
}
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
-import javax.swing.JLabel;
import javax.swing.JPanel;
import net.pterodactylus.sonitus.data.controller.Switch;
/**
* Creates a new fader panel.
*
- * @param fader
- * The fader being controlled
+ * @param switchController
+ * The switch being controlled
*/
public SwitchPanel(final Switch switchController) {
super(new GridBagLayout());