MPU-401 support - UART working, intelligent not so much

This commit is contained in:
Ian Scott committed 2022-09-28 21:49:27 -06:00
1 parent 53a4f0d780
commit 714964fdb4
8 files changed
+1189 -11

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"board": {
"active_layer": 44,
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": true,
"hidden_nets": [],
+15 -3
View File
@@ -20,8 +20,9 @@ set(CMAKE_BUILD_TYPE "Release")
set(PICO_COPY_TO_RAM 1)
#set(SKIP_PICO_MALLOC 1)
set(PROJECT_TYPE "GUS")
# set(PROJECT_TYPE "GUS")
# set(PROJECT_TYPE "OPL")
set(PROJECT_TYPE "MPU")
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
@@ -44,7 +45,7 @@ target_compile_definitions(picogus PRIVATE
)
if(PROJECT_TYPE STREQUAL "GUS")
target_sources(picogus PRIVATE gusplay.cpp pico_pic.cpp)
target_sources(picogus PRIVATE gusplay.cpp pico_pic.c)
target_compile_definitions(picogus PRIVATE
SOUND_GUS=1
PSRAM=1
@@ -68,12 +69,23 @@ if(PROJECT_TYPE STREQUAL "OPL")
add_subdirectory(opl)
endif()
if(PROJECT_TYPE STREQUAL "MPU")
target_compile_definitions(picogus PRIVATE
SOUND_MPU=1
USE_ALARM=1
)
target_sources(picogus PRIVATE
pico_pic.c
mpu401/mpu401.c
mpu401/midi.c
)
endif()
pico_set_program_name(picogus "picogus")
pico_set_program_version(picogus "0.1")
pico_enable_stdio_uart(picogus 0)
pico_enable_stdio_usb(picogus 0)
pico_enable_stdio_usb(picogus 1)
pico_enable_stdio_semihosting(picogus 0)
pico_generate_pio_header(picogus ${CMAKE_CURRENT_LIST_DIR}/isa_io.pio)
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2002-2012 The DOSBox Team
* Copyright (C) 2013-2014 bjt, elianda
* Copyright (C) 2015 ab0tj
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef EXPORT_H
#ifdef __cplusplus
extern "C" {
#endif
// #define NULL 0 /* SOFTMPU */
#define RTCFREQ 4000 /* SOFTMPU: RTC interrupt frequency */
typedef unsigned char Bit8u;
typedef unsigned int Bitu;
// typedef enum bool {false = 0,true = 1} bool;
typedef enum EventID {MPU_EVENT,RESET_DONE,EOI_HANDLER,NUM_EVENTS} EventID;
/* Interface functions */
void MPU401_Init();
void MPU401_WriteCommand(Bit8u val);
Bit8u MPU401_ReadData(void);
void MPU401_WriteData(Bit8u val);
Bit8u QueueUsed();
void send_midi_byte();
void output_to_uart(Bit8u val);
void wait_for_uart();
Bit8u uart_tx_status();
#ifdef __cplusplus
}
#endif
#define EXPORT_H
#endif
+321
View File
@@ -0,0 +1,321 @@
/*
* Copyright (C) 2002-2013 The DOSBox Team
* Copyright (C) 2013-2014 bjt, elianda
* Copyright (C) 2015 ab0tj
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* --------------------------------------------
* HardMPU by ab0tj - Hardware MPU-401 Emulator
* --------------------------------------------
*
* Based on midi.c from SoftMPU by bjt which was
* based on original midi.c from DOSBox
*
*/
/* SOFTMPU: Moved exported functions & types to header */
#include "export.h"
/* HardMPU includes */
/*
#include "config.h"
#include <util/delay.h>
*/
#include "hardware/timer.h"
/* SOFTMPU: Additional defines, typedefs etc. for C */
typedef unsigned long Bit32u;
typedef int Bits;
#define SYSEX_SIZE 1024 // sysex buffer for delay calculation
/* RAWBUF: This is the buffer for outgoing MIDI data. The larger the buffer,
the less likely it is to overrun when SysEx delay is enabled and large SysEx
transfers are occurring. */
#define RAWBUF 14336
typedef struct ring_buffer {
unsigned char buffer[RAWBUF];
unsigned int head;
unsigned int tail;
} ring_buffer;
static ring_buffer midi_out_buff = { {0}, 0, 0 };
/* SOFTMPU: Note tracking for RA-50 */
#define MAX_TRACKED_CHANNELS 16
#define MAX_TRACKED_NOTES 8
static const char* MIDI_welcome_msg = "\xf0\x41\x10\x16\x12\x20\x00\x00 *** PicoGUS *** \x0a\xf7"; // message to show on MT-32 display
static Bit8u MIDI_note_off[3] = { 0x80,0x00,0x00 }; /* SOFTMPU */
static const Bit8u MIDI_evt_len[256] = {
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x00
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x10
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x20
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x30
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x40
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x50
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x60
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x70
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0x80
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0x90
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xa0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xb0
2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2, // 0xc0
2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2, // 0xd0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xe0
0,2,3,2, 0,0,1,0, 1,0,1,1, 1,0,1,0 // 0xf0
};
/* SOFTMPU: Note tracking for RA-50 */
typedef struct {
Bit8u used;
Bit8u next;
Bit8u notes[MAX_TRACKED_NOTES];
} channel;
channel tracked_channels[MAX_TRACKED_CHANNELS];
static struct {
Bit8u status;
Bit8u cmd_len;
Bit8u cmd_pos;
Bit8u cmd_buf[8];
Bit8u rt_buf[8];
struct {
Bit8u buf[SYSEX_SIZE];
Bitu used;
Bit8u usedbufs;
//Bitu delay;
bool delay;
Bit8u status;
} sysex;
bool fakeallnotesoff;
bool available;
/*MidiHandler * handler;*/ /* SOFTMPU */
} midi;
/* SOFTMPU: Sysex delay is decremented from PIC_Update */
volatile Bitu MIDI_sysex_delaytime;
static void PlayMsg(Bit8u* msg, Bitu len)
{
// despite the name of this function, we're just going to buffer this message to send later.
for (Bitu i = 0; i < len; i++) {
unsigned int next = (unsigned int)(midi_out_buff.head + 1) % RAWBUF;
if (next != midi_out_buff.tail) {
midi_out_buff.buffer[midi_out_buff.head] = msg[i];
midi_out_buff.head = next;
}
}
}
static void send_midi_byte_now(Bit8u byte) {
wait_for_uart();
output_to_uart(byte);
}
/* SOFTMPU: Fake "All Notes Off" for Roland RA-50 */
static void FakeAllNotesOff(Bit8u chan)
{
Bit8u note;
channel* pChan;
MIDI_note_off[0] &= 0xf0;
MIDI_note_off[0] |= chan;
pChan=&tracked_channels[chan];
for (note=0;note<pChan->used;note++)
{
MIDI_note_off[1]=pChan->notes[note];
PlayMsg(MIDI_note_off,3);
}
pChan->used=0;
pChan->next=0;
}
void MIDI_RawOutByte(Bit8u data) {
channel* pChan; /* SOFTMPU */
/* Test for a realtime MIDI message */
if (data>=0xf8) {
midi.rt_buf[0]=data;
PlayMsg(midi.rt_buf,1);
return;
}
if (data&0x80) {
midi.status=data;
midi.cmd_pos=0;
midi.cmd_len=MIDI_evt_len[data];
}
if (midi.cmd_len) {
midi.cmd_buf[midi.cmd_pos++]=data;
if (midi.cmd_pos >= midi.cmd_len) {
/*if (CaptureState & CAPTURE_MIDI) {
CAPTURE_AddMidi(false, midi.cmd_len, midi.cmd_buf);
}*/ /* SOFTMPU */
if (midi.fakeallnotesoff)
{
/* SOFTMPU: Test for "Note On" */
if ((midi.status&0xf0)==0x90)
{
if (midi.cmd_buf[2]>0)
{
pChan=&tracked_channels[midi.status&0x0f];
pChan->notes[pChan->next++]=midi.cmd_buf[1];
if (pChan->next==MAX_TRACKED_NOTES) pChan->next=0;
if (pChan->used<MAX_TRACKED_NOTES) pChan->used++;
}
PlayMsg(midi.cmd_buf,midi.cmd_len);
}
/* SOFTMPU: Test for "All Notes Off" */
else if (((midi.status&0xf0)==0xb0) &&
(midi.cmd_buf[1]>=0x7b) &&
(midi.cmd_buf[1]<=0x7f))
{
FakeAllNotesOff(midi.status&0x0f);
}
else
{
PlayMsg(midi.cmd_buf,midi.cmd_len);
}
}
else
{
PlayMsg(midi.cmd_buf,midi.cmd_len);
}
midi.cmd_pos=1; //Use Running status
}
}
else
{
midi.rt_buf[0] = data;
PlayMsg(midi.rt_buf,1);
}
}
void send_midi_byte() {
if (midi_out_buff.head == midi_out_buff.tail) return; // nothing to send
if (uart_tx_status()) return; // can't send yet
//loop_until_bit_is_set(UCSR0A, UDRE0);
if (midi.sysex.delay && MIDI_sysex_delaytime) { // still waiting for sysex delay
busy_wait_us(320);
return;
}
Bit8u data = midi_out_buff.buffer[midi_out_buff.tail];
midi_out_buff.tail = (unsigned int)(midi_out_buff.tail + 1) % RAWBUF; // increment tail, wrap to 0 if we're at the end
if (midi.sysex.status==0xf0) {
if (!(data&0x80)) {
if (midi.sysex.used==SYSEX_SIZE) {
midi.sysex.used = 0;
midi.sysex.usedbufs++;
}
output_to_uart(data);
midi.sysex.buf[midi.sysex.used++] = data;
return;
} else {
output_to_uart(0xf7);
midi.sysex.buf[midi.sysex.used++] = 0xf7;
midi.sysex.status = 0xf7;
/*LOG(LOG_ALL,LOG_NORMAL)("Play sysex; address:%02X %02X %02X, length:%4d, delay:%3d", midi.sysex.buf[5], midi.sysex.buf[6], midi.sysex.buf[7], midi.sysex.used, midi.sysex.delay);*/
if (midi.sysex.delay) {
if (midi.sysex.usedbufs == 0 && midi.sysex.buf[5] == 0x7F) {
/*midi.sysex.delay = 290;*/ /* SOFTMPU */ // All Parameters reset
MIDI_sysex_delaytime = 290*(RTCFREQ/1000);
} else if (midi.sysex.usedbufs == 0 && midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x04) {
/*midi.sysex.delay = 145;*/ /* SOFTMPU */ // Viking Child
MIDI_sysex_delaytime = 145*(RTCFREQ/1000);
} else if (midi.sysex.usedbufs == 0 && midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x01) {
/*midi.sysex.delay = 30;*/ /* SOFTMPU */ // Dark Sun 1
MIDI_sysex_delaytime = 30*(RTCFREQ/1000);
} else MIDI_sysex_delaytime = ((((midi.sysex.usedbufs*SYSEX_SIZE)+midi.sysex.used)/2)+2)*(RTCFREQ/1000); /*(Bitu)(((float)(midi.sysex.used) * 1.25f) * 1000.0f / 3125.0f) + 2;
midi.sysex.start = GetTicks();*/ /* SOFTMPU */
}
return;
/*LOG(LOG_ALL,LOG_NORMAL)("Sysex message size %d",midi.sysex.used);*/ /* SOFTMPU */
/*if (CaptureState & CAPTURE_MIDI) {
CAPTURE_AddMidi( true, midi.sysex.used-1, &midi.sysex.buf[1]);
}*/ /* SOFTMPU */
}
}
if (data&0x80) {
midi.sysex.status=data;
if (midi.sysex.status==0xf0) {
output_to_uart(0xf0);
midi.sysex.used=1;
midi.sysex.buf[0]=0xf0;
midi.sysex.usedbufs=0;
return;
}
}
output_to_uart(data); // not sysex
}
bool MIDI_Available(void) {
return midi.available;
}
/* SOFTMPU: Initialization */
void MIDI_Init(bool delaysysex,bool fakeallnotesoff){
Bit8u i; /* SOFTMPU */
MIDI_sysex_delaytime = 0; /* SOFTMPU */
midi.sysex.delay = delaysysex;
midi.status=0x00;
midi.sysex.status=0x00;
midi.cmd_pos=0;
midi.cmd_len=0;
midi.fakeallnotesoff=fakeallnotesoff>0;
midi.available=true;
midi_out_buff.head = midi_out_buff.tail = 0;
/* SOFTMPU: Display welcome message on MT-32 */
for (i=0;i<30;i++)
{
send_midi_byte_now(MIDI_welcome_msg[i]);
}
/* HardMPU: Turn off any stuck notes */
for (i=0xb0;i<0xc0;i++)
{
send_midi_byte_now(i);
send_midi_byte_now(0x7b);
send_midi_byte_now(0);
}
/* SOFTMPU: Init note tracking */
for (i=0;i<MAX_TRACKED_CHANNELS;i++)
{
tracked_channels[i].used=0;
tracked_channels[i].next=0;
}
}
+724
View File
@@ -0,0 +1,724 @@
/*
* Copyright (C) 2002-2012 The DOSBox Team
* Copyright (C) 2013-2014 bjt, elianda
* Copyright (C) 2015 ab0tj
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* --------------------------------------------
* HardMPU by ab0tj - Hardware MPU-401 Emulator
* --------------------------------------------
*
* Based on mpu401.c from SoftMPU by bjt which was
* based on original mpu401.c from DOSBox
*
*/
#include "hardware/uart.h"
/* SOFTMPU: Moved exported functions & types to header */
#include "export.h"
/* HardMPU includes */
/*
#include <avr/sfr_defs.h>
#include <avr/io.h>
*/
/* SOFTMPU: Additional defines, typedefs etc. for C */
typedef uint16_t Bit16u;
typedef int32_t Bits;
typedef int8_t Bit8s;
/*typedef void (*PIC_EventHandler)(Bitu val);*/ /* SOFTMPU */
#include "../pico_pic.h"
/* SOFTMPU: Stubbed functions */
#if 0
void PIC_Init(void); /* SOFTMPU */
void PIC_AddEvent(EventID event,Bitu delay);
void PIC_RemoveEvents(EventID event);
#endif
void MIDI_Init(bool delaysysex,bool fakeallnotesoff);
void MIDI_RawOutByte(Bit8u data);
bool MIDI_Available(void);
static uint32_t MPU401_Event(Bitu val);
static uint32_t MPU401_ResetDone(Bitu val);
static uint32_t MPU401_EOIHandler(Bitu val);
static void MPU401_Reset(void);
static void MPU401_EOIHandlerDispatch(void);
#define MPU401_VERSION 0x15
#define MPU401_REVISION 0x01
#define MPU401_QUEUE 32
//#define MPU401_TIMECONSTANT 58000 /* SOFTMPU: Was 60000 */
#define MPU401_TIMECONSTANT 60000000
#define MPU401_RESETBUSY 27*(RTCFREQ/1000) /* SOFTMPU */
enum MpuMode { M_UART,M_INTELLIGENT };
typedef enum MpuMode MpuMode; /* SOFTMPU */
enum MpuDataType {T_OVERFLOW,T_MARK,T_MIDI_SYS,T_MIDI_NORM,T_COMMAND};
typedef enum MpuDataType MpuDataType; /* SOFTMPU */
/* Messages sent to MPU-401 from host */
#define MSG_EOX 0xf7
#define MSG_OVERFLOW 0xf8
#define MSG_MARK 0xfc
/* Messages sent to host from MPU-401 */
#define MSG_MPU_OVERFLOW 0xf8
#define MSG_MPU_COMMAND_REQ 0xf9
#define MSG_MPU_END 0xfc
#define MSG_MPU_CLOCK 0xfd
#define MSG_MPU_ACK 0xfe
/* HardMPU: MPU config bits */
#define CONFIG_SYSEXDELAY (mpu.config & 0x80)
#define CONFIG_FAKEALLNOTESOFF (mpu.config & 0x40)
#define CONFIG_VERSIONFIX (mpu.config & 0x20)
#define CONFIG_MIDIPORT (mpu.config & 0x10)
static struct {
bool intelligent;
Bit8u config;
MpuMode mode;
Bit8u queue[MPU401_QUEUE];
Bit8u queue_pos,queue_used;
struct track {
Bits counter;
Bit8u value[8],sys_val;
Bit8u vlength,length;
MpuDataType type;
} playbuf[8],condbuf;
struct {
bool conductor,cond_req,cond_set, block_ack;
bool playing,reset;
bool wsd,wsm,wsd_start;
bool run_irq,irq_pending;
bool send_now;
bool eoi_scheduled;
Bit8s data_onoff;
Bitu command_byte,cmd_pending;
Bit8u tmask,cmask,amask;
Bit16u midi_mask;
Bit16u req_mask;
Bit8u channel,old_chan;
} state;
struct {
Bit8u timebase,old_timebase;
Bit8u tempo,old_tempo;
Bit8u tempo_rel,old_tempo_rel;
Bit8u tempo_grad;
Bit8u cth_rate,cth_counter;
bool clock_to_host,cth_active;
} clock;
} mpu;
Bit8u QueueUsed() {
return mpu.queue_used;
}
static void QueueByte(Bit8u data) {
if (mpu.state.block_ack) {mpu.state.block_ack=false;return;}
if (mpu.queue_used==0 && mpu.intelligent) {
mpu.state.irq_pending=true;
PIC_ActivateIRQ();
}
if (mpu.queue_used<MPU401_QUEUE) {
Bit8u pos=mpu.queue_used+mpu.queue_pos;
if (mpu.queue_pos>=MPU401_QUEUE) mpu.queue_pos-=MPU401_QUEUE;
if (pos>=MPU401_QUEUE) pos-=MPU401_QUEUE;
mpu.queue_used++;
mpu.queue[pos]=data;
} /*else LOG(LOG_MISC,LOG_NORMAL)("MPU401:Data queue full");*/ /* SOFTMPU */
}
static void ClrQueue(void) {
mpu.queue_used=0;
mpu.queue_pos=0;
}
void MPU401_WriteCommand(Bit8u val) { /* SOFTMPU */
Bit8u i; /* SOFTMPU */
if (mpu.state.reset) {
if (mpu.state.cmd_pending || (val!=0x3f && val!=0xff)) {
mpu.state.cmd_pending=val+1;
return;
}
PIC_RemoveEvents(MPU401_ResetDone);
mpu.state.reset=false;
}
if (val<=0x2f) {
switch (val&3) { /* MIDI stop, start, continue */
case 1: {MIDI_RawOutByte(0xfc);break;}
case 2: {MIDI_RawOutByte(0xfa);break;}
case 3: {MIDI_RawOutByte(0xfb);break;}
}
/*if (val&0x20) LOG(LOG_MISC,LOG_ERROR)("MPU-401:Unhandled Recording Command %x",val);*/ /* SOFTMPU */
switch (val&0xc) {
case 0x4: /* Stop */
PIC_RemoveEvents(MPU401_Event);
mpu.state.playing=false;
for (i=0xb0;i<0xbf;i++) { /* All notes off */
MIDI_RawOutByte(i);
MIDI_RawOutByte(0x7b);
MIDI_RawOutByte(0);
}
break;
case 0x8: /* Play */
/*LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Intelligent mode playback started");*/ /* SOFTMPU */
mpu.state.playing=true;
PIC_RemoveEvents(MPU_EVENT);
puts("start intell");
PIC_AddEvent(MPU401_Event,MPU401_TIMECONSTANT/((mpu.clock.tempo*mpu.clock.timebase*mpu.clock.tempo_rel)/0x40), 2);
ClrQueue();
break;
}
}
else if (val>=0xa0 && val<=0xa7) { /* Request play counter */
if (mpu.state.cmask&(1<<(val&7))) QueueByte((Bit8u)mpu.playbuf[val&7].counter);
}
else if (val>=0xd0 && val<=0xd7) { /* Send data */
mpu.state.old_chan=mpu.state.channel;
mpu.state.channel=val&7;
mpu.state.wsd=true;
mpu.state.wsm=false;
mpu.state.wsd_start=true;
}
else
switch (val) {
case 0xdf: /* Send system message */
mpu.state.wsd=false;
mpu.state.wsm=true;
mpu.state.wsd_start=true;
break;
case 0x8e: /* Conductor */
mpu.state.cond_set=false;
break;
case 0x8f:
mpu.state.cond_set=true;
break;
case 0x94: /* Clock to host */
mpu.clock.clock_to_host=false;
break;
case 0x95:
mpu.clock.clock_to_host=true;
break;
case 0xc2: /* Internal timebase */
mpu.clock.timebase=48/(RTCFREQ/1000); /* SOFTMPU */
break;
case 0xc3:
mpu.clock.timebase=72/(RTCFREQ/1000); /* SOFTMPU */
break;
case 0xc4:
mpu.clock.timebase=96/(RTCFREQ/1000); /* SOFTMPU */
break;
case 0xc5:
mpu.clock.timebase=120/(RTCFREQ/1000); /* SOFTMPU */
break;
case 0xc6:
mpu.clock.timebase=144/(RTCFREQ/1000); /* SOFTMPU */
break;
case 0xc7:
mpu.clock.timebase=168/(RTCFREQ/1000); /* SOFTMPU */
break;
case 0xc8:
mpu.clock.timebase=192/(RTCFREQ/1000); /* SOFTMPU */
break;
/* Commands with data byte */
case 0xe0: case 0xe1: case 0xe2: case 0xe4: case 0xe6:
case 0xe7: case 0xec: case 0xed: case 0xee: case 0xef:
mpu.state.command_byte=val;
break;
/* Commands 0xa# returning data */
case 0xab: /* Request and clear recording counter */
QueueByte(MSG_MPU_ACK);
QueueByte(0);
return;
case 0xac: /* Request version */
if (true)
{
/* SOFTMPU: Fix missing music in Gateway by reversing version and ACK */
QueueByte(MPU401_VERSION);
QueueByte(MSG_MPU_ACK);
}
else
{
QueueByte(MSG_MPU_ACK);
QueueByte(MPU401_VERSION);
}
return;
case 0xad: /* Request revision */
QueueByte(MSG_MPU_ACK);
QueueByte(MPU401_REVISION);
return;
case 0xae: /* HardMPU: Request config */
QueueByte(MSG_MPU_ACK);
QueueByte(mpu.config);
return;
case 0xaf: /* Request tempo */
QueueByte(MSG_MPU_ACK);
QueueByte(mpu.clock.tempo);
return;
case 0xb1: /* Reset relative tempo */
mpu.clock.tempo_rel=40;
break;
case 0xb9: /* Clear play map */
case 0xb8: /* Clear play counters */
for (i=0xb0;i<0xbf;i++) { /* All notes off */
MIDI_RawOutByte((Bit8u)i);
MIDI_RawOutByte(0x7b);
MIDI_RawOutByte(0);
}
for (i=0;i<8;i++) {
mpu.playbuf[i].counter=0;
mpu.playbuf[i].type=T_OVERFLOW;
}
mpu.condbuf.counter=0;
mpu.condbuf.type=T_OVERFLOW;
if (!(mpu.state.conductor=mpu.state.cond_set)) mpu.state.cond_req=0;
mpu.state.amask=mpu.state.tmask;
mpu.state.req_mask=0;
mpu.state.irq_pending=true;
break;
case 0xfe: /* HardMPU: Reset with config byte */
mpu.state.command_byte=val;
break;
case 0xff: /* Reset MPU-401 */
/*LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Reset %X",val);*/ /* SOFTMPU */
PIC_AddEvent(MPU401_ResetDone, MPU401_RESETBUSY, 0);
mpu.state.reset=true;
MPU401_Reset();
if (mpu.mode==M_UART) return;//do not send ack in UART mode
break;
case 0x3f: /* UART mode */
/*LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Set UART mode %X",val);*/ /* SOFTMPU */
mpu.mode=M_UART;
break;
default:
/*LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Unhandled command %X",val);*/
break;
}
QueueByte(MSG_MPU_ACK);
}
Bit8u MPU401_ReadData(void) { /* SOFTMPU */
Bit8u ret=MSG_MPU_ACK; // HardMPU: we shouldn't be running this function if the queue is empty.
//if (mpu.queue_used) {
if (mpu.queue_pos>=MPU401_QUEUE) mpu.queue_pos-=MPU401_QUEUE;
ret=mpu.queue[mpu.queue_pos];
mpu.queue_pos++;mpu.queue_used--;
//}
if (!mpu.intelligent) return ret;
if (mpu.queue_used == 0) PIC_DeActivateIRQ();
if (ret>=0xf0 && ret<=0xf7) { /* MIDI data request */
mpu.state.channel=ret&7;
mpu.state.data_onoff=0;
mpu.state.cond_req=false;
}
if (ret==MSG_MPU_COMMAND_REQ) {
mpu.state.data_onoff=0;
mpu.state.cond_req=true;
if (mpu.condbuf.type!=T_OVERFLOW) {
mpu.state.block_ack=true;
MPU401_WriteCommand(mpu.condbuf.value[0]);
if (mpu.state.command_byte) MPU401_WriteData(mpu.condbuf.value[1]);
}
mpu.condbuf.type=T_OVERFLOW;
}
if (ret==MSG_MPU_END || ret==MSG_MPU_CLOCK || ret==MSG_MPU_ACK) {
mpu.state.data_onoff=-1;
MPU401_EOIHandlerDispatch();
}
return ret;
}
void MPU401_WriteData(Bit8u val) { /* SOFTMPU */
static Bit8u length,cnt,posd; /* SOFTMPU */
if (mpu.mode==M_UART) { MIDI_RawOutByte(val); return; }
switch (mpu.state.command_byte) { /* 0xe# command data */
case 0x00:
break;
case 0xe0: /* Set tempo */
mpu.state.command_byte=0;
mpu.clock.tempo=val;
return;
case 0xe1: /* Set relative tempo */
mpu.state.command_byte=0;
/*if (val!=0x40) //default value
LOG(LOG_MISC,LOG_ERROR)("MPU-401:Relative tempo change not implemented");*/ /* SOFTMPU */
return;
case 0xe7: /* Set internal clock to host interval */
mpu.state.command_byte=0;
mpu.clock.cth_rate=val>>2;
return;
case 0xec: /* Set active track mask */
mpu.state.command_byte=0;
mpu.state.tmask=val;
return;
case 0xed: /* Set play counter mask */
mpu.state.command_byte=0;
mpu.state.cmask=val;
return;
case 0xee: /* Set 1-8 MIDI channel mask */
mpu.state.command_byte=0;
mpu.state.midi_mask&=0xff00;
mpu.state.midi_mask|=val;
return;
case 0xef: /* Set 9-16 MIDI channel mask */
mpu.state.command_byte=0;
mpu.state.midi_mask&=0x00ff;
mpu.state.midi_mask|=((Bit16u)val)<<8;
return;
//case 0xe2: /* Set graduation for relative tempo */
//case 0xe4: /* Set metronome */
//case 0xe6: /* Set metronome measure length */
case 0xfe: /* Config byte */
mpu.state.command_byte=0;
mpu.config=val;
MPU401_Init();
QueueByte(MSG_MPU_ACK);
return;
default:
mpu.state.command_byte=0;
return;
}
if (mpu.state.wsd) { /* Directly send MIDI message */
if (mpu.state.wsd_start) {
mpu.state.wsd_start=0;
cnt=0;
switch (val&0xf0) {
case 0xc0:case 0xd0:
mpu.playbuf[mpu.state.channel].value[0]=val;
length=2;
break;
case 0x80:case 0x90:case 0xa0:case 0xb0:case 0xe0:
mpu.playbuf[mpu.state.channel].value[0]=val;
length=3;
break;
case 0xf0:
/*LOG(LOG_MISC,LOG_ERROR)("MPU-401:Illegal WSD byte");*/ /* SOFTMPU */
mpu.state.wsd=0;
mpu.state.channel=mpu.state.old_chan;
return;
default: /* MIDI with running status */
cnt++;
MIDI_RawOutByte(mpu.playbuf[mpu.state.channel].value[0]);
}
}
if (cnt<length) {MIDI_RawOutByte(val);cnt++;}
if (cnt==length) {
mpu.state.wsd=0;
mpu.state.channel=mpu.state.old_chan;
}
return;
}
if (mpu.state.wsm) { /* Directly send system message */
if (val==MSG_EOX) {MIDI_RawOutByte(MSG_EOX);mpu.state.wsm=0;return;}
if (mpu.state.wsd_start) {
mpu.state.wsd_start=0;
cnt=0;
switch (val) {
case 0xf2:{ length=3; break;}
case 0xf3:{ length=2; break;}
case 0xf6:{ length=1; break;}
case 0xf0:{ length=0; break;}
default:
length=0;
}
}
if (!length || cnt<length) {MIDI_RawOutByte(val);cnt++;}
if (cnt==length) mpu.state.wsm=0;
return;
}
if (mpu.state.cond_req) { /* Command */
switch (mpu.state.data_onoff) {
case -1:
return;
case 0: /* Timing byte */
mpu.condbuf.vlength=0;
if (val<0xf0) mpu.state.data_onoff++;
else {
mpu.state.data_onoff=-1;
MPU401_EOIHandlerDispatch();
return;
}
if (val==0) mpu.state.send_now=true;
else mpu.state.send_now=false;
mpu.condbuf.counter=val;
break;
case 1: /* Command byte #1 */
mpu.condbuf.type=T_COMMAND;
if (val==0xf8 || val==0xf9) mpu.condbuf.type=T_OVERFLOW;
mpu.condbuf.value[mpu.condbuf.vlength]=val;
mpu.condbuf.vlength++;
if ((val&0xf0)!=0xe0) MPU401_EOIHandlerDispatch();
else mpu.state.data_onoff++;
break;
case 2:/* Command byte #2 */
mpu.condbuf.value[mpu.condbuf.vlength]=val;
mpu.condbuf.vlength++;
MPU401_EOIHandlerDispatch();
break;
}
return;
}
switch (mpu.state.data_onoff) { /* Data */
case -1:
return;
case 0: /* Timing byte */
if (val<0xf0) mpu.state.data_onoff=1;
else {
mpu.state.data_onoff=-1;
MPU401_EOIHandlerDispatch();
return;
}
if (val==0) mpu.state.send_now=true;
else mpu.state.send_now=false;
mpu.playbuf[mpu.state.channel].counter=val;
break;
case 1: /* MIDI */
mpu.playbuf[mpu.state.channel].vlength++;
posd=mpu.playbuf[mpu.state.channel].vlength;
if (posd==1) {
switch (val&0xf0) {
case 0xf0: /* System message or mark */
if (val>0xf7) {
mpu.playbuf[mpu.state.channel].type=T_MARK;
mpu.playbuf[mpu.state.channel].sys_val=val;
length=1;
} else {
/*LOG(LOG_MISC,LOG_ERROR)("MPU-401:Illegal message");*/ /* SOFTMPU */
mpu.playbuf[mpu.state.channel].type=T_MIDI_SYS;
mpu.playbuf[mpu.state.channel].sys_val=val;
length=1;
}
break;
case 0xc0: case 0xd0: /* MIDI Message */
mpu.playbuf[mpu.state.channel].type=T_MIDI_NORM;
length=mpu.playbuf[mpu.state.channel].length=2;
break;
case 0x80: case 0x90: case 0xa0: case 0xb0: case 0xe0:
mpu.playbuf[mpu.state.channel].type=T_MIDI_NORM;
length=mpu.playbuf[mpu.state.channel].length=3;
break;
default: /* MIDI data with running status */
posd++;
mpu.playbuf[mpu.state.channel].vlength++;
mpu.playbuf[mpu.state.channel].type=T_MIDI_NORM;
length=mpu.playbuf[mpu.state.channel].length;
break;
}
}
if (!(posd==1 && val>=0xf0)) mpu.playbuf[mpu.state.channel].value[posd-1]=val;
if (posd==length) MPU401_EOIHandlerDispatch();
}
}
static void MPU401_IntelligentOut(Bit8u chan) {
Bit8u val, i; /* SOFTMPU */
switch (mpu.playbuf[chan].type) {
case T_OVERFLOW:
break;
case T_MARK:
val=mpu.playbuf[chan].sys_val;
if (val==0xfc) {
MIDI_RawOutByte(val);
mpu.state.amask&=~(1<<chan);
mpu.state.req_mask&=~(1<<chan);
}
break;
case T_MIDI_NORM:
for (i=0;i<mpu.playbuf[chan].vlength;i++)
MIDI_RawOutByte(mpu.playbuf[chan].value[i]);
break;
default:
break;
}
}
static void UpdateTrack(Bit8u chan) {
MPU401_IntelligentOut(chan);
if (mpu.state.amask&(1<<chan)) {
mpu.playbuf[chan].vlength=0;
mpu.playbuf[chan].type=T_OVERFLOW;
mpu.playbuf[chan].counter=0xf0;
mpu.state.req_mask|=(1<<chan);
} else {
if (mpu.state.amask==0 && !mpu.state.conductor) mpu.state.req_mask|=(1<<12);
}
}
static void UpdateConductor(void) {
if (mpu.condbuf.value[0]==0xfc) {
mpu.condbuf.value[0]=0;
mpu.state.conductor=false;
mpu.state.req_mask&=~(1<<9);
if (mpu.state.amask==0) mpu.state.req_mask|=(1<<12);
return;
}
mpu.condbuf.vlength=0;
mpu.condbuf.counter=0xf0;
mpu.state.req_mask|=(1<<9);
}
uint32_t MPU401_Event(Bitu val) {
/* SOFTMPU */
putc('.');
Bit8u i;
Bitu new_time;
if (mpu.mode==M_UART) return 0;
if (mpu.state.irq_pending) goto next_event;
for (i=0;i<8;i++) { /* Decrease counters */
if (mpu.state.amask&(1<<i)) {
mpu.playbuf[i].counter--;
if (mpu.playbuf[i].counter<=0) UpdateTrack(i);
}
}
if (mpu.state.conductor) {
mpu.condbuf.counter--;
if (mpu.condbuf.counter<=0) UpdateConductor();
}
if (mpu.clock.clock_to_host) {
mpu.clock.cth_counter++;
if (mpu.clock.cth_counter >= mpu.clock.cth_rate) {
mpu.clock.cth_counter=0;
mpu.state.req_mask|=(1<<13);
}
}
if (!mpu.state.irq_pending && mpu.state.req_mask) MPU401_EOIHandler(0);
next_event:
PIC_RemoveEvents(MPU_EVENT);
if ((new_time=((mpu.clock.tempo*mpu.clock.timebase*mpu.clock.tempo_rel)/0x40))==0) return 0;
return (Bitu)MPU401_TIMECONSTANT/new_time;
}
static void MPU401_EOIHandlerDispatch(void) {
if (mpu.state.send_now) {
mpu.state.eoi_scheduled=true;
PIC_AddEvent(MPU401_EOIHandler,1, 1); // Possible a bit longer
}
else if (!mpu.state.eoi_scheduled) MPU401_EOIHandler(0);
}
//Updates counters and requests new data on "End of Input"
uint32_t MPU401_EOIHandler(Bitu val) {
Bit8u i=0; /* SOFTMPU */
mpu.state.eoi_scheduled=false;
if (mpu.state.send_now) {
mpu.state.send_now=false;
if (mpu.state.cond_req) UpdateConductor();
else UpdateTrack(mpu.state.channel);
}
mpu.state.irq_pending=false;
if (!mpu.state.playing || !mpu.state.req_mask) return 0;
do {
if (mpu.state.req_mask&(1<<i)) {
QueueByte(0xf0+i);
mpu.state.req_mask&=~(1<<i);
break;
}
} while ((i++)<16);
return 0;
}
static uint32_t MPU401_ResetDone(Bitu val) { /* SOFTMPU */
mpu.state.reset=false;
if (mpu.state.cmd_pending) {
MPU401_WriteCommand(mpu.state.cmd_pending-1);
mpu.state.cmd_pending=0;
}
return 0;
}
static void MPU401_Reset(void) {
Bit8u i; /* SOFTMPU */
PIC_DeActivateIRQ();
mpu.mode=(mpu.intelligent ? M_INTELLIGENT : M_UART);
PIC_RemoveEvents(MPU401_EOIHandler);
mpu.state.eoi_scheduled=false;
mpu.state.wsd=false;
mpu.state.wsm=false;
mpu.state.conductor=false;
mpu.state.cond_req=false;
mpu.state.cond_set=false;
mpu.state.playing=false;
mpu.state.run_irq=false;
mpu.state.irq_pending=false;
mpu.state.cmask=0xff;
mpu.state.amask=mpu.state.tmask=0;
mpu.state.midi_mask=0xffff;
mpu.state.data_onoff=0;
mpu.state.command_byte=0;
mpu.state.block_ack=false;
mpu.clock.tempo=mpu.clock.old_tempo=100;
mpu.clock.timebase=mpu.clock.old_timebase=120/(RTCFREQ/1000);
mpu.clock.tempo_rel=mpu.clock.old_tempo_rel=40;
mpu.clock.tempo_grad=0;
mpu.clock.clock_to_host=false;
mpu.clock.cth_rate=60;
mpu.clock.cth_counter=0;
ClrQueue();
mpu.state.req_mask=0;
mpu.condbuf.counter=0;
mpu.condbuf.type=T_OVERFLOW;
for (i=0;i<8;i++) {mpu.playbuf[i].type=T_OVERFLOW;mpu.playbuf[i].counter=0;}
}
/* HardMPU: Initialisation */
void MPU401_Init()
{
/* Initalise PIC code */
PIC_Init();
/* Initialise MIDI handler */
MIDI_Init(CONFIG_SYSEXDELAY,CONFIG_FAKEALLNOTESOFF);
if (!MIDI_Available()) return;
mpu.queue_used=0;
mpu.queue_pos=0;
mpu.mode=M_UART;
mpu.intelligent=true; /* Default is on */
if (!mpu.intelligent) return;
/* SOFTMPU: Moved IRQ 9 handler init to asm */
MPU401_Reset();
}
/* HardMPU: Output a byte to the physical UART */
void output_to_uart(Bit8u val)
{
uart_write_blocking(uart0, &val, 1);
}
/* HardMPU: Wait for UART TX buffer to be empty */
void wait_for_uart()
{
uart_tx_wait_blocking(uart0);
}
/* HardMPU: Check UART TX status, returns 0 for ready */
Bit8u uart_tx_status()
{
return uart_is_writable(uart0) ? 0 : 1;
}
+3 -3
View File
@@ -2,7 +2,7 @@
#include "pico/time.h"
#include <cstdio>
#include <stdio.h>
// A fixed pool of only 3 events for now. gus-x only has 3 different timer events - two timers and one DMA
PIC_TimerEvent timerEvents[3];
@@ -64,13 +64,13 @@ void PIC_Init() {
irq_set_priority(TIMER_IRQ_2, PICO_HIGHEST_IRQ_PRIORITY);
for (int i = 0; i < 3; ++i) {
timerEvents[i].alarm_id = 0;
timerEvents[i].handler = nullptr;
timerEvents[i].handler = 0;
}
#else
for (int i = 0; i < 3; ++i) {
timerEvents[i].active = false;
timerEvents[i].deadline = UINT32_MAX;
timerEvents[i].handler = nullptr;
timerEvents[i].handler = 0;
}
#endif
}
+10 -2
View File
@@ -13,9 +13,13 @@
#define IRQ_PIN 21 // TODO don't spread around pin definitions like this
#ifdef __cplusplus
extern "C" {
#endif
typedef uint32_t (* PIC_EventHandler)(Bitu val);
struct PIC_TimerEvent {
typedef struct {
PIC_EventHandler handler;
Bitu value;
#ifdef USE_ALARM
@@ -24,7 +28,7 @@ struct PIC_TimerEvent {
uint32_t deadline;
bool active;
#endif
};
} PIC_TimerEvent;
extern PIC_TimerEvent timerEvents[3];
@@ -74,3 +78,7 @@ __force_inline void PIC_HandleEvents() {
}
}
#endif
#ifdef __cplusplus
}
#endif
+63 -2
View File
@@ -6,8 +6,10 @@
// #include "hardware/vreg.h"
#include "hardware/regs/vreg_and_chip_reset.h"
#ifndef SOUND_MPU
#include "stdio_async_uart.h"
#endif
/*
#define UART_TX_PIN (0)
#define UART_RX_PIN (1)
@@ -49,6 +51,10 @@ constexpr uint16_t GUS_PORT_TEST = GUS_PORT >> 4 | 0x10;
void play_gus(void);
#endif
#ifdef SOUND_MPU
#include "mpu401/export.h"
#endif
constexpr uint LED_PIN = PICO_DEFAULT_LED_PIN;
static uint iow_sm;
@@ -110,6 +116,28 @@ __force_inline void handle_iow(void) {
pio_sm_put(pio0, iow_sm, 0x0u);
}
#endif // SOUND_OPL
#ifdef SOUND_MPU
switch (port) {
case 0x330:
pio_sm_put(pio0, iow_sm, 0xffffffffu);
value = iow_read & 0xFF;
// printf("MPU IOW: port: %x value: %x\n", port, value);
MPU401_WriteData(value);
// Tell PIO that we are done
pio_sm_put(pio0, iow_sm, 0x0u);
break;
case 0x331:
pio_sm_put(pio0, iow_sm, 0xffffffffu);
MPU401_WriteCommand(iow_read & 0xFF);
// printf("MPU IOW: port: %x value: %x\n", port, value);
__dsb();
// Tell PIO that we are done
pio_sm_put(pio0, iow_sm, 0x0u);
break;
default:
pio_sm_put(pio0, iow_sm, 0x0u);
}
#endif // SOUND_OPL
}
__force_inline void handle_ior(void) {
@@ -138,6 +166,19 @@ __force_inline void handle_ior(void) {
pio_sm_put(pio0, ior_sm, 0x0u);
}
#endif
#ifdef SOUND_MPU
if (port == 0x331) {
// Tell PIO to wait for data
pio_sm_put(pio0, ior_sm, 0xffffffffu);
value = MPU401_ReadData();
// printf("MPU IOR: port: %x value: %x\n", port, value);
// OR with 0x00ffff00 is required to set pindirs in the PIO
pio_sm_put(pio0, ior_sm, 0x00ffff00u | value);
} else {
// Reset PIO
pio_sm_put(pio0, ior_sm, 0x0u);
}
#endif
}
#ifdef USE_IRQ
@@ -175,7 +216,11 @@ int main()
// vreg_set_voltage(VREG_VOLTAGE_1_20);
// stdio_init_all();
#ifndef SOUND_MPU
stdio_async_uart_init_full(UART_ID, BAUD_RATE, UART_TX_PIN, UART_RX_PIN);
#else
stdio_init_all();
#endif
puts("PicoGUS booting!");
@@ -191,10 +236,21 @@ int main()
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
alarm_pool_init_default();
// alarm_pool_init_default();
gpio_init(IRQ_PIN);
gpio_set_dir(IRQ_PIN, GPIO_OUT);
#ifdef SOUND_MPU
puts("Initing MIDI UART...");
uart_init(UART_ID, 31250);
uart_set_translate_crlf(UART_ID, false);
uart_set_format(UART_ID, 8, 1, UART_PARITY_NONE);
gpio_set_drive_strength(UART_TX_PIN, GPIO_DRIVE_STRENGTH_12MA);
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
busy_wait_ms(1000);
MPU401_Init();
#endif
#ifdef PSRAM
puts("Initing PSRAM...");
psram_spi = psram_init();
@@ -324,8 +380,10 @@ int main()
irq_set_enabled(PIO0_IRQ_1, true);
#endif
#ifdef SOUND_GUS
puts("Initing ISA DMA PIO...");
dma_config = DMA_init(pio);
#endif
gpio_xor_mask(1u << LED_PIN);
@@ -347,6 +405,9 @@ int main()
#endif
#ifndef USE_ALARM
PIC_HandleEvents();
#endif
#ifdef SOUND_MPU
send_midi_byte(); // see if we need to send a byte
#endif
}
}