| @@ -798,8 +798,8 @@ public: | |||
| static CarlaPlugin* newLV2(const Initializer& init); | |||
| static CarlaPlugin* newVST(const Initializer& init); | |||
| static CarlaPlugin* newGIG(const Initializer& init); | |||
| static CarlaPlugin* newSF2(const Initializer& init, const bool use16Outs); | |||
| static CarlaPlugin* newSFZ(const Initializer& init); | |||
| static CarlaPlugin* newSF2(const Initializer& init); | |||
| // ------------------------------------------------------------------- | |||
| @@ -713,7 +713,7 @@ bool CarlaEngine::addPlugin(const BinaryType btype, const PluginType ptype, cons | |||
| break; | |||
| case PLUGIN_SF2: | |||
| plugin = CarlaPlugin::newSF2(init); | |||
| plugin = CarlaPlugin::newSF2(init, (extra != nullptr)); | |||
| break; | |||
| case PLUGIN_SFZ: | |||
| @@ -49,7 +49,7 @@ public: | |||
| kClient(client), | |||
| kPort(port) | |||
| { | |||
| qDebug("CarlaEngineJackAudioPort::CarlaEngineJackAudioPort(%s, %s, %p, %p)", bool2str(isInput), ProcessMode2Str(processMode), client, port); | |||
| carla_debug("CarlaEngineJackAudioPort::CarlaEngineJackAudioPort(%s, %s, %p, %p)", bool2str(isInput), ProcessMode2Str(processMode), client, port); | |||
| if (processMode == PROCESS_MODE_SINGLE_CLIENT || processMode == PROCESS_MODE_MULTIPLE_CLIENTS) | |||
| { | |||
| @@ -63,7 +63,7 @@ public: | |||
| ~CarlaEngineJackAudioPort() | |||
| { | |||
| qDebug("CarlaEngineJackAudioPort::~CarlaEngineJackAudioPort()"); | |||
| carla_debug("CarlaEngineJackAudioPort::~CarlaEngineJackAudioPort()"); | |||
| if (kClient != nullptr && kPort != nullptr) | |||
| jackbridge_port_unregister(kClient, kPort); | |||
| @@ -111,7 +111,7 @@ public: | |||
| kPort(port), | |||
| fJackBuffer(nullptr) | |||
| { | |||
| qDebug("CarlaEngineJackEventPort::CarlaEngineJackEventPort(%s, %s, %p, %p)", bool2str(isInput), ProcessMode2Str(processMode), client, port); | |||
| carla_debug("CarlaEngineJackEventPort::CarlaEngineJackEventPort(%s, %s, %p, %p)", bool2str(isInput), ProcessMode2Str(processMode), client, port); | |||
| if (processMode == PROCESS_MODE_SINGLE_CLIENT || processMode == PROCESS_MODE_MULTIPLE_CLIENTS) | |||
| { | |||
| @@ -125,7 +125,7 @@ public: | |||
| ~CarlaEngineJackEventPort() | |||
| { | |||
| qDebug("CarlaEngineJackEventPort::~CarlaEngineJackEventPort()"); | |||
| carla_debug("CarlaEngineJackEventPort::~CarlaEngineJackEventPort()"); | |||
| if (kClient != nullptr && kPort != nullptr) | |||
| jackbridge_port_unregister(kClient, kPort); | |||
| @@ -368,7 +368,7 @@ public: | |||
| kClient(client), | |||
| kUseClient(processMode == PROCESS_MODE_SINGLE_CLIENT || processMode == PROCESS_MODE_MULTIPLE_CLIENTS) | |||
| { | |||
| qDebug("CarlaEngineJackClient::CarlaEngineJackClient(%s, %s, %p)", EngineType2Str(engineType), ProcessMode2Str(processMode), client); | |||
| carla_debug("CarlaEngineJackClient::CarlaEngineJackClient(%s, %s, %p)", EngineType2Str(engineType), ProcessMode2Str(processMode), client); | |||
| if (kUseClient) | |||
| { | |||
| @@ -382,7 +382,7 @@ public: | |||
| ~CarlaEngineJackClient() | |||
| { | |||
| qDebug("CarlaEngineClient::~CarlaEngineClient()"); | |||
| carla_debug("CarlaEngineClient::~CarlaEngineClient()"); | |||
| if (kProcessMode == PROCESS_MODE_MULTIPLE_CLIENTS) | |||
| { | |||
| @@ -393,7 +393,7 @@ public: | |||
| void activate() | |||
| { | |||
| qDebug("CarlaEngineJackClient::activate()"); | |||
| carla_debug("CarlaEngineJackClient::activate()"); | |||
| if (kProcessMode == PROCESS_MODE_MULTIPLE_CLIENTS) | |||
| { | |||
| @@ -408,7 +408,7 @@ public: | |||
| void deactivate() | |||
| { | |||
| qDebug("CarlaEngineJackClient::deactivate()"); | |||
| carla_debug("CarlaEngineJackClient::deactivate()"); | |||
| if (kProcessMode == PROCESS_MODE_MULTIPLE_CLIENTS) | |||
| { | |||
| @@ -423,7 +423,7 @@ public: | |||
| bool isOk() const | |||
| { | |||
| qDebug("CarlaEngineJackClient::isOk()"); | |||
| carla_debug("CarlaEngineJackClient::isOk()"); | |||
| if (kUseClient) | |||
| return bool(kClient); | |||
| @@ -441,7 +441,7 @@ public: | |||
| const CarlaEnginePort* addPort(const EnginePortType portType, const char* const name, const bool isInput) | |||
| { | |||
| qDebug("CarlaEngineJackClient::addPort(%s, \"%s\", %s)", EnginePortType2Str(portType), name, bool2str(isInput)); | |||
| carla_debug("CarlaEngineJackClient::addPort(%s, \"%s\", %s)", EnginePortType2Str(portType), name, bool2str(isInput)); | |||
| jack_port_t* port = nullptr; | |||
| @@ -500,7 +500,7 @@ public: | |||
| #endif | |||
| fFreewheel(false) | |||
| { | |||
| qDebug("CarlaEngineJack::CarlaEngineJack()"); | |||
| carla_debug("CarlaEngineJack::CarlaEngineJack()"); | |||
| #ifdef BUILD_BRIDGE | |||
| fOptions.processMode = PROCESS_MODE_MULTIPLE_CLIENTS; | |||
| @@ -511,7 +511,7 @@ public: | |||
| ~CarlaEngineJack() | |||
| { | |||
| qDebug("CarlaEngineJack::~CarlaEngineJack()"); | |||
| carla_debug("CarlaEngineJack::~CarlaEngineJack()"); | |||
| CARLA_ASSERT(fClient == nullptr); | |||
| } | |||
| @@ -539,7 +539,7 @@ public: | |||
| bool init(const char* const clientName) | |||
| { | |||
| qDebug("CarlaEngineJack::init(\"%s\")", clientName); | |||
| carla_debug("CarlaEngineJack::init(\"%s\")", clientName); | |||
| fFreewheel = false; | |||
| fTransportState = JackTransportStopped; | |||
| @@ -607,7 +607,7 @@ public: | |||
| bool close() | |||
| { | |||
| qDebug("CarlaEngineJack::close()"); | |||
| carla_debug("CarlaEngineJack::close()"); | |||
| CarlaEngine::close(); | |||
| #ifdef BUILD_BRIDGE | |||
| @@ -53,10 +53,10 @@ public: | |||
| { | |||
| showGui(false); | |||
| // Wait a bit first, try safe quit, then force kill | |||
| // Wait a bit first, then force kill | |||
| if (kData->osc.thread.isRunning() && ! kData->osc.thread.stop(kData->engine->getOptions().oscUiTimeout)) | |||
| { | |||
| carla_stderr("Failed to properly stop DSSI GUI thread"); | |||
| carla_stderr("DSSI GUI thread still running, forcing termination now"); | |||
| kData->osc.thread.terminate(); | |||
| } | |||
| } | |||
| @@ -1170,7 +1170,7 @@ public: | |||
| const EngineMidiEvent& midiEvent = event.midi; | |||
| uint8_t status = MIDI_GET_STATUS_FROM_DATA(midiEvent.data); | |||
| uint8_t channel = MIDI_GET_CHANNEL_FROM_DATA(midiEvent.data); | |||
| uint8_t channel = event.channel; | |||
| // Fix bad note-off (per DSSI spec) | |||
| if (MIDI_IS_STATUS_NOTE_ON(status) && midiEvent.data[2] == 0) | |||
| @@ -28,15 +28,22 @@ CARLA_BACKEND_START_NAMESPACE | |||
| class FluidSynthPlugin : public CarlaPlugin | |||
| { | |||
| public: | |||
| FluidSynthPlugin(CarlaEngine* const engine, const unsigned int id) | |||
| : CarlaPlugin(engine, id) | |||
| FluidSynthPlugin(CarlaEngine* const engine, const unsigned int id, const bool use16Outs) | |||
| : CarlaPlugin(engine, id), | |||
| kUses16Outs(use16Outs), | |||
| fSettings(nullptr), | |||
| fSynth(nullptr), | |||
| fSynthId(-1), | |||
| fAudio16Buffers(nullptr) | |||
| { | |||
| carla_debug("FluidSynthPlugin::FluidSynthPlugin()"); | |||
| carla_debug("FluidSynthPlugin::FluidSynthPlugin(%p, %i, %s)", engine, id, bool2str(use16Outs)); | |||
| // create settings | |||
| fSettings = new_fluid_settings(); | |||
| // define settings | |||
| fluid_settings_setint(fSettings, "synth.audio-channels", use16Outs ? 16 : 1); | |||
| fluid_settings_setint(fSettings, "synth.audio-groups", use16Outs ? 16 : 1); | |||
| fluid_settings_setnum(fSettings, "synth.sample-rate", kData->engine->getSampleRate()); | |||
| fluid_settings_setint(fSettings, "synth.threadsafe-api ", 0); | |||
| @@ -66,6 +73,8 @@ public: | |||
| delete_fluid_synth(fSynth); | |||
| delete_fluid_settings(fSettings); | |||
| deleteBuffers(); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| @@ -400,7 +409,7 @@ public: | |||
| deleteBuffers(); | |||
| uint32_t aOuts, params, j; | |||
| aOuts = 2; | |||
| aOuts = kUses16Outs ? 32 : 2; | |||
| params = FluidSynthParametersMax; | |||
| kData->audioOut.createNew(aOuts); | |||
| @@ -412,7 +421,44 @@ public: | |||
| // --------------------------------------- | |||
| // Audio Outputs | |||
| if (kUses16Outs) | |||
| { | |||
| for (j=0; j < 32; j++) | |||
| { | |||
| portName.clear(); | |||
| if (processMode == PROCESS_MODE_SINGLE_CLIENT) | |||
| { | |||
| portName = fName; | |||
| portName += ":"; | |||
| } | |||
| portName += "out-"; | |||
| if ((j+2)/2 < 9) | |||
| portName += "0"; | |||
| portName += CarlaString((j+2)/2); | |||
| if (j % 2 == 0) | |||
| portName += "L"; | |||
| else | |||
| portName += "R"; | |||
| portName.truncate(portNameSize); | |||
| kData->audioOut.ports[j].port = (CarlaEngineAudioPort*)kData->client->addPort(kEnginePortTypeAudio, portName, false); | |||
| kData->audioOut.ports[j].rindex = j; | |||
| } | |||
| fAudio16Buffers = new float*[aOuts]; | |||
| for (j=0; j < aOuts; j++) | |||
| fAudio16Buffers[j] = nullptr; | |||
| } | |||
| else | |||
| { | |||
| // out-left | |||
| portName.clear(); | |||
| if (processMode == PROCESS_MODE_SINGLE_CLIENT) | |||
| @@ -426,9 +472,8 @@ public: | |||
| kData->audioOut.ports[0].port = (CarlaEngineAudioPort*)kData->client->addPort(kEnginePortTypeAudio, portName, false); | |||
| kData->audioOut.ports[0].rindex = 0; | |||
| } | |||
| { | |||
| // out-right | |||
| portName.clear(); | |||
| if (processMode == PROCESS_MODE_SINGLE_CLIENT) | |||
| @@ -714,6 +759,7 @@ public: | |||
| fHints |= PLUGIN_CAN_BALANCE; | |||
| fHints |= PLUGIN_CAN_FORCE_STEREO; | |||
| bufferSizeChanged(kData->engine->getBufferSize()); | |||
| reloadPrograms(true); | |||
| kData->client->activate(); | |||
| @@ -819,7 +865,6 @@ public: | |||
| void process(float** const, float** const outBuffer, const uint32_t frames, const uint32_t framesOffset) | |||
| { | |||
| uint32_t i, k; | |||
| uint32_t midiEventCount = 0; | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| // Check if active | |||
| @@ -871,8 +916,6 @@ public: | |||
| fluid_synth_noteon(fSynth, note.channel, note.note, note.velo); | |||
| else | |||
| fluid_synth_noteoff(fSynth,note.channel, note.note); | |||
| midiEventCount += 1; | |||
| } | |||
| kData->extNotes.mutex.unlock(); | |||
| @@ -905,7 +948,11 @@ public: | |||
| if (time > timeOffset) | |||
| { | |||
| fluid_synth_write_float(fSynth, time - timeOffset, outBuffer[0] + timeOffset, 0, 1, outBuffer[1] + timeOffset, 0, 1); | |||
| if (kUses16Outs) | |||
| processSingle(outBuffer, time - timeOffset, timeOffset); | |||
| else | |||
| fluid_synth_write_float(fSynth, time - timeOffset, outBuffer[0] + timeOffset, 0, 1, outBuffer[1] + timeOffset, 0, 1); | |||
| timeOffset = time; | |||
| } | |||
| @@ -1011,7 +1058,7 @@ public: | |||
| } | |||
| case kEngineControlEventTypeMidiBank: | |||
| if (event.channel < 16 && event.channel != 9) // FIXME | |||
| if (event.channel < 16) | |||
| nextBankIds[event.channel] = ctrlEvent.param; | |||
| break; | |||
| @@ -1068,13 +1115,10 @@ public: | |||
| case kEngineEventTypeMidi: | |||
| { | |||
| if (midiEventCount >= MAX_MIDI_EVENTS) | |||
| continue; | |||
| const EngineMidiEvent& midiEvent = event.midi; | |||
| uint8_t status = MIDI_GET_STATUS_FROM_DATA(midiEvent.data); | |||
| uint8_t channel = MIDI_GET_CHANNEL_FROM_DATA(midiEvent.data); | |||
| uint8_t channel = event.channel; | |||
| // Fix bad note-off | |||
| if (MIDI_IS_STATUS_NOTE_ON(status) && midiEvent.data[2] == 0) | |||
| @@ -1103,8 +1147,18 @@ public: | |||
| const uint8_t pressure = midiEvent.data[2]; | |||
| // TODO, not in fluidsynth API? | |||
| Q_UNUSED(note); | |||
| Q_UNUSED(pressure); | |||
| continue; | |||
| // unused | |||
| (void)note; | |||
| (void)pressure; | |||
| } | |||
| else if (MIDI_IS_STATUS_CONTROL_CHANGE(status) && (fHints & PLUGIN_OPTION_SELF_AUTOMATION) != 0) | |||
| { | |||
| const uint8_t control = midiEvent.data[1]; | |||
| const uint8_t value = midiEvent.data[2]; | |||
| fluid_synth_cc(fSynth, channel, control, value); | |||
| } | |||
| else if (MIDI_IS_STATUS_AFTERTOUCH(status)) | |||
| { | |||
| @@ -1122,8 +1176,6 @@ public: | |||
| else | |||
| continue; | |||
| midiEventCount += 1; | |||
| break; | |||
| } | |||
| } | |||
| @@ -1132,7 +1184,12 @@ public: | |||
| kData->postRtEvents.trySplice(); | |||
| if (frames > timeOffset) | |||
| fluid_synth_write_float(fSynth, frames - timeOffset, outBuffer[0] + timeOffset, 0, 1, outBuffer[1] + timeOffset, 0, 1); | |||
| { | |||
| if (kUses16Outs) | |||
| processSingle(outBuffer, frames - timeOffset, timeOffset); | |||
| else | |||
| fluid_synth_write_float(fSynth, frames - timeOffset, outBuffer[0] + timeOffset, 0, 1, outBuffer[1] + timeOffset, 0, 1); | |||
| } | |||
| } // End of Event Input and Processing | |||
| @@ -1208,6 +1265,60 @@ public: | |||
| kData->activeBefore = kData->active; | |||
| } | |||
| void processSingle(float** const outBuffer, const uint32_t frames, const uint32_t timeOffset) | |||
| { | |||
| for (uint32_t i=0; i < kData->audioOut.count; i++) | |||
| carla_zeroFloat(fAudio16Buffers[i], frames); | |||
| fluid_synth_process(fSynth, frames, 0, nullptr, kData->audioOut.count, fAudio16Buffers); | |||
| for (uint32_t i=0, k; i < kData->audioOut.count; i++) | |||
| { | |||
| for (k=0; k < frames; k++) | |||
| outBuffer[i][k+timeOffset] = fAudio16Buffers[i][k]; | |||
| } | |||
| } | |||
| void bufferSizeChanged(const uint32_t newBufferSize) | |||
| { | |||
| if (! kUses16Outs) | |||
| return; | |||
| for (uint32_t i=0; i < kData->audioOut.count; i++) | |||
| { | |||
| if (fAudio16Buffers[i] != nullptr) | |||
| delete[] fAudio16Buffers[i]; | |||
| fAudio16Buffers[i] = new float[newBufferSize]; | |||
| } | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // Cleanup | |||
| void deleteBuffers() | |||
| { | |||
| carla_debug("FluidSynthPlugin::deleteBuffers() - start"); | |||
| if (fAudio16Buffers != nullptr) | |||
| { | |||
| for (uint32_t i=0; i < kData->audioOut.count; i++) | |||
| { | |||
| if (fAudio16Buffers[i] != nullptr) | |||
| { | |||
| delete[] fAudio16Buffers[i]; | |||
| fAudio16Buffers[i] = nullptr; | |||
| } | |||
| } | |||
| delete[] fAudio16Buffers; | |||
| fAudio16Buffers = nullptr; | |||
| } | |||
| CarlaPlugin::deleteBuffers(); | |||
| carla_debug("FluidSynthPlugin::deleteBuffers() - end"); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| bool init(const char* const filename, const char* const name, const char* const label) | |||
| @@ -1271,13 +1382,16 @@ private: | |||
| FluidSynthParametersMax = 14 | |||
| }; | |||
| const bool kUses16Outs; | |||
| CarlaString fLabel; | |||
| fluid_settings_t* fSettings; | |||
| fluid_synth_t* fSynth; | |||
| int fSynthId; | |||
| double fParamBuffers[FluidSynthParametersMax]; | |||
| float** fAudio16Buffers; | |||
| double fParamBuffers[FluidSynthParametersMax]; | |||
| }; | |||
| /**@}*/ | |||
| @@ -1290,19 +1404,24 @@ CARLA_BACKEND_END_NAMESPACE | |||
| CARLA_BACKEND_START_NAMESPACE | |||
| CarlaPlugin* CarlaPlugin::newSF2(const Initializer& init) | |||
| CarlaPlugin* CarlaPlugin::newSF2(const Initializer& init, const bool use16Outs) | |||
| { | |||
| carla_debug("CarlaPlugin::newSF2({%p, \"%s\", \"%s\", \"%s\"})", init.engine, init.filename, init.name, init.label); | |||
| #ifdef WANT_FLUIDSYNTH | |||
| if (! fluid_is_soundfont(init.filename)) | |||
| { | |||
| init.engine->setLastError("Requested file is not a valid SoundFont"); | |||
| return nullptr; | |||
| } | |||
| FluidSynthPlugin* const plugin = new FluidSynthPlugin(init.engine, init.id); | |||
| if (init.engine->getProccessMode() == PROCESS_MODE_CONTINUOUS_RACK && use16Outs) | |||
| { | |||
| init.engine->setLastError("Carla's rack mode can only work with Stereo modules, please choose the 2-channel only SoundFont version"); | |||
| return nullptr; | |||
| } | |||
| FluidSynthPlugin* const plugin = new FluidSynthPlugin(init.engine, init.id, use16Outs); | |||
| if (! plugin->init(init.filename, init.name, init.label)) | |||
| { | |||
| @@ -509,7 +509,7 @@ public: | |||
| if (max - min == 0.0f) | |||
| { | |||
| carla_stderr2("Broken plugin parameter: max - min == 0"); | |||
| carla_stderr2("WARNING - Broken plugin parameter '%s': max - min == 0.0f", fDescriptor->PortNames[i]); | |||
| max = min + 0.1f; | |||
| } | |||
| @@ -1069,7 +1069,6 @@ public: | |||
| #endif | |||
| } // End of Post-processing | |||
| CARLA_PROCESS_CONTINUE_CHECK; | |||
| // -------------------------------------------------------------------------------------------------------- | |||
| @@ -1075,6 +1075,14 @@ class CarlaMainW(QMainWindow): | |||
| if gui: | |||
| return gui.encode("utf-8") | |||
| elif ptype == PLUGIN_SF2: | |||
| if plugin['name'].endswith(" (16 outputs)"): | |||
| # return a dummy non-null pointer | |||
| INTPOINTER = POINTER(c_int) | |||
| ptr = c_int(0x1) | |||
| addr = addressof(ptr) | |||
| return cast(addr, INTPOINTER) | |||
| return c_nullptr | |||
| def loadRDFs(self): | |||
| @@ -86,3 +86,8 @@ carla-discovery-win64.exe: $(OBJS) ../libs/lilv_win64.a | |||
| clean: | |||
| rm -f carla-discovery-* | |||
| # -------------------------------------------------------------- | |||
| debug: | |||
| $(MAKE) DEBUG=true | |||
| @@ -1291,9 +1291,21 @@ void do_fluidsynth_check(const char* const filename, const bool init) | |||
| delete_fluid_settings(f_settings); | |||
| } | |||
| #if CARLA_OS_WIN | |||
| int sep = '\\'; | |||
| #else | |||
| int sep = '/'; | |||
| #endif | |||
| CarlaString name(std::strrchr(filename, sep)+1); | |||
| name.truncate(name.rfind('.')); | |||
| CarlaString label(name); | |||
| // 2 channels | |||
| DISCOVERY_OUT("init", "-----------"); | |||
| DISCOVERY_OUT("name", ""); | |||
| DISCOVERY_OUT("label", ""); | |||
| DISCOVERY_OUT("name", (const char*)name); | |||
| DISCOVERY_OUT("label", (const char*)label); | |||
| DISCOVERY_OUT("maker", ""); | |||
| DISCOVERY_OUT("copyright", ""); | |||
| DISCOVERY_OUT("hints", PLUGIN_IS_SYNTH); | |||
| @@ -1307,6 +1319,26 @@ void do_fluidsynth_check(const char* const filename, const bool init) | |||
| DISCOVERY_OUT("parameters.total", 14); | |||
| DISCOVERY_OUT("build", BINARY_NATIVE); | |||
| DISCOVERY_OUT("end", "------------"); | |||
| // 16 channels | |||
| if (name.isNotEmpty()) | |||
| name += " (16 outputs)"; | |||
| DISCOVERY_OUT("init", "-----------"); | |||
| DISCOVERY_OUT("name", ""); | |||
| DISCOVERY_OUT("name", (const char*)name); | |||
| DISCOVERY_OUT("label", (const char*)label); | |||
| DISCOVERY_OUT("copyright", ""); | |||
| DISCOVERY_OUT("hints", PLUGIN_IS_SYNTH); | |||
| DISCOVERY_OUT("audio.outs", 32); | |||
| DISCOVERY_OUT("audio.total", 32); | |||
| DISCOVERY_OUT("midi.ins", 1); | |||
| DISCOVERY_OUT("midi.total", 1); | |||
| DISCOVERY_OUT("programs.total", programs); | |||
| DISCOVERY_OUT("parameters.ins", 13); // defined in Carla | |||
| DISCOVERY_OUT("parameters.outs", 1); | |||
| DISCOVERY_OUT("parameters.total", 14); | |||
| DISCOVERY_OUT("build", BINARY_NATIVE); | |||
| DISCOVERY_OUT("end", "------------"); | |||
| #else | |||
| DISCOVERY_OUT("error", "SF2 support not available"); | |||
| Q_UNUSED(filename); | |||
| @@ -187,6 +187,30 @@ public: | |||
| truncate(0); | |||
| } | |||
| size_t find(const char c) | |||
| { | |||
| for (size_t i=0; i < bufferLen; i++) | |||
| { | |||
| if (buffer[i] == c) | |||
| return i; | |||
| } | |||
| return 0; | |||
| } | |||
| size_t rfind(const char c) | |||
| { | |||
| size_t pos = 0; | |||
| for (size_t i=0; i < bufferLen; i++) | |||
| { | |||
| if (buffer[i] == c) | |||
| pos = i; | |||
| } | |||
| return pos; | |||
| } | |||
| void replace(const char before, const char after) | |||
| { | |||
| if (after == '\0') | |||