From c57e297804632287c0be8c0d69af08fc702e0f41 Mon Sep 17 00:00:00 2001 From: Jeremy Bullock Date: Thu, 22 Oct 2020 10:43:35 -0600 Subject: [PATCH] Fix audio memory leak and fix windows distortion (#402) * Fix audio memory leak and fix windows distortion * Fix play button resetting --- toonz/sources/common/tsound/tsound_l.cpp | 2218 ----------------- toonz/sources/include/toonz/txshsoundcolumn.h | 66 +- toonz/sources/tnztools/tool.cpp | 32 +- toonz/sources/toonz/sceneviewer.cpp | 1 + toonz/sources/toonz/tapp.cpp | 18 +- toonz/sources/toonz/viewerpane.cpp | 3 +- toonz/sources/toonz/xshcellviewer.cpp | 12 +- toonz/sources/toonz/xshcolumnviewer.cpp | 8 +- toonz/sources/toonz/xsheetviewer.cpp | 11 +- toonz/sources/toonzlib/stage.cpp | 2 +- toonz/sources/toonzlib/txsheet.cpp | 22 +- toonz/sources/toonzlib/txshsoundcolumn.cpp | 98 +- 12 files changed, 165 insertions(+), 2326 deletions(-) delete mode 100644 toonz/sources/common/tsound/tsound_l.cpp diff --git a/toonz/sources/common/tsound/tsound_l.cpp b/toonz/sources/common/tsound/tsound_l.cpp deleted file mode 100644 index 36219e70..00000000 --- a/toonz/sources/common/tsound/tsound_l.cpp +++ /dev/null @@ -1,2218 +0,0 @@ - - -#include "tfilepath.h" -#include "tsound.h" -#include "tsound_io.h" -#include "tsop.h" -#include "tthread.h" -#include "texception.h" -#include "tsystem.h" -#include -#include -#include -#include - -#include - -#include -#include - -// using namespace std; - -// forward declaration -namespace { -int openMixer(); -int getCurrentRecordSource(int mixer); -bool writeVolume(int volume, int mixer, int indexDev); -bool selectInputDevice(TSoundInputDevice::Source dev); -string parseError(int error); -} - -// bisogna interagire con /dev/dsp per il settaggio delle -// caratteristiche della traccia tipo bit, rate, canali e -// per effettuare lettura/scrittura ossia record/play -// -// mentre bisogna interagire con /dev/mixer per modificare -// i valori del volume e per selezionare il dispositivo da -// cui registrare e ascoltare - -class SmartWatch { - struct timeval m_start_tv; - TINT32 m_totalus; - bool m_stopped; - -public: - SmartWatch() : m_totalus(0), m_stopped(true) { timerclear(&m_start_tv); } - void start() { - m_stopped = false; - gettimeofday(&m_start_tv, 0); - } - void stop() { - m_stopped = true; - struct timeval tv; - gettimeofday(&tv, 0); - m_totalus = (tv.tv_sec - m_start_tv.tv_sec) * 1000000 + - (tv.tv_usec - m_start_tv.tv_usec); - } - double getTotalTime() { - if (!m_stopped) // questa e' una porcata! - { - stop(); - m_stopped = false; - } - return m_totalus / 1000.; - } - - void addDelay(double ms) { m_start_tv.tv_usec += (long)(ms * 1000.); } -}; - -//====================================================================== -//====================================================================== -// CLASSI PER IL PLAYBACK -//====================================================================== -//====================================================================== -class TSoundOutputDeviceImp { -private: - static int m_count; - -public: - int m_dev; - bool m_stopped; - bool m_isPlaying; - bool m_looped; - TSoundTrackFormat m_currentFormat; - std::set m_supportedRate; - static std::multimap m_supportFormats; - - typedef pair WaitPair; - vector m_waitingTracks; - std::set m_listeners; - TThread::Executor m_executor; - TThread::Mutex m_mutex; - - TSoundOutputDeviceImp() - : m_dev(-1) - , m_stopped(false) - , m_isPlaying(false) - , m_looped(false) - , m_supportedRate() { - /* -if (m_count != 0) -throw TException("unable to create second instance of TSoundOutputDeviceImp"); -*/ - ++m_count; - checkSupportedFormat(); - }; - - ~TSoundOutputDeviceImp() { --m_count; }; - - bool doOpenDevice(); - bool doCloseDevice(); - bool verifyRate(); - void insertAllRate(); - void checkSupportedFormat(); - bool isSupportFormat(const TSoundTrackFormat &fmt); - void setFormat(const TSoundTrackFormat &fmt); -}; - -int TSoundOutputDeviceImp::m_count = 0; -std::multimap - TSoundOutputDeviceImp::m_supportFormats; -//---------------------------------------------------------------------------- - -bool TSoundOutputDeviceImp::doOpenDevice() { - if (m_dev >= 0) return true; - - TThread::ScopedLock sl(m_mutex); - m_dev = open("/dev/dsp", O_WRONLY, 0); - if (m_dev < 0) { - string errMsg = strerror(errno); - throw TSoundDeviceException( - TSoundDeviceException::UnableOpenDevice, errMsg + " /dev/dsp" - /*"Unable to open device /dev/dsp; check permissions"*/); - } - - // le chiamate a questa ioctl sono state commentate perche' pag 36 doc OSS - //"this ioctl stop the device immadiately and returns it to a state where - // it - // can accept new parameters. It Should not be called after opening the device - // as it may cause unwanted side effect in his situation. require to abort - // play - // or secord.Generally recommended to open and close device after using the - // RESET" - // ioctl(m_dev, SNDCTL_DSP_RESET,0); - - // N.B. e' bene che la dimensione sia piccola cosi' l'attesa, in seguito - // alla richiesta di stop, - // e' minore in questo caso vogliamo 32 frammenti ognuno di 256 byte - // Se non chiamata questa ioctl il device la calcola per conto suo - // ma alcune volte la dimensione dei frammenti potrebbe essere eccessiva e - // creare dei click e dei silenzi in attesi ad esempio nel playback dentro - // zcomp, quindi e' meglio settarla. 32 rappresenta il numero di frammenti - // di solito e' documentato che 2 siano sufficienti ma potrebbero essere pochi - // soprattutto se si interagisce con altre console - // int fraginfo = ( 32<<16)|8; - - int fraginfo = 0xffffffff; - if (ioctl(m_dev, SNDCTL_DSP_SETFRAGMENT, &fraginfo) == -1) - perror("SETFRAGMENT"); - // if(fraginfo != ((32<<16)|8)) - // std::cout << std::hex << fraginfo<::iterator it; - - for (it = m_supportedRate.begin(); it != m_supportedRate.end(); ++it) { - int sampleRate = *it; - if (ioctl(m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1) - throw TSoundDeviceException( - TSoundDeviceException::UnablePrepare, - "Failed setting the specified sample rate aaaa"); - if (sampleRate != *it) m_supportedRate.erase(*it); - } - if (m_supportedRate.end() == m_supportedRate.begin()) return false; - - return true; -} - -//------------------------------------------------------------------------------ - -void TSoundOutputDeviceImp::checkSupportedFormat() { - if (!m_supportFormats.empty()) return; - int test_formats[] = { - AFMT_U8, AFMT_S8, AFMT_S16_LE, AFMT_S16_BE, - /* // servono per supportare traccie a 24 e 32 bit ma non compaiono in -// nessun file linux/soundcard.h in distribuzione sulle macchine che abbiamo -// il che fa pensare che non sono supportati ancora -AFMT_S32_LE, -AFMT_S32_BE, -*/ - 0}; - - int test_channels[] = {1, 2, 0}; - - TUINT32 test_sample_rates[] = {8000, 11025, 16000, 22050, - 32000, 44100, 48000, 0}; - - // Open the device nonblocking, so the open call returns immediately. - if (m_dev) - if ((m_dev = open("/dev/dsp", O_WRONLY | O_NONBLOCK)) == -1) { - /* -m_dev = -1; -string errMsg = strerror(errno); -throw TSoundDeviceException( - TSoundDeviceException::UnableOpenDevice, errMsg + " /dev/dsp\n: -impossible check supported formats"); -*/ - return; - } - - int mask; - // Querying hardware supported formats. - if (ioctl(m_dev, SNDCTL_DSP_GETFMTS, &mask) == -1) { - /* -throw TSoundDeviceException( - TSoundDeviceException::UnsupportedFormat, - "Getting supported formats failed."); -*/ - return; - } else { - for (int i = 0; test_formats[i] != 0; i++) { - // Test all the formats in test_formats which are supported by the - // hardware. - - if (mask & test_formats[i]) { - // Test the format only if it is supported by the hardware. - // Note: We also could test formats that are not supported by the - // hardware. - // In some cases there exist OSS software converter, so that some - // formats - // work but are not reported by the above SNDCTL_DSP_GETFMTS. - - int fmt = test_formats[i]; - // Try to set the format... - if (ioctl(m_dev, SNDCTL_DSP_SETFMT, &fmt) == -1) - continue; // gli altri formati potrebbero essere supportati - else { - // and always check the variable after doing an ioctl! - if (fmt == test_formats[i]) { - // Test the supported channel numbers for this format. - // Note: If you need a channel that is not tested here, simply add - // it to - // the definition of the array test_channels in this file. - for (int j = 0; test_channels[j] != 0; j++) { - int test_channel = test_channels[j]; - - // Try to set the channel number. - if (ioctl(m_dev, SNDCTL_DSP_CHANNELS, &test_channel) == -1) - continue; // altri canali potrebbero essere supportati - else { - if (test_channel == test_channels[j]) { - // Last step: Test the supported sample rates for the current - // channel number - // and format. - // Note: If you need a sample rate that is not tested here, - // simply add it to - // the definition of the array test_sample_rates in this - // file. - for (int k = 0; test_sample_rates[k] != 0; k++) { - TUINT32 test_rate = test_sample_rates[k]; - if (ioctl(m_dev, SNDCTL_DSP_SPEED, &test_rate) == -1) - continue; // altri rates ppotrebbero essere supportati - else { - bool sign = true; - int bits; - - if (fmt == AFMT_U8 || fmt == AFMT_S8) { - bits = 8; - if (fmt == AFMT_U8) sign = false; - } else if (fmt == AFMT_S16_LE || fmt == AFMT_S16_BE) - bits = 16; - /*// vedi commento alla variabile test_formats -else if(fmt == AFMT_S32_LE || fmt == AFMT_S32_BE) -bits = 24; -*/ - // Add it to the format in the input property. - // std::cout << test_rate << " " <::iterator it; - pair::iterator, - std::multimap::iterator> - findRange; - findRange = m_supportFormats.equal_range(fmt.m_sampleRate); - - it = findRange.first; - for (; it != findRange.second; ++it) { - assert(it->first == fmt.m_sampleRate); - if (it->second == fmt) { - return true; - } - } - return false; -} - -//------------------------------------------------------------------------------ - -void TSoundOutputDeviceImp::setFormat(const TSoundTrackFormat &fmt) { - int bps, ch, status; - TUINT32 sampleRate; - - ch = fmt.m_channelCount; - sampleRate = fmt.m_sampleRate; - - if (m_dev == -1) - if (!doOpenDevice()) return; - - if (fmt.m_bitPerSample == 8) { - if (fmt.m_signedSample) - bps = AFMT_S8; - else - bps = AFMT_U8; - } else if (fmt.m_bitPerSample == 16) { - bps = AFMT_S16_NE; - } - int bitPerSample = bps; - - status = ioctl(m_dev, SNDCTL_DSP_SETFMT, &bps); - if (status == -1) { - throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, - "Failed setting the specified number of bits"); - } - - status = ioctl(m_dev, SNDCTL_DSP_CHANNELS, &ch); - if (status == -1) - throw TSoundDeviceException( - TSoundDeviceException::UnablePrepare, - "Failed setting the specified number of channel"); - - if (ioctl(m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1) - throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, - "Failed setting the specified sample rate"); - - if (ch != fmt.m_channelCount || bps != bitPerSample || - sampleRate != fmt.m_sampleRate) { - doCloseDevice(); - m_currentFormat = TSoundTrackFormat(); - return; - } - m_currentFormat = fmt; -} - -//============================================================================== - -class TPlayTask : public TThread::Runnable { - SmartWatch *m_stopWatch; - -public: - TSoundOutputDeviceImp *m_devImp; - TSoundTrackP m_sndtrack; - static int m_skipBytes; - - TPlayTask(TSoundOutputDeviceImp *devImp, const TSoundTrackP &st); - - ~TPlayTask() { delete m_stopWatch; } - - void run(); - void run2(); -}; - -//------------------------------------------------------------------------------- - -TPlayTask::TPlayTask(TSoundOutputDeviceImp *devImp, const TSoundTrackP &st) - : Runnable() - , m_stopWatch(new SmartWatch) - , m_devImp(devImp) - , m_sndtrack(st) { - if (st->getFormat() != m_devImp->m_currentFormat) - if (m_devImp->doCloseDevice()) m_devImp->setFormat(st->getFormat()); - m_stopWatch->start(); -}; - -//------------------------------------------------------------------------------- - -void TPlayTask::run() { - int bytesLeft = m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize(); - char *buf = (char *)m_sndtrack->getRawData(); - int done = 0; - int written = 0; - TINT32 sampleSize = (TINT32)m_sndtrack->getSampleSize(); - const double msToBytes = sampleSize * m_sndtrack->getSampleRate() / 1000.; - - TThread::milestone(); - double startupDelay = m_stopWatch->getTotalTime(); - // std::cout << "ritardo iniziale " << startupDelay << std::endl; - int miss = 0; - m_stopWatch->start(); // e' meglio ignorare il ritardo iniziale - if (done > 0) { - m_stopWatch->addDelay(((done / sampleSize) * 1000) / - double(m_sndtrack->getSampleRate())); - } - int auxbuffersize = 0; - int preWrittenBytes = 0; - int bytesToSkipNext; - TSoundTrackP src = TSoundTrack::create(m_sndtrack->getFormat(), 1); - TSoundTrackP dst = src; - TSoundTrackP newSrc = src; - try { - do // gia' tracce accodate - { - bool changeSnd = false; - do // c'e' il flag loop settato - { - while ((bytesLeft > 0)) { - TThread::milestone(); - changeSnd = false; - audio_buf_info info; - TINT32 bytesToWrite = 0; - TINT32 bytesToWriteNext = 0; - double samplesDone = done / (double)sampleSize; - double trackTime = - (samplesDone * 1000.) / m_sndtrack->getSampleRate(); - double curTime = m_stopWatch->getTotalTime(); - double delta = curTime - trackTime; - - /* - delta - == 0 sync - < 0 audio piu' veloce del tempo - di playback --> simuliamo un ritardo con un continue; - > 0 audio piu' lento del playback -> - skip una porzione di audio - */ - - const double minDelay = -10; - const double maxDelay = 0; - // if (delta>maxDelay) - // std::cout << "buffer underrun:" << delta << std::endl; - // std::cout << "buffer " << - // (deltamaxDelay?"underrun":"sync") << " " - // << delta<< std::endl; - - if (delta < minDelay) // overrun - { - // std::cout << "out of sync -> audio troppo veloce" << std::endl; - continue; - } - - if (ioctl(m_devImp->m_dev, SNDCTL_DSP_GETOSPACE, &info) == -1) { - miss++; - break; - } - - int fragmentsFree_bytes = info.fragsize * info.fragments; - if (fragmentsFree_bytes == 0) { - // std::cout << "no bytes left on device" << std::endl; - continue; - } - - int bytesToSkip = 0; - bytesToSkipNext = 0; - if (delta > maxDelay) // underrun - { - // std::cout << "out of sync -> audio troppo lento"< la corrente traccia non - // basta per avere il sync - bytesLeft -= bytesToSkip; - done += bytesToSkip; - - bytesToWrite = tmin(bytesLeft, fragmentsFree_bytes); - bytesToWriteNext = fragmentsFree_bytes - bytesToWrite; - assert(bytesToWrite >= 0); - assert(bytesToWriteNext >= 0); - - if (bytesToWrite % info.fragsize != - 0) { // cerco di gestire il write di un frammento non intero - auxbuffersize = - ((bytesToWrite / info.fragsize) + 1) * info.fragsize; - } else - auxbuffersize = 0; - - //--------------write - if (bytesToSkipNext == 0) // la corrente traccia basta per lo skip - { - std::cout << " QUI 0 " << std::endl; - dst = m_sndtrack->extract(done / sampleSize, - (done + bytesToWrite) / sampleSize); - if (bytesToSkip != 0) { - // costruisco traccia su cui fare il crossfade - // utilizzo il contenuto della traccia di crossfade - dst = TSop::crossFade(0.2, src, dst); - } - char *auxbuf = new char[fragmentsFree_bytes]; - memcpy(auxbuf, (char *)dst->getRawData(), bytesToWrite); - if (bytesToWriteNext != 0) { - int offset = bytesToWrite; - if (m_devImp->m_looped) { - offset += bytesToWriteNext; - preWrittenBytes = bytesToWriteNext; - memcpy(auxbuf + offset, buf, preWrittenBytes); - newSrc = m_sndtrack->extract(preWrittenBytes / sampleSize, - preWrittenBytes / sampleSize); - std::cout << " QUI 1" << std::endl; - } else { - while (!m_devImp->m_waitingTracks.empty()) { - TSoundTrackP st = m_devImp->m_waitingTracks[0].first; - int count = st->getSampleCount() * sampleSize; - if (bytesToWriteNext >= count) { - char *buffer = (char *)st->getRawData(); - memcpy(auxbuf + offset, buffer, count); - bytesToWriteNext -= count; - offset += count; - std::cout << " QUI 2" << std::endl; - if (m_devImp->m_waitingTracks[0].second) { - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - newSrc = m_sndtrack->extract(count / sampleSize, - count / sampleSize); - m_sndtrack = st; - preWrittenBytes = 0; - std::cout << " QUI 3" << std::endl; - break; - } - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - newSrc = m_sndtrack->extract(count / sampleSize, - count / sampleSize); - } else { - m_sndtrack = st; - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - preWrittenBytes = bytesToWriteNext; - buf = (char *)m_sndtrack->getRawData(); - memcpy(auxbuf + offset, buf, bytesToWriteNext); - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - newSrc = - m_sndtrack->extract((bytesToWriteNext) / sampleSize, - (bytesToWriteNext) / sampleSize); - std::cout << " QUI 4" << std::endl; - break; - } - } // end while - } - - if (fragmentsFree_bytes > offset) { - std::cout << " QUI 5" << std::endl; - int val = m_sndtrack->isSampleSigned() ? 0 : 127; - memset(auxbuf + offset, val, - fragmentsFree_bytes - offset); // ci metto silenzio - newSrc = TSoundTrack::create(m_sndtrack->getFormat(), 1); - } - } - - written = write(m_devImp->m_dev, auxbuf, fragmentsFree_bytes); - delete[] auxbuf; - } else // devo skippare anche parte di una delle seguenti - { - std::cout << " QUI 6a" << std::endl; - assert(bytesToWriteNext > 0); - assert(bytesToWriteNext == fragmentsFree_bytes - bytesToWrite); - assert(bytesToWrite == 0); - assert(bytesToSkip != 0); - char *auxbuf = new char[fragmentsFree_bytes]; - // memcpy(auxbuf, buf+done, bytesToWrite); - // TSoundTrackP subCross = - // m_sndtrack->extract((done+bytesToWrite)/sampleSize, - // (done+bytesToWrite)/sampleSize); - // togli quelle da skippare - int backupSkipNext = bytesToSkipNext; - while (!m_devImp->m_waitingTracks.empty()) { - TSoundTrackP st = m_devImp->m_waitingTracks[0].first; - int count = st->getSampleCount() * sampleSize; - if (bytesToSkipNext >= count) { - std::cout << " QUI 6b" << std::endl; - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - bytesToSkipNext -= count; - } else { - std::cout << " QUI 7" << std::endl; - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - m_sndtrack = st; - buf = (char *)st->getRawData(); - break; - } - } - // scrivi byteWriteNext fai crossfade e cerca quella successiva - // con cui riempire - TINT32 displacement = - 0; // deve essere in munero di campioni non bytes - dst = TSoundTrack::create( - m_sndtrack->getFormat(), - (fragmentsFree_bytes - bytesToWrite) / sampleSize); - int count = m_sndtrack->getSampleCount() * sampleSize; - if (count >= bytesToSkipNext + bytesToWriteNext) // la traccia - // trovata e' suff - // sia per - // skippare che - // per scrivere - { - preWrittenBytes = bytesToSkipNext + bytesToWriteNext; - dst = m_sndtrack->extract(bytesToSkipNext / sampleSize, - preWrittenBytes / sampleSize); - newSrc = m_sndtrack->extract(preWrittenBytes / sampleSize, - preWrittenBytes / sampleSize); - } else // non e' suff per scrivere - { - dst->copy(m_sndtrack->extract(bytesToSkipNext / sampleSize, - m_sndtrack->getSampleCount() - 1), - 0); - displacement = - m_sndtrack->getSampleCount() - bytesToSkipNext / sampleSize; - bytesToWriteNext -= displacement * sampleSize; - while (!m_devImp->m_waitingTracks.empty()) { - TSoundTrackP st = m_devImp->m_waitingTracks[0].first; - int count = st->getSampleCount() * sampleSize; - if (bytesToWriteNext >= count) { - std::cout << " QUI 8" << std::endl; - dst->copy(st, displacement); - bytesToWriteNext -= count; - displacement += count; - if (m_devImp->m_waitingTracks[0].second) { - std::cout << " QUI 9" << std::endl; - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - newSrc = m_sndtrack->extract(count / sampleSize, - count / sampleSize); - m_sndtrack = st; - break; - } - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - newSrc = m_sndtrack->extract(count / sampleSize, - count / sampleSize); - } else { - std::cout << " QUI 10" << std::endl; - dst->copy(st->extract(0L, bytesToWriteNext / sampleSize), - displacement); - m_sndtrack = st; - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - preWrittenBytes = bytesToWriteNext; - done = preWrittenBytes; - bytesLeft = m_sndtrack->getSampleCount() * sampleSize - done; - buf = (char *)m_sndtrack->getRawData(); - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - newSrc = m_sndtrack->extract(preWrittenBytes / sampleSize, - preWrittenBytes / sampleSize); - break; - } - } - bytesToSkipNext = backupSkipNext; - } - - TSoundTrackP st = TSop::crossFade(0.2, src, dst); - memcpy(auxbuf + bytesToWrite, (char *)st->getRawData(), - fragmentsFree_bytes - bytesToWrite); - - // devo ricercare quella giusta che non deve essere skippata - // ma sostitutita come traccia corrente - // devo fare un cross fade - written = write(m_devImp->m_dev, auxbuf, fragmentsFree_bytes); - delete[] auxbuf; - } - //----------- end write - src = newSrc; - if (written == -1) break; - std::cout << written << " " << (bytesToWrite + preWrittenBytes) - << std::endl; - if (written != bytesToWrite + preWrittenBytes) break; - std::cout << " update done 2" << std::endl; - bytesLeft -= written; - done += written; - } // chiudo il while((bytesLeft > 0)) - std::cout << " QUI 11" << std::endl; - done = preWrittenBytes + bytesToSkipNext; - written = 0; - bytesLeft = m_sndtrack->getSampleCount() * sampleSize - done; - m_stopWatch->start(); - if (done > 0) { - m_stopWatch->addDelay(((done / m_sndtrack->getSampleSize()) * 1000) / - double(m_sndtrack->getSampleRate())); - } - preWrittenBytes = 0; - } while (m_devImp->m_looped || changeSnd); - - if (m_devImp->m_waitingTracks.empty()) break; - m_sndtrack = m_devImp->m_waitingTracks[0].first; - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - m_devImp->m_waitingTracks.erase(m_devImp->m_waitingTracks.begin()); - bytesLeft = m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize(); - buf = (char *)m_sndtrack->getRawData(); - done = 0; - written = 0; - - m_stopWatch->start(); // ignoro il ritardo iniziale - if (done > 0) { - m_stopWatch->addDelay(((done / m_sndtrack->getSampleSize()) * 1000) / - double(m_sndtrack->getSampleRate())); - } - } while (true); // ci sono gia' tracce accodate - - if (!m_devImp->m_waitingTracks.empty()) { - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - m_devImp->m_executor.addTask( - new TPlayTask(m_devImp, m_devImp->m_waitingTracks[0].first)); - m_devImp->m_waitingTracks.erase(m_devImp->m_waitingTracks.begin()); - // std::cout<<"OPS ..... erase 4"<m_dev != -1) { - if (ioctl(m_devImp->m_dev, SNDCTL_DSP_SYNC) == -1) { - std::cout << "unable to sync! " << std::endl; - throw TException("unable to sync!"); - } - - m_devImp->m_isPlaying = false; - m_devImp->m_stopped = true; - m_devImp->m_looped = false; - - // std::cout << "miss = " << miss << std::endl; - } - } catch (TThread::Interrupt &e) { - std::cout << "Play interrupted " << e.getMessage() << std::endl; - m_devImp->m_isPlaying = false; - m_devImp->m_stopped = true; - m_devImp->m_looped = false; - } catch (TException &e) { - std::cout << "esco dal play " << e.getMessage() << std::endl; - m_devImp->m_isPlaying = false; - m_devImp->m_stopped = true; - m_devImp->m_looped = false; - } -} - -//------------------------------------------------------------------------------- - -void TPlayTask::run2() { - int bytesLeft = m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize(); - char *buf = (char *)m_sndtrack->getRawData(); - int done = 0; - int written = 0; - TINT32 sampleSize = (TINT32)m_sndtrack->getSampleSize(); - const double msToBytes = sampleSize * m_sndtrack->getSampleRate() / 1000.; - - TThread::milestone(); - double startupDelay = m_stopWatch->getTotalTime(); - // std::cout << "ritardo iniziale " << startupDelay << std::endl; - int miss = 0; - m_stopWatch->start(); // e' meglio ignorare il ritardo iniziale - if (done > 0) { - m_stopWatch->addDelay(((done / sampleSize) * 1000) / - double(m_sndtrack->getSampleRate())); - } - int auxbuffersize = 0; - int preWrittenBytes = 0; - TSoundTrackP src = TSoundTrack::create(m_sndtrack->getFormat(), 1); - TSoundTrackP dst = src; - try { - do // gia' tracce accodate - { - bool changeSnd = false; - do // c'e' il flag loop settato - { - while ((bytesLeft > 0)) { - changeSnd = false; - TThread::milestone(); - audio_buf_info info; - TINT32 bytesToWrite; - double samplesDone = done / (double)sampleSize; - double trackTime = - (samplesDone * 1000.) / m_sndtrack->getSampleRate(); - double curTime = m_stopWatch->getTotalTime(); - double delta = curTime - trackTime; - - /* - delta - == 0 sync - < 0 audio piu' veloce del tempo - di playback --> simuliamo un ritardo con un continue; - > 0 audio piu' lento del playback -> - skip una porzione di audio - */ - - const double minDelay = -10; - const double maxDelay = 0; - // if (delta>maxDelay) - // std::cout << "buffer underrun:" << delta << std::endl; - // std::cout << "buffer " << - // (deltamaxDelay?"underrun":"sync") << " " - // << delta<< std::endl; - - if (delta < minDelay) // overrun - { - // std::cout << "out of sync -> audio troppo veloce" << std::endl; - continue; - } - - if (ioctl(m_devImp->m_dev, SNDCTL_DSP_GETOSPACE, &info) == -1) { - miss++; - break; - } - - int fragmentsFree_bytes = info.fragsize * info.fragments; - if (fragmentsFree_bytes == 0) { - // std::cout << "no bytes left on device" << std::endl; - continue; - } - - int bytesToSkip = 0; - int bigSkip = 0; - if (delta > maxDelay) // underrun - { - // std::cout << "out of sync -> audio troppo lento"<m_waitingTracks.empty()) { - TSoundTrackP st = m_devImp->m_waitingTracks[0].first; - int count = st->getSampleCount() * sampleSize; - if (bigSkip >= count) { - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - bigSkip -= count; - } else - break; - } - - preWrittenBytes = 0; - if (auxbuffersize == 0) { - if (bytesToSkip != 0) { - // costruisco traccia su cui fare il crossfade - // utilizzo il contenuto della traccia di crossfade - dst = m_sndtrack->extract(done / sampleSize, - (done + bytesToWrite) / sampleSize); - TSoundTrackP st = TSop::crossFade(0.2, src, dst); - char *buffer = (char *)st->getRawData(); - written = write(m_devImp->m_dev, buffer, bytesToWrite); - } else - written = write(m_devImp->m_dev, buf + done, bytesToWrite); - src = m_sndtrack->extract((done + bytesToWrite) / sampleSize, - (done + bytesToWrite) / sampleSize); - } else { // auxbuffersize != 0 sse il numero di bytes residui nella - // traccia e' inferiore alla dimensione del frammento - char *auxbuf = new char[auxbuffersize]; - TSoundTrackP newSrc; - dst = TSoundTrack::create(m_sndtrack->getFormat(), - auxbuffersize / sampleSize); - memcpy(auxbuf, buf + done, bytesToWrite); - dst->copy(m_sndtrack->extract(done / sampleSize, - (done + bytesToWrite) / sampleSize), - 0); - preWrittenBytes = auxbuffersize - bytesToWrite; - if (m_devImp->m_looped) { - memcpy(auxbuf + bytesToWrite, buf, preWrittenBytes); - dst->copy(m_sndtrack->extract(0, preWrittenBytes / sampleSize), - bytesToWrite / sampleSize); - newSrc = m_sndtrack->extract(preWrittenBytes / sampleSize, - preWrittenBytes / sampleSize); - } else { - newSrc = TSoundTrack::create(m_sndtrack->getFormat(), 1); - static int added = 0; - // se non c'e' alcuna altra traccia o e di diverso format - // riempo il frammento con del silenzio - if (m_devImp->m_waitingTracks.empty() || - (m_sndtrack->getFormat() != - m_devImp->m_waitingTracks[0].first->getFormat())) { - int val = m_sndtrack->isSampleSigned() ? 0 : 127; - memset(auxbuf + bytesToWrite, val, - preWrittenBytes); // ci metto silenzio - } else - while (true) // ci sono altre tracce accodate - { - TSoundTrackP st = m_devImp->m_waitingTracks[0].first; - int sampleBytes = st->getSampleCount() * st->getSampleSize(); - if (sampleBytes >= preWrittenBytes - added) { - // La traccia ha abbastanza campioni per riempire il - // frammento - // quindi la sostituisco alla corrente del runnable e - // continuo - buf = (char *)st->getRawData(); - memcpy(auxbuf + bytesToWrite, buf, preWrittenBytes - added); - m_sndtrack = st; - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - changeSnd = true; - dst->copy(m_sndtrack->extract( - 0, (preWrittenBytes - added) / sampleSize), - bytesToWrite / sampleSize + added); - newSrc = m_sndtrack->extract( - (preWrittenBytes - added) / sampleSize, - (preWrittenBytes - added) / sampleSize); - break; - } else { // occhio al loop - // La traccia successiva e' piu corta del frammento da - // riempire quindi - // ce la metto tutta e se non ha il flag di loop settato - // cerco di aggiungere - // i byte della successiva - memcpy(auxbuf + bytesToWrite, st->getRawData(), - sampleBytes); - dst->copy(st->extract(0, st->getSampleCount() - 1), - bytesToWrite / sampleSize); - added += st->getSampleCount(); - if (m_devImp->m_waitingTracks[0] - .second) // e' quella che deve essere in loop - { - buf = (char *)st->getRawData(); - m_sndtrack = st; - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - preWrittenBytes = 0; - bytesLeft = 0; - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - changeSnd = true; - break; - } - - // la elimino e vedo se esiste la successiva altrimenti - // metto campioni "zero" - m_devImp->m_waitingTracks.erase( - m_devImp->m_waitingTracks.begin()); - if (!m_devImp->m_waitingTracks.empty()) { - st = m_devImp->m_waitingTracks[0].first; - std::cout - << " Traccia con meno campioni cerco la successiva" - << std::endl; - } else { - int val = m_sndtrack->isSampleSigned() ? 0 : 127; - memset( - auxbuf + bytesToWrite, val, - preWrittenBytes - sampleBytes); // ci metto silenzio - std::cout << "OPS ..... silence" << std::endl; - break; - } - } - } // end while(true) - } - // qui andrebbe fatto un cross-fade se c'erano da skippare campioni - // => bytesToSkip != 0 - if (bytesToSkip != 0) { - TSoundTrackP st = TSop::crossFade(0.2, src, dst); - char *buffer = (char *)st->getRawData(); - written = write(m_devImp->m_dev, buffer, bytesToWrite); - } else - written = write(m_devImp->m_dev, auxbuf, auxbuffersize); - src = newSrc; - auxbuffersize = 0; - delete[] auxbuf; - } - if (written == -1) break; - if (written != bytesToWrite + preWrittenBytes) break; - bytesLeft -= written; - done += written; - } // chiudo il while((bytesLeft > 0)) - done = preWrittenBytes; - written = 0; - bytesLeft = - m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize() - done; - m_stopWatch->start(); - if (done > 0) { - m_stopWatch->addDelay(((done / m_sndtrack->getSampleSize()) * 1000) / - double(m_sndtrack->getSampleRate())); - } - } while (m_devImp->m_looped || changeSnd); - - if (m_devImp->m_waitingTracks.empty()) { - // std::cout<<"OPS ..... non accodato"<m_waitingTracks[0].first; - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - m_devImp->m_waitingTracks.erase(m_devImp->m_waitingTracks.begin()); - bytesLeft = m_sndtrack->getSampleCount() * m_sndtrack->getSampleSize(); - buf = (char *)m_sndtrack->getRawData(); - done = 0; - written = 0; - - m_stopWatch->start(); // ignoro il ritardo iniziale - if (done > 0) { - m_stopWatch->addDelay(((done / m_sndtrack->getSampleSize()) * 1000) / - double(m_sndtrack->getSampleRate())); - } - } while (true); // ci sono gia' tracce accodate - - if (!m_devImp->m_waitingTracks.empty()) { - m_devImp->m_looped = m_devImp->m_waitingTracks[0].second; - m_devImp->m_executor.addTask( - new TPlayTask(m_devImp, m_devImp->m_waitingTracks[0].first)); - m_devImp->m_waitingTracks.erase(m_devImp->m_waitingTracks.begin()); - // std::cout<<"OPS ..... erase 4"<m_dev != -1) { - if (ioctl(m_devImp->m_dev, SNDCTL_DSP_SYNC) == -1) { - std::cout << "unable to sync! " << std::endl; - throw TException("unable to sync!"); - } - - m_devImp->m_isPlaying = false; - m_devImp->m_stopped = true; - m_devImp->m_looped = false; - - // std::cout << "miss = " << miss << std::endl; - } - } catch (TThread::Interrupt &e) { - std::cout << "Play interrupted " << e.getMessage() << std::endl; - m_devImp->m_isPlaying = false; - m_devImp->m_stopped = true; - m_devImp->m_looped = false; - } catch (TException &e) { - std::cout << "esco dal play " << e.getMessage() << std::endl; - m_devImp->m_isPlaying = false; - m_devImp->m_stopped = true; - m_devImp->m_looped = false; - } -} - -//============================================================================== - -TSoundOutputDevice::TSoundOutputDevice() : m_imp(new TSoundOutputDeviceImp) { - if (m_imp->doOpenDevice()) { - m_imp->insertAllRate(); - try { - if (!m_imp->verifyRate()) - throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, - "No default samplerate are supported"); - } catch (TSoundDeviceException &e) { - throw TSoundDeviceException(e.getType(), e.getMessage()); - } - m_imp->doCloseDevice(); - } -} - -//------------------------------------------------------------------------------ - -TSoundOutputDevice::~TSoundOutputDevice() { close(); } - -//------------------------------------------------------------------------------ - -bool TSoundOutputDevice::installed() { - bool ret = false; - int dev = ::open("/dev/dsp", O_WRONLY, 0); - if (dev >= 0) { - ret = true; - ::close(dev); - } - return ret; -} - -//------------------------------------------------------------------------------ - -bool TSoundOutputDevice::open(const TSoundTrackP &st) { - m_imp->m_currentFormat = st->getFormat(); - try { - m_imp->doOpenDevice(); - } catch (TSoundDeviceException &e) { - throw TSoundDeviceException(e.getType(), e.getMessage()); - } - return true; -} - -//------------------------------------------------------------------------------ - -bool TSoundOutputDevice::close() { - stop(); - if (m_imp->m_dev != -1) { - bool closed = m_imp->doCloseDevice(); - if (!closed) - throw TSoundDeviceException( - TSoundDeviceException::UnableCloseDevice, - "Error during the closing of the output device"); - } - return true; -} - -//------------------------------------------------------------------------------ - -void TSoundOutputDevice::attach(TSoundOutputDeviceListener *listener) { - m_imp->m_listeners.insert(listener); -} - -//------------------------------------------------------------------------------ - -void TSoundOutputDevice::detach(TSoundOutputDeviceListener *listener) { - m_imp->m_listeners.erase(listener); -} - -//------------------------------------------------------------------------------ - -void TSoundOutputDevice::play(const TSoundTrackP &st, TINT32 s0, TINT32 s1, - bool loop, bool scrubbing) { - assert((scrubbing && !loop) || !scrubbing); - if (!st->getSampleCount()) return; - - TThread::ScopedLock sl(m_imp->m_mutex); - if (m_imp->m_looped) - throw TSoundDeviceException( - TSoundDeviceException::Busy, - "Unable to queue another playback when the sound player is looping"); - - TSoundTrackFormat format = st->getFormat(); - if (!m_imp->isSupportFormat(format)) { - throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, - "Unsupported format for playback"); - } - - if (m_imp->m_isPlaying) { - assert(s1 >= s0); - TSoundTrackP subTrack = st->extract(s0, s1); - m_imp->m_waitingTracks.push_back(std::make_pair(subTrack, loop)); - // std::cout<<"Sono in pushback"<m_dev == -1)) try { - if (m_imp->doOpenDevice()) m_imp->setFormat(format); - } catch (TSoundDeviceException &e) { - m_imp->doCloseDevice(); - throw TSoundDeviceException(e.getType(), e.getMessage()); - } - - m_imp->m_isPlaying = true; - m_imp->m_stopped = false; - m_imp->m_looped = loop; - // m_imp->m_currentFormat = st->getFormat(); - - assert(s1 >= s0); - TSoundTrackP subTrack = st->extract(s0, s1); - - m_imp->m_executor.addTask(new TPlayTask(m_imp, subTrack)); -} -//------------------------------------------------------------------------------ - -void TSoundOutputDevice::stop() { - TThread::ScopedLock sl(m_imp->m_mutex); - if (!m_imp->m_isPlaying) return; - m_imp->m_executor.cancel(); - ioctl(m_imp->m_dev, SNDCTL_DSP_POST, 0); - m_imp->m_isPlaying = false; - m_imp->m_stopped = true; - m_imp->m_looped = false; - m_imp->m_waitingTracks.clear(); -} - -//------------------------------------------------------------------------------ -double TSoundOutputDevice::getVolume() { - int mixer; - if ((mixer = openMixer()) < 0) - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't open the mixer device"); - - int devmask; - if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Error in ioctl with mixer device"); - } - - int recmask; - if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Error in ioctl with mixer device"); - } - - int stereo; - if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Error in ioctl with mixer device"); - } - - int outmask = devmask | ~recmask; - - int index; - if (outmask & (1 << SOUND_MIXER_ALTPCM)) - index = SOUND_MIXER_ALTPCM; - else if (outmask & (1 << SOUND_MIXER_PCM)) - index = SOUND_MIXER_PCM; - - int level; - if (ioctl(mixer, MIXER_READ(index), &level) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::UnableVolume, - "Error to read the volume"); - } - if ((1 << index) & stereo) { - int left = level & 0xff; - int right = ((level & 0xff00) >> 8); - ::close(mixer); - return (left + right) / 20.0; - } else { - ::close(mixer); - return (level & 0xff) / 10.0; - } -} - -//------------------------------------------------------------------------------ - -bool TSoundOutputDevice::setVolume(double volume) { - int mixer; - if ((mixer = openMixer()) < 0) - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't open the mixer device"); - - int devmask; - if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Error in ioctl with mixer device"); - } - - int recmask; - if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &recmask) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Error in ioctl with mixer device"); - } - - int stereo; - if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Error in ioctl with mixer device"); - } - - int outmask = devmask | ~recmask; - - if (outmask & (1 << SOUND_MIXER_ALTPCM)) { - int vol, index = SOUND_MIXER_ALTPCM; - if ((1 << index) & stereo) { - volume *= 10.0; - vol = (int)volume + ((int)(volume * 256.0)); - } else - vol = (int)(volume * 10.0); - - if (!writeVolume(vol, mixer, index)) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::UnableVolume, - "Can't write the volume"); - } - - // metto anche l'altro ad un livello di sensibilita' adeguata - if (outmask & (1 << SOUND_MIXER_PCM)) { - int vol, index = SOUND_MIXER_PCM; - double volDefault = 6.7; - if ((1 << index) & stereo) { - volDefault *= 10.0; - vol = (int)volDefault + ((int)(volDefault * 256.0)); - } else - vol = (int)(volDefault * 10.0); - - if (!writeVolume(vol, mixer, index)) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::UnableVolume, - "Can't write the volume"); - } - } - } else if (outmask & (1 << SOUND_MIXER_PCM)) { - int vol, index = SOUND_MIXER_PCM; - if ((1 << index) & stereo) { - volume *= 10.0; - vol = (int)volume + ((int)(volume * 256.0)); - } else - vol = (int)(volume * 10.0); - - if (!writeVolume(vol, mixer, index)) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't write the volume"); - } - } - - ::close(mixer); - return true; -} - -//------------------------------------------------------------------------------ - -bool TSoundOutputDevice::isPlaying() const { - TThread::ScopedLock sl(m_imp->m_mutex); - return m_imp->m_isPlaying; -} - -//------------------------------------------------------------------------------ - -bool TSoundOutputDevice::isLooping() { - TThread::ScopedLock sl(m_imp->m_mutex); - return m_imp->m_looped; -} - -//------------------------------------------------------------------------------ - -void TSoundOutputDevice::setLooping(bool loop) { - TThread::ScopedLock sl(m_imp->m_mutex); - m_imp->m_looped = loop; -} - -//------------------------------------------------------------------------------ - -bool TSoundOutputDevice::supportsVolume() { - int mixer; - if ((mixer = openMixer()) < 0) - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't open the mixer device"); - - int devmask; - if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Error in ioctl with mixer device"); - } - - int recmask; - if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &recmask) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Error in ioctl with mixer device"); - } - - int stereo; - if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Error in ioctl with mixer device"); - } - - int outmask = devmask | ~recmask; - - if ((outmask & (1 << SOUND_MIXER_ALTPCM)) || - (outmask & (1 << SOUND_MIXER_PCM))) { - ::close(mixer); - return true; - } - - return false; -} - -//------------------------------------------------------------------------------ - -TSoundTrackFormat TSoundOutputDevice::getPreferredFormat(TUINT32 sampleRate, - int channelCount, - int bitPerSample) { - TSoundTrackFormat fmt; - if (bitPerSample == 8) - fmt = TSoundTrackFormat(sampleRate, channelCount, bitPerSample, false); - else - fmt = TSoundTrackFormat(sampleRate, channelCount, bitPerSample); - if (m_imp->isSupportFormat(fmt)) return fmt; - - int bps, ch, status; - - bps = bitPerSample; - ch = channelCount; - - if (m_imp->m_dev == -1) m_imp->doOpenDevice(); - - if (bitPerSample <= 8) { - bitPerSample = AFMT_U8; - fmt.m_signedSample = false; - } else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16) { - bitPerSample = AFMT_S16_NE; - fmt.m_signedSample = true; - } - - status = ioctl(m_imp->m_dev, SNDCTL_DSP_SETFMT, &bitPerSample); - if (status == -1) { - perror("CHE palle "); - throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, - "Failed setting the specified number of bits"); - } - fmt.m_bitPerSample = bitPerSample; - - status = ioctl(m_imp->m_dev, SNDCTL_DSP_CHANNELS, &channelCount); - if (status == -1) - throw TSoundDeviceException( - TSoundDeviceException::UnablePrepare, - "Failed setting the specified number of channel"); - fmt.m_channelCount = channelCount; - - if (m_imp->m_supportedRate.find((int)sampleRate) == - m_imp->m_supportedRate.end()) { - std::set::iterator it = - m_imp->m_supportedRate.lower_bound((int)sampleRate); - if (it == m_imp->m_supportedRate.end()) { - it = std::max_element(m_imp->m_supportedRate.begin(), - m_imp->m_supportedRate.end()); - if (it != m_imp->m_supportedRate.end()) - sampleRate = *(m_imp->m_supportedRate.rbegin()); - else - throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, - "There isn't a supported rate"); - } else - sampleRate = *it; - } - - if (ioctl(m_imp->m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1) - throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, - "Failed setting the specified sample rate"); - fmt.m_sampleRate = sampleRate; - - if (ch != channelCount || bps != bitPerSample) { - m_imp->doCloseDevice(); - } - - return fmt; -} - -//------------------------------------------------------------------------------ -TSoundTrackFormat TSoundOutputDevice::getPreferredFormat( - const TSoundTrackFormat &format) { - try { - return getPreferredFormat(format.m_sampleRate, format.m_channelCount, - format.m_bitPerSample); - } catch (TSoundDeviceException &e) { - throw TSoundDeviceException(e.getType(), e.getMessage()); - } -} - -//====================================================================== -//====================================================================== -// CLASSI PER IL RECORD -//====================================================================== -//====================================================================== - -class TSoundInputDeviceImp { -public: - int m_dev; - bool m_stopped; - bool m_isRecording; - TSoundTrackFormat m_currentFormat; - TSoundTrackP m_st; - std::set m_supportedRate; - - TINT32 m_recordedSampleCount; - vector m_recordedBlocks; - vector m_samplePerBlocks; - bool m_oneShotRecording; - - TThread::Executor m_executor; - - TSoundInputDeviceImp() - : m_dev(-1) - , m_stopped(false) - , m_isRecording(false) - , m_st(0) - , m_supportedRate() - , m_recordedBlocks() - , m_samplePerBlocks() - , m_oneShotRecording(false) {} - - ~TSoundInputDeviceImp() {} - - bool doOpenDevice(const TSoundTrackFormat &format, - TSoundInputDevice::Source devType); - bool doCloseDevice(); - void insertAllRate(); - bool verifyRate(); -}; - -//------------------------------------------------------------------------------ - -bool TSoundInputDeviceImp::doOpenDevice(const TSoundTrackFormat &format, - TSoundInputDevice::Source devType) { - m_dev = open("/dev/dsp", O_RDONLY); - if (m_dev < 0) - throw TSoundDeviceException(TSoundDeviceException::UnableOpenDevice, - "Cannot open the dsp device"); - - // ioctl(m_dev, SNDCTL_DSP_RESET,0); - - return true; -} - -//------------------------------------------------------------------------------ - -bool TSoundInputDeviceImp::doCloseDevice() { - if (close(m_dev) < 0) - throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice, - "Cannot close the dsp device"); - m_dev = -1; - return true; -} - -//---------------------------------------------------------------------------- - -void TSoundInputDeviceImp::insertAllRate() { - m_supportedRate.insert(8000); - m_supportedRate.insert(11025); - m_supportedRate.insert(16000); - m_supportedRate.insert(22050); - m_supportedRate.insert(32000); - m_supportedRate.insert(44100); - m_supportedRate.insert(48000); -} - -//---------------------------------------------------------------------------- - -bool TSoundInputDeviceImp::verifyRate() { - std::set::iterator it; - - for (it = m_supportedRate.begin(); it != m_supportedRate.end(); ++it) { - int sampleRate = *it; - if (ioctl(m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1) - throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, - "Failed setting the specified sample rate"); - if (sampleRate != *it) m_supportedRate.erase(*it); - } - if (m_supportedRate.end() == m_supportedRate.begin()) return false; - - return true; -} -//============================================================================== - -class TRecordTask : public TThread::Runnable { -public: - TSoundInputDeviceImp *m_devImp; - - TRecordTask(TSoundInputDeviceImp *devImp) : Runnable(), m_devImp(devImp){}; - - ~TRecordTask(){}; - - void run(); -}; - -//------------------------------------------------------------------------------ - -void TRecordTask::run() { - // N.B. e' bene che la dimensione sia piccola cosi' l'attesa perche' termini - // e' minore in questo caso vogliamo 16 frammenti ognuno di 4096 byte - int fraginfo = (16 << 16) | 12; - if (ioctl(m_devImp->m_dev, SNDCTL_DSP_SETFRAGMENT, &fraginfo) == -1) - perror("SETFRAGMENT"); - - int fragsize = 0; - if (ioctl(m_devImp->m_dev, SNDCTL_DSP_GETBLKSIZE, &fragsize) == -1) - perror("GETFRAGMENT"); - TINT32 byteRecordedSample = 0; - - if (m_devImp->m_oneShotRecording) { - TINT32 byteToSample = - m_devImp->m_st->getSampleSize() * m_devImp->m_st->getSampleCount(); - char *buf = (char *)m_devImp->m_st->getRawData(); - - while ((byteRecordedSample < byteToSample) && m_devImp->m_isRecording) { - int sample; - if (fragsize > (byteToSample - byteRecordedSample)) - sample = byteToSample - byteRecordedSample; - else - sample = fragsize; - int nread = read(m_devImp->m_dev, buf + byteRecordedSample, sample); - if (nread == -1) break; - if (nread != sample) break; - - byteRecordedSample += nread; - } - } else { - int bytePerSample = m_devImp->m_currentFormat.m_bitPerSample >> 3; - switch (bytePerSample) { - case 3: - bytePerSample++; - break; - default: - break; - } - bytePerSample *= m_devImp->m_currentFormat.m_channelCount; - - while (m_devImp->m_isRecording) { - char *dataBuffer = new char[fragsize]; - m_devImp->m_recordedBlocks.push_back(dataBuffer); - m_devImp->m_samplePerBlocks.push_back(fragsize); - - int nread = read(m_devImp->m_dev, dataBuffer, fragsize); - if (nread == -1) break; - if (nread != fragsize) break; - - m_devImp->m_recordedSampleCount += (fragsize / bytePerSample); - } - } - ioctl(m_devImp->m_dev, SNDCTL_DSP_RESET, 0); -} - -//============================================================================== - -TSoundInputDevice::TSoundInputDevice() : m_imp(new TSoundInputDeviceImp) { - m_imp->m_dev = open("/dev/dsp", O_RDONLY); - if (m_imp->m_dev < 0) - throw TSoundDeviceException(TSoundDeviceException::UnableOpenDevice, - "Cannot open the dsp device"); - - m_imp->insertAllRate(); - try { - if (!m_imp->verifyRate()) - throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, - "No default samplerate are supported"); - } catch (TSoundDeviceException &e) { - throw TSoundDeviceException(e.getType(), e.getMessage()); - } - m_imp->doCloseDevice(); -} - -//------------------------------------------------------------------------------ - -TSoundInputDevice::~TSoundInputDevice() { - if (m_imp->m_dev != -1) m_imp->doCloseDevice(); - delete m_imp; -} - -//------------------------------------------------------------------------------ - -bool TSoundInputDevice::installed() { - bool ret = false; - int dev = ::open("/dev/dsp", O_RDONLY); - if (dev >= 0) { - ret = true; - ::close(dev); - } - return ret; -} - -//------------------------------------------------------------------------------ - -void TSoundInputDevice::record(const TSoundTrackFormat &format, - TSoundInputDevice::Source type) { - m_imp->m_recordedBlocks.clear(); - m_imp->m_samplePerBlocks.clear(); - - // registra creando una nuova traccia - m_imp->m_oneShotRecording = false; - try { - if (m_imp->m_dev == -1) m_imp->doOpenDevice(format, type); - - if (!selectInputDevice(type)) - throw TSoundDeviceException( - TSoundDeviceException::UnableSetDevice, - "Input device is not supported for recording"); - - TSoundTrackFormat fmt = getPreferredFormat(format); - if (fmt != format) - throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, - "Unsupported format"); - } catch (TSoundDeviceException &e) { - throw TSoundDeviceException(e.getType(), e.getMessage()); - } - - try { - if (getVolume() == 0.0) { - double volume = 5.0; - setVolume(volume); - } - } catch (TSoundDeviceException &e) { - throw TSoundDeviceException(e.getType(), e.getMessage()); - } - - m_imp->m_currentFormat = format; - m_imp->m_isRecording = true; - m_imp->m_stopped = false; - m_imp->m_recordedSampleCount = 0; - - // far partire il thread - /*TRecordThread *recordThread = new TRecordThread(m_imp); -if (!recordThread) -throw TSoundDeviceException( -TSoundDeviceException::UnablePrepare, -"Problemi per creare il thread"); -recordThread->start();*/ - m_imp->m_executor.addTask(new TRecordTask(m_imp)); -} - -//------------------------------------------------------------------------------ - -void TSoundInputDevice::record(const TSoundTrackP &st, - TSoundInputDevice::Source type) { - m_imp->m_recordedBlocks.clear(); - m_imp->m_samplePerBlocks.clear(); - - try { - if (m_imp->m_dev == -1) m_imp->doOpenDevice(st->getFormat(), type); - - if (!selectInputDevice(type)) - throw TSoundDeviceException( - TSoundDeviceException::UnableSetDevice, - "Input device is not supported for recording"); - - TSoundTrackFormat fmt = getPreferredFormat(st->getFormat()); - if (fmt != st->getFormat()) - throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, - "Unsupported format"); - - if (getVolume() == 0.0) { - double volume = 5.0; - setVolume(volume); - } - } catch (TSoundDeviceException &e) { - throw TSoundDeviceException(e.getType(), e.getMessage()); - } - - // Sovrascive un'intera o parte di traccia gia' esistente - m_imp->m_oneShotRecording = true; - m_imp->m_currentFormat = st->getFormat(); - m_imp->m_isRecording = true; - m_imp->m_stopped = false; - m_imp->m_recordedSampleCount = 0; - m_imp->m_st = st; - - m_imp->m_recordedBlocks.push_back((char *)st->getRawData()); - - // far partire il thread - /*TRecordThread *recordThread = new TRecordThread(m_imp); -if (!recordThread) -throw TSoundDeviceException( -TSoundDeviceException::UnablePrepare, -"Problemi per creare il thread"); -recordThread->start();*/ - m_imp->m_executor.addTask(new TRecordTask(m_imp)); -} - -//------------------------------------------------------------------------------ - -TSoundTrackP TSoundInputDevice::stop() { - TSoundTrackP st; - - if (!m_imp->m_isRecording) return st; - - m_imp->m_isRecording = false; - - // mettere istruzioni per fermare il rec - ioctl(m_imp->m_dev, SNDCTL_DSP_SYNC, 0); - try { - m_imp->doCloseDevice(); - } catch (TSoundDeviceException &e) { - throw TSoundDeviceException(e.getType(), e.getMessage()); - } - - // attendo 1/5 di secondo - usleep(200000); - if (m_imp->m_oneShotRecording) - st = m_imp->m_st; - else { - st = TSoundTrack::create(m_imp->m_currentFormat, - m_imp->m_recordedSampleCount); - TINT32 bytesCopied = 0; - - for (int i = 0; i < (int)m_imp->m_recordedBlocks.size(); ++i) { - memcpy((void *)(st->getRawData() + bytesCopied), - m_imp->m_recordedBlocks[i], m_imp->m_samplePerBlocks[i]); - delete[] m_imp->m_recordedBlocks[i]; - - bytesCopied += m_imp->m_samplePerBlocks[i]; - } - m_imp->m_samplePerBlocks.clear(); - } - - return st; -} - -//------------------------------------------------------------------------------ - -double TSoundInputDevice::getVolume() { - int mixer; - if ((mixer = openMixer()) < 0) - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't have the mixer"); - - int index; - if ((index = getCurrentRecordSource(mixer)) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't obtain information by mixer"); - } - - int stereo; - if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't obtain information by mixer"); - } - - int level; - if (ioctl(mixer, MIXER_READ(index), &level) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::UnableVolume, - "Can't read the volume value"); - } - - if ((1 << index) & stereo) { - int left = level & 0xff; - int right = ((level & 0xff00) >> 8); - ::close(mixer); - return (left + right) / 20.0; - } else { - ::close(mixer); - return (level & 0xff) / 10.0; - } -} - -//------------------------------------------------------------------------------ - -bool TSoundInputDevice::setVolume(double volume) { - int mixer; - if ((mixer = openMixer()) < 0) - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't have the mixer"); - - int caps; - if (ioctl(mixer, SOUND_MIXER_READ_CAPS, &caps) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't obtain information by mixer"); - } - - if (!(caps & SOUND_CAP_EXCL_INPUT)) { - int rec; - if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &rec) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't obtain information by mixer"); - } - int i; - int nosound = 0; - for (i = 0; i < 32; ++i) - if (rec & (1 << i)) - if (!writeVolume(nosound, mixer, i)) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::UnableVolume, - "Can't set the volume value"); - } - } - - int index; - if ((index = getCurrentRecordSource(mixer)) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't obtain information by mixer"); - } - - int stereo; - if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::NoMixer, - "Can't obtain information by mixer"); - } - - int vol; - if ((1 << index) & stereo) { - volume *= 10.0; - vol = (int)volume + ((int)(volume * 256.0)); - } else - vol = (int)(volume * 10.0); - - if (!writeVolume(vol, mixer, index)) { - ::close(mixer); - throw TSoundDeviceException(TSoundDeviceException::UnableVolume, - "Can't write the volume value"); - } - ::close(mixer); - return true; -} - -//------------------------------------------------------------------------------ - -bool TSoundInputDevice::isRecording() { return m_imp->m_isRecording; } - -//------------------------------------------------------------------------------ - -TSoundTrackFormat TSoundInputDevice::getPreferredFormat(TUINT32 sampleRate, - int channelCount, - int bitPerSample) { - TSoundTrackFormat fmt; - int status; - - if (bitPerSample <= 8) { - bitPerSample = AFMT_U8; - fmt.m_signedSample = false; - } else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16) { - bitPerSample = AFMT_S16_NE; - fmt.m_signedSample = true; - } - - status = ioctl(m_imp->m_dev, SNDCTL_DSP_SETFMT, &bitPerSample); - if (status == -1) - throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, - "Failed setting the specified number of bits"); - fmt.m_bitPerSample = bitPerSample; - - status = ioctl(m_imp->m_dev, SNDCTL_DSP_CHANNELS, &channelCount); - if (status == -1) - throw TSoundDeviceException( - TSoundDeviceException::UnablePrepare, - "Failed setting the specified number of channel"); - fmt.m_channelCount = channelCount; - - if (m_imp->m_supportedRate.find((int)sampleRate) == - m_imp->m_supportedRate.end()) { - std::set::iterator it = - m_imp->m_supportedRate.lower_bound((int)sampleRate); - if (it == m_imp->m_supportedRate.end()) { - it = std::max_element(m_imp->m_supportedRate.begin(), - m_imp->m_supportedRate.end()); - if (it != m_imp->m_supportedRate.end()) - sampleRate = *(m_imp->m_supportedRate.rbegin()); - else - throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat, - "There isn't a supported rate"); - } else - sampleRate = *it; - } - - if (ioctl(m_imp->m_dev, SNDCTL_DSP_SPEED, &sampleRate) == -1) - throw TSoundDeviceException(TSoundDeviceException::UnablePrepare, - "Failed setting the specified sample rate"); - fmt.m_sampleRate = sampleRate; - - return fmt; -} - -//------------------------------------------------------------------------------ - -TSoundTrackFormat TSoundInputDevice::getPreferredFormat( - const TSoundTrackFormat &format) { - try { - return getPreferredFormat(format.m_sampleRate, format.m_channelCount, - format.m_bitPerSample); - } catch (TSoundDeviceException &e) { - throw TSoundDeviceException(e.getType(), e.getMessage()); - } -} - -//****************************************************************************** -//****************************************************************************** -// funzioni per l'interazione con -// la -// libreria -// OSS -//****************************************************************************** -//****************************************************************************** -namespace { -string parseError(int error) { - switch (error) { - case EBADF: - return string("Bad file descriptor"); - case EFAULT: - return string("Pointer to/from buffer data is invalid"); - case EINTR: - return string("Signal interrupt the signal"); - case EINVAL: - return string("Request/arg isn't valid for this device"); - case EIO: - return string("Some phisical I/O error has occurred"); - case ENOTTY: - return string( - "Fieldes isn't associated with a device that accepts control"); - case ENXIO: - return string( - "Request/arg valid, but the requested cannot be performed on this " - "subdevice"); - default: - return string("Unknown error"); - break; - } -} - -//------------------------------------------------------------------------------ - -TSoundInputDevice::Source stringToSource(string dev) { - if (dev == "mic") - return TSoundInputDevice::Mic; - else if (dev == "line") - return TSoundInputDevice::LineIn; - else if (dev == "cd") - return TSoundInputDevice::CdAudio; - else - return TSoundInputDevice::DigitalIn; -} - -//------------------------------------------------------------------------------ - -string sourceToString(TSoundInputDevice::Source dev) { - switch (dev) { - case TSoundInputDevice::Mic: - return string("mic"); - case TSoundInputDevice::LineIn: - return string("line"); - case TSoundInputDevice::DigitalIn: - return string("digital"); - default: - return string("cd"); - } -} - -//------------------------------------------------------------------------------ - -int openMixer() { - int mixer = open("/dev/mixer", O_RDWR); - if (mixer == -1) return false; - return mixer; -} -//------------------------------------------------------------------------------ - -int getCurrentRecordSource(int mixer) { - int recsrc; - if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) return -1; - int index = -1; - for (index = 0; index < 32; ++index) - if (recsrc & 1 << index) break; - return index; -} -//------------------------------------------------------------------------------ - -bool writeVolume(int volume, int mixer, int indexDev) { - if (ioctl(mixer, MIXER_WRITE(indexDev), &volume) == -1) return false; - return true; -} - -//------------------------------------------------------------------------------ - -bool controlEnableRecord(int mixer) { - int recmask; - if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) { - perror("Read recmask"); - return false; - } - if (recmask & (1 << SOUND_MIXER_IGAIN)) { - int volume; - if (ioctl(mixer, MIXER_READ(SOUND_MIXER_IGAIN), &volume) == -1) - return false; - - int app = (volume & 0xff); - if (app <= 30) { - volume = 80 | 80 << 8; - if (!writeVolume(volume, mixer, SOUND_MIXER_IGAIN)) return false; - } - } - return true; -} - -//------------------------------------------------------------------------------ - -int isInputDeviceSupported(TSoundInputDevice::Source dev, int &mixer) { - int recmask; - if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) { - perror("Read recmask"); - return -1; - } - - int i; - string devS = sourceToString(dev); - const char *deviceName[] = SOUND_DEVICE_NAMES; - for (i = 0; i < 32; ++i) { - if (!(recmask & 1 << i)) continue; - if (strcmp(devS.c_str(), deviceName[i]) == 0) return i; - } - return -1; -} - -//------------------------------------------------------------------------------ - -bool selectInputDevice(TSoundInputDevice::Source dev) { - int mixer; - if ((mixer = openMixer()) < 0) { - close(mixer); - return false; // throw TException("Can't open the mixer device"); - } - int index = isInputDeviceSupported(dev, mixer); - if (index == -1) return false; - int recsrc; - if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) { - perror("Read recsrc"); - close(mixer); - return false; - } - if (!(recsrc & 1 << index)) { - recsrc = 1 << index; - if (ioctl(mixer, SOUND_MIXER_WRITE_RECSRC, &recsrc) == -1) { - perror("Write recsrc"); - ::close(mixer); - return false; - } - } - - if (!controlEnableRecord(mixer)) { - close(mixer); - return false; // throw TException("Can't enable recording"); - } - ::close(mixer); - return true; -} -} diff --git a/toonz/sources/include/toonz/txshsoundcolumn.h b/toonz/sources/include/toonz/txshsoundcolumn.h index 23c4eb6b..1849d882 100644 --- a/toonz/sources/include/toonz/txshsoundcolumn.h +++ b/toonz/sources/include/toonz/txshsoundcolumn.h @@ -5,6 +5,7 @@ #include "toonz/txshcolumn.h" #include "toonz/txshcell.h" +#include "toonz/txshsoundlevel.h" #include "tsound.h" #include @@ -23,7 +24,69 @@ //============================================================================= // forward declarations class TFilePath; -class ColumnLevel; + + +//============================================================================= +// ColumnLevel +//============================================================================= + +class ColumnLevel { + TXshSoundLevelP m_soundLevel; + + /*!Offsets: in frames. Start offset is a positive number.*/ + int m_startOffset; + /*!Offsets: in frames. End offset is a positive number(to subtract to..).*/ + int m_endOffset; + + //! Starting frame in the timeline + int m_startFrame; + + //! frameRate + double m_fps; + +public: + ColumnLevel(TXshSoundLevel* soundLevel = 0, int startFrame = -1, + int startOffset = -1, int endOffset = -1, double fps = -1); + ~ColumnLevel(); + ColumnLevel* clone() const; + + //! Overridden from TXshLevel + TXshSoundLevel* getSoundLevel() const { return m_soundLevel.getPointer(); } + void setSoundLevel(TXshSoundLevelP level) { m_soundLevel = level; } + + void loadData(TIStream& is); + void saveData(TOStream& os); + + void setStartOffset(int value); + int getStartOffset() const { return m_startOffset; } + + void setEndOffset(int value); + int getEndOffset() const { return m_endOffset; } + + void setOffsets(int startOffset, int endOffset); + + //! Return the starting frame without offsets. + void setStartFrame(int frame) { m_startFrame = frame; } + int getStartFrame() const { return m_startFrame; } + + //! Return the ending frame without offsets. + int getEndFrame() const; + + //! Return frame count without offset. + int getFrameCount() const; + + //! Return frame count with offset. + int getVisibleFrameCount() const; + + //! Return start frame with offset. + int getVisibleStartFrame() const; + //! Return last frame with offset. + int getVisibleEndFrame() const; + //! Updates m_startOfset and m_endOffset. + void updateFrameRate(double newFrameRate); + + void setFrameRate(double fps) { m_fps = fps; } +}; //============================================================================= //! The TXshSoundColumn class provides a sound column in xsheet and allows its @@ -75,6 +138,7 @@ public: int getFirstRow() const override; const TXshCell &getCell(int row) const override; + TXshCell getSoundCell(int row); void getCells(int row, int rowCount, TXshCell cells[]) override; bool setCell(int row, const TXshCell &cell) override; diff --git a/toonz/sources/tnztools/tool.cpp b/toonz/sources/tnztools/tool.cpp index f4b0dae4..8c296bf0 100644 --- a/toonz/sources/tnztools/tool.cpp +++ b/toonz/sources/tnztools/tool.cpp @@ -856,10 +856,12 @@ QString TTool::updateEnabled(int rowIndex, int columnIndex) { // find the nearest level before it if (levelType == NO_XSHLEVEL && !m_application->getCurrentFrame()->isEditingLevel()) { - TXshCell cell = xsh->getCell(rowIndex, columnIndex); - xl = cell.isEmpty() ? 0 : (TXshLevel *)(&cell.m_level); - sl = cell.isEmpty() ? 0 : cell.getSimpleLevel(); - levelType = cell.isEmpty() ? NO_XSHLEVEL : cell.m_level->getType(); + if (!column || (column && !column->getSoundColumn())) { + TXshCell cell = xsh->getCell(rowIndex, columnIndex); + xl = cell.isEmpty() ? 0 : (TXshLevel*)(&cell.m_level); + sl = cell.isEmpty() ? 0 : cell.getSimpleLevel(); + levelType = cell.isEmpty() ? NO_XSHLEVEL : cell.m_level->getType(); + } } if (Preferences::instance()->isAutoCreateEnabled()) { @@ -867,16 +869,18 @@ QString TTool::updateEnabled(int rowIndex, int columnIndex) { // find the nearest level before it if (levelType == NO_XSHLEVEL && !m_application->getCurrentFrame()->isEditingLevel()) { - int r0, r1; - xsh->getCellRange(columnIndex, r0, r1); - for (int r = std::min(r1, rowIndex); r > r0; r--) { - TXshCell cell = xsh->getCell(r, columnIndex); - if (cell.isEmpty()) continue; - xl = (TXshLevel *)(&cell.m_level); - sl = cell.getSimpleLevel(); - levelType = cell.m_level->getType(); - break; - } + if (!column || (column && !column->getSoundColumn())) { + int r0, r1; + xsh->getCellRange(columnIndex, r0, r1); + for (int r = std::min(r1, rowIndex); r > r0; r--) { + TXshCell cell = xsh->getCell(r, columnIndex); + if (cell.isEmpty()) continue; + xl = (TXshLevel*)(&cell.m_level); + sl = cell.getSimpleLevel(); + levelType = cell.m_level->getType(); + break; + } + } } // If the current tool does not match the current type, check for diff --git a/toonz/sources/toonz/sceneviewer.cpp b/toonz/sources/toonz/sceneviewer.cpp index 2745c2db..4c7e9a53 100644 --- a/toonz/sources/toonz/sceneviewer.cpp +++ b/toonz/sources/toonz/sceneviewer.cpp @@ -2108,6 +2108,7 @@ void SceneViewer::drawScene() { args.m_osm = &osm; args.m_xsheetLevel = xsheetLevel; args.m_isPlaying = frameHandle->isPlaying(); + if (app->getCurrentColumn()->getColumn() && !app->getCurrentColumn()->getColumn()->getSoundColumn()) args.m_currentFrameId = app->getCurrentXsheet() ->getXsheet() diff --git a/toonz/sources/toonz/tapp.cpp b/toonz/sources/toonz/tapp.cpp index 45a934e9..01079177 100644 --- a/toonz/sources/toonz/tapp.cpp +++ b/toonz/sources/toonz/tapp.cpp @@ -49,6 +49,7 @@ #include "toonz/tcamera.h" #include "toonz/preferences.h" #include "toonz/fullcolorpalette.h" +#include "toonz/txshsoundcolumn.h" #include "toonzqt/tabbar.h" @@ -309,7 +310,9 @@ int TApp::getCurrentImageType() { : TImage::RASTER; // and OVL_XSHLEVEL level types } - TXsheet *xsh = getCurrentXsheet()->getXsheet(); + TXsheet *xsh = getCurrentXsheet()->getXsheet(); + if (xsh->getColumn(col) && xsh->getColumn(col)->getSoundColumn()) + return TImage::VECTOR; TXshCell cell = xsh->getCell(row, col); if (cell.isEmpty()) { int r0, r1; @@ -357,7 +360,18 @@ void TApp::updateXshLevel() { int column = m_currentColumn->getColumnIndex(); TXsheet *xsheet = m_currentXsheet->getXsheet(); - if (xsheet && column >= 0 && frame >= 0 && !xsheet->isColumnEmpty(column)) { + bool isSoundColumn = false; + if (xsheet->getColumn(column) && + xsheet->getColumn(column)->getSoundColumn()) { + isSoundColumn = true; + if (xsheet->getColumn(column)->getSoundColumn()->m_levels.size() > 0) { + xl = static_cast(xsheet->getColumn(column) + ->getSoundColumn() + ->m_levels.at(0) + ->getSoundLevel()); + } + } else if (xsheet && column >= 0 && frame >= 0 && + !xsheet->isColumnEmpty(column)) { TXshCell cell = xsheet->getCell(frame, column); xl = cell.m_level.getPointer(); diff --git a/toonz/sources/toonz/viewerpane.cpp b/toonz/sources/toonz/viewerpane.cpp index 6131c6bf..d8144816 100644 --- a/toonz/sources/toonz/viewerpane.cpp +++ b/toonz/sources/toonz/viewerpane.cpp @@ -645,7 +645,8 @@ void SceneViewerPanel::changeWindowTitle() { return; } TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); - TXshCell cell = xsh->getCell(frame, col); + TXshCell cell; + if (app->getCurrentColumn()->getColumn() && !app->getCurrentColumn()->getColumn()->getSoundColumn()) cell = xsh->getCell(frame, col); if (cell.isEmpty()) { if (!m_sceneViewer->is3DView()) { TAffine aff = m_sceneViewer->getViewMatrix() * diff --git a/toonz/sources/toonz/xshcellviewer.cpp b/toonz/sources/toonz/xshcellviewer.cpp index 2e341235..2f221300 100644 --- a/toonz/sources/toonz/xshcellviewer.cpp +++ b/toonz/sources/toonz/xshcellviewer.cpp @@ -1415,8 +1415,8 @@ void CellArea::drawSoundCell(QPainter &p, int row, int col, bool isReference) { } TXshCell nextCell; - nextCell = - m_viewer->getXsheet()->getCell(row + 1, col); // cell in next frame + nextCell = soundColumn->getSoundCell(row + 1); // cell in next frame + bool isNextEmpty = nextCell.getFrameId().getNumber() < 0; int frameAdj = m_viewer->getFrameZoomAdjustment(); int frameZoomF = m_viewer->getFrameZoomFactor(); @@ -1424,13 +1424,11 @@ void CellArea::drawSoundCell(QPainter &p, int row, int col, bool isReference) { cellRect.adjust(0, 0, -frameAdj, 0); QRect rect = cellRect.adjusted( 1, 1, - (!m_viewer->orientation()->isVerticalTimeline() && !nextCell.isEmpty() - ? 2 - : 0), + (!m_viewer->orientation()->isVerticalTimeline() && !isNextEmpty ? 2 : 0), 0); int maxNumFrame = soundColumn->getMaxFrame() + 1; int startFrame = soundColumn->getFirstRow(); - TXshCell cell = soundColumn->getCell(row); + TXshCell cell = soundColumn->getSoundCell(row); if (soundColumn->isCellEmpty(row) || cell.isEmpty() || row > maxNumFrame || row < startFrame) { drawFrameSeparator(p, row, col, true); @@ -2948,7 +2946,7 @@ void CellArea::mouseMoveEvent(QMouseEvent *event) { ->getZeraryColumnFx() ->getZeraryFx() ->getName()); - else if ((!xsh->getCell(row, col).isEmpty() && !isSoundColumn) && // x > 6 && + else if ((!isSoundColumn && !xsh->getCell(row, col).isEmpty()) && // x > 6 && x < (o->cellWidth() - frameAdj)) { TXshCell cell = xsh->getCell(row, col); TFrameId fid = cell.getFrameId(); diff --git a/toonz/sources/toonz/xshcolumnviewer.cpp b/toonz/sources/toonz/xshcolumnviewer.cpp index 546f7d86..9597c1c6 100644 --- a/toonz/sources/toonz/xshcolumnviewer.cpp +++ b/toonz/sources/toonz/xshcolumnviewer.cpp @@ -2282,8 +2282,12 @@ void ColumnArea::mousePressEvent(QMouseEvent *event) { TSoundTrackP sTrack = s->getCurrentPlaySoundTruck(); interval = sTrack->getDuration() * 1000 + 300; } - if (s->isPlaying() && interval > 0) - QTimer::singleShot(interval, this, SLOT(update())); + if (s->isPlaying() && interval > 0) { + QTimer::singleShot(interval, this, [this, s] { + if (s && s->isPlaying()) s->stop(); + update(); + }); + } } update(); } else if (!o->flag(PredefinedFlag::CONFIG_AREA_VISIBLE) && diff --git a/toonz/sources/toonz/xsheetviewer.cpp b/toonz/sources/toonz/xsheetviewer.cpp index c04c03e8..8f7fa0cf 100644 --- a/toonz/sources/toonz/xsheetviewer.cpp +++ b/toonz/sources/toonz/xsheetviewer.cpp @@ -128,9 +128,14 @@ void XsheetViewer::getColumnColor(QColor &color, QColor &sideColor, int index, xsh->getCellRange(index, r0, r1); if (0 <= r0 && r0 <= r1) { // column color depends on the level type in the top-most occupied cell - TXshCell cell = xsh->getCell(r0, index); - int ltype; - getCellTypeAndColors(ltype, color, sideColor, cell); + if (xsh->getColumn(index)->getSoundColumn()) { + color = m_soundColumnColor; + sideColor = m_soundColumnBorderColor; + } else { + TXshCell cell = xsh->getCell(r0, index); + int ltype; + getCellTypeAndColors(ltype, color, sideColor, cell); + } } if (xsh->getColumn(index)->isMask()) color = QColor(255, 0, 255); } diff --git a/toonz/sources/toonzlib/stage.cpp b/toonz/sources/toonzlib/stage.cpp index e9202b3b..48ad3c15 100644 --- a/toonz/sources/toonzlib/stage.cpp +++ b/toonz/sources/toonzlib/stage.cpp @@ -677,7 +677,7 @@ void StageBuilder::addFrame(PlayerSet &players, ToonzScene *scene, TXsheet *xsh, } TXshColumn *column = xsh->getColumn(c); bool isMask = false; - if (column && !column->isEmpty()) { + if (column && !column->isEmpty() && !column->getSoundColumn()) { if (!column->isPreviewVisible() && checkPreviewVisibility) continue; if (column->isCamstandVisible() || includeUnvisible) // se l'"occhietto" non e' chiuso diff --git a/toonz/sources/toonzlib/txsheet.cpp b/toonz/sources/toonzlib/txsheet.cpp index 9cd8d5c3..4318a743 100644 --- a/toonz/sources/toonzlib/txsheet.cpp +++ b/toonz/sources/toonzlib/txsheet.cpp @@ -34,6 +34,7 @@ #include "orientation.h" #include "toonz/txsheet.h" +#include "toonz/preferences.h" using namespace std; @@ -781,7 +782,7 @@ void TXsheet::increaseStepCells(int r0, int c0, int &r1, int c1) { // controllo se devo cambiare la selezione bool allIncreaseIsEqual = true; for (c = 0; c < ends.size() - 1 && allIncreaseIsEqual; c++) - allIncreaseIsEqual = allIncreaseIsEqual && ends[c] == ends[c + 1]; + allIncreaseIsEqual = allIncreaseIsEqual && ends[c] == ends[c + 1]; if (allIncreaseIsEqual) r1 = ends[0]; } @@ -815,7 +816,7 @@ void TXsheet::decreaseStepCells(int r0, int c0, int &r1, int c1) { // controllo se devo cambiare la selezione bool allDecreaseIsEqual = true; for (c = 0; c < ends.size() - 1 && allDecreaseIsEqual; c++) - allDecreaseIsEqual = allDecreaseIsEqual && ends[c] == ends[c + 1]; + allDecreaseIsEqual = allDecreaseIsEqual && ends[c] == ends[c + 1]; if (allDecreaseIsEqual) r1 = ends[0]; } @@ -1477,7 +1478,7 @@ TSoundTrack *TXsheet::makeSound(SoundProperties *properties) { void TXsheet::scrub(int frame, bool isPreview) { try { double fps = - getScene()->getProperties()->getOutputProperties()->getFrameRate(); + getScene()->getProperties()->getOutputProperties()->getFrameRate(); TXsheet::SoundProperties *prop = new TXsheet::SoundProperties(); prop->m_isPreview = isPreview; @@ -1488,7 +1489,20 @@ void TXsheet::scrub(int frame, bool isPreview) { double samplePerFrame = st->getSampleRate() / fps; double s0 = frame * samplePerFrame, s1 = s0 + samplePerFrame; - + // if (m_player && m_player->isPlaying()) { + // try { + // m_player->stop(); + // } + // catch (const std::runtime_error& e) { + // int i = 0; + // } + // catch (const std::exception& e) { + // int i = 0; + // } + // catch (...) { + // int i = 0; + // } + //} play(st, s0, s1, false); } catch (TSoundDeviceException &e) { if (e.getType() == TSoundDeviceException::NoDevice) { diff --git a/toonz/sources/toonzlib/txshsoundcolumn.cpp b/toonz/sources/toonzlib/txshsoundcolumn.cpp index 2d46dc4c..faeb1e72 100644 --- a/toonz/sources/toonzlib/txshsoundcolumn.cpp +++ b/toonz/sources/toonzlib/txshsoundcolumn.cpp @@ -6,78 +6,16 @@ #include "toonz/toonzscene.h" #include "toonz/tproject.h" #include "toonz/sceneproperties.h" -#include "toonz/txshsoundlevel.h" #include "tstream.h" #include "toutputproperties.h" #include "tsop.h" #include "tconvert.h" +#include "toonz/preferences.h" #include #include -//============================================================================= -// ColumnLevel -//============================================================================= - -class ColumnLevel { - TXshSoundLevelP m_soundLevel; - - /*!Offsets: in frames. Start offset is a positive number.*/ - int m_startOffset; - /*!Offsets: in frames. End offset is a positive number(to subtract to..).*/ - int m_endOffset; - - //! Starting frame in the timeline - int m_startFrame; - - //! frameRate - double m_fps; - -public: - ColumnLevel(TXshSoundLevel *soundLevel = 0, int startFrame = -1, - int startOffset = -1, int endOffset = -1, double fps = -1); - ~ColumnLevel(); - ColumnLevel *clone() const; - - //! Overridden from TXshLevel - TXshSoundLevel *getSoundLevel() const { return m_soundLevel.getPointer(); } - void setSoundLevel(TXshSoundLevelP level) { m_soundLevel = level; } - - void loadData(TIStream &is); - void saveData(TOStream &os); - - void setStartOffset(int value); - int getStartOffset() const { return m_startOffset; } - - void setEndOffset(int value); - int getEndOffset() const { return m_endOffset; } - - void setOffsets(int startOffset, int endOffset); - - //! Return the starting frame without offsets. - void setStartFrame(int frame) { m_startFrame = frame; } - int getStartFrame() const { return m_startFrame; } - - //! Return the ending frame without offsets. - int getEndFrame() const; - - //! Return frame count without offset. - int getFrameCount() const; - - //! Return frame count with offset. - int getVisibleFrameCount() const; - - //! Return start frame with offset. - int getVisibleStartFrame() const; - //! Return last frame with offset. - int getVisibleEndFrame() const; - //! Updates m_startOfset and m_endOffset. - void updateFrameRate(double newFrameRate); - - void setFrameRate(double fps) { m_fps = fps; } -}; - //============================================================================= ColumnLevel::ColumnLevel(TXshSoundLevel *soundLevel, int startFrame, @@ -381,18 +319,34 @@ const TXshCell &TXshSoundColumn::getCell(int row) const { if (!l) return emptyCell; TXshSoundLevel *soundLevel = l->getSoundLevel(); TXshCell *cell = new TXshCell(soundLevel, TFrameId(row - l->getStartFrame())); - // La nuova cella aggiunge un reference al TXshSoundLevel; poiche' le celle - // delle - // TXshSoundColumn non sono strutture persistenti ma sono strutture dinamiche - // (vengono ricreate ogni volta) devo occuparmi di fare il release altrimenti - // il - // TXshSoundLevel non viene mai buttato. + // The new cell adds a reference to the TXshSoundLevel; + // since the cells of the TXshSoundColumn are not persistent structures + // but are dynamic structures (they are recreated every time) I have to take + // care of making the release otherwise the TXshSoundLevel is never thrown + // away. soundLevel->release(); return *cell; } //----------------------------------------------------------------------------- +TXshCell TXshSoundColumn::getSoundCell(int row) { + static TXshCell emptyCell; + + ColumnLevel* l = getColumnLevelByFrame(row); + if (row < 0 || row < getFirstRow() || row > getMaxFrame()) { + if (l) emptyCell.m_level = l->getSoundLevel(); + return emptyCell; + } + + if (!l) return emptyCell; + TXshSoundLevel* soundLevel = l->getSoundLevel(); + TXshCell cell(soundLevel, TFrameId(row - l->getStartFrame())); + return cell; +} + +//----------------------------------------------------------------------------- + bool TXshSoundColumn::isCellEmpty(int row) const { if (m_levels.empty()) return true; ColumnLevel *l = getColumnLevelByFrame(row); @@ -978,7 +932,7 @@ TSoundTrackP TXshSoundColumn::getOverallSoundTrack(int fromFrame, int toFrame, if (toFrame == -1) toFrame = getMaxFrame(); if (format.m_sampleRate == 0) { - // Found the best format inside the soundsequences + // Find the best format inside the soundsequences int sampleRate = 0; int bitsPerSample = 8; int channels = 1; @@ -1015,10 +969,8 @@ TSoundTrackP TXshSoundColumn::getOverallSoundTrack(int fromFrame, int toFrame, format.m_signedSample = true; } -// We prefer to have 22050 as a maximum sampleRate (to avoid crashes or -// another issues) #ifdef _WIN32 - if (format.m_sampleRate >= 44100) format.m_sampleRate = 22050; + if (format.m_sampleRate > 44100) format.m_sampleRate = 44100; #else QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); if (info.deviceName().length() == 0) throw TSoundDeviceException(TSoundDeviceException::NoDevice,