Browse Source

Continue AudioFile C++ work; Cleanup

tags/1.9.4
falkTX 12 years ago
parent
commit
fc8de7aa21
5 changed files with 455 additions and 260 deletions
  1. +1
    -0
      source/backend/native/CarlaNative.pro
  2. +7
    -7
      source/backend/native/Makefile
  3. +375
    -0
      source/backend/native/audio-base.hpp
  4. +71
    -252
      source/backend/native/audio-file.cpp
  5. +1
    -1
      source/backend/native/midi-base.hpp

+ 1
- 0
source/backend/native/CarlaNative.pro View File

@@ -68,6 +68,7 @@ SOURCES += \
distrho-3bandeq.cpp distrho-3bandeq.cpp


HEADERS = \ HEADERS = \
audio-base.hpp \
midi-base.hpp midi-base.hpp


HEADERS += \ HEADERS += \


+ 7
- 7
source/backend/native/Makefile View File

@@ -153,12 +153,6 @@ debug:
CDEPS = ../CarlaNative.h CDEPS = ../CarlaNative.h
CXXDEPS = ../CarlaNative.h ../CarlaNative.hpp CXXDEPS = ../CarlaNative.h ../CarlaNative.hpp


%.c.o: %.c $(CDEPS)
$(CC) $< $(BUILD_C_FLAGS) -c -o $@

%.cpp.o: %.cpp $(CXXDEPS)
$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@

$(TARGET): $(OBJS) $(TARGET): $(OBJS)
$(AR) rs $@ $^ $(AR) rs $@ $^


@@ -167,7 +161,7 @@ $(TARGET): $(OBJS)
audio_decoder/%.c.o: audio_decoder/%.c audio_decoder/%.c.o: audio_decoder/%.c
$(CC) $< $(AF_C_FLAGS) -c -o $@ $(CC) $< $(AF_C_FLAGS) -c -o $@


audio-file.cpp.o: audio-file.cpp $(CXXDEPS)
audio-file.cpp.o: audio-file.cpp audio-base.hpp $(CXXDEPS)
$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ $(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@


distrho-3bandeq.cpp.o: distrho-3bandeq.cpp 3bandeq/*.cpp 3bandeq/*.h 3bandeq/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) distrho-3bandeq.cpp.o: distrho-3bandeq.cpp 3bandeq/*.cpp 3bandeq/*.h 3bandeq/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS)
@@ -202,6 +196,12 @@ zynaddsubfx-ui.cpp.o: zynaddsubfx-ui.cpp $(ZYN_UI_FILES_H)


# -------------------------------------------------------------- # --------------------------------------------------------------


%.c.o: %.c $(CDEPS)
$(CC) $< $(BUILD_C_FLAGS) -c -o $@

%.cpp.o: %.cpp $(CXXDEPS)
$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@

moc_%.cpp: %.hpp moc_%.cpp: %.hpp
$(MOC) $< -DMOC_PARSING -o $@ $(MOC) $< -DMOC_PARSING -o $@




+ 375
- 0
source/backend/native/audio-base.hpp View File

@@ -0,0 +1,375 @@
/*
* Carla Native Plugins
* Copyright (C) 2013 Filipe Coelho <falktx@falktx.com>
*
* 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 2 of
* the License, or 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.
*
* For a full copy of the GNU General Public License see the GPL.txt file
*/

#ifndef __AUDIO_BASE_HPP__
#define __AUDIO_BASE_HPP__

#include "CarlaMutex.hpp"

#include <QtCore/QThread>

extern "C" {
#include "audio_decoder/ad.h"
}

typedef struct adinfo ADInfo;

struct AudioFilePool {
float* buffer[2];
uint32_t startFrame;
uint32_t size;

AudioFilePool()
: buffer{nullptr},
startFrame(0),
size(0) {}

~AudioFilePool()
{
CARLA_ASSERT(buffer[0] == nullptr);
CARLA_ASSERT(buffer[1] == nullptr);
CARLA_ASSERT(startFrame == 0);
CARLA_ASSERT(size == 0);
}

void create(const uint32_t sampleRate)
{
CARLA_ASSERT(buffer[0] == nullptr);
CARLA_ASSERT(buffer[1] == nullptr);
CARLA_ASSERT(startFrame == 0);
CARLA_ASSERT(size == 0);

size = sampleRate * 6;

buffer[0] = new float[size];
buffer[1] = new float[size];

reset();
}

void destroy()
{
CARLA_ASSERT(buffer[0] != nullptr);
CARLA_ASSERT(buffer[1] != nullptr);
CARLA_ASSERT(size != 0);

if (buffer[0] != nullptr)
{
delete[] buffer[0];
buffer[0] = nullptr;
}

if (buffer[1] != nullptr)
{
delete[] buffer[1];
buffer[1] = nullptr;
}

startFrame = 0;
size = 0;
}

void reset()
{
startFrame = 0;
carla_zeroFloat(buffer[0], size);
carla_zeroFloat(buffer[1], size);
}

void operator=(AudioFilePool& pool)
{
CARLA_ASSERT(pool.size == size);

pool.startFrame = startFrame;
carla_copyFloat(pool.buffer[0], buffer[0], size);
carla_copyFloat(pool.buffer[1], buffer[1], size);
}
};

class AbstractAudioPlayer
{
public:
virtual ~AbstractAudioPlayer() {}
virtual uint32_t getLastFrame() const = 0;
};

class AudioFileThread : public QThread
{
public:
AudioFileThread(AbstractAudioPlayer* const player, const double sampleRate)
: QThread(nullptr),
kPlayer(player),
fNeedsRead(false),
fQuitNow(true),
fFilePtr(nullptr)
{
CARLA_ASSERT(kPlayer != nullptr);

static bool adInitiated = false;

//if (! adInitiated)
{
ad_init();
adInitiated = true;
}

ad_clear_nfo(&fFileNfo);

fPool.create(sampleRate);
}

~AudioFileThread() override
{
CARLA_ASSERT(fQuitNow);
CARLA_ASSERT(! isRunning());

if (fFilePtr != nullptr)
ad_close(fFilePtr);

fPool.destroy();
}

void startNow()
{
start(IdlePriority);
}

void stopNow()
{
fNeedsRead = false;
fQuitNow = true;

if (isRunning() && ! wait(1000))
terminate();
}

uint32_t getMaxFrame() const
{
return fFileNfo.frames > 0 ? fFileNfo.frames : 0;
}

void setNeedsRead()
{
fNeedsRead = true;
}

bool loadFilename(const char* const filename)
{
CARLA_ASSERT(! isRunning());
CARLA_ASSERT(filename != nullptr);

fPool.startFrame = 0;

// clear old data
if (fFilePtr != nullptr)
{
ad_close(fFilePtr);
fFilePtr = nullptr;
}

ad_clear_nfo(&fFileNfo);

ad_init();

// open new
fFilePtr = ad_open(filename, &fFileNfo);

if (fFilePtr == nullptr)
return false;

ad_dump_nfo(99, &fFileNfo);

if (fFileNfo.frames == 0)
carla_stderr("L: filename \"%s\" has 0 frames", filename);

if ((fFileNfo.channels == 1 || fFileNfo.channels == 2) && fFileNfo.frames > 0)
{
// valid
readPoll();
return true;
}
else
{
// invalid
ad_clear_nfo(&fFileNfo);
ad_close(fFilePtr);
fFilePtr = nullptr;
return false;
}
}

void tryPutData(AudioFilePool& pool)
{
if (! fMutex.tryLock())
return;

pool = fPool;

fMutex.unlock();
}

void readPoll()
{
if (fFileNfo.frames <= 0 || fFilePtr == nullptr)
{
carla_stderr("R: no song loaded");
fNeedsRead = false;
return;
}

int64_t lastFrame = kPlayer->getLastFrame();
int64_t readFrame = lastFrame;
int64_t maxFrame = fFileNfo.frames;

if (lastFrame >= maxFrame)
{
#if 0
if (false)
//if (handlePtr->loopMode)
{
carla_stderr("R: DEBUG read loop, lastFrame:%i, maxFrame:%i", lastFrame, maxFrame);

if (maxFrame >= static_cast<int64_t>(fPool.size))
{
readFrame %= maxFrame;
}
else
{
readFrame = 0;
lastFrame -= lastFrame % maxFrame;
}
}
else
#endif
{
carla_stderr("R: transport out of bounds");
fNeedsRead = false;
return;
}
}

// temp data buffer
const size_t tmpSize = fPool.size * fFileNfo.channels;

float tmpData[tmpSize];
carla_zeroFloat(tmpData, tmpSize);

{
carla_stderr("R: poll data - reading at %li:%02li", readFrame/44100/60, (readFrame/44100) % 60);

ad_seek(fFilePtr, readFrame);
ssize_t i, j, rv = ad_read(fFilePtr, tmpData, tmpSize);
i = j = 0;

// lock, and put data asap
const CarlaMutex::ScopedLocker sl(&fMutex);

for (; i < fPool.size && j < rv; ++j)
{
if (fFileNfo.channels == 1)
{
fPool.buffer[0][i] = tmpData[j];
fPool.buffer[1][i] = tmpData[j];
i++;
}
else
{
if (j % 2 == 0)
{
fPool.buffer[0][i] = tmpData[j];
}
else
{
fPool.buffer[1][i] = tmpData[j];
i++;
}
}
}

#if 0
if (false)
//if (handlePtr->loopMode && i < fPool.size)
{
while (i < fPool.size)
{
for (j=0; i < fPool.size && j < rv; ++j)
{
if (fFileNfo.channels == 1)
{
fPool.buffer[0][i] = tmpData[j];
fPool.buffer[1][i] = tmpData[j];
i++;
}
else
{
if (j % 2 == 0)
{
fPool.buffer[0][i] = tmpData[j];
}
else
{
fPool.buffer[1][i] = tmpData[j];
i++;
}
}
}
}
}
else
#endif
{
for (; i < fPool.size; ++i)
{
fPool.buffer[0][i] = 0.0f;
fPool.buffer[1][i] = 0.0f;
}
}

fPool.startFrame = lastFrame;
}

fNeedsRead = false;
}

protected:
void run() override
{
while (! fQuitNow)
{
const uint32_t lastFrame(kPlayer->getLastFrame());

if (fNeedsRead || lastFrame < fPool.startFrame || lastFrame - fPool.startFrame >= fPool.size*3/4)
readPoll();
else
carla_msleep(50);
}
}

private:
AbstractAudioPlayer* const kPlayer;

bool fNeedsRead;
bool fQuitNow;

void* fFilePtr;
ADInfo fFileNfo;

AudioFilePool fPool;
CarlaMutex fMutex;
};

#endif // __AUDIO_BASE_HPP__

+ 71
- 252
source/backend/native/audio-file.cpp View File

@@ -16,54 +16,37 @@
*/ */


#include "CarlaNative.hpp" #include "CarlaNative.hpp"
#include "CarlaMutex.hpp"
#include "CarlaString.hpp" #include "CarlaString.hpp"


#include <QtCore/QThread>

extern "C" {
#include "audio_decoder/ad.h"
}
#include "audio-base.hpp"


#define PROGRAM_COUNT 16 #define PROGRAM_COUNT 16


typedef struct adinfo ADInfo;

class AudioFilePlugin : public PluginDescriptorClass
class AudioFilePlugin : public PluginDescriptorClass,
public AbstractAudioPlayer
{ {
public: public:
AudioFilePlugin(const HostDescriptor* const host) AudioFilePlugin(const HostDescriptor* const host)
: PluginDescriptorClass(host), : PluginDescriptorClass(host),
filePtr(nullptr),
lastFrame(0),
maxFrame(0),
loopMode(false),
doProcess(false)
AbstractAudioPlayer(),
fLoopMode(false),
fDoProcess(false),
fLastFrame(0),
fMaxFrame(0),
fThread(this, getSampleRate())
{ {
static bool adInitiated = false;

if (! adInitiated)
{
ad_init();
adInitiated = true;
}

ad_clear_nfo(&fileNfo);

pool.create(getSampleRate());
thread.setPoolSize(pool.size);
fPool.create(getSampleRate());
} }


~AudioFilePlugin() override ~AudioFilePlugin() override
{ {
doProcess = false;

thread.stopNow();

if (filePtr != nullptr)
ad_close(filePtr);
fPool.destroy();
fThread.stopNow();
}


pool.destroy();
uint32_t getLastFrame() const override
{
return fLastFrame;
} }


protected: protected:
@@ -72,14 +55,11 @@ protected:


uint32_t getParameterCount() override uint32_t getParameterCount() override
{ {
return 0; // FIXME
return 1; return 1;
} }


const Parameter* getParameterInfo(const uint32_t index) override const Parameter* getParameterInfo(const uint32_t index) override
{ {
return nullptr; // FIXME - loop mode needs work

if (index != 0) if (index != 0)
return nullptr; return nullptr;


@@ -105,7 +85,7 @@ protected:
if (index != 0) if (index != 0)
return 0.0f; return 0.0f;


return loopMode ? 1.0f : 0.0f;
return fLoopMode ? 1.0f : 0.0f;
} }


// ------------------------------------------------------------------- // -------------------------------------------------------------------
@@ -125,7 +105,7 @@ protected:


midiProgram.bank = 0; midiProgram.bank = 0;
midiProgram.program = index; midiProgram.program = index;
midiProgram.name = (const char*)programs.shortNames[index];
midiProgram.name = (const char*)fPrograms.shortNames[index];


return &midiProgram; return &midiProgram;
} }
@@ -140,11 +120,11 @@ protected:


bool b = (value > 0.5f); bool b = (value > 0.5f);


if (b == loopMode)
if (b == fLoopMode)
return; return;


loopMode = b;
thread.setNeedsRead();
fLoopMode = b;
fThread.setNeedsRead();
} }


void setMidiProgram(const uint8_t, const uint32_t bank, const uint32_t program) override void setMidiProgram(const uint8_t, const uint32_t bank, const uint32_t program) override
@@ -152,10 +132,10 @@ protected:
if (bank != 0 || program >= PROGRAM_COUNT) if (bank != 0 || program >= PROGRAM_COUNT)
return; return;


if (programs.current != program)
if (fPrograms.current != program)
{ {
loadFilename(programs.fullNames[program]);
programs.current = program;
loadFilename(fPrograms.fullNames[program]);
fPrograms.current = program;
} }
} }


@@ -178,16 +158,16 @@ protected:
if (program >= PROGRAM_COUNT) if (program >= PROGRAM_COUNT)
return; return;


programs.fullNames[program] = value;
fPrograms.fullNames[program] = value;


if (const char* shortName = std::strrchr(value, OS_SEP))
programs.shortNames[program] = shortName+1;
if (const char* const shortName = std::strrchr(value, OS_SEP))
fPrograms.shortNames[program] = shortName+1;
else else
programs.shortNames[program] = value;
fPrograms.shortNames[program] = value;


programs.shortNames[program].truncate(programs.shortNames[program].rfind('.'));
fPrograms.shortNames[program].truncate(fPrograms.shortNames[program].rfind('.'));


if (programs.current == program)
if (fPrograms.current == program)
loadFilename(value); loadFilename(value);
} }


@@ -201,10 +181,10 @@ protected:
float* out1 = outBuffer[0]; float* out1 = outBuffer[0];
float* out2 = outBuffer[1]; float* out2 = outBuffer[1];


if (! doProcess)
if (! fDoProcess)
{ {
lastFrame = timePos->frame;
carla_stderr("P: no process");
fLastFrame = timePos->frame;
carla_zeroFloat(out1, frames); carla_zeroFloat(out1, frames);
carla_zeroFloat(out2, frames); carla_zeroFloat(out2, frames);
return; return;
@@ -213,42 +193,45 @@ protected:
// not playing // not playing
if (! timePos->playing) if (! timePos->playing)
{ {
if (timePos->frame == 0 && lastFrame > 0)
thread.setNeedsRead();
carla_stderr("P: not playing");
fLastFrame = timePos->frame;


lastFrame = timePos->frame;
if (timePos->frame == 0 && fLastFrame > 0)
fThread.setNeedsRead();


carla_zeroFloat(out1, frames); carla_zeroFloat(out1, frames);
carla_zeroFloat(out2, frames); carla_zeroFloat(out2, frames);
return; return;
} }


const CarlaMutex::ScopedLocker sl(&mutex);
fThread.tryPutData(fPool);


// out of reach // out of reach
if (timePos->frame + frames < pool.startFrame || timePos->frame >= maxFrame) /*&& ! loopMode)*/
if (timePos->frame + frames < fPool.startFrame || timePos->frame >= fMaxFrame) /*&& ! loopMode)*/
{ {
lastFrame = timePos->frame;
thread.setNeedsRead();
carla_stderr("P: out of reach");
fLastFrame = timePos->frame;

fThread.setNeedsRead();


carla_zeroFloat(out1, frames); carla_zeroFloat(out1, frames);
carla_zeroFloat(out2, frames); carla_zeroFloat(out2, frames);
return; return;
} }


int64_t poolFrame = (int64_t)timePos->frame - pool.startFrame;
int64_t poolSize = pool.size;
int64_t poolFrame = (int64_t)timePos->frame - fPool.startFrame;
int64_t poolSize = fPool.size;


for (uint32_t i=0; i < frames; ++i, ++poolFrame) for (uint32_t i=0; i < frames; ++i, ++poolFrame)
{ {
if (poolFrame >= 0 && poolFrame < poolSize) if (poolFrame >= 0 && poolFrame < poolSize)
{ {
out1[i] = pool.buffer[0][poolFrame];
out2[i] = pool.buffer[1][poolFrame];
out1[i] = fPool.buffer[0][poolFrame];
out2[i] = fPool.buffer[1][poolFrame];


// reset // reset
pool.buffer[0][poolFrame] = 0.0f;
pool.buffer[1][poolFrame] = 0.0f;
fPool.buffer[0][poolFrame] = 0.0f;
fPool.buffer[1][poolFrame] = 0.0f;
} }
else else
{ {
@@ -257,7 +240,7 @@ protected:
} }
} }


lastFrame = timePos->frame;
fLastFrame = timePos->frame;
} }


// ------------------------------------------------------------------- // -------------------------------------------------------------------
@@ -271,8 +254,8 @@ protected:
if (const char* const filename = uiOpenFile(false, "Open Audio File", "")) if (const char* const filename = uiOpenFile(false, "Open Audio File", ""))
{ {
char fileStr[] = { 'f', 'i', 'l', 'e', '\0', '\0', '\0' }; char fileStr[] = { 'f', 'i', 'l', 'e', '\0', '\0', '\0' };
fileStr[4] = '0' + (programs.current / 10);
fileStr[5] = '0' + (programs.current % 10);
fileStr[4] = '0' + (fPrograms.current / 10);
fileStr[5] = '0' + (fPrograms.current % 10);


uiCustomDataChanged(fileStr, filename); uiCustomDataChanged(fileStr, filename);
} }
@@ -281,62 +264,14 @@ protected:
} }


private: private:
struct Pool {
float* buffer[2];
uint32_t startFrame;
uint32_t size;
bool fLoopMode;
bool fDoProcess;


Pool()
: buffer{nullptr},
startFrame(0),
size(0) {}
uint32_t fLastFrame;
uint32_t fMaxFrame;


~Pool()
{
CARLA_ASSERT(buffer[0] == nullptr);
CARLA_ASSERT(buffer[1] == nullptr);
CARLA_ASSERT(startFrame == 0);
CARLA_ASSERT(size == 0);
}

void create(const uint32_t sampleRate)
{
CARLA_ASSERT(buffer[0] == nullptr);
CARLA_ASSERT(buffer[1] == nullptr);
CARLA_ASSERT(startFrame == 0);
CARLA_ASSERT(size == 0);

size = sampleRate * 6;

buffer[0] = new float[size];
buffer[1] = new float[size];

carla_zeroFloat(buffer[0], size);
carla_zeroFloat(buffer[1], size);
}

void destroy()
{
CARLA_ASSERT(buffer[0] != nullptr);
CARLA_ASSERT(buffer[1] != nullptr);
CARLA_ASSERT(size != 0);

if (buffer[0] != nullptr)
{
delete[] buffer[0];
buffer[0] = nullptr;
}

if (buffer[1] != nullptr)
{
delete[] buffer[1];
buffer[1] = nullptr;
}

startFrame = 0;
size = 0;
}
};
AudioFilePool fPool;
AudioFileThread fThread;


struct Programs { struct Programs {
uint32_t current; uint32_t current;
@@ -345,146 +280,30 @@ private:


Programs() Programs()
: current(0) {} : current(0) {}
};

class AudioFileThread : public QThread
{
public:
AudioFileThread()
: QThread(nullptr),
fNeedsRead(false),
fQuitNow(true),
fLastFrame(0),
fPoolStartFrame(0),
fPoolSize(0)
{
}

~AudioFileThread()
{
CARLA_ASSERT(fQuitNow == true);
CARLA_ASSERT(! isRunning());
}

void stopNow()
{
fNeedsRead = false;
fQuitNow = true;

if (isRunning() && ! wait(1000))
terminate();
}

void readPoll()
{
}

void setNeedsRead()
{
fNeedsRead = true;
}

void setLastFrame(const uint32_t lastFrame)
{
fLastFrame = lastFrame;
}

void setPoolStart(const uint32_t poolStartFrame)
{
fPoolStartFrame = poolStartFrame;
}

void setPoolSize(const uint32_t poolSize)
{
fPoolSize = poolSize;
}

protected:
void run() override
{
while (! fQuitNow)
{
if (fNeedsRead || (fLastFrame >= fPoolStartFrame && fLastFrame - fPoolStartFrame >= fPoolSize*3/4))
readPoll();
else
carla_msleep(50);
}
}

private:
bool fNeedsRead;
bool fQuitNow;

uint32_t fLastFrame;
uint32_t fPoolStartFrame;
uint32_t fPoolSize;
};

void* filePtr;
ADInfo fileNfo;

uint32_t lastFrame;
uint32_t maxFrame;

bool loopMode;
bool doProcess;

Pool pool;
Programs programs;

CarlaMutex mutex;
AudioFileThread thread;
} fPrograms;


void loadFilename(const char* const filename) void loadFilename(const char* const filename)
{ {
// wait for jack processing to end
doProcess = false;

{
const CarlaMutex::ScopedLocker sl(&mutex);

maxFrame = 0;
pool.startFrame = 0;

thread.stopNow();
}

// clear old data
if (filePtr != nullptr)
{
ad_close(filePtr);
filePtr = nullptr;
}

ad_clear_nfo(&fileNfo);
CARLA_ASSERT(filename != nullptr);
carla_debug("AudioFilePlugin::loadFilename(\"%s\")", filename);


if (filename == nullptr) if (filename == nullptr)
return; return;


// open new
filePtr = ad_open(filename, &fileNfo);
fThread.stopNow();


if (filePtr == nullptr)
return;

ad_dump_nfo(99, &fileNfo);

if (fileNfo.frames == 0)
carla_stderr("L: filename \"%s\" has 0 frames", filename);

if ((fileNfo.channels == 1 || fileNfo.channels == 2) && fileNfo.frames > 0)
if (fThread.loadFilename(filename))
{ {
maxFrame = fileNfo.frames;
thread.readPoll();
doProcess = true;

thread.start();
carla_stdout("AudioFilePlugin::loadFilename(\"%s\") - sucess", filename);
fThread.startNow();
fMaxFrame = fThread.getMaxFrame();
fDoProcess = true;
} }
else else
{ {
ad_clear_nfo(&fileNfo);
ad_close(filePtr);
filePtr = nullptr;
carla_stderr("AudioFilePlugin::loadFilename(\"%s\") - failed", filename);
fDoProcess = false;
fMaxFrame = 0;
} }
} }


@@ -496,7 +315,7 @@ private:


static const PluginDescriptor audiofileDesc = { static const PluginDescriptor audiofileDesc = {
/* category */ PLUGIN_CATEGORY_UTILITY, /* category */ PLUGIN_CATEGORY_UTILITY,
/* hints */ static_cast<PluginHints>(PLUGIN_HAS_GUI),
/* hints */ static_cast<PluginHints>(PLUGIN_IS_RTSAFE|PLUGIN_HAS_GUI),
/* audioIns */ 0, /* audioIns */ 0,
/* audioOuts */ 2, /* audioOuts */ 2,
/* midiIns */ 0, /* midiIns */ 0,


+ 1
- 1
source/backend/native/midi-base.hpp View File

@@ -228,4 +228,4 @@ private:
} }
}; };


#endif
#endif // __MIDI_BASE_HPP__

Loading…
Cancel
Save