/* ------------------------------------------------------------------ This file is part of the Open Ephys GUI Copyright (C) 2013 Open Ephys ------------------------------------------------------------------ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "SequentialBlockFile.h" using namespace BinaryRecordingEngine; SequentialBlockFile::SequentialBlockFile(int nChannels, int samplesPerBlock) : m_file(nullptr), m_nChannels(nChannels), m_samplesPerBlock(samplesPerBlock), m_blockSize(nChannels*samplesPerBlock) { m_memBlocks.ensureStorageAllocated(blockArrayInitSize); for (int i = 0; i < nChannels; i++) m_currentBlock.add(-1); } SequentialBlockFile::~SequentialBlockFile() { //Ensure that all remaining blocks are flushed in order int n = m_memBlocks.size(); for (int i = 0; i < n; i++) m_memBlocks.remove(0); } bool SequentialBlockFile::openFile(String filename) { File file(filename); Result res = file.create(); if (res.failed()) { std::cerr << "Error creating file " << filename << ":" << res.getErrorMessage() << std::endl; return false; } m_file = file.createOutputStream(streamBufferSize); if (!m_file) return false; m_memBlocks.add(new FileBlock(m_file, m_blockSize, 0)); return true; } bool SequentialBlockFile::writeChannel(uint64 startPos, int channel, int16* data, int nSamples) { if (!m_file) return false; int bIndex = m_memBlocks.size() - 1; if ((bIndex < 0) || (m_memBlocks[bIndex]->getOffset() + m_samplesPerBlock) < (startPos + nSamples)) allocateBlocks(startPos, nSamples); for (bIndex = m_memBlocks.size() - 1; bIndex >= 0; bIndex--) { if (m_memBlocks[bIndex]->getOffset() <= startPos) break; } if (bIndex < 0) { std::cerr << "BINARY WRITER: Memory block unloaded ahead of time for chan " << channel << " start " << startPos << " ns " << nSamples << " first " << m_memBlocks[0]->getOffset() <<std::endl; for (int i = 0; i < m_nChannels; i++) std::cout << "channel " << i << " last block " << m_currentBlock[i] << std::endl; return false; } int writtenSamples = 0; int startIdx = startPos - m_memBlocks[bIndex]->getOffset(); int startMemPos = startIdx*m_nChannels; int dataIdx = 0; while (writtenSamples < nSamples) { int16* blockPtr = m_memBlocks[bIndex]->getData(); int samplesToWrite = jmin((nSamples - writtenSamples), (m_samplesPerBlock - startIdx)); for (int i = 0; i < samplesToWrite; i++) { *(blockPtr + startMemPos + channel + i*m_nChannels) = *(data + dataIdx); dataIdx++; } writtenSamples += samplesToWrite; startIdx = 0; startMemPos = 0; bIndex++; } m_currentBlock.set(channel, bIndex - 1); //store the last block a channel was written in return true; } void SequentialBlockFile::allocateBlocks(uint64 startIndex, int numSamples) { //First deallocate full blocks //Search for the earliest unused block; unsigned int minBlock = 0xFFFFFFFF; //large number; for (int i = 0; i < m_nChannels; i++) { if (m_currentBlock[i] < minBlock) minBlock = m_currentBlock[i]; } //Update block indexes for (int i = 0; i < m_nChannels; i++) { m_currentBlock.set(i, m_currentBlock[i] - minBlock); } m_memBlocks.removeRange(0, minBlock); //for (int i = 0; i < minBlock; i++) //{ //Not the most efficient way, as it has to move back all the elements, but it's a simple array of pointers, so it's quick enough // m_memBlocks.remove(0); //} //Look for the last available position and calculate needed space uint64 lastOffset = m_memBlocks.getLast()->getOffset(); uint64 maxAddr = lastOffset + m_samplesPerBlock - 1; uint64 newSpaceNeeded = numSamples - (maxAddr - startIndex); int newBlocks = (newSpaceNeeded + m_samplesPerBlock - 1) / m_samplesPerBlock; //Fast ceiling division for (int i = 0; i < newBlocks; i++) { lastOffset += m_samplesPerBlock; m_memBlocks.add(new FileBlock(m_file, m_blockSize, lastOffset)); } }