// // kernel.cpp // // mt32-pi - A bare-metal Roland MT-32 emulator for Raspberry Pi // Copyright (C) 2020 Dale Whinham // // 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 . // #include "kernel.h" #include #include #include #include #include #include #include static const char MT32ControlROMName[] = "MT32_CONTROL.ROM"; static const char MT32PCMROMName[] = "MT32_PCM.ROM"; CKernel *CKernel::pThis = nullptr; CKernel::CKernel(void) : CStdlibApp("mt32"), CPWMSoundBaseDevice(&mInterrupt, 32000, 2), //mScreen(mOptions.GetWidth(), mOptions.GetHeight()), mSerial(&mInterrupt, true), mTimer(&mInterrupt), mLogger(mOptions.GetLogLevel(), &mTimer), mUSBHCI(&mInterrupt, &mTimer), mEMMC(&mInterrupt, &mTimer, &mActLED), //mConsole(&mScreen), mConsole(&mSerial), mSerialState(0), mSerialMessage{0}, mShouldReboot(false), mLEDOn(false), mLEDOnTime(0), mControlROMImage(nullptr), mPCMROMImage(nullptr), mSynth(nullptr) { pThis = this; mLowLevel = GetRangeMin(); mHighLevel = GetRangeMax(); mNullLevel = (mHighLevel + mLowLevel) / 2; } bool CKernel::Initialize(void) { if (!CStdlibApp::Initialize()) { return false; } // if (!mScreen.Initialize()) // { // return false; // } //if (!mSerial.Initialize(31250)) if (!mSerial.Initialize(115200)) { return false; } // CDevice *pTarget = mDeviceNameService.GetDevice(mOptions.GetLogDevice(), false); // if (pTarget == 0) // { // pTarget = &mScreen; // } if (!mLogger.Initialize(&mSerial)) { return false; } if (!mTimer.Initialize()) { return false; } if (!mEMMC.Initialize()) { return false; } char const *partitionName = "SD:"; if (f_mount(&mFileSystem, partitionName, 1) != FR_OK) { mLogger.Write(GetKernelName(), LogError, "Cannot mount partition: %s", partitionName); return false; } #if !defined(__aarch64__) || !defined(LEAVE_QEMU_ON_HALT) // The USB driver is not supported under 64-bit QEMU, so // the initialization must be skipped in this case, or an // exit happens here under 64-bit QEMU. if (!mUSBHCI.Initialize()) { return false; } #endif if (!mConsole.Initialize()) { return false; } // Initialize newlib stdio with a reference to Circle's file system and console CGlueStdioInit(mFileSystem, mConsole); mLogger.Write(GetKernelName(), LogNotice, "Compile time: " __DATE__ " " __TIME__); return true; } void CKernel::printDebug(const char *fmt, va_list list) { mLogger.WriteV(GetKernelName(), LogNotice, fmt, list); } void CKernel::showLCDMessage(const char *message) { mLogger.Write("lcd", LogNotice, message); } bool CKernel::onMIDIQueueOverflow() { mLogger.Write("midi", LogError, "MIDI queue overflow"); return false; } bool CKernel::initMT32() { if (!mControlFile.open(MT32ControlROMName)) { CLogger::Get()->Write(GetKernelName(), LogError, "Could not open %s", MT32ControlROMName); return false; } if (!mPCMFile.open(MT32PCMROMName)) { CLogger::Get()->Write(GetKernelName(), LogError, "Could not open %s", MT32PCMROMName); return false; } mControlROMImage = MT32Emu::ROMImage::makeROMImage(&mControlFile); mPCMROMImage = MT32Emu::ROMImage::makeROMImage(&mPCMFile); mSynth = new MT32Emu::Synth(this); return mSynth->open(*mControlROMImage, *mPCMROMImage); } void CKernel::ledOn() { mActLED.On(); mLEDOnTime = mTimer.GetTicks(); mLEDOn = true; } unsigned int CKernel::GetChunk(u32 *pBuffer, unsigned nChunkSize) { unsigned nResult = nChunkSize; if (!mSynth) return nResult; // MT32Emu::Bit16s samples[nChunkSize]; // mSynth->render(samples, nChunkSize / 2); float samples[nChunkSize]; mSynth->render(samples, nChunkSize / 2); for (size_t i = 0; i < nChunkSize; ++i) { // unsigned nValue = samples[i]; // nValue = (nValue + 0x8000) & 0xFFFF; // nValue >>= 4; //*pBuffer++ = nValue; *pBuffer++ = static_cast(samples[i] * mHighLevel / 2 + mNullLevel); } return nResult; } CStdlibApp::TShutdownMode CKernel::Run(void) { mLogger.Write(GetKernelName(), LogNotice, "Starting up..."); if (!initMT32()) return ShutdownHalt; // Start PWM audio Start(); mLogger.Write(GetKernelName(), LogNotice, "Audio output range: lo=%d null=%d hi=%d", mLowLevel, mNullLevel, mHighLevel); CUSBMIDIDevice *pMIDIDevice = static_cast(CDeviceNameService::Get()->GetDevice("umidi1", FALSE)); if (pMIDIDevice) { pMIDIDevice->RegisterPacketHandler(MIDIPacketHandler); mLogger.Write(GetKernelName(), LogNotice, "Using USB MIDI interface"); } else { //mLogger.Write(GetKernelName(), LogNotice, "Using serial MIDI interface"); mLogger.Write(GetKernelName(), LogError, "No USB MIDI device detected - please connect and restart."); return ShutdownHalt; } while (true) { if (mLEDOn && (mTimer.GetTicks() - mLEDOnTime) > 5) { mActLED.Off(); mLEDOn = false; } if (mShouldReboot) { // Stop audio and reboot Cancel(); return ShutdownReboot; } } // while (true) // { // // Read serial MIDI data // u8 Buffer[100]; // int nResult = mSerial.Read(Buffer, sizeof Buffer); // if (nResult <= 0) // { // continue; // } // // Process MIDI messages // // See: https://www.midi.org/specifications/item/table-1-summary-of-midi-message // for (int i = 0; i < nResult; i++) // { // u8 uchData = Buffer[i]; // switch (mSerialState) // { // case 0: // // Channel message // MIDIRestart: // if (uchData >= 0x80 && uchData <= 0xEF) // { // mSerialMessage[mSerialState++] = uchData; // } // // // SysEx // // else if (uchData == 0xF0) // // { // // mSerialState = 4; // // mSysExMessage.push_back(uchData); // // } // break; // case 1: // case 2: // if (uchData & 0x80) // got status when parameter expected // { // mSerialState = 0; // goto MIDIRestart; // } // mSerialMessage[mSerialState++] = uchData; // // MIDI message is complete // if (mSerialState == 3) // { // MIDIPacketHandler(0, mSerialMessage, sizeof mSerialMessage); // mSerialState = 0; // } // break; // // case 4: // // mSysExMessage.push_back(uchData); // // if (uchData == 0xF7) // // { // // mSynth->playSysex(mSysExMessage.data(), mSysExMessage.size()); // // mSysExMessage.clear(); // // mSerialState = 0; // // } // // break; // default: // assert(0); // break; // } // } // } return ShutdownHalt; } bool CKernel::parseSysEx() { if (mSysExMessage.size() < 4) return false; // 'Educational' manufactuer if (mSysExMessage[1] != 0x7D) return false; // Reboot (F0 7F 00 F7) if (mSysExMessage[2] == 0x00) { pThis->mLogger.Write(pThis->GetKernelName(), LogNotice, "midi: Reboot command received"); pThis->mShouldReboot = true; return true; } return false; } void CKernel::MIDIPacketHandler(unsigned nCable, u8 *pPacket, unsigned nLength) { assert(pThis != 0); MT32Emu::Bit32u packet = 0; for (size_t i = 0; i < nLength; ++i) { // SysEx message if (pThis->mSysExMessage.size() || pPacket[i] == 0xF0) { pThis->mSysExMessage.push_back(pPacket[i]); if (pPacket[i] == 0xF7) { // If we don't consume the SysEx message, forward it to mt32emu if (!pThis->parseSysEx()) pThis->mSynth->playSysex(pThis->mSysExMessage.data(), pThis->mSysExMessage.size()); pThis->mSysExMessage.clear(); packet = 0; } } // Channel message else packet |= pPacket[i] << 8 * i; } if (packet) { // Flash LED on note on or off if (packet & 0x80 == 0x80) pThis->ledOn(); //pThis->mLogger.Write(pThis->GetKernelName(), LogNotice, "midi 0x%08x", packet); pThis->mSynth->playMsg(packet); } }