2023-05-23 10:53:35 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Helpers for UMP < - > MIDI 1.0 byte stream conversion
*/
# include <linux/module.h>
# include <linux/export.h>
# include <sound/core.h>
# include <sound/asound.h>
# include <sound/ump.h>
2023-06-23 10:55:30 +03:00
# include <sound/ump_convert.h>
2023-05-23 10:53:35 +03:00
/*
* Upgrade / downgrade value bits
*/
static u8 downscale_32_to_7bit ( u32 src )
{
return src > > 25 ;
}
static u16 downscale_32_to_14bit ( u32 src )
{
return src > > 18 ;
}
static u8 downscale_16_to_7bit ( u16 src )
{
return src > > 9 ;
}
static u16 upscale_7_to_16bit ( u8 src )
{
u16 val , repeat ;
val = ( u16 ) src < < 9 ;
if ( src < = 0x40 )
return val ;
repeat = src & 0x3f ;
return val | ( repeat < < 3 ) | ( repeat > > 3 ) ;
}
static u32 upscale_7_to_32bit ( u8 src )
{
u32 val , repeat ;
val = src < < 25 ;
if ( src < = 0x40 )
return val ;
repeat = src & 0x3f ;
return val | ( repeat < < 19 ) | ( repeat < < 13 ) |
( repeat < < 7 ) | ( repeat < < 1 ) | ( repeat > > 5 ) ;
}
static u32 upscale_14_to_32bit ( u16 src )
{
u32 val , repeat ;
val = src < < 18 ;
if ( src < = 0x2000 )
return val ;
repeat = src & 0x1fff ;
return val | ( repeat < < 5 ) | ( repeat > > 8 ) ;
}
/*
* UMP - > MIDI 1 byte stream conversion
*/
/* convert a UMP System message to MIDI 1.0 byte stream */
static int cvt_ump_system_to_legacy ( u32 data , unsigned char * buf )
{
buf [ 0 ] = ump_message_status_channel ( data ) ;
switch ( ump_message_status_code ( data ) ) {
case UMP_SYSTEM_STATUS_MIDI_TIME_CODE :
case UMP_SYSTEM_STATUS_SONG_SELECT :
buf [ 1 ] = ( data > > 8 ) & 0x7f ;
2023-06-28 12:43:52 +03:00
return 2 ;
2023-05-23 10:53:35 +03:00
case UMP_SYSTEM_STATUS_SONG_POSITION :
buf [ 1 ] = ( data > > 8 ) & 0x7f ;
buf [ 2 ] = data & 0x7f ;
return 3 ;
default :
return 1 ;
}
}
/* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */
static int cvt_ump_midi1_to_legacy ( u32 data , unsigned char * buf )
{
buf [ 0 ] = ump_message_status_channel ( data ) ;
buf [ 1 ] = ( data > > 8 ) & 0xff ;
switch ( ump_message_status_code ( data ) ) {
case UMP_MSG_STATUS_PROGRAM :
case UMP_MSG_STATUS_CHANNEL_PRESSURE :
return 2 ;
default :
buf [ 2 ] = data & 0xff ;
return 3 ;
}
}
/* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */
static int cvt_ump_midi2_to_legacy ( const union snd_ump_midi2_msg * midi2 ,
unsigned char * buf )
{
unsigned char status = midi2 - > note . status ;
unsigned char channel = midi2 - > note . channel ;
u16 v ;
buf [ 0 ] = ( status < < 4 ) | channel ;
switch ( status ) {
case UMP_MSG_STATUS_NOTE_OFF :
case UMP_MSG_STATUS_NOTE_ON :
buf [ 1 ] = midi2 - > note . note ;
buf [ 2 ] = downscale_16_to_7bit ( midi2 - > note . velocity ) ;
if ( status = = UMP_MSG_STATUS_NOTE_ON & & ! buf [ 2 ] )
buf [ 2 ] = 1 ;
return 3 ;
case UMP_MSG_STATUS_POLY_PRESSURE :
buf [ 1 ] = midi2 - > paf . note ;
buf [ 2 ] = downscale_32_to_7bit ( midi2 - > paf . data ) ;
return 3 ;
case UMP_MSG_STATUS_CC :
buf [ 1 ] = midi2 - > cc . index ;
buf [ 2 ] = downscale_32_to_7bit ( midi2 - > cc . data ) ;
return 3 ;
case UMP_MSG_STATUS_CHANNEL_PRESSURE :
buf [ 1 ] = downscale_32_to_7bit ( midi2 - > caf . data ) ;
return 2 ;
case UMP_MSG_STATUS_PROGRAM :
if ( midi2 - > pg . bank_valid ) {
buf [ 0 ] = channel | ( UMP_MSG_STATUS_CC < < 4 ) ;
buf [ 1 ] = UMP_CC_BANK_SELECT ;
buf [ 2 ] = midi2 - > pg . bank_msb ;
buf [ 3 ] = channel | ( UMP_MSG_STATUS_CC < < 4 ) ;
buf [ 4 ] = UMP_CC_BANK_SELECT_LSB ;
buf [ 5 ] = midi2 - > pg . bank_lsb ;
buf [ 6 ] = channel | ( UMP_MSG_STATUS_PROGRAM < < 4 ) ;
buf [ 7 ] = midi2 - > pg . program ;
return 8 ;
}
buf [ 1 ] = midi2 - > pg . program ;
return 2 ;
case UMP_MSG_STATUS_PITCH_BEND :
v = downscale_32_to_14bit ( midi2 - > pb . data ) ;
buf [ 1 ] = v & 0x7f ;
buf [ 2 ] = v > > 7 ;
return 3 ;
case UMP_MSG_STATUS_RPN :
case UMP_MSG_STATUS_NRPN :
buf [ 0 ] = channel | ( UMP_MSG_STATUS_CC < < 4 ) ;
buf [ 1 ] = status = = UMP_MSG_STATUS_RPN ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB ;
buf [ 2 ] = midi2 - > rpn . bank ;
buf [ 3 ] = buf [ 0 ] ;
buf [ 4 ] = status = = UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB ;
buf [ 5 ] = midi2 - > rpn . index ;
buf [ 6 ] = buf [ 0 ] ;
buf [ 7 ] = UMP_CC_DATA ;
v = downscale_32_to_14bit ( midi2 - > rpn . data ) ;
buf [ 8 ] = v > > 7 ;
buf [ 9 ] = buf [ 0 ] ;
buf [ 10 ] = UMP_CC_DATA_LSB ;
buf [ 11 ] = v & 0x7f ;
return 12 ;
default :
return 0 ;
}
}
/* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */
static int cvt_ump_sysex7_to_legacy ( const u32 * data , unsigned char * buf )
{
unsigned char status ;
unsigned char bytes ;
int size , offset ;
status = ump_sysex_message_status ( * data ) ;
if ( status > UMP_SYSEX_STATUS_END )
return 0 ; // unsupported, skip
bytes = ump_sysex_message_length ( * data ) ;
if ( bytes > 6 )
return 0 ; // skip
size = 0 ;
if ( status = = UMP_SYSEX_STATUS_SINGLE | |
status = = UMP_SYSEX_STATUS_START ) {
buf [ 0 ] = UMP_MIDI1_MSG_SYSEX_START ;
size = 1 ;
}
offset = 8 ;
for ( ; bytes ; bytes - - , size + + ) {
buf [ size ] = ( * data > > offset ) & 0x7f ;
if ( ! offset ) {
offset = 24 ;
data + + ;
} else {
offset - = 8 ;
}
}
if ( status = = UMP_SYSEX_STATUS_SINGLE | |
status = = UMP_SYSEX_STATUS_END )
buf [ size + + ] = UMP_MIDI1_MSG_SYSEX_END ;
return size ;
}
2023-06-23 10:55:30 +03:00
/**
* snd_ump_convert_from_ump - convert from UMP to legacy MIDI
* @ data : UMP packet
* @ buf : buffer to store legacy MIDI data
* @ group_ret : pointer to store the target group
*
* Convert from a UMP packet @ data to MIDI 1.0 bytes at @ buf .
* The target group is stored at @ group_ret .
*
* The function returns the number of bytes of MIDI 1.0 stream .
2023-05-23 10:53:35 +03:00
*/
2023-06-23 10:55:30 +03:00
int snd_ump_convert_from_ump ( const u32 * data ,
2023-05-23 10:53:35 +03:00
unsigned char * buf ,
unsigned char * group_ret )
{
* group_ret = ump_message_group ( * data ) ;
switch ( ump_message_type ( * data ) ) {
case UMP_MSG_TYPE_SYSTEM :
return cvt_ump_system_to_legacy ( * data , buf ) ;
case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE :
return cvt_ump_midi1_to_legacy ( * data , buf ) ;
case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE :
return cvt_ump_midi2_to_legacy ( ( const union snd_ump_midi2_msg * ) data ,
buf ) ;
case UMP_MSG_TYPE_DATA :
return cvt_ump_sysex7_to_legacy ( data , buf ) ;
}
return 0 ;
}
2023-06-23 10:55:30 +03:00
EXPORT_SYMBOL_GPL ( snd_ump_convert_from_ump ) ;
2023-05-23 10:53:35 +03:00
/*
* MIDI 1 byte stream - > UMP conversion
*/
/* convert MIDI 1.0 SysEx to a UMP packet */
static int cvt_legacy_sysex_to_ump ( struct ump_cvt_to_ump * cvt ,
unsigned char group , u32 * data , bool finish )
{
unsigned char status ;
bool start = cvt - > in_sysex = = 1 ;
int i , offset ;
if ( start & & finish )
status = UMP_SYSEX_STATUS_SINGLE ;
else if ( start )
status = UMP_SYSEX_STATUS_START ;
else if ( finish )
status = UMP_SYSEX_STATUS_END ;
else
status = UMP_SYSEX_STATUS_CONTINUE ;
* data = ump_compose ( UMP_MSG_TYPE_DATA , group , status , cvt - > len ) ;
offset = 8 ;
for ( i = 0 ; i < cvt - > len ; i + + ) {
* data | = cvt - > buf [ i ] < < offset ;
if ( ! offset ) {
offset = 24 ;
data + + ;
} else
offset - = 8 ;
}
cvt - > len = 0 ;
if ( finish )
cvt - > in_sysex = 0 ;
else
cvt - > in_sysex + + ;
return 8 ;
}
/* convert to a UMP System message */
static int cvt_legacy_system_to_ump ( struct ump_cvt_to_ump * cvt ,
unsigned char group , u32 * data )
{
data [ 0 ] = ump_compose ( UMP_MSG_TYPE_SYSTEM , group , 0 , cvt - > buf [ 0 ] ) ;
if ( cvt - > cmd_bytes > 1 )
data [ 0 ] | = cvt - > buf [ 1 ] < < 8 ;
if ( cvt - > cmd_bytes > 2 )
data [ 0 ] | = cvt - > buf [ 2 ] ;
return 4 ;
}
static void fill_rpn ( struct ump_cvt_to_ump_bank * cc ,
union snd_ump_midi2_msg * midi2 )
{
if ( cc - > rpn_set ) {
midi2 - > rpn . status = UMP_MSG_STATUS_RPN ;
midi2 - > rpn . bank = cc - > cc_rpn_msb ;
midi2 - > rpn . index = cc - > cc_rpn_lsb ;
cc - > rpn_set = 0 ;
cc - > cc_rpn_msb = cc - > cc_rpn_lsb = 0 ;
} else {
midi2 - > rpn . status = UMP_MSG_STATUS_NRPN ;
midi2 - > rpn . bank = cc - > cc_nrpn_msb ;
midi2 - > rpn . index = cc - > cc_nrpn_lsb ;
cc - > nrpn_set = 0 ;
cc - > cc_nrpn_msb = cc - > cc_nrpn_lsb = 0 ;
}
midi2 - > rpn . data = upscale_14_to_32bit ( ( cc - > cc_data_msb < < 7 ) |
cc - > cc_data_lsb ) ;
cc - > cc_data_msb = cc - > cc_data_lsb = 0 ;
}
/* convert to a MIDI 1.0 Channel Voice message */
2023-06-23 10:55:30 +03:00
static int cvt_legacy_cmd_to_ump ( struct ump_cvt_to_ump * cvt ,
unsigned char group ,
unsigned int protocol ,
u32 * data , unsigned char bytes )
2023-05-23 10:53:35 +03:00
{
const unsigned char * buf = cvt - > buf ;
struct ump_cvt_to_ump_bank * cc ;
union snd_ump_midi2_msg * midi2 = ( union snd_ump_midi2_msg * ) data ;
unsigned char status , channel ;
BUILD_BUG_ON ( sizeof ( union snd_ump_midi1_msg ) ! = 4 ) ;
BUILD_BUG_ON ( sizeof ( union snd_ump_midi2_msg ) ! = 8 ) ;
/* for MIDI 1.0 UMP, it's easy, just pack it into UMP */
2023-06-23 10:55:30 +03:00
if ( protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1 ) {
2023-05-23 10:53:35 +03:00
data [ 0 ] = ump_compose ( UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE ,
group , 0 , buf [ 0 ] ) ;
data [ 0 ] | = buf [ 1 ] < < 8 ;
if ( bytes > 2 )
data [ 0 ] | = buf [ 2 ] ;
return 4 ;
}
status = * buf > > 4 ;
channel = * buf & 0x0f ;
cc = & cvt - > bank [ channel ] ;
/* special handling: treat note-on with 0 velocity as note-off */
if ( status = = UMP_MSG_STATUS_NOTE_ON & & ! buf [ 2 ] )
status = UMP_MSG_STATUS_NOTE_OFF ;
/* initialize the packet */
data [ 0 ] = ump_compose ( UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE ,
group , status , channel ) ;
data [ 1 ] = 0 ;
switch ( status ) {
case UMP_MSG_STATUS_NOTE_ON :
case UMP_MSG_STATUS_NOTE_OFF :
midi2 - > note . note = buf [ 1 ] ;
midi2 - > note . velocity = upscale_7_to_16bit ( buf [ 2 ] ) ;
break ;
case UMP_MSG_STATUS_POLY_PRESSURE :
midi2 - > paf . note = buf [ 1 ] ;
midi2 - > paf . data = upscale_7_to_32bit ( buf [ 2 ] ) ;
break ;
case UMP_MSG_STATUS_CC :
switch ( buf [ 1 ] ) {
case UMP_CC_RPN_MSB :
cc - > rpn_set = 1 ;
cc - > cc_rpn_msb = buf [ 2 ] ;
return 0 ; // skip
case UMP_CC_RPN_LSB :
cc - > rpn_set = 1 ;
cc - > cc_rpn_lsb = buf [ 2 ] ;
return 0 ; // skip
case UMP_CC_NRPN_MSB :
cc - > nrpn_set = 1 ;
cc - > cc_nrpn_msb = buf [ 2 ] ;
return 0 ; // skip
case UMP_CC_NRPN_LSB :
cc - > nrpn_set = 1 ;
cc - > cc_nrpn_lsb = buf [ 2 ] ;
return 0 ; // skip
case UMP_CC_DATA :
cc - > cc_data_msb = buf [ 2 ] ;
return 0 ; // skip
case UMP_CC_BANK_SELECT :
cc - > bank_set = 1 ;
cc - > cc_bank_msb = buf [ 2 ] ;
return 0 ; // skip
case UMP_CC_BANK_SELECT_LSB :
cc - > bank_set = 1 ;
cc - > cc_bank_lsb = buf [ 2 ] ;
return 0 ; // skip
case UMP_CC_DATA_LSB :
cc - > cc_data_lsb = buf [ 2 ] ;
if ( cc - > rpn_set | | cc - > nrpn_set )
fill_rpn ( cc , midi2 ) ;
else
return 0 ; // skip
break ;
default :
midi2 - > cc . index = buf [ 1 ] ;
midi2 - > cc . data = upscale_7_to_32bit ( buf [ 2 ] ) ;
break ;
}
break ;
case UMP_MSG_STATUS_PROGRAM :
midi2 - > pg . program = buf [ 1 ] ;
if ( cc - > bank_set ) {
midi2 - > pg . bank_valid = 1 ;
midi2 - > pg . bank_msb = cc - > cc_bank_msb ;
midi2 - > pg . bank_lsb = cc - > cc_bank_lsb ;
cc - > bank_set = 0 ;
}
break ;
case UMP_MSG_STATUS_CHANNEL_PRESSURE :
midi2 - > caf . data = upscale_7_to_32bit ( buf [ 1 ] ) ;
break ;
case UMP_MSG_STATUS_PITCH_BEND :
midi2 - > pb . data = upscale_14_to_32bit ( buf [ 1 ] | ( buf [ 2 ] < < 7 ) ) ;
break ;
default :
return 0 ;
}
return 8 ;
}
2023-06-23 10:55:30 +03:00
static int do_convert_to_ump ( struct ump_cvt_to_ump * cvt , unsigned char group ,
unsigned int protocol , unsigned char c , u32 * data )
2023-05-23 10:53:35 +03:00
{
/* bytes for 0x80-0xf0 */
static unsigned char cmd_bytes [ 8 ] = {
3 , 3 , 3 , 3 , 2 , 2 , 3 , 0
} ;
/* bytes for 0xf0-0xff */
static unsigned char system_bytes [ 16 ] = {
0 , 2 , 3 , 2 , 0 , 0 , 1 , 0 , 1 , 1 , 1 , 1 , 0 , 0 , 1 , 1
} ;
unsigned char bytes ;
if ( c = = UMP_MIDI1_MSG_SYSEX_START ) {
cvt - > in_sysex = 1 ;
cvt - > len = 0 ;
return 0 ;
}
if ( c = = UMP_MIDI1_MSG_SYSEX_END ) {
if ( ! cvt - > in_sysex )
return 0 ; /* skip */
return cvt_legacy_sysex_to_ump ( cvt , group , data , true ) ;
}
if ( ( c & 0xf0 ) = = UMP_MIDI1_MSG_REALTIME ) {
bytes = system_bytes [ c & 0x0f ] ;
if ( ! bytes )
return 0 ; /* skip */
if ( bytes = = 1 ) {
data [ 0 ] = ump_compose ( UMP_MSG_TYPE_SYSTEM , group , 0 , c ) ;
return 4 ;
}
cvt - > buf [ 0 ] = c ;
cvt - > len = 1 ;
cvt - > cmd_bytes = bytes ;
cvt - > in_sysex = 0 ; /* abort SysEx */
return 0 ;
}
if ( c & 0x80 ) {
2023-05-25 11:31:24 +03:00
bytes = cmd_bytes [ ( c > > 4 ) & 7 ] ;
2023-05-23 10:53:35 +03:00
cvt - > buf [ 0 ] = c ;
cvt - > len = 1 ;
cvt - > cmd_bytes = bytes ;
cvt - > in_sysex = 0 ; /* abort SysEx */
return 0 ;
}
if ( cvt - > in_sysex ) {
cvt - > buf [ cvt - > len + + ] = c ;
if ( cvt - > len = = 6 )
return cvt_legacy_sysex_to_ump ( cvt , group , data , false ) ;
return 0 ;
}
if ( ! cvt - > len )
return 0 ;
cvt - > buf [ cvt - > len + + ] = c ;
if ( cvt - > len < cvt - > cmd_bytes )
return 0 ;
cvt - > len = 1 ;
if ( ( cvt - > buf [ 0 ] & 0xf0 ) = = UMP_MIDI1_MSG_REALTIME )
return cvt_legacy_system_to_ump ( cvt , group , data ) ;
2023-06-23 10:55:30 +03:00
return cvt_legacy_cmd_to_ump ( cvt , group , protocol , data , cvt - > cmd_bytes ) ;
2023-05-23 10:53:35 +03:00
}
2023-06-23 10:55:30 +03:00
/**
* snd_ump_convert_to_ump - convert legacy MIDI byte to UMP packet
* @ cvt : converter context
* @ group : target UMP group
* @ protocol : target UMP protocol
* @ c : MIDI 1.0 byte data
*
* Feed a MIDI 1.0 byte @ c and convert to a UMP packet if completed .
* The result is stored in the buffer in @ cvt .
2023-05-23 10:53:35 +03:00
*/
2023-06-23 10:55:30 +03:00
void snd_ump_convert_to_ump ( struct ump_cvt_to_ump * cvt , unsigned char group ,
unsigned int protocol , unsigned char c )
2023-05-23 10:53:35 +03:00
{
2023-06-23 10:55:30 +03:00
cvt - > ump_bytes = do_convert_to_ump ( cvt , group , protocol , c , cvt - > ump ) ;
2023-05-23 10:53:35 +03:00
}
2023-06-23 10:55:30 +03:00
EXPORT_SYMBOL_GPL ( snd_ump_convert_to_ump ) ;