1*6a569666SGoran Mekić /*
2*6a569666SGoran Mekić * SPDX-License-Identifier: BSD-2-Clause
3*6a569666SGoran Mekić *
4*6a569666SGoran Mekić * Copyright (c) 2024 The FreeBSD Foundation
5*6a569666SGoran Mekić * Copyright (c) 2025 Goran Mekić
6*6a569666SGoran Mekić *
7*6a569666SGoran Mekić * Portions of this software were developed by Christos Margiolis
8*6a569666SGoran Mekić * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
9*6a569666SGoran Mekić *
10*6a569666SGoran Mekić * Redistribution and use in source and binary forms, with or without
11*6a569666SGoran Mekić * modification, are permitted provided that the following conditions
12*6a569666SGoran Mekić * are met:
13*6a569666SGoran Mekić * 1. Redistributions of source code must retain the above copyright
14*6a569666SGoran Mekić * notice, this list of conditions and the following disclaimer.
15*6a569666SGoran Mekić * 2. Redistributions in binary form must reproduce the above copyright
16*6a569666SGoran Mekić * notice, this list of conditions and the following disclaimer in the
17*6a569666SGoran Mekić * documentation and/or other materials provided with the distribution.
18*6a569666SGoran Mekić *
19*6a569666SGoran Mekić * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20*6a569666SGoran Mekić * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21*6a569666SGoran Mekić * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22*6a569666SGoran Mekić * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23*6a569666SGoran Mekić * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24*6a569666SGoran Mekić * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25*6a569666SGoran Mekić * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26*6a569666SGoran Mekić * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27*6a569666SGoran Mekić * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28*6a569666SGoran Mekić * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29*6a569666SGoran Mekić * SUCH DAMAGE.
30*6a569666SGoran Mekić */
31*6a569666SGoran Mekić
32*6a569666SGoran Mekić #include "oss.h"
33*6a569666SGoran Mekić
34*6a569666SGoran Mekić /*
35*6a569666SGoran Mekić * Split input buffer into channels. The input buffer is in interleaved format,
36*6a569666SGoran Mekić * which means if we have 2 channels (L and R), this is what the buffer of 8
37*6a569666SGoran Mekić * samples would contain: L,R,L,R,L,R,L,R. The result of this function is a
38*6a569666SGoran Mekić * buffer containing: L,L,L,L,R,R,R,R.
39*6a569666SGoran Mekić */
40*6a569666SGoran Mekić static void
to_channels(struct config * config,void * output)41*6a569666SGoran Mekić to_channels(struct config *config, void *output)
42*6a569666SGoran Mekić {
43*6a569666SGoran Mekić uint8_t *in = config->buf;
44*6a569666SGoran Mekić uint8_t *out = output;
45*6a569666SGoran Mekić int i, channel, index, offset, byte;
46*6a569666SGoran Mekić
47*6a569666SGoran Mekić /* Iterate over bytes in the input buffer */
48*6a569666SGoran Mekić for (byte = 0; byte < config->buffer_info.bytes;
49*6a569666SGoran Mekić byte += config->sample_size) {
50*6a569666SGoran Mekić /*
51*6a569666SGoran Mekić * Get index of a sample in the input buffer measured in
52*6a569666SGoran Mekić * samples
53*6a569666SGoran Mekić */
54*6a569666SGoran Mekić i = byte / config->sample_size;
55*6a569666SGoran Mekić
56*6a569666SGoran Mekić /* Get which channel is being processed */
57*6a569666SGoran Mekić channel = i % config->audio_info.max_channels;
58*6a569666SGoran Mekić
59*6a569666SGoran Mekić /* Get offset of the sample inside a single channel */
60*6a569666SGoran Mekić offset = i / config->audio_info.max_channels;
61*6a569666SGoran Mekić
62*6a569666SGoran Mekić /* Get index of a sample in the output buffer */
63*6a569666SGoran Mekić index = (channel * config->chsamples + offset) *
64*6a569666SGoran Mekić config->sample_size;
65*6a569666SGoran Mekić
66*6a569666SGoran Mekić /* Copy singe sample from input to output */
67*6a569666SGoran Mekić memcpy(out+index, in+byte, config->sample_size);
68*6a569666SGoran Mekić }
69*6a569666SGoran Mekić }
70*6a569666SGoran Mekić
71*6a569666SGoran Mekić /*
72*6a569666SGoran Mekić * Convert channels into interleaved format and put into output buffer
73*6a569666SGoran Mekić */
74*6a569666SGoran Mekić static void
to_interleaved(struct config * config,void * input)75*6a569666SGoran Mekić to_interleaved(struct config *config, void *input)
76*6a569666SGoran Mekić {
77*6a569666SGoran Mekić uint8_t *out = config->buf;
78*6a569666SGoran Mekić uint8_t *in = input;
79*6a569666SGoran Mekić int i, index, offset, channel, byte;
80*6a569666SGoran Mekić
81*6a569666SGoran Mekić /* Iterate over bytes in the input buffer */
82*6a569666SGoran Mekić for (byte = 0; byte < config->buffer_info.bytes;
83*6a569666SGoran Mekić byte += config->sample_size) {
84*6a569666SGoran Mekić /*
85*6a569666SGoran Mekić * Get index of a sample in the input buffer measured in
86*6a569666SGoran Mekić * samples
87*6a569666SGoran Mekić */
88*6a569666SGoran Mekić index = byte / config->sample_size;
89*6a569666SGoran Mekić
90*6a569666SGoran Mekić /* Get which channel is being processed */
91*6a569666SGoran Mekić channel = index / config->chsamples;
92*6a569666SGoran Mekić
93*6a569666SGoran Mekić /* Get offset of the sample inside a single channel */
94*6a569666SGoran Mekić offset = index % config->chsamples;
95*6a569666SGoran Mekić
96*6a569666SGoran Mekić /* Get index of a sample in the output buffer */
97*6a569666SGoran Mekić i = (config->audio_info.max_channels * offset + channel) *
98*6a569666SGoran Mekić config->sample_size;
99*6a569666SGoran Mekić
100*6a569666SGoran Mekić /* Copy singe sample from input to output */
101*6a569666SGoran Mekić memcpy(out+i, in+byte, config->sample_size);
102*6a569666SGoran Mekić }
103*6a569666SGoran Mekić }
104*6a569666SGoran Mekić
105*6a569666SGoran Mekić int
main(int argc,char * argv[])106*6a569666SGoran Mekić main(int argc, char *argv[])
107*6a569666SGoran Mekić {
108*6a569666SGoran Mekić struct config config = {
109*6a569666SGoran Mekić .device = "/dev/dsp",
110*6a569666SGoran Mekić .mode = O_RDWR,
111*6a569666SGoran Mekić .format = AFMT_S32_NE,
112*6a569666SGoran Mekić .sample_rate = 48000,
113*6a569666SGoran Mekić };
114*6a569666SGoran Mekić int32_t *channels;
115*6a569666SGoran Mekić int rc, bytes;
116*6a569666SGoran Mekić
117*6a569666SGoran Mekić oss_init(&config);
118*6a569666SGoran Mekić bytes = config.buffer_info.bytes;
119*6a569666SGoran Mekić channels = malloc(bytes);
120*6a569666SGoran Mekić
121*6a569666SGoran Mekić for (;;) {
122*6a569666SGoran Mekić if ((rc = read(config.fd, config.buf, bytes)) < bytes) {
123*6a569666SGoran Mekić warn("Requested %d bytes, but read %d!\n", bytes, rc);
124*6a569666SGoran Mekić break;
125*6a569666SGoran Mekić }
126*6a569666SGoran Mekić /*
127*6a569666SGoran Mekić * Strictly speaking, we could omit "channels" and operate only
128*6a569666SGoran Mekić * using config->buf, but this example tries to show the real
129*6a569666SGoran Mekić * world application usage. The problem is that the buffer is
130*6a569666SGoran Mekić * in interleaved format, and if you'd like to do any
131*6a569666SGoran Mekić * processing and/or mixing, it is easier to do that if samples
132*6a569666SGoran Mekić * are grouped per channel.
133*6a569666SGoran Mekić */
134*6a569666SGoran Mekić to_channels(&config, channels);
135*6a569666SGoran Mekić to_interleaved(&config, channels);
136*6a569666SGoran Mekić if ((rc = write(config.fd, config.buf, bytes)) < bytes) {
137*6a569666SGoran Mekić warn("Requested %d bytes, but wrote %d!\n", bytes, rc);
138*6a569666SGoran Mekić break;
139*6a569666SGoran Mekić }
140*6a569666SGoran Mekić }
141*6a569666SGoran Mekić
142*6a569666SGoran Mekić free(channels);
143*6a569666SGoran Mekić free(config.buf);
144*6a569666SGoran Mekić close(config.fd);
145*6a569666SGoran Mekić
146*6a569666SGoran Mekić return (0);
147*6a569666SGoran Mekić }
148