Add FluidSynth synthesizer

This commit is contained in:
Dale Whinham committed 2020-11-22 23:16:44 +00:00
1 parent ff8cca440d
commit 61759d278c
6 files changed
+335 -7

No files matched your search

+2 -1
View File
@@ -16,7 +16,8 @@ OBJS := src/config.o \
src/mt32pi.o \
src/power.o \
src/rommanager.o \
src/synth/mt32synth.o
src/synth/mt32synth.o \
src/synth/soundfontsynth.o
EXTRACLEAN += src/*.d src/*.o \
src/lcd/*.d src/lcd/*.o \
+3
View File
@@ -39,6 +39,7 @@
#include "midiparser.h"
#include "power.h"
#include "synth/mt32synth.h"
#include "synth/soundfontsynth.h"
class CMT32Pi : public CMultiCoreSupport, CPower, CMIDIParser
{
@@ -110,7 +111,9 @@ private:
CSoundBaseDevice* m_pSound;
// Synthesizers
CSynthBase* m_pCurrentSynth;
CMT32Synth* m_pMT32Synth;
CSoundFontSynth* m_pSoundFontSynth;
static void USBMIDIPacketHandler(unsigned nCable, u8* pPacket, unsigned nLength);
+54
View File
@@ -0,0 +1,54 @@
//
// soundfontsynth.h
//
// mt32-pi - A bare-metal Roland MT-32 emulator for Raspberry Pi
// Copyright (C) 2020 Dale Whinham <daleyo@gmail.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 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/>.
//
#ifndef _soundfontsynth_h
#define _soundfontsynth_h
#include <circle/types.h>
#include <fluidsynth.h>
#include "synth/synthbase.h"
class CSoundFontSynth : public CSynthBase
{
public:
CSoundFontSynth(unsigned nSampleRate);
virtual ~CSoundFontSynth() override;
// CSynthBase
virtual bool Initialize() override;
virtual void HandleMIDIShortMessage(u32 nMessage) override;
virtual void HandleMIDISysExMessage(const u8* pData, size_t nSize) override;
virtual bool IsActive() const override { return fluid_synth_get_active_voice_count(m_pSynth) > 0; }
virtual void AllSoundOff() override;
virtual size_t Render(s16* pOutBuffer, size_t nFrames) override;
virtual size_t Render(float* pOutBuffer, size_t nFrames) override;
private:
unsigned m_nSampleRate;
fluid_settings_t* m_pSettings;
fluid_synth_t* m_pSynth;
static void FluidSynthLogCallback(int nLevel, const char* pMessage, void* pUser);
};
#endif
+2
View File
@@ -91,6 +91,8 @@ output_device = pwm
# mt32emu uses an internal samplerate of 32000Hz (just like the real hardware)
# which is then resampled to this value.
#
# FluidSynth renders at this sample rate directly.
#
# Values: 32000-192000 (96000*)
sample_rate = 96000
+28 -6
View File
@@ -64,7 +64,9 @@ CMT32Pi::CMT32Pi(CI2CMaster* pI2CMaster, CInterruptSystem* pInterrupt, CSerialDe
m_nLEDOnTime(0),
m_pSound(nullptr),
m_pMT32Synth(nullptr)
m_pCurrentSynth(nullptr),
m_pMT32Synth(nullptr),
m_pSoundFontSynth(nullptr)
{
s_pThis = this;
}
@@ -140,6 +142,26 @@ bool CMT32Pi::Initialize(bool bSerialMIDIEnabled)
if (config.MT32EmuMIDIChannels == CMT32Synth::TMIDIChannels::Alternate)
m_pMT32Synth->SetMIDIChannels(config.MT32EmuMIDIChannels);
LCDLog(TLCDLogType::Startup, "Init FluidSynth");
m_pSoundFontSynth = new CSoundFontSynth(config.AudioSampleRate);
if (!m_pSoundFontSynth->Initialize())
{
logger.Write(MT32PiName, LogWarning, "FluidSynth init failed; no SoundFonts present?");
delete m_pSoundFontSynth;
m_pSoundFontSynth = nullptr;
}
// TODO: Config option
// Set initial synthesizer
m_pCurrentSynth = m_pMT32Synth;
if (!m_pCurrentSynth)
{
logger.Write(MT32PiName, LogError, "No synthesizers initialized successfully");
LCDLog(TLCDLogType::Startup, "Synth init failed!");
return false;
}
CUSBMIDIDevice* pMIDIDevice = static_cast<CUSBMIDIDevice*>(CDeviceNameService::Get()->GetDevice("umidi1", FALSE));
if (pMIDIDevice)
{
@@ -190,13 +212,13 @@ void CMT32Pi::MainTask()
// Check for active sensing timeout
if (m_bActiveSenseFlag && (ticks > m_nActiveSenseTime) && (ticks - m_nActiveSenseTime) >= MSEC2HZ(ACTIVE_SENSE_TIMEOUT_MILLIS))
{
m_pMT32Synth->AllSoundOff();
m_pCurrentSynth->AllSoundOff();
m_bActiveSenseFlag = false;
logger.Write(MT32PiName, LogNotice, "Active sense timeout - turning notes off");
}
// Update power management
if (m_pMT32Synth->IsActive())
if (m_pCurrentSynth->IsActive())
Awaken();
CPower::Update();
@@ -258,7 +280,7 @@ void CMT32Pi::AudioTask()
const size_t nFrames = nQueueSize - m_pSound->GetQueueFramesAvail();
const size_t nWriteBytes = nFrames * 2 * sizeof(s16);
m_pMT32Synth->Render(Buffer, nFrames);
m_pCurrentSynth->Render(Buffer, nFrames);
int nResult = m_pSound->Write(Buffer, nWriteBytes);
if (nResult != static_cast<int>(nWriteBytes))
@@ -330,7 +352,7 @@ void CMT32Pi::OnShortMessage(u32 nMessage)
// Wake from power saving mode if necessary
Awaken();
m_pMT32Synth->HandleMIDIShortMessage(nMessage);
m_pCurrentSynth->HandleMIDIShortMessage(nMessage);
}
void CMT32Pi::OnSysExMessage(const u8* pData, size_t nSize)
@@ -343,7 +365,7 @@ void CMT32Pi::OnSysExMessage(const u8* pData, size_t nSize)
// If we don't consume the SysEx message, forward it to mt32emu
if (!ParseCustomSysEx(pData, nSize))
m_pMT32Synth->HandleMIDISysExMessage(pData, nSize);
m_pCurrentSynth->HandleMIDISysExMessage(pData, nSize);
}
void CMT32Pi::OnUnexpectedStatus()
+246
View File
@@ -0,0 +1,246 @@
//
// soundfontsynth.cpp
//
// mt32-pi - A bare-metal Roland MT-32 emulator for Raspberry Pi
// Copyright (C) 2020 Dale Whinham <daleyo@gmail.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 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 <fatfs/ff.h>
#include <circle/logger.h>
#include <circle/timer.h>
#include "synth/soundfontsynth.h"
#include "utility.h"
const char SoundFontSynthName[] = "soundfontsynth";
const char SoundFontPath[] = "soundfonts";
extern "C"
{
// Replacements for fluid_sys.c functions
FILE* fluid_file_open(const char* path, const char** errMsg)
{
FILE* handle = fopen(path, "rb");
if (!handle && errMsg)
*errMsg = "Failed to open file";
return handle;
}
void fluid_msleep(unsigned int msecs) { CTimer::SimpleMsDelay(msecs); }
double fluid_utime() { return static_cast<double>(CTimer::GetClockTicks()); }
// Replacements for fluid_sfont.c functions
// These were found to be much faster than FluidSynth's default approach of going through libc
void* default_fopen(const char* path)
{
FIL* pFile = new FIL;
if (f_open(pFile, path, FA_READ) != FR_OK)
{
delete pFile;
pFile = nullptr;
}
return pFile;
}
int default_fclose(void* handle)
{
FIL* pFile = static_cast<FIL*>(handle);
if (f_close(pFile) == FR_OK)
{
delete pFile;
return FLUID_OK;
}
return FLUID_FAILED;
}
long default_ftell(void* handle)
{
FIL* pFile = static_cast<FIL*>(handle);
return f_tell(pFile);
}
int safe_fread(void* buf, int count, void* fd)
{
FIL* pFile = static_cast<FIL*>(fd);
UINT nRead;
return f_read(pFile, buf, count, &nRead) == FR_OK ? FLUID_OK : FLUID_FAILED;
}
int safe_fseek(void* fd, long ofs, int whence)
{
FIL* pFile = static_cast<FIL*>(fd);
switch (whence)
{
case SEEK_CUR:
ofs += f_tell(pFile);
break;
case SEEK_END:
ofs += f_size(pFile);
break;
default:
break;
}
return f_lseek(pFile, ofs) == FR_OK ? FLUID_OK : FLUID_FAILED;
}
}
CSoundFontSynth::CSoundFontSynth(unsigned nSampleRate)
: m_nSampleRate(nSampleRate),
m_pSettings(nullptr),
m_pSynth(nullptr)
{
}
CSoundFontSynth::~CSoundFontSynth()
{
if (m_pSynth)
delete_fluid_synth(m_pSynth);
if (m_pSettings)
delete_fluid_settings(m_pSettings);
}
void CSoundFontSynth::FluidSynthLogCallback(int nLevel, const char* pMessage, void* pUser)
{
CLogger::Get()->Write(SoundFontSynthName, static_cast<TLogSeverity>(nLevel), pMessage);
}
bool CSoundFontSynth::Initialize()
{
// Install logging handlers
fluid_set_log_function(FLUID_PANIC, FluidSynthLogCallback, this);
fluid_set_log_function(FLUID_ERR, FluidSynthLogCallback, this);
// fluid_set_log_function(FLUID_WARN, FluidSynthLogCallback, this);
// fluid_set_log_function(FLUID_INFO, FluidSynthLogCallback, this);
// fluid_set_log_function(FLUID_DBG, FluidSynthLogCallback, this);
CLogger::Get()->Write(SoundFontSynthName, LogNotice, "Loading soundfont");
m_pSettings = new_fluid_settings();
if (!m_pSettings)
{
CLogger::Get()->Write(SoundFontSynthName, LogError, "Failed to create settings");
return false;
}
fluid_settings_setnum(m_pSettings, "synth.sample-rate", static_cast<double>(m_nSampleRate));
fluid_settings_setint(m_pSettings, "synth.threadsafe-api", false);
m_pSynth = new_fluid_synth(m_pSettings);
if (!m_pSynth)
{
CLogger::Get()->Write(SoundFontSynthName, LogError, "Failed to create synth");
return false;
}
if (fluid_synth_sfload(m_pSynth, "soundfonts/SC-55.sf2", true) == FLUID_FAILED)
{
CLogger::Get()->Write(SoundFontSynthName, LogError, "Failed to load SoundFont");
return false;
}
// TODO: Expose in config file
fluid_synth_set_polyphony(m_pSynth, 128);
return true;
}
void CSoundFontSynth::HandleMIDIShortMessage(u32 nMessage)
{
const u8 status = nMessage & 0xFF;
const u8 channel = nMessage & 0xF;
const u8 data1 = (nMessage >> 8) & 0xFF;
const u8 data2 = (nMessage >> 16) & 0xFF;
// Handle system real-time messages
if (status == 0xFF)
{
fluid_synth_system_reset(m_pSynth);
return;
}
// Handle channel messages
switch (status & 0xF0)
{
// Note off
case 0x80:
fluid_synth_noteoff(m_pSynth, channel, data1);
break;
// Note on
case 0x90:
fluid_synth_noteon(m_pSynth, channel, data1, data2);
break;
// Polyphonic key pressure/aftertouch
case 0xA0:
fluid_synth_key_pressure(m_pSynth, channel, data1, data2);
break;
// Control change
case 0xB0:
fluid_synth_cc(m_pSynth, channel, data1, data2);
break;
// Program change
case 0xC0:
fluid_synth_program_change(m_pSynth, channel, data1);
break;
// Channel pressure/aftertouch
case 0xD0:
fluid_synth_channel_pressure(m_pSynth, channel, data1);
break;
// Pitch bend
case 0xE0:
fluid_synth_pitch_bend(m_pSynth, channel, (data2 << 7) | data1);
break;
}
}
void CSoundFontSynth::HandleMIDISysExMessage(const u8* pData, size_t nSize)
{
// Exclude leading 0xF0 and trailing 0xF7
fluid_synth_sysex(m_pSynth, reinterpret_cast<const char*>(pData + 1), nSize - 1, nullptr, nullptr, nullptr, false);
}
void CSoundFontSynth::AllSoundOff()
{
fluid_synth_all_sounds_off(m_pSynth, -1);
}
size_t CSoundFontSynth::Render(float* pOutBuffer, size_t nFrames)
{
assert(fluid_synth_write_float(m_pSynth, nFrames, pOutBuffer, 0, 2, pOutBuffer, 1, 2) == FLUID_OK);
return nFrames;
}
size_t CSoundFontSynth::Render(s16* pOutBuffer, size_t nFrames)
{
assert(fluid_synth_write_s16(m_pSynth, nFrames, pOutBuffer, 0, 2, pOutBuffer, 1, 2) == FLUID_OK);
return nFrames;
}