diff --git a/src/common/serialization/clap/README.md b/src/common/serialization/clap/README.md
index b037db3e..d8f8eaf0 100644
--- a/src/common/serialization/clap/README.md
+++ b/src/common/serialization/clap/README.md
@@ -9,6 +9,7 @@ Yabridge currently tracks CLAP 1.1.1. The implementation status for CLAP's core
| core feature | status |
| ----------------------------------------- | ------------------------------------------------------ |
| Core plugin and host functionality | :warning: Everything but actual audio processing works |
+| Streams | :heavy_check_mark: |
| `clap.plugin-factory` | :heavy_check_mark: |
| `clap.plugin-invalidation-factory/draft0` | :x: Not supported yet |
diff --git a/src/common/serialization/clap/stream.cpp b/src/common/serialization/clap/stream.cpp
new file mode 100644
index 00000000..b3e49b04
--- /dev/null
+++ b/src/common/serialization/clap/stream.cpp
@@ -0,0 +1,117 @@
+// yabridge: a Wine plugin bridge
+// Copyright (C) 2020-2022 Robbert van der Helm
+//
+// 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 destates.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "stream.h"
+
+namespace clap {
+namespace stream {
+
+/**
+ * We'll try to read the host's `clap_istream_t` in 1 MB chunks.
+ */
+constexpr size_t read_chunk_size = 1 << 20;
+
+Stream::Stream() {}
+
+Stream::Stream(const clap_istream_t& original) {
+ // CLAP streams have no length indication. A plugin could do something like
+ // prepending the stream's length to the stream, but we can't do that. So
+ // instead we'll try to read in 1 MB chunks until we reach end of file. Even
+ // if the stream's size is over 1 MB, the host may still return less than 1
+ // MB at a time at its discretion.
+ size_t stream_length = 0;
+ while (true) {
+ // Start by reserving enough capacity to read 1 MB
+ buffer_.resize(stream_length + read_chunk_size);
+ const int64_t num_bytes_read = original.read(
+ &original, buffer_.data() + stream_length, read_chunk_size);
+
+ // We're done when we reach the end of the file
+ if (num_bytes_read <= 0) {
+ break;
+ }
+
+ stream_length += num_bytes_read;
+ }
+
+ // Trim the excess reserved space
+ buffer_.resize(stream_length);
+}
+
+const clap_ostream_t* Stream::ostream() {
+ ostream_vtable.write = ostream_write;
+ ostream_vtable.ctx = this;
+
+ return &ostream_vtable;
+}
+
+const clap_istream_t* Stream::istream() {
+ istream_vtable.read = istream_read;
+ istream_vtable.ctx = this;
+
+ return &istream_vtable;
+}
+
+void Stream::write_to_stream(const clap_ostream_t& original) const {
+ // The host may not let us write the whole stream all at once, so we need to
+ // keep track of how many bytes we've written and keep going until
+ // everything has been written back to the host.
+ size_t num_bytes_written = 0;
+ while (num_bytes_written < buffer_.size()) {
+ const int64_t actual_written_bytes =
+ original.write(&original, buffer_.data() + num_bytes_written,
+ buffer_.size() - num_bytes_written);
+ assert(actual_written_bytes > 0);
+
+ num_bytes_written += actual_written_bytes;
+ }
+}
+
+int64_t CLAP_ABI Stream::ostream_write(const struct clap_ostream* stream,
+ const void* buffer,
+ uint64_t size) {
+ assert(stream && stream->ctx && buffer);
+ auto self = static_cast(stream->ctx);
+
+ // We can just read everything at the same time
+ const size_t start_pos = self->buffer_.size();
+ self->buffer_.resize(start_pos + size);
+ std::copy_n(static_cast(buffer), size,
+ self->buffer_.data() + start_pos);
+
+ return static_cast(size);
+}
+
+int64_t CLAP_ABI Stream::istream_read(const struct clap_istream* stream,
+ void* buffer,
+ uint64_t size) {
+ assert(stream && stream->ctx && buffer);
+ auto self = static_cast(stream->ctx);
+
+ // `self->read_pos` is a cursor in the buffer. CLAP streams always read from
+ // begin to end with no way to rewind.
+ const size_t num_bytes_read = std::min(
+ static_cast(size), self->buffer_.size() - self->read_pos);
+
+ std::copy_n(self->buffer_.data() + self->read_pos, num_bytes_read,
+ static_cast(buffer));
+ self->read_pos += num_bytes_read;
+
+ return static_cast(num_bytes_read);
+}
+
+} // namespace stream
+} // namespace clap
diff --git a/src/common/serialization/clap/stream.h b/src/common/serialization/clap/stream.h
new file mode 100644
index 00000000..bb73a4d9
--- /dev/null
+++ b/src/common/serialization/clap/stream.h
@@ -0,0 +1,92 @@
+// yabridge: a Wine plugin bridge
+// Copyright (C) 2020-2022 Robbert van der Helm
+//
+// 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 destates.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#pragma once
+
+#include
+
+#include
+#include
+
+// Serialization messages for `clap/stream.h`
+
+namespace clap {
+namespace stream {
+
+/**
+ * A serialization wrapper around streams that can be used as both a
+ * `clap_istream_t` and a `clap_ostream_t`.
+ */
+class Stream {
+ public:
+ /**
+ * Create an empty stream that can be written to by the plugin using
+ * `ostream()`, and then written back to the host using
+ * `write_to_ostream()`.
+ */
+ Stream();
+
+ /**
+ * Read a `clap_istream_t` from the host to a buffer. The results are
+ * written to a buffer that can be serialized and send to the other side.
+ */
+ Stream(const clap_istream_t& original);
+
+ /**
+ * Get a `clap_ostream_t` for this buffer that the plugin can write to. This
+ * is only valid as long as this object is not moved.
+ */
+ const clap_ostream_t* ostream();
+ /**
+ * Get a `clap_istream_t` for this buffer that the plugin can read the
+ * buffer from. This is only valid as long as this object is not moved.
+ */
+ const clap_istream_t* istream();
+
+ /**
+ * Write the entire buffer to a host provided `clap_ostream_t`.
+ */
+ void write_to_stream(const clap_ostream_t& original) const;
+
+ template
+ void serialize(S& s) {
+ s.container1b(buffer_, 50 << 20);
+ }
+
+ protected:
+ static int64_t CLAP_ABI ostream_write(const struct clap_ostream* stream,
+ const void* buffer,
+ uint64_t size);
+
+ static int64_t CLAP_ABI istream_read(const struct clap_istream* stream,
+ void* buffer,
+ uint64_t size);
+
+ private:
+ std::vector buffer_;
+
+ /**
+ * The current position in the buffer used in `istream_read()`.
+ */
+ size_t read_pos = 0;
+
+ // These are populated in the `ostream()` and `istream()` methods
+ clap_ostream_t ostream_vtable{};
+ clap_istream_t istream_vtable{};
+};
+
+} // namespace stream
+} // namespace clap
diff --git a/src/plugin/meson.build b/src/plugin/meson.build
index c0139462..f8ad36e5 100644
--- a/src/plugin/meson.build
+++ b/src/plugin/meson.build
@@ -82,6 +82,7 @@ if with_clap
'../common/serialization/clap/ext/params.cpp',
'../common/serialization/clap/host.cpp',
'../common/serialization/clap/plugin.cpp',
+ '../common/serialization/clap/stream.cpp',
'../common/utils.cpp',
'../include/llvm/small-vector.cpp',
'bridges/clap-impls/plugin-proxy.cpp',
diff --git a/src/wine-host/meson.build b/src/wine-host/meson.build
index 0a4beedd..d8f9f550 100644
--- a/src/wine-host/meson.build
+++ b/src/wine-host/meson.build
@@ -85,6 +85,7 @@ if with_clap
'../common/serialization/clap/ext/params.cpp',
'../common/serialization/clap/host.cpp',
'../common/serialization/clap/plugin.cpp',
+ '../common/serialization/clap/stream.cpp',
'bridges/clap-impls/host-proxy.cpp',
'bridges/clap.cpp',
)