gigatron/rom/Contrib/at67/midi.cpp
2025-01-28 19:17:01 +03:00

210 lines
6.4 KiB
C++

#include <fstream>
#include <cstring>
#include "midi.h"
#ifndef STAND_ALONE
#include "cpu.h"
#include "memory.h"
#include "timing.h"
#include "audio.h"
#include <SDL.h>
#endif
namespace Midi
{
bool loadFile(const std::string& filepath, uint8_t* midiBuffer, int& midiBufferSize, GtMidiHdr* gtMidiHdr)
{
// Read
std::ifstream infile(filepath, std::ios::binary | std::ios::in);
if(!infile.is_open())
{
fprintf(stderr, "Midi::loadFile() : failed to open file '%s' for reading\n", filepath.c_str());
return false;
}
// Process header if required
uint16_t streamSize = 0;
if(gtMidiHdr)
{
infile.read((char *)gtMidiHdr, GTMIDI_HDR_SIZE);
if(infile.bad())
{
fprintf(stderr, "Midi::loadFile() : read error of header in file '%s'\n", filepath.c_str());
return false;
}
if(strncmp((char *)gtMidiHdr->_tag, GTMIDI_TAG_STRING, GTMIDI_TAG_SIZE) != 0)
{
fprintf(stderr, "Midi::loadFile() : bad header tag in file '%s'\n", filepath.c_str());
return false;
}
streamSize = (gtMidiHdr->_hiSize <<8) + gtMidiHdr->_loSize;
}
infile.read((char *)midiBuffer, MIDI_MAX_BUFFER_SIZE - 1);
if(infile.bad())
{
fprintf(stderr, "Midi::loadFile() : read error of stream in file '%s'\n", filepath.c_str());
return false;
}
midiBufferSize = int(infile.gcount());
if(gtMidiHdr)
{
if(midiBufferSize != streamSize)
{
fprintf(stderr, "Midi::loadFile() : buffer stream size %d does not match header stream size %d in file '%s'\n", midiBufferSize, streamSize, filepath.c_str());
return false;
}
}
else
{
if(midiBufferSize < int(GTMIDI_STREAM_OFFSET))
{
fprintf(stderr, "Midi::loadFile() : malformed header in file '%s'\n", filepath.c_str());
return false;
}
}
if(midiBufferSize >= MIDI_MAX_BUFFER_SIZE)
{
fprintf(stderr, "Midi::loadFile() : midi data too large in file '%s'\n", filepath.c_str());
return false;
}
return true;
}
#ifndef STAND_ALONE
bool _hasVolume = false;
uint8_t* _midiBase = nullptr;
uint8_t* _midiStream = nullptr;
uint8_t* _midiPaused = nullptr;
uint8_t _midiDelay = 0;
uint16_t _midiStreamSize = 0;
uint8_t* getStream(void) {return _midiStream;}
bool setStream(const std::string* filenamePtr, uint8_t* midiBuffer, uint16_t midiBufferSize)
{
if(midiBuffer == nullptr) return false;
Audio::initialiseChannels();
GtMidiHdr* gtMidiHdr = (GtMidiHdr*)midiBuffer;
if(strncmp((char *)gtMidiHdr->_tag, GTMIDI_TAG_STRING, GTMIDI_TAG_SIZE) != 0)
{
_midiBase = nullptr;
_midiStream = nullptr;
_midiPaused = nullptr;
if(filenamePtr) fprintf(stderr, "Midi::setStream() : malformed midi header in file '%s'\n", filenamePtr->c_str());
return false;
}
_hasVolume = bool(gtMidiHdr->_hasVolume);
_midiStreamSize = (gtMidiHdr->_hiSize <<8) + gtMidiHdr->_loSize;
if(_midiStreamSize == 0 || _midiStreamSize != midiBufferSize - GTMIDI_STREAM_OFFSET)
{
_midiBase = nullptr;
_midiStream = nullptr;
_midiPaused = nullptr;
if(filenamePtr) fprintf(stderr, "Midi::setStream() : malformed midi header in file '%s'\n", filenamePtr->c_str());
return false;
}
_midiBase = midiBuffer + GTMIDI_STREAM_OFFSET;
_midiStream = midiBuffer + GTMIDI_STREAM_OFFSET;
return true;
}
void stop(void)
{
_hasVolume = false;
_midiBase = nullptr;
_midiStream = nullptr;
_midiDelay = 0;
_midiStreamSize = 0;
Audio::initialiseChannels();
}
void pause(bool enable)
{
if(enable)
{
_midiPaused = _midiStream;
_midiStream = nullptr;
}
else
{
_midiStream = _midiPaused;
_midiPaused = nullptr;
}
}
void play(void)
{
if(_midiStream == nullptr) return;
// Tick audio
Cpu::setRAM(GIGA_SOUND_TIMER, 2);
if(_midiDelay) _midiDelay--;
while(_midiDelay == 0)
{
if(_midiStream - _midiBase >= _midiStreamSize)
{
_midiStream = nullptr; //_midiBase;
break;
}
uint8_t command = *_midiStream++;
if(command & 0x80)
{
// Start note
if((command & 0xF0) == MIDI_CMD_START_NOTE)
{
// Midi note converted to Giga note
uint8_t channel = command & GIGA_CHANNELS_MASK; // spec supports up to 16 channels, Gigatron supports 4
uint16_t note = *_midiStream++;
note = (note - 11) * 2;
note = Cpu::getROM16(note + 0x0900, 1);
Cpu::setRAM16(GIGA_CH0_KEY_L + channel*GIGA_CHANNEL_OFFSET, note);
// Midi volume
if(_hasVolume)
{
uint8_t volume = *_midiStream++;
Cpu::setRAM(GIGA_CH0_WAV_A + channel*GIGA_CHANNEL_OFFSET, volume);
}
}
// Stop note
else if((command & 0xF0) == MIDI_CMD_STOP_NOTE)
{
uint8_t channel = command & GIGA_CHANNELS_MASK; // spec supports up to 16 channels, Gigatron supports 4
Cpu::setRAM16(GIGA_CH0_KEY_L + channel*GIGA_CHANNEL_OFFSET, 0x0000);
}
// New segment address
else if((command & 0xF0) == MIDI_CMD_JMP_SEG)
{
uint16_t segment = *_midiStream++;
segment |= HI_MASK((*_midiStream++) <<8);
_midiStream = (uint8_t*)(uintptr_t)segment;
}
}
// Delay n*16.66666667 milliseconds where n = 8bit value
else
{
_midiDelay = command;
}
}
}
#endif
}