Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * Inspired by tonegenerator of
5 : * Laurielle Lea <laurielle.lea@savoirfairelinux.com> (2004)
6 : * Inspired by ringbuffer of Audacity Project
7 : *
8 : * This program is free software: you can redistribute it and/or modify
9 : * it under the terms of the GNU General Public License as published by
10 : * the Free Software Foundation, either version 3 of the License, or
11 : * (at your option) any later version.
12 : *
13 : * This program is distributed in the hope that it will be useful,
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : * GNU General Public License for more details.
17 : *
18 : * You should have received a copy of the GNU General Public License
19 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 : */
21 : #include "tone.h"
22 : #include "logger.h"
23 : #include "string_utils.h"
24 :
25 : #include <vector>
26 : #include <cmath>
27 : #include <cstdlib>
28 :
29 : namespace jami {
30 :
31 380 : Tone::Tone(std::string_view definition, unsigned int sampleRate, AVSampleFormat sampleFormat)
32 380 : : AudioLoop(AudioFormat(sampleRate, 1, sampleFormat))
33 : {
34 380 : genBuffer(definition); // allocate memory with definition parameter
35 380 : }
36 :
37 : struct ParsedDefinition
38 : {
39 : unsigned total_samples;
40 : std::vector<std::tuple<unsigned, unsigned, unsigned>> frequencies;
41 : };
42 :
43 : ParsedDefinition
44 348 : parseDefinition(std::string_view definition, unsigned sampleRate)
45 : {
46 348 : ParsedDefinition parsed;
47 348 : parsed.total_samples = 0;
48 :
49 348 : std::string_view s; // portion of frequencyq
50 957 : while (getline_full(definition, s, ',')) {
51 : // Sample string: "350+440" or "350+440/2000,244+655/2000"
52 : unsigned low, high, time;
53 : size_t count; // number of int for one sequence
54 :
55 : // The 1st frequency is before the first + or the /
56 609 : size_t pos_plus = s.find('+');
57 609 : size_t pos_slash = s.find('/');
58 609 : size_t len = s.length();
59 609 : size_t endfrequency = 0;
60 :
61 609 : if (pos_slash == std::string::npos) {
62 87 : time = 0;
63 87 : endfrequency = len;
64 : } else {
65 522 : time = to_int<unsigned>(s.substr(pos_slash + 1, len - pos_slash - 1), 0);
66 522 : endfrequency = pos_slash;
67 : }
68 :
69 : // without a plus = 1 frequency
70 609 : if (pos_plus == std::string::npos) {
71 261 : low = to_int<unsigned>(s.substr(0, endfrequency), 0);
72 261 : high = 0;
73 : } else {
74 348 : low = to_int<unsigned>(s.substr(0, pos_plus), 0);
75 348 : high = to_int<unsigned>(s.substr(pos_plus + 1, endfrequency - pos_plus - 1), 0);
76 : }
77 :
78 : // If there is time or if it's unlimited
79 609 : if (time == 0)
80 87 : count = sampleRate;
81 : else
82 522 : count = (sampleRate * time) / 1000;
83 :
84 609 : parsed.frequencies.emplace_back(low, high, count);
85 609 : parsed.total_samples += count;
86 : }
87 696 : return parsed;
88 0 : }
89 :
90 : void
91 380 : Tone::genBuffer(std::string_view definition)
92 : {
93 380 : if (definition.empty())
94 32 : return;
95 :
96 348 : auto [total_samples, frequencies] = parseDefinition(definition, format_.sample_rate);
97 348 : buffer_->nb_samples = total_samples;
98 348 : buffer_->format = format_.sampleFormat;
99 348 : buffer_->sample_rate = format_.sample_rate;
100 348 : av_channel_layout_default(&buffer_->ch_layout, format_.nb_channels);
101 348 : av_frame_get_buffer(buffer_.get(), 0);
102 :
103 348 : size_t outPos = 0;
104 957 : for (auto& [low, high, count] : frequencies) {
105 609 : genSin(buffer_.get(), outPos, count, low, high);
106 609 : outPos += count;
107 : }
108 348 : }
109 :
110 : void
111 1121 : Tone::genSin(AVFrame* buffer, size_t outPos, unsigned nb_samples, unsigned lowFrequency, unsigned highFrequency)
112 : {
113 : static constexpr auto PI_2 = 3.141592653589793238462643383279502884L * 2.0L;
114 1121 : const double sr = (double) buffer->sample_rate;
115 1121 : const double dx_h = sr ? PI_2 * lowFrequency / sr : 0.0;
116 1121 : const double dx_l = sr ? PI_2 * highFrequency / sr : 0.0;
117 : static constexpr double DATA_AMPLITUDE_S16 = 2048;
118 : static constexpr double DATA_AMPLITUDE_FLT = 0.0625;
119 :
120 1121 : if (buffer->format == AV_SAMPLE_FMT_S16 || buffer->format == AV_SAMPLE_FMT_S16P) {
121 1121 : int16_t* ptr = ((int16_t*) buffer->data[0]) + outPos;
122 20025121 : for (size_t t = 0; t < nb_samples; t++) {
123 20024000 : ptr[t] = DATA_AMPLITUDE_S16 * (sin(t * dx_h) + sin(t * dx_l));
124 : }
125 1121 : } else if (buffer->format == AV_SAMPLE_FMT_FLT || buffer->format == AV_SAMPLE_FMT_FLTP) {
126 0 : float* ptr = ((float*) buffer->data[0]) + outPos;
127 0 : for (size_t t = 0; t < nb_samples; t++) {
128 0 : ptr[t] = (sin(t * dx_h) + sin(t * dx_l)) * DATA_AMPLITUDE_FLT;
129 : }
130 0 : } else {
131 0 : JAMI_ERROR("Unsupported sample format: {}", av_get_sample_fmt_name((AVSampleFormat) buffer->format));
132 : }
133 1121 : }
134 :
135 : } // namespace jami
|