Line data Source code
1 : /* 2 : * Copyright (C) 2004-2025 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