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 283 : Tone::Tone(std::string_view definition, unsigned int sampleRate, AVSampleFormat sampleFormat) 32 283 : : AudioLoop(AudioFormat(sampleRate, 1, sampleFormat)) 33 : { 34 283 : genBuffer(definition); // allocate memory with definition parameter 35 283 : } 36 : 37 : struct ParsedDefinition 38 : { 39 : unsigned total_samples; 40 : std::vector<std::tuple<unsigned, unsigned, unsigned>> frequencies; 41 : }; 42 : 43 : ParsedDefinition 44 252 : parseDefinition(std::string_view definition, unsigned sampleRate) 45 : { 46 252 : ParsedDefinition parsed; 47 252 : parsed.total_samples = 0; 48 : 49 252 : std::string_view s; // portion of frequencyq 50 693 : 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 441 : size_t pos_plus = s.find('+'); 57 441 : size_t pos_slash = s.find('/'); 58 441 : size_t len = s.length(); 59 441 : size_t endfrequency = 0; 60 : 61 441 : if (pos_slash == std::string::npos) { 62 63 : time = 0; 63 63 : endfrequency = len; 64 : } else { 65 378 : time = to_int<unsigned>(s.substr(pos_slash + 1, len - pos_slash - 1), 0); 66 378 : endfrequency = pos_slash; 67 : } 68 : 69 : // without a plus = 1 frequency 70 441 : if (pos_plus == std::string::npos) { 71 189 : low = to_int<unsigned>(s.substr(0, endfrequency), 0); 72 189 : high = 0; 73 : } else { 74 252 : low = to_int<unsigned>(s.substr(0, pos_plus), 0); 75 252 : 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 441 : if (time == 0) 80 63 : count = sampleRate; 81 : else 82 378 : count = (sampleRate * time) / 1000; 83 : 84 441 : parsed.frequencies.emplace_back(low, high, count); 85 441 : parsed.total_samples += count; 86 : } 87 504 : return parsed; 88 0 : } 89 : 90 : void 91 283 : Tone::genBuffer(std::string_view definition) 92 : { 93 283 : if (definition.empty()) 94 31 : return; 95 : 96 252 : auto [total_samples, frequencies] = parseDefinition(definition, format_.sample_rate); 97 252 : buffer_->nb_samples = total_samples; 98 252 : buffer_->format = format_.sampleFormat; 99 252 : buffer_->sample_rate = format_.sample_rate; 100 252 : av_channel_layout_default(&buffer_->ch_layout, format_.nb_channels); 101 252 : av_frame_get_buffer(buffer_.get(), 0); 102 : 103 252 : size_t outPos = 0; 104 693 : for (auto& [low, high, count] : frequencies) { 105 441 : genSin(buffer_.get(), outPos, count, low, high); 106 441 : outPos += count; 107 : } 108 252 : } 109 : 110 : void 111 937 : 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 937 : const double sr = (double) buffer->sample_rate; 115 937 : const double dx_h = sr ? PI_2 * lowFrequency / sr : 0.0; 116 937 : 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 937 : if (buffer->format == AV_SAMPLE_FMT_S16 || buffer->format == AV_SAMPLE_FMT_S16P) { 121 937 : int16_t* ptr = ((int16_t*) buffer->data[0]) + outPos; 122 16504937 : for (size_t t = 0; t < nb_samples; t++) { 123 16504000 : ptr[t] = DATA_AMPLITUDE_S16 * (sin(t * dx_h) + sin(t * dx_l)); 124 : } 125 937 : } 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 937 : } 134 : 135 : } // namespace jami