LCOV - code coverage report
Current view: top level - muse/framework/audio/engine/internal/export - wavencoder.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 108 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "wavencoder.h"
      24              : 
      25              : #include <limits>
      26              : 
      27              : #include "../dsp/audiomathutils.h"
      28              : 
      29              : #include "global/io/iodevice.h"
      30              : #include "global/types/bytearray.h"
      31              : 
      32              : #include "log.h"
      33              : 
      34              : namespace muse::audio::encode {
      35              : namespace {
      36              : template<typename T>
      37            0 : void writeTagData(io::IODevice& outDev, const T value, size_t offset = 0)
      38              : {
      39            0 :     if (offset != 0) {
      40            0 :         outDev.seek(offset);
      41              :     }
      42              : 
      43              :     // little endian
      44            0 :     outDev.write(reinterpret_cast<const std::uint8_t*>(&value), sizeof(T));
      45            0 : }
      46              : 
      47              : struct WavHeader {
      48              :     uint32_t chunkSize = 0;
      49              :     uint16_t audioChannelsNumber = 0;
      50              :     uint16_t bitsPerSample = 0;
      51              :     uint32_t samplesPerChannel = 0;
      52              :     uint16_t code = 0;
      53              :     uint32_t sampleRate = 0;
      54              : 
      55            0 :     void write(io::IODevice& outDev)
      56              :     {
      57            0 :         const uint32_t bytesPerSample = bitsPerSample / 8;
      58            0 :         const uint32_t sampleDataLength = audioChannelsNumber * samplesPerChannel * bytesPerSample;
      59            0 :         const uint32_t headerLength = 20 + chunkSize + 8;
      60            0 :         const uint32_t file_length = headerLength + sampleDataLength;
      61            0 :         const uint32_t overallSize = file_length - 8;
      62            0 :         const uint32_t bytesPerFrame = audioChannelsNumber * bytesPerSample;
      63            0 :         const uint32_t bytesPerSec = audioChannelsNumber * sampleRate * bytesPerSample;
      64              : 
      65            0 :         outDev.write(ByteArray::fromRawData("RIFF", 4)); // chunk ID
      66              : 
      67            0 :         writeTagData<uint32_t>(outDev, overallSize);
      68            0 :         outDev.write(ByteArray::fromRawData("WAVE", 4)); // WAVEID
      69            0 :         outDev.write(ByteArray::fromRawData("fmt ", 4)); // chunk ID
      70              : 
      71            0 :         writeTagData<uint32_t>(outDev, chunkSize);
      72            0 :         writeTagData<uint16_t>(outDev, code);
      73            0 :         writeTagData<uint16_t>(outDev, audioChannelsNumber);
      74            0 :         writeTagData<uint32_t>(outDev, sampleRate);
      75            0 :         writeTagData<uint32_t>(outDev, bytesPerSec);
      76            0 :         writeTagData<uint16_t>(outDev, bytesPerFrame);
      77            0 :         writeTagData<uint16_t>(outDev, bitsPerSample);
      78              : 
      79            0 :         uint16_t ext = 0;
      80            0 :         if (chunkSize > 16) {
      81            0 :             writeTagData<uint16_t>(outDev, ext); // cbSize (extension size)
      82              :         }
      83            0 :         outDev.write(ByteArray::fromRawData("data", 4)); // chunk ID
      84              : 
      85            0 :         writeTagData<uint32_t>(outDev, sampleDataLength);
      86            0 :     }
      87              : };
      88              : }
      89              : 
      90            0 : WavEncoder::WavEncoder(const SoundTrackFormat& format, io::IODevice& dstDevice)
      91            0 :     : AbstractAudioEncoder(format), m_dstDevice{&dstDevice}
      92              : {
      93            0 :     DO_ASSERT(m_dstDevice);
      94            0 : }
      95              : 
      96            0 : bool WavEncoder::begin(const samples_t)
      97              : {
      98            0 :     m_headerWritten = false;
      99            0 :     m_pcmStartPos = 0;
     100            0 :     return true;
     101              : }
     102              : 
     103            0 : bool WavEncoder::writePlaceholderHeader()
     104              : {
     105            0 :     WavHeader header;
     106            0 :     header.chunkSize = 18; // 18 is 2 bytes more to include cbsize field / extension size
     107              : 
     108            0 :     switch (m_format.sampleFormat) {
     109            0 :     case AudioSampleFormat::Int16:
     110            0 :         header.bitsPerSample = 16;
     111            0 :         header.code = 1; // PCM
     112            0 :         break;
     113            0 :     case AudioSampleFormat::Int24:
     114            0 :         header.bitsPerSample = 24;
     115            0 :         header.code = 1; // PCM
     116            0 :         break;
     117            0 :     case AudioSampleFormat::Float32:
     118            0 :         header.bitsPerSample = 32;
     119            0 :         header.code = 3; // IEEE_FLOAT
     120            0 :         break;
     121            0 :     case AudioSampleFormat::Undefined:
     122              :     default:
     123            0 :         return false;
     124              :     }
     125              : 
     126            0 :     header.audioChannelsNumber = m_format.outputSpec.audioChannelCount;
     127            0 :     header.sampleRate = m_format.outputSpec.sampleRate;
     128            0 :     header.samplesPerChannel = 0;
     129              : 
     130            0 :     header.write(*m_dstDevice);
     131            0 :     return true;
     132              : }
     133              : 
     134            0 : void WavEncoder::patchHeaderSizes()
     135              : {
     136            0 :     const size_t endPos = m_dstDevice->pos();
     137            0 :     if (endPos < m_pcmStartPos || endPos < 8) {
     138            0 :         return;
     139              :     }
     140              : 
     141            0 :     const uint64_t dataBytes = endPos - m_pcmStartPos;
     142            0 :     if (dataBytes > std::numeric_limits<uint32_t>::max()) {
     143            0 :         LOGE() << "WAV export: file size exceeds 32-bit chunk limits";
     144            0 :         return;
     145              :     }
     146              : 
     147            0 :     const uint32_t dataSize = static_cast<uint32_t>(dataBytes);
     148            0 :     const uint32_t riffChunkSize = static_cast<uint32_t>(endPos - 8);
     149              : 
     150            0 :     writeTagData<uint32_t>(*m_dstDevice, riffChunkSize, 4); // RIFF chunk size at offset 4
     151            0 :     writeTagData<uint32_t>(*m_dstDevice, dataSize, 42); // data chunk size at offset 42 (with 18-byte fmt + cbSize)
     152              : 
     153            0 :     m_dstDevice->seek(endPos);
     154              : }
     155              : 
     156            0 : size_t WavEncoder::encode(const samples_t samplesPerChannel, const float* input)
     157              : {
     158            0 :     if (!m_headerWritten) {
     159            0 :         if (!writePlaceholderHeader()) {
     160            0 :             return 0;
     161              :         }
     162            0 :         m_pcmStartPos = m_dstDevice->pos();
     163            0 :         m_headerWritten = true;
     164              :     }
     165              : 
     166            0 :     const int channels = m_format.outputSpec.audioChannelCount;
     167              : 
     168            0 :     if (m_format.sampleFormat == AudioSampleFormat::Float32) {
     169            0 :         for (samples_t sampleIdx = 0; sampleIdx < samplesPerChannel; ++sampleIdx) {
     170            0 :             for (audioch_t audioChNum = 0; audioChNum < channels; ++audioChNum) {
     171            0 :                 const int idx = static_cast<int>(sampleIdx) * channels + audioChNum;
     172            0 :                 if (m_dstDevice->write(reinterpret_cast<const std::uint8_t*>(input + idx), 4) != 4) {
     173            0 :                     return 0;
     174              :                 }
     175              :             }
     176              :         }
     177              :     } else {
     178            0 :         int bitsPerSample = 0;
     179            0 :         switch (m_format.sampleFormat) {
     180            0 :         case AudioSampleFormat::Int16:
     181            0 :             bitsPerSample = 16;
     182            0 :             break;
     183            0 :         case AudioSampleFormat::Int24:
     184            0 :             bitsPerSample = 24;
     185            0 :             break;
     186            0 :         default:
     187            0 :             return 0;
     188              :         }
     189              : 
     190            0 :         const int bytesToWrite = bitsPerSample / 8;
     191              : 
     192            0 :         for (samples_t sampleIdx = 0; sampleIdx < samplesPerChannel; ++sampleIdx) {
     193            0 :             for (audioch_t audioChNum = 0; audioChNum < channels; ++audioChNum) {
     194            0 :                 const int idx = static_cast<int>(sampleIdx) * channels + audioChNum;
     195              : 
     196            0 :                 const int32_t sampleInt = dsp::convertFloatSamples<int32_t>(input[idx], bitsPerSample);
     197              : 
     198            0 :                 m_dstDevice->write(reinterpret_cast<const std::uint8_t*>(&sampleInt), bytesToWrite);
     199              :             }
     200              :         }
     201              :     }
     202              : 
     203            0 :     return static_cast<size_t>(samplesPerChannel) * static_cast<size_t>(channels);
     204              : }
     205              : 
     206            0 : size_t WavEncoder::end()
     207              : {
     208            0 :     if (m_headerWritten) {
     209            0 :         patchHeaderSizes();
     210              :     }
     211              : 
     212            0 :     return 0;
     213              : }
     214              : }
        

Generated by: LCOV version 2.0-1