Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"xref": [
"https://bonsai-rx.org/docs/xrefmap.yml",
"https://bonsai-rx.org/gui/xrefmap.yml",
"https://bonsai-rx.org/numerics/xrefmap.yml",
"https://horizongir.github.io/opencv.net/xrefmap.yml",
"https://horizongir.github.io/ZedGraph/xrefmap.yml",
"https://horizongir.github.io/opentk/xrefmap.yml",
Expand Down
Binary file added images/device-soundcard-connection.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/device-soundcard-pcb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
155 changes: 155 additions & 0 deletions tutorials/soundcard-playsound.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Play Sound

The [Harp SoundCard](https://github.com/harp-tech/device.soundcard) supports playback of waveforms stored in its onboard memory. It also includes an internal sine wave generator for pure tones. The following exercises demonstrate how to play these sounds in Bonsai.

> [!WARNING]
> When adding these operators to the workflow, make sure to use the device-specific versions, e.g. `Device (Harp.SoundCard)` instead of `Device (Harp)`. If correctly selected, the names of these operators in the workflow panel will change to reflect either the name of the device or the selected register/payload.

## Prerequisites

- Install the `Bonsai.Windows.Input` package from the Bonsai [package manager](https://bonsai-rx.org/docs/articles/packages.html).

## Device pattern

Set up the standard Harp [device pattern](../articles/operators.md#device-pattern) to initialize the device, log data, broadcast events, and send commands to the `SoundCard`.

:::workflow
![SoundCard Device Pattern](../workflows/soundcard-playsound-devicepattern.bonsai)
:::

- Insert a [`Device`] operator and set the `PortName` property to the communications port for the device.
- Insert a [`DeviceDataWriter`] sink and set the `Path` property (e.g. `SoundCard.harp`).
- This will save the data in the standard Harp logging format, which can be loaded with [`harp-python`](../articles/python.md).
- Insert a [`PublishSubject`] operator and name it `SoundCard Events`.
- Right-click the [`Device`] operator, select "Create Source (Bonsai.Harp.HarpMessage)" > "BehaviorSubject".
- Name the generated [``BehaviourSubject`1``] [source subject](https://bonsai-rx.org/docs/articles/subjects.html#source-subjects) `SoundCard Commands`.
- Connect it as input to the [`Device`] operator.

## Exercise 1 - Play sound index

Sounds can be played from the `SoundCard` onboard memory by using the [`PlaySoundOrFrequency`] register.

:::workflow
![Play Sound Index Keydown](../workflows/soundcard-playsound-indexkeydown.bonsai)
:::

- Insert a [`KeyDown`] source and set the `Filter` property to `A`.
- Insert a [`CreateMessage`] operator to construct a [`HarpMessage`] command to send to the device and configure these properties:
- `Payload` - Select [`PlaySoundOrFrequencyPayload`] from the property dropdown menu.
- `PlaySoundOrFrequency` - Set the index of the sound you want to play from the `SoundCard` onboard memory (2-31).
- Insert a [`MulticastSubject`] operator to send [`HarpMessage`] commands to named subjects, and configure the `Name` property to `SoundCard Commands`.

Run the workflow and press the <kbd>A</kbd> key to play the sound. Sound duration is determined by the length of the stored waveform.

You can replace [`KeyDown`] with other operators to trigger sound playback on other events in Bonsai.

:::workflow
![Play Sound Index Timer](../workflows/soundcard-playsound-indextimer.bonsai)
:::

- Replace the [`KeyDown`] source with a [`Timer`] source and set the `DueTime` property to 0.
- Insert a [`SubscribeWhen`] operator after `SoundCard Commands`.
- Insert a [`SubscribeSubject`] operator named `SoundCard Events`, and connect it to [`SubscribeWhen`].

> [!TIP]
> The `SubscribeWhen` > `SoundCard Events` pattern is useful for ensuring that [`HarpMessage`] commands are only sent after the [`Device`] has been initialized. It relies on the `DumpRegisters` property being set to `True` in [`Device`]. Use it when needed, for instance, if sounds are being played at the start of the workflow.

## Exercise 2 - Play pure tone

The [`PlaySoundOrFrequency`] register can also be used to play pure tones using the internal sine wave generator.

:::workflow
![Play Sound Frequency](../workflows/soundcard-playsound-frequency.bonsai)
:::

- Insert a [`KeyDown`] source and set the `Filter` property to `A`.
- Insert a [`CreateMessage`] operator and configure these properties:
- `Payload` - Select [`PlaySoundOrFrequencyPayload`].
- `PlaySoundOrFrequency` - Set the desired frequency in Hz (e.g. 1000).
- Insert a [`MulticastSubject`] operator named `SoundCard Commands`.

Unlike playback of sounds from the onboard memory, the pure tone will continue playing until it is terminated via the [`Stop`] register.

- Insert a [`KeyDown`] source and set the `Filter` property to `S`.
- Insert a [`CreateMessage`] operator and configure these properties:
- `Payload` - Select [`StopPayload`].
- `Stop` - Set the value to 1 (or any other value than 0).
- Insert a [`MulticastSubject`] operator named `SoundCard Commands`.

Run the workflow, press the <kbd>A</kbd> key to play the sound, and press the <kbd>S</kbd> key to stop playback.

> [!WARNING]
> The [`Stop`] register can only be used to stop playback from the internal sine wave generator, not sounds from the onboard memory.

## Exercise 3 - Lower sound playback volume

The [`PlaySoundOrFrequency`] register plays the sound at the amplitude of the stored waveform or at maximum amplitude for pure tones. To lower the volume, use the [`AttenuationAndPlaySoundOrFreq`] register instead to set the attenuation in 0.1 dB steps.

:::workflow
![Play Sound Attenuation](../workflows/soundcard-playsound-attenuation.bonsai)
:::

- Insert a [`KeyDown`] source and set the `Filter` property to `A`.
- Insert a [`CreateMessage`] operator and configure these properties:
- `Payload` - Select [`AttenuationAndPlaySoundOrFreqPayload`].
- `AttenuationAndPlaySoundOrFreq` - Click on the dialog button in the property grid to open the member collection editor. Add three members:
- The sound index or pure tone frequency to be played (e.g. 2).
- The attenuation of the left channel (e.g. 200 = -20 dB).
- The attenuation of the right channel (e.g. 200 = -20 dB).
- Insert a [`MulticastSubject`] operator named `SoundCard Commands`.

Run the workflow and press the <kbd>A</kbd> key to play the sound at reduced volume.

> [!TIP]
> Pure tone playback must be stopped explicitly via the [`Stop`] register.

## Exercise 4 - Trigger sound index playback with digital inputs

The `SoundCard` features digital input channels that can be configured to trigger sound index playback.

:::workflow
![Play Sound Digital Input](../workflows/soundcard-playsound-configureDI.bonsai)
:::

- Connect a TTL signal from another device to the digital input channel `DI0` (5 V tolerant) and `GND` on the `SoundCard`.
- Insert a [`SubscribeSubject`] operator named `SoundCard Events`.
- Insert a [`Take`] combinator and set the `Count` property to 1.
- Insert a [`CreateMessage`] operator and configure these properties:
- `Payload` - Select [`ConfigureDI0Payload`].
- `ConfigureDI0` - Select `StartSound`.
- Insert a second [`CreateMessage`] operator on a new branch and configure these properties:
- `Payload` - Select [`SoundIndexDI0Payload`].
- `SoundIndexDI0` - Set the sound index for playback.
- Combine both messages with a [`Merge`] combinator.
- Insert a [`MulticastSubject`] operator named `SoundCard Commands`.

Run the workflow and send the TTL signal from the other device to trigger sound playback.

> [!WARNING]
> Only sound index playback is supported currently.

> [!TIP]
> The `SoundCard Events` > `Take(1)` is another useful pattern for ensuring that configuration commands are sent as soon as the `SoundCard` has initialized. It also relies on the `DumpRegisters` property being set to `True` in the [`Device`] operator.

<!--Reference Style Links -->
[`AttenuationAndPlaySoundOrFreq`]: xref:Harp.SoundCard.AttenuationAndPlaySoundOrFreq
[`AttenuationAndPlaySoundOrFreqPayload`]: xref:Harp.SoundCard.CreateAttenuationAndPlaySoundOrFreqPayload
[``BehaviourSubject`1``]: xref:Bonsai.Reactive.BehaviorSubject
[`ConfigureDI0Payload`]: xref:Harp.SoundCard.CreateConfigureDI0Payload
[`SoundIndexDI0Payload`]: xref:Harp.SoundCard.CreateSoundIndexDI0Payload
[`CreateMessage`]: xref:Harp.SoundCard.CreateMessage
[`Device`]: xref:Harp.SoundCard.Device
[`DeviceDataWriter`]: xref:Harp.SoundCard.DeviceDataWriter
[`HarpMessage`]: xref:Bonsai.Harp.HarpMessage
[`KeyDown`]: xref:Bonsai.Windows.Input.KeyDown
[`Merge`]: xref:Bonsai.Reactive.Merge
[`MulticastSubject`]: xref:Bonsai.Expressions.MulticastSubject
[`PlaySoundOrFrequency`]: xref:Harp.SoundCard.PlaySoundOrFrequency
[`PlaySoundOrFrequencyPayload`]: xref:Harp.SoundCard.CreatePlaySoundOrFrequencyPayload
[`PublishSubject`]: xref:Bonsai.Reactive.PublishSubject
[`Stop`]: xref:Harp.SoundCard.Stop
[`StopPayload`]: xref:Harp.SoundCard.CreateStopPayload
[`SubscribeSubject`]: xref:Bonsai.Expressions.SubscribeSubject
[`SubscribeWhen`]: xref:Bonsai.Reactive.SubscribeWhen
[`Take`]: xref:Bonsai.Reactive.Take
[`Timer`]: xref:Bonsai.Reactive.Timer
46 changes: 46 additions & 0 deletions tutorials/soundcard-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Getting Started

The [Harp SoundCard](https://github.com/harp-tech/device.soundcard) is a high-performance audio device with two output channels using 24-bit DACs and a maximum sampling rate of 192 kHz.

![Harp SoundCard](../images/device-soundcard-pcb.png){width=300}

## Installation

- Install the WinUSB driver if you plan to upload sounds to the onboard memory:
- Download and launch [Zadig](https://zadig.akeo.ie/).
- Connect the USB Micro-B cable to the computer.
- Select the "Harp Sound Card" from the list. If the device is not available, go to "Options" > "List All Devices".
- Select the "WinUSB" driver and click "Install Driver".
- Install [Bonsai](https://bonsai-rx.org/docs/articles/installation.html).
- Install the `Harp.SoundCard` package by searching for it in the [Bonsai package manager](https://bonsai-rx.org/docs/articles/packages.html).

Waveforms can be generated and uploaded to the `SoundCard` in Bonsai. Optionally, you can use the [Harp SoundCard GUI](https://bitbucket.org/fchampalimaud/downloads/downloads/Harp_Sound_Card_v1.3.2.zip) as a standalone interface for waveform management. This requires the [LabVIEW runtime](https://bitbucket.org/fchampalimaud/downloads/downloads/Runtime-1.0.zip) to be installed first.

## Connections

![Harp SoundCard Connections](../images/device-soundcard-connection.jpg){width=450}

*<small>Single channel connection diagram with Harp Audio Amplifier and attached speaker. Reproduced from [Silva et al. (2024)](https://doi.org/10.1016/j.ohx.2024.e00555). CC BY 4.0.</small>*

**Amplifier** - The `SoundCard` requires an external amplifier. For high-fidelity applications, consider using the [Harp Audio Amplifier](https://github.com/harp-tech/peripheral.audioamp).

**Speaker** - The choice of speaker depends on the amplifier. For the `Harp Audio Amplifier`, any speaker with an impedance from 4 to 8 ohms can be used. The XT25SC90-04 (Peerless by Tymphany) has been tested and has a good frequency response up to 80 kHz.

## Testing the device

:::workflow
![SoundCard Hello World](../workflows/soundcard-helloworld.bonsai)
:::

- Hover over the workflow cell above, click the "Copy" icon in the top right, and paste the workflow into Bonsai.
- Set the `PortName` property of the [`SoundCard`](xref:Harp.SoundCard.Device) operator to the communications port of the `SoundCard` (e.g. COM7).
- Run the workflow. If the `SoundCard` is properly connected, you should hear a short tone.

<br>

---

These tutorials were written and tested with:<br>
**Hardware** v2.2<br>
**Firmware** v2.2<br>
**Harp.SoundCard** v0.2
135 changes: 135 additions & 0 deletions tutorials/soundcard-uploadwaveform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Upload Waveform

The `Harp.SoundCard` Bonsai package supports preload and update of sound waveforms during runtime. The following exercises will demonstrate how to generate and load various waveforms for upload to the `SoundCard` in Bonsai.

## Prerequisites

- Install the following packages from the Bonsai [package manager](https://bonsai-rx.org/docs/articles/packages.html):
- `Bonsai.WindowsInput`
- `Bonsai.Dsp`
- `Bonsai.Dsp.Design`
- `Bonsai.Audio`
- `Bonsai.Numerics`

## UpdateSoundWaveform

Uploading of waveforms is done with the [`UpdateSoundWaveform`] operator, which interfaces directly with the `SoundCard` onboard memory via `libusb` (not [`HarpMessage`] commands).

:::workflow
![UpdateSoundWaveform Operator](../workflows/soundcard-uploadwaveform-updatesoundwaveform.bonsai)
:::

For each of the following exercises, configure these properties for the [`UpdateSoundWaveform`] operator:
- `DeviceIndex` - Set the index of the `SoundCard` device to update. If no index is specified, the first `SoundCard` will be used.
- `SampleRate` - Set the sample rate used for playback. Only 96 kHz or 192 kHz are supported.
- `SoundIndex` - Set the index of the sound to update. The `SoundCard` onboard memory is partitioned into 32 indices, of which index 2-31 can be used for storing waveforms.
- `SoundName` - Set the name of the sound to be stored in the device. This field is optional.

Continuous streaming of sample buffers is not supported, so the entire waveform must be sent during upload. Dynamic update of waveforms when another sound is playing is only possible for waveforms with a 96 kHz sample rate.

## Waveform Requirements

- `Type` - The [`UpdateSoundWaveform`] operator accepts `byte[]` and `Mat` type inputs. For these exercises, we will use `Mat`, a matrix format commonly used for audio data in Bonsai, where rows represent channels and columns represent samples.
- `Bit Depth` - Waveforms must be encoded as 32-bit signed integers (`S32`).
- `Channels` - Both mono and stereo waveforms are supported.
- `Size` - Each sound index on the `SoundCard` memory can store an 8 MB file, corresponding to ~2 million samples. Mono waveforms are duplicated for dual-channel playback and occupy the same amount of storage space as stereo waveforms.

Given these parameters, sounds are limited to a length of 10.922 s at a 96 kHz sample rate, or 5.461 s at 192 kHz.

## Exercise 1: Generate periodic waveform

Pure tones and other periodic waveforms can be generated using a function generator. Sound length is determined by the number of samples in combination with the sampling rate.

:::workflow
![Upload Waveform Pure Tone](../workflows/soundcard-uploadwaveform-puretone.bonsai)
:::

- Insert a [`KeyDown`] source and set the `Filter` property to `A`.
- Insert a [`FunctionGenerator`] source and configure the following properties:
- `Amplitude` - Set the volume in 32-bit range (e.g. 2147483647 for max value).
- `BufferLength` - Set the number of samples (e.g. 192000 for a 2 s sound at 96 kHz sample rate).
- `Depth` - Select `S32` for 32-bit signed integers.
- `Frequency` - Set the frequency of the pure tone, in Hz (e.g. 2000).
- `SampleRate` - Set the sample rate, in Hz (e.g. 96000).
- `Waveform` - Select `Sine` for pure tones.
- Insert a [`UpdateSoundWaveform`] operator and configure the relevant properties.

Run the workflow and press the <kbd>A</kbd> key to upload the waveform. Test it out by playing the [sound index](soundcard-playsound.md#exercise-1---play-sound-index).

> [!WARNING]
> [`FunctionGenerator`] should always be connected to another trigger source to prevent continuous buffer generation. [`KeyDown`] can be replaced with other operators, such as a one-shot [`Timer`].

## Exercise 2: Generate white noise waveform

White noise can be generated by randomly sampling from a discrete uniform distribution.

:::workflow
![Upload Waveform White Noise](../workflows/soundcard-uploadwaveform-whitenoise.bonsai)
:::

- Insert a [`CreateDiscreteUniform`] source and set the `Lower` and `Upper` properties to the volume in 32-bit range.
- Insert a [`SampleArray`] operator and set the `Count` property to the number of samples to draw (e.g. 192000 for a 2 s sound at 96 kHz sample rate).
- Insert a [`Sample`] operator.
- Insert a [`KeyDown`] source and set the `Filter` property to `A`.
- Insert a [`ConvertFromArray`] transform and set the `Depth` property to `S32`.
- Insert a [`UpdateSoundWaveform`] operator and configure the relevant properties.

Run the workflow and press the <kbd>A</kbd> key to upload the waveform. Test it out by playing the [sound index](soundcard-playsound.md#exercise-1---play-sound-index).

## Exercise 3: Load waveform from WAV file

Waveforms can be loaded from uncompressed WAV files, but will require bit depth conversion and scaling.

:::workflow
![Upload Waveform WAV File](../workflows/soundcard-uploadwaveform-wavfile.bonsai)
:::

- Insert a [`KeyDown`] source and set the `Filter` property to `A`.
- Insert an [`AudioReader`] source and configure the following properties:
- `BufferLength` - Set 0 to load the entire file into a single buffer (`SampleRate` is ignored here).
- `FileName` - Set the file path of the WAV file to load.
- Insert a [`ConvertScale`] transform and configure the following properties:
- `Depth` - Select `S32` for 32-bit signed integers.
- `Scale` - Set the scaling factor to fit the `S32` range (e.g. 65536 for a 1:1 conversion from a 16-bit WAV file).
- Insert a [`UpdateSoundWaveform`] operator and configure the relevant properties.

Run the workflow and press the <kbd>A</kbd> key to upload the waveform. Test it out by playing the [sound index](soundcard-playsound.md#exercise-1---play-sound-index).

> [!WARNING]
> 24-bit (or higher) and `WAVE_FORMAT_EXTENSIBLE` WAV files are not currently supported by [`AudioReader`].

## Exercise 4: Load waveform from raw binary matrix file

Waveforms can also be loaded from raw binary matrix files (`*.bin`).

:::workflow
![Upload Waveform BIN File](../workflows/soundcard-uploadwaveform-binfile.bonsai)
:::

- Insert a [`KeyDown`] source and set the `Filter` property to `A`.
- Insert a [`MatrixReader`] source and configure the following properties:
- `BufferLength` - Set 0 to load the entire file into a single buffer (`SampleRate` is ignored here).
- `ChannelCount` - Set the number of channels in the file (1 for mono or 2 for stereo).
- `Depth` - Select the bit depth of the input file (e.g. `S32`).
- `Layout` - For mono files, this has no effect. For stereo files, select `RowMajor` if the channels are stored sequentially, or `ColumnMajor` if the channels are interleaved.
- `Path` - Set the file path of the `*.bin` file to load.
- Insert a [`UpdateSoundWaveform`] operator and configure the relevant properties.

Run the workflow and press the <kbd>A</kbd> key to upload the waveform. Test it out by playing the [sound index](soundcard-playsound.md#exercise-1---play-sound-index).

> [!TIP]
> Add a [`ConvertScale`] after [`MatrixReader`] if the `*.bin` bit depth is not `S32`.

<!--Reference Style Links -->
[`AudioReader`]: xref:Bonsai.Audio.AudioReader
[`ConvertScale`]: xref:Bonsai.Dsp.ConvertScale
[`ConvertFromArray`]: xref:Bonsai.Dsp.ConvertFromArray
[`CreateDiscreteUniform`]: xref:Bonsai.Numerics.Distributions.CreateDiscreteUniform
[`HarpMessage`]: xref:Bonsai.Harp.HarpMessage
[`FunctionGenerator`]: xref:Bonsai.Dsp.FunctionGenerator
[`KeyDown`]: xref:Bonsai.Windows.Input.KeyDown
[`MatrixReader`]: xref:Bonsai.Dsp.MatrixReader
[`Sample`]: xref:Bonsai.Reactive.Sample
[`SampleArray`]: xref:Bonsai.Numerics.Distributions.SampleArray
[`Timer`]: xref:Bonsai.Reactive.Timer
[`UpdateSoundWaveform`]: xref:Harp.SoundCard.UpdateSoundWaveform
6 changes: 5 additions & 1 deletion tutorials/toc.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
- name: Harp Hobgoblin
- href: hobgoblin-setup.md
- href: hobgoblin-acquisition.md
- href: hobgoblin-reaction.md
- href: hobgoblin-reaction.md
- name: Harp SoundCard
- href: soundcard-setup.md
- href: soundcard-playsound.md
- href: soundcard-uploadwaveform.md
Loading