From aefdc8d0edad5d77d73ac40d8002363e12da00da Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Tue, 9 Aug 2022 02:56:10 -0400 Subject: fures, ostendite se! --- .gitignore | 1 + LICENSE | 19 ++ README.md | 119 ++++++++++++ amaps/gs.amap | 101 +++++++++++ amaps/sd-80.amap | 441 +++++++++++++++++++++++++++++++++++++++++++++ amaps/xv-5080.amap | 471 ++++++++++++++++++++++++++++++++++++++++++++++++ data/.gitignore | 3 + data/gm2pcbanks | 2 + dump_patches.py | 54 ++++++ inspect_memoryregion.py | 76 ++++++++ srx_convert.py | 280 ++++++++++++++++++++++++++++ test_amap.py | 9 + wave_stats.py | 64 +++++++ xv/__init__.py | 1 + xv/amap.py | 265 +++++++++++++++++++++++++++ xv/model.py | 140 ++++++++++++++ xv/util.py | 90 +++++++++ 17 files changed, 2136 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 amaps/gs.amap create mode 100644 amaps/sd-80.amap create mode 100644 amaps/xv-5080.amap create mode 100644 data/.gitignore create mode 100644 data/gm2pcbanks create mode 100644 dump_patches.py create mode 100644 inspect_memoryregion.py create mode 100644 srx_convert.py create mode 100644 test_amap.py create mode 100644 wave_stats.py create mode 100644 xv/__init__.py create mode 100644 xv/amap.py create mode 100644 xv/model.py create mode 100644 xv/util.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..89eb657 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022 Chris Xiong + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..973d5fd --- /dev/null +++ b/README.md @@ -0,0 +1,119 @@ +# Fifteen Thieves + +**XV fures** + +Interface for talking to Roland synthesizers, and a few extra utilities (mostly in Python). + +## List of tools + +- `dump_patches.py`: Dump factory patches from a SD-80 or SD-90. +- `inspect_memoryregion.py`: Show the content of a `.memoryregion` file generated from patch dumps + in a somewhat human-readable form. +- `srx_convert.py`: Convert dumped SD-80 / SD-90 patches to Roland SRX VSTi plugin. +- `test_amap.py`: Load an address map file and print the memory structure specified by it. +- `wave_stats.py`: Generate stats on usage of SD-80 / SD-90 waveforms from the dumps. + +## Other files + +- `amap`: System exclusive address maps. + - `gs.amap`: Address map for SOUNDCanvas GS devices. + - `sd-80.amap`: Address map for STUDIOCanvas SD-80 and SD-90. + - `xv-5080.amap`: Address map for XV-5080 and other models in the XV lineup. +- `xv`: Python library for interfacing with Roland DT1/RQ1 system exclusive messages. + +## Caution + +- Many scripts contain hardcoded MIDI ports. This is subject to change, but before such changes + happen, you'll have to modify them manually. + +- pypy is recommended for running all the scripts whenever possible. + +- All scripts are developed under Linux. There's no reason they wouldn't work on other major + platforms, though (given you have specified the correct MIDI port). + +## Information for individual tools + +### `dump_patches.py` + +Requires hardware SD-90 or SD-80. + +Give this script a few minutes to run, then you'll get the following files: + +``` +data + +--patchparam + | +--classical + | | +--00-000-Piano 1.memoryregion + | | +--... + | +--contemporary + | | +--00-000-Ac.Piano.memoryregion + | | +--... + | +--... + +--rhythmparam + +--classical + | +--00-Standard Set.memoryregion + | +--... + +--contemporary + | +--00-StandardSet2.memoryregion + | +--... + +--... +``` + +These files contain preset data for each individual factory patch and can be used +by other tools. + +### `inspect_memoryregion.py` + +Requires one argument (path to the `.memoryregion` file). Prints the content of the given file +in a semi-human-readable form. + +Needs `data/sdwavelist` to work. This file is still distributed by Roland Japan. You need to +remove the header from the file (the first line contains name of the first waveform). + +### `srx_convert.py` + +Requires populated `data/patchparam` and `data/rhythmparam` folder. Converts patch data in +these folders into a format that can be used by Roland SRX VSTi plugins. You need to mod +the plugin with wave ROM dump of SD-90 or SD-80 for these patches to work. You'll have to +obtain the dump yourself, as they are not in this repository for obvious reasons. Of course, +you can use these patches with unmodified SRX VSTi plugins as well. + +Takes one argument, specifying the SRX VSTi title you want to generate the patches for. + + | SRX Plguin | Parameter Used | + | --------------- | -------------- | + | Piano 1 | 2 | + | Studio | 3 | + | Strings | 4 | + | Dance Trax | 5 | + | Orchestra | 6 | + | Keyboards | 7 | + | World | 9 | + | Brass | 10 | + | Piano 2 | 11 | + | Electric Piano | 12 | + +Generated patches are in `data/srxsdpresets`. Subfolders indicates different organization +of patches. + +### `test_amap.py` + +Load an address map file and print the memory structure specified by it. The path to the amap +file is hardcoded. + +### `wave_stats.py` + +Requires populated `data/patchparam` and `data/rhythmparam` folder. Print how many times each +waveform is used by all the patches. + +Needs `data/sdwavelist` to work. This file is still distributed by Roland Japan. You need to +remove the header from the file (the first line contains name of the first waveform). + +## Disclaimer + +This project is in no way endorsed by or affiliated with Roland Corporation. +This project contains no reverse engineered code, or data originated from Roland. +All memory address maps are transcribed from public documentation. Misuse of this +project can lead to legal disputes, and the author of this project shall not be +held responsible for such misuse. + diff --git a/amaps/gs.amap b/amaps/gs.amap new file mode 100644 index 0000000..f398df9 --- /dev/null +++ b/amaps/gs.amap @@ -0,0 +1,101 @@ +# Address mapping for Roland SoundCanvas/GS Devices +# Reference: MIDI Implementation of Roland SC-8850/SC-8820/SC-88Pro/SC-88/SC-55 (MkII) + +sc_root* + 000000, system # 88/88p/8850/8820 only + 100000, display # 55/55M2/88/88p + 101000, function_control # 55M2 only + 200000, user_patches # 88/88p/8850/8820 only + 210000, user_drums # 88/88p/8850/8820 only + 400000, patch_common + 401000, patch_parts + 403000, info # 55 only + 410000, drum_setup + 480000, patch_bulk # 55/55M2 only + 490000, drum_bulk # 55/55M2 only + 500000, patch_common # 88/88p only, alternate port + 501000, patch_parts # 88/88p only, alternate port + 510000, drum_setup # 88/88p only, alternate port + +display + 0000, 07, char_display[20] + 0100, 07, dot_display[64] +# 88/88p only + 0140, 07, dot_display2[64] + 0200, 07, dot_display3[64] + 0240, 07, dot_display4[64] + 0300, 07, dot_display5[64] + 0340, 07, dot_display6[64] + 0400, 07, dot_display7[64] + 0440, 07, dot_display8[64] + 0500, 07, dot_display9[64] + 0540, 07, dot_display10[64] +# end 88/88p only +# 55M2 only + 0800, 03, display_type + 0801, 02, peak_hold + 0810, 03, parameter_displayed +# end 55M2 only +# 88/88p only + 2000, 04, page_select + 2001, 04, display_time +# end 88/88p only + +function_control + 0000, 04, part_select + 0001, 01, minus_one + 0002, 01, solo + 0100, 01, all_mute + 0111, 01, part_mute[16] + +system* + 007F, 01, system_mode + 0100, 02, part_port[64] + +patch_common + 0000, p4, master_tune + 0004, 07, master_volume + 0005, 07, master_key_shift + 0006, 07, master_pan + 007F, 01, mode_set + 0100, 07, patch_name[16] + 0130, 03, reverb_macro + 0131, 03, reverb_character + 0132, 03, reverb_filter + 0133, 07, reverb_level + 0134, 07, reverb_time + 0135, 07, reverb_feedback + 0137, 07, reverb_predelay + 0138, 03, chorus_macro + 0139, 03, chorus_filter + 013a, 07, chorus_level + 013b, 07, chorus_feedback + 013c, 07, chorus_delay + 013d, 07, chorus_rate + 013e, 07, chorus_depth + 013f, 07, chorus_send_reverb + 0140, 07, chorus_send_delay + 0150, 04, delay_macro + 0151, 03, delay_filter + 0152, 07, delay_time_c + 0153, 07, delay_time_ratio_l + 0154, 07, delay_time_ratio_r + 0155, 07, delay_level_c + 0156, 07, delay_level_l + 0157, 07, delay_level_r + 0158, 07, delay_level + 0159, 07, delay_feedback + 015a, 07, delay_send_reverb + 0200, 01, eq_low_freq + 0201, 07, eq_low_gain + 0202, 01, eq_high_freq + 0203, 07, eq_high_gain + 0300, 07, efx_type + 0303, 07, efx_param[20] + 0317, 07, efx_send_reverb + 0318, 07, efx_send_chorus + 0319, 07, efx_send_delay + 031b, 07, efx_ctrl_src[2+2] + 031c, 07, efx_ctrl_depth[2+2] + 031f, 01, efx_send_eq + diff --git a/amaps/sd-80.amap b/amaps/sd-80.amap new file mode 100644 index 0000000..5f442eb --- /dev/null +++ b/amaps/sd-80.amap @@ -0,0 +1,441 @@ +# Address mapping for EDIROL SD-80 (without User Patch/User Rhythm block) +# Address mapping for EDIROL SD-90 (without Audio block) +# Reference: MIDI Implementation of Edirol SD-90 + +sd_root* + 00000000, setup + 01000000, system +# 02000000, audio{SD-90 only} + 10000000, multitimbre + 11000000, part[32+200000] +# 30000000, user_patch[128+10000]{SD-80 only} +# 40000000, user_rhythm[16+100000]{SD-80 only} + +system* + 000000, sys_common + 000200, sys_eq + +part* + 000000, patch + 100000, rhythm + +multitimbre* + 000000, mt_common + 000200, chorus + 000400, reverb + 000600, mfx + 002000, mt_part[32+100] + 004000, mt_midi[32+100] + +patch* + 000000, pc_common + 000200, chorus + 000400, reverb + 000600, mfx + 001000, tmt + 002000, tone[4+200] + +tmt* + 0000, tmt_common + 0005, tmt_block[4+9] + +tone* + 0000, tn_common + 000c, tn_send + 0012, tn_rx + 0027, tn_wave + 003a, tn_pitch_env + 0048, tn_filter + 005e, tn_amp + 006d, tn_lfo[2+e] + +rhythm* + 000000, ry_common + 000200, chorus + 000400, reverb + 000600, mfx + 001000, rtone[88+200] + +rtone* + 0000, rt_common + 0016, rt_send + 001c, rt_rx + 0021, rt_wmt[4+1d] + 0115, rt_pitch_env + 0122, rt_filter + 0136, rt_amp + +setup + 0000, 07, native_on + +sys_common + 0000, p4, master_tune + 0004, 06, master_key_shift + 0005, 07, master_level + 0006, 07, sys_ctrl_src[4] +# The following four switches are exclusive to SD-80 + 000a, 01, mfx_switch + 000b, 01, chorus_switch + 000c, 01, reverb_switch + 000d, 01, scale_tune_switch + +sys_eq + 0000, 01, switch + 0001, 01, low_freq[8+4] + 0002, 05, low_gain[8+4] + 0003, 02, high_freq[8+4] + 0004, 05, high_gain[8+4] + +mt_common + 0000, 07, name[12] + 000c, 06, solo_part + 0010, 07, reserved[32] + 0030, 06, mfx_src[3] + 0033, 06, chorus_src + 0034, 06, reverb_src + 0035, 05, mfx_ctrl_ch[3+2] + 0036, 01, mfx_ctrl_port[3+2] + +mt_midi + 0000, 01, rx_program_change + 0001, 01, rx_bank_select + 0002, 01, rx_wheel + 0003, 01, rx_key_aftertouch + 0004, 01, rx_channel_aftertouch + 0005, 01, rx_modulation + 0006, 01, rx_volume + 0007, 01, rx_pan + 0008, 01, rx_expression + 0009, 01, rx_hold + 000a, 01, phase_lock + 000b, 03, velocity_curve + +mt_part + 0000, 04, rx_channel + 0001, 01, rx_switch + 0003, 01, rx_port + 0004, 07, bank_msb + 0005, 07, bank_lsb + 0006, 07, program_number + 0007, 07, volume + 0008, 07, pan + 0009, 07, coarse_tune + 000a, 07, fine_tune + 000b, 02, mono + 000c, 02, legato + 000d, 05, pitch_bend_range + 000e, 02, portamento_switch + 000f, p2, portamento_time + 0011, 07, cutoff + 0012, 07, resonance + 0013, 07, attack + 0014, 07, release + 0015, 03, octave_shift + 0016, 07, velocity_sens + 0017, 07, key_range_low + 0018, 07, key_range_high + 0019, 07, key_fade_lower + 001a, 07, key_fade_upper + 001b, 07, mute + 001c, 07, dry_send + 001d, 07, chorus_send + 001e, 07, reverb_send + 001f, 04, output + 0020, 02, dest_mfx + 0021, 07, decay + 0022, 07, vibrato_rate + 0023, 07, vibrato_depth + 0024, 07, vibrato_delay + 0025, 07, modulation_depth + 0026, 06, chaft_pitch + 0027, 07, chaft_filter + 0028, 07, chaft_amp + 0029, 07, chaft_lfo_pitch + 002a, 07, chaft_lfo_filter + 002b, 07, chaft_lfo_amp + 002c, 07, cc_assign + 002d, 06, cc_pitch + 002e, 07, cc_filter + 002f, 07, cc_amp + 0030, 07, cc_lfo_pitch + 0031, 07, cc_lfo_filter + 0032, 07, cc_lfo_amp + 0033, 07, scale_tune[12] + 003f, 02, gm2_inst_set + +pc_common + 0000, 07, name[12] + 000c, 07, category + 000d, 07, tone_type + 000e, 07, level + 000f, 07, pan + 0010, 07, priority + 0011, 07, coarse_tune + 0012, 07, fine_tune + 0013, 03, octave_shift + 0014, 02, stretch_tune + 0015, 07, analog_feel + 0016, 01, mono + 0017, 01, legato + 0018, 01, retrigger + 0019, 01, portamento_switch + 001a, 01, portamento_mode + 001b, 01, portamento_type + 001c, 01, portamento_start + 001d, 01, portamento_time + 001e, 01, clock_source + 001f, p2, tempo + 0021, 01, oneshot + 0022, 07, cutoff + 0023, 07, resonance + 0024, 07, attack + 0025, 07, release + 0026, 07, velocity_sens + 0027, 07, output + 0028, 01, tmt_control + 0029, 06, pbr_up + 002a, 06, pbr_down + 002b, 06, matrix_ctrl_1_src + 002c, 06, matrix_ctrl_1_dest[4+2] + 002d, 07, matrix_ctrl_1_sens[4+2] + 0034, 06, matrix_ctrl_2_src + 0035, 06, matrix_ctrl_2_dest[4+2] + 0036, 07, matrix_ctrl_2_sens[4+2] + 003d, 06, matrix_ctrl_3_src + 003e, 06, matrix_ctrl_3_dest[4+2] + 003f, 07, matrix_ctrl_3_sens[4+2] + 0046, 06, matrix_ctrl_4_src + 0047, 06, matrix_ctrl_4_dest[4+2] + 0048, 07, matrix_ctrl_4_sens[4+2] + 004f, 03, vibrato_lfo_waveform + 0050, 07, vibrato_rate + 0051, 07, vibrato_delay + 0052, 07, vibrato_attack + 0053, 07, vibrato_pitch + 0054, 07, vibrato_cutoff + 0055, 07, vibrato_amp + +tmt_common + 0000, 04, structure_12 + 0001, 02, booster_12 + 0002, 04, structure_34 + 0003, 02, booster_34 + 0004, 02, velocity_control + +tmt_block + 0000, 01, tone_switch + 0001, 07, key_range_lower + 0002, 07, key_range_upper + 0003, 07, key_fade_lower + 0004, 07, key_fade_upper + 0005, 07, velo_range_lower + 0006, 07, velo_range_upper + 0007, 07, velo_fade_lower + 0008, 07, velo_fade_upper + +tn_common + 0000, 07, level + 0001, 07, coarse_tune + 0002, 07, fine_tune + 0003, 05, pitch_random + 0004, 07, pan + 0005, 05, pan_keyfollow + 0006, 06, pan_random + 0007, 07, pan_alt + 0008, 01, env_mode + 0009, 02, delay_mode + 000a, p2, delay_time + +tn_send + 0000, 07, dry_send + 0001, 07, mfx_chorus_send + 0002, 07, mfx_reverb_send + 0003, 07, chorus_send + 0004, 07, reverb_send + 0005, 04, output + +tn_rx + 0000, 01, rx_wheel + 0001, 01, rx_expression + 0002, 01, rx_hold + 0003, 01, rx_pan_mode + 0004, 01, rx_redamper + 0005, 02, tone_control_switch[16] + +tn_wave + 0000, 02, wave_group_type + 0001, p4, wave_group_id + 0005, p4, wave_number_l + 0009, p4, wave_number_r + 000d, 02, gain + 000e, 01, fm + 000f, 02, fm_color + 0010, 05, fm_depth + 0011, 01, tempo_sync + 0012, 06, pitch_keyfollow + +tn_pitch_env + 0000, 07, env_depth + 0001, 07, env_velocity_sens + 0002, 07, env_t1_velocity_sens + 0003, 07, env_t4_velocity_sens + 0004, 07, env_time_keyfollow + 0005, 07, env_time[4] + 0009, 07, env_level[5] + +tn_filter + 0000, 03, type + 0001, 07, cutoff + 0002, 06, cutoff_keyfollow + 0003, 03, cutoff_velocity_curve + 0004, 07, cutoff_velocity_sens + 0005, 07, resonance + 0006, 07, resonance_velocity_sens + 0007, 07, env_depth + 0008, 07, env_velocity_curve + 0009, 07, env_velocity_sens + 000a, 07, env_t1_velocity_sens + 000b, 07, env_t4_velocity_sens + 000c, 07, env_time_keyfollow + 000d, 07, env_time[4] + 0011, 07, env_level[5] + +tn_amp + 0000, 05, bias_level + 0001, 07, bias_position + 0002, 02, bias_direction + 0003, 03, env_velocity_curve + 0004, 07, env_velocity_sens + 0005, 07, env_t1_velocity_sens + 0006, 07, env_t4_velocity_sens + 0007, 07, env_time_keyfollow + 0008, 07, env_time[4] + 000c, 07, env_level[3] + +tn_lfo + 0000, 07, waveform + 0001, p2, rate + 0003, 03, offset + 0004, 07, rate_detune + 0005, 07, delay + 0006, 07, delay_keyfollow + 0007, 02, fade_mode + 0008, 07, fade_time + 0009, 01, key_trigger + 000a, 07, pitch_depth + 000b, 07, filter_depth + 000c, 07, amp_depth + 000d, 07, pan_depth + +ry_common + 0000, 07, name[12] + 000c, 07, level + 000d, 07, clock_source + 000e, p2, tempo + 0010, 01, oneshot + 0011, 04, output + +rt_common + 0000, 07, name[12] + 000c, 01, assign_type + 000d, 05, mute_group + 000e, 07, level + 000f, 07, coarse_tune + 0010, 07, fine_tune + 0011, 05, pitch_random + 0012, 05, pan + 0013, 06, pan_random + 0014, 07, pan_alt + 0015, 01, env_mode + +rt_send + 0000, 07, dry_send + 0001, 07, mfx_chorus_send + 0002, 07, mfx_reverb_send + 0003, 07, chorus_send + 0004, 07, reverb_send + 0005, 04, output + +rt_rx + 0000, 06, pitch_bend_range + 0001, 01, rx_expression + 0002, 01, rx_hold + 0003, 01, rx_pan_mode + 0004, 02, velocity_control + +rt_wmt + 0000, 01, switch + 0001, 02, wave_group_type + 0002, p4, wave_group_id + 0006, p4, wave_number_l + 000a, p4, wave_number_r + 000e, 02, gain + 000f, 01, fm + 0010, 02, fm_color + 0011, 05, fm_depth + 0012, 01, tempo_sync + 0013, 07, coarse_tune + 0014, 07, fine_tune + 0015, 07, pan + 0016, 01, pan_random_switch + 0017, 02, pan_alt_switch + 0018, 07, level + 0019, 07, velo_range_lower + 001a, 07, velo_range_upper + 001b, 07, velo_fade_lower + 001c, 07, velo_fade_upper + +rt_pitch_env + 0000, 05, env_depth + 0001, 07, env_velocity_sens + 0002, 07, env_t1_velocity_sens + 0003, 07, env_t4_velocity_sens + 0004, 07, env_time[4] + 0008, 07, env_level[5] + +rt_filter + 0000, 03, type + 0001, 07, cutoff + 0002, 03, cutoff_velocity_curve + 0003, 07, cutoff_velocity_sens + 0004, 07, resonance + 0005, 07, resonance_velocity_sens + 0006, 07, env_depth + 0007, 07, env_velocity_curve + 0008, 07, env_velocity_sens + 0009, 07, env_t1_velocity_sens + 000a, 07, env_t4_velocity_sens + 000b, 07, env_time[4] + 000f, 07, env_level[5] + +rt_amp + 0000, 03, env_velocity_curve + 0001, 07, env_velocity_sens + 0002, 07, env_t1_velocity_sens + 0003, 07, env_t4_velocity_sens + 0004, 07, env_time[4] + 0008, 07, env_level[3] + +mfx + 0000, 07, type + 0001, 07, dry_send + 0002, 07, chorus_send + 0003, 07, reverb_send + 0004, 01, output + 0005, 07, ctrl_src[4+2] + 0006, 07, ctrl_sens[4+2] + 000d, 05, ctrl_assign[4] + 0011, p4, param[32+4] + +chorus + 0000, 04, type + 0001, 07, level + 0002, 02, output + 0003, 02, route + 0004, p4, param[12+4] + +reverb + 0000, 04, type + 0001, 07, level + 0002, 02, output + 0003, p4, param[20+4] diff --git a/amaps/xv-5080.amap b/amaps/xv-5080.amap new file mode 100644 index 0000000..4eff57c --- /dev/null +++ b/amaps/xv-5080.amap @@ -0,0 +1,471 @@ +# Address mapping for Roland XV-5080 +# Reference: MIDI Implementation of Roland XV-5080 + +xv_root* + 00000000, system + 10000000, performance + 11000000, part_perform[32+200000] + 1F000000, patch_or_rhythm + 20000000, user_performance[64+10000] + 30000000, user_patch[128+10000] + 40000000, user_rhythm[4+100000] + +system* + 000000, sys_common + 000200, sys_eq + 001000, sys_part[32+100] + +part_perform* + 000000, patch_or_rhythm + +user_performance* + 000000, performance + +user_patch* + 000000, patch + +user_rhythm* + 000000, rhythm + +patch_or_rhythm* + 000000, patch + 100000, rhythm + +performance* + 000000, pf_common + 000200, mfx + 000400, chorus + 000600, reverb + 001000, pf_midi[16+100] + 002000, pf_part[32+100] + +patch* + 000000, pc_common + 000200, mfx + 000400, chorus + 000600, reverb + 001000, tmt + 002000, tone[4+200] + 003000, split[88+20] + +rhythm* + 000000, ry_common + 000200, mfx + 000400, chorus + 000600, reverb + 001000, rtone[88+200] + +sys_common + 0000, 03, mode + 0001, p4, master_tune + 0005, 06, master_key_shift + 0006, 07, master_level + 0007, 01, scale_tune_switch + 0008, 01, patch_remain + 0009, 01, mix_parallel +#============================= + 000a, 01, mfx_switch + 000b, 01, chorus_switch + 000c, 01, reverb_switch +#============================= + 000d, 05, pf_ctrl_channel + 000e, 07, pf_msb + 000f, 07, pf_lsb + 0010, 07, pf_pc +#============================= + 0011, 04, patch_rx_channel + 0012, 07, pt_msb + 0013, 07, pt_lsb + 0014, 07, pt_pc +#============================= + 0015, 01, clock_source + 0016, p2, sys_tempo +#============================= + 0018, 07, sys_ctrl_src[4] +#============================= + 001c, 01, rx_program_change + 001d, 01, rx_bank_select + +sys_eq + 0000, 01, switch + 0001, 01, low_freq[8+4] + 0002, 05, low_gain[8+4] + 0003, 02, high_freq[8+4] + 0004, 05, high_gain[8+4] + +sys_part + 0000, 07, scale_tune[12] + +pf_common + 0000, 07, name[12] + 000c, 06, solo_part + 000d, 05, mfx_ctrl_ch + 000e, 01, mfx_ctrl_midi1 + 000f, 01, mfx_ctrl_midi1 + 0010, 07, voice_reserve[32] + 0030, 06, mfx_src[3] + 0033, 06, chorus_src + 0034, 06, reverb_src + +pf_midi + 0000, 01, rx_program_change + 0001, 01, rx_bank_select + 0002, 01, rx_wheel + 0003, 01, rx_key_aftertouch + 0004, 01, rx_channel_aftertouch + 0005, 01, rx_modulation + 0006, 01, rx_volume + 0007, 01, rx_pan + 0008, 01, rx_expression + 0009, 01, rx_hold + 000a, 01, phase_lock + 000b, 03, velocity_curve + +pf_part + 0000, 04, rx_channel + 0001, 01, rx_switch + 0003, 01, rx_port +#============================= + 0004, 07, bank_msb + 0005, 07, bank_lsb + 0006, 07, program_number +#============================= + 0007, 07, volume + 0008, 07, pan + 0009, 07, coarse_tune + 000a, 07, fine_tune + 000b, 02, mono + 000c, 02, legato + 000d, 05, pitch_bend_range + 000e, 02, portamento_switch + 000f, p2, portamento_time + 0011, 07, cutoff + 0012, 07, resonance + 0013, 07, attack + 0014, 07, release +#============================= + 0015, 03, octave_shift + 0016, 07, velocity_sens + 0017, 07, key_range_low + 0018, 07, key_range_high + 0019, 07, key_fade_lower + 001a, 07, key_fade_upper + 001b, 01, mute +#============================= + 001c, 07, dry_send + 001d, 07, chorus_send + 001e, 07, reverb_send + 001f, 04, output + 0020, 02, dest_mfx + +pc_common + 0000, 07, name[12] + 000c, 07, category +#============================= + 000d, 01, tone_type +#============================= + 000e, 07, level + 000f, 07, pan + 0010, 07, priority + 0011, 07, coarse_tune + 0012, 07, fine_tune + 0013, 03, octave_shift + 0014, 02, stretch_tune_depth + 0015, 07, analog_feel + 0016, 01, mono + 0017, 01, legato + 0018, 01, retrigger + 0019, 01, portamento_switch + 001a, 01, portamento_mode + 001b, 01, portamento_type + 001c, 01, portamento_start + 001d, 01, portamento_time + 001e, 01, clock_source + 001f, p2, tempo + 0021, 01, oneshot +#============================= + 0022, 07, cutoff + 0023, 07, resonance + 0024, 07, attack + 0025, 07, release + 0026, 07, velocity_sens +#============================= + 0027, 07, output +#============================= + 0028, 01, tmt_control + 0029, 06, pbr_up + 002a, 06, pbr_down +#============================= + 002b, 06, matrix_ctrl_1_src + 002c, 06, matrix_ctrl_1_dest[4+2] + 002d, 07, matrix_ctrl_1_sens[4+2] +#============================= + 0034, 06, matrix_ctrl_2_src + 0035, 06, matrix_ctrl_2_dest[4+2] + 0036, 07, matrix_ctrl_2_sens[4+2] +#============================= + 003d, 06, matrix_ctrl_3_src + 003e, 06, matrix_ctrl_3_dest[4+2] + 003f, 07, matrix_ctrl_3_sens[4+2] +#============================= + 0046, 06, matrix_ctrl_4_src + 0047, 06, matrix_ctrl_4_dest[4+2] + 0048, 07, matrix_ctrl_4_sens[4+2] + +tmt* + 0000, tmt_common + 0005, tmt_block[4+9] + +tmt_common + 0000, 04, structure_12 + 0001, 02, booster_12 + 0002, 04, structure_34 + 0003, 02, booster_34 + 0004, 02, velocity_control + +tmt_block + 0000, 01, tone_switch + 0001, 07, key_range_lower + 0002, 07, key_range_upper + 0003, 07, key_fade_lower + 0004, 07, key_fade_upper + 0005, 07, velo_range_lower + 0006, 07, velo_range_upper + 0007, 07, velo_fade_lower + 0008, 07, velo_fade_upper + +tone* + 0000, tn_common + 000c, tn_send + 0012, tn_rx + 0027, tn_wave + 003a, tn_pitch_env + 0048, tn_filter + 005e, tn_amp + 006d, tn_lfo[2+e] + +tn_common + 0000, 07, level + 0001, 07, coarse_tune + 0002, 07, fine_tune + 0003, 05, pitch_random + 0004, 07, pan + 0005, 05, pan_keyfollow + 0006, 06, pan_random + 0007, 07, pan_alt + 0008, 01, env_mode + 0009, 02, delay_mode + 000a, p2, delay_time + +tn_send + 0000, 07, dry_send + 0001, 07, mfx_chorus_send + 0002, 07, mfx_reverb_send + 0003, 07, chorus_send + 0004, 07, reverb_send + 0005, 04, output + +tn_rx + 0000, 01, rx_wheel + 0001, 01, rx_expression + 0002, 01, rx_hold + 0003, 01, rx_pan_mode + 0004, 01, rx_redamper + 0005, 02, tone_control_switch[16] + +tn_wave + 0000, 02, wave_group_type + 0001, p4, wave_group_id + 0005, p4, wave_number_l + 0009, p4, wave_number_r + 000d, 02, gain + 000e, 01, fm + 000f, 02, fm_color + 0010, 05, fm_depth + 0011, 01, tempo_sync + 0012, 06, pitch_keyfollow + +tn_pitch_env + 0000, 07, env_depth + 0001, 07, env_velocity_sens + 0002, 07, env_t1_velocity_sens + 0003, 07, env_t4_velocity_sens + 0004, 07, env_time_keyfollow + 0005, 07, env_time[4] + 0009, 07, env_level[5] + +tn_filter + 0000, 03, type + 0001, 07, cutoff + 0002, 06, cutoff_keyfollow + 0003, 03, cutoff_velocity_curve + 0004, 07, cutoff_velocity_sens + 0005, 07, resonance + 0006, 07, resonance_velocity_sens + 0007, 07, env_depth + 0008, 07, env_velocity_curve + 0009, 07, env_velocity_sens + 000a, 07, env_t1_velocity_sens + 000b, 07, env_t4_velocity_sens + 000c, 07, env_time_keyfollow + 000d, 07, env_time[4] + 0011, 07, env_level[5] + +tn_amp + 0000, 05, bias_level + 0001, 07, bias_position + 0002, 02, bias_direction + 0003, 03, env_velocity_curve + 0004, 07, env_velocity_sens + 0005, 07, env_t1_velocity_sens + 0006, 07, env_t4_velocity_sens + 0007, 07, env_time_keyfollow + 0008, 07, env_time[4] + 000c, 07, env_level[3] + +tn_lfo + 0000, 07, waveform + 0001, p2, rate + 0003, 03, offset + 0004, 07, rate_detune + 0005, 07, delay + 0006, 05, delay_keyfollow + 0007, 02, fade_mode + 0008, 07, fade_time + 0009, 01, key_trigger + 000a, 07, pitch_depth + 000b, 07, filter_depth + 000c, 07, amp_depth + 000d, 07, pan_depth + +split + 0000, p4, partial_no + 0004, 01, assign_type + 0005, 05, assign_group + 0006, 07, dry_send + 0007, 07, mfx_chorus_send + 0008, 07, mfx_reverb_send + 0009, 07, chorus_send + 000a, 07, reverb_send + 000b, 04, output + +rtone* + 0000, rt_common + 0016, rt_send + 001c, rt_rx + 0021, rt_wmt[4+1d] + 0115, rt_pitch_env + 0122, rt_filter + 0136, rt_amp + +ry_common + 0000, 07, name[12] + 000c, 07, level + 000d, 07, clock_source + 000e, p2, tempo + 0010, 01, oneshot + 0011, 04, output + +rt_common + 0000, 07, name[12] + 000c, 01, assign_type + 000d, 05, mute_group + 000e, 07, level + 000f, 07, coarse_tune + 0010, 07, fine_tune + 0011, 05, pitch_random + 0012, 05, pan + 0013, 06, pan_random + 0014, 07, pan_alt + 0015, 01, env_mode + +rt_send + 0000, 07, dry_send + 0001, 07, mfx_chorus_send + 0002, 07, mfx_reverb_send + 0003, 07, chorus_send + 0004, 07, reverb_send + 0005, 04, output + +rt_rx + 0000, 06, pitch_bend_range + 0001, 01, rx_expression + 0002, 01, rx_hold + 0003, 01, rx_pan_mode + 0004, 02, velocity_control + +rt_wmt + 0000, 01, switch + 0001, 02, wave_group_type + 0002, p4, wave_group_id + 0006, p4, wave_number_l + 000a, p4, wave_number_r + 000e, 02, gain + 000f, 01, fm + 0010, 02, fm_color + 0011, 05, fm_depth + 0012, 01, tempo_sync + 0013, 07, coarse_tune + 0014, 07, fine_tune + 0015, 07, pan + 0016, 01, pan_random_switch + 0017, 02, pan_alt_switch + 0018, 07, level + 0019, 07, velo_range_lower + 001a, 07, velo_range_upper + 001b, 07, velo_fade_lower + 001c, 07, velo_fade_upper + +rt_pitch_env + 0000, 05, env_depth + 0001, 07, env_velocity_sens + 0002, 07, env_t1_velocity_sens + 0003, 07, env_t4_velocity_sens + 0004, 07, env_time[4] + 0008, 07, env_level[5] + +rt_filter + 0000, 03, type + 0001, 07, cutoff + 0002, 03, cutoff_velocity_curve + 0003, 07, cutoff_velocity_sens + 0004, 07, resonance + 0005, 07, resonance_velocity_sens + 0006, 07, env_depth + 0007, 07, env_velocity_curve + 0008, 07, env_velocity_sens + 0009, 07, env_t1_velocity_sens + 000a, 07, env_t4_velocity_sens + 000b, 07, env_time[4] + 000f, 07, env_level[5] + +rt_amp + 0000, 03, env_velocity_curve + 0001, 07, env_velocity_sens + 0002, 07, env_t1_velocity_sens + 0003, 07, env_t4_velocity_sens + 0004, 07, env_time[4] + 0008, 07, env_level[3] + +mfx + 0000, 07, type + 0001, 07, dry_send + 0002, 07, chorus_send + 0003, 07, reverb_send + 0004, 02, output + 0005, 07, ctrl_src[4+2] + 0006, 07, ctrl_sens[4+2] + 000d, 05, ctrl_assign[4] + 0011, p4, param[32+4] + +chorus + 0000, 04, type + 0001, 07, level + 0003, 02, output + 0004, p4, param[12+4] + +reverb + 0000, 04, type + 0001, 07, level + 0002, 02, output + 0003, p4, param[20+4] diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..d5ea748 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!gm2pcbanks diff --git a/data/gm2pcbanks b/data/gm2pcbanks new file mode 100644 index 0000000..7143a9c --- /dev/null +++ b/data/gm2pcbanks @@ -0,0 +1,2 @@ +3,2,2,2,4,5,4,2,1,1,1,2,2,1,3,1,4,3,1,3,2,2,1,1,4,4,2,3,4,2,3,2,1,2,1,1,1,1,5,4,2,1,1,1,1,1,2,1,3,1,2,1,2,2,2,4,2,3,1,2,2,2,4,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,5,1,1,2,1,1,2,1,2,1,2,1,1,1,1,1,1,2,1,1,1,3,1,2,1,1,2,1,1,1,1,1,1,1,2,2,2,3,1,3,2,6,4,6,a,6,4 +0,8,16,24,25,32,40,48,56 diff --git a/dump_patches.py b/dump_patches.py new file mode 100644 index 0000000..22416c8 --- /dev/null +++ b/dump_patches.py @@ -0,0 +1,54 @@ +# Part of the Fifteen-Thieves Project +# Chris Xiong 2020 +# License: Expat (MIT) + +import time +import math +import sys +import xv.model +import xv.amap +import xv.util + +inst_sets=[(96,"classical"),(97,"contemporary"),(98,"solo"),(99,"enhanced"),(80,"special_1"),(81,"special_2")] +rhyt_sets=[(104,"classical"),(105,"contemporary"),(106,"solo"),(107,"enhanced")] + +if __name__ == "__main__": + print("loading address mapping...", flush=True) + m = xv.amap.AddrMapTemplate("amaps/sd-80.amap") + t = xv.amap.create_addr_map_tree(m) + print("creating device...", flush=True) + x = xv.model.Model("SD-80 Part A",[0x00, 0x48],0x10,t) + print("initializing device...", flush=True) + x.write_locations("sd_root.setup.native_on", 0) + time.sleep(0.05) + + gm2pcb=[] + gm2rhy=[] + with open("data/gm2pcbanks","r") as f: + gm2pcb = [int(x.strip(), 16) for x in f.readline().split(',')] + gm2rhy = [int(x.strip()) for x in f.readline().split(',')] + + for iset in inst_sets: + for pc in range(0, 128): + for bk in range(0, 1 if iset[0] < 96 else gm2pcb[pc]): + x.midio.send_message([0xb0,0x00,iset[0]]) + x.midio.send_message([0xb0,0x20,bk]) + x.midio.send_message([0xc0,pc]) + mr = x.read_locations("sd_root.part[0]", 1) + pcn = x.aroot.value("sd_root.part[0].patch.pc_common.name") + pcn = pcn.decode().strip().replace('/','∕') + print(f"{bk:02d}:{pc:03d} {pcn}") + fn = f"data/patchparam/{iset[1]}/{bk:02d}-{pc:03d}-{pcn}.memoryregion" + xv.util.write_memoryregion(fn, mr) + + for rset in rhyt_sets: + for pc in gm2rhy: + x.midio.send_message([0xb0,0x00,rset[0]]) + x.midio.send_message([0xb0,0x20,0]) + x.midio.send_message([0xc0,pc]) + mr = x.read_locations("sd_root.part[0]", 2) + pcn = x.aroot.value("sd_root.part[0].rhythm.ry_common.name") + pcn = pcn.decode().strip().replace('/','∕') + print(f"{pc:02d} {pcn}") + fn = f"data/rhythmparam/{rset[1]}/{pc:02d}-{pcn}.memoryregion" + xv.util.write_memoryregion(fn, mr) diff --git a/inspect_memoryregion.py b/inspect_memoryregion.py new file mode 100644 index 0000000..7fdccd2 --- /dev/null +++ b/inspect_memoryregion.py @@ -0,0 +1,76 @@ +# Part of the Fifteen-Thieves Project +# Chris Xiong 2020 +# License: Expat (MIT) +import sys +import xv.util +import xv.amap + +wavenames = ['None'] + +def dfs_checkpopulated(node): + node.populated = False + for ch in node.children_nodes: + if node.template.is_aggregate_node: + if type(ch) == list: + for el in ch: + node.populated |= dfs_checkpopulated(el) + else: + node.populated |= dfs_checkpopulated(ch) + else: + for a in node.get_data(): + if a != 0xff: + node.populated = True + break + return node.populated + +def dfs_print(node, level=0, instance=None): + if not node.populated: + return + ba_rep = ''.join([format(x, "02x") for x in xv.util.padbelist(xv.util.int2b7belist(node.base_address))]) + print(f"{ba_rep}{level*' '} {node.template.name}{'['+str(instance)+']' if instance is not None else ''}") + for child in node.children_nodes: + if node.template.is_aggregate_node: + if type(child) == list: + for i, element in enumerate(child): + dfs_print(element, level + 1, i) + else: + dfs_print(child, level + 1) + else: + if type(child) == list: + if child[0].name == "name": + print(f"{ba_rep}{(level + 1)*' '} {child[0].name} {node.data[child[0].offset:child[0].offset + 12].decode()}") + else: + for i, element in enumerate(child): + ba_rep = ''.join([format(x, "02x") for x in xv.util.padbelist(xv.util.int2b7belist(element.offset))]) + print(f"{ba_rep}{(level + 1)*' '} {element.name}[{i}] {xv.amap.get_leaf_data(node.data, element)}") + else: + ba_rep = ''.join([format(x, "02x") for x in xv.util.padbelist(xv.util.int2b7belist(child.offset))]) + if child.name.startswith("wave_number"): + try: + print(f"{ba_rep}{(level + 1)*' '} {child.name} {xv.amap.get_leaf_data(node.data, child)}: {wavenames[xv.amap.get_leaf_data(node.data, child)]}") + except IndexError: + print(f"{ba_rep}{(level + 1)*' '} {child.name} {xv.amap.get_leaf_data(node.data, child)}: unknown") + else: + print(f"{ba_rep}{(level + 1)*' '} {child.name} {xv.amap.get_leaf_data(node.data, child)}") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} ") + print("loading address mapping...", flush=True, file=sys.stderr) + m = xv.amap.AddrMapTemplate("amaps/sd-80.amap") + t = xv.amap.create_addr_map_tree(m) + t.data[:] = [0xff for i in range(0, len(t.data))] + + print("loading wave names...", file=sys.stderr) + with open("data/sdwavelist","r") as f: + for l in f: + wavenames += [l.split('\t')[1].strip()] + + print("loading memoryregion...", file=sys.stderr) + mr = xv.util.load_memoryregion(sys.argv[1]) + for addr, data in mr: + ra = xv.util.b7belist2int(addr) + t.data[ra:ra + len(data)] = data + + dfs_checkpopulated(t) + dfs_print(t) diff --git a/srx_convert.py b/srx_convert.py new file mode 100644 index 0000000..e7a98c1 --- /dev/null +++ b/srx_convert.py @@ -0,0 +1,280 @@ +# Part of the Fifteen-Thieves Project +# Chris Xiong 2022 +# License: Expat (MIT) + +import time +import math +import sys +from glob import glob +import xv.amap +import xv.util +import os + +inst_sets = [(96,"classical"),(97,"contemporary"),(98,"solo"),(99,"enhanced"),(80,"special_1"),(81,"special_2")] +rhyt_sets = [(104,"classical"),(105,"contemporary"),(106,"solo"),(107,"enhanced")] +mfx_map = [ 0, 1, 35, 36, -1, # 0 + -1, 7, 8, 21, -1, # 5 + -1, 26, 27, 28, 23, # 10 + 24, 25, 43, 46, 47, # 15 + 48, 53, 62, 61, 64, # 20 + 65, 66, 67, 68, 69, # 25 + 70, 71, 72, 73, 74, # 30 + 75, 76, 77, -1, -1, # 35 + -1, 11, -1, -1, 15, # 40 + 49, 50, 51, 52, -1, # 45 + 57, 56, 10, 37, 38, # 50 + 40, 41, 42, 20, 3, # 55 + 29, 30, 17, 18, -1, # 60 + -1, 9, -1, -1, -1, # 65 + -1, 22, -1, -1, -1, # 70 + -1, -1, -1, 39, -1, # 75 + -1, -1, -1, -1, -1, # 80 + -1, -1, -1, 2, -1, # 85 + -1] # 90 +mfx_offsets = [ + 0x0000, 0x48c3, 0x48f3, 0x491f, 0x494b, # 0 + 0x4967, 0x499f, 0x4a07, 0x4a1f, 0x4a57, # 5 + 0x4a97, 0x4aaf, 0x4ae7, 0x4b2b, 0x4b5f, # 10 + 0x4b83, 0x4ba3, 0x4c07, 0x4c2b, 0x4c4f, # 15 + 0x4caf, 0x4d17, 0x4d47, 0x4d8b, 0x4dbf, # 20 + 0x4df7, 0x4e3b, 0x4e67, 0x4e9b, 0x4ec7, # 25 + 0x4eff, 0x4f3b, 0x4f83, 0x4fc3, 0x500b, # 30 + 0x506b, 0x5087, 0x50a3, 0x50c7, 0x50eb, # 35 + 0x5133, 0x514f, 0x516f, 0x518f, 0x51cf, # 40 + 0x51ff, 0x523f, 0x528b, 0x52d7, 0x5333, # 45 + 0x539f, 0x540f, 0x544f, 0x549f, 0x54cb, # 50 + 0x54fb, 0x5537, 0x557b, 0x559f, 0x55c7, # 55 + 0x55db, 0x5613, 0x563f, 0x5693, 0x570f, # 60 + 0x5733, 0x5753, 0x577b, 0x57a7, 0x57cf, # 65 + 0x57f7, 0x5823, 0x584b, 0x5873, 0x589f, # 70 + 0x58c7, 0x58ff, 0x593b, 0x5977, 0x59c3 # 75 +] +# off chorus delay gm2 end +chorus_offsets = [0x0000, 0x59c3, 0x59e7, 0x5a1f, 0x5a3b] +# off reverb s.room s.hall s.plat gm2 end +reverb_offsets = [0x0000, 0x5a3b, 0x5a4b, 0x5a73, 0x5a9b, 0x5ac3, 0x5ad7] + +#srx-09 is NEX04, srx-07 is NEX02 + +headers = [ + b"", + b"", + b"KoaBankFile00003PG-NEX09", # Piano 1 / Concert Piano + b"KoaBankFile00003PG-NEX05", # Studio / Studio SRX + b"KoaBankFile00003PG-NEX07", # Strings / Symphonique Strings + b"KoaBankFile00003PG-NEX03", # Dance Trax / Supreme Dance + Platinum Trax + b"KoaBankFile00003PG-NEX01", # Orchestra / Complete Orchestra + b"KoaBankFile00003PG-NEX02", # Keyboards / Ultimate Keys + b"KoaBankFile00003PG-NEX03", # Dance Trax / Supreme Dance + Platinum Trax + b"KoaBankFile00003PG-NEX04", # World / World Collection + b"KoaBankFile00003PG-NEX08", # Brass / Big Brass Ensemble + b"KoaBankFile00003PG-NEX10", # Piano 2 / Complete Piano + b"KoaBankFile00003PG-NEX06", # Electric Piano / Classic EPs + ] + +header = b"KoaBankFile00003PG-NEX02" +template = b"" +amap = xv.amap.AddrMapTemplate("amaps/sd-80.amap") +tree = xv.amap.create_addr_map_tree(amap) +tree.data[:] = [0xff for i in range(0, len(tree.data))] + +def load_memregion(path): + mr = xv.util.load_memoryregion(path) + for addr, data in mr: + ra = xv.util.b7belist2int(addr) + tree.data[ra:ra + len(data)] = data + +def clip_or_pad(b, l): + if len(b) < l: + return b + b'\00' * (l - len(b)) + else: return b[0:l] + +def convert_patch(iset, bk, pc): + ret = bytearray(template) + name = tree.value("sd_root.part[0].patch.pc_common.name") + ret[0:12] = name + + common = tree.value("sd_root.part[0].patch.pc_common") + ret[0x54 : 0x54 + len(common)] = common + + patch_common = tree.find_node("sd_root.part[0].patch.pc_common") + do_mod_lfo = patch_common.value("vibrato_pitch") != 64 or \ + patch_common.value("vibrato_cutoff") != 64 or \ + patch_common.value("vibrato_amp") != 64 + + tmt = tree.value("sd_root.part[0].patch.tmt") + ret[0x1dc : 0x1dc + len(tmt)] = tmt + + tone_offsets = [0x205, 0x29f, 0x339, 0x3d3] + for t in range(0, 4): + tree.set_value(f"sd_root.part[0].patch.tone[{t}].tn_wave.wave_group_type", 1) + if do_mod_lfo: + lfo = [] + lfo.append(tree.find_node(f"sd_root.part[0].patch.tone[{t}].tn_lfo[0]")) + lfo.append(tree.find_node(f"sd_root.part[0].patch.tone[{t}].tn_lfo[1]")) + dst_lfo = -1 + for i in range(0, 2): + if lfo[i].value("pitch_depth") == 64 and \ + lfo[i].value("filter_depth") == 64 and \ + lfo[i].value("amp_depth") == 64 and \ + lfo[i].value("pan_depth") == 64: + dst_lfo = i + break + if dst_lfo == -1: + print(f"WARNING ({iset}, {bk}, {pc}): cannot relocate modulation LFO for tone {t}") + else: + lfo[dst_lfo].set_value("waveform", patch_common.value("vibrato_lfo_waveform")) + lfo[dst_lfo].set_value("rate", patch_common.value("vibrato_rate")) + lfo[dst_lfo].set_value("offset", 2) + lfo[dst_lfo].set_value("rate_detune", 0) + lfo[dst_lfo].set_value("delay", patch_common.value("vibrato_delay")) + lfo[dst_lfo].set_value("delay_keyfollow", 64) + lfo[dst_lfo].set_value("fade_mode", 0) + lfo[dst_lfo].set_value("fade_time", patch_common.value("vibrato_attack")) + lfo[dst_lfo].set_value("key_trigger", 0) + lfo[dst_lfo].set_value("pitch_depth", patch_common.value("vibrato_pitch")) + lfo[dst_lfo].set_value("filter_depth", patch_common.value("vibrato_cutoff")) + lfo[dst_lfo].set_value("amp_depth", patch_common.value("vibrato_amp")) + lfo[dst_lfo].set_value("pan_depth", 64) + tone = tree.value(f"sd_root.part[0].patch.tone[{t}]") + ret[tone_offsets[t] : tone_offsets[t] + len(tone)] = tone + + mfx = tree.value("sd_root.part[0].patch.mfx") + mfx_type = tree.value("sd_root.part[0].patch.mfx.type") + ret[0xa4 : 0xa4 + 13] = mfx[0:13] + mapped_mfx = mfx_map[tree.value("sd_root.part[0].patch.mfx.type")] + if mapped_mfx == -1: + print(f"WARNING ({iset}, {bk}, {pc}): no valid MFX mapped (XV MFX #{mfx_type})") + else: + ret[0xa4] = mapped_mfx + if mapped_mfx != 0: + ret[mfx_offsets[mapped_mfx] : mfx_offsets[mapped_mfx + 1]] = mfx[13 : 13 + mfx_offsets[mapped_mfx + 1] - mfx_offsets[mapped_mfx]] + + chorus = tree.value("sd_root.part[0].patch.chorus") + chorus_type = tree.value("sd_root.part[0].patch.chorus.type") + ret[0x135 : 0x135 + 4] = chorus[0:4] + if chorus_type != 0: + ret[chorus_offsets[chorus_type] : chorus_offsets[chorus_type + 1]] = clip_or_pad(chorus[4:], chorus_offsets[chorus_type + 1] - chorus_offsets[chorus_type]) + + reverb = tree.value("sd_root.part[0].patch.reverb") + reverb_type = tree.value("sd_root.part[0].patch.reverb.type") + ret[0x189 : 0x189 + 3] = reverb[0:3] + if reverb_type != 0: + ret[reverb_offsets[reverb_type] : reverb_offsets[reverb_type + 1]] = reverb[3 : 3 + reverb_offsets[reverb_type + 1] - reverb_offsets[reverb_type]] + + if len(ret) != 0x5ad7: + print(f"ERROR: ({iset}, {bk}, {pc}) messed up: {len(ret):x}!!! {chorus_type}") + return ret + +def convert_rhythm(rset, pc): + ret = bytearray(template) + name = tree.value("sd_root.part[0].rhythm.ry_common.name") + ret[0:12] = name + ret[0x0c] = 1 + + common = tree.value("sd_root.part[0].rhythm.ry_common") + ret[0x46d : 0x46d + len(common)] = common + + for key in range(0, 88): + for tone in range(0, 4): + tree.set_value(f"sd_root.part[0].rhythm.rtone[{key}].rt_wmt[{tone}].wave_group_type", 1) + rtone = tree.value(f"sd_root.part[0].rhythm.rtone[{key}]") + ret[0x5b7 + 0xc3 * key : 0x5b7 + 0xc3 * key + len(rtone)] = rtone + + mfx = tree.value("sd_root.part[0].rhythm.mfx") + mfx_type = tree.value("sd_root.part[0].rhythm.mfx.type") + ret[0x47f : 0x47f + 13] = mfx[0:13] + mapped_mfx = mfx_map[tree.value("sd_root.part[0].rhythm.mfx.type")] + if mapped_mfx == -1: + print(f"WARNING ({rset}, {pc}): no valid MFX mapped (XV MFX #{mfx_type})") + else: + ret[0xa4] = mapped_mfx + if mapped_mfx != 0: + ret[mfx_offsets[mapped_mfx] : mfx_offsets[mapped_mfx + 1]] = mfx[13 : 13 + mfx_offsets[mapped_mfx + 1] - mfx_offsets[mapped_mfx]] + + chorus = tree.value("sd_root.part[0].rhythm.chorus") + chorus_type = tree.value("sd_root.part[0].rhythm.chorus.type") + ret[0x510 : 0x510 + 4] = chorus[0:4] + if chorus_type != 0: + ret[chorus_offsets[chorus_type] : chorus_offsets[chorus_type + 1]] = clip_or_pad(chorus[4:], chorus_offsets[chorus_type + 1] - chorus_offsets[chorus_type]) + + reverb = tree.value("sd_root.part[0].rhythm.reverb") + reverb_type = tree.value("sd_root.part[0].rhythm.reverb.type") + ret[0x564 : 0x564 + 3] = reverb[0:3] + if reverb_type != 0: + ret[reverb_offsets[reverb_type] : reverb_offsets[reverb_type + 1]] = reverb[3 : 3 + reverb_offsets[reverb_type + 1] - reverb_offsets[reverb_type]] + + if len(ret) != 0x5ad7: + print(f"ERROR: ({iset}, {bk}, {pc}) messed up: {len(ret):x}!!!") + return ret + +def write_bank(fn, b): + if len(b) != 0x2d6b80: + print(f"ERROR: Invalid bank file: {len(b):x}") + return + with open(fn, "wb") as f: + f.write(header) + f.write(b) + +if __name__ == "__main__": + try: + os.mkdir("data/srxsdpresets") + except Exception: pass + try: + os.mkdir("data/srxsdpresets/by_order") + except Exception: pass + try: + os.mkdir("data/srxsdpresets/by_set") + except Exception: pass + + if len(sys.argv) > 1: + if int(sys.argv[1]) >= 2 and int(sys.argv[1]) <= 12: + header = headers[int(sys.argv[1])] + + gm2pcb = [] + gm2rhy = [] + with open("data/gm2pcbanks", "r") as f: + gm2pcb = [int(x.strip(), 16) for x in f.readline().split(',')] + gm2rhy = [int(x.strip()) for x in f.readline().split(',')] + with open("data/srx_vsti_preset_template", "rb") as f: + template = f.read() + + cur_prs = [bytearray() for i in range(0, 10)] + for iset in inst_sets: + patchdata = [bytearray(), bytearray()] + c = 0 + for pc in range(0, 128): + for bk in range(0, 1 if iset[0] < 96 else gm2pcb[pc]): + fn = glob(f"data/patchparam/{iset[1]}/{bk:02d}-{pc:03d}-*.memoryregion")[0] + load_memregion(fn) + p = convert_patch(iset[0], bk, pc) + patchdata[int(bk > 0)] += p + if iset[0] < 96: + cur_prs[iset[0] - 80 + 8] += p + else: + cur_prs[c // 32] += p + c += 1 + for bank in range(0, 1 + int(iset[0] >= 96)): + typ = "variation" if bank else "capital" + fn = f"data/srxsdpresets/by_set/{iset[1]}_{typ}.bin" + if iset[0] < 96: + fn = f"data/srxsdpresets/by_set/{iset[1]}.bin" + write_bank(fn, patchdata[bank]) + for i, b in enumerate(cur_prs): + write_bank(f"data/srxsdpresets/by_order/PR-{chr(ord('A') + i)}.bin", b) + + rhy_prs = bytearray() + for rset in rhyt_sets: + rhydata = bytearray() + for pc in range(0, 128): + if pc in gm2rhy: + fn = glob(f"data/rhythmparam/{rset[1]}/{pc:02d}-*.memoryregion")[0] + load_memregion(fn) + p = convert_rhythm(rset[0], pc) + rhydata += p + rhy_prs += p + else: rhydata += template + rhy_prs += 23 * template + fn = f"data/srxsdpresets/by_set/{rset[1]}_rhythm.bin" + write_bank(fn, rhydata) + write_bank(f"data/srxsdpresets/by_order/PR-K.bin", rhy_prs) diff --git a/test_amap.py b/test_amap.py new file mode 100644 index 0000000..1d028ce --- /dev/null +++ b/test_amap.py @@ -0,0 +1,9 @@ +# Part of the Fifteen-Thieves Project +# Chris Xiong 2020 +# License: Expat (MIT) + +import xv.amap + +m = xv.amap.AddrMapTemplate("amaps/sd-80.amap") +t = xv.amap.create_addr_map_tree(m) +t.dump() diff --git a/wave_stats.py b/wave_stats.py new file mode 100644 index 0000000..26dd2dc --- /dev/null +++ b/wave_stats.py @@ -0,0 +1,64 @@ +# Part of the Fifteen-Thieves Project +# Chris Xiong 2020 +# License: Expat (MIT) +import sys +import os +import xv.util +import xv.amap + +wavecnt = [0] * 1024 +wavenames = [] + +def process_preset(t, is_rhy=False): + if is_rhy: + for rkey in range(0, 85): + for rtone in range(0, 4): + if t.value(f"sd_root.part[0].rhythm.rtone[{rkey}].rt_wmt[{rtone}].switch"): + try: + wavecnt[t.value(f"sd_root.part[0].rhythm.rtone[{rkey}].rt_wmt[{rtone}].wave_number_l")] += 1 + wavecnt[t.value(f"sd_root.part[0].rhythm.rtone[{rkey}].rt_wmt[{rtone}].wave_number_r")] += 1 + except IndexError: + pass + else: + for tone in range(0, 4): + if t.value(f"sd_root.part[0].patch.tmt.tmt_block[{tone}].tone_switch"): + try: + wavecnt[t.value(f"sd_root.part[0].patch.tone[{tone}].tn_wave.wave_number_l")] += 1 + wavecnt[t.value(f"sd_root.part[0].patch.tone[{tone}].tn_wave.wave_number_r")] += 1 + except IndexError: + pass + +if __name__ == "__main__": + print("loading address mapping...", flush=True, file=sys.stderr) + m = xv.amap.AddrMapTemplate("amaps/sd-80.amap") + t = xv.amap.create_addr_map_tree(m) + + print("loading wave names...", file=sys.stderr) + with open("data/sdwavelist","r") as f: + for l in f: + wavenames += [l.split('\t')[1].strip()] + + inst_dir = ["./data/patchparam/classical/", "./data/patchparam/contemporary/", "./data/patchparam/solo/"] + #inst_dir += ["./data/patchparam/enhanced/", "./data/patchparam/special_1/", "./data/patchparam/special_2/"] + + rhy_dir = ["./data/rhythmparam/classical/", "./data/rhythmparam/contemporary/", "./data/rhythmparam/solo/"] + #rhy_dir += ["./data/rhythmparam/enhanced/"] + + for i in inst_dir: + for fn in os.listdir(i): + mr = xv.util.load_memoryregion(os.path.join(i, fn)) + for addr, data in mr: + ra = xv.util.b7belist2int(addr) + t.data[ra:ra + len(data)] = data + process_preset(t) + + for i in rhy_dir: + for fn in os.listdir(i): + mr = xv.util.load_memoryregion(os.path.join(i, fn)) + for addr, data in mr: + ra = xv.util.b7belist2int(addr) + t.data[ra:ra + len(data)] = data + process_preset(t, True) + + for i, w in enumerate(wavenames): + print(f"{w.ljust(16)}{wavecnt[i + 1]}") diff --git a/xv/__init__.py b/xv/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/xv/__init__.py @@ -0,0 +1 @@ + diff --git a/xv/amap.py b/xv/amap.py new file mode 100644 index 0000000..4ad7ff5 --- /dev/null +++ b/xv/amap.py @@ -0,0 +1,265 @@ +# Part of the Fifteen-Thieves Project +# Chris Xiong 2020 +# License: Expat (MIT) + +import re +from collections import namedtuple +import xv.util as u + +LeafNode = namedtuple("LeafNode", ["offset", "name", "size", "elements", "stride"]) + +def strsize2bytesize(s): + if s.startswith('0'): + return 1 + else: + return int(s[1:]) + +def get_leaf_data(data, leaf): + if leaf.size.startswith('0'): + return data[leaf.offset] + else: + ret = 0 + for i in range(0, int(leaf.size[1:])): + ret <<= 4 + ret |= data[leaf.offset + i] & 0xf + return ret + +def set_leaf_data(data, leaf, value): + sz = int(leaf.size[1:]) + if leaf.size.startswith('0'): + if value >= (1 << sz): + raise ValueError("Value overflow") + data[leaf.offset] = value + else: + if value >= (1 << (sz * 4)): + raise ValueError("Value overflow") + for i in range(0, sz): + data[leaf.offset + sz - 1 - i] = value & 0xf + value >>= 4 + +class AddrMapTemplateNode: + def __init__(self, lines, is_root=False): + self.valid = True + self.name = lines[0].strip() + self.is_root = is_root + self.is_aggregate_node = False + self.children_nodes = [] + self.children_names = dict() + if self.name.endswith('*'): + self.is_aggregate_node = True + self.name = self.name[:-1] + for line in lines[1:]: + el = line.split(',') + if not 2 <= len(el) <= 3: + self.valid = False + break + offset = u.hexrep2b7val(int(el[0].strip(), 16)) + size = None if len(el) == 2 else el[1].strip() + m = re.match("([a-z_0-9]*)(\[([0-9]*)(\+[0-9a-f]*)?\])?", el[-1].strip()) + if not m: + self.valid = False + break + name = m.groups()[0] + elements = None if not m.groups()[2] else int(m.groups()[2]) + stride = None if not m.groups()[-1] else u.hexrep2b7val(int(m.groups()[-1][1:], 16)) + + child = LeafNode(offset, name, size, elements, stride) + self.children_names[name] = len(self.children_nodes) + self.children_nodes.append(child) + +class AddrMapTemplate: + def __init__(self, amapfile=None): + self.root_node_name = None + self.nodes = dict() + self.parse(amapfile) + if not self.verify(): + raise ValueError("invalid address map file") + + def parse(self,amapfile=None): + linebuf = [] + with open(amapfile, "r", encoding="utf-8") as f: + for line in f: + line = line[:line.find('#')] + if len(line.strip()) == 0: + continue + if re.match("^[a-z]", line): + if len(linebuf) > 0: + newnode = AddrMapTemplateNode(linebuf, len(self.nodes) == 0) + if newnode.valid: + self.nodes[newnode.name] = newnode + if newnode.is_root: + self.root_node_name = newnode.name + linebuf = [line] + else: + linebuf.append(line) + if len(linebuf) > 0: + newnode = AddrMapTemplateNode(linebuf) + if newnode.valid: + self.nodes[newnode.name] = newnode + + def verify(self): + for nodename, node in self.nodes.items(): + if not node.is_aggregate_node: + continue + for child in node.children_nodes: + if not child.name in self.nodes: + print(f"referencing undefined node {child.name} in {node.name}") + return False + return True + +class AddrMapNode: + def __init__(self, template_node, base_address, data, template): + self.template = template_node + self.base_address = base_address + self.upper_address = base_address + self.children_nodes = [] + self.data = data + self._populate(template) + + def _populate(self, template): + for t in self.template.children_nodes: + if self.template.is_aggregate_node: + ba = self.base_address + t.offset + if t.elements: + stride = t.stride if t.stride else 1 + l = [] + for i in range(0, t.elements): + l.append(AddrMapNode(template.nodes[t.name], ba, self.data, template)) + ba += stride + self.children_nodes.append(l) + if l[-1].upper_address > self.upper_address: + self.upper_address = l[-1].upper_address + else: + self.children_nodes.append(AddrMapNode(template.nodes[t.name], ba, self.data, template)) + if self.children_nodes[-1].upper_address > self.upper_address: + self.upper_address = self.children_nodes[-1].upper_address + else: + ba = self.base_address + t.offset + if t.elements: + stride = t.stride if t.stride else 1 + l = [] + for i in range(0, t.elements): + cn = LeafNode(ba, t.name, t.size, t.elements, t.stride) + l.append(cn) + ba += stride + self.children_nodes.append(l) + if ba - stride + strsize2bytesize(t.size) > self.upper_address: + self.upper_address = ba - stride + strsize2bytesize(t.size) + else: + cn = LeafNode(ba, t.name, t.size, t.elements, t.stride) + self.children_nodes.append(cn) + if ba + strsize2bytesize(t.size) > self.upper_address: + self.upper_address = ba + strsize2bytesize(t.size) + + def dump(self, level=0, instance=None): + ba_rep = ''.join([format(x, "02x") for x in u.padbelist(u.int2b7belist(self.base_address))]) + print(f"{ba_rep}{level*' '} {self.template.name}{'['+str(instance)+']' if instance is not None else ''}") + for child in self.children_nodes: + if self.template.is_aggregate_node: + if type(child) == list: + for i, element in enumerate(child): + element.dump(level + 1, i) + else: + child.dump(level + 1) + else: + if type(child) == list: + for i, element in enumerate(child): + ba_rep = ''.join([format(x, "02x") for x in u.padbelist(u.int2b7belist(element.offset))]) + print(f"{ba_rep}{(level + 1)*' '} {element.name}[{i}] {element.size}") + else: + ba_rep = ''.join([format(x, "02x") for x in u.padbelist(u.int2b7belist(child.offset))]) + print(f"{ba_rep}{(level + 1)*' '} {child.name} {child.size}") + + def set_data(self, data): + if len(data) > self.size(): + raise ValueError("excessive data") + self.data[self.base_address:self.base_address + len(data)] = data + + def get_data(self): + return self.data[self.base_address:self.upper_address + 1] + + def size(self): + return self.upper_address - self.base_address + 1 + + def find_node(self, key): + if type(key) != list: + key = key.split('.') + if len(key) == 0: + raise ValueError("???") + if key[0] == self.template.name: + key = key[1:] + if len(key) == 0: + return self + m = re.match("([a-z_0-9]*)(\[([0-9]*)\])?", key[0]) + if not m: + raise ValueError(f"Invalid key: {key[0]}") + if m.groups()[0] not in self.template.children_names: + raise ValueError(f"No such region: {m.groups()[0]}") + child = self.children_nodes[self.template.children_names[m.groups()[0]]] + if m.groups()[2] and type(child) != list: + raise ValueError(f"{m.groups()[0]} is not a list") + if not m.groups()[2] and type(child) == list: + if len(key) > 1: + raise ValueError(f"index needed for {m.groups()[0]}") + if not self.template.is_aggregate_node and len(key) > 1: + raise ValueError(f"{key[0]} has no more subregions") + if type(child) == list: + if len(key) > 1: + return child[int(m.groups()[2])].find_node(key[1:]) + else: + return child if not m.groups()[2] else child[int(m.groups()[2])] + else: + if len(key) > 1: + return child.find_node(key[1:]) + else: + return child + + def value(self, key): + node = self.find_node(key) + if type(node) == list: + if type(node[0]) == AddrMapNode: + return node.data[node[0].base_address:node[-1].upper_address + 1] + else: + stride = node[0].stride if node[0].stride else 1 + return self.data[node[0].offset:node[0].offset + len(node) * stride:stride] + else: + if type(node) == AddrMapNode: + return node.data[node.base_address:node.upper_address + 1] + else: + return get_leaf_data(self.data, node) + + def set_value(self, key, data): + node = self.find_node(key) + if type(node) == list: + if type(node[0]) == AddrMapNode: + if len(data) > node[-1].upper_address + 1 - node[0].base_address: + raise ValueError("excessive data") + node.data[node[0].base_address:node[0].base_address + len(data)] = data + else: + stride = node[0].stride if node[0].stride else 1 + if len(data) > len(self.data[node[0].offset:node[0].offset + len(node) * stride:stride]): + raise ValueError("excessive data") + self.data[node[0].offset:node[0].offset + len(node) * stride:stride] = data + else: + if type(node) == AddrMapNode: + if len(data) > node.upper_address + 1 - node.base_address: + raise ValueError("excessive data") + node.data[node.base_address:node.base_address + len(data)] = data + else: + return set_leaf_data(self.data, node, data) + + def __getitem__(self, key): + return self.children_nodes[self.template.children_names[key]] + + def __iter__(self): + return self.children_nodes.__iter__() + + def __len__(self): + return len(self.children_nodes) + +def create_addr_map_tree(template): + r = AddrMapNode(template.nodes[template.root_node_name], 0, bytearray(), template) + r.data.extend(bytearray(r.upper_address)) + return r + +# vim: expandtab shiftwidth=4 tabstop=4 diff --git a/xv/model.py b/xv/model.py new file mode 100644 index 0000000..0c0c014 --- /dev/null +++ b/xv/model.py @@ -0,0 +1,140 @@ +# Part of the Fifteen-Thieves Project +# Chris Xiong 2020 +# License: Expat (MIT) + +import rtmidi.midiutil as r +import xv.util as u +import xv.amap +import threading + +class Model: + def __init__(self, port_name=None, model=[0x00, 0x00], devn=0x10, address_mapping=None): + self.midii,_ = r.open_midiinput(port_name) + self.midio,_ = r.open_midioutput(port_name) + self.midii.ignore_types(sysex=False) + self.model = model + self.devn = devn + self.aroot = address_mapping + + def __del__(self): + del self.midii + del self.midio + + def writeraw(self, addr, data): + if type(addr) == int: + addr = u.int2belist(addr) + addr = u.padbelist(addr) + if len(addr) > 4: + raise ValueError("invalid address") + + for a in addr: + if a > 0x80 or a < 0: + raise ValueError("invalid address") + + cs = u.roland_checksum(addr + data) + d = [0xf0, 0x41, self.devn] + self.model + [0x12] + addr + data + [cs, 0xf7] + #print(' '.join([format(a, "02x") for a in d])) + + self.midio.send_message(d) + + def readraw(self, addr, size, timeout=None): + if type(addr) == int: + addr = u.int2belist(addr) + addr = u.padbelist(addr) + if len(addr) > 4: + raise ValueError("invalid address") + if type(size) == int: + size = u.padbelist(u.int2b7belist(size)) + rsize = u.b7belist2int(size) + + for a in addr: + if a > 0x80 or a < 0: + raise ValueError("invalid address") + + cs = u.roland_checksum(addr + size) + d = [0xf0, 0x41, self.devn] + self.model + [0x11] + addr + size + [cs, 0xf7] + #print(' '.join([format(a, "02x") for a in d])) + + cv = threading.Condition() + m = [] + def msgcb(d, cd): + _m, dt = d + nonlocal m, cv, addr + is_last = False + + if u.check_roland_checksum(_m): + m.append( (_m[6:10], _m[10:-2]) ) + if u.b7belist2int(_m[6:10]) - u.b7belist2int(addr) + len(_m[10:-2]) >= rsize: + is_last = True + else: + raise RuntimeError("checksum error") + + if is_last: + cv.acquire() + cv.notify() + cv.release() + + self.midii.set_callback(msgcb) + cv.acquire() + self.midio.send_message(d) + cv.wait(timeout) + cv.release() + self.midii.cancel_callback() + return m + + #current implementation doesn't support reading entire strided list of primitives + def read_locations(self, key, timeout=None): + node = self.aroot.find_node(key) + rqsz = 0 + rqba = 0 + if type(node) == list: + if type(node[0]) == xv.amap.AddrMapNode: + rqsz = node[-1].upper_address + 1 - node[0].base_address + rqba = node[0].base_address + else: + rqsz = node[-1].offset - node[0].offset + xv.amap.strsize2bytesize(node[-1].size) + 1 + rqba = node[0].offset + else: + if type(node) == xv.amap.AddrMapNode: + rqba = node.base_address + rqsz = node.upper_address + 1 - node.base_address + else: + rqba = node.offset + rqsz = xv.amap.strsize2bytesize(node.size) + rqba = u.padbelist(u.int2b7belist(rqba)) + r = self.readraw(rqba, rqsz, timeout) + for m in r: + mba = u.b7belist2int(m[0]) + self.aroot.data[mba:mba + len(m[1])] = m[1] + return r + + #current implementation doesn't support writing entire strided list of primitives + def write_locations(self, key, data): + node = self.aroot.find_node(key) + dstsz = 0 + dstba = 0 + if type(node) == list: + if type(node[0]) == xv.amap.AddrMapNode: + dstsz = node[-1].upper_address + 1 - node[0].base_address + dstba = node[0].base_address + else: + dstsz = node[-1].offset - node[0].offset + xv.amap.strsize2bytesize(node[-1].size) + 1 + dstba = node[0].offset + else: + if type(node) == xv.amap.AddrMapNode: + dstba = node.base_address + dstsz = node.upper_address + 1 - node.base_address + else: + dstba = node.offset + dstsz = xv.amap.strsize2bytesize(node.size) + if type(data) != list: + self.aroot.set_value(key, data) + data = list(self.aroot.data[node.offset:node.offset + dstsz]) + if len(data) > dstsz: + raise ValueError("excessive data") + self.aroot.data[dstba:dstba + len(data)] = data + + dstba = u.padbelist(u.int2b7belist(dstba)) + self.writeraw(dstba, data) + +# vim: expandtab shiftwidth=4 tabstop=4 diff --git a/xv/util.py b/xv/util.py new file mode 100644 index 0000000..0b3f2e6 --- /dev/null +++ b/xv/util.py @@ -0,0 +1,90 @@ +# Part of the Fifteen-Thieves Project +# Chris Xiong 2020 +# License: Expat (MIT) + +def roland_checksum(payload): + return (0x80 - sum(payload) % 0x80) % 0x80 + +def check_roland_checksum(msg): + return (0x80 - sum(msg[6:-2]) % 0x80) % 0x80 == msg[-2] + +def int2belist(x): + ret = [] + while x > 0: + ret = [x & 0xff] + ret + x >>= 8 + return ret + +def belist2int(x): + ret = 0 + for i in x: + ret <<= 8 + ret |= i + return ret + +def int2b7belist(x): + ret = [] + while x > 0: + ret = [x & 0x7f] + ret + x >>= 7 + return ret + +def strb7list(x): + return ' '.join([format(a, "02x") for a in x]) + +def b7belist2int(x): + ret = 0 + for i in x: + ret <<= 7 + ret |= i + return ret + +def hexrep2b7val(x): + ret = 0 + b = 0 + while x > 0: + if x & 0xff > 0x7f: + raise ValueError("invalid value") + ret |= (x & 0xff) << (7 * b) + b += 1 + x >>= 8 + return ret + +def padbelist(x, l=4): + return ([0]*(l-len(x))) + x + +def int2packetint(x): + ret = [] + while x > 0: + ret = [x & 0x0f] + ret + x >>= 4 + return ret + +def packetint2int(x): + ret = 0 + for i in x: + ret <<= 4 + ret |= i + return ret + +def load_memoryregion(fn): + ret = [] + with open(fn, "rb") as f: + while True: + addr = list(f.read(4)) + if len(addr) < 4: + break + l = belist2int(list(f.read(4))) + data = list(f.read(l)) + ret.append( (addr, data) ) + return ret + +def write_memoryregion(fn, mr): + with open(fn, "wb") as f: + for addr, data in mr: + l = padbelist(int2belist(len(data))) + f.write(bytearray(addr)) + f.write(bytearray(l)) + f.write(bytearray(data)) + +# vim: expandtab shiftwidth=4 tabstop=4 -- cgit v1.2.3