@@ -29,6 +29,8 @@ | |||
#include "CarlaOscUtils.hpp" | |||
#include "CarlaThread.hpp" | |||
#include "juce_audio_formats.h" | |||
#ifdef BUILD_BRIDGE | |||
# undef HAVE_JUCE_UI | |||
#endif | |||
@@ -44,6 +46,11 @@ using juce::Thread; | |||
namespace CB = CarlaBackend; | |||
using CB::EngineOptions; | |||
using juce::AudioFormat; | |||
using juce::AudioFormatManager; | |||
using juce::String; | |||
using juce::StringArray; | |||
// ------------------------------------------------------------------------------------------------------------------- | |||
// Juce Message Thread | |||
@@ -650,9 +657,17 @@ const char* carla_get_supported_file_extensions() | |||
retText += ";*.gig;*.sfz"; | |||
#endif | |||
// Audio files, FIXME | |||
retText += ";*.aiff;*.flac;*.oga;*.ogg;*.w64;*.wav"; | |||
retText += ";*.3g2;*.3gp;*.aac;*.ac3;*.amr;*.ape;*.mp2;*.mp3;*.mpc;*.wma"; | |||
// Audio files | |||
AudioFormatManager afm; | |||
afm.registerBasicFormats(); | |||
for (AudioFormat **it=afm.begin(), **end=afm.end(); it != end; ++it) | |||
{ | |||
const StringArray& exts((*it)->getFileExtensions()); | |||
for (String *eit=exts.begin(), *end=exts.end(); eit != end; ++eit) | |||
retText += String(";*" + (*eit)).toRawUTF8(); | |||
} | |||
// MIDI files | |||
retText += ";*.mid;*.midi"; | |||
@@ -892,8 +892,7 @@ bool CarlaEngine::loadFile(const char* const filename) | |||
CARLA_SAFE_ASSERT_RETURN_ERR(file.existsAsFile(), "Requested file does not exist or is not a readable file"); | |||
CarlaString baseName(file.getFileName().toRawUTF8()); | |||
CarlaString extension(file.getFileExtension().toRawUTF8()); | |||
extension.toLower(); | |||
CarlaString extension(file.getFileExtension().replace(".","").toLowerCase().toRawUTF8()); | |||
// ------------------------------------------------------------------- | |||
@@ -913,20 +912,7 @@ bool CarlaEngine::loadFile(const char* const filename) | |||
// ------------------------------------------------------------------- | |||
// FIXME | |||
if (extension == "aiff" || extension == "flac" || extension == "oga" || extension == "ogg" || extension == "w64" || extension == "wav") | |||
{ | |||
if (addPlugin(PLUGIN_INTERNAL, nullptr, baseName, "audiofile", 0, nullptr)) | |||
{ | |||
if (CarlaPlugin* const plugin = getPlugin(pData->curPluginCount-1)) | |||
plugin->setCustomData(CUSTOM_DATA_TYPE_STRING, "file", filename, true); | |||
return true; | |||
} | |||
return false; | |||
} | |||
if (extension == "3g2" || extension == "3gp" || extension == "aac" || extension == "ac3" || extension == "amr" || extension == "ape" || | |||
extension == "mp2" || extension == "mp3" || extension == "mpc" || extension == "wma") | |||
if (extension == "aif" || extension == "aiff" || extension == "bwf" || extension == "flac" || extension == "ogg" || extension == "wav") | |||
{ | |||
if (addPlugin(PLUGIN_INTERNAL, nullptr, baseName, "audiofile", 0, nullptr)) | |||
{ | |||
@@ -17,14 +17,14 @@ | |||
If your app doesn't need to read FLAC files, you might want to disable this to | |||
reduce the size of your codebase and build time. | |||
*/ | |||
#define JUCE_USE_FLAC 0 | |||
#define JUCE_USE_FLAC 1 | |||
/** Config: JUCE_USE_OGGVORBIS | |||
Enables the Ogg-Vorbis audio codec classes (available on all platforms). | |||
If your app doesn't need to read Ogg-Vorbis files, you might want to disable this to | |||
reduce the size of your codebase and build time. | |||
*/ | |||
#define JUCE_USE_OGGVORBIS 0 | |||
#define JUCE_USE_OGGVORBIS 1 | |||
/** Config: JUCE_USE_MP3AUDIOFORMAT | |||
Enables the software-based MP3AudioFormat class. | |||
@@ -43,7 +43,7 @@ | |||
/** Config: JUCE_USE_LAME_AUDIO_FORMAT | |||
Enables the LameEncoderAudioFormat class. | |||
*/ | |||
#define JUCE_USE_LAME_AUDIO_FORMAT 0 | |||
#define JUCE_USE_LAME_AUDIO_FORMAT 1 | |||
/** Config: JUCE_USE_WINDOWS_MEDIA_FORMAT | |||
Enables the Windows Media SDK codecs. | |||
@@ -20,14 +20,9 @@ | |||
#include "CarlaThread.hpp" | |||
#include "juce_audio_basics.h" | |||
using juce::FloatVectorOperations; | |||
extern "C" { | |||
#include "audio_decoder/ad.h" | |||
} | |||
#include "juce_audio_formats.h" | |||
typedef struct adinfo ADInfo; | |||
using juce::FloatVectorOperations; | |||
struct AudioFilePool { | |||
float* buffer[2]; | |||
@@ -16,37 +16,53 @@ | |||
*/ | |||
#include "CarlaNative.hpp" | |||
#include "CarlaMutex.hpp" | |||
#include "CarlaString.hpp" | |||
#include "audio-base.hpp" | |||
#include "juce_audio_formats.h" | |||
using juce::AudioFormat; | |||
using juce::AudioFormatManager; | |||
using juce::AudioFormatReader; | |||
using juce::AudioFormatReaderSource; | |||
using juce::AudioSampleBuffer; | |||
using juce::BufferingAudioReader; | |||
using juce::File; | |||
using juce::FloatVectorOperations; | |||
using juce::MemoryMappedAudioFormatReader; | |||
using juce::ScopedPointer; | |||
using juce::TimeSliceThread; | |||
#define PROGRAM_COUNT 16 | |||
// ----------------------------------------------------------------------- | |||
class AudioFilePlugin : public NativePluginClass, | |||
public AbstractAudioPlayer | |||
static AudioFormatManager& getAudioFormatManagerInstance() | |||
{ | |||
static AudioFormatManager afm; | |||
afm.registerBasicFormats(); | |||
return afm; | |||
} | |||
// ----------------------------------------------------------------------- | |||
class AudioFilePlugin : public NativePluginClass | |||
{ | |||
public: | |||
AudioFilePlugin(const NativeHostDescriptor* const host) | |||
: NativePluginClass(host), | |||
AbstractAudioPlayer(), | |||
fLoopMode(false), | |||
fDoProcess(false), | |||
fLastFrame(0), | |||
fMaxFrame(0), | |||
fThread(this, getSampleRate()) | |||
fLength(0) | |||
//fThread("AudioFilePluginThread") | |||
{ | |||
fPool.create((uint32_t)getSampleRate()); | |||
fReaderBuffer.setSize(2, getBufferSize()); | |||
} | |||
~AudioFilePlugin() override | |||
{ | |||
fPool.destroy(); | |||
fThread.stopNow(); | |||
} | |||
uint64_t getLastFrame() const override | |||
{ | |||
return fLastFrame; | |||
//fThread.stopThread(-1); | |||
fReader = nullptr; | |||
fReaderSource = nullptr; | |||
} | |||
protected: | |||
@@ -55,7 +71,7 @@ protected: | |||
uint32_t getParameterCount() const override | |||
{ | |||
return 0; // TODO - loopMode | |||
return 1; | |||
} | |||
const NativeParameter* getParameterInfo(const uint32_t index) const override | |||
@@ -96,21 +112,28 @@ protected: | |||
if (index != 0) | |||
return; | |||
bool b = (value > 0.5f); | |||
const bool loopMode(value > 0.5f); | |||
if (b == fLoopMode) | |||
if (fLoopMode == loopMode) | |||
return; | |||
fLoopMode = b; | |||
fThread.setNeedsRead(); | |||
fLoopMode = loopMode; | |||
const CarlaMutexLocker cml(fReaderMutex); | |||
if (fReaderSource != nullptr) | |||
fReaderSource->setLooping(loopMode); | |||
} | |||
void setCustomData(const char* const key, const char* const value) override | |||
{ | |||
CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',); | |||
CARLA_SAFE_ASSERT_RETURN(value != nullptr && value[0] != '\0',); | |||
if (std::strcmp(key, "file") != 0) | |||
return; | |||
loadFilename(value); | |||
_loadAudioFile(value); | |||
} | |||
// ------------------------------------------------------------------- | |||
@@ -120,71 +143,46 @@ protected: | |||
{ | |||
const NativeTimeInfo* const timePos(getTimeInfo()); | |||
float* out1 = outBuffer[0]; | |||
float* out2 = outBuffer[1]; | |||
float* const out1(outBuffer[0]); | |||
float* const out2(outBuffer[1]); | |||
if (! fDoProcess) | |||
if (fLength == 0 || ! fDoProcess) | |||
{ | |||
//carla_stderr("P: no process"); | |||
fLastFrame = timePos->frame; | |||
FloatVectorOperations::clear(out1, frames); | |||
FloatVectorOperations::clear(out2, frames); | |||
return; | |||
} | |||
const int64_t nextReadPos(fLoopMode ? (timePos->frame % fLength) : timePos->frame); | |||
// not playing | |||
if (! timePos->playing) | |||
{ | |||
//carla_stderr("P: not playing"); | |||
fLastFrame = timePos->frame; | |||
if (timePos->frame == 0 && fLastFrame > 0) | |||
fThread.setNeedsRead(); | |||
FloatVectorOperations::clear(out1, frames); | |||
FloatVectorOperations::clear(out2, frames); | |||
return; | |||
} | |||
fThread.tryPutData(fPool); | |||
// out of reach | |||
if (timePos->frame + frames < fPool.startFrame || timePos->frame >= fMaxFrame) /*&& ! loopMode)*/ | |||
{ | |||
//carla_stderr("P: out of reach"); | |||
fLastFrame = timePos->frame; | |||
const CarlaMutexLocker cml(fReaderMutex); | |||
if (timePos->frame + frames < fPool.startFrame) | |||
fThread.setNeedsRead(); | |||
if (fReaderSource != nullptr) | |||
fReaderSource->setNextReadPosition(nextReadPos); | |||
FloatVectorOperations::clear(out1, frames); | |||
FloatVectorOperations::clear(out2, frames); | |||
return; | |||
} | |||
int64_t poolFrame = (int64_t)timePos->frame - fPool.startFrame; | |||
int64_t poolSize = fPool.size; | |||
const CarlaMutexLocker cml(fReaderMutex); | |||
for (uint32_t i=0; i < frames; ++i, ++poolFrame) | |||
{ | |||
if (poolFrame >= 0 && poolFrame < poolSize) | |||
{ | |||
out1[i] = fPool.buffer[0][poolFrame]; | |||
out2[i] = fPool.buffer[1][poolFrame]; | |||
// reset | |||
fPool.buffer[0][poolFrame] = 0.0f; | |||
fPool.buffer[1][poolFrame] = 0.0f; | |||
} | |||
else | |||
{ | |||
out1[i] = 0.0f; | |||
out2[i] = 0.0f; | |||
} | |||
} | |||
if (fReaderSource != nullptr) | |||
fReaderSource->setNextReadPosition(nextReadPos); | |||
if (fReader == nullptr) | |||
return; | |||
fLastFrame = timePos->frame; | |||
fReader->read(&fReaderBuffer, 0, frames, nextReadPos, true, true); | |||
FloatVectorOperations::copy(out1, fReaderBuffer.getReadPointer(0), frames); | |||
FloatVectorOperations::copy(out2, fReaderBuffer.getReadPointer(1), frames); | |||
} | |||
// ------------------------------------------------------------------- | |||
@@ -201,41 +199,84 @@ protected: | |||
uiClosed(); | |||
} | |||
// ------------------------------------------------------------------- | |||
// Plugin dispatcher calls | |||
void bufferSizeChanged(const uint32_t bufferSize) override | |||
{ | |||
fReaderBuffer.setSize(2, bufferSize); | |||
} | |||
private: | |||
bool fLoopMode; | |||
bool fDoProcess; | |||
int64_t fLength; | |||
uint64_t fLastFrame; | |||
uint32_t fMaxFrame; | |||
//TimeSliceThread fThread; | |||
AudioFilePool fPool; | |||
AudioFileThread fThread; | |||
AudioSampleBuffer fReaderBuffer; | |||
CarlaMutex fReaderMutex; | |||
void loadFilename(const char* const filename) | |||
ScopedPointer<AudioFormatReader> fReader; | |||
ScopedPointer<AudioFormatReaderSource> fReaderSource; | |||
void _loadAudioFile(const char* const filename) | |||
{ | |||
CARLA_ASSERT(filename != nullptr); | |||
carla_debug("AudioFilePlugin::loadFilename(\"%s\")", filename); | |||
carla_stdout("AudioFilePlugin::loadFilename(\"%s\")", filename); | |||
fDoProcess = false; | |||
fLength = 0; | |||
fThread.stopNow(); | |||
//fThread.stopThread(-1); | |||
if (filename == nullptr || *filename == '\0') | |||
{ | |||
fDoProcess = false; | |||
fMaxFrame = 0; | |||
return; | |||
fReaderMutex.lock(); | |||
AudioFormatReader* const reader(fReader.release()); | |||
AudioFormatReaderSource* const readerSource(fReaderSource.release()); | |||
fReaderMutex.unlock(); | |||
delete readerSource; | |||
delete reader; | |||
} | |||
if (fThread.loadFilename(filename)) | |||
File file(filename); | |||
if (! file.existsAsFile()) | |||
return; | |||
AudioFormatManager& afm(getAudioFormatManagerInstance()); | |||
AudioFormat* const format(afm.findFormatForFileExtension(file.getFileExtension())); | |||
CARLA_SAFE_ASSERT_RETURN(format != nullptr,); | |||
if (MemoryMappedAudioFormatReader* const memReader = format->createMemoryMappedReader(file)) | |||
{ | |||
fThread.startNow(); | |||
fMaxFrame = fThread.getMaxFrame(); | |||
fDoProcess = true; | |||
memReader->mapEntireFile(); | |||
fReader = memReader; | |||
carla_stdout("Using memory mapped read file"); | |||
} | |||
else | |||
{ | |||
fDoProcess = false; | |||
fMaxFrame = 0; | |||
AudioFormatReader* const reader(afm.createReaderFor(file)); | |||
CARLA_SAFE_ASSERT_RETURN(reader != nullptr,); | |||
// this code can be used for very large files | |||
//fThread.startThread(); | |||
//BufferingAudioReader* const bufferingReader(new BufferingAudioReader(reader, fThread, getSampleRate()*2)); | |||
//bufferingReader->setReadTimeout(50); | |||
AudioFormatReaderSource* const readerSource(new AudioFormatReaderSource(/*bufferingReader*/reader, false)); | |||
readerSource->setLooping(fLoopMode); | |||
fReaderSource = readerSource; | |||
fReader = reader; | |||
carla_stdout("Using regular read file"); | |||
} | |||
fLength = fReader->lengthInSamples; | |||
fDoProcess = true; | |||
} | |||
PluginClassEND(AudioFilePlugin) | |||
@@ -246,13 +287,13 @@ private: | |||
static const NativePluginDescriptor audiofileDesc = { | |||
/* category */ PLUGIN_CATEGORY_UTILITY, | |||
/* hints */ static_cast<NativePluginHints>(PLUGIN_IS_RTSAFE|PLUGIN_HAS_UI|PLUGIN_NEEDS_UI_OPEN_SAVE), | |||
/* hints */ static_cast<NativePluginHints>(PLUGIN_HAS_UI|PLUGIN_NEEDS_UI_OPEN_SAVE), | |||
/* supports */ static_cast<NativePluginSupports>(0x0), | |||
/* audioIns */ 0, | |||
/* audioOuts */ 2, | |||
/* midiIns */ 0, | |||
/* midiOuts */ 0, | |||
/* paramIns */ 0, // TODO - loopMode | |||
/* paramIns */ 1, | |||
/* paramOuts */ 0, | |||
/* name */ "Audio File", | |||
/* label */ "audiofile", | |||
@@ -18,7 +18,9 @@ | |||
#include "CarlaNative.hpp" | |||
#include "midi-base.hpp" | |||
#include <smf.h> | |||
#include "juce_audio_basics.h" | |||
// ----------------------------------------------------------------------- | |||
class MidiFilePlugin : public NativePluginClass, | |||
public AbstractMidiPlayer | |||
@@ -27,13 +29,7 @@ public: | |||
MidiFilePlugin(const NativeHostDescriptor* const host) | |||
: NativePluginClass(host), | |||
fMidiOut(this), | |||
fWasPlayingBefore(false) | |||
{ | |||
} | |||
~MidiFilePlugin() override | |||
{ | |||
} | |||
fWasPlayingBefore(false) {} | |||
protected: | |||
// ------------------------------------------------------------------- | |||
@@ -41,8 +37,8 @@ protected: | |||
void setCustomData(const char* const key, const char* const value) override | |||
{ | |||
CARLA_ASSERT(key != nullptr); | |||
CARLA_ASSERT(value != nullptr); | |||
CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',); | |||
CARLA_SAFE_ASSERT_RETURN(value != nullptr && value[0] != '\0',); | |||
if (std::strcmp(key, "file") != 0) | |||
return; | |||
@@ -127,88 +123,71 @@ private: | |||
{ | |||
fMidiOut.clear(); | |||
if (smf_t* const smf = smf_load(filename)) | |||
using juce::File; | |||
using juce::FileInputStream; | |||
using juce::MidiFile; | |||
using juce::MidiMessage; | |||
using juce::MidiMessageSequence; | |||
using juce::ScopedPointer; | |||
File file(filename); | |||
if (! file.existsAsFile()) | |||
return; | |||
FileInputStream fileStream(file); | |||
MidiFile midiFile; | |||
if (! midiFile.readFrom(fileStream)) | |||
return; | |||
midiFile.convertTimestampTicksToSeconds(); | |||
const double sampleRate(getSampleRate()); | |||
for (int i=0, numTracks = midiFile.getNumTracks(); i<numTracks; ++i) | |||
{ | |||
smf_event_t* event; | |||
const double sampleRate(getSampleRate()); | |||
const MidiMessageSequence* const track(midiFile.getTrack(i)); | |||
CARLA_SAFE_ASSERT_CONTINUE(track != nullptr); | |||
while ((event = smf_get_next_event(smf)) != nullptr) | |||
for (int j=0, numEvents = track->getNumEvents(); j<numEvents; ++j) | |||
{ | |||
if (smf_event_is_valid(event) == 0) | |||
const MidiMessageSequence::MidiEventHolder* const midiEventHolder(track->getEventPointer(j)); | |||
CARLA_SAFE_ASSERT_CONTINUE(midiEventHolder != nullptr); | |||
const MidiMessage& midiMessage(midiEventHolder->message); | |||
//const double time(track->getEventTime(i)*sampleRate); | |||
const int dataSize(midiMessage.getRawDataSize()); | |||
if (dataSize <= 0 || dataSize > MAX_EVENT_DATA_SIZE) | |||
continue; | |||
if (midiMessage.isActiveSense()) | |||
continue; | |||
if (midiMessage.isMetaEvent()) | |||
continue; | |||
if (midiMessage.isMidiStart()) | |||
continue; | |||
if (smf_event_is_metadata(event)) | |||
if (midiMessage.isMidiContinue()) | |||
continue; | |||
if (smf_event_is_system_realtime(event)) | |||
if (midiMessage.isMidiStop()) | |||
continue; | |||
if (smf_event_is_system_common(event)) | |||
if (midiMessage.isMidiClock()) | |||
continue; | |||
if (event->midi_buffer_length <= 0 || event->midi_buffer_length > MAX_EVENT_DATA_SIZE) | |||
if (midiMessage.isSongPositionPointer()) | |||
continue; | |||
if (midiMessage.isQuarterFrame()) | |||
continue; | |||
if (midiMessage.isFullFrame()) | |||
continue; | |||
if (midiMessage.isMidiMachineControlMessage()) | |||
continue; | |||
if (midiMessage.isSysEx()) | |||
continue; | |||
const uint64_t time(uint64_t(event->time_seconds*sampleRate)); | |||
#if 1 | |||
fMidiOut.addRaw(time, event->midi_buffer, uint8_t(event->midi_buffer_length)); | |||
#else | |||
const uint8_t status = MIDI_GET_STATUS_FROM_DATA(event->midi_buffer); | |||
const uint8_t channel = MIDI_GET_CHANNEL_FROM_DATA(event->midi_buffer); | |||
uint8_t nextBank[MAX_MIDI_CHANNELS] = { 0 }; | |||
if (MIDI_IS_STATUS_NOTE_OFF(status)) | |||
{ | |||
const uint8_t note = event->midi_buffer[1]; | |||
const uint8_t velo = event->midi_buffer[2]; | |||
fMidiOut.addNoteOff(time, channel, note, velo); | |||
} | |||
else if (MIDI_IS_STATUS_NOTE_ON(status)) | |||
{ | |||
const uint8_t note = event->midi_buffer[1]; | |||
const uint8_t velo = event->midi_buffer[2]; | |||
fMidiOut.addNoteOn(time, channel, note, velo); | |||
} | |||
else if (MIDI_IS_STATUS_POLYPHONIC_AFTERTOUCH(status)) | |||
{ | |||
const uint8_t note = event->midi_buffer[1]; | |||
const uint8_t pressure = event->midi_buffer[2]; | |||
fMidiOut.addNoteAftertouch(time, channel, note, pressure); | |||
} | |||
else if (MIDI_IS_STATUS_CONTROL_CHANGE(status)) | |||
{ | |||
const uint8_t control = event->midi_buffer[1]; | |||
const uint8_t value = (event->midi_buffer_length > 2) ? event->midi_buffer[2] : 0; | |||
if (MIDI_IS_CONTROL_BANK_SELECT(control)) | |||
nextBank[channel] = value; | |||
else | |||
fMidiOut.addControl(time, channel, control, value); | |||
} | |||
else if (MIDI_IS_STATUS_PROGRAM_CHANGE(status)) | |||
{ | |||
const uint8_t program = event->midi_buffer[1]; | |||
fMidiOut.addProgram(time, channel, nextBank[channel], program); | |||
} | |||
else if (MIDI_IS_STATUS_AFTERTOUCH(status)) | |||
{ | |||
const uint8_t pressure = event->midi_buffer[1]; | |||
fMidiOut.addChannelPressure(time, channel, pressure); | |||
} | |||
else if (MIDI_IS_STATUS_PITCH_WHEEL_CONTROL(status)) | |||
{ | |||
const uint8_t lsb = event->midi_buffer[1]; | |||
const uint8_t msb = event->midi_buffer[2]; | |||
fMidiOut.addPitchbend(time, channel, lsb, msb); | |||
} | |||
#endif | |||
} | |||
const double time(midiMessage.getTimeStamp()*sampleRate); | |||
smf_delete(smf); | |||
fMidiOut.addRaw(time, midiMessage.getRawData(), static_cast<uint8_t>(dataSize)); | |||
} | |||
} | |||
} | |||