> $GITHUB_ENV
+ echo "### Changelog from version ${{ env.previous_tag }} to ${{ env.version }}:" >> $GITHUB_ENV
+ echo "$commits" >> $GITHUB_ENV
+ echo "EOF" >> $GITHUB_ENV
+
+ - name: Create GitHub release
+ uses: actions/create-release@latest
+ env:
+ GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
+ with:
+ tag_name: "${{ env.version }}"
+ release_name: "${{ env.version }}"
+ body: "${{ env.release_body }}"
+ draft: false
+ prerelease: false
diff --git a/src/lib/audio_common/.github/workflows/doxygen-deployment.yml b/src/lib/audio_common/.github/workflows/doxygen-deployment.yml
new file mode 100644
index 000000000..fe991d15d
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/doxygen-deployment.yml
@@ -0,0 +1,45 @@
+name: Doxygen Deployment
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ doxygen_generation:
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Generate Doxygen
+ uses: mattnotmitt/doxygen-action@edge
+ with:
+ doxyfile-path: ".github/Doxyfile"
+
+ - name: Create redirect for root
+ run: |
+ mkdir -p redirect
+ echo '' > redirect/index.html
+
+ - name: Deploy Doxygen page
+ uses: peaceiris/actions-gh-pages@v4
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_branch: gh-pages
+ publish_dir: docs
+ destination_dir: ${{ github.event.release.tag_name }}
+ keep_files: true
+
+ - name: Deploy redirect to root
+ uses: peaceiris/actions-gh-pages@v4
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_branch: gh-pages
+ publish_dir: redirect
+ keep_files: true
diff --git a/src/lib/audio_common/.github/workflows/foxy-build-test.yml b/src/lib/audio_common/.github/workflows/foxy-build-test.yml
new file mode 100644
index 000000000..08797924c
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/foxy-build-test.yml
@@ -0,0 +1,28 @@
+name: Foxy Build and Test
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: "0 5 * * 1"
+
+jobs:
+ foxy_build_and_test:
+ runs-on: ubuntu-latest
+ container:
+ image: ros:foxy
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build and Test
+ uses: ros-tooling/action-ros-ci@v0.4
+ with:
+ target-ros2-distro: foxy
+ coverage-result: false
+ skip-tests: true
+ colcon-defaults: |
+ {
+ "test": {
+ "parallel-workers" : 1
+ }
+ }
diff --git a/src/lib/audio_common/.github/workflows/foxy-docker-push.yml b/src/lib/audio_common/.github/workflows/foxy-docker-push.yml
new file mode 100644
index 000000000..3ce04e208
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/foxy-docker-push.yml
@@ -0,0 +1,35 @@
+name: Foxy Docker Push
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ foxy_docker_push:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Get tag name
+ run: |
+ tag_name=$(git describe --tags --abbrev=0 HEAD^)
+ echo "tag_name=$tag_name" >> $GITHUB_ENV
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push docker
+ uses: docker/build-push-action@v6
+ with:
+ build-args: ROS_DISTRO=foxy
+ push: true
+ tags: mgons/audio_common:foxy-${{ github.event.release.tag_name }}
diff --git a/src/lib/audio_common/.github/workflows/galactic-build-test.yml b/src/lib/audio_common/.github/workflows/galactic-build-test.yml
new file mode 100644
index 000000000..23b8a4c22
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/galactic-build-test.yml
@@ -0,0 +1,28 @@
+name: Galactic Build and Test
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: "0 5 * * 1"
+
+jobs:
+ galactic_build_and_test:
+ runs-on: ubuntu-latest
+ container:
+ image: ros:galactic
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build and Test
+ uses: ros-tooling/action-ros-ci@v0.4
+ with:
+ target-ros2-distro: galactic
+ coverage-result: false
+ skip-tests: true
+ colcon-defaults: |
+ {
+ "test": {
+ "parallel-workers" : 1
+ }
+ }
diff --git a/src/lib/audio_common/.github/workflows/galactic-docker-push.yml b/src/lib/audio_common/.github/workflows/galactic-docker-push.yml
new file mode 100644
index 000000000..738cd33dc
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/galactic-docker-push.yml
@@ -0,0 +1,35 @@
+name: Galactic Docker Push
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ galactic_docker_push:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Get tag name
+ run: |
+ tag_name=$(git describe --tags --abbrev=0 HEAD^)
+ echo "tag_name=$tag_name" >> $GITHUB_ENV
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push docker
+ uses: docker/build-push-action@v6
+ with:
+ build-args: ROS_DISTRO=galactic
+ push: true
+ tags: mgons/audio_common:galactic-${{ github.event.release.tag_name }}
diff --git a/src/lib/audio_common/.github/workflows/humble-build-test.yml b/src/lib/audio_common/.github/workflows/humble-build-test.yml
new file mode 100644
index 000000000..3c42460b7
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/humble-build-test.yml
@@ -0,0 +1,28 @@
+name: Humble Build and Test
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: "0 5 * * 1"
+
+jobs:
+ humble_build_and_test:
+ runs-on: ubuntu-latest
+ container:
+ image: ros:humble
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build and Test
+ uses: ros-tooling/action-ros-ci@v0.4
+ with:
+ target-ros2-distro: humble
+ coverage-result: false
+ skip-tests: true
+ colcon-defaults: |
+ {
+ "test": {
+ "parallel-workers" : 1
+ }
+ }
diff --git a/src/lib/audio_common/.github/workflows/humble-docker-push.yml b/src/lib/audio_common/.github/workflows/humble-docker-push.yml
new file mode 100644
index 000000000..2e63ffb3d
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/humble-docker-push.yml
@@ -0,0 +1,30 @@
+name: Humble Docker Push
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ humble_docker_push:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push docker
+ uses: docker/build-push-action@v6
+ with:
+ build-args: ROS_DISTRO=humble
+ push: true
+ tags: mgons/audio_common:humble-${{ github.event.release.tag_name }}
diff --git a/src/lib/audio_common/.github/workflows/iron-build-test.yml b/src/lib/audio_common/.github/workflows/iron-build-test.yml
new file mode 100644
index 000000000..b08113691
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/iron-build-test.yml
@@ -0,0 +1,28 @@
+name: Iron Build and Test
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: "0 5 * * 1"
+
+jobs:
+ iron_build_and_test:
+ runs-on: ubuntu-latest
+ container:
+ image: ros:iron
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build and Test
+ uses: ros-tooling/action-ros-ci@v0.4
+ with:
+ target-ros2-distro: iron
+ coverage-result: false
+ skip-tests: true
+ colcon-defaults: |
+ {
+ "test": {
+ "parallel-workers" : 1
+ }
+ }
diff --git a/src/lib/audio_common/.github/workflows/iron-docker-push.yml b/src/lib/audio_common/.github/workflows/iron-docker-push.yml
new file mode 100644
index 000000000..41c030417
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/iron-docker-push.yml
@@ -0,0 +1,35 @@
+name: Iron Docker Push
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ iron_docker_push:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Get tag name
+ run: |
+ tag_name=$(git describe --tags --abbrev=0 HEAD^)
+ echo "tag_name=$tag_name" >> $GITHUB_ENV
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push docker
+ uses: docker/build-push-action@v6
+ with:
+ build-args: ROS_DISTRO=iron
+ push: true
+ tags: mgons/audio_common:iron-${{ github.event.release.tag_name }}
diff --git a/src/lib/audio_common/.github/workflows/jazzy-build-test.yml b/src/lib/audio_common/.github/workflows/jazzy-build-test.yml
new file mode 100644
index 000000000..262a9e4e6
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/jazzy-build-test.yml
@@ -0,0 +1,28 @@
+name: Jazzy Build and Test
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: "0 5 * * 1"
+
+jobs:
+ jazzy_build_and_test:
+ runs-on: ubuntu-latest
+ container:
+ image: ros:jazzy
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build and Test
+ uses: ros-tooling/action-ros-ci@v0.4
+ with:
+ target-ros2-distro: jazzy
+ coverage-result: false
+ skip-tests: true
+ colcon-defaults: |
+ {
+ "test": {
+ "parallel-workers" : 1
+ }
+ }
diff --git a/src/lib/audio_common/.github/workflows/jazzy-docker-push.yml b/src/lib/audio_common/.github/workflows/jazzy-docker-push.yml
new file mode 100644
index 000000000..3631b3808
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/jazzy-docker-push.yml
@@ -0,0 +1,35 @@
+name: Jazzy Docker Push
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ jazzy_docker_push:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Get tag name
+ run: |
+ tag_name=$(git describe --tags --abbrev=0 HEAD^)
+ echo "tag_name=$tag_name" >> $GITHUB_ENV
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push docker
+ uses: docker/build-push-action@v6
+ with:
+ build-args: ROS_DISTRO=jazzy
+ push: true
+ tags: mgons/audio_common:jazzy-${{ github.event.release.tag_name }}
diff --git a/src/lib/audio_common/.github/workflows/kilted-build-test.yml b/src/lib/audio_common/.github/workflows/kilted-build-test.yml
new file mode 100644
index 000000000..b26699840
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/kilted-build-test.yml
@@ -0,0 +1,28 @@
+name: Kilted Build and Test
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: "0 5 * * 1"
+
+jobs:
+ kilted_build_and_test:
+ runs-on: ubuntu-latest
+ container:
+ image: ros:kilted
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build and Test
+ uses: ros-tooling/action-ros-ci@v0.4
+ with:
+ target-ros2-distro: kilted
+ coverage-result: false
+ skip-tests: true
+ colcon-defaults: |
+ {
+ "test": {
+ "parallel-workers" : 1
+ }
+ }
diff --git a/src/lib/audio_common/.github/workflows/kilted-docker-push.yml b/src/lib/audio_common/.github/workflows/kilted-docker-push.yml
new file mode 100644
index 000000000..13472c270
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/kilted-docker-push.yml
@@ -0,0 +1,35 @@
+name: Kilted Docker Push
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ kilted_docker_push:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Get tag name
+ run: |
+ tag_name=$(git describe --tags --abbrev=0 HEAD^)
+ echo "tag_name=$tag_name" >> $GITHUB_ENV
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push docker
+ uses: docker/build-push-action@v6
+ with:
+ build-args: ROS_DISTRO=kilted
+ push: true
+ tags: mgons/audio_common:kilted-${{ github.event.release.tag_name }}
diff --git a/src/lib/audio_common/.github/workflows/rolling-build-test.yml b/src/lib/audio_common/.github/workflows/rolling-build-test.yml
new file mode 100644
index 000000000..30204168d
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/rolling-build-test.yml
@@ -0,0 +1,28 @@
+name: Rolling Build and Test
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: "0 5 * * 1"
+
+jobs:
+ rolling_build_and_test:
+ runs-on: ubuntu-latest
+ container:
+ image: ros:rolling
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build and Test
+ uses: ros-tooling/action-ros-ci@v0.4
+ with:
+ target-ros2-distro: rolling
+ coverage-result: false
+ skip-tests: true
+ colcon-defaults: |
+ {
+ "test": {
+ "parallel-workers" : 1
+ }
+ }
diff --git a/src/lib/audio_common/.github/workflows/rolling-docker-push.yml b/src/lib/audio_common/.github/workflows/rolling-docker-push.yml
new file mode 100644
index 000000000..ec205621e
--- /dev/null
+++ b/src/lib/audio_common/.github/workflows/rolling-docker-push.yml
@@ -0,0 +1,35 @@
+name: Rolling Docker Push
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ rolling_docker_push:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Get tag name
+ run: |
+ tag_name=$(git describe --tags --abbrev=0 HEAD^)
+ echo "tag_name=$tag_name" >> $GITHUB_ENV
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push docker
+ uses: docker/build-push-action@v6
+ with:
+ build-args: ROS_DISTRO=rolling
+ push: true
+ tags: mgons/audio_common:rolling-${{ github.event.release.tag_name }}
diff --git a/src/lib/audio_common/.gitignore b/src/lib/audio_common/.gitignore
new file mode 100644
index 000000000..600d2d33b
--- /dev/null
+++ b/src/lib/audio_common/.gitignore
@@ -0,0 +1 @@
+.vscode
\ No newline at end of file
diff --git a/src/lib/audio_common/.gitrepo b/src/lib/audio_common/.gitrepo
new file mode 100644
index 000000000..c8d68c941
--- /dev/null
+++ b/src/lib/audio_common/.gitrepo
@@ -0,0 +1,12 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme
+;
+[subrepo]
+ remote = git@github.com:mgonzs13/audio_common.git
+ branch = main
+ commit = 61bfa834859dff8e63a62936074f14d6e4b69933
+ parent = c3ec16f405948a0d740ec3aa4a38f6cd12805ad6
+ method = merge
+ cmdver = 0.4.9
diff --git a/src/lib/audio_common/CITATION.cff b/src/lib/audio_common/CITATION.cff
new file mode 100644
index 000000000..5f3fa9e07
--- /dev/null
+++ b/src/lib/audio_common/CITATION.cff
@@ -0,0 +1,8 @@
+cff-version: 1.2.0
+message: "If you use this software, please cite it as below."
+authors:
+ - family-names: "González-Santamarta"
+ given-names: "Miguel Á."
+title: "audio_common"
+date-released: 2023-05-06
+url: "https://github.com/mgonzs13/audio_common"
\ No newline at end of file
diff --git a/src/lib/audio_common/Dockerfile b/src/lib/audio_common/Dockerfile
new file mode 100644
index 000000000..63d5ed1af
--- /dev/null
+++ b/src/lib/audio_common/Dockerfile
@@ -0,0 +1,24 @@
+# Use the official ROS 2 humble base image
+ARG ROS_DISTRO=humble
+FROM ros:${ROS_DISTRO} AS deps
+
+# Set the working directory and copy files
+WORKDIR /root/ros2_ws
+SHELL ["/bin/bash", "-c"]
+COPY . /root/ros2_ws/src
+
+# Install dependencies
+RUN source /opt/ros/${ROS_DISTRO}/setup.bash
+RUN apt-get update
+RUN rosdep install --from-paths src --ignore-src -r -y
+
+# Colcon the ws
+FROM deps AS builder
+ARG CMAKE_BUILD_TYPE=Release
+RUN source /opt/ros/${ROS_DISTRO}/setup.bash && colcon build
+
+# Source the ROS2 setup file
+RUN echo "source /root/ros2_ws/install/setup.bash" >> ~/.bashrc
+
+# Run a default command, e.g., starting a bash shell
+CMD ["bash"]
diff --git a/src/lib/audio_common/LICENSE b/src/lib/audio_common/LICENSE
new file mode 100644
index 000000000..a32dfc40d
--- /dev/null
+++ b/src/lib/audio_common/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Miguel Ángel González Santamarta
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/lib/audio_common/README.md b/src/lib/audio_common/README.md
new file mode 100644
index 000000000..8feefc311
--- /dev/null
+++ b/src/lib/audio_common/README.md
@@ -0,0 +1,240 @@
+# audio_capture
+
+This repositiory provides a set of ROS 2 packages for audio. It provides a C++ version to capture and play audio data using PortAudio.
+
+
+
+[](https://opensource.org/license/mit) [](https://github.com/mgonzs13/audio_common/releases) [](https://github.com/mgonzs13/audio_common?branch=main) [](https://github.com/mgonzs13/audio_common/commits/main) [](https://github.com/mgonzs13/audio_common/issues) [](https://github.com/mgonzs13/audio_common/pulls) [](https://github.com/mgonzs13/audio_common/graphs/contributors) [](https://github.com/mgonzs13/audio_common/actions/workflows/cpp-formatter.yml?branch=main) [](https://mgonzs13.github.io/audio_common/latest)
+
+| ROS 2 Distro | Branch | Build status | Docker Image |
+| :----------: | :----------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------: |
+| **Foxy** | [`main`](https://github.com/mgonzs13/audio_common/tree/main) | [](https://github.com/mgonzs13/audio_common/actions/workflows/foxy-build-test.yml?branch=main) | [](https://hub.docker.com/r/mgons/audio_common/tags?name=foxy) |
+| **Galactic** | [`main`](https://github.com/mgonzs13/audio_common/tree/main) | [](https://github.com/mgonzs13/audio_common/actions/workflows/galactic-build-test.yml?branch=main) | [](https://hub.docker.com/r/mgons/audio_common/tags?name=galactic) |
+| **Humble** | [`main`](https://github.com/mgonzs13/audio_common/tree/main) | [](https://github.com/mgonzs13/audio_common/actions/workflows/humble-build-test.yml?branch=main) | [](https://hub.docker.com/r/mgons/audio_common/tags?name=humble) |
+| **Iron** | [`main`](https://github.com/mgonzs13/audio_common/tree/main) | [](https://github.com/mgonzs13/audio_common/actions/workflows/iron-build-test.yml?branch=main) | [](https://hub.docker.com/r/mgons/audio_common/tags?name=iron) |
+| **Jazzy** | [`main`](https://github.com/mgonzs13/audio_common/tree/main) | [](https://github.com/mgonzs13/audio_common/actions/workflows/jazzy-build-test.yml?branch=main) | [](https://hub.docker.com/r/mgons/audio_common/tags?name=jazzy) |
+| **Kilted** | [`main`](https://github.com/mgonzs13/audio_common/tree/main) | [](https://github.com/mgonzs13/audio_common/actions/workflows/kilted-build-test.yml?branch=main) | [](https://hub.docker.com/r/mgons/audio_common/tags?name=kilted) |
+| **Rolling** | [`main`](https://github.com/mgonzs13/audio_common/tree/main) | [](https://github.com/mgonzs13/audio_common/actions/workflows/rolling-build-test.yml?branch=main) | [](https://hub.docker.com/r/mgons/audio_common/tags?name=rolling) |
+
+
+
+## Table of Contents
+
+1. [Installation](#installation)
+2. [Docker](#docker)
+3. [Nodes](#nodes)
+4. [Demos](#demos)
+
+## Installation
+
+```shell
+cd ~/ros2_ws/src
+git clone https://github.com/mgonzs13/audio_common.git
+cd ~/ros2_ws
+rosdep install --from-paths src --ignore-src -r -y
+colcon build
+```
+
+## Docker
+
+You can create a docker image to test audio_common. Use the following command inside the directory of audio_common.
+
+```shell
+docker build -t audio_common .
+```
+
+After the image is created, run a docker container with the following command.
+
+```shell
+docker run -it --rm --device /dev/snd audio_common
+```
+
+## Nodes
+
+### audio_capturer_node
+
+Node to obtain audio data from a microphone and publish it into the `audio` topic.
+
+
+Click to expand
+
+#### Parameters
+
+- **format**: Specifies the audio format to be used for capturing. Possible values are:
+
+ - `1` (paFloat32 - 32-bit floating point)
+ - `2` (paInt32 - 32-bit integer)
+ - `8` (paInt16 - 16-bit integer)
+ - `16` (paInt8 - 8-bit integer)
+ - `32` (paUInt8 - 8-bit unsigned integer)
+
+ Default: `8` (paInt16)
+
+ The integer values correspond to PortAudio sample format flags.
+
+- **channels**: The number of audio channels to capture. Typically, `1` for mono and `2` for stereo. Default: `1`
+
+- **rate**: The sample rate that is how many samples per second should be captured. Default: `16000`
+
+- **chunk**: The size of each audio frame. Default: `512`
+
+- **device**: The ID of the audio input device. A value of `-1` indicates that the default audio input device should be used. Default: `-1`
+
+- **frame_id**: An identifier for the audio frame. This can be useful for synchronizing audio data with other data streams. Default: `""`
+
+#### ROS 2 Interfaces
+
+- **audio**: Topic to publish the audio data captured from the microphone. Type: `audio_common_msgs/msg/AudioStamped`
+
+
+
+### audio_player_node
+
+Node to play the audio data obtained from the `audio` topic.
+
+
+Click to expand
+
+#### Parameters
+
+- **channels**: The number of audio channels to play. Typically, `1` for mono and `2` for stereo. Default: `2`
+
+ - The node automatically handles conversion between mono and stereo formats if needed.
+
+- **device**: The ID of the audio output device. A value of `-1` indicates that the default audio output device should be used. Default: `-1`
+
+#### ROS 2 Interfaces
+
+- **audio**: Topic subscriber to get the audio data to be played. Type: `audio_common_msgs/msg/AudioStamped`
+
+
+
+### music_node
+
+Node to play music from audio files in `wav` format.
+
+
+Click to expand
+
+#### Parameters
+
+- **chunk**: The size of each audio frame. Default: `2048`
+
+- **frame_id**: An identifier for the audio frame. This can be useful for synchronizing audio data with other data streams. Default: `""`
+
+#### ROS 2 Interfaces
+
+- **audio**: Topic to publish the audio data from the files. Type: `audio_common_msgs/msg/AudioStamped`
+
+- **music_play**: Service to play audio files. Type: `audio_common_msgs/srv/MusicPlay`
+
+ - Parameters:
+ - `audio`: Name of a built-in audio sample (e.g., "elevator")
+ - `file_path`: Path to a custom WAV file (ignored if audio is specified)
+ - `loop`: Boolean to indicate if the audio should loop. Default: `false`
+
+- **music_stop**: Service to stop the currently playing music. Type: `std_srvs/srv/Trigger`
+
+- **music_pause**: Service to pause the currently playing music. Type: `std_srvs/srv/Trigger`
+
+- **music_resume**: Service to resume paused music. Type: `std_srvs/srv/Trigger`
+
+
+
+### tts_node
+
+Node to generate audio from text (TTS) using espeak.
+
+
+Click to expand
+
+#### Parameters
+
+- **chunk**: The size of each audio frame. Default: `4096`
+
+- **frame_id**: An identifier for the audio frame. This can be useful for synchronizing audio data with other data streams. Default: `""`
+
+#### ROS 2 Interfaces
+
+- **audio**: Topic publisher to send the audio data generated by the TTS. Type: `audio_common_msgs/msg/AudioStamped`
+
+- **say**: Action to generate audio data from a text. Type: `audio_common_msgs/action/TTS`
+ - Goal:
+ - `text`: The text to convert to speech
+ - `language`: The language to use for speech synthesis. Default: `"en"`
+ - `volume`: The volume of the generated speech (0.0-1.0). Default: `1.0`
+ - `rate`: The speech rate (1.0 is normal speed). Default: `1.0`
+ - Feedback:
+ - `audio`: The audio being currently played
+ - Result:
+ - `text`: The text that was converted to speech
+
+
+
+## Demos
+
+### Audio Capturer/Player
+
+```shell
+ros2 run audio_common audio_capturer_node
+```
+
+```shell
+ros2 run audio_common audio_player_node
+```
+
+### TTS
+
+```shell
+ros2 run audio_common tts_node
+```
+
+```shell
+ros2 run audio_common audio_player_node
+```
+
+```shell
+ros2 action send_goal /say audio_common_msgs/action/TTS "{'text': 'Hello World'}"
+```
+
+Advanced TTS example with additional parameters:
+
+```shell
+ros2 action send_goal /say audio_common_msgs/action/TTS "{'text': 'Hello World', 'language': 'en-us', 'volume': 0.8, 'rate': 1.2}"
+```
+
+### Music Player
+
+```shell
+ros2 run audio_common music_node
+```
+
+```shell
+ros2 run audio_common audio_player_node
+```
+
+Play a built-in sample:
+
+```shell
+ros2 service call /music_play audio_common_msgs/srv/MusicPlay "{audio: 'elevator'}"
+```
+
+Play a custom WAV file:
+
+```shell
+ros2 service call /music_play audio_common_msgs/srv/MusicPlay "{file_path: '/path/to/your/file.wav'}"
+```
+
+Play with looping enabled:
+
+```shell
+ros2 service call /music_play audio_common_msgs/srv/MusicPlay "{audio: 'elevator', loop: true}"
+```
+
+Control playback:
+
+```shell
+ros2 service call /music_pause std_srvs/srv/Trigger "{}"
+ros2 service call /music_resume std_srvs/srv/Trigger "{}"
+ros2 service call /music_stop std_srvs/srv/Trigger "{}"
+```
diff --git a/src/lib/audio_common/audio_common/.clang-format b/src/lib/audio_common/audio_common/.clang-format
new file mode 100644
index 000000000..48b2c6783
--- /dev/null
+++ b/src/lib/audio_common/audio_common/.clang-format
@@ -0,0 +1,192 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignArrayOfStructures: None
+AlignConsecutiveMacros: None
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
+AlignEscapedNewlines: Right
+AlignOperands: Align
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: MultiLine
+AttributeMacros:
+ - __capability
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat: false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: BinPack
+BasedOnStyle: ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IfMacros:
+ - KJ_IF_MAYBE
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
+ Priority: 2
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '^(<|"(gtest|gmock|isl|json)/)'
+ Priority: 3
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '.*'
+ Priority: 1
+ SortPriority: 0
+ CaseSensitive: false
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: false
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequires: false
+IndentWidth: 2
+IndentWrappedFunctionNames: false
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 2
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Right
+PPIndentWidth: -1
+ReferenceAlignment: Pointer
+ReflowComments: true
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes: CaseSensitive
+SortJavaStaticImport: Before
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeParensOptions:
+ AfterControlStatements: true
+ AfterForeachMacros: true
+ AfterFunctionDefinitionName: false
+ AfterFunctionDeclarationName: false
+ AfterIfMacros: true
+ AfterOverloadedOperator: false
+ BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+ Minimum: 1
+ Maximum: -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard: Latest
+StatementAttributeLikeMacros:
+ - Q_EMIT
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 8
+UseCRLF: false
+UseTab: Never
+WhitespaceSensitiveMacros:
+ - STRINGIZE
+ - PP_STRINGIZE
+ - BOOST_PP_STRINGIZE
+ - NS_SWIFT_NAME
+ - CF_SWIFT_NAME
+...
+
diff --git a/src/lib/audio_common/audio_common/CMakeLists.txt b/src/lib/audio_common/audio_common/CMakeLists.txt
new file mode 100644
index 000000000..b6fd54fbe
--- /dev/null
+++ b/src/lib/audio_common/audio_common/CMakeLists.txt
@@ -0,0 +1,117 @@
+cmake_minimum_required(VERSION 3.10)
+project(audio_common)
+
+# find dependencies
+find_package(ament_cmake REQUIRED)
+find_package(ament_index_cpp REQUIRED)
+find_package(rclcpp REQUIRED)
+find_package(rclcpp_action REQUIRED)
+find_package(std_srvs REQUIRED)
+find_package(audio_common_msgs REQUIRED)
+find_library(PORTAUDIO_LIB portaudio REQUIRED)
+
+include_directories(
+ include
+ ${PORTAUDIO_INCLUDE_DIR}
+)
+
+# audio_capturer__node
+add_executable(audio_capturer_node
+ src/audio_common/audio_capturer_node.cpp
+ src/audio_capturer_main.cpp
+)
+target_link_libraries(audio_capturer_node
+ ${audio_common_msgs_TARGETS}
+ ${PORTAUDIO_LIB}
+ rclcpp::rclcpp
+)
+install(TARGETS
+ audio_capturer_node
+ DESTINATION lib/${PROJECT_NAME}
+)
+
+# audio_player_node
+add_executable(audio_player_node
+ src/audio_common/audio_player_node.cpp
+ src/audio_player_main.cpp
+)
+target_link_libraries(audio_player_node
+ ${audio_common_msgs_TARGETS}
+ ${PORTAUDIO_LIB}
+ rclcpp::rclcpp
+)
+install(TARGETS
+ audio_player_node
+ DESTINATION lib/${PROJECT_NAME}
+)
+
+# music_node
+add_executable(music_node
+ src/audio_common/wave_file.cpp
+ src/audio_common/music_node.cpp
+ src/music_main.cpp
+)
+target_link_libraries(music_node
+ ament_index_cpp::ament_index_cpp
+ ${audio_common_msgs_TARGETS}
+ ${PORTAUDIO_LIB}
+ rclcpp::rclcpp
+ ${std_srvs_TARGETS}
+)
+
+install(TARGETS
+ music_node
+ DESTINATION lib/${PROJECT_NAME}
+)
+
+install(DIRECTORY
+ samples
+ DESTINATION share/${PROJECT_NAME}
+)
+
+# tts node
+add_executable(tts_node
+ src/audio_common/wave_file.cpp
+ src/audio_common/tts_node.cpp
+ src/tts_main.cpp
+)
+target_link_libraries(tts_node
+ ${audio_common_msgs_TARGETS}
+ rclcpp::rclcpp
+ rclcpp_action::rclcpp_action
+ ${PORTAUDIO_LIB}
+)
+install(TARGETS
+ tts_node
+ DESTINATION lib/${PROJECT_NAME}
+)
+
+# Create lib
+set(LIB ${CMAKE_PROJECT_NAME}_lib)
+set(SOURCES
+ src/audio_common/wave_file.cpp
+)
+
+add_library(${LIB} STATIC ${SOURCES})
+target_link_libraries(${LIB} ${DEPENDENCIES})
+
+install(TARGETS ${LIB}
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+ RUNTIME DESTINATION bin
+)
+
+install(DIRECTORY include/
+ DESTINATION include/
+)
+
+if(BUILD_TESTING)
+ find_package(ament_cmake_clang_format REQUIRED)
+ ament_clang_format(CONFIG_FILE .clang-format)
+endif()
+
+ament_export_dependencies(${DEPENDENCIES})
+ament_export_include_directories(include)
+ament_export_libraries(${LIB})
+
+ament_package()
diff --git a/src/lib/audio_common/audio_common/include/audio_common/audio_capturer_node.hpp b/src/lib/audio_common/audio_common/include/audio_common/audio_capturer_node.hpp
new file mode 100644
index 000000000..0d65467a3
--- /dev/null
+++ b/src/lib/audio_common/audio_common/include/audio_common/audio_capturer_node.hpp
@@ -0,0 +1,57 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef AUDIO_COMMON__AUDIO_CAPTURER_NODE
+#define AUDIO_COMMON__AUDIO_CAPTURER_NODE
+
+#include
+#include
+#include
+
+#include "audio_common_msgs/msg/audio_stamped.hpp"
+
+namespace audio_common {
+
+class AudioCapturerNode : public rclcpp::Node {
+public:
+ AudioCapturerNode();
+ ~AudioCapturerNode() override;
+
+ void work();
+
+private:
+ PaStream *stream_;
+ int format_;
+ int channels_;
+ int rate_;
+ int chunk_;
+ std::string frame_id_;
+
+ rclcpp::Publisher::SharedPtr audio_pub_;
+
+ // Methods
+ template std::vector read_data();
+};
+
+} // namespace audio_common
+
+#endif
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common/include/audio_common/audio_player_node.hpp b/src/lib/audio_common/audio_common/include/audio_common/audio_player_node.hpp
new file mode 100644
index 000000000..b35bc820a
--- /dev/null
+++ b/src/lib/audio_common/audio_common/include/audio_common/audio_player_node.hpp
@@ -0,0 +1,61 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef AUDIO_COMMON__AUDIO_PLAYER_NODE
+#define AUDIO_COMMON__AUDIO_PLAYER_NODE
+
+#include
+#include
+#include
+
+#include "audio_common_msgs/msg/audio_stamped.hpp"
+
+namespace audio_common {
+
+class AudioPlayerNode : public rclcpp::Node {
+public:
+ AudioPlayerNode();
+ ~AudioPlayerNode() override;
+
+private:
+ // ROS 2 subscription for audio messages
+ rclcpp::Subscription::SharedPtr
+ audio_sub_;
+
+ // PortAudio stream dictionary
+ std::unordered_map stream_dict_;
+
+ // Parameters
+ int channels_;
+ int device_;
+
+ // Methods
+ void
+ audio_callback(const audio_common_msgs::msg::AudioStamped::SharedPtr msg);
+ template
+ void write_data(const std::vector &data, int channels, int chunk,
+ const std::string &stream_key);
+};
+
+} // namespace audio_common
+
+#endif
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common/include/audio_common/music_node.hpp b/src/lib/audio_common/audio_common/include/audio_common/music_node.hpp
new file mode 100644
index 000000000..49e65d5c6
--- /dev/null
+++ b/src/lib/audio_common/audio_common/include/audio_common/music_node.hpp
@@ -0,0 +1,86 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef AUDIO_COMMON__MUSIC_NODE
+#define AUDIO_COMMON__MUSIC_NODE
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+namespace audio_common {
+
+class MusicNode : public rclcpp::Node {
+public:
+ MusicNode();
+ ~MusicNode() override;
+
+private:
+ // ROS 2 parameters
+ int chunk_;
+ std::string frame_id_;
+
+ // Audio control flags
+ std::atomic pause_music_;
+ std::atomic stop_music_;
+ bool audio_loop_;
+ std::atomic is_thread_alive_;
+
+ // Threads and synchronization
+ std::thread publish_thread_;
+ std::mutex pause_mutex_;
+ std::condition_variable pause_cv_;
+
+ // ROS 2 publisher and services
+ rclcpp::Publisher::SharedPtr
+ player_pub_;
+ rclcpp::Service::SharedPtr play_service_;
+ rclcpp::Service::SharedPtr stop_service_;
+ rclcpp::Service::SharedPtr pause_service_;
+ rclcpp::Service::SharedPtr resume_service_;
+
+ // Methods
+ void publish_audio(const std::string &file_path);
+ void play_callback(
+ const std::shared_ptr request,
+ std::shared_ptr response);
+ void
+ pause_callback(const std::shared_ptr request,
+ std::shared_ptr response);
+ void resume_callback(
+ const std::shared_ptr request,
+ std::shared_ptr response);
+ void
+ stop_callback(const std::shared_ptr request,
+ std::shared_ptr response);
+};
+
+} // namespace audio_common
+
+#endif
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common/include/audio_common/tts_node.hpp b/src/lib/audio_common/audio_common/include/audio_common/tts_node.hpp
new file mode 100644
index 000000000..cf28a81a3
--- /dev/null
+++ b/src/lib/audio_common/audio_common/include/audio_common/tts_node.hpp
@@ -0,0 +1,75 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef AUDIO_COMMON__TTS_NODE
+#define AUDIO_COMMON__TTS_NODE
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "audio_common/wave_file.hpp"
+#include "audio_common_msgs/action/tts.hpp"
+#include "audio_common_msgs/msg/audio_stamped.hpp"
+
+namespace audio_common {
+
+class TtsNode : public rclcpp::Node {
+public:
+ using TTS = audio_common_msgs::action::TTS;
+ using GoalHandleTTS = rclcpp_action::ServerGoalHandle;
+
+ TtsNode();
+
+private:
+ int chunk_;
+ std::string frame_id_;
+ rclcpp::Publisher::SharedPtr
+ player_pub_;
+ rclcpp_action::Server::SharedPtr action_server_;
+ std::mutex goal_lock_;
+ std::shared_ptr goal_handle_;
+
+ // Methods
+ rclcpp_action::GoalResponse
+ handle_goal(const rclcpp_action::GoalUUID &uuid,
+ std::shared_ptr goal);
+ rclcpp_action::CancelResponse
+ handle_cancel(const std::shared_ptr goal_handle);
+ void handle_accepted(const std::shared_ptr goal_handle);
+ void execute_callback(const std::shared_ptr goal_handle);
+};
+
+} // namespace audio_common
+
+#endif
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common/include/audio_common/wave_file.hpp b/src/lib/audio_common/audio_common/include/audio_common/wave_file.hpp
new file mode 100644
index 000000000..f92d764e2
--- /dev/null
+++ b/src/lib/audio_common/audio_common/include/audio_common/wave_file.hpp
@@ -0,0 +1,57 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef AUDIO_COMMON__WAVE_FILE_HPP
+#define AUDIO_COMMON__WAVE_FILE_HPP
+
+#include
+#include
+#include
+
+namespace audio_common {
+
+class WaveFile {
+public:
+ explicit WaveFile(const std::string &filepath);
+ ~WaveFile();
+
+ bool open();
+ void rewind();
+ bool read(std::vector &buffer, size_t size);
+ int get_sample_rate() const { return this->sample_rate_; }
+ int get_num_channels() const { return this->channels_; }
+ int get_bits_per_sample() const { return this->bits_per_sample_; }
+
+private:
+ std::string filepath_;
+ std::ifstream file_;
+ int sample_rate_;
+ int channels_;
+ int bits_per_sample_;
+
+ // Convert int16_t sample to float [-1.0, 1.0]
+ float int16ToFloat(int16_t sample) { return sample / 32768.0f; }
+};
+
+} // namespace audio_common
+
+#endif // WAVE_FILE_HPP
diff --git a/src/lib/audio_common/audio_common/package.xml b/src/lib/audio_common/audio_common/package.xml
new file mode 100644
index 000000000..43b5dd8da
--- /dev/null
+++ b/src/lib/audio_common/audio_common/package.xml
@@ -0,0 +1,20 @@
+
+
+
+ audio_common
+ 4.0.8
+ audio_common
+ Miguel Ángel González Santamarta
+ MIT
+ ament_cmake
+ ament_lint_auto
+ ament_clang_format
+ ament_cmake_clang_format
+ ament_index_cpp
+ audio_common_msgs
+ portaudio19-dev
+ espeak
+
+ ament_cmake
+
+
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common/samples/elevator.wav b/src/lib/audio_common/audio_common/samples/elevator.wav
new file mode 100644
index 000000000..964dddd28
Binary files /dev/null and b/src/lib/audio_common/audio_common/samples/elevator.wav differ
diff --git a/src/lib/audio_common/audio_common/src/audio_capturer_main.cpp b/src/lib/audio_common/audio_common/src/audio_capturer_main.cpp
new file mode 100644
index 000000000..838816c55
--- /dev/null
+++ b/src/lib/audio_common/audio_common/src/audio_capturer_main.cpp
@@ -0,0 +1,33 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include
+
+#include "audio_common/audio_capturer_node.hpp"
+
+int main(int argc, char *argv[]) {
+ rclcpp::init(argc, argv);
+ auto node = std::make_shared();
+ node->work();
+ rclcpp::shutdown();
+ return 0;
+}
diff --git a/src/lib/audio_common/audio_common/src/audio_common/audio_capturer_node.cpp b/src/lib/audio_common/audio_common/src/audio_common/audio_capturer_node.cpp
new file mode 100644
index 000000000..cfe8b1ad3
--- /dev/null
+++ b/src/lib/audio_common/audio_common/src/audio_common/audio_capturer_node.cpp
@@ -0,0 +1,142 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include
+#include
+#include
+
+#include "audio_common/audio_capturer_node.hpp"
+#include "audio_common_msgs/msg/audio_stamped.hpp"
+
+using namespace audio_common;
+
+AudioCapturerNode::AudioCapturerNode() : Node("audio_capturer_node") {
+
+ // Declare parameters with default values
+ this->declare_parameter("format", paInt16);
+ this->declare_parameter("channels", 1);
+ this->declare_parameter("rate", 16000);
+ this->declare_parameter("chunk", 512);
+ this->declare_parameter("device", -1);
+ this->declare_parameter("frame_id", "");
+
+ // Get parameters
+ this->format_ = this->get_parameter("format").as_int();
+ this->channels_ = this->get_parameter("channels").as_int();
+ this->rate_ = this->get_parameter("rate").as_int();
+ this->chunk_ = this->get_parameter("chunk").as_int();
+ int device = this->get_parameter("device").as_int();
+ this->frame_id_ = this->get_parameter("frame_id").as_string();
+
+ // Initialize PortAudio
+ PaError err = Pa_Initialize();
+ if (err != paNoError) {
+ RCLCPP_ERROR(this->get_logger(), "PortAudio error: %s",
+ Pa_GetErrorText(err));
+ throw std::runtime_error("Failed to initialize PortAudio");
+ }
+
+ PaStreamParameters inputParameters;
+ inputParameters.device = (device >= 0) ? device : Pa_GetDefaultInputDevice();
+ inputParameters.channelCount = this->channels_;
+ inputParameters.sampleFormat = this->format_;
+ inputParameters.suggestedLatency =
+ Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency;
+ inputParameters.hostApiSpecificStreamInfo = nullptr;
+
+ err = Pa_OpenStream(&this->stream_, &inputParameters,
+ nullptr, // output parameters (not used)
+ this->rate_, this->chunk_, paClipOff, nullptr, nullptr);
+
+ if (err != paNoError) {
+ RCLCPP_ERROR(this->get_logger(), "Failed to open audio stream: %s",
+ Pa_GetErrorText(err));
+ throw std::runtime_error("Failed to open PortAudio stream");
+ }
+
+ err = Pa_StartStream(this->stream_);
+ if (err != paNoError) {
+ RCLCPP_ERROR(this->get_logger(), "Failed to start audio stream: %s",
+ Pa_GetErrorText(err));
+ throw std::runtime_error("Failed to start PortAudio stream");
+ }
+
+ this->audio_pub_ =
+ this->create_publisher(
+ "audio", rclcpp::SensorDataQoS());
+
+ RCLCPP_INFO(this->get_logger(), "AudioCapturer node started");
+}
+
+AudioCapturerNode::~AudioCapturerNode() {
+ Pa_StopStream(this->stream_);
+ Pa_CloseStream(this->stream_);
+ Pa_Terminate();
+}
+
+void AudioCapturerNode::work() {
+ while (rclcpp::ok()) {
+
+ auto msg = audio_common_msgs::msg::AudioStamped();
+ msg.header.frame_id = this->frame_id_;
+ msg.header.stamp = this->get_clock()->now();
+
+ switch (this->format_) {
+ case paFloat32: {
+ msg.audio.audio_data.float32_data = this->read_data();
+ break;
+ }
+ case paInt32: {
+ msg.audio.audio_data.int32_data = this->read_data();
+ break;
+ }
+ case paInt16: {
+ msg.audio.audio_data.int16_data = this->read_data();
+ break;
+ }
+ case paInt8: {
+ msg.audio.audio_data.int8_data = this->read_data();
+ break;
+ }
+ case paUInt8: {
+ msg.audio.audio_data.uint8_data = this->read_data();
+ break;
+ }
+ default:
+ RCLCPP_ERROR(this->get_logger(), "Unsupported format");
+ continue;
+ }
+
+ msg.audio.info.format = this->format_;
+ msg.audio.info.channels = this->channels_;
+ msg.audio.info.chunk = this->chunk_;
+ msg.audio.info.rate = this->rate_;
+
+ this->audio_pub_->publish(msg);
+ }
+}
+
+template std::vector AudioCapturerNode::read_data() {
+ std::vector data(this->chunk_ * this->channels_);
+ Pa_ReadStream(this->stream_, data.data(), this->chunk_);
+ return data;
+}
diff --git a/src/lib/audio_common/audio_common/src/audio_common/audio_player_node.cpp b/src/lib/audio_common/audio_common/src/audio_common/audio_player_node.cpp
new file mode 100644
index 000000000..17cdd1d60
--- /dev/null
+++ b/src/lib/audio_common/audio_common/src/audio_common/audio_player_node.cpp
@@ -0,0 +1,201 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "audio_common/audio_player_node.hpp"
+#include "audio_common_msgs/msg/audio.hpp"
+#include "audio_common_msgs/msg/audio_stamped.hpp"
+
+using namespace audio_common;
+using std::placeholders::_1;
+
+AudioPlayerNode::AudioPlayerNode() : Node("audio_player_node") {
+ // Declare parameters
+ this->declare_parameter("channels", 2);
+ this->declare_parameter("device", -1);
+
+ // Get parameters
+ this->channels_ = this->get_parameter("channels").as_int();
+ this->device_ = this->get_parameter("device").as_int();
+
+ // Initialize PortAudio
+ PaError err = Pa_Initialize();
+ if (err != paNoError) {
+ RCLCPP_ERROR(this->get_logger(), "PortAudio error: %s",
+ Pa_GetErrorText(err));
+ throw std::runtime_error("Failed to initialize PortAudio");
+ }
+
+ // Subscription to audio topic
+ auto qos_profile = rclcpp::SensorDataQoS();
+ this->audio_sub_ =
+ this->create_subscription(
+ "audio", qos_profile,
+ std::bind(&AudioPlayerNode::audio_callback, this, _1));
+
+ RCLCPP_INFO(this->get_logger(), "AudioPlayer node started");
+}
+
+AudioPlayerNode::~AudioPlayerNode() {
+ // Close all open streams and terminate PortAudio
+ for (auto &stream_pair : this->stream_dict_) {
+ Pa_StopStream(stream_pair.second);
+ Pa_CloseStream(stream_pair.second);
+ }
+ Pa_Terminate();
+}
+
+void AudioPlayerNode::audio_callback(
+ const audio_common_msgs::msg::AudioStamped::SharedPtr msg) {
+
+ // Create a unique stream key based on format, rate, and channels
+ std::string stream_key = std::to_string(msg->audio.info.format) + "_" +
+ std::to_string(msg->audio.info.rate) + "_" +
+ std::to_string(this->channels_);
+
+ // Check if stream already exists, if not, create one
+ if (this->stream_dict_.find(stream_key) == this->stream_dict_.end()) {
+ PaStreamParameters outputParameters;
+ outputParameters.device =
+ (this->device_ >= 0) ? this->device_ : Pa_GetDefaultOutputDevice();
+ outputParameters.channelCount = this->channels_;
+ outputParameters.sampleFormat = msg->audio.info.format;
+ outputParameters.suggestedLatency =
+ Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency;
+ outputParameters.hostApiSpecificStreamInfo = nullptr;
+
+ PaError err = Pa_OpenStream(&this->stream_dict_[stream_key], nullptr,
+ &outputParameters, msg->audio.info.rate, 1024,
+ paClipOff, nullptr, nullptr);
+
+ if (err != paNoError) {
+ RCLCPP_ERROR(this->get_logger(), "Failed to open audio stream: %s",
+ Pa_GetErrorText(err));
+ return;
+ }
+ Pa_StartStream(this->stream_dict_[stream_key]);
+ }
+
+ // Write audio from ROS 2 msg
+ switch (msg->audio.info.format) {
+ case paFloat32:
+ this->write_data(msg->audio.audio_data.float32_data,
+ msg->audio.info.channels, msg->audio.info.chunk,
+ stream_key);
+ break;
+
+ case paInt32:
+ this->write_data(msg->audio.audio_data.int32_data, msg->audio.info.channels,
+ msg->audio.info.chunk, stream_key);
+ break;
+
+ case paInt16:
+ this->write_data(msg->audio.audio_data.int16_data, msg->audio.info.channels,
+ msg->audio.info.chunk, stream_key);
+ break;
+
+ case paInt8:
+ this->write_data(msg->audio.audio_data.int8_data, msg->audio.info.channels,
+ msg->audio.info.chunk, stream_key);
+ break;
+
+ case paUInt8:
+ this->write_data(msg->audio.audio_data.uint8_data, msg->audio.info.channels,
+ msg->audio.info.chunk, stream_key);
+ break;
+ default:
+ RCLCPP_ERROR(this->get_logger(), "Unsupported format");
+ return;
+ }
+}
+
+template
+void AudioPlayerNode::write_data(const std::vector &input_data, int channels,
+ int chunk, const std::string &stream_key) {
+
+ std::vector data; // Buffer for the actual data to write
+
+ // Handle mono-to-stereo or stereo-to-mono conversions if necessary
+ if (channels != this->channels_) {
+ if (channels == 1 && this->channels_ == 2) {
+ // Mono to stereo conversion
+ data.resize(input_data.size() * 2);
+ for (size_t i = 0; i < input_data.size(); ++i) {
+ data[2 * i] = input_data[i];
+ data[2 * i + 1] = input_data[i];
+ }
+
+ } else if (channels == 2 && this->channels_ == 1) {
+ // Stereo to mono conversion
+ data.resize(input_data.size() / 2);
+ for (size_t i = 0; i < data.size(); ++i) {
+ data[i] =
+ static_cast((input_data[2 * i] + input_data[2 * i + 1]) / 2);
+ }
+ }
+
+ } else {
+ // No conversion needed
+ data = input_data;
+ }
+
+ // Make sure chunk size is correct for frames (not samples)
+ if (data.size() < chunk * this->channels_) {
+ RCLCPP_WARN(this->get_logger(),
+ "Insufficient data (%ld) for requested chunk size (%d).",
+ data.size(), chunk * this->channels_);
+ return;
+ }
+
+ // Write in smaller blocks to reduce underrun risk
+ size_t frames_written = 0;
+ size_t total_frames = chunk;
+ const size_t max_block = 1024;
+
+ while (frames_written < total_frames) {
+ size_t frames_to_write = std::min(max_block, total_frames - frames_written);
+ PaError err = Pa_WriteStream(this->stream_dict_[stream_key],
+ data.data() + frames_written * this->channels_,
+ frames_to_write);
+
+ if (err == paOutputUnderflowed) {
+ RCLCPP_WARN(this->get_logger(),
+ "PortAudio underrun detected, retrying...");
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ continue; // Try again this block
+
+ } else if (err != paNoError) {
+ RCLCPP_ERROR(this->get_logger(), "PortAudio write error: %s",
+ Pa_GetErrorText(err));
+ break;
+ }
+
+ frames_written += frames_to_write;
+ }
+}
diff --git a/src/lib/audio_common/audio_common/src/audio_common/music_node.cpp b/src/lib/audio_common/audio_common/src/audio_common/music_node.cpp
new file mode 100644
index 000000000..7587dd7df
--- /dev/null
+++ b/src/lib/audio_common/audio_common/src/audio_common/music_node.cpp
@@ -0,0 +1,210 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "audio_common/music_node.hpp"
+#include "audio_common/wave_file.hpp"
+
+using namespace audio_common;
+using std::placeholders::_1;
+using std::placeholders::_2;
+
+MusicNode::MusicNode()
+ : Node("music_node"), pause_music_(false), stop_music_(false),
+ audio_loop_(false), is_thread_alive_(false) {
+
+ // Parameters
+ this->declare_parameter("chunk", 2048);
+ this->declare_parameter("frame_id", "");
+
+ this->chunk_ = this->get_parameter("chunk").as_int();
+ this->frame_id_ = this->get_parameter("frame_id").as_string();
+
+ // Publisher
+ this->player_pub_ =
+ this->create_publisher(
+ "audio", rclcpp::SensorDataQoS());
+
+ // Services
+ this->play_service_ = this->create_service(
+ "music_play", std::bind(&MusicNode::play_callback, this, _1, _2));
+ this->stop_service_ = this->create_service(
+ "music_stop", std::bind(&MusicNode::stop_callback, this, _1, _2));
+ this->pause_service_ = this->create_service(
+ "music_pause", std::bind(&MusicNode::pause_callback, this, _1, _2));
+ this->resume_service_ = this->create_service(
+ "music_resume", std::bind(&MusicNode::resume_callback, this, _1, _2));
+
+ RCLCPP_INFO(this->get_logger(), "Music node started");
+}
+
+MusicNode::~MusicNode() {
+ this->stop_music_ = true;
+ if (this->publish_thread_.joinable()) {
+ this->publish_thread_.join();
+ }
+}
+
+void MusicNode::publish_audio(const std::string &file_path) {
+
+ this->is_thread_alive_ = true;
+
+ audio_common::WaveFile wf(file_path);
+ if (!wf.open()) {
+ RCLCPP_ERROR(this->get_logger(), "Error opening audio file: %s",
+ file_path.c_str());
+ return;
+ }
+
+ // Create rate
+ std::chrono::nanoseconds period(
+ (int)(1e9 * this->chunk_ / wf.get_sample_rate()));
+ rclcpp::Rate pub_rate(period);
+ std::vector data(this->chunk_);
+
+ while (!this->stop_music_) {
+ while (wf.read(data, this->chunk_)) {
+
+ auto msg = audio_common_msgs::msg::AudioStamped();
+ msg.header.frame_id = this->frame_id_;
+ msg.header.stamp = this->get_clock()->now();
+ msg.audio.audio_data.float32_data = data;
+ msg.audio.info.channels = wf.get_num_channels();
+ msg.audio.info.chunk = this->chunk_;
+ msg.audio.info.format = 1;
+ msg.audio.info.rate = wf.get_sample_rate();
+
+ this->player_pub_->publish(msg);
+ pub_rate.sleep();
+
+ if (this->pause_music_) {
+ std::unique_lock lock(this->pause_mutex_);
+ this->pause_cv_.wait(lock, [&]() { return !this->pause_music_; });
+ }
+
+ if (this->stop_music_) {
+ break;
+ }
+ }
+
+ wf.rewind();
+ if (!this->audio_loop_ || this->stop_music_) {
+ break;
+ }
+ }
+
+ this->is_thread_alive_ = false;
+}
+
+void MusicNode::play_callback(
+ const std::shared_ptr request,
+ std::shared_ptr response) {
+
+ if (this->is_thread_alive_) {
+ RCLCPP_WARN(this->get_logger(), "There is other music playing");
+ response->success = false;
+ return;
+ }
+
+ if (this->publish_thread_.joinable()) {
+ this->publish_thread_.join();
+ }
+
+ std::string path = request->file_path;
+ if (path.empty()) {
+ path = ament_index_cpp::get_package_share_directory("audio_common") +
+ "/samples/" + request->audio + ".wav";
+ }
+
+ if (!std::ifstream(path).good()) {
+ RCLCPP_ERROR(this->get_logger(), "File %s not found", path.c_str());
+ response->success = false;
+ return;
+ }
+
+ RCLCPP_INFO(this->get_logger(), "Playing %s", path.c_str());
+ this->audio_loop_ = request->loop;
+ this->pause_music_ = false;
+ this->stop_music_ = false;
+ response->success = true;
+
+ this->publish_thread_ = std::thread(&MusicNode::publish_audio, this, path);
+}
+
+void MusicNode::pause_callback(
+ const std::shared_ptr request,
+ std::shared_ptr response) {
+
+ if (this->is_thread_alive_) {
+ this->pause_music_ = true;
+ RCLCPP_INFO(this->get_logger(), "Music paused");
+ response->success = true;
+
+ } else {
+ RCLCPP_WARN(this->get_logger(), "No music to pause");
+ response->success = false;
+ }
+}
+
+void MusicNode::resume_callback(
+ const std::shared_ptr request,
+ std::shared_ptr response) {
+
+ if (this->is_thread_alive_) {
+ this->pause_music_ = false;
+ this->pause_cv_.notify_all();
+ RCLCPP_INFO(this->get_logger(), "Music resumed");
+ response->success = true;
+
+ } else {
+ RCLCPP_WARN(this->get_logger(), "No music to resume");
+ response->success = false;
+ }
+}
+
+void MusicNode::stop_callback(
+ const std::shared_ptr request,
+ std::shared_ptr response) {
+
+ if (this->is_thread_alive_) {
+ this->stop_music_ = true;
+ this->publish_thread_.join();
+ RCLCPP_INFO(this->get_logger(), "Music stopped");
+ response->success = true;
+
+ } else {
+ RCLCPP_WARN(this->get_logger(), "No music to stop");
+ response->success = false;
+ }
+}
diff --git a/src/lib/audio_common/audio_common/src/audio_common/tts_node.cpp b/src/lib/audio_common/audio_common/src/audio_common/tts_node.cpp
new file mode 100644
index 000000000..c402996e8
--- /dev/null
+++ b/src/lib/audio_common/audio_common/src/audio_common/tts_node.cpp
@@ -0,0 +1,164 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "audio_common/tts_node.hpp"
+#include "audio_common/wave_file.hpp"
+#include "audio_common_msgs/action/tts.hpp"
+#include "audio_common_msgs/msg/audio_stamped.hpp"
+
+using namespace audio_common;
+using namespace std::chrono_literals;
+using std::placeholders::_1;
+using std::placeholders::_2;
+
+TtsNode::TtsNode() : Node("tts_node") {
+
+ this->declare_parameter("chunk", 4096);
+ this->declare_parameter("frame_id", "");
+
+ this->chunk_ = this->get_parameter("chunk").as_int();
+ this->frame_id_ = this->get_parameter("frame_id").as_string();
+
+ this->player_pub_ =
+ this->create_publisher(
+ "audio", rclcpp::SensorDataQoS());
+
+ // Action server
+ this->action_server_ = rclcpp_action::create_server(
+ this, "say", std::bind(&TtsNode::handle_goal, this, _1, _2),
+ std::bind(&TtsNode::handle_cancel, this, _1),
+ std::bind(&TtsNode::handle_accepted, this, _1));
+
+ RCLCPP_INFO(this->get_logger(), "TTS node started");
+}
+
+rclcpp_action::GoalResponse
+TtsNode::handle_goal(const rclcpp_action::GoalUUID &uuid,
+ std::shared_ptr goal) {
+ (void)uuid;
+ return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
+}
+
+rclcpp_action::CancelResponse
+TtsNode::handle_cancel(const std::shared_ptr goal_handle) {
+ RCLCPP_INFO(this->get_logger(), "Canceling TTS...");
+ (void)goal_handle;
+ return rclcpp_action::CancelResponse::ACCEPT;
+}
+
+void TtsNode::handle_accepted(
+ const std::shared_ptr goal_handle) {
+ std::unique_lock lock(this->goal_lock_);
+ if (this->goal_handle_ != nullptr && this->goal_handle_->is_active()) {
+ auto result = std::make_shared();
+ this->goal_handle_->abort(result);
+ this->goal_handle_ = goal_handle;
+ }
+
+ std::thread{std::bind(&TtsNode::execute_callback, this, _1), goal_handle}
+ .detach();
+}
+
+void TtsNode::execute_callback(
+ const std::shared_ptr goal_handle) {
+ auto result = std::make_shared();
+ const auto goal = goal_handle->get_goal();
+ std::string text = goal->text;
+ std::string language = goal->language;
+ int rate = static_cast(goal->rate * 175);
+ int volume = static_cast(goal->volume * 100);
+
+ // Create audio file using espeak
+ char temp_file[] = "/tmp/tts_audio.wav";
+ std::stringstream cmd;
+ cmd << "espeak -v" << language << " -s" << rate << " -a" << volume << " -w "
+ << temp_file << " '" << text << "'";
+
+ int ret = std::system(cmd.str().c_str());
+ if (ret != 0) {
+ RCLCPP_ERROR(this->get_logger(),
+ "espeak command failed with return code: %d", ret);
+ goal_handle->abort(result);
+ return;
+ }
+
+ // Read audio file
+ audio_common::WaveFile wf(temp_file);
+ if (!wf.open()) {
+ RCLCPP_ERROR(this->get_logger(), "Error opening audio file: %s", temp_file);
+ goal_handle->abort(result);
+ return;
+ }
+
+ // Create rate
+ std::chrono::nanoseconds period(
+ (int)(1e9 * this->chunk_ / wf.get_sample_rate()));
+ rclcpp::Rate pub_rate(period);
+ std::vector data(this->chunk_);
+
+ // Initialize the audio message
+ audio_common_msgs::msg::AudioStamped msg;
+ msg.header.frame_id = this->frame_id_;
+
+ // Publish the audio data in chunks
+ while (wf.read(data, this->chunk_)) {
+ if (!goal_handle->is_active()) {
+ return;
+ }
+
+ if (goal_handle->is_canceling()) {
+ goal_handle->canceled(result);
+ return;
+ }
+
+ auto msg = audio_common_msgs::msg::AudioStamped();
+ msg.header.stamp = this->get_clock()->now();
+ msg.audio.audio_data.float32_data = data;
+ msg.audio.info.channels = wf.get_num_channels();
+ msg.audio.info.chunk = this->chunk_;
+ msg.audio.info.format = 1;
+ msg.audio.info.rate = wf.get_sample_rate();
+
+ auto feedback = std::make_shared();
+ feedback->audio = msg;
+
+ this->player_pub_->publish(msg);
+ goal_handle->publish_feedback(feedback);
+ pub_rate.sleep();
+ }
+
+ // Cleanup and set result
+ std::remove(temp_file);
+
+ result->text = text;
+ goal_handle->succeed(result);
+}
diff --git a/src/lib/audio_common/audio_common/src/audio_common/wave_file.cpp b/src/lib/audio_common/audio_common/src/audio_common/wave_file.cpp
new file mode 100644
index 000000000..17ebdd434
--- /dev/null
+++ b/src/lib/audio_common/audio_common/src/audio_common/wave_file.cpp
@@ -0,0 +1,87 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include
+#include
+
+#include "audio_common/wave_file.hpp"
+
+using namespace audio_common;
+
+WaveFile::WaveFile(const std::string &filepath)
+ : filepath_(filepath), sample_rate_(0), channels_(0), bits_per_sample_(0) {}
+
+WaveFile::~WaveFile() { this->file_.close(); }
+
+bool WaveFile::open() {
+ this->file_.open(this->filepath_, std::ios::binary);
+ if (!this->file_.is_open()) {
+ std::cerr << "Failed to open file: " << this->filepath_ << std::endl;
+ return false;
+ }
+ // Read the WAV header
+ char riff_header[4];
+ this->file_.read(riff_header, 4);
+ if (std::strncmp(riff_header, "RIFF", 4) != 0) {
+ std::cerr << "Invalid WAV file" << std::endl;
+ return false;
+ }
+ this->file_.seekg(24);
+ this->file_.read(reinterpret_cast(&this->sample_rate_), 4);
+ this->file_.seekg(22);
+ this->file_.read(reinterpret_cast(&this->channels_), 2);
+ this->file_.seekg(34);
+ this->file_.read(reinterpret_cast(&this->bits_per_sample_), 2);
+ this->file_.seekg(44); // Move to the data section start
+ return true;
+}
+
+void WaveFile::rewind() {
+ this->file_.close();
+ this->open();
+}
+
+bool WaveFile::read(std::vector &buffer, size_t size) {
+ if (this->bits_per_sample_ != 16) {
+ std::cerr << "Only 16-bit PCM WAV files are supported" << std::endl;
+ return false;
+ }
+
+ // Allocate temporary buffer to read int16 data
+ std::vector temp_buffer(size * this->channels_);
+
+ // Read raw int16 samples from the file
+ this->file_.read(reinterpret_cast(temp_buffer.data()),
+ size * this->channels_ * sizeof(int16_t));
+ if (this->file_.gcount() !=
+ static_cast(size * this->channels_ * sizeof(int16_t))) {
+ return false; // End of file or read error
+ }
+
+ // Convert int16 samples to float and store in buffer
+ buffer.resize(size * this->channels_);
+ for (size_t i = 0; i < size * this->channels_; ++i) {
+ buffer[i] = int16ToFloat(temp_buffer[i]);
+ }
+
+ return true;
+}
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common/src/audio_player_main.cpp b/src/lib/audio_common/audio_common/src/audio_player_main.cpp
new file mode 100644
index 000000000..fb0b13f89
--- /dev/null
+++ b/src/lib/audio_common/audio_common/src/audio_player_main.cpp
@@ -0,0 +1,33 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include
+
+#include "audio_common/audio_player_node.hpp"
+
+int main(int argc, char *argv[]) {
+ rclcpp::init(argc, argv);
+ auto node = std::make_shared();
+ rclcpp::spin(node);
+ rclcpp::shutdown();
+ return 0;
+}
diff --git a/src/lib/audio_common/audio_common/src/music_main.cpp b/src/lib/audio_common/audio_common/src/music_main.cpp
new file mode 100644
index 000000000..e93c441c3
--- /dev/null
+++ b/src/lib/audio_common/audio_common/src/music_main.cpp
@@ -0,0 +1,33 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include
+
+#include "audio_common/music_node.hpp"
+
+int main(int argc, char **argv) {
+ rclcpp::init(argc, argv);
+ auto node = std::make_shared();
+ rclcpp::spin(node);
+ rclcpp::shutdown();
+ return 0;
+}
diff --git a/src/lib/audio_common/audio_common/src/tts_main.cpp b/src/lib/audio_common/audio_common/src/tts_main.cpp
new file mode 100644
index 000000000..07f23d775
--- /dev/null
+++ b/src/lib/audio_common/audio_common/src/tts_main.cpp
@@ -0,0 +1,33 @@
+// MIT License
+//
+// Copyright (c) 2024 Miguel Ángel González Santamarta
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include
+
+#include "audio_common/tts_node.hpp"
+
+int main(int argc, char *argv[]) {
+ rclcpp::init(argc, argv);
+ auto node = std::make_shared();
+ rclcpp::spin(node);
+ rclcpp::shutdown();
+ return 0;
+}
diff --git a/src/lib/audio_common/audio_common_msgs/CMakeLists.txt b/src/lib/audio_common/audio_common_msgs/CMakeLists.txt
new file mode 100644
index 000000000..a30191cd1
--- /dev/null
+++ b/src/lib/audio_common/audio_common_msgs/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.10)
+project(audio_common_msgs)
+
+if(POLICY CMP0148)
+ cmake_policy(SET CMP0148 OLD)
+endif()
+
+# find dependencies
+find_package(ament_cmake REQUIRED)
+find_package(std_msgs REQUIRED)
+find_package(rosidl_default_generators REQUIRED)
+
+rosidl_generate_interfaces(${PROJECT_NAME}
+ "msg/AudioData.msg"
+ "msg/AudioInfo.msg"
+ "msg/Audio.msg"
+ "msg/AudioStamped.msg"
+ "action/TTS.action"
+ "srv/MusicPlay.srv"
+ DEPENDENCIES std_msgs
+)
+
+ament_export_dependencies(
+ std_msgs
+ rosidl_default_runtime
+)
+
+ament_package()
diff --git a/src/lib/audio_common/audio_common_msgs/action/TTS.action b/src/lib/audio_common/audio_common_msgs/action/TTS.action
new file mode 100644
index 000000000..1f02060fe
--- /dev/null
+++ b/src/lib/audio_common/audio_common_msgs/action/TTS.action
@@ -0,0 +1,8 @@
+string text
+string language "en"
+float32 volume 1.0
+float32 rate 1.0
+---
+string text
+---
+audio_common_msgs/AudioStamped audio
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common_msgs/msg/Audio.msg b/src/lib/audio_common/audio_common_msgs/msg/Audio.msg
new file mode 100644
index 000000000..1f237c0b7
--- /dev/null
+++ b/src/lib/audio_common/audio_common_msgs/msg/Audio.msg
@@ -0,0 +1,2 @@
+audio_common_msgs/AudioData audio_data
+audio_common_msgs/AudioInfo info
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common_msgs/msg/AudioData.msg b/src/lib/audio_common/audio_common_msgs/msg/AudioData.msg
new file mode 100644
index 000000000..712da1afa
--- /dev/null
+++ b/src/lib/audio_common/audio_common_msgs/msg/AudioData.msg
@@ -0,0 +1,5 @@
+float32[] float32_data
+int32[] int32_data
+int16[] int16_data
+int8[] int8_data
+uint8[] uint8_data
diff --git a/src/lib/audio_common/audio_common_msgs/msg/AudioInfo.msg b/src/lib/audio_common/audio_common_msgs/msg/AudioInfo.msg
new file mode 100644
index 000000000..a7498f5dd
--- /dev/null
+++ b/src/lib/audio_common/audio_common_msgs/msg/AudioInfo.msg
@@ -0,0 +1,4 @@
+int32 format
+int32 channels
+int32 rate
+int32 chunk
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common_msgs/msg/AudioStamped.msg b/src/lib/audio_common/audio_common_msgs/msg/AudioStamped.msg
new file mode 100644
index 000000000..ac6ea5b8f
--- /dev/null
+++ b/src/lib/audio_common/audio_common_msgs/msg/AudioStamped.msg
@@ -0,0 +1,2 @@
+std_msgs/Header header
+audio_common_msgs/Audio audio
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common_msgs/package.xml b/src/lib/audio_common/audio_common_msgs/package.xml
new file mode 100644
index 000000000..dc3cff815
--- /dev/null
+++ b/src/lib/audio_common/audio_common_msgs/package.xml
@@ -0,0 +1,17 @@
+
+
+
+ audio_common_msgs
+ 4.0.8
+ audio_common_msgs
+ Miguel Ángel González Santamarta
+ MIT
+ ament_cmake
+ rosidl_interface_packages
+ std_msgs
+
+ ament_cmake
+ rosidl_default_generators
+ rosidl_default_runtime
+
+
\ No newline at end of file
diff --git a/src/lib/audio_common/audio_common_msgs/srv/MusicPlay.srv b/src/lib/audio_common/audio_common_msgs/srv/MusicPlay.srv
new file mode 100644
index 000000000..5699d6bc8
--- /dev/null
+++ b/src/lib/audio_common/audio_common_msgs/srv/MusicPlay.srv
@@ -0,0 +1,5 @@
+string audio
+string file_path ""
+bool loop false
+---
+bool success
\ No newline at end of file