/* * Copyright 2006-2014, Haiku. * * Copyright (c) 2004-2005 Matthijs Hollemans * Copyright (c) 2003 Jerome Leveque * Distributed under the terms of the MIT License. * * Authors: * Jérôme Duval * Jérôme Leveque * Matthijs Hollemans * Pete Goodeve */ #include #include #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "MidiGlue.h" // for MAKE_BIGTIME #include "SoftSynth.h" using namespace BPrivate; struct ReverbSettings { double room, damp, width, level; } gReverbSettings[] = { {0.0, 0.0, 0.0, 0.0}, // B_REVERB_NONE {0.2, 0.0, 0.5, 0.9}, // B_REVERB_CLOSET {0.5, 0.0, 0.9, 0.9}, // B_REVERB_GARAGE {0.7, 0.25, 0.9, 0.95}, // B_REVERB_BALLROOM {0.99, 0.3, 1.0, 1.0}, // B_REVERB_CAVERN {1.03, 0.6, 1.0, 1.0} // B_REVERB_DUNGEON }; BSoftSynth::BSoftSynth() : fInitCheck(false), fSynth(NULL), fSettings(NULL), fSoundPlayer(NULL), fMonitor(NULL), fMonitorSize(0), fMonitorChans(-1) { fInstrumentsFile = NULL; SetDefaultInstrumentsFile(); fSampleRate = 44100; fInterpMode = B_LINEAR_INTERPOLATION; fMaxVoices = 256; fLimiterThreshold = 7; fReverbEnabled = true; fReverbMode = B_REVERB_BALLROOM; } BSoftSynth::~BSoftSynth() { // Note: it is possible that we don't get deleted. When BSynth is // created, it is assigned to the global variable be_synth. While // BSynth is alive, it keeps a copy of BSoftSynth around too. Not // a big deal, but the Midi Kit will complain (on stdout) that we // didn't release our endpoints. delete[] fMonitor; Unload(); } bool BSoftSynth::InitCheck() { if (!fSynth) _Init(); return fInitCheck; } void BSoftSynth::Unload(void) { _Done(); free(fInstrumentsFile); fInstrumentsFile = NULL; } bool BSoftSynth::IsLoaded(void) const { return fInstrumentsFile != NULL; } status_t BSoftSynth::SetDefaultInstrumentsFile() { // TODO: Duplicated code, check MidiSettingsView::_LoadSettings() and // MidiSettingsView::_RetrieveSoftSynthList() // We first search for a setting file (or symlink to it) // in the user settings directory struct BPrivate::midi_settings settings; if (BPrivate::read_midi_settings(&settings) == B_OK) { if (SetInstrumentsFile(settings.soundfont_file) == B_OK) return B_OK; } // Try a well-known (and usually present on a default install) soft synth BPath path; if (find_directory(B_SYNTH_DIRECTORY, &path, false, NULL) == B_OK) { path.Append("synth/TimGM6mb.sf2"); if (SetInstrumentsFile(path.Path()) == B_OK) return B_OK; } // Just use the first soundfont we find BStringList paths; status_t status = BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, "synth", paths); if (status != B_OK) return B_ERROR; for (int32 i = 0; i < paths.CountStrings(); i++) { BDirectory directory(paths.StringAt(i).String()); BEntry entry; if (directory.InitCheck() != B_OK) continue; while (directory.GetNextEntry(&entry) == B_OK) { BNode node(&entry); BNodeInfo nodeInfo(&node); char mimeType[B_MIME_TYPE_LENGTH]; // TODO: For some reason the mimetype check fails. // maybe because the file hasn't yet been sniffed and recognized? if (nodeInfo.GetType(mimeType) == B_OK /*&& !strcmp(mimeType, "audio/x-soundfont")*/) { BPath fullPath = paths.StringAt(i).String(); fullPath.Append(entry.Name()); if (SetInstrumentsFile(fullPath.Path()) == B_OK) return B_OK; } } } // TODO: Write the settings file ? return B_ERROR; } status_t BSoftSynth::SetInstrumentsFile(const char* path) { if (path == NULL) return B_BAD_VALUE; if (!BEntry(path).Exists()) return B_ENTRY_NOT_FOUND; if (IsLoaded()) Unload(); fInstrumentsFile = strdup(path); return B_OK; } status_t BSoftSynth::LoadAllInstruments() { InitCheck(); return B_OK; } status_t BSoftSynth::LoadInstrument(int16 instrument) { UNIMPLEMENTED return B_OK; } status_t BSoftSynth::UnloadInstrument(int16 instrument) { UNIMPLEMENTED return B_OK; } status_t BSoftSynth::RemapInstrument(int16 from, int16 to) { UNIMPLEMENTED return B_OK; } void BSoftSynth::FlushInstrumentCache(bool startStopCache) { // TODO: we may decide not to support this function because it's weird! UNIMPLEMENTED } void BSoftSynth::SetVolume(double volume) { if (InitCheck()) if (volume >= 0.0) { fluid_synth_set_gain(fSynth, volume); } } double BSoftSynth::Volume(void) const { return fluid_synth_get_gain(fSynth); } status_t BSoftSynth::SetSamplingRate(int32 rate) { if (rate == 22050 || rate == 44100 || rate == 48000) { fSampleRate = rate; return B_OK; } return B_BAD_VALUE; } int32 BSoftSynth::SamplingRate() const { return fSampleRate; } status_t BSoftSynth::SetInterpolation(interpolation_mode mode) { // not used because our synth uses the same format than the soundplayer fInterpMode = mode; return B_OK; } interpolation_mode BSoftSynth::Interpolation() const { return fInterpMode; } status_t BSoftSynth::EnableReverb(bool enabled) { fReverbEnabled = enabled; fluid_synth_set_reverb_on(fSynth, enabled); return B_OK; } bool BSoftSynth::IsReverbEnabled() const { return fReverbEnabled; } void BSoftSynth::SetReverb(reverb_mode mode) { if (mode < B_REVERB_NONE || mode > B_REVERB_DUNGEON) return; fReverbMode = mode; if (fSynth) { // We access the table using "mode - 1" because B_REVERB_NONE == 1 ReverbSettings *rvb = &gReverbSettings[mode - 1]; fluid_synth_set_reverb(fSynth, rvb->room, rvb->damp, rvb->width, rvb->level); } } reverb_mode BSoftSynth::Reverb() const { return fReverbMode; } status_t BSoftSynth::SetMaxVoices(int32 max) { if (max > 0 && max <= 4096) { fMaxVoices = max; return B_OK; } return B_BAD_VALUE; } int16 BSoftSynth::MaxVoices(void) const { return fMaxVoices; } status_t BSoftSynth::SetLimiterThreshold(int32 threshold) { // not used if (threshold > 0 && threshold <= 32) { fLimiterThreshold = threshold; return B_OK; } return B_BAD_VALUE; } int16 BSoftSynth::LimiterThreshold(void) const { return fLimiterThreshold; } void BSoftSynth::Pause(void) { UNIMPLEMENTED } void BSoftSynth::Resume(void) { UNIMPLEMENTED } void BSoftSynth::NoteOff( uchar channel, uchar note, uchar velocity, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); fluid_synth_noteoff(fSynth, channel - 1, note); // velocity isn't used in FS } } void BSoftSynth::NoteOn( uchar channel, uchar note, uchar velocity, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); fluid_synth_noteon(fSynth, channel - 1, note, velocity); } } void BSoftSynth::KeyPressure( uchar channel, uchar note, uchar pressure, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); // unavailable } } void BSoftSynth::ControlChange( uchar channel, uchar controlNumber, uchar controlValue, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); fluid_synth_cc(fSynth, channel - 1, controlNumber, controlValue); } } void BSoftSynth::ProgramChange( uchar channel, uchar programNumber, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); fluid_synth_program_change(fSynth, channel - 1, programNumber); } } void BSoftSynth::ChannelPressure(uchar channel, uchar pressure, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); //unavailable } } void BSoftSynth::PitchBend(uchar channel, uchar lsb, uchar msb, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); // fluid_synth only accepts an int fluid_synth_pitch_bend(fSynth, channel - 1, ((uint32)(msb & 0x7f) << 7) | (lsb & 0x7f)); } } void BSoftSynth::SystemExclusive(void* data, size_t length, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); // unsupported } } void BSoftSynth::SystemCommon( uchar status, uchar data1, uchar data2, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); // unsupported } } void BSoftSynth::SystemRealTime(uchar status, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); // unsupported } } void BSoftSynth::TempoChange(int32 beatsPerMinute, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); // unsupported } } void BSoftSynth::AllNotesOff(bool justChannel, uint32 time) { if (InitCheck()) { snooze_until(MAKE_BIGTIME(time), B_SYSTEM_TIMEBASE); // from BMidi::AllNotesOff for (uchar channel = 1; channel <= 16; ++channel) { fluid_synth_cc(fSynth, channel - 1, B_ALL_NOTES_OFF, 0); if (!justChannel) { for (uchar note = 0; note <= 0x7F; ++note) { fluid_synth_noteoff(fSynth, channel - 1, note); } } } } } void BSoftSynth::_Init() { status_t err; fInitCheck = false; _Done(); fSettings = new_fluid_settings(); fluid_settings_setnum(fSettings, (char*)"synth.sample-rate", fSampleRate); fluid_settings_setint(fSettings, (char*)"synth.polyphony", fMaxVoices); fSynth = new_fluid_synth(fSettings); if (!fSynth) return; err = fluid_synth_sfload(fSynth, fInstrumentsFile, 1); if (err < B_OK) { fprintf(stderr, "error in fluid_synth_sfload\n"); return; } SetReverb(fReverbMode); media_raw_audio_format format = media_raw_audio_format::wildcard; format.channel_count = 2; format.frame_rate = fSampleRate; format.format = media_raw_audio_format::B_AUDIO_FLOAT; fSoundPlayer = new BSoundPlayer(&format, "Soft Synth", &PlayBuffer, NULL, this); err = fSoundPlayer->InitCheck(); if (err != B_OK) { fprintf(stderr, "error in BSoundPlayer\n"); return; } fSoundPlayer->SetHasData(true); fSoundPlayer->Start(); fInitCheck = true; } void BSoftSynth::_Done() { if (fSoundPlayer) { fSoundPlayer->SetHasData(false); fSoundPlayer->Stop(); delete fSoundPlayer; fSoundPlayer = NULL; } if (fSynth) { delete_fluid_synth(fSynth); fSynth = NULL; } if (fSettings) { delete_fluid_settings(fSettings); fSettings = NULL; } } void BSoftSynth::PlayBuffer(void* cookie, void* data, size_t size, const media_raw_audio_format& format) { BSoftSynth* synth = (BSoftSynth*)cookie; if (synth->fMonitorSize == 0) { synth->fMonitor = (float*)new void*[size]; synth->fMonitorSize = size; synth->fMonitorChans = format.channel_count; } // we use float samples if (synth->fSynth) { fluid_synth_write_float( synth->fSynth, size / sizeof(float) / format.channel_count, data, 0, format.channel_count, data, 1, format.channel_count); memcpy(synth->fMonitor, data, size); } }