/* ============================================================================== This file is part of the JUCE framework. Copyright (c) Raw Material Software Limited JUCE is an open source framework subject to commercial or open source licensing. By downloading, installing, or using the JUCE framework, or combining the JUCE framework with any other source code, object code, content or any other copyrightable work, you agree to the terms of the JUCE End User Licence Agreement, and all incorporated terms including the JUCE Privacy Policy and the JUCE Website Terms of Service, as applicable, which will bind you. If you do not agree to the terms of these agreements, we will not license the JUCE framework to you, and you must discontinue the installation or download process and cease use of the JUCE framework. JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ JUCE Privacy Policy: https://juce.com/juce-privacy-policy JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ Or: You may also use this code under the terms of the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.en.html THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { constexpr unsigned char javaMidiByteCode[] { 0x1f, 0x8b, 0x08, 0x08, 0x90, 0xda, 0xee, 0x67, 0x00, 0x03, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x00, 0xad, 0x7c, 0x09, 0x7c, 0x94, 0xd5, 0xd5, 0xf7, 0xb9, 0xcf, 0xcc, 0x64, 0x87, 0x4c, 0x26, 0x21, 0x09, 0x01, 0x92, 0xc9, 0x24, 0x40, 0x02, 0x64, 0x4f, 0x20, 0x21, 0x01, 0xb3, 0x22, 0x81, 0x00, 0x31, 0x99, 0x44, 0x21, 0x52, 0x1c, 0x92, 0x81, 0x0c, 0x4c, 0x66, 0xc6, 0x99, 0x09, 0x8b, 0xb5, 0x15, 0x97, 0x56, 0xac, 0x4b, 0xdd, 0x5e, 0x97, 0x56, 0x2d, 0x56, 0x45, 0xdc, 0x5a, 0x5a, 0x97, 0x5a, 0xeb, 0x82, 0xc5, 0x56, 0xdb, 0xda, 0xd6, 0xf6, 0xed, 0x6b, 0x37, 0xdf, 0x7e, 0xb5, 0xa5, 0xad, 0x76, 0x7b, 0xb5, 0xf5, 0x7d, 0x5f, 0xfb, 0x75, 0xf1, 0xfb, 0x9f, 0x7b, 0xef, 0x33, 0xf3, 0x64, 0x01, 0xda, 0xdf, 0xef, 0x8b, 0xfe, 0xe7, 0x9c, 0x7b, 0xee, 0xb9, 0xfb, 0xb9, 0xe7, 0x9e, 0xfb, 0xcc, 0xc3, 0x8c, 0xf9, 0x0f, 0x64, 0xd4, 0x36, 0xac, 0xa2, 0x3b, 0x1e, 0xf0, 0x6c, 0xbe, 0xfe, 0xe0, 0xfd, 0x83, 0x8d, 0xef, 0x45, 0xdf, 0x7b, 0xe8, 0xca, 0xbd, 0xfb, 0x9e, 0xef, 0x2e, 0x7a, 0xb7, 0xec, 0xce, 0xde, 0xcf, 0xbd, 0x7f, 0x01, 0x51, 0x84, 0x88, 0x0e, 0x0c, 0x37, 0xba, 0x48, 0xff, 0x55, 0x40, 0xb6, 0x51, 0x28, 0xf9, 0x30, 0x70, 0x97, 0x83, 0xc8, 0x07, 0xfa, 0x5e, 0x0a, 0x51, 0x1b, 0xe8, 0xa1, 0x4c, 0xa2, 0x57, 0x40, 0x2f, 0x9a, 0x43, 0x94, 0x03, 0x7a, 0x24, 0x97, 0xa8, 0xb9, 0x8b, 0xe8, 0xc4, 0x3c, 0xa2, 0xf8, 0x4a, 0xa2, 0x7d, 0xc0, 0x41, 0xe0, 0x23, 0xc0, 0xe5, 0xc0, 0x55, 0xc0, 0xc3, 0xc0, 0x13, 0xc0, 0xb3, 0xc0, 0x09, 0xe0, 0x6b, 0xc0, 0xb7, 0x80, 0xef, 0x01, 0xbf, 0x01, 0x96, 0xae, 0x22, 0x0a, 0x02, 0x87, 0x80, 0x5b, 0x81, 0xa3, 0xc0, 0x43, 0xc0, 0x63, 0xc0, 0x17, 0x80, 0x27, 0x81, 0x67, 0x80, 0x9f, 0x02, 0x46, 0x33, 0x51, 0x31, 0x50, 0x07, 0x34, 0x02, 0xab, 0x81, 0xcd, 0x80, 0x0f, 0x98, 0x04, 0xae, 0x04, 0xee, 0x04, 0xee, 0x02, 0x8e, 0x00, 0x0f, 0x00, 0x8f, 0x00, 0x5f, 0x00, 0x9e, 0x02, 0x9e, 0x01, 0x5e, 0x00, 0x5e, 0x02, 0xbe, 0x09, 0xbc, 0x03, 0xcc, 0x6b, 0xc1, 0x18, 0x80, 0x51, 0xe0, 0x1a, 0xe0, 0x09, 0xe0, 0xc7, 0x80, 0x7d, 0x35, 0xfa, 0x06, 0x6c, 0x00, 0xf6, 0x03, 0x77, 0x01, 0x5f, 0x02, 0x7e, 0x04, 0xa4, 0xb4, 0x12, 0x2d, 0x07, 0xb6, 0x00, 0x87, 0x80, 0x2f, 0x03, 0x3f, 0x05, 0x78, 0x92, 0xca, 0x80, 0x73, 0x00, 0x2f, 0xb0, 0x07, 0xb8, 0x12, 0xb8, 0x1d, 0xf8, 0x36, 0x20, 0xd6, 0xa0, 0xdf, 0xc0, 0x38, 0xf0, 0x69, 0xe0, 0xbb, 0xc0, 0xdf, 0x81, 0x0d, 0x6b, 0xa1, 0x03, 0xfc, 0x14, 0xa8, 0x3c, 0x87, 0x28, 0x0c, 0x7c, 0x16, 0x78, 0x1d, 0xa0, 0x76, 0xa2, 0x16, 0x60, 0x1b, 0xf0, 0x51, 0xe0, 0x1e, 0xe0, 0x45, 0xe0, 0x37, 0x40, 0x4a, 0x07, 0x91, 0x07, 0x68, 0x01, 0xfa, 0x80, 0x51, 0xe0, 0x1a, 0xe0, 0x1e, 0xe0, 0x04, 0xf0, 0x03, 0xe0, 0x2d, 0xc0, 0xd6, 0x49, 0x54, 0x04, 0xb4, 0x00, 0x17, 0x00, 0x61, 0xe0, 0xe3, 0xc0, 0xa7, 0x80, 0x2f, 0x02, 0x2f, 0x03, 0x6f, 0x00, 0xef, 0x02, 0x29, 0x58, 0xcf, 0x85, 0x40, 0x35, 0xd0, 0x0e, 0x0c, 0x01, 0xbb, 0x80, 0xfd, 0xc0, 0x27, 0x80, 0x7b, 0x81, 0xaf, 0x02, 0xbf, 0x05, 0xb2, 0xba, 0x31, 0x0f, 0xc0, 0x06, 0x60, 0x3b, 0x70, 0x04, 0x78, 0x1d, 0xf8, 0x2d, 0xf0, 0x0f, 0x20, 0xad, 0x07, 0x76, 0x02, 0x34, 0x03, 0x9b, 0x81, 0xed, 0xc0, 0x3e, 0xe0, 0x20, 0xf0, 0x11, 0xe0, 0x0a, 0xe0, 0x30, 0x70, 0x1d, 0x70, 0x2b, 0x70, 0x07, 0x70, 0x37, 0x70, 0x1f, 0xf0, 0x30, 0x70, 0x1c, 0x78, 0x12, 0x78, 0x06, 0x78, 0x11, 0xf8, 0x06, 0xf0, 0x5d, 0xe0, 0x75, 0xe0, 0x27, 0xc0, 0x7f, 0x02, 0x6f, 0x02, 0xbf, 0x02, 0xde, 0x06, 0xe6, 0xac, 0xc3, 0x12, 0x00, 0x3b, 0x81, 0x03, 0xc0, 0xf5, 0xc0, 0x9d, 0xc0, 0x03, 0xc0, 0x17, 0x80, 0xe7, 0x81, 0x93, 0xc0, 0x6b, 0xc0, 0x1b, 0xc0, 0x29, 0xe0, 0x77, 0xc0, 0x9f, 0x81, 0x7f, 0x00, 0x45, 0xe7, 0x12, 0xad, 0x00, 0x3a, 0x01, 0x2f, 0xb0, 0x0b, 0x98, 0x00, 0xf6, 0x03, 0x97, 0x01, 0x1f, 0x07, 0xae, 0x07, 0x6e, 0x03, 0xee, 0x03, 0x1e, 0x03, 0x9e, 0x01, 0x9e, 0x03, 0xbe, 0x03, 0xbc, 0x0e, 0xfc, 0x06, 0x78, 0x17, 0xf8, 0x3b, 0x60, 0xac, 0x27, 0xca, 0x00, 0xf2, 0x80, 0xf9, 0x40, 0x31, 0x50, 0x06, 0x0c, 0x02, 0xdb, 0x81, 0x43, 0xc0, 0xbd, 0xc0, 0x63, 0xc0, 0xe3, 0xc0, 0xd3, 0xc0, 0x4b, 0xc0, 0x6b, 0xc0, 0xef, 0x81, 0xf4, 0x5e, 0x22, 0x37, 0xd0, 0x04, 0xf4, 0x02, 0xa3, 0xc0, 0x25, 0xc0, 0x55, 0xc0, 0xed, 0xc0, 0xe3, 0xc0, 0xcb, 0xc0, 0xf7, 0x81, 0x9f, 0x01, 0xff, 0x03, 0x64, 0x6f, 0x80, 0x0d, 0x01, 0xf5, 0xc0, 0x76, 0x60, 0x0c, 0xd8, 0x0b, 0xc4, 0x81, 0xab, 0x81, 0xcf, 0x02, 0x8f, 0x00, 0x5f, 0x06, 0xbe, 0x0d, 0xbc, 0x01, 0xbc, 0x0d, 0xfc, 0x19, 0x30, 0x36, 0x12, 0xe5, 0x02, 0xc5, 0x40, 0x2d, 0x70, 0x2e, 0xd0, 0x0f, 0x8c, 0x02, 0x07, 0x80, 0xab, 0x81, 0x1b, 0x81, 0xdb, 0x80, 0x23, 0xc0, 0x51, 0xe0, 0x61, 0xe0, 0x38, 0xf0, 0x0c, 0xf0, 0x12, 0xf0, 0x3d, 0xe0, 0x27, 0xc0, 0x29, 0xe0, 0xf7, 0xc0, 0xda, 0x3e, 0xa2, 0x9b, 0x80, 0xcf, 0x00, 0xc7, 0x80, 0xa7, 0x80, 0x67, 0x81, 0xef, 0x02, 0x6f, 0x00, 0xbf, 0x00, 0xde, 0x06, 0x8c, 0x4d, 0xb0, 0x17, 0x60, 0x21, 0xd0, 0x04, 0x74, 0x03, 0x5e, 0x60, 0x2f, 0x70, 0x29, 0xf0, 0x31, 0xe0, 0x16, 0xe0, 0x5e, 0xe0, 0x21, 0xe0, 0x79, 0xe0, 0x9b, 0xc0, 0x8f, 0x81, 0x3f, 0x01, 0x59, 0x9b, 0x51, 0x16, 0x58, 0x0a, 0xd4, 0x03, 0x2d, 0xc0, 0x66, 0x60, 0x0f, 0x70, 0x25, 0x70, 0x03, 0x70, 0x3b, 0x70, 0x17, 0x70, 0x14, 0x78, 0x1e, 0xf8, 0x3a, 0xf0, 0x7d, 0xe0, 0x0f, 0xc0, 0x9f, 0x80, 0xbf, 0x02, 0x8e, 0x2d, 0x44, 0x99, 0xc0, 0x52, 0xa0, 0x09, 0xe8, 0x06, 0x7a, 0x81, 0x6d, 0xc0, 0x6e, 0x60, 0x3f, 0x70, 0x29, 0x70, 0x25, 0x70, 0x03, 0x70, 0x37, 0x70, 0x1f, 0xf0, 0x18, 0xf0, 0x24, 0xf0, 0x02, 0xf0, 0x4d, 0xe0, 0x67, 0xc0, 0x29, 0xe0, 0xaf, 0x40, 0x7e, 0x3f, 0xe6, 0x13, 0x58, 0x05, 0xb4, 0x03, 0xec, 0x70, 0x17, 0x00, 0x2b, 0x80, 0x2a, 0xa0, 0x1a, 0xa8, 0x01, 0x6a, 0x81, 0x3a, 0xa0, 0x1e, 0x68, 0x00, 0x1a, 0x81, 0x26, 0x00, 0x6e, 0x92, 0xe0, 0xfe, 0x08, 0xee, 0x8a, 0xe0, 0x9a, 0x08, 0x2e, 0x88, 0xb4, 0xbb, 0x21, 0xb8, 0x0f, 0x82, 0xdb, 0x20, 0xed, 0x2a, 0x08, 0xdb, 0x9d, 0xb0, 0x8d, 0x09, 0xdb, 0x93, 0xb0, 0x05, 0x09, 0xdb, 0x82, 0x60, 0xda, 0x04, 0x73, 0x24, 0x98, 0x14, 0xc1, 0x44, 0x08, 0x4b, 0x4d, 0x58, 0x1a, 0xc2, 0xb4, 0x13, 0xa6, 0x8e, 0x30, 0x6c, 0x42, 0x37, 0x09, 0x5d, 0xa3, 0xf3, 0x80, 0x01, 0x60, 0x10, 0xf0, 0x02, 0x43, 0xfa, 0x2c, 0x38, 0x1f, 0xc0, 0xf1, 0x40, 0x5b, 0x81, 0x6d, 0xc0, 0x08, 0x70, 0x21, 0xb0, 0x1d, 0xf8, 0x10, 0xb0, 0x83, 0xcf, 0x05, 0x52, 0xe7, 0xc5, 0x4e, 0x60, 0x14, 0x18, 0x03, 0xfc, 0xc0, 0x2e, 0x60, 0x37, 0x30, 0x0e, 0xec, 0x01, 0x82, 0xc0, 0x04, 0x10, 0x22, 0x75, 0xd6, 0x44, 0x81, 0x18, 0x30, 0x09, 0xec, 0xe3, 0x33, 0x09, 0x38, 0x08, 0x5c, 0x02, 0x7c, 0x18, 0xb8, 0x14, 0xf8, 0x08, 0xf0, 0x09, 0x3e, 0x93, 0x80, 0xcf, 0xf0, 0xd9, 0x03, 0xdc, 0x0b, 0x7c, 0x16, 0xb8, 0x0f, 0xb8, 0x9f, 0xd4, 0xbc, 0x9a, 0x7f, 0x79, 0x9a, 0x1e, 0xc1, 0x24, 0xce, 0xd3, 0xfc, 0x31, 0xf0, 0xf9, 0x9a, 0x3f, 0x6e, 0x91, 0x3f, 0x0d, 0xde, 0x03, 0x6a, 0xe8, 0x74, 0x85, 0xe6, 0x4f, 0x68, 0xb9, 0xcd, 0x22, 0x67, 0xfe, 0x95, 0xc6, 0x24, 0x7f, 0x4c, 0xeb, 0xd8, 0xb5, 0xce, 0x72, 0xcd, 0xbf, 0xa6, 0xe5, 0xa9, 0x96, 0xb2, 0xe9, 0xc0, 0x1b, 0x5a, 0x9e, 0xa1, 0xe5, 0x65, 0x40, 0x96, 0xee, 0x27, 0xcb, 0xe7, 0x68, 0x39, 0xf3, 0x73, 0x2d, 0x7c, 0x8e, 0x45, 0xdf, 0xa5, 0xf5, 0x99, 0xcf, 0xb7, 0x94, 0x9d, 0x6f, 0x69, 0x6b, 0x81, 0xee, 0x1b, 0xf3, 0x8b, 0xf4, 0x58, 0xca, 0x75, 0xf9, 0x53, 0xe0, 0x17, 0x6b, 0xfe, 0xf7, 0xe0, 0x97, 0x68, 0xfe, 0x7d, 0xf0, 0x4b, 0x35, 0x6f, 0x6f, 0x4a, 0xf2, 0x59, 0x4d, 0x8a, 0x2e, 0xd1, 0x6d, 0x55, 0x68, 0xde, 0xac, 0xbf, 0xc2, 0xc2, 0x57, 0x5a, 0xe6, 0x6d, 0xb9, 0xa5, 0xff, 0x4d, 0x96, 0xfe, 0x37, 0x5b, 0xfa, 0xbc, 0x5a, 0xcb, 0x97, 0x6a, 0x3e, 0xaf, 0x49, 0xd5, 0xc3, 0xfc, 0x42, 0xf0, 0xcb, 0x34, 0x5f, 0x6e, 0x91, 0x9b, 0x73, 0xde, 0x6a, 0x19, 0x6f, 0xab, 0xa5, 0x0f, 0x6d, 0x16, 0x9d, 0x6e, 0x4b, 0x1f, 0x7a, 0x2c, 0xfa, 0xcc, 0xaf, 0x68, 0x4a, 0xf2, 0xa6, 0x0d, 0x9c, 0x6b, 0xe9, 0xe7, 0xb9, 0xba, 0x9f, 0x95, 0x9a, 0x6f, 0x6c, 0x52, 0x3a, 0x1b, 0xb5, 0x0e, 0xdb, 0xe3, 0x66, 0xcd, 0x5f, 0xab, 0x79, 0xd6, 0xbf, 0x4e, 0xf3, 0x6d, 0xd0, 0xbf, 0x5e, 0xf3, 0xdd, 0xe0, 0x6f, 0xd0, 0x7c, 0x3f, 0xf8, 0x4f, 0x6a, 0xfe, 0x02, 0xf0, 0x37, 0x6a, 0x7e, 0x1c, 0xfc, 0x9d, 0x9a, 0x8f, 0x80, 0xbf, 0x59, 0xf3, 0x97, 0x5a, 0x74, 0x0e, 0x5b, 0x78, 0x9e, 0x67, 0x93, 0xbf, 0xd1, 0x22, 0x67, 0xfb, 0xbc, 0x49, 0xf3, 0x77, 0x58, 0xea, 0x3c, 0x62, 0xd1, 0x79, 0x14, 0xfc, 0x6d, 0x9a, 0x7f, 0xc2, 0x22, 0x7f, 0xd6, 0xc2, 0xbf, 0x64, 0xe1, 0x5f, 0xb5, 0xf0, 0x3f, 0x68, 0x4a, 0xd6, 0xff, 0x06, 0xf8, 0x5b, 0x35, 0x7f, 0x0a, 0xfc, 0xa7, 0x34, 0xff, 0x0e, 0xf8, 0xdb, 0x35, 0xff, 0x37, 0xf0, 0x77, 0x68, 0x3e, 0x6d, 0x65, 0xb2, 0xdd, 0xc2, 0x95, 0xc9, 0xbe, 0xb9, 0x57, 0x26, 0xeb, 0x5f, 0xb1, 0x32, 0x59, 0x7f, 0xa3, 0x45, 0xbe, 0xd0, 0xd2, 0x87, 0x63, 0x96, 0xb1, 0xb7, 0x59, 0x74, 0xba, 0x2d, 0x75, 0xf6, 0x81, 0xff, 0x37, 0x73, 0x9e, 0xc1, 0x7f, 0x5a, 0xf3, 0x17, 0x81, 0xbf, 0xc5, 0x9c, 0xf3, 0x95, 0xca, 0xa7, 0x6c, 0xd1, 0xeb, 0x78, 0xb7, 0xe6, 0x79, 0x1d, 0xef, 0xd1, 0x3c, 0xaf, 0x8b, 0xc9, 0x2f, 0xb4, 0xf0, 0xa6, 0x8d, 0x0d, 0x58, 0x6c, 0x6c, 0x50, 0xf3, 0x85, 0xc0, 0xc7, 0x88, 0xf7, 0xde, 0x5c, 0x7a, 0x44, 0xd2, 0x56, 0x3a, 0x2e, 0x69, 0x17, 0x7d, 0x51, 0xd2, 0x54, 0xaa, 0x45, 0xac, 0x5d, 0x8c, 0x53, 0xe0, 0x29, 0x62, 0x0a, 0xbf, 0x8d, 0x74, 0x09, 0x76, 0xfa, 0x3f, 0x88, 0xa9, 0x4a, 0xbb, 0x41, 0x5f, 0xe0, 0xf9, 0x81, 0x87, 0x7f, 0x89, 0x54, 0xfa, 0xef, 0x92, 0xb6, 0x52, 0xba, 0x50, 0x74, 0x8e, 0xa6, 0x15, 0x9a, 0x36, 0x0a, 0xa5, 0xbf, 0x5a, 0x97, 0x37, 0xeb, 0xe9, 0x06, 0x2d, 0x85, 0xc7, 0x78, 0x98, 0x98, 0x22, 0x8e, 0x93, 0x74, 0x0b, 0x09, 0x29, 0xaf, 0xa4, 0x32, 0x49, 0x97, 0xd1, 0x32, 0xa1, 0xf2, 0xb9, 0x9c, 0x07, 0xde, 0xe6, 0x61, 0x3d, 0xb6, 0xaf, 0x4a, 0xaa, 0xf4, 0x3c, 0x3a, 0xbf, 0x1c, 0xe9, 0x97, 0x49, 0xf9, 0x96, 0x57, 0x24, 0x15, 0xf4, 0x0d, 0x62, 0xff, 0xa2, 0xe4, 0x8b, 0xb5, 0x7c, 0x09, 0x65, 0xd3, 0x09, 0x49, 0xed, 0xf4, 0x5d, 0x49, 0x2b, 0xa9, 0x5d, 0x28, 0x3f, 0xc2, 0xf5, 0x2c, 0xd5, 0xf5, 0x2e, 0xd5, 0xf2, 0x0a, 0xe4, 0xb0, 0x7e, 0x85, 0x96, 0x57, 0x68, 0x79, 0xa5, 0x9e, 0xcf, 0x4a, 0x9c, 0x72, 0x4f, 0x48, 0x7a, 0x0e, 0xbd, 0x28, 0xe9, 0x06, 0xfa, 0xba, 0xa4, 0xf3, 0x28, 0x4d, 0x28, 0x79, 0x96, 0x50, 0x7a, 0xa5, 0x9a, 0x7a, 0x34, 0xed, 0x12, 0xec, 0x57, 0x04, 0xfd, 0x8d, 0x94, 0x8f, 0xfa, 0xaa, 0xa6, 0x4b, 0x85, 0xa2, 0x3c, 0x4f, 0x7c, 0x2e, 0x5f, 0x4d, 0x8a, 0x1e, 0x96, 0x74, 0x11, 0x7d, 0x41, 0xd2, 0x4a, 0xca, 0x14, 0x7c, 0x3e, 0x64, 0xd1, 0x8f, 0x89, 0xa9, 0x83, 0x7e, 0x2a, 0x69, 0x3a, 0xbd, 0x4b, 0x7c, 0x26, 0x64, 0xd2, 0x57, 0x24, 0xad, 0xa4, 0xef, 0x6b, 0xfa, 0x5b, 0xe2, 0x33, 0xa1, 0x91, 0xbe, 0xac, 0xe9, 0x49, 0x49, 0x53, 0xe9, 0x67, 0x92, 0x8e, 0x50, 0x35, 0xea, 0x73, 0x40, 0xce, 0x67, 0x45, 0x0a, 0xb8, 0xef, 0x11, 0x9f, 0x1b, 0xc3, 0xd4, 0x26, 0x98, 0xae, 0xa1, 0x73, 0x40, 0xd3, 0x74, 0x7e, 0x7a, 0x82, 0xce, 0xa5, 0x27, 0x25, 0x9d, 0x43, 0x2d, 0xc8, 0xcf, 0xd4, 0xf5, 0x65, 0xe9, 0xfc, 0x2c, 0x72, 0xca, 0xfc, 0x2c, 0xcc, 0xfb, 0x2a, 0xa1, 0x68, 0xb3, 0xe0, 0x33, 0x25, 0x87, 0xfe, 0x83, 0x98, 0x56, 0xd0, 0x9f, 0x41, 0xb3, 0x75, 0xbf, 0xb2, 0xb1, 0xce, 0xbf, 0x91, 0x34, 0x97, 0x16, 0x09, 0xa6, 0x79, 0xe4, 0x06, 0x75, 0xea, 0xfe, 0x3b, 0x81, 0x3f, 0x68, 0xfa, 0xdf, 0xa4, 0xce, 0xa3, 0xff, 0xd4, 0xf4, 0xd7, 0x9a, 0xbe, 0xa5, 0xe9, 0xef, 0x25, 0xdd, 0x41, 0xff, 0x25, 0xe9, 0x3c, 0x7a, 0x47, 0xcb, 0xb9, 0x9c, 0x4b, 0xb7, 0xe7, 0xd2, 0xf5, 0xb9, 0x10, 0x51, 0xd4, 0xa3, 0x9d, 0x5c, 0xdd, 0xef, 0x5c, 0x44, 0x31, 0xb9, 0x42, 0xd1, 0x7c, 0xc1, 0x67, 0xf4, 0x45, 0xf4, 0x7f, 0x88, 0x69, 0x1d, 0xfd, 0x5c, 0xd2, 0x56, 0x5a, 0x20, 0xe5, 0x9d, 0xb4, 0x50, 0xd2, 0x55, 0xb4, 0x5c, 0xd2, 0x6e, 0x4d, 0xbb, 0xe8, 0x5c, 0xc1, 0xe7, 0xa2, 0x6a, 0x27, 0x1f, 0xde, 0xfd, 0x19, 0x4d, 0x4f, 0x92, 0x3a, 0x2f, 0xb9, 0xdd, 0x02, 0xdd, 0xde, 0x7c, 0xcc, 0xc3, 0x6b, 0x92, 0x2e, 0xa3, 0x3f, 0x11, 0x9f, 0x9b, 0xf9, 0xf4, 0x1d, 0xde, 0xe7, 0x58, 0xb9, 0xe7, 0xe5, 0x3e, 0x5d, 0x2b, 0xf5, 0x16, 0x61, 0x85, 0x7f, 0x24, 0xe9, 0x32, 0x3a, 0x25, 0x69, 0x15, 0xbd, 0x2d, 0xe9, 0x10, 0xe5, 0x08, 0xa6, 0x4d, 0x54, 0x28, 0xe9, 0x4a, 0x9a, 0x2f, 0xe9, 0x87, 0x68, 0x89, 0xa4, 0x17, 0xd0, 0x1a, 0x49, 0x07, 0x69, 0x9d, 0xdc, 0xef, 0xe7, 0xc9, 0xfa, 0x8a, 0xf5, 0x38, 0x8b, 0xf5, 0x38, 0x4b, 0x10, 0xb9, 0xa5, 0xca, 0x7d, 0xad, 0xfa, 0x55, 0x8a, 0xc8, 0x4c, 0x51, 0x35, 0x0e, 0xa6, 0xdf, 0x92, 0xb4, 0x94, 0xfe, 0x28, 0x29, 0xa2, 0x30, 0xa1, 0xe4, 0x05, 0x92, 0x6e, 0xa5, 0x22, 0x9d, 0x6e, 0xd5, 0x74, 0xad, 0xdc, 0xa7, 0x7d, 0xb2, 0x1e, 0x8f, 0xae, 0xc7, 0xa3, 0xeb, 0xf1, 0xe8, 0x7a, 0x3c, 0xba, 0x1e, 0x8f, 0x2e, 0xef, 0xd1, 0xe5, 0x3d, 0xba, 0x7c, 0x99, 0xee, 0x4f, 0x99, 0x2e, 0x5f, 0xa6, 0xcb, 0x95, 0xe9, 0x72, 0x65, 0x5a, 0xbf, 0x4c, 0xeb, 0x97, 0x63, 0x1f, 0xa6, 0x4a, 0x3f, 0xb0, 0x84, 0x1e, 0x93, 0xfb, 0xbf, 0x57, 0xa6, 0x17, 0xeb, 0xf4, 0x12, 0x9c, 0x9e, 0x9c, 0x5e, 0x0a, 0xef, 0xab, 0x68, 0x1b, 0xb9, 0xe4, 0xfe, 0xde, 0x22, 0xd3, 0x15, 0xa8, 0xf7, 0x4b, 0x7a, 0xdf, 0xbf, 0x2e, 0x69, 0x06, 0xfd, 0x92, 0xd4, 0x39, 0xfd, 0x2b, 0x49, 0x1b, 0xa9, 0x46, 0xee, 0x63, 0xb5, 0x2e, 0x95, 0xd8, 0x19, 0x0f, 0x48, 0xaa, 0xc6, 0x55, 0x89, 0x19, 0x7d, 0x55, 0xd2, 0x12, 0xfa, 0xb6, 0xce, 0xff, 0xa1, 0xa4, 0x6a, 0xfd, 0x2a, 0xb1, 0x12, 0xbf, 0x90, 0x54, 0xd0, 0x7b, 0x92, 0x16, 0x91, 0x43, 0xd6, 0xb7, 0x80, 0x52, 0x24, 0x5d, 0x48, 0xa9, 0x92, 0xae, 0x93, 0xeb, 0xc3, 0x7e, 0x24, 0x4f, 0xd2, 0xf5, 0x34, 0x4f, 0x52, 0xb5, 0x5e, 0x95, 0xb0, 0x98, 0x62, 0x49, 0x0b, 0xa9, 0x44, 0xd2, 0x0b, 0xa9, 0x5c, 0xd2, 0xd5, 0xb4, 0x58, 0xa8, 0xfe, 0x2c, 0x97, 0xb4, 0x9f, 0xaa, 0x24, 0x1d, 0xa4, 0x1e, 0xe9, 0x77, 0x9a, 0x65, 0xbf, 0x97, 0x21, 0x92, 0x7f, 0x5f, 0x52, 0x35, 0xef, 0xcb, 0xf5, 0x3c, 0x2f, 0x47, 0x44, 0xfd, 0x35, 0xed, 0x8f, 0xfe, 0x4a, 0x7c, 0x47, 0x50, 0xf2, 0x15, 0xba, 0x3f, 0x2b, 0x74, 0xfb, 0x55, 0x90, 0xa7, 0x90, 0xa2, 0xa9, 0x92, 0x36, 0xd1, 0x51, 0x49, 0xcf, 0xa5, 0x07, 0x25, 0x2d, 0xa0, 0x7f, 0x97, 0xd4, 0x4d, 0x3f, 0x90, 0x34, 0x85, 0xde, 0x90, 0xb4, 0x5e, 0xee, 0xa3, 0x2a, 0xdc, 0x3a, 0xde, 0xd4, 0xfa, 0xff, 0xa3, 0xcb, 0xff, 0x2f, 0xa9, 0xd8, 0xec, 0x03, 0x49, 0xcb, 0xa8, 0x57, 0x70, 0xbc, 0xa5, 0xea, 0x5f, 0xad, 0xe7, 0x77, 0x35, 0x2c, 0xe3, 0x27, 0x92, 0x56, 0x52, 0x87, 0xe0, 0xb8, 0x4b, 0xf5, 0xb7, 0x15, 0xeb, 0x66, 0x08, 0x8e, 0xbd, 0x06, 0xa5, 0x7e, 0x1b, 0x46, 0xf0, 0xa8, 0xa4, 0x4a, 0x6f, 0x2d, 0x4e, 0x49, 0x96, 0xaf, 0x85, 0x65, 0xf0, 0x7e, 0x3b, 0x47, 0xd7, 0x7b, 0x0e, 0x6e, 0x19, 0x8a, 0x6e, 0xa7, 0x87, 0xb4, 0xfc, 0x69, 0x9d, 0x7e, 0x56, 0xd2, 0x15, 0x64, 0x13, 0x2a, 0xbd, 0x42, 0xd2, 0xc5, 0xb4, 0x52, 0xf0, 0x5d, 0xa7, 0x86, 0x3e, 0x47, 0x7c, 0xd7, 0x51, 0xf5, 0x74, 0xea, 0x72, 0x9d, 0xd0, 0x7b, 0x4e, 0xd2, 0x25, 0xb2, 0x9d, 0x4e, 0x58, 0xd8, 0xef, 0x24, 0xad, 0x25, 0xbb, 0x50, 0xe9, 0x4a, 0xa1, 0xf2, 0xb9, 0xbe, 0x2e, 0x5d, 0xbe, 0x4b, 0xf7, 0xa3, 0x4b, 0xf7, 0xa3, 0x4b, 0xb7, 0xd7, 0x8d, 0x71, 0xfd, 0x85, 0x98, 0x96, 0x93, 0x53, 0x70, 0xac, 0xa8, 0xfa, 0xb3, 0x0e, 0x9e, 0xfb, 0xff, 0x12, 0xc7, 0x85, 0x2a, 0xbd, 0x5e, 0xd7, 0xb3, 0x1e, 0x37, 0xa5, 0x3a, 0xc1, 0xf7, 0x2c, 0x95, 0xee, 0xd5, 0xf6, 0xdc, 0x87, 0x1b, 0x5d, 0xb6, 0xe0, 0xfb, 0x96, 0xda, 0x07, 0x1c, 0x93, 0x23, 0x09, 0xeb, 0x54, 0x7f, 0x7c, 0xde, 0x1e, 0xc6, 0x45, 0xeb, 0x35, 0x04, 0x13, 0xab, 0x87, 0x54, 0x3c, 0x20, 0xa4, 0x3f, 0x4c, 0xe6, 0x1f, 0x47, 0x7e, 0x8a, 0x0e, 0x30, 0x4a, 0x74, 0xfe, 0x6a, 0x4b, 0xfe, 0x09, 0xe4, 0x2f, 0xd4, 0xf9, 0xee, 0x59, 0xf2, 0x5f, 0x43, 0xfe, 0x32, 0x9d, 0xcf, 0xf1, 0xad, 0xb0, 0xb3, 0x1f, 0x4c, 0xe6, 0xbf, 0x83, 0xfc, 0xf5, 0x83, 0xc9, 0xb4, 0xd0, 0x7a, 0x66, 0x9a, 0x2f, 0x81, 0xbb, 0x74, 0xbe, 0x67, 0x96, 0x7c, 0x37, 0xf2, 0xef, 0xd2, 0xf9, 0x1c, 0x0f, 0xe4, 0x38, 0x55, 0xfb, 0x37, 0x36, 0xf2, 0x79, 0xa2, 0xfe, 0xbe, 0xa9, 0xf3, 0x17, 0x9f, 0x26, 0xff, 0x4d, 0x9d, 0xbf, 0x44, 0xa7, 0xad, 0xfd, 0x6f, 0x46, 0xfd, 0xff, 0xad, 0xf3, 0x97, 0x6a, 0xb9, 0xc3, 0x92, 0xdf, 0x8f, 0xfc, 0x34, 0xaf, 0x4a, 0x57, 0x68, 0x79, 0xaf, 0x25, 0x7f, 0x1c, 0xf9, 0x0b, 0x74, 0xfe, 0x8a, 0x59, 0xe6, 0xe7, 0x10, 0xf2, 0xdb, 0x75, 0x7e, 0xa5, 0xce, 0x67, 0xbd, 0x3b, 0x1a, 0x55, 0xfe, 0x8d, 0xc8, 0xf7, 0xea, 0xfc, 0x65, 0x3a, 0xdf, 0x3a, 0x7f, 0x3f, 0x47, 0xfe, 0xb7, 0x74, 0xfe, 0xf2, 0x59, 0xea, 0x7f, 0x1f, 0xf9, 0x6f, 0xea, 0xfc, 0xaa, 0x59, 0xf2, 0xd3, 0x70, 0xc1, 0xfe, 0x2b, 0xf2, 0xd7, 0x0c, 0x71, 0xdc, 0x60, 0xc8, 0x3b, 0xe8, 0x45, 0x75, 0x6a, 0x2d, 0xbd, 0x30, 0xbe, 0x8b, 0x9d, 0x1c, 0x39, 0x67, 0x51, 0xc8, 0x6d, 0x40, 0x96, 0x65, 0xb4, 0x18, 0x99, 0x54, 0x64, 0x2c, 0x84, 0x07, 0xfb, 0x26, 0x5d, 0xec, 0x6e, 0x83, 0xbe, 0xcb, 0xc8, 0x36, 0x94, 0xe6, 0xbf, 0x69, 0xcd, 0x2b, 0xa0, 0x99, 0x01, 0xa9, 0x59, 0xdf, 0x78, 0x1d, 0xc7, 0x0a, 0x66, 0x7d, 0xa6, 0xd6, 0x3e, 0xad, 0x95, 0x6c, 0x37, 0x54, 0xa7, 0xd6, 0x64, 0xb6, 0x76, 0x43, 0x4e, 0x07, 0xc7, 0x3f, 0x46, 0x8e, 0xd4, 0x17, 0x52, 0x7f, 0xb2, 0x4e, 0xd9, 0xb2, 0x87, 0x77, 0x74, 0xa9, 0x8d, 0x22, 0xee, 0x87, 0x10, 0x2b, 0xe4, 0xc8, 0xfa, 0x78, 0x9c, 0x97, 0xd4, 0xa9, 0xbb, 0x6d, 0xc4, 0x39, 0x17, 0x69, 0x0f, 0xf6, 0x57, 0xc4, 0xc9, 0xe7, 0xc9, 0x88, 0xd3, 0x46, 0x23, 0x38, 0x00, 0xf8, 0xae, 0x6b, 0x87, 0x2e, 0xdf, 0xa9, 0x0f, 0xd7, 0xa9, 0x73, 0x3a, 0xea, 0x7c, 0x01, 0xe9, 0x2c, 0x5b, 0xd4, 0xf9, 0x1c, 0x68, 0x06, 0x7c, 0xd2, 0x1c, 0xc8, 0x9e, 0x67, 0x19, 0xec, 0x3f, 0x93, 0x5c, 0x39, 0xa1, 0xda, 0x2e, 0x44, 0x10, 0x15, 0xef, 0xcc, 0xd5, 0xbd, 0x56, 0x7f, 0xdc, 0xde, 0x5c, 0x69, 0x1b, 0x36, 0x29, 0xbd, 0xa9, 0x4e, 0x3d, 0x43, 0x71, 0x39, 0xeb, 0x6d, 0x0e, 0x72, 0xb9, 0x1b, 0x6c, 0x39, 0x14, 0x72, 0xba, 0xd0, 0x5e, 0x16, 0xea, 0xcc, 0x94, 0xfd, 0x0d, 0x39, 0xed, 0x7a, 0x8c, 0x5f, 0x41, 0x79, 0x57, 0x7b, 0x83, 0xad, 0x84, 0x8a, 0x6c, 0x3c, 0xb7, 0x1f, 0x97, 0x73, 0x6b, 0x33, 0x4b, 0xd8, 0x9a, 0x6d, 0x2e, 0xf2, 0x96, 0xab, 0x12, 0x36, 0x59, 0xe2, 0x59, 0xc8, 0x75, 0xca, 0x16, 0x72, 0xaf, 0x83, 0x87, 0x9d, 0x0b, 0x6f, 0x6b, 0xc8, 0xe7, 0x00, 0x77, 0xd7, 0xa9, 0x67, 0x27, 0xde, 0xf6, 0xe4, 0x3c, 0x16, 0x89, 0x85, 0xf0, 0xfe, 0x69, 0x54, 0xe4, 0x98, 0x2b, 0xeb, 0x3e, 0x0f, 0xfa, 0x21, 0x0e, 0xac, 0x90, 0xa7, 0xe4, 0x73, 0x12, 0xf2, 0x66, 0xc7, 0x2a, 0xf2, 0x38, 0xd2, 0x31, 0x5b, 0xd9, 0x18, 0x4f, 0x91, 0x70, 0xa0, 0x96, 0x56, 0xf4, 0x30, 0x4b, 0x84, 0xdc, 0x1c, 0x5b, 0x79, 0x44, 0x16, 0xf2, 0x72, 0xb9, 0xcf, 0xae, 0x90, 0x7b, 0x1e, 0xec, 0x28, 0xcb, 0x70, 0xa1, 0x17, 0xf9, 0xe8, 0x05, 0xb8, 0x32, 0xc5, 0x85, 0x9c, 0x4e, 0x94, 0xce, 0x72, 0x94, 0x3b, 0xcf, 0x93, 0xf4, 0x62, 0xe7, 0x71, 0x49, 0x43, 0xce, 0x3c, 0x55, 0x57, 0x7b, 0x16, 0xf5, 0x77, 0xa0, 0x17, 0xee, 0x39, 0xf0, 0xac, 0x3c, 0x77, 0xa6, 0x1d, 0x7c, 0xa1, 0x4e, 0x3d, 0xab, 0x98, 0x6a, 0x2f, 0x1f, 0x87, 0x1d, 0xcc, 0x85, 0x8e, 0x4d, 0xce, 0xf5, 0x97, 0xf4, 0xda, 0x47, 0x9c, 0xbc, 0x2b, 0x46, 0xd0, 0xc7, 0xad, 0x46, 0x0a, 0xf2, 0x1d, 0x7a, 0x3d, 0x4f, 0xd4, 0xa9, 0xb8, 0x31, 0xec, 0x5e, 0xce, 0x7e, 0xb6, 0xd6, 0x81, 0x5e, 0x97, 0xd0, 0x40, 0x7d, 0x0a, 0x45, 0x6a, 0x37, 0x50, 0x87, 0x11, 0x72, 0xdf, 0x80, 0xfc, 0xb9, 0x89, 0x36, 0x5f, 0xae, 0x53, 0xcf, 0x4a, 0xc2, 0xee, 0x15, 0x18, 0x8f, 0x17, 0xd6, 0x16, 0x72, 0x7f, 0x52, 0xb6, 0x48, 0x89, 0x36, 0xbf, 0x93, 0x68, 0xf3, 0x6e, 0xd9, 0x66, 0x2a, 0xda, 0x4c, 0x23, 0xb6, 0x2d, 0x21, 0xf3, 0x7f, 0x58, 0xa7, 0x9e, 0xc3, 0xa8, 0xfc, 0xb9, 0xda, 0x0a, 0x71, 0x5f, 0xad, 0x53, 0xcf, 0x6d, 0x86, 0x9c, 0xf3, 0xa4, 0x7d, 0xdb, 0xb4, 0xfe, 0x9b, 0x75, 0xea, 0x79, 0x90, 0x07, 0x3b, 0x37, 0xe2, 0xe4, 0x9b, 0xfd, 0x40, 0x69, 0x1e, 0x6d, 0x15, 0x4d, 0xb0, 0x8e, 0x5c, 0xcc, 0xdf, 0x2a, 0x68, 0x65, 0x88, 0xad, 0x62, 0x25, 0xb9, 0x4a, 0xb7, 0x8a, 0x55, 0x32, 0x9e, 0x4d, 0x83, 0x87, 0xe7, 0xba, 0xde, 0xd6, 0xf6, 0x1a, 0x71, 0x6e, 0x97, 0xb6, 0xbd, 0x0c, 0xe3, 0xba, 0x9a, 0x2e, 0x4a, 0x1d, 0x71, 0x16, 0xc0, 0xb6, 0xf3, 0x69, 0x24, 0x37, 0x97, 0xb6, 0xe6, 0xa1, 0xb6, 0x79, 0x85, 0xd8, 0xa1, 0x17, 0xba, 0xe6, 0xc9, 0xb9, 0x11, 0xb2, 0x4f, 0x7f, 0xa9, 0x53, 0xbe, 0xce, 0xdb, 0x9e, 0x8b, 0x38, 0xa8, 0x19, 0x51, 0x77, 0xc8, 0xb9, 0x45, 0x5a, 0xa6, 0xb7, 0x93, 0xdb, 0x6d, 0x84, 0x56, 0x45, 0x86, 0xdc, 0xe5, 0xb6, 0x02, 0x69, 0x15, 0xf5, 0x54, 0x61, 0xa4, 0x3a, 0xbd, 0x9d, 0xf9, 0xd4, 0x6c, 0xa4, 0xc2, 0x7e, 0x79, 0x76, 0x3e, 0x06, 0xad, 0x91, 0x0e, 0xb4, 0xd4, 0x91, 0x8b, 0xba, 0xd3, 0xa5, 0xed, 0x63, 0x5c, 0xa2, 0x31, 0xdb, 0x9c, 0x0f, 0x47, 0xbd, 0x7a, 0x16, 0x15, 0x72, 0xf2, 0x13, 0xbf, 0xb0, 0xf3, 0x1e, 0xbd, 0x47, 0x84, 0x9e, 0x17, 0xeb, 0xbe, 0x49, 0x91, 0x91, 0xc5, 0x54, 0x99, 0xb9, 0xcf, 0x33, 0xeb, 0xd5, 0x3c, 0x79, 0x61, 0x57, 0xcd, 0x98, 0x41, 0xaf, 0x3b, 0x57, 0xee, 0xa1, 0xa1, 0xd2, 0x79, 0xb8, 0x87, 0xa4, 0x62, 0xb6, 0x0a, 0xd0, 0x9f, 0x26, 0xac, 0xa7, 0xcb, 0x79, 0xa1, 0x7b, 0xde, 0x94, 0xb2, 0xae, 0x33, 0x94, 0x6d, 0x4e, 0x94, 0x5d, 0xc9, 0x65, 0xc9, 0x2c, 0x6b, 0x58, 0xfa, 0x27, 0xeb, 0x40, 0x39, 0x73, 0xcd, 0xe6, 0xd7, 0xcf, 0x5c, 0xb3, 0x22, 0xbd, 0x66, 0xf3, 0x31, 0xd2, 0x8a, 0xe4, 0x9a, 0xe5, 0x98, 0x6b, 0xe6, 0x90, 0x33, 0x03, 0x7f, 0x5b, 0xaf, 0x9e, 0xbb, 0xf1, 0x9a, 0xb1, 0xfd, 0x2c, 0xa0, 0x11, 0x63, 0x3e, 0x6d, 0xb5, 0xa1, 0xbc, 0x7d, 0xa1, 0x65, 0x7d, 0xaa, 0xeb, 0xcd, 0xf5, 0x99, 0x9f, 0x58, 0x9f, 0x0b, 0xf4, 0xfa, 0x70, 0x1b, 0x4b, 0x4f, 0xbb, 0x3e, 0x0b, 0x66, 0xac, 0x0f, 0xda, 0xe8, 0x98, 0x7f, 0x9a, 0xf5, 0x59, 0x9d, 0x58, 0x9f, 0xe1, 0x29, 0xeb, 0x93, 0x25, 0x57, 0x43, 0x50, 0x7b, 0xbd, 0x7a, 0xa6, 0xe9, 0xbd, 0x68, 0x3e, 0xea, 0xcc, 0xa1, 0xd4, 0x8b, 0xc5, 0x15, 0xe2, 0x16, 0xfb, 0xfd, 0xf1, 0x94, 0x4a, 0xa9, 0x59, 0x94, 0x8a, 0x1e, 0xa4, 0x35, 0x08, 0xee, 0xc1, 0xd5, 0x48, 0xa7, 0x99, 0xb6, 0x2d, 0x1a, 0xb3, 0x66, 0x5b, 0xe3, 0xd3, 0xc9, 0x98, 0xdf, 0x58, 0x6f, 0xee, 0x9d, 0x66, 0x99, 0x93, 0x22, 0x6f, 0xb9, 0x44, 0x5b, 0xea, 0x95, 0x0f, 0xf7, 0xb6, 0x2f, 0xa2, 0x81, 0x8e, 0x62, 0x1a, 0xec, 0x2c, 0xa1, 0x12, 0xc7, 0x12, 0x0a, 0x0d, 0x1d, 0x24, 0x67, 0x7d, 0x96, 0x23, 0xc7, 0x91, 0x9c, 0x5f, 0x6f, 0xbd, 0xf2, 0x1b, 0xe6, 0xfe, 0x5c, 0x84, 0xfd, 0x59, 0x4c, 0xdb, 0xe0, 0x55, 0xe7, 0x26, 0xfc, 0x3c, 0xd7, 0x27, 0xcf, 0x22, 0xb7, 0x1b, 0x2b, 0x57, 0x4a, 0x25, 0x36, 0xd4, 0x55, 0x7b, 0x09, 0x39, 0x6d, 0x59, 0xb6, 0x1c, 0x5b, 0x72, 0xaf, 0x8f, 0xd4, 0x4f, 0xdd, 0xeb, 0x6e, 0xd4, 0x55, 0x2a, 0xd7, 0x48, 0xb5, 0xe5, 0xd3, 0x6b, 0x39, 0x22, 0xca, 0x4d, 0x1d, 0xc3, 0x43, 0x23, 0xb6, 0x32, 0xac, 0xe5, 0x62, 0x8b, 0x9f, 0x9a, 0xd0, 0xf5, 0x8c, 0x88, 0xa5, 0xd0, 0xcb, 0x90, 0x7a, 0x4b, 0x64, 0x7f, 0xec, 0xd2, 0xb6, 0xf6, 0xd5, 0xab, 0x7b, 0xaa, 0xcb, 0xd3, 0x50, 0x96, 0x2d, 0xfd, 0x69, 0x3a, 0xfb, 0x1f, 0xe8, 0x84, 0x9c, 0x69, 0xda, 0xe7, 0x5e, 0x09, 0x0b, 0xb4, 0xda, 0xe5, 0x65, 0xf5, 0x2a, 0x06, 0xf1, 0xc2, 0xda, 0xbc, 0x88, 0x86, 0x72, 0xcc, 0xb5, 0x45, 0x9b, 0x57, 0xd5, 0xab, 0x78, 0x7a, 0x21, 0xa2, 0x91, 0x45, 0xb0, 0x9e, 0x91, 0x8e, 0x4a, 0xac, 0xec, 0xd7, 0xd0, 0x5a, 0x86, 0xe1, 0xed, 0x82, 0x7e, 0x43, 0x19, 0x35, 0xdb, 0xf2, 0xc8, 0xe4, 0x43, 0x4e, 0xf6, 0x27, 0xc9, 0x54, 0x8a, 0x4e, 0x8d, 0xd4, 0x95, 0x69, 0xa9, 0x07, 0xfa, 0x73, 0x12, 0x7c, 0xc8, 0x39, 0x3f, 0xa1, 0xe1, 0x41, 0xc4, 0x98, 0x6d, 0xcc, 0x11, 0xc5, 0xb4, 0x94, 0xc3, 0x3a, 0xec, 0x09, 0x21, 0x63, 0x20, 0xae, 0xb1, 0x46, 0xfa, 0x2a, 0xb6, 0xe7, 0xa6, 0xcb, 0x1a, 0x6b, 0x9a, 0x64, 0xdf, 0xb9, 0xf7, 0xb7, 0x9a, 0xeb, 0x29, 0xfb, 0xce, 0xf5, 0x15, 0x49, 0x8b, 0x0b, 0x39, 0x39, 0x1a, 0xc9, 0x90, 0xfe, 0x51, 0x8d, 0xc5, 0xa0, 0x3b, 0xeb, 0x55, 0x4c, 0xa3, 0xc6, 0xe2, 0xed, 0x40, 0x89, 0x1c, 0xf4, 0x46, 0x24, 0x79, 0x2e, 0x2d, 0xe7, 0xc8, 0xb9, 0x84, 0xf7, 0x9d, 0xe1, 0x6a, 0x68, 0xa8, 0xc7, 0xf9, 0xe6, 0xbc, 0x58, 0x4a, 0x9b, 0x45, 0x9a, 0x3c, 0x23, 0x99, 0x2f, 0xc6, 0x8d, 0xb9, 0x98, 0xb8, 0xad, 0x1c, 0x52, 0x3d, 0x56, 0x3e, 0x99, 0xfb, 0x5c, 0x99, 0xb0, 0xc3, 0x0a, 0xb9, 0x2e, 0xdc, 0xcf, 0xa3, 0xda, 0x96, 0x46, 0x04, 0x3c, 0xab, 0xf3, 0x25, 0xb9, 0x6e, 0x95, 0x58, 0xdf, 0x0a, 0xb9, 0x76, 0x6a, 0x9f, 0x1e, 0xaf, 0x57, 0xb1, 0xb2, 0xee, 0x5f, 0x5d, 0x25, 0xfa, 0x96, 0x0e, 0x2b, 0xac, 0xc0, 0x2e, 0xe4, 0x1d, 0xf8, 0x51, 0xac, 0x64, 0x31, 0xf4, 0xad, 0xed, 0x65, 0xcb, 0x76, 0x9c, 0x09, 0xbb, 0x7f, 0x5a, 0xaf, 0xe5, 0xc5, 0xce, 0x49, 0x39, 0x0f, 0x39, 0x7a, 0x9d, 0xd9, 0x36, 0x9e, 0xd7, 0x73, 0x15, 0x72, 0x2e, 0x96, 0x63, 0x28, 0xa2, 0xc7, 0x51, 0x6b, 0x8b, 0xe4, 0x4b, 0x84, 0x4d, 0x3e, 0x03, 0x50, 0x67, 0xbe, 0xa0, 0x97, 0xea, 0xd5, 0x33, 0x65, 0x1e, 0xaf, 0x43, 0xce, 0x27, 0x7f, 0x4b, 0x91, 0x21, 0x5c, 0x46, 0x33, 0x34, 0xb3, 0x8d, 0x85, 0x02, 0xb7, 0x50, 0xc1, 0x51, 0x97, 0x5d, 0x46, 0x35, 0x76, 0xdc, 0x24, 0x11, 0xb7, 0xb5, 0xaf, 0x30, 0x4b, 0xf0, 0x09, 0x5e, 0xfb, 0x25, 0x72, 0xdb, 0xbc, 0xed, 0xcb, 0x21, 0x2b, 0x90, 0xbd, 0xf1, 0x18, 0xb0, 0xda, 0xda, 0x71, 0xea, 0xb4, 0xbb, 0x72, 0x43, 0xed, 0x36, 0x72, 0x94, 0x71, 0xf4, 0xc2, 0x7e, 0x28, 0x51, 0xce, 0xc1, 0xa5, 0x06, 0xa8, 0xc2, 0xce, 0x76, 0xea, 0xe5, 0x5a, 0x6d, 0x73, 0x1c, 0xc5, 0x62, 0xa9, 0x43, 0xc5, 0x72, 0x4d, 0x72, 0x9c, 0x9d, 0x18, 0x87, 0x43, 0xfa, 0x9a, 0xff, 0xa8, 0x57, 0xf7, 0x0c, 0xef, 0x09, 0xac, 0x9e, 0x7b, 0x21, 0xdd, 0x88, 0xf8, 0xa5, 0xc5, 0x66, 0x27, 0x57, 0x7a, 0x4e, 0xba, 0x47, 0xc0, 0xf2, 0xbf, 0xa1, 0x56, 0x35, 0x9d, 0xb2, 0xd2, 0xd9, 0x26, 0xd2, 0x29, 0xc3, 0x3e, 0xf0, 0xb5, 0xc5, 0x94, 0x7a, 0x85, 0xfd, 0xe8, 0xbe, 0xd4, 0x3e, 0xd4, 0x95, 0x23, 0x38, 0xc6, 0xe2, 0x39, 0xfb, 0x59, 0x3d, 0xcf, 0x3d, 0xfb, 0x06, 0xae, 0x6b, 0x01, 0x6e, 0x5b, 0xba, 0x2e, 0xec, 0x64, 0x0f, 0x56, 0xd6, 0xdb, 0x61, 0xb5, 0x90, 0x52, 0xe9, 0x99, 0x07, 0xba, 0x17, 0x53, 0xc4, 0x3b, 0x44, 0xee, 0xdc, 0x1c, 0x4a, 0xd1, 0xbb, 0xf4, 0xd7, 0xf5, 0xea, 0xfb, 0xb0, 0x85, 0xf2, 0xa6, 0x1e, 0x71, 0xef, 0x47, 0xc4, 0x90, 0x65, 0x87, 0xe7, 0xb5, 0xa3, 0x3f, 0x9d, 0x1e, 0x6a, 0x41, 0x24, 0x5b, 0xb1, 0x20, 0xea, 0x7e, 0x19, 0x33, 0x9c, 0x65, 0x2f, 0xb1, 0x97, 0x51, 0x8b, 0x1d, 0xf3, 0x87, 0x9e, 0x78, 0x9b, 0xea, 0xd0, 0xc2, 0x0e, 0xf6, 0xcd, 0xc2, 0x63, 0xac, 0x45, 0xd9, 0xa3, 0x18, 0x2b, 0xcf, 0xc8, 0x3c, 0x78, 0x9e, 0x1c, 0x3b, 0xeb, 0xcc, 0xc1, 0xe7, 0x52, 0x3b, 0xaf, 0xfd, 0x32, 0x39, 0x17, 0xcb, 0xe5, 0xf3, 0x36, 0xee, 0xff, 0xff, 0x6a, 0x1b, 0xf3, 0xe0, 0xe6, 0x12, 0x19, 0x1a, 0x25, 0x67, 0x57, 0xa4, 0xf6, 0xc3, 0x32, 0x9a, 0xca, 0x31, 0xec, 0xb2, 0x6f, 0xb8, 0x17, 0x27, 0x74, 0xd0, 0xef, 0x5a, 0x3f, 0xfc, 0x54, 0x52, 0x27, 0x55, 0xc7, 0x2e, 0x29, 0x0d, 0xea, 0xfb, 0x3b, 0x6f, 0x7f, 0x2d, 0x7a, 0xf3, 0x98, 0x5c, 0x39, 0x8e, 0x5f, 0x79, 0xfd, 0x9b, 0xc5, 0x12, 0x19, 0xbf, 0x92, 0xb4, 0x99, 0x25, 0xe4, 0x75, 0xa9, 0x39, 0x31, 0x64, 0x4c, 0xbd, 0x04, 0x34, 0xc3, 0xe6, 0x6a, 0x6c, 0xe8, 0x7a, 0xfb, 0x03, 0xb6, 0x40, 0x96, 0x36, 0x1b, 0xbf, 0xfc, 0x80, 0xd7, 0x56, 0x6a, 0xb8, 0xef, 0xa5, 0x9d, 0xd8, 0x57, 0xcd, 0xc6, 0x8f, 0x3f, 0xc8, 0x11, 0xae, 0x94, 0x9c, 0x94, 0x14, 0xbd, 0x93, 0x5d, 0x0d, 0xea, 0xbe, 0xa2, 0xe6, 0xcc, 0xdb, 0xa1, 0x5a, 0xe6, 0x99, 0xe6, 0x96, 0x85, 0x2c, 0x93, 0x2f, 0x5b, 0x86, 0xcc, 0x28, 0x81, 0x3f, 0xf3, 0x96, 0xa9, 0x96, 0x65, 0xc4, 0x8a, 0xd5, 0x40, 0x74, 0x6d, 0x6b, 0x18, 0xfc, 0xed, 0x07, 0x98, 0x23, 0xcc, 0x95, 0x0b, 0x9e, 0x1c, 0x76, 0x43, 0x6c, 0x37, 0x3c, 0xee, 0xc5, 0xb2, 0x95, 0x25, 0x32, 0x9e, 0xe3, 0x31, 0x16, 0x37, 0x28, 0x5f, 0xe0, 0xad, 0xe5, 0xc8, 0xf2, 0x29, 0x3d, 0xc6, 0x17, 0xa7, 0x8d, 0x75, 0x81, 0x65, 0xac, 0xcd, 0xd8, 0x89, 0xd0, 0x75, 0x3f, 0x01, 0xef, 0xc9, 0xe9, 0x0a, 0x94, 0x0b, 0xca, 0xde, 0xf0, 0xa8, 0x3a, 0x65, 0x0f, 0x4f, 0xf1, 0xa8, 0xec, 0x39, 0x76, 0xd2, 0xed, 0xc0, 0x03, 0x34, 0x98, 0x3e, 0x87, 0xc7, 0xc5, 0xbb, 0x08, 0xfb, 0x45, 0xb4, 0x60, 0x07, 0x47, 0xdc, 0x31, 0xb6, 0x0b, 0xb4, 0x82, 0x78, 0x3e, 0x0f, 0x9e, 0xcf, 0xee, 0xad, 0xc3, 0x2e, 0x70, 0x3f, 0x4e, 0x1d, 0xbc, 0xcf, 0x3c, 0xcd, 0xc2, 0x2e, 0xfd, 0x61, 0xc4, 0x1d, 0x97, 0xf6, 0xd3, 0x6c, 0x57, 0x69, 0x8c, 0xcd, 0x9e, 0x6d, 0x57, 0x56, 0xa0, 0xc6, 0xb6, 0x54, 0xfb, 0x9a, 0x14, 0x79, 0x26, 0x19, 0xb4, 0xb2, 0x41, 0xdf, 0xb7, 0xda, 0x1b, 0xa4, 0xcf, 0xf6, 0x76, 0x36, 0xc0, 0x1f, 0xdc, 0xa5, 0xa3, 0xe6, 0xbb, 0xb8, 0xcf, 0xd8, 0x9b, 0x4f, 0xd2, 0x80, 0x2d, 0xcb, 0x51, 0xe2, 0x68, 0x46, 0xaf, 0x3e, 0x8d, 0xbc, 0x0c, 0xcc, 0x56, 0x36, 0xe6, 0xac, 0xc2, 0x06, 0xca, 0x1e, 0xc6, 0xc1, 0x3e, 0x98, 0x63, 0xeb, 0x42, 0xe9, 0x83, 0x31, 0xa2, 0xcb, 0x5a, 0x16, 0x16, 0xc1, 0xe7, 0xa5, 0xe8, 0xb1, 0x75, 0x34, 0xa8, 0xef, 0x36, 0x23, 0xce, 0xeb, 0x31, 0xaa, 0x8b, 0xe5, 0xb3, 0xeb, 0x2c, 0x1a, 0x69, 0xaf, 0x86, 0x75, 0x75, 0x42, 0xca, 0xcf, 0x9a, 0x47, 0xe0, 0x11, 0xac, 0xa9, 0xfa, 0x29, 0xa9, 0x06, 0x79, 0xd7, 0x8a, 0x38, 0x8f, 0xc9, 0x54, 0xed, 0x94, 0x54, 0xd5, 0x14, 0xcd, 0x3a, 0x1a, 0xe9, 0x59, 0x8e, 0xb6, 0x33, 0xe0, 0x73, 0x0a, 0xe5, 0x48, 0x4a, 0x1c, 0xf3, 0x20, 0xab, 0x81, 0x0c, 0x37, 0x11, 0x07, 0xdf, 0x38, 0x0e, 0xc9, 0xd8, 0xa5, 0x9f, 0xd8, 0x63, 0xdf, 0x21, 0x75, 0xf8, 0xf6, 0xa5, 0xe8, 0x73, 0x3c, 0x42, 0xcc, 0xb5, 0x47, 0xae, 0xa8, 0x43, 0xae, 0x60, 0x09, 0x66, 0xfb, 0x52, 0x8c, 0xb0, 0xe2, 0x5d, 0x8f, 0xd1, 0x86, 0xd8, 0xe7, 0x72, 0xac, 0xc9, 0x7d, 0xf2, 0xa6, 0x71, 0x44, 0x9f, 0x94, 0xf7, 0x63, 0xad, 0x43, 0xce, 0x07, 0xd8, 0x76, 0x65, 0x54, 0xe2, 0xa4, 0x8a, 0x9f, 0x46, 0x9c, 0x57, 0x61, 0xb4, 0x7c, 0x7e, 0x6e, 0x4f, 0xc4, 0x27, 0xab, 0x7c, 0x2a, 0xd6, 0x32, 0xe8, 0x22, 0xcc, 0xc9, 0x83, 0x72, 0xee, 0xab, 0x66, 0xd9, 0x3b, 0x73, 0x2d, 0xf6, 0x54, 0x0c, 0x5f, 0x50, 0x83, 0x1e, 0x34, 0xf0, 0xed, 0xef, 0xbf, 0x94, 0xfe, 0x23, 0x96, 0x1d, 0xc0, 0x1e, 0xf1, 0x8b, 0xa8, 0xd5, 0xdb, 0xa1, 0x2c, 0x54, 0xed, 0x85, 0x17, 0xa7, 0xed, 0x89, 0x3c, 0xcb, 0x9e, 0x80, 0x85, 0x76, 0x29, 0x0b, 0x2d, 0x93, 0x69, 0xb6, 0xd0, 0xbd, 0x1c, 0xdb, 0xbd, 0xcd, 0xe7, 0x76, 0x3d, 0xac, 0xc7, 0xdb, 0x5e, 0x77, 0x3a, 0x6b, 0x47, 0x5d, 0x45, 0x66, 0xef, 0x50, 0x76, 0x39, 0xea, 0xaa, 0xb3, 0xd4, 0xb5, 0x16, 0xe5, 0x1e, 0x94, 0x7b, 0xb8, 0xc4, 0xe0, 0x73, 0xfd, 0x43, 0x5c, 0xef, 0x2f, 0x47, 0x3a, 0xea, 0x50, 0xe7, 0x6c, 0x5e, 0x62, 0xe1, 0x34, 0x2f, 0x81, 0x28, 0xc1, 0xc8, 0x92, 0x94, 0x23, 0x04, 0x43, 0x73, 0x6c, 0x49, 0xea, 0xdc, 0x9d, 0xcf, 0x16, 0xfc, 0xa6, 0xaa, 0x8b, 0x67, 0x81, 0xa3, 0x47, 0xbb, 0xe5, 0xac, 0x63, 0x7b, 0x5e, 0x0e, 0x9d, 0x07, 0x12, 0x67, 0xeb, 0xfd, 0x42, 0x9d, 0x55, 0x88, 0x51, 0x1a, 0xd4, 0xb3, 0x18, 0x65, 0xef, 0x5e, 0x98, 0xa5, 0x77, 0x20, 0x55, 0x46, 0x00, 0xf0, 0x46, 0x06, 0x5b, 0x3c, 0xf7, 0x7b, 0x60, 0x30, 0x4d, 0xee, 0x02, 0xe9, 0x2f, 0xe0, 0x51, 0x4b, 0x6d, 0x3c, 0x1f, 0x35, 0xe4, 0x1d, 0x4a, 0xc5, 0x1e, 0x09, 0xa1, 0xe5, 0xfe, 0x3c, 0xb4, 0x67, 0xf1, 0x19, 0xf9, 0x68, 0x65, 0xbe, 0x8c, 0x3b, 0x6c, 0xea, 0x59, 0x93, 0x5c, 0x63, 0x8f, 0x58, 0x59, 0xca, 0x77, 0x3b, 0x43, 0xc6, 0x15, 0x37, 0xea, 0x3d, 0xc0, 0x9e, 0x28, 0xcd, 0x12, 0x77, 0x44, 0xdc, 0x07, 0xb0, 0x67, 0x32, 0x70, 0x17, 0xb6, 0x61, 0x14, 0xfa, 0xb4, 0x54, 0x3a, 0x86, 0xee, 0x99, 0xcd, 0x1b, 0xaf, 0x37, 0x7b, 0xe4, 0xe0, 0x7d, 0xef, 0xc5, 0xd9, 0x24, 0x77, 0xb9, 0xe0, 0xbb, 0xb4, 0xf4, 0xa5, 0x76, 0x57, 0x93, 0x2b, 0xa5, 0xc1, 0x5b, 0x4e, 0xa6, 0x37, 0x75, 0xe5, 0x35, 0xc3, 0x03, 0x98, 0xfe, 0xd4, 0x3b, 0xa9, 0x3c, 0xc6, 0x62, 0xec, 0xde, 0x66, 0x07, 0xee, 0xf4, 0x32, 0xfd, 0x04, 0xd2, 0x59, 0x8e, 0xd4, 0xe1, 0x92, 0x14, 0xbb, 0x96, 0x3c, 0x0d, 0x49, 0x85, 0xe1, 0xb2, 0x7b, 0xe0, 0x37, 0x23, 0xfd, 0xbb, 0x29, 0x3a, 0xee, 0x49, 0xab, 0xa0, 0x48, 0xfb, 0x1e, 0x3a, 0xb0, 0xd4, 0x1b, 0x49, 0xf4, 0xc2, 0x90, 0x27, 0x76, 0x9a, 0x87, 0xd6, 0x63, 0xdf, 0xf1, 0x37, 0x4a, 0xcd, 0x76, 0x07, 0xe5, 0x1a, 0xaf, 0xa5, 0x56, 0xd8, 0x72, 0x8d, 0x31, 0xba, 0xdc, 0x13, 0x6a, 0x3f, 0x49, 0x87, 0xea, 0xb9, 0x7f, 0x73, 0xd2, 0x70, 0x26, 0xa7, 0xa9, 0xef, 0x40, 0xc6, 0xe5, 0xcc, 0x1c, 0xc0, 0x68, 0x0d, 0x79, 0xca, 0x1c, 0xc5, 0x9c, 0xb4, 0x8b, 0x84, 0x25, 0xdf, 0x34, 0xf3, 0xd4, 0xf4, 0x5e, 0xa1, 0xda, 0x94, 0x9e, 0x08, 0xfd, 0xaf, 0x97, 0xf6, 0xf7, 0x01, 0x79, 0xaf, 0xc0, 0xfa, 0xbb, 0x1f, 0xa5, 0x9b, 0x91, 0x6e, 0x41, 0x8c, 0x61, 0xd1, 0x13, 0x3c, 0x0a, 0x8c, 0xf0, 0x72, 0xd6, 0x78, 0x98, 0x6e, 0xc2, 0x3a, 0xb8, 0xd1, 0xcb, 0x8d, 0xbc, 0x43, 0x6e, 0xb6, 0x9e, 0x41, 0x8b, 0x25, 0x2d, 0xb2, 0xcd, 0x41, 0x89, 0xd5, 0xf2, 0x09, 0xc2, 0x88, 0x6b, 0xce, 0x69, 0x74, 0xd2, 0x2d, 0x3a, 0x59, 0xa7, 0xd1, 0xa9, 0xb0, 0xe8, 0xcc, 0x3d, 0x8d, 0x4e, 0x93, 0x45, 0x27, 0x7b, 0x9a, 0x8e, 0xb2, 0xc2, 0xad, 0xae, 0x4c, 0x1a, 0xb8, 0x79, 0x31, 0x6d, 0x75, 0xe5, 0x4c, 0xcb, 0x5f, 0xa2, 0xf3, 0x5d, 0x88, 0x8e, 0x94, 0xf7, 0xc3, 0x3d, 0xcb, 0x95, 0x31, 0x25, 0x85, 0xdb, 0xa4, 0x21, 0xca, 0xbc, 0xb7, 0xaa, 0x72, 0x88, 0x0e, 0xec, 0x21, 0xa7, 0x87, 0x4f, 0x12, 0x7b, 0x53, 0x17, 0xfa, 0x9d, 0x97, 0x01, 0xef, 0x47, 0x72, 0x5e, 0xbc, 0xf4, 0x23, 0x9b, 0x4d, 0x54, 0xfc, 0x6e, 0xba, 0x76, 0x99, 0x45, 0xdb, 0x39, 0x43, 0xdb, 0x7b, 0x8b, 0xf5, 0x2c, 0x2d, 0x97, 0xb4, 0xb4, 0xb1, 0xa9, 0xb3, 0x98, 0xd6, 0x39, 0x6c, 0xe8, 0x63, 0x09, 0x7b, 0xcf, 0x14, 0x57, 0x41, 0xc3, 0x3e, 0x07, 0x79, 0xf3, 0x33, 0xa8, 0xc2, 0xe6, 0xcd, 0x77, 0xa2, 0x44, 0x31, 0xcb, 0x53, 0x43, 0xce, 0x45, 0x3a, 0x72, 0xfb, 0x3c, 0xed, 0x73, 0xfc, 0xc8, 0x30, 0x44, 0xc5, 0x2f, 0x2f, 0xc2, 0x21, 0x9b, 0x6b, 0x2b, 0xa5, 0x95, 0x65, 0x98, 0xfb, 0x3b, 0xad, 0x31, 0x98, 0x9b, 0x63, 0xb0, 0x74, 0x58, 0xe3, 0x5f, 0xb6, 0xce, 0x4f, 0xc7, 0xea, 0xde, 0xc2, 0x3b, 0x7f, 0x89, 0xd5, 0x4a, 0xd8, 0xd6, 0x55, 0xbc, 0x9d, 0x9f, 0x88, 0xb7, 0xf9, 0xb4, 0x2c, 0x94, 0x32, 0x8c, 0x20, 0x07, 0xde, 0x44, 0xc6, 0xe2, 0xa9, 0xc8, 0x63, 0x2f, 0x22, 0x74, 0x1b, 0xf3, 0x51, 0xb7, 0x8c, 0x90, 0xd3, 0xb1, 0x8b, 0xd3, 0x79, 0x0f, 0x37, 0x08, 0x21, 0x9f, 0xb5, 0x3b, 0x30, 0x7e, 0xfe, 0x8e, 0x47, 0xc5, 0xda, 0xcf, 0x19, 0x97, 0x35, 0x7e, 0xc5, 0x78, 0xce, 0x50, 0xfb, 0x98, 0xbd, 0xc8, 0xeb, 0x0d, 0xea, 0x9d, 0x1d, 0x6f, 0x44, 0xfa, 0x90, 0x8b, 0x1b, 0xe4, 0x4e, 0x4e, 0xb3, 0xf8, 0x0f, 0xb6, 0x3f, 0x8e, 0xbf, 0xcd, 0xbd, 0x2c, 0x73, 0x65, 0x74, 0x97, 0x2a, 0x9f, 0x79, 0xa9, 0x7d, 0xeb, 0x35, 0x66, 0xae, 0xbd, 0x37, 0xa6, 0xbc, 0xae, 0x7a, 0x0e, 0xf8, 0xa2, 0xf5, 0x79, 0x20, 0xf6, 0xbb, 0xc7, 0x7c, 0x1e, 0x88, 0x48, 0x70, 0x39, 0x0d, 0xf4, 0x34, 0x99, 0x2d, 0xa7, 0x34, 0xf8, 0xff, 0xf8, 0x81, 0x2c, 0x8b, 0x1d, 0x8d, 0x08, 0xc0, 0x56, 0x62, 0x53, 0x5e, 0xd8, 0x26, 0x79, 0xe5, 0x85, 0x11, 0x49, 0x7e, 0xcf, 0x9b, 0x86, 0xf9, 0x48, 0xc3, 0xbc, 0xa4, 0x29, 0xef, 0x9a, 0xa6, 0xb9, 0x14, 0xe6, 0x0e, 0xf0, 0x7e, 0xf9, 0x1c, 0xb8, 0x62, 0xcc, 0xeb, 0xcd, 0xe8, 0x8f, 0xb9, 0x77, 0xf1, 0x49, 0x4b, 0xd3, 0x6c, 0xf2, 0x76, 0x2c, 0xe4, 0xbd, 0x7c, 0x1b, 0x6a, 0x8b, 0x49, 0x3f, 0x67, 0x93, 0xef, 0x30, 0x29, 0x7f, 0x37, 0x29, 0xdf, 0x6d, 0xb2, 0xeb, 0x67, 0x81, 0x7f, 0x6e, 0x50, 0x77, 0x7c, 0xaf, 0xbb, 0x46, 0x7a, 0xcd, 0x1a, 0xaa, 0x75, 0xa9, 0xe7, 0x2d, 0x42, 0xe6, 0xff, 0x05, 0xf9, 0xfb, 0x89, 0xef, 0x31, 0xa5, 0x74, 0x11, 0xe6, 0xdb, 0xe5, 0x5a, 0xe9, 0xdc, 0x4c, 0x2e, 0x37, 0xcf, 0x22, 0x9f, 0x10, 0x7a, 0x36, 0x45, 0xb9, 0xdb, 0x8b, 0xd3, 0x94, 0xd3, 0xc7, 0xf5, 0xc9, 0xf1, 0xc2, 0xb4, 0x13, 0xa4, 0x60, 0x4a, 0xec, 0xc5, 0x31, 0x8a, 0xf4, 0x1c, 0x03, 0x88, 0x0b, 0xe0, 0xb9, 0x23, 0xb5, 0xe7, 0xd2, 0xa0, 0x60, 0x1b, 0xae, 0xa7, 0x8a, 0x5f, 0x7b, 0xfb, 0x67, 0x3b, 0x6d, 0x9b, 0xa6, 0x9e, 0xb6, 0x03, 0x35, 0x34, 0x90, 0x9b, 0x42, 0x5e, 0x6f, 0x35, 0x85, 0x70, 0x0e, 0xd6, 0xe7, 0xc1, 0xee, 0x51, 0xce, 0x23, 0x54, 0x6d, 0xe7, 0x19, 0x5c, 0x9b, 0x93, 0x66, 0xaf, 0x2b, 0x7b, 0x46, 0x5d, 0x7c, 0xbf, 0xa9, 0xc7, 0x67, 0xc5, 0x1f, 0xad, 0xcf, 0x1a, 0x33, 0x1b, 0x49, 0x3e, 0x79, 0xf2, 0x3a, 0xeb, 0xa4, 0xdf, 0x75, 0xeb, 0x67, 0x88, 0xec, 0x19, 0x73, 0x1a, 0xd5, 0xfb, 0x66, 0x3a, 0x0a, 0xc4, 0xcd, 0x07, 0xab, 0x37, 0xa0, 0xbd, 0xa4, 0x25, 0xf6, 0xe3, 0xb1, 0xab, 0xf4, 0x13, 0x48, 0x73, 0x8b, 0xf6, 0x99, 0xf6, 0x5e, 0xa7, 0xfc, 0x7a, 0x07, 0xe9, 0xf8, 0x10, 0xa5, 0x38, 0x56, 0x09, 0xc9, 0xb8, 0x80, 0xcf, 0x7d, 0xd6, 0xa9, 0xa7, 0xa9, 0xd6, 0xab, 0x63, 0x4e, 0x58, 0x5d, 0x3e, 0x79, 0xbb, 0x13, 0xfe, 0x01, 0xf3, 0x78, 0x19, 0xe5, 0xca, 0xfd, 0x63, 0xa7, 0x8a, 0xb4, 0x22, 0xfb, 0x42, 0x2a, 0x12, 0x85, 0x32, 0x9a, 0xca, 0x53, 0xfb, 0x49, 0x46, 0x9b, 0x7c, 0x6f, 0xe8, 0x44, 0xbd, 0x7d, 0xa4, 0x9e, 0x6b, 0xf0, 0xbb, 0x76, 0x99, 0xd2, 0x46, 0xb6, 0x5e, 0xd6, 0xd8, 0xbf, 0x35, 0xf1, 0x8c, 0x62, 0xa9, 0x9e, 0x03, 0xf5, 0xec, 0x65, 0x9b, 0x68, 0x44, 0x0d, 0xfc, 0x04, 0x86, 0x9f, 0x5b, 0x54, 0x37, 0xaa, 0x3b, 0xee, 0xe0, 0xe5, 0x8d, 0x94, 0x6f, 0xe3, 0x77, 0x05, 0x85, 0x8d, 0x6f, 0x80, 0x6b, 0x32, 0x33, 0x28, 0xf5, 0x50, 0xea, 0x2d, 0xe2, 0x7e, 0xf1, 0xa4, 0xfd, 0xeb, 0xfb, 0xd2, 0x0e, 0x93, 0xf9, 0xfc, 0x5b, 0x3d, 0xa5, 0x68, 0xd6, 0xef, 0x8a, 0x85, 0x9c, 0x9f, 0x91, 0x4f, 0xae, 0x98, 0x62, 0x1f, 0xe0, 0x3e, 0x5a, 0xef, 0xe4, 0xfb, 0xa8, 0xdc, 0x3b, 0xed, 0x4d, 0x34, 0x50, 0xd7, 0x44, 0x0d, 0x4e, 0x27, 0xf8, 0x95, 0xe0, 0x57, 0x82, 0xcf, 0xa0, 0x81, 0xee, 0x55, 0x34, 0x50, 0xbb, 0x8a, 0x1a, 0x70, 0xb6, 0xba, 0xf2, 0xb2, 0xed, 0xfc, 0x1d, 0x81, 0x5d, 0x5a, 0x39, 0xd1, 0x39, 0x8d, 0xea, 0x3d, 0xb3, 0x81, 0xda, 0x26, 0x32, 0x6d, 0x94, 0xcb, 0x99, 0xa7, 0xce, 0x40, 0xfd, 0xaa, 0xc4, 0x1c, 0x96, 0xd7, 0x0e, 0x91, 0xd3, 0x60, 0x9b, 0x3d, 0x91, 0x78, 0xb6, 0x60, 0xde, 0xbb, 0xbb, 0x1a, 0xa7, 0x3e, 0xab, 0x35, 0xe5, 0xe7, 0x36, 0x4e, 0x7f, 0x86, 0x3b, 0xf3, 0x19, 0xff, 0x6c, 0x32, 0xf3, 0x79, 0x63, 0x5f, 0xa3, 0x7a, 0x0f, 0xe7, 0x22, 0xde, 0x53, 0x38, 0xf1, 0x9a, 0x60, 0x03, 0x17, 0x4b, 0x2d, 0xbe, 0xb1, 0x15, 0xd1, 0xe7, 0x89, 0xe3, 0x61, 0x15, 0x97, 0xa5, 0x60, 0x56, 0x32, 0xf5, 0xb7, 0x1a, 0x36, 0x1d, 0x8f, 0x7a, 0xf5, 0x3b, 0x73, 0xfc, 0xdc, 0x9e, 0xdf, 0xf7, 0x70, 0x09, 0x8e, 0x48, 0x72, 0x44, 0xf2, 0x59, 0x3d, 0xa7, 0x16, 0xca, 0x77, 0x43, 0x76, 0x8a, 0xd5, 0xd4, 0x22, 0xd2, 0xb1, 0x23, 0xf8, 0xd6, 0xba, 0x06, 0xf6, 0x12, 0x80, 0xa4, 0x98, 0x76, 0x1a, 0xab, 0x71, 0xa7, 0x9a, 0x83, 0xb8, 0x63, 0xa9, 0xe1, 0x92, 0xa5, 0x84, 0xfe, 0xee, 0x8c, 0xff, 0x2b, 0x25, 0xb3, 0x2d, 0x5f, 0xa3, 0xfa, 0x4e, 0xcf, 0x8c, 0xd2, 0x0d, 0xed, 0x17, 0x5d, 0xe8, 0x8f, 0xd9, 0x42, 0xab, 0x6e, 0xa1, 0x12, 0x2d, 0x44, 0x64, 0x0b, 0xad, 0xb2, 0x85, 0xd6, 0x44, 0x0b, 0xea, 0x19, 0x05, 0xb7, 0xb1, 0x28, 0xd1, 0xc6, 0x42, 0x4a, 0xd5, 0xeb, 0x35, 0xd1, 0xa8, 0xbe, 0x0f, 0x4b, 0xde, 0x14, 0x5c, 0xa5, 0xe6, 0x5a, 0xb9, 0x5c, 0x89, 0x95, 0x82, 0x77, 0x29, 0x95, 0x27, 0xc9, 0x71, 0x1d, 0x2f, 0xbf, 0x30, 0x2d, 0x6e, 0xae, 0x9c, 0x12, 0x37, 0xb3, 0x77, 0x91, 0xb1, 0xa2, 0xfb, 0x76, 0xdc, 0x11, 0x67, 0xf3, 0xd2, 0xa7, 0x3e, 0x48, 0x7a, 0xe9, 0x1c, 0xf2, 0x38, 0x4a, 0xe4, 0x73, 0xff, 0x1e, 0xe9, 0x33, 0xfa, 0xa9, 0xe2, 0xf7, 0x39, 0xda, 0x47, 0xf2, 0x9a, 0x5d, 0xa2, 0xdf, 0xa9, 0x32, 0x7d, 0xe0, 0x4a, 0xac, 0x59, 0xc8, 0x5d, 0x2d, 0xe3, 0x83, 0x1c, 0xc3, 0xe5, 0x6c, 0xb0, 0x39, 0xe1, 0xc3, 0xba, 0xd0, 0x6a, 0x95, 0xec, 0x2d, 0xf7, 0x92, 0x69, 0xc4, 0xfd, 0x0c, 0x3f, 0x7d, 0xb3, 0x79, 0x0c, 0xf5, 0xcd, 0x92, 0x41, 0x39, 0x49, 0xc3, 0xa0, 0x99, 0xcf, 0x38, 0xaf, 0x3a, 0x8d, 0xcd, 0x1d, 0xd6, 0xf2, 0x8b, 0x9d, 0x9f, 0x3a, 0x8d, 0xfc, 0x4e, 0x29, 0xbf, 0xc2, 0x10, 0x34, 0xf7, 0xeb, 0x77, 0xd2, 0x0f, 0x05, 0x7f, 0x77, 0xf5, 0x3d, 0xfe, 0x7c, 0x80, 0x5e, 0x65, 0xfe, 0xeb, 0xcc, 0x1b, 0xef, 0x51, 0xab, 0x71, 0xfd, 0x65, 0xfb, 0xe9, 0x71, 0xd6, 0x13, 0x39, 0xd9, 0x74, 0xb7, 0x81, 0xfe, 0x8c, 0xef, 0x7d, 0xf9, 0x40, 0xf0, 0x36, 0x7a, 0x83, 0x55, 0xf6, 0xf4, 0x05, 0x83, 0xd7, 0x6d, 0xbb, 0x3d, 0xb0, 0x6d, 0xdb, 0x9a, 0x93, 0xbd, 0xfd, 0xf4, 0x49, 0x56, 0xa4, 0x07, 0xed, 0xd0, 0x9a, 0xdb, 0x56, 0x45, 0x9f, 0xb7, 0x23, 0xd9, 0x46, 0x4f, 0x29, 0xf2, 0xb0, 0xd0, 0xe2, 0x4f, 0x72, 0x5c, 0xff, 0x57, 0x3e, 0x88, 0x3e, 0x8d, 0xd8, 0x78, 0xdb, 0xc6, 0xdb, 0xe8, 0x75, 0x43, 0x7d, 0x05, 0x3a, 0xb7, 0xed, 0x40, 0x55, 0x55, 0x55, 0x1b, 0x5d, 0x09, 0xf9, 0xc8, 0xe6, 0xe0, 0x9d, 0x81, 0x35, 0x55, 0xf4, 0x0b, 0x83, 0x0b, 0xbf, 0x8d, 0xcf, 0x93, 0x7b, 0xe8, 0x4f, 0x8a, 0x5c, 0x6e, 0x57, 0xe5, 0x9e, 0xb2, 0xd9, 0x55, 0x31, 0x94, 0xa2, 0xb7, 0x2d, 0x85, 0x4e, 0xd8, 0xb8, 0xd0, 0x0f, 0x75, 0xf6, 0x27, 0x36, 0xd3, 0x41, 0x9e, 0x38, 0xf9, 0x0d, 0x9d, 0x3a, 0xf0, 0x78, 0x9d, 0xe6, 0xd2, 0x2d, 0xe9, 0x4a, 0x61, 0x1b, 0x8a, 0xbf, 0x2c, 0x7b, 0x8d, 0xc6, 0x5f, 0x93, 0xcf, 0x56, 0x36, 0xb4, 0xd1, 0x3d, 0x0e, 0xa8, 0xfc, 0x0e, 0x4d, 0xf5, 0x55, 0xf5, 0xed, 0x39, 0x70, 0x70, 0x53, 0xf0, 0x23, 0x9b, 0xda, 0xe8, 0x3e, 0x16, 0x5e, 0x8b, 0x8f, 0x35, 0x7b, 0xae, 0x3e, 0xb8, 0x67, 0x6f, 0xc9, 0x7a, 0xfa, 0xa5, 0xd4, 0x47, 0x1d, 0xf4, 0x10, 0x8b, 0xaf, 0x6e, 0xa3, 0x17, 0x78, 0xc0, 0xf4, 0x86, 0xfc, 0xfc, 0x76, 0x0a, 0x3e, 0x37, 0x5e, 0xb3, 0x26, 0x50, 0xfd, 0x00, 0xec, 0xf3, 0xd8, 0x1a, 0x3a, 0x92, 0x22, 0xd5, 0x1f, 0xa2, 0xc7, 0x52, 0x0c, 0x49, 0xaf, 0x95, 0x74, 0xcd, 0xc1, 0x0b, 0xaf, 0xa9, 0x7e, 0xd3, 0x38, 0xb0, 0xa2, 0x9b, 0xee, 0x51, 0x1a, 0xf4, 0x88, 0xcc, 0xa0, 0x4f, 0xa7, 0xf2, 0xec, 0xbb, 0x9c, 0x81, 0x83, 0x01, 0xfa, 0x78, 0x3a, 0xf8, 0x35, 0xc2, 0x95, 0x7d, 0xb2, 0x8a, 0x1e, 0x91, 0xf2, 0x7c, 0xe7, 0xd5, 0xf4, 0x65, 0x6e, 0x63, 0xcd, 0x9e, 0x03, 0xd5, 0xb7, 0x55, 0x07, 0xaa, 0xe9, 0x03, 0x96, 0x6f, 0x16, 0x85, 0xae, 0x6a, 0x7a, 0xca, 0xc1, 0xcb, 0xf8, 0x96, 0xad, 0x75, 0xcf, 0x01, 0xfe, 0x33, 0x6e, 0xbe, 0x74, 0x7f, 0xd5, 0xd1, 0xbe, 0xa0, 0xc8, 0x73, 0x6d, 0x16, 0xf9, 0x2e, 0xfa, 0x95, 0x83, 0x2b, 0xce, 0xde, 0x33, 0xb2, 0x66, 0x44, 0xe4, 0x67, 0xff, 0x7b, 0x55, 0x09, 0x3e, 0xd1, 0x50, 0x35, 0xd0, 0xb7, 0xed, 0xc2, 0xbd, 0x23, 0x46, 0x70, 0x2b, 0x3d, 0x97, 0x2a, 0x5b, 0x9c, 0x97, 0x5d, 0x7a, 0x84, 0x3e, 0x99, 0x06, 0xfe, 0xbd, 0x35, 0x1b, 0x37, 0x9d, 0xac, 0xbb, 0xe6, 0xc3, 0x7d, 0x87, 0x6f, 0xbb, 0x74, 0xdb, 0xb6, 0xdb, 0x3e, 0xa0, 0x67, 0x58, 0xba, 0xe6, 0xe8, 0x4b, 0x87, 0x6f, 0x1b, 0xd9, 0x86, 0xda, 0x12, 0xff, 0x9f, 0xdc, 0xf8, 0xd2, 0x81, 0xdb, 0x4f, 0x3e, 0x79, 0xf2, 0xe8, 0xbb, 0x27, 0x7f, 0x19, 0x38, 0x59, 0xdd, 0x1f, 0x08, 0x3e, 0xb8, 0xb9, 0xad, 0x73, 0x07, 0x7d, 0x57, 0xd6, 0xf8, 0xfd, 0xd2, 0x35, 0x0f, 0x56, 0x3f, 0x8c, 0xee, 0x1f, 0x16, 0xae, 0x42, 0xb4, 0xb6, 0xad, 0xe4, 0x7c, 0x63, 0x5b, 0x2b, 0xbd, 0xc5, 0x43, 0xa1, 0xef, 0x3b, 0x68, 0xc5, 0x1e, 0x51, 0x9e, 0xfd, 0x24, 0xaa, 0x39, 0x76, 0x14, 0x1f, 0xf4, 0x25, 0x2e, 0x44, 0x7f, 0xe0, 0xdc, 0x4b, 0xd6, 0x1c, 0x7e, 0xb0, 0x6d, 0xed, 0x85, 0x7b, 0x37, 0xb6, 0xdd, 0x11, 0xac, 0xfa, 0xf0, 0xe6, 0x4b, 0xe8, 0x45, 0x43, 0xda, 0xd7, 0x2b, 0x86, 0x5a, 0xcc, 0x77, 0x68, 0x1c, 0xe9, 0xaf, 0x55, 0x23, 0x70, 0x99, 0x4b, 0x3b, 0x80, 0x7b, 0xd8, 0xde, 0xae, 0x61, 0x73, 0xdd, 0xbb, 0x9d, 0xbe, 0xcc, 0x33, 0x78, 0xed, 0x25, 0x6b, 0x36, 0x1e, 0xc0, 0xd2, 0x3d, 0xc2, 0x89, 0x07, 0x15, 0xff, 0xa4, 0xb4, 0x67, 0xb1, 0xd8, 0x29, 0x10, 0x1f, 0x7f, 0x8a, 0x6d, 0x75, 0xc5, 0xc6, 0x6b, 0x4f, 0xe1, 0x8e, 0x3f, 0x97, 0xb6, 0xab, 0xaf, 0x76, 0xe5, 0x1e, 0xda, 0xa4, 0x69, 0x9e, 0xa6, 0xa6, 0xbc, 0x95, 0xd4, 0x77, 0x3b, 0xad, 0xd4, 0x2a, 0xd3, 0x43, 0x53, 0xf6, 0xab, 0x7a, 0x26, 0xaf, 0x96, 0x75, 0x8b, 0x7c, 0x56, 0xc7, 0xb2, 0x05, 0x94, 0x7c, 0x5f, 0xdb, 0x66, 0xa9, 0xcb, 0xa6, 0xb9, 0x25, 0x89, 0xfc, 0x25, 0x9a, 0x6e, 0xd2, 0x74, 0x35, 0x99, 0xdf, 0xbb, 0x1b, 0x92, 0xaa, 0xf4, 0x3c, 0x9d, 0x9e, 0x97, 0x28, 0x97, 0xa9, 0xeb, 0xda, 0x20, 0xcf, 0x5c, 0xa1, 0xa1, 0xda, 0xef, 0x4f, 0xb4, 0x67, 0xb6, 0xa9, 0x90, 0x27, 0x9f, 0x6a, 0xdb, 0x28, 0xf9, 0x3e, 0xb8, 0xf9, 0x8e, 0x07, 0xd3, 0x6c, 0x5d, 0xb7, 0x39, 0x9e, 0x1c, 0xad, 0x6f, 0xbe, 0xe7, 0x61, 0x48, 0x5a, 0x44, 0xe6, 0x7b, 0x05, 0x4c, 0xcd, 0xf7, 0x3c, 0xdc, 0x9a, 0x9a, 0xe3, 0xaa, 0xd4, 0x75, 0x98, 0xef, 0x29, 0xd8, 0x25, 0x9f, 0x2d, 0xa9, 0xe2, 0xd5, 0xdb, 0xf0, 0x0e, 0xc9, 0xe7, 0x27, 0xfa, 0xa2, 0xca, 0x56, 0xea, 0xb2, 0x95, 0xb2, 0x3e, 0x96, 0x2f, 0xd7, 0x79, 0xe6, 0x7b, 0x11, 0x1d, 0x9a, 0xf6, 0xe8, 0x76, 0x7a, 0x70, 0xb2, 0xa4, 0x69, 0x9d, 0x5e, 0xed, 0x13, 0xb7, 0xe8, 0xb6, 0x2c, 0xb3, 0x51, 0x41, 0x46, 0x45, 0x25, 0x00, 0x95, 0x4a, 0x2a, 0xaf, 0x6d, 0xe8, 0x6c, 0xae, 0xed, 0x69, 0xea, 0xa8, 0xea, 0xe9, 0xee, 0x69, 0xae, 0x6a, 0xec, 0x6c, 0x68, 0xa8, 0xea, 0x58, 0xd5, 0x54, 0x57, 0xb5, 0xb2, 0xab, 0xa7, 0xa1, 0xb1, 0xa7, 0xab, 0xb1, 0x6b, 0x55, 0x6d, 0x2d, 0xa5, 0xb5, 0x8d, 0x06, 0x03, 0xa1, 0x40, 0x7c, 0x2d, 0xa5, 0xb4, 0x29, 0x6a, 0xac, 0x6d, 0x25, 0xdb, 0xda, 0xd6, 0x4a, 0xb2, 0xe3, 0x63, 0x98, 0x3f, 0x21, 0xc8, 0xeb, 0x0c, 0x4e, 0xfa, 0xe3, 0xe1, 0x70, 0x7c, 0x7c, 0x53, 0x60, 0x2c, 0xb0, 0xc9, 0x17, 0xf2, 0xed, 0xf6, 0x47, 0x69, 0xed, 0x6c, 0x52, 0xb7, 0x3f, 0x1a, 0x0d, 0x47, 0x57, 0xbb, 0x47, 0xc3, 0x93, 0xc1, 0x31, 0x77, 0x28, 0x1c, 0x77, 0xef, 0xf6, 0xc7, 0xdd, 0x09, 0x4d, 0x77, 0x5f, 0x8f, 0x3b, 0x36, 0xea, 0x0b, 0x85, 0x50, 0xbe, 0xeb, 0x9f, 0x2f, 0x3f, 0xe6, 0xdf, 0xe5, 0x9b, 0x0c, 0x5a, 0xeb, 0xf1, 0x8d, 0xf9, 0x22, 0x71, 0x54, 0xe2, 0xea, 0x6e, 0x2e, 0x2f, 0x1f, 0x3c, 0x18, 0x8a, 0x8f, 0xfb, 0xe3, 0x81, 0xd1, 0xae, 0xa0, 0x2f, 0x16, 0x83, 0xcc, 0xbf, 0x2f, 0x30, 0xea, 0xef, 0xf2, 0x05, 0x83, 0x3b, 0x7d, 0xa3, 0x7b, 0x7b, 0x27, 0x22, 0x41, 0x72, 0x76, 0x07, 0x62, 0xa3, 0xe1, 0x7d, 0xfe, 0xa8, 0x7f, 0x4c, 0xe5, 0x92, 0xe8, 0x25, 0xa3, 0x17, 0xf3, 0xd5, 0xcb, 0xb4, 0x8f, 0x6c, 0xbd, 0x7d, 0x7d, 0x54, 0xd2, 0xbb, 0xc5, 0xdd, 0x73, 0x60, 0xd4, 0x1f, 0x89, 0x07, 0xc2, 0x21, 0xf7, 0xfe, 0xf1, 0x40, 0xd0, 0xef, 0x1e, 0x0d, 0x86, 0x63, 0x81, 0xd0, 0x6e, 0x77, 0x24, 0x1c, 0x8d, 0xd3, 0xe2, 0xde, 0x2d, 0xa7, 0xcb, 0x9f, 0xc0, 0x20, 0xd0, 0x51, 0x59, 0x77, 0x51, 0x2f, 0x06, 0x18, 0x4f, 0xf4, 0xf6, 0x5c, 0x5f, 0x3c, 0x6e, 0xf6, 0x86, 0x72, 0x7a, 0x43, 0xbb, 0xc2, 0xe7, 0x07, 0xe2, 0xe3, 0xde, 0xa8, 0x2f, 0x14, 0x93, 0xb5, 0x8a, 0x0d, 0x64, 0xdf, 0x30, 0xd4, 0xd5, 0x43, 0xae, 0x0d, 0x93, 0xa3, 0x7e, 0x9e, 0x0d, 0xd5, 0x47, 0xd6, 0xa4, 0x1c, 0x53, 0xd6, 0x1b, 0x8a, 0x4c, 0xc6, 0xfb, 0x59, 0x3f, 0xa1, 0xb6, 0x65, 0x32, 0x6e, 0xca, 0xb2, 0x4c, 0x99, 0x4c, 0xe5, 0x99, 0xa9, 0xc1, 0xc9, 0x08, 0x37, 0x51, 0xbd, 0xc7, 0xb7, 0xcf, 0x47, 0xa2, 0x8f, 0x8c, 0xbe, 0x5e, 0xb2, 0xf5, 0x61, 0xcc, 0x76, 0x7c, 0x6c, 0xe0, 0xcf, 0x0d, 0x7d, 0x2c, 0xe0, 0x1c, 0x66, 0x64, 0x36, 0x38, 0x7b, 0x5f, 0xdf, 0xb6, 0x3e, 0x2a, 0xef, 0xf3, 0x85, 0xc6, 0xa2, 0xe1, 0xc0, 0x58, 0xcd, 0x4e, 0x73, 0x2c, 0x35, 0x89, 0x51, 0x75, 0xa8, 0x25, 0x68, 0xa5, 0xb2, 0x33, 0x69, 0xa9, 0x91, 0xb4, 0x52, 0xe9, 0x99, 0x94, 0x78, 0x82, 0x5a, 0xa9, 0xf2, 0x6c, 0x2a, 0xe6, 0x1c, 0xb6, 0x9e, 0xb9, 0x63, 0xda, 0x94, 0x66, 0xaf, 0x30, 0xe8, 0x4f, 0x2a, 0xf6, 0xf9, 0x07, 0x95, 0x2d, 0xce, 0x3e, 0x06, 0xa8, 0x72, 0x7e, 0xb2, 0xd5, 0xd3, 0xd4, 0xc7, 0x4a, 0xeb, 0x02, 0x41, 0xcc, 0x45, 0x79, 0xe7, 0x64, 0x20, 0x38, 0xc6, 0xf5, 0xcd, 0x36, 0xdc, 0x29, 0xaa, 0x67, 0x54, 0x19, 0xf0, 0xc7, 0x60, 0xee, 0xad, 0xb4, 0xfc, 0xf4, 0x2a, 0x83, 0xfe, 0x78, 0x1c, 0x66, 0x17, 0x4b, 0x36, 0x79, 0x86, 0x21, 0x98, 0xca, 0xad, 0x34, 0x3f, 0xa1, 0x34, 0x1a, 0x0e, 0xc5, 0xfd, 0xa1, 0x78, 0x4d, 0x17, 0xd3, 0x03, 0x68, 0xac, 0x24, 0x91, 0x35, 0xe1, 0x1f, 0x0b, 0xf8, 0x6a, 0xd8, 0xa0, 0x6b, 0x92, 0xc6, 0xd8, 0x4a, 0x2b, 0xce, 0xac, 0xc0, 0xd6, 0x5a, 0xce, 0xd6, 0xc7, 0x8c, 0xb5, 0x3b, 0xa7, 0xd5, 0x6e, 0x25, 0xcf, 0xe9, 0x94, 0x12, 0xc6, 0xde, 0x4a, 0x35, 0xa7, 0xd3, 0xd1, 0xeb, 0x5c, 0x3e, 0x75, 0xbb, 0xb7, 0xd2, 0xaa, 0xb3, 0x15, 0xd8, 0x12, 0x52, 0x45, 0xb6, 0x44, 0xfc, 0x21, 0xff, 0x58, 0x5f, 0x20, 0x86, 0x89, 0xe0, 0x19, 0x74, 0x9f, 0xa5, 0xe0, 0x19, 0x06, 0x95, 0xdc, 0x89, 0xd6, 0x85, 0x9d, 0xa6, 0x34, 0xe0, 0x1f, 0xf5, 0x07, 0xf6, 0x71, 0x3d, 0x45, 0x09, 0x95, 0x70, 0xac, 0x46, 0xae, 0x60, 0xf9, 0x70, 0xcf, 0xc0, 0x60, 0xef, 0x96, 0xcd, 0xad, 0x94, 0x3b, 0x35, 0x2f, 0x34, 0x16, 0xc4, 0xdc, 0xe7, 0x59, 0x85, 0xeb, 0x7d, 0x2c, 0x44, 0x35, 0x05, 0x56, 0x69, 0xbf, 0x2f, 0x3a, 0xea, 0x0f, 0x0e, 0x4d, 0x06, 0xc6, 0x5a, 0xc9, 0x95, 0xc8, 0x98, 0x8c, 0x07, 0x82, 0x35, 0x7d, 0xe1, 0xdd, 0xad, 0xd4, 0xd8, 0x37, 0x1a, 0x9e, 0xa8, 0x89, 0x4e, 0xc4, 0x82, 0x35, 0x7b, 0xe0, 0x1d, 0x6a, 0xa6, 0xb9, 0x88, 0xf2, 0xd9, 0xdc, 0x71, 0x2b, 0xd5, 0x9f, 0xa5, 0xd4, 0x4c, 0x5f, 0xdb, 0x4a, 0x75, 0x67, 0x29, 0x33, 0xc3, 0x03, 0x9e, 0xbd, 0x99, 0x99, 0x8e, 0xf1, 0xec, 0xcd, 0xcc, 0x70, 0x9c, 0xff, 0x7c, 0x33, 0xd6, 0xe5, 0x5c, 0xf1, 0x4f, 0x96, 0x51, 0xda, 0x1b, 0xcf, 0xa2, 0x9d, 0x1c, 0x84, 0x69, 0x8d, 0xe5, 0x3d, 0x07, 0xe0, 0x0f, 0x42, 0xbe, 0x60, 0xe2, 0x18, 0xeb, 0xf3, 0x4d, 0xec, 0x1c, 0xf3, 0xd5, 0xfe, 0xff, 0xac, 0xac, 0xae, 0x95, 0x3a, 0xff, 0xe5, 0xca, 0xa6, 0x9f, 0x99, 0xad, 0xd4, 0xf7, 0x2f, 0xd7, 0x71, 0xfa, 0x93, 0xb0, 0x95, 0xba, 0xff, 0xe5, 0xda, 0x92, 0x12, 0xde, 0xbb, 0x5e, 0x5f, 0x6c, 0xef, 0xd9, 0xcd, 0x60, 0x46, 0x2d, 0x67, 0x37, 0x83, 0xcd, 0xbe, 0x38, 0xf6, 0xe9, 0xd4, 0x2d, 0x5b, 0x71, 0x96, 0x32, 0xbc, 0xfc, 0xfd, 0xbe, 0xf8, 0x38, 0xfb, 0x9f, 0x33, 0x6b, 0x26, 0xcc, 0x7e, 0xba, 0xe3, 0x2a, 0x39, 0x73, 0x41, 0x76, 0x97, 0x63, 0xbe, 0xe0, 0xbe, 0xc0, 0xde, 0x1a, 0x9c, 0x5a, 0xe1, 0xb8, 0x8f, 0xa3, 0x90, 0x9a, 0x9e, 0x90, 0x8e, 0x40, 0x64, 0xf0, 0xd3, 0x4a, 0xc5, 0xb3, 0xe8, 0xf4, 0xf2, 0x21, 0xa7, 0xf3, 0x4b, 0x67, 0xc9, 0xdf, 0xe4, 0x9f, 0xd8, 0xa9, 0x15, 0xfc, 0x31, 0x3e, 0x5d, 0x67, 0x53, 0x89, 0x8f, 0x87, 0xc7, 0xe0, 0x63, 0x7c, 0x13, 0x7e, 0x58, 0x18, 0xb4, 0x16, 0xcd, 0xa2, 0x35, 0x18, 0xd8, 0x1d, 0xf2, 0xc5, 0x27, 0xa3, 0x7e, 0x76, 0x71, 0x33, 0xb3, 0xbd, 0xe3, 0xd1, 0xf0, 0x7e, 0x14, 0x9d, 0xd7, 0xc7, 0xf1, 0x48, 0x4d, 0x20, 0x5c, 0x63, 0x89, 0xa7, 0x5a, 0x29, 0x47, 0x89, 0x83, 0xbe, 0xd0, 0xee, 0x1a, 0xdd, 0xdb, 0x79, 0x16, 0x91, 0x45, 0xb3, 0xdc, 0x22, 0xee, 0x0d, 0x06, 0xfd, 0xbb, 0x7d, 0xc1, 0x8e, 0xe8, 0xee, 0xc9, 0x09, 0x1c, 0x68, 0x16, 0xad, 0x5c, 0xab, 0x16, 0x4e, 0xb9, 0xdd, 0xca, 0xc9, 0x27, 0x85, 0x9b, 0x27, 0x83, 0xc1, 0xfe, 0x70, 0x00, 0x59, 0x51, 0x4b, 0x31, 0x97, 0x45, 0x63, 0xcb, 0xce, 0x3d, 0xfe, 0xd1, 0xf8, 0x54, 0xd9, 0x60, 0x3c, 0x8a, 0xe9, 0xe6, 0xb3, 0x74, 0xba, 0x2c, 0x71, 0x16, 0x5b, 0xbb, 0x2d, 0x07, 0xed, 0xdb, 0xc9, 0x5e, 0x7c, 0x91, 0x45, 0x1c, 0xf5, 0xef, 0xaa, 0x39, 0xdf, 0xef, 0xdb, 0x3b, 0xe0, 0xdf, 0x85, 0x1d, 0x16, 0x1a, 0x3d, 0x5b, 0x76, 0x9b, 0x59, 0xa9, 0xf4, 0xe8, 0x1d, 0xd1, 0xa8, 0xef, 0x20, 0x9f, 0x5c, 0xad, 0xb3, 0x8b, 0xdb, 0xcc, 0x1e, 0x27, 0xc5, 0x98, 0xcd, 0x7c, 0x8b, 0xac, 0x2b, 0x8c, 0x79, 0x1b, 0x9d, 0x32, 0x51, 0x52, 0xbe, 0xde, 0x17, 0x43, 0x04, 0x15, 0x99, 0x55, 0xd8, 0x36, 0x43, 0x88, 0xc8, 0x62, 0xa6, 0x26, 0x84, 0x6d, 0x38, 0xb1, 0x92, 0xc2, 0x5e, 0x4c, 0xb0, 0x2f, 0x1e, 0xc6, 0xc4, 0x38, 0x2d, 0x52, 0xd5, 0xfd, 0xe9, 0x92, 0xa9, 0x6d, 0xa8, 0xf9, 0x47, 0xd7, 0xb3, 0x2d, 0x42, 0xd9, 0xe8, 0x34, 0x41, 0x9b, 0x69, 0x3d, 0x52, 0xe0, 0x0d, 0x4c, 0x58, 0x96, 0x21, 0x29, 0x52, 0x1e, 0xa3, 0xc4, 0x22, 0x46, 0x18, 0x34, 0x3a, 0x19, 0x8d, 0x72, 0x24, 0xd4, 0x73, 0xc0, 0x3f, 0x3a, 0x29, 0x7b, 0xb9, 0xe4, 0xcc, 0x0a, 0x83, 0xfe, 0xa8, 0x72, 0x87, 0xee, 0x33, 0xeb, 0xc5, 0x12, 0x4b, 0x2a, 0x35, 0x76, 0x4d, 0x86, 0xe4, 0x7c, 0xd7, 0xac, 0xd3, 0xcc, 0x59, 0xb2, 0x31, 0xa4, 0x19, 0xbe, 0x8b, 0x3a, 0x66, 0x88, 0x66, 0xbf, 0x32, 0x59, 0xef, 0x54, 0xb1, 0x83, 0x88, 0x70, 0x26, 0xdc, 0x31, 0xd5, 0x6d, 0x6a, 0x9c, 0x59, 0xc5, 0x7e, 0x5f, 0x34, 0x04, 0xfb, 0x5d, 0xed, 0x1e, 0xdc, 0x1b, 0x88, 0x44, 0xf8, 0x3a, 0xb3, 0xa9, 0xb7, 0xbb, 0xd7, 0x3d, 0xb4, 0xa9, 0x5f, 0x5f, 0x69, 0xdc, 0xe4, 0x9a, 0xe9, 0x7b, 0xc9, 0x35, 0xd3, 0x51, 0x52, 0x9a, 0xe9, 0x08, 0x29, 0x75, 0xb0, 0x7b, 0xe3, 0x8e, 0xde, 0xcd, 0x5e, 0x5a, 0x6c, 0x8d, 0x41, 0xab, 0xbb, 0x3a, 0xfa, 0xfa, 0x3a, 0x3b, 0xba, 0x36, 0xee, 0xf0, 0x6e, 0xed, 0xef, 0xd9, 0xb1, 0xa9, 0xc3, 0xdb, 0xb5, 0x7e, 0x47, 0xdf, 0x96, 0x41, 0x2f, 0xe5, 0x79, 0x07, 0x3a, 0x36, 0x0f, 0xf6, 0x6f, 0x19, 0xf0, 0xee, 0xe8, 0xdc, 0xea, 0xed, 0x19, 0xf4, 0x0e, 0xf4, 0x74, 0x6c, 0xa2, 0x39, 0x49, 0x29, 0xba, 0x43, 0x05, 0xa7, 0xf1, 0x9e, 0x24, 0x86, 0xc9, 0x18, 0xc6, 0xed, 0x65, 0x18, 0x77, 0x19, 0xfb, 0x30, 0xdf, 0xe6, 0x8c, 0xe1, 0x0d, 0x94, 0x32, 0xbc, 0x41, 0x5e, 0x74, 0x8c, 0x61, 0xdc, 0x6e, 0x86, 0x71, 0xbb, 0xb1, 0x0f, 0xf3, 0xed, 0xc7, 0x31, 0xac, 0xae, 0x3f, 0x20, 0x1b, 0x58, 0x2e, 0x3f, 0xfa, 0x58, 0xcc, 0xb9, 0x29, 0x92, 0xc8, 0x6c, 0x2d, 0x44, 0x41, 0x63, 0x78, 0x1b, 0x89, 0x6d, 0x64, 0x6c, 0x03, 0x8b, 0xab, 0x91, 0x31, 0xd2, 0x49, 0x9e, 0x91, 0xb3, 0x47, 0xf9, 0x55, 0x23, 0xff, 0x52, 0xd4, 0x5c, 0xfe, 0x4f, 0xa8, 0x23, 0x1a, 0x1c, 0x99, 0xcd, 0xb3, 0xe5, 0x8e, 0xcc, 0xe2, 0xb8, 0x32, 0x7c, 0xa3, 0xa3, 0xfe, 0x58, 0xac, 0xbc, 0x16, 0xd7, 0x7d, 0x93, 0xaf, 0xb3, 0xf0, 0xf5, 0xe0, 0x33, 0x15, 0xbf, 0x2e, 0xe8, 0xdb, 0x1d, 0x23, 0x9b, 0x6f, 0x6c, 0x0c, 0x12, 0xd5, 0x09, 0x79, 0x99, 0xcc, 0xf0, 0x45, 0x22, 0xfa, 0x96, 0x40, 0x29, 0xe0, 0xfd, 0xa1, 0x31, 0x72, 0x80, 0x06, 0x0f, 0x22, 0x19, 0xe3, 0x3d, 0x4b, 0xe9, 0x89, 0xf1, 0x53, 0x51, 0x82, 0xed, 0xeb, 0x91, 0x87, 0x97, 0xb2, 0xba, 0xa1, 0xa1, 0xde, 0x6e, 0x72, 0xee, 0x9c, 0x76, 0x47, 0xa3, 0xbc, 0x9d, 0xd6, 0xa8, 0x53, 0x8d, 0x31, 0x66, 0xd1, 0xdb, 0xa1, 0x6f, 0xd4, 0xce, 0x9d, 0x7a, 0xbd, 0x63, 0xfd, 0xbe, 0x00, 0xbb, 0x5a, 0x72, 0xec, 0x64, 0x67, 0x4b, 0x69, 0xa3, 0xa6, 0x01, 0xa4, 0x60, 0xd6, 0x11, 0x00, 0x93, 0x63, 0x34, 0xe8, 0xf7, 0x45, 0x99, 0x84, 0x63, 0x7e, 0x4a, 0xc5, 0xbe, 0x0c, 0x61, 0x26, 0x28, 0x53, 0x33, 0x1c, 0x82, 0xa0, 0x14, 0xc6, 0xe3, 0x0b, 0x84, 0x62, 0x52, 0x2c, 0xb9, 0x8d, 0xfe, 0x83, 0x24, 0xc6, 0x28, 0x5b, 0x3f, 0x6b, 0xe8, 0x8f, 0x86, 0xe3, 0xe1, 0xd1, 0x70, 0x90, 0x52, 0x74, 0x0f, 0xb2, 0xc7, 0xa6, 0xd8, 0x5b, 0x8c, 0xd2, 0x94, 0xa0, 0x77, 0x8c, 0x32, 0xc6, 0x38, 0x50, 0x52, 0xcd, 0xd8, 0xc6, 0x30, 0x1d, 0x29, 0xfe, 0x8b, 0x27, 0x7d, 0x41, 0xa8, 0xf8, 0xb5, 0x27, 0x20, 0xdb, 0xae, 0xf2, 0x5a, 0xfe, 0xa8, 0xe3, 0x8f, 0x7a, 0xaa, 0xde, 0xe5, 0x0b, 0x04, 0xfd, 0x63, 0xee, 0x78, 0xd8, 0x3d, 0x1a, 0xf5, 0xfb, 0xe2, 0x7e, 0x77, 0x62, 0xc4, 0xe6, 0x86, 0xdb, 0x15, 0x0d, 0x4f, 0xb8, 0xb1, 0x16, 0x51, 0xac, 0x0c, 0xa5, 0xed, 0x0a, 0x20, 0xa6, 0x0b, 0x5c, 0xe2, 0xa7, 0x52, 0x70, 0x63, 0xc9, 0xb9, 0x5a, 0x17, 0x8e, 0x5a, 0x2e, 0xe4, 0x4a, 0xb9, 0x84, 0x55, 0xcc, 0x3d, 0x3a, 0x9b, 0x42, 0x06, 0xd7, 0xad, 0x4e, 0x2c, 0xb2, 0xef, 0xe6, 0xf9, 0xb0, 0xc1, 0x65, 0x50, 0x06, 0x3e, 0xf4, 0xa5, 0x5e, 0xf3, 0x4a, 0xbd, 0x88, 0x79, 0x65, 0x0f, 0xd3, 0xef, 0xd8, 0xb4, 0x20, 0x99, 0x37, 0xd3, 0x57, 0xcd, 0xe3, 0x4c, 0xd8, 0x49, 0x60, 0x54, 0x1e, 0xff, 0xa6, 0x0d, 0xe5, 0x42, 0x3c, 0xa3, 0x4f, 0x2e, 0xab, 0x50, 0x3f, 0xa2, 0x29, 0x9c, 0x29, 0x1b, 0x44, 0x24, 0x31, 0x19, 0x93, 0x35, 0xcf, 0xbc, 0xc6, 0x53, 0x1a, 0xc4, 0xea, 0x19, 0x50, 0x0e, 0xb8, 0x6e, 0xb5, 0x92, 0xe6, 0x90, 0x8a, 0x92, 0xa2, 0xe9, 0xcf, 0x30, 0x64, 0xeb, 0xdd, 0xd3, 0x16, 0x3e, 0x5d, 0xca, 0x64, 0x47, 0x32, 0x12, 0x6c, 0x8c, 0xf2, 0x93, 0x3c, 0xe6, 0x36, 0xf9, 0x10, 0xa7, 0x20, 0x29, 0xef, 0x8f, 0xf2, 0xa1, 0xcd, 0xd3, 0x3b, 0x34, 0x11, 0x91, 0xa3, 0x98, 0x1e, 0x49, 0xa3, 0x24, 0xec, 0x26, 0x15, 0x39, 0xd2, 0x1c, 0x2b, 0xc1, 0xac, 0x9f, 0x9c, 0xe0, 0x8b, 0xbf, 0x6f, 0x8c, 0x43, 0x05, 0xb5, 0x38, 0xb3, 0xad, 0x9d, 0x03, 0xaa, 0xd8, 0x49, 0x92, 0xa8, 0x1a, 0xd4, 0x53, 0x22, 0xc9, 0xe8, 0x3b, 0x4b, 0x57, 0x78, 0x32, 0x14, 0xa7, 0xf9, 0x10, 0xf1, 0x9a, 0x4c, 0xaf, 0x02, 0x63, 0xe0, 0x62, 0x9b, 0x11, 0xc8, 0x51, 0x16, 0x98, 0x2d, 0x51, 0x3d, 0x72, 0x39, 0x0b, 0xc9, 0x9b, 0x8f, 0xaa, 0x65, 0x0e, 0x64, 0x9c, 0xda, 0x3c, 0xc9, 0x21, 0x22, 0x65, 0xea, 0xa4, 0x74, 0xf0, 0x69, 0x3a, 0x11, 0x53, 0x5a, 0xd1, 0x70, 0x04, 0xb1, 0x7e, 0x00, 0xf5, 0x67, 0x23, 0x39, 0xe0, 0x9f, 0x08, 0xc7, 0xfd, 0x7a, 0x02, 0x79, 0x2e, 0xb5, 0xc1, 0x39, 0x99, 0x95, 0x07, 0x92, 0xf6, 0x0c, 0x72, 0x31, 0x13, 0xf3, 0x88, 0x31, 0xeb, 0x32, 0xdc, 0x49, 0xef, 0xc1, 0x88, 0x9f, 0x72, 0xc6, 0xe5, 0x7d, 0x57, 0x89, 0x31, 0x08, 0xff, 0x18, 0xe5, 0x5a, 0x45, 0xdc, 0xd2, 0x3e, 0x08, 0xe7, 0x28, 0xa1, 0x3e, 0x89, 0x28, 0x75, 0xdc, 0x17, 0xdb, 0xcc, 0xf6, 0x66, 0x07, 0x83, 0xce, 0xf2, 0x67, 0x57, 0x78, 0xcc, 0x8f, 0x74, 0x18, 0x7b, 0xd4, 0x08, 0x8c, 0x91, 0x3d, 0xc0, 0x73, 0x97, 0x86, 0x00, 0x70, 0xd8, 0x87, 0x59, 0x02, 0x17, 0xeb, 0x18, 0xe5, 0x13, 0x8d, 0x32, 0x03, 0xb1, 0x2e, 0xb5, 0xa3, 0x51, 0x71, 0x6a, 0x20, 0xd6, 0x33, 0x11, 0x89, 0x1f, 0x44, 0xbe, 0x0e, 0x64, 0x28, 0x65, 0xaf, 0xff, 0x20, 0x4e, 0x34, 0xea, 0x0f, 0xca, 0xeb, 0x55, 0x39, 0x86, 0x1e, 0x9a, 0x72, 0xd7, 0xe4, 0xfb, 0x6d, 0x6f, 0x77, 0x79, 0x6d, 0x39, 0x42, 0xfa, 0x2a, 0x0e, 0xe9, 0xab, 0x38, 0xa4, 0xaf, 0x3a, 0xeb, 0xe5, 0x84, 0xce, 0x9b, 0x56, 0x63, 0x72, 0x41, 0x74, 0x95, 0x75, 0xff, 0x72, 0x95, 0x59, 0x13, 0xbe, 0xbd, 0xc9, 0x67, 0x83, 0xa9, 0x13, 0x49, 0x71, 0x68, 0x72, 0x97, 0x6f, 0x94, 0xe3, 0xf5, 0x28, 0xd9, 0xf9, 0xa4, 0xa1, 0x9c, 0x89, 0x19, 0xa5, 0x33, 0x27, 0x2c, 0x9e, 0xd9, 0x1e, 0x62, 0xb3, 0x71, 0xf0, 0x67, 0x8c, 0x0a, 0x42, 0xfe, 0xfd, 0x83, 0x58, 0xd3, 0xa0, 0x1f, 0x31, 0x2e, 0x2c, 0xd7, 0x8c, 0x7b, 0xa0, 0xc5, 0xf3, 0xee, 0x0a, 0x87, 0x3a, 0x7d, 0xf1, 0xd1, 0xf1, 0xe4, 0x13, 0x2d, 0x14, 0x91, 0x6e, 0x20, 0xa4, 0x02, 0x4f, 0xde, 0xcc, 0xfe, 0x2e, 0xac, 0xda, 0x6e, 0x3f, 0xcd, 0x09, 0x87, 0xac, 0x0b, 0x3c, 0x37, 0x3c, 0xe5, 0x19, 0x0d, 0xcd, 0x9b, 0x9a, 0xee, 0xf6, 0x07, 0x7d, 0x07, 0x21, 0xce, 0x36, 0xc5, 0xa6, 0x0d, 0xa4, 0x86, 0x43, 0xeb, 0x82, 0x93, 0x58, 0xee, 0x2c, 0x54, 0xcf, 0xc7, 0xb0, 0xf4, 0xb3, 0x66, 0x4a, 0xf5, 0x82, 0x52, 0x90, 0xe2, 0x43, 0x2c, 0x97, 0xe7, 0x78, 0xba, 0xd3, 0xc9, 0x60, 0xa1, 0xe6, 0xe7, 0x30, 0x9f, 0x7c, 0xee, 0x5a, 0x70, 0x9a, 0x55, 0xa6, 0xc2, 0xd3, 0x2d, 0x16, 0xc6, 0x81, 0x1c, 0xcb, 0x63, 0xda, 0x74, 0x4e, 0xab, 0x3d, 0x23, 0x59, 0x76, 0xd1, 0xd8, 0xd4, 0xe1, 0xfd, 0xec, 0xbe, 0x72, 0x23, 0x38, 0xdb, 0xa6, 0xf7, 0xa7, 0x68, 0x16, 0xe1, 0x60, 0xdc, 0x1f, 0xf1, 0xee, 0x0f, 0x53, 0xc1, 0x94, 0xbc, 0xe4, 0xc2, 0x53, 0x7a, 0x44, 0x46, 0x14, 0x63, 0xfe, 0x03, 0x94, 0x1a, 0x89, 0x86, 0xc7, 0x26, 0xf9, 0x64, 0x42, 0x1f, 0x28, 0x2d, 0x6a, 0xc6, 0x69, 0xf9, 0x51, 0xff, 0x6e, 0x7e, 0xf2, 0x15, 0x9d, 0x16, 0x47, 0xa5, 0x44, 0xe5, 0x54, 0x52, 0x86, 0xa2, 0xb2, 0xd7, 0xf3, 0xa3, 0x38, 0xd0, 0xfc, 0xb1, 0x78, 0x72, 0xe9, 0xfa, 0xa3, 0x81, 0x70, 0x34, 0x80, 0x2d, 0x61, 0x8b, 0x4e, 0x86, 0x28, 0xd5, 0x7c, 0xa6, 0x9f, 0x16, 0x1b, 0x1d, 0xf7, 0x8f, 0x4d, 0x06, 0xb1, 0xc9, 0x62, 0x3c, 0xc3, 0x45, 0xfc, 0xa9, 0x1e, 0x8d, 0x8f, 0xfb, 0xc6, 0xdc, 0xbd, 0x5b, 0xdc, 0x7e, 0xf3, 0x9e, 0x45, 0x59, 0x88, 0x47, 0x03, 0xbe, 0xa0, 0x76, 0x2c, 0x73, 0x54, 0x6a, 0x47, 0x48, 0x25, 0x6d, 0x31, 0x6c, 0xae, 0x6c, 0x7c, 0x98, 0xfd, 0x92, 0x9e, 0x20, 0x13, 0x02, 0x5e, 0xc7, 0x4d, 0xbc, 0x8d, 0xe7, 0x72, 0x42, 0x07, 0x17, 0x93, 0xd8, 0xcc, 0xb6, 0x58, 0x74, 0x94, 0x1c, 0xb1, 0xb8, 0x8f, 0xa7, 0x59, 0x12, 0x56, 0x45, 0xc5, 0x92, 0x8d, 0x87, 0x23, 0x32, 0x69, 0x8f, 0x81, 0x43, 0x3f, 0xcd, 0x74, 0x4a, 0x7c, 0x3c, 0x80, 0xe8, 0x88, 0xd2, 0xe2, 0x61, 0xed, 0xa0, 0xd2, 0xe3, 0x09, 0x9f, 0xee, 0x8a, 0xcf, 0x74, 0x4b, 0xf6, 0x38, 0xf7, 0x64, 0xde, 0x64, 0x68, 0xb6, 0xc5, 0x9a, 0x3f, 0x4d, 0x6c, 0x59, 0x92, 0xc2, 0xc9, 0xd0, 0x69, 0x66, 0xdc, 0xb1, 0x4f, 0x3a, 0x9f, 0x54, 0x49, 0xb6, 0xec, 0xa2, 0xcf, 0x88, 0x8f, 0x7e, 0xb4, 0xbb, 0xf9, 0xc3, 0x1e, 0xce, 0xc4, 0xf4, 0x79, 0x56, 0x7b, 0xb0, 0x8e, 0x9e, 0x15, 0x1e, 0x6c, 0xfb, 0x48, 0x20, 0x28, 0x8f, 0xd2, 0xaa, 0x09, 0xcc, 0x00, 0x32, 0xa2, 0x7e, 0xc4, 0x3a, 0x31, 0x3f, 0x32, 0xe1, 0xdd, 0xaa, 0x30, 0xf7, 0x08, 0x4c, 0x26, 0x27, 0x62, 0x9e, 0xd5, 0xbb, 0x10, 0x7e, 0xf8, 0x57, 0x78, 0x26, 0x02, 0xa1, 0x2a, 0x5f, 0x24, 0xe0, 0x59, 0x5d, 0xdf, 0xb8, 0xc2, 0x13, 0x1b, 0xf7, 0x55, 0xd5, 0xa1, 0x90, 0x6f, 0x95, 0x6f, 0xac, 0xae, 0xd9, 0xb7, 0xaa, 0xb6, 0x71, 0x65, 0xed, 0xce, 0x55, 0x2d, 0x2d, 0x63, 0xb5, 0x8d, 0xcd, 0xf5, 0xfe, 0xc6, 0x96, 0x55, 0xa3, 0x75, 0xb5, 0x2d, 0xbe, 0x55, 0x4d, 0x3b, 0x77, 0xad, 0xda, 0xd5, 0x52, 0x37, 0xc6, 0xb5, 0xc2, 0x50, 0x62, 0x68, 0x0e, 0x85, 0x9a, 0xab, 0xeb, 0x6a, 0xab, 0x5b, 0xaa, 0x10, 0x9c, 0x78, 0x3e, 0x42, 0x46, 0xbd, 0x78, 0xc5, 0x58, 0x68, 0x2f, 0x30, 0x0a, 0xe2, 0x05, 0xfe, 0x82, 0x34, 0x33, 0x29, 0x90, 0x6c, 0x2a, 0xc8, 0x50, 0x49, 0x5b, 0xc1, 0xc5, 0x32, 0xaf, 0x1a, 0x89, 0xc2, 0x2a, 0xa3, 0xc6, 0x38, 0x26, 0xec, 0xe9, 0xd7, 0x1b, 0x05, 0x59, 0x49, 0xd6, 0x99, 0x64, 0x8b, 0xcc, 0x42, 0x7b, 0x65, 0xa1, 0x84, 0x7c, 0x91, 0x64, 0xcb, 0xd3, 0xed, 0xe0, 0xdd, 0x46, 0x03, 0xeb, 0x88, 0xc2, 0x35, 0x26, 0xd3, 0x98, 0x54, 0x2c, 0x4e, 0xb2, 0x25, 0x46, 0x1d, 0xd8, 0x85, 0xf6, 0x6e, 0xe7, 0x21, 0xf9, 0xef, 0x58, 0xec, 0x74, 0x03, 0x7a, 0x57, 0x8c, 0x3f, 0xd5, 0x99, 0x4a, 0xa5, 0x4a, 0x50, 0xcd, 0x51, 0xaa, 0x46, 0xb9, 0xd3, 0x29, 0xb5, 0x8c, 0x62, 0x5d, 0x8f, 0x81, 0xcc, 0xf9, 0x2a, 0xd3, 0x26, 0x33, 0x65, 0xb6, 0xad, 0xd8, 0x9a, 0x7f, 0x45, 0xb2, 0xc9, 0x6b, 0x54, 0xf7, 0x53, 0x0a, 0x6c, 0x05, 0x1f, 0x2d, 0xe8, 0x2f, 0xd8, 0x54, 0x90, 0x5e, 0xb0, 0xc9, 0x9c, 0x95, 0x30, 0x46, 0x54, 0x9d, 0x9c, 0xa4, 0x70, 0xc1, 0xce, 0x82, 0x81, 0xa9, 0xc9, 0x9d, 0x66, 0x92, 0x87, 0xdf, 0x6f, 0x4d, 0xac, 0x37, 0x13, 0x0e, 0x28, 0x0e, 0x15, 0x04, 0x50, 0x73, 0x66, 0xb2, 0xd5, 0xcb, 0x8c, 0x5a, 0xa9, 0x58, 0x58, 0x51, 0xb8, 0xa4, 0x70, 0x69, 0x52, 0x7e, 0x65, 0x92, 0xbd, 0xca, 0xec, 0x98, 0x28, 0x38, 0xa7, 0x20, 0xb7, 0x60, 0x7f, 0x41, 0xab, 0x59, 0xa3, 0x12, 0xd9, 0xb1, 0x64, 0xbd, 0x10, 0xc9, 0x8a, 0x32, 0x0b, 0xdd, 0x85, 0x25, 0x85, 0x95, 0x85, 0x8b, 0x0a, 0x8b, 0x0b, 0x57, 0x14, 0x7a, 0x0a, 0x4b, 0x0b, 0x97, 0x15, 0x96, 0x15, 0x2e, 0x17, 0x36, 0x91, 0x6e, 0x14, 0x09, 0xc3, 0x30, 0x84, 0xb1, 0xe6, 0xd0, 0x21, 0xfb, 0xe1, 0xaa, 0xb5, 0xe2, 0xc6, 0x15, 0x42, 0x3c, 0x0d, 0xbc, 0x05, 0x3c, 0x51, 0x25, 0xc4, 0x3b, 0xc0, 0x55, 0xd5, 0x42, 0x9c, 0x58, 0x2e, 0xc4, 0xa1, 0x1a, 0xd0, 0x5a, 0x82, 0xb6, 0x23, 0x47, 0xe4, 0x9c, 0x7b, 0xf9, 0x21, 0xfb, 0xa9, 0xda, 0xf5, 0xe2, 0xaa, 0x3a, 0x68, 0xd6, 0xf1, 0x0f, 0x15, 0xa4, 0x42, 0xba, 0x01, 0xf5, 0xfc, 0xa8, 0x8e, 0x32, 0x04, 0xa5, 0x8b, 0x99, 0x7f, 0x1b, 0x51, 0xe6, 0xfd, 0x3a, 0x22, 0xe1, 0xd8, 0x0e, 0xee, 0x89, 0xe6, 0x0f, 0x1d, 0x4a, 0x23, 0x31, 0x0d, 0x94, 0x22, 0xd2, 0x72, 0xf5, 0x3b, 0x22, 0xfc, 0x5f, 0x1f, 0xb7, 0x53, 0xbf, 0x49, 0x1c, 0x69, 0x10, 0xf6, 0xc3, 0x8d, 0x42, 0x1c, 0xab, 0x17, 0xe2, 0x46, 0xe0, 0x18, 0xf8, 0x67, 0x81, 0xd7, 0x80, 0xeb, 0x9a, 0xc8, 0x2e, 0x52, 0xe7, 0xeb, 0x12, 0x43, 0x28, 0x71, 0xc7, 0xca, 0x61, 0x71, 0x62, 0xa5, 0xb0, 0x1f, 0x5b, 0x25, 0xc4, 0x1b, 0x4d, 0x42, 0xbc, 0xc2, 0x00, 0x7f, 0x5d, 0xb3, 0x10, 0x47, 0x9a, 0xc9, 0x26, 0xc4, 0xa2, 0x9b, 0xdc, 0x42, 0xfe, 0x3f, 0x7a, 0xf9, 0x11, 0xfb, 0xfb, 0xcd, 0x63, 0x39, 0xaf, 0x36, 0x63, 0x10, 0xc2, 0x2d, 0x65, 0x7e, 0xc8, 0x5e, 0x6a, 0xd9, 0x95, 0x73, 0x57, 0x0b, 0xff, 0xc2, 0x01, 0xff, 0x23, 0x2a, 0x71, 0xa3, 0x5b, 0xd0, 0x6e, 0x8c, 0xee, 0x54, 0x0b, 0xab, 0x2d, 0x31, 0x20, 0x18, 0x47, 0x43, 0xd7, 0xad, 0x0e, 0x88, 0x47, 0x57, 0xa3, 0x42, 0x47, 0x05, 0x26, 0x06, 0xc2, 0x3d, 0x10, 0xbe, 0xb4, 0x66, 0xaf, 0xf8, 0xdb, 0x6a, 0xb4, 0xd8, 0x26, 0xc4, 0xcf, 0x41, 0xef, 0x02, 0x7d, 0x6b, 0x0d, 0xc6, 0x6d, 0x5c, 0x2f, 0x90, 0xfd, 0xec, 0xf6, 0x1b, 0x84, 0xf8, 0xc1, 0x76, 0xc8, 0xb6, 0x53, 0x7a, 0xfa, 0xdc, 0xe5, 0x86, 0x70, 0x25, 0xfe, 0xe3, 0x4a, 0x5c, 0x11, 0x1e, 0xc1, 0xb9, 0xe2, 0xb0, 0xfb, 0xd1, 0xb5, 0x86, 0xf1, 0x4e, 0xa7, 0x30, 0x7e, 0xd0, 0x2d, 0x32, 0x5e, 0x01, 0x7f, 0xaa, 0x4b, 0x18, 0xc7, 0xd7, 0xd9, 0x8c, 0xe3, 0x1d, 0xe9, 0xc6, 0xa3, 0xe7, 0x47, 0xed, 0x8f, 0xf6, 0xda, 0xc5, 0xb3, 0x3d, 0xb6, 0x43, 0xee, 0xbb, 0xda, 0xc5, 0x21, 0xf7, 0x5b, 0xed, 0x86, 0x38, 0xd2, 0x87, 0xe6, 0x36, 0x09, 0x71, 0x7c, 0x8b, 0x10, 0xa7, 0xbc, 0x98, 0xa6, 0x4e, 0x21, 0x5e, 0x05, 0xde, 0x5b, 0x8b, 0xc1, 0x9f, 0x6f, 0x88, 0x47, 0xb7, 0xa2, 0x5b, 0x5b, 0xd1, 0x13, 0xd1, 0x28, 0xae, 0x16, 0x18, 0xcf, 0xf1, 0x11, 0x71, 0x85, 0xc1, 0xef, 0x23, 0xbc, 0x3a, 0x82, 0x51, 0x18, 0xea, 0x1f, 0x34, 0x7d, 0x82, 0xbb, 0x79, 0xff, 0xf6, 0x6b, 0xb1, 0xf8, 0x17, 0x62, 0x02, 0x2f, 0xb4, 0x53, 0x26, 0x35, 0x17, 0x89, 0x22, 0x91, 0x21, 0x32, 0x6e, 0x14, 0x87, 0x0f, 0xd9, 0xaf, 0xda, 0xc9, 0x1a, 0x77, 0xec, 0x44, 0x1f, 0x8f, 0xf3, 0xc7, 0x2b, 0x3b, 0x45, 0xfa, 0xb3, 0x1f, 0x12, 0xe9, 0xc7, 0x76, 0x88, 0xb4, 0xbf, 0x6d, 0x17, 0x69, 0xc7, 0x7d, 0x22, 0xed, 0xba, 0x8b, 0xc4, 0x27, 0x0c, 0x92, 0x28, 0x70, 0x18, 0x76, 0xc1, 0x6f, 0xb4, 0xf0, 0x37, 0xda, 0xbf, 0xe8, 0x57, 0xf4, 0x5d, 0x4d, 0xf9, 0x07, 0xf2, 0xf8, 0x3b, 0xf0, 0x39, 0xa0, 0x79, 0x16, 0xbe, 0xc4, 0xc2, 0x2f, 0x3b, 0x4f, 0xe9, 0x36, 0x59, 0x64, 0x9d, 0x16, 0x7e, 0x93, 0xce, 0xdf, 0xaa, 0xe9, 0x4e, 0x4b, 0xde, 0x5e, 0x0b, 0xbf, 0x4f, 0xe7, 0x5f, 0xae, 0x65, 0x9f, 0x02, 0xbd, 0x57, 0xcb, 0x1e, 0xb5, 0xc8, 0x9e, 0xd7, 0xb2, 0x6f, 0x58, 0x64, 0x3f, 0xb3, 0xd4, 0xf3, 0xb6, 0xce, 0x7f, 0x4f, 0xd3, 0xd4, 0x01, 0x45, 0xf3, 0x35, 0x5d, 0xac, 0x69, 0xa3, 0xa6, 0x1d, 0x9a, 0x6e, 0x1e, 0x50, 0xdf, 0xfd, 0x73, 0x1d, 0x3b, 0xc0, 0xef, 0x19, 0x48, 0xd6, 0xb9, 0xdf, 0xc2, 0x5f, 0xa1, 0xf5, 0xaf, 0xd7, 0xf4, 0x88, 0xa6, 0x8f, 0x83, 0x5e, 0x38, 0x94, 0x7c, 0xef, 0x80, 0xf4, 0xbb, 0x04, 0x5e, 0xc8, 0xda, 0x41, 0xbb, 0x87, 0xd4, 0x7b, 0x06, 0x7d, 0xa0, 0xe3, 0x43, 0x34, 0xe5, 0x2f, 0x3e, 0x2d, 0x7d, 0x78, 0x28, 0x59, 0x07, 0xff, 0xf1, 0xfb, 0x94, 0x87, 0x86, 0xd4, 0xbf, 0x2b, 0x67, 0x7a, 0xeb, 0x34, 0xfd, 0xe3, 0x3a, 0x6d, 0x96, 0x91, 0xbf, 0x7f, 0x36, 0xa4, 0x7e, 0xab, 0xeb, 0x18, 0xe8, 0xb3, 0x3a, 0xdf, 0x7c, 0x0f, 0xe5, 0x7c, 0x9d, 0xff, 0xda, 0x34, 0x39, 0xff, 0x6e, 0xe1, 0x2b, 0x90, 0x9d, 0x9a, 0x26, 0xe7, 0xdf, 0x32, 0x7c, 0x03, 0xb2, 0xf7, 0xa7, 0xc9, 0xf9, 0xf7, 0x0d, 0xdf, 0x81, 0xcc, 0x3e, 0x3c, 0xb5, 0x3f, 0xeb, 0x87, 0xd5, 0x7b, 0x9d, 0x66, 0x7f, 0xf8, 0x3d, 0x8b, 0xc2, 0x61, 0xf5, 0x3b, 0x10, 0xcd, 0xc3, 0xea, 0xb7, 0x1f, 0x6b, 0x87, 0xd5, 0xef, 0x3f, 0xb6, 0x0f, 0xab, 0x7f, 0x43, 0xe6, 0x1e, 0x56, 0xbf, 0x03, 0x59, 0x31, 0xac, 0x7e, 0x23, 0x91, 0xfb, 0xc7, 0xff, 0x5e, 0x82, 0xff, 0x89, 0xf0, 0x05, 0xc3, 0x53, 0xdb, 0xbd, 0x46, 0x8f, 0x6b, 0x6c, 0x5a, 0xbb, 0x87, 0x86, 0xa7, 0xce, 0x03, 0xff, 0x26, 0x5b, 0x64, 0x58, 0xfd, 0x1e, 0xdb, 0x81, 0xe1, 0xe4, 0xef, 0x0a, 0x5a, 0xdf, 0xc7, 0x31, 0x7f, 0x03, 0x98, 0xcb, 0x98, 0xbf, 0x03, 0xcc, 0xeb, 0xe6, 0x23, 0xf5, 0x5b, 0xc0, 0xfc, 0xce, 0x87, 0xf9, 0x7b, 0xc0, 0xfc, 0x7e, 0x89, 0xf9, 0x9b, 0xc0, 0x3c, 0x3e, 0xf3, 0x77, 0x81, 0x85, 0x5b, 0x95, 0xe5, 0xdf, 0x06, 0xb6, 0xb9, 0xd5, 0x6f, 0xe6, 0xf1, 0x6f, 0x59, 0x08, 0xa7, 0xfa, 0x5d, 0x4b, 0xfe, 0xcd, 0x0f, 0xc3, 0xad, 0xda, 0xe2, 0xdf, 0x0e, 0xb6, 0xbb, 0xd5, 0xbb, 0x96, 0xbc, 0xdf, 0xf8, 0x65, 0x17, 0xae, 0x87, 0x7f, 0x0b, 0xc5, 0xe1, 0xd6, 0xef, 0xe9, 0xf0, 0x6f, 0x62, 0x38, 0xd5, 0xef, 0x64, 0xb0, 0xdd, 0xa4, 0xb8, 0xd5, 0xfb, 0x34, 0xfc, 0x9b, 0x29, 0x6c, 0x04, 0xdc, 0x6f, 0xfe, 0xfd, 0xe2, 0xff, 0x07, 0xf7, 0x0e, 0xe8, 0x14, 0xf8, 0x58, 0x00, 0x00 }; #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (nanoTime, "nanoTime", "()J") DECLARE_JNI_CLASS (JavaLangSystem, "java/lang/System") #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (openMidiInputPortWithID, "openMidiInputPortWithID", "(IIJ)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") \ METHOD (openMidiOutputPortWithID, "openMidiOutputPortWithID", "(II)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") DECLARE_JNI_CLASS_WITH_BYTECODE (MidiDeviceManager, "com/rmsl/juce/JuceMidiSupport$MidiDeviceManager", 24, javaMidiByteCode) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ FIELD (deviceId, "deviceId", "I") \ FIELD (portIndex, "portIndex", "I") \ FIELD (type, "type", "I") \ DECLARE_JNI_CLASS_WITH_BYTECODE (JavaPortPath, "com/rmsl/juce/JuceMidiSupport$PortPath", 24, javaMidiByteCode) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(J)V") \ DECLARE_JNI_CLASS_WITH_BYTECODE (NativeMidiReceiver, "com/rmsl/juce/JuceMidiSupport$NativeMidiReceiver", 24, javaMidiByteCode) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (send, "send", "([BIIJ)V") \ DECLARE_JNI_CLASS (JavaMidiReceiver, "android/media/midi/MidiReceiver") #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ FIELD (name, "name", "Ljava/lang/String;") \ FIELD (manufacturer, "manufacturer", "Ljava/lang/String;") \ FIELD (product, "product", "Ljava/lang/String;") \ FIELD (serialNumber, "serialNumber", "Ljava/lang/String;") \ FIELD (id, "id", "I") \ FIELD (transport, "transport", "I") \ FIELD (type, "type", "I") \ FIELD (dst, "dst", "Ljava/util/ArrayList;") \ FIELD (src, "src", "Ljava/util/ArrayList;") DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiDeviceInfo, "com/rmsl/juce/JuceMidiSupport$JuceMidiDeviceInfo", 24, javaMidiByteCode) #undef JNI_CLASS_MEMBERS struct AndroidMidiHelpers { static constexpr int TRANSPORT_BYTESTREAM = 1; static constexpr int TRANSPORT_UMP = 2; static jlong midiClockMillis (JNIEnv* env) { const auto nanos = env->CallStaticLongMethod (JavaLangSystem, JavaLangSystem.nanoTime); return nanos / (jlong) 1e6; } /* When dealing in UMP, the system always sends/receives UMP packets in network order, i.e. big-endian. This function will ensure the correct byte ordering for a range of bytes that are about to cross the API boundary to/from android. */ static void swapByteOrderOfWordsIfNecessary (Span bytes) { if ((bytes.size() % 4) != 0) { // We're expecting to receive 32-bit words, but the system hasn't given us a whole // number of words. jassertfalse; return; } #if JUCE_LITTLE_ENDIAN // Words are in network order (big endian) for (auto i = decltype (bytes.size()) {}; i < bytes.size(); i += sizeof (uint32_t)) std::reverse (bytes.data() + i, bytes.data() + i + sizeof (uint32_t)); #endif } /* The device info is all out-of-band. These are properties of the device that we can fetch without needing to open a connection to the device. This differs from a ump::Endpoint, which requires talking to the device in order to retrieve some of the information. We can still use this info to create a stand-in endpoint for a device that's only aware of MIDI 1.0. */ struct StaticDeviceInfo { String name; ///< PROPERTY_NAME String manufacturer; ///< PROPERTY_MANUFACTURER String product; ///< PROPERTY_PRODUCT String serialNumber; ///< PROPERTY_SERIAL_NUMBER std::vector dst; ///< input port names + port count std::vector src; ///< output port names + port count jint id; ///< device id jint transport; ///< transport, bytestream or ump jint type; ///< type, virtual/usb/bluetooth /* This returns the name used by old JUCE versions to identify a specific port */ String getLegacyId (size_t portIndex, ump::IOKind direction) const { jassert (portIndex < (direction == ump::IOKind::dst ? dst.size() : src.size())); return String { ((id * 128) + jmin ((int) portIndex, 127)) * (direction == ump::IOKind::dst ? -1 : 1) }; } std::array getIds (ump::IOKind dir) const { auto& names = dir == ump::IOKind::src ? src : dst; jassert (names.size() <= 16); std::array result; for (size_t i = 0; i < names.size(); ++i) result[i] = getLegacyId (i, dir); return result; } /* Result does *not* have an EndpointId set */ ump::Endpoint getEquivalentBytestreamEndpoint() const { // This isn't a bytestream device! jassert (transport == TRANSPORT_BYTESTREAM); size_t numBlocks{}; std::array blocks; for (auto [index, portName] : enumerate (dst, uint8_t{})) { blocks[numBlocks++] = ump::Block{}.withEnabled (true) .withDirection (ump::BlockDirection::receiver) .withUiHint (ump::BlockUiHint::receiver) .withFirstGroup (index) .withNumGroups (1) .withMIDI1ProxyKind (ump::BlockMIDI1ProxyKind::unrestrictedBandwidth) .withName (portName.isNotEmpty() ? portName : "Input Port " + String (index + 1)); } for (auto [index, portName] : enumerate (src, uint8_t{})) { blocks[numBlocks++] = ump::Block{}.withEnabled (true) .withDirection (ump::BlockDirection::sender) .withUiHint (ump::BlockUiHint::sender) .withFirstGroup (index) .withNumGroups (1) .withMIDI1ProxyKind (ump::BlockMIDI1ProxyKind::unrestrictedBandwidth) .withName (portName.isNotEmpty() ? portName : "Output Port " + String (index + 1)); } return ump::Endpoint{}.withName (name) .withMidi1Support (true) .withStaticBlocks (true) .withProductInstanceId (serialNumber) .withProtocol (ump::PacketProtocol::MIDI_1_0) .withBlocks ({ blocks.data(), numBlocks }); } ump::StaticDeviceInfo getUmpStaticDeviceInfo() const { return ump::StaticDeviceInfo{}.withName (name) .withManufacturer (manufacturer) .withProduct (product) .withTransport (transport == TRANSPORT_UMP ? ump::Transport::ump : ump::Transport::bytestream) .withHasSource (! src.empty()) .withHasDestination (! dst.empty()) .withLegacyIdentifiersSrc (getIds (ump::IOKind::src)) .withLegacyIdentifiersDst (getIds (ump::IOKind::dst)); } }; struct PortPath { int deviceID; int portIndex; int type; }; class MidiReceiver { public: MidiReceiver() = default; explicit MidiReceiver (GlobalRef g) : port (g) {} void send (jbyteArray byteArray, jint len) { getEnv()->CallVoidMethod (port, JavaMidiReceiver.send, byteArray, (jint) 0, len, (jlong) 0); } bool operator== (const MidiReceiver& other) const { return port == other.port; } bool operator!= (const MidiReceiver& other) const { return ! operator== (other); } bool operator< (const MidiReceiver& other) const { return port < other.port; } GlobalRef get() const { return port; } private: GlobalRef port; }; /** Very low-level RAII wrapper over JUCE's Java MIDI port. For simplicity we use the same interface for sources and destinations, so some functions won't work depending on the actual type of the backing port. This may deal in either bytestream or UMP messages depending on the result of isUMP(). */ class MidiPort { public: MidiPort() = default; explicit MidiPort (GlobalRef p) : port (p) {} MidiPort (MidiPort&& other) noexcept : port (std::exchange (other.port, {})) { } MidiPort& operator= (MidiPort&& other) noexcept { std::swap (other.port, port); return *this; } ~MidiPort() noexcept { if (port != nullptr) getEnv()->CallVoidMethod (port, JuceMidiPort.close); } void start() { jassert (port != nullptr); getEnv()->CallVoidMethod (port, JuceMidiPort.start); } void stop() { jassert (port != nullptr); getEnv()->CallVoidMethod (port, JuceMidiPort.stop); } bool isActive() const { jassert (port != nullptr); return getEnv()->CallBooleanMethod (port, JuceMidiPort.isActive); } void sendMidi (jbyteArray byteArray, jint len) { jassert (port != nullptr); getEnv()->CallVoidMethod (port, JavaMidiReceiver.send, byteArray, (jint) 0, len, (jlong) 0); } std::optional getPortPath() const { auto* env = getEnv(); const LocalRef result { env->CallObjectMethod (port, JuceMidiPort.getPortPath) }; if (result == nullptr) return {}; return PortPath { env->GetIntField (result, JavaPortPath.deviceId), env->GetIntField (result, JavaPortPath.portIndex), env->GetIntField (result, JavaPortPath.type), }; } bool operator== (std::nullptr_t) const { return port == nullptr; } bool operator!= (std::nullptr_t other) const { return ! operator== (other); } bool operator== (const MidiPort& other) const { return port == other.port; } bool operator!= (const MidiPort& other) const { return port != other.port; } bool operator< (const MidiPort& other) const { return port.get() < other.port.get(); } explicit operator bool() const { return operator!= (nullptr); } GlobalRef get() const { return port; } private: GlobalRef port; #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (start, "start", "()V") \ METHOD (stop, "stop", "()V") \ METHOD (close, "close", "()V") \ METHOD (isActive, "isActive", "()Z") \ METHOD (send, "send", "([BIIJ)V") \ METHOD (getPortPath, "getPortPath", "()Lcom/rmsl/juce/JuceMidiSupport$PortPath;") \ DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiPort", 24, javaMidiByteCode) #undef JNI_CLASS_MEMBERS }; struct DeviceManagerListener { virtual ~DeviceManagerListener() = default; virtual void deviceAdded (const StaticDeviceInfo&) = 0; virtual void deviceRemoved (int) = 0; }; /* Dispatches device-change events onto the main thread */ class DeviceListener : private AsyncUpdater { public: ~DeviceListener() override { cancelPendingUpdate(); } void addListener (DeviceManagerListener& l) { listeners.add (&l); } void removeListener (DeviceManagerListener& l) { listeners.remove (&l); } void deviceAdded (const StaticDeviceInfo& info) { { const std::scoped_lock lock { mutex }; events.push_back (info); } triggerAsyncUpdate(); } void deviceRemoved (int id) { { const std::scoped_lock lock { mutex }; events.push_back (id); } triggerAsyncUpdate(); } private: void handleAsyncUpdate() override { const auto copy = std::invoke ([&] { const std::scoped_lock lock { mutex }; auto result = events; events.clear(); return result; }); for (const auto& item : copy) { if (auto* x = std::get_if (&item)) listeners.call ([&] (auto& l) { l.deviceAdded (*x); }); if (auto* x = std::get_if (&item)) listeners.call ([&] (auto& l) { l.deviceRemoved (*x); }); } } using Event = std::variant; std::vector events; std::mutex mutex; ListenerList listeners; }; /** Provides notifications when a MIDI device is added/removed, and allows creating MidiPorts referring to known device ports. */ class DeviceManager { public: static std::shared_ptr getSingleton() { static std::weak_ptr weak; if (auto strong = weak.lock()) return strong; LocalRef manager { getEnv()->CallStaticObjectMethod (JuceMidiSupport, JuceMidiSupport.getAndroidMidiDeviceManager, getAppContext().get()) }; if (manager == nullptr) return {}; std::shared_ptr strong (new DeviceManager { GlobalRef { std::move (manager) } }); weak = strong; return strong; } MidiPort openMidiInputPortWithID (jint deviceID, jint portIndex, jlong host) const { return MidiPort { GlobalRef { LocalRef (getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, deviceID, portIndex, host)) } }; } MidiPort openMidiOutputPortWithID (jint deviceID, jint portIndex) const { return MidiPort { GlobalRef { LocalRef (getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiOutputPortWithID, deviceID, portIndex)) } }; } void addListener (DeviceManagerListener& l) { listener.addListener (l); } void removeListener (DeviceManagerListener& l) { listener.removeListener (l); } private: explicit DeviceManager (GlobalRef g) : deviceManager (g) { } static std::vector arrayListStringToVector (JNIEnv* env, jobject obj) { const auto size = env->CallIntMethod (obj, JavaArrayList.size); std::vector result; result.reserve ((size_t) size); for (jint i = 0; i < size; ++i) result.push_back (juceString ((jstring) env->CallObjectMethod (obj, JavaArrayList.get, i))); return result; } static void handleDeviceAdded (JNIEnv* env, jclass, jobject obj) { if (obj == nullptr) return; StaticDeviceInfo info; info.name = juceString ((jstring) env->GetObjectField (obj, JuceMidiDeviceInfo.name)); info.manufacturer = juceString ((jstring) env->GetObjectField (obj, JuceMidiDeviceInfo.manufacturer)); info.serialNumber = juceString ((jstring) env->GetObjectField (obj, JuceMidiDeviceInfo.serialNumber)); info.product = juceString ((jstring) env->GetObjectField (obj, JuceMidiDeviceInfo.product)); info.id = env->GetIntField (obj, JuceMidiDeviceInfo.id); info.transport = env->GetIntField (obj, JuceMidiDeviceInfo.transport); info.type = env->GetIntField (obj, JuceMidiDeviceInfo.type); info.src = arrayListStringToVector (env, env->GetObjectField (obj, JuceMidiDeviceInfo.src)); info.dst = arrayListStringToVector (env, env->GetObjectField (obj, JuceMidiDeviceInfo.dst)); if (auto strong = getSingleton()) strong->listener.deviceAdded (info); } static void handleDeviceRemoved (JNIEnv*, jclass, jint id) { if (auto strong = getSingleton()) strong->listener.deviceRemoved (id); } DeviceListener listener; GlobalRef deviceManager; #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ CALLBACK (handleDeviceAdded, "handleDeviceAdded", "(Lcom/rmsl/juce/JuceMidiSupport$JuceMidiDeviceInfo;)V") \ CALLBACK (handleDeviceRemoved, "handleDeviceRemoved", "(I)V" ) \ STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \ STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothMidiManager;") \ DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 24, javaMidiByteCode) #undef JNI_CLASS_MEMBERS }; /* A source of MIDI messages. This converts bytestream to UMP if necessary, but performs no other conversions. */ class MidiSource { public: MidiSource (ump::Consumer& c, uint8_t portIndex, bool isUmpIn) : consumer (c), bytestreamDispatcher { portIndex, ump::PacketProtocol::MIDI_1_0, 4096 }, isUmp (isUmpIn) { } private: void handleInput (JNIEnv* env, jbyteArray byteArray, jint offset, jint len, jlong timestamp) { jassert (byteArray != nullptr); thread_local std::vector buffer (4096); { auto* data = env->GetByteArrayElements (byteArray, nullptr); auto* bytes = reinterpret_cast (data + offset); buffer.resize ((size_t) len); std::copy (bytes, bytes + len, buffer.begin()); env->ReleaseByteArrayElements (byteArray, data, 0); } const auto time = convertNativeTimestamp (timestamp); const auto send = [this] (const ump::View& view, double t) { const ump::Iterator b { view.data(), view.size() }; consumer.consume (b, std::next (b), t); }; if (isUmp) { swapByteOrderOfWordsIfNecessary (buffer); const auto words = reinterpret_cast (buffer.data()); const auto numWords = buffer.size() / sizeof (uint32_t); umpDispatcher.dispatch ({ words, numWords }, time, send); } else { bytestreamDispatcher.dispatch (buffer, time, send); } } /* Native timestamps are based on System.nanoTime JUCE timestamps use Time::getMillisecondCounter */ double convertNativeTimestamp (jlong absoluteNanos) const { const auto millis = std::invoke ([&] { // A timestamp of 0 means ASAP if (absoluteNanos == 0) return (double) Time::getMillisecondCounter(); const auto elapsedNanos = absoluteNanos - startTimeNative; const auto elapsedMillis = elapsedNanos / (jlong) 1e6; return (double) (startTimeMillis + elapsedMillis); }); return millis * 0.001; } static void handleReceive (JNIEnv* env, MidiSource& myself, jbyteArray byteArray, jint offset, jint len, jlong timestamp) { myself.handleInput (env, byteArray, offset, len, timestamp); } ump::Consumer& consumer; ump::Dispatcher umpDispatcher; ump::BytestreamToUMPDispatcher bytestreamDispatcher; const jlong startTimeNative = midiClockMillis (getEnv()); const uint32 startTimeMillis = Time::getMillisecondCounter(); bool isUmp; #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ CALLBACK (generatedCallback<&MidiSource::handleReceive>, "handleReceive", "(J[BIIJ)V") DECLARE_JNI_CLASS (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$NativeMidiReceiver") #undef JNI_CLASS_MEMBERS }; /* A destination for MIDI messages. This converts UMP to bytestream if necessary, but performs no other conversions. If the destination is using UMP transport, this won't convert to the target's preferred protocol. The connection is exclusive, so this should be shared between all entities that want to concurrently send MIDI to this destination. */ class MidiDestination { public: static std::unique_ptr make (MidiReceiver outputReceiver, bool isUmp) { if (outputReceiver == MidiReceiver{}) return {}; auto result = rawToUniquePtr (new MidiDestination); result->outputReceiver = std::move (outputReceiver); result->isUMP = isUmp; return result; } bool send (ump::View v) { ump::Iterator b { v.data(), v.size() }; return send (b, std::next (b)); } bool send (ump::Iterator b, ump::Iterator e) { if (outputReceiver == MidiReceiver{}) return false; if (isUMP) { auto* env = getEnv(); const auto messageSize = size_t (e->data() - b->data()) * sizeof (uint32_t); LocalRef messageContent (env->NewByteArray ((jsize) messageSize)); auto* rawBytes = env->GetByteArrayElements (messageContent.get(), nullptr); std::memcpy (rawBytes, b->data(), static_cast (messageSize)); swapByteOrderOfWordsIfNecessary (Span (unalignedPointerCast (rawBytes), messageSize)); env->ReleaseByteArrayElements (messageContent.get(), rawBytes, 0); outputReceiver.send (messageContent.get(), (jint) messageSize); } else { for (auto v : makeRange (b, e)) { toBytestream.convert (v, 0.0, [&] (ump::BytesOnGroup bytesView, double) { auto *env = getEnv(); const auto messageSize = (jsize) bytesView.bytes.size(); LocalRef messageContent (env->NewByteArray (messageSize)); auto *rawBytes = env->GetByteArrayElements (messageContent.get(), nullptr); std::memcpy (rawBytes, bytesView.bytes.data(), static_cast (messageSize)); env->ReleaseByteArrayElements (messageContent.get(), rawBytes, 0); outputReceiver.send (messageContent.get(), messageSize); }); } } return true; } private: ump::ToBytestreamConverter toBytestream { 4096 }; MidiReceiver outputReceiver; bool isUMP; }; class ConverterWithOptionalProtocol { public: void setProtocol (std::optional newProtocol) { const auto previousProtocol = std::invoke ([&] { const SpinLock::ScopedLockType lock { mutex }; return converter.has_value() ? std::make_optional (converter->getProtocol()) : std::nullopt; }); if (newProtocol == previousProtocol) return; const SpinLock::ScopedLockType lock { mutex }; if (newProtocol.has_value()) converter.emplace (*newProtocol); else converter.reset(); } template bool convert (ump::Iterator b, ump::Iterator e, Callback&& callback) { SpinLock::ScopedTryLockType lock { mutex }; if (! lock.isLocked()) { // There's contention on the output lock! jassertfalse; return false; } if (! converter.has_value()) { bool allSent = true; // filter out channel voice messages for (const auto& v : makeRange (b, e)) { const auto kind = ump::Utils::getMessageType (v[0]); if (kind == ump::Utils::MessageKind::channelVoice1 || kind == ump::Utils::MessageKind::channelVoice2) { // Dropping this packet because we don't know whether the endpoint is prepared to receive it allSent = false; continue; } const ump::Iterator it { v.data(), v.size() }; allSent &= callback (it, std::next (it)); } return allSent; } bool allSent = true; converter->convert (b, e, [&] (ump::View v) { const ump::Iterator it { v.data(), v.size() }; allSent &= callback (it, std::next (it)); }); return allSent; } private: SpinLock mutex; std::optional converter; }; struct Connection { virtual ~Connection() = default; virtual ump::EndpointId getEndpointId() const = 0; virtual std::optional getEndpoint() const = 0; virtual ump::StaticDeviceInfo getStaticDeviceInfo() const = 0; virtual bool send (ump::Iterator, ump::Iterator) = 0; virtual void addConsumer (ump::Consumer&) = 0; virtual void removeConsumer (ump::Consumer&) = 0; virtual void disconnected() = 0; virtual void addDisconnectionListener (ump::DisconnectionListener&) = 0; virtual void removeDisconnectionListener (ump::DisconnectionListener&) = 0; }; class VirtualPortConnection : public Connection, private ump::Consumer { public: static std::unique_ptr make (MidiReceiver outputReceiver, bool isUmp) { auto result = rawToUniquePtr (new VirtualPortConnection); result->src = std::make_unique (static_cast (*result), 0, isUmp); result->dst = MidiDestination::make (std::move (outputReceiver), isUmp); result->isUmp = isUmp; if (result->src == nullptr && result->dst == nullptr) return {}; return result; } ump::EndpointId getEndpointId() const override { const String v = "VIRTUAL " + String (isUmp ? "UMP" : "BYTESTREAM"); return ump::EndpointId::makeSrcDst (v, v); } std::optional getEndpoint() const override { return endpoint; } ump::StaticDeviceInfo getStaticDeviceInfo() const override { return ump::StaticDeviceInfo{}.withTransport (isUmp ? ump::Transport::ump : ump::Transport::bytestream) .withHasSource (true) .withHasDestination (true); } void setEndpoint (std::optional ep) { endpoint = std::move (ep); converter.setProtocol (endpoint.has_value() ? endpoint->getProtocol() : std::nullopt); } jlong getHost() const { if (src != nullptr) return (jlong) src.get(); return 0; } void addConsumer (ump::Consumer& x) override { consumers.add (x); } void removeConsumer (ump::Consumer& x) override { consumers.remove (x); } void disconnected() override { disconnectListeners.call ([] (auto& x) { x.disconnected(); }); } void addDisconnectionListener (ump::DisconnectionListener& x) override { disconnectListeners.add (&x); } void removeDisconnectionListener (ump::DisconnectionListener& x) override { disconnectListeners.remove (&x); } bool send (ump::Iterator b, ump::Iterator e) override { if (dst != nullptr) return converter.convert (b, e, [&] (auto&&... args) { return dst->send (args...); }); return false; } private: void consume (ump::Iterator b, ump::Iterator e, double time) override { consumers.call ([&] (auto& x) { x.consume (b, e, time); }); } std::optional endpoint; WaitFreeListeners consumers; ConverterWithOptionalProtocol converter; ListenerList disconnectListeners; std::unique_ptr src; std::unique_ptr dst; bool isUmp = false; }; class VirtualEndpointImplNative : public ump::DisconnectionListener, private ump::Consumer { public: static std::unique_ptr make (const String& name, const ump::DeviceInfo& info, const String& instanceId, ump::PacketProtocol protocol, Span blocks, ump::BlocksAreStatic blocksAreStatic, std::shared_ptr connection) { if (connection == nullptr) return {}; return rawToUniquePtr (new VirtualEndpointImplNative (name, info, instanceId, protocol, blocks, blocksAreStatic, connection)); } ~VirtualEndpointImplNative() override { if (port != nullptr) { port->setEndpoint ({}); port->removeConsumer (*this); } } ump::EndpointId getId() const { return port->getEndpointId(); } bool setBlock (uint8_t index, const ump::Block& b) { if (port == nullptr) return false; const auto ep = port->getEndpoint(); if (! ep.has_value()) return false; auto endpoint = *ep; if (index >= endpoint.getBlocks().size()) return false; const auto old = std::exchange (endpoint.getBlocks()[index], b); if (! b.nameMatches (old)) sendFunctionBlockNameNotification (index); if (! b.infoMatches (old)) sendFunctionBlockInfoNotification (index); port->setEndpoint (std::move (endpoint)); return true; } bool setName (const String& name) { if (port == nullptr) return false; // Per the spec, there's a max length of 98 bytes for endpoint names if (name.getNumBytesAsUTF8() > 98) return false; const auto ep = port->getEndpoint(); if (! ep.has_value()) return false; auto endpoint = *ep; if (name == endpoint.getName()) return true; port->setEndpoint (endpoint.withName (name)); sendEndpointNameNotification(); return true; } void disconnected() override { port = nullptr; } private: VirtualEndpointImplNative (const String& name, const ump::DeviceInfo& info, const String& instanceId, ump::PacketProtocol protocol, Span blocks, ump::BlocksAreStatic blocksAreStatic, std::shared_ptr c) : port (std::move (c)) { port->addConsumer (*this); port->setEndpoint (makeEndpoint (name, info, instanceId, protocol, blocks, blocksAreStatic)); processEndpointDiscovery (ump::View { ump::Factory::makeEndpointDiscovery (1, 1, std::byte { 0x1f }).data() }); processFunctionBlockDiscovery (ump::View { ump::Factory::makeFunctionBlockDiscovery (0xff, std::byte { 0x03 }).data() }); } void consume (ump::Iterator b, ump::Iterator e, double) override { for (auto v : makeRange (b, e)) processPacket (v); } void processPacket (ump::View v) { const auto kind = ump::Utils::getMessageType (v[0]); if (kind != ump::Utils::MessageKind::stream) return; const auto status = ump::Utils::U8<1>::get (v[0]); switch (status) { case 0x0: processEndpointDiscovery (v); break; case 0x5: processStreamConfigRequest (v); break; case 0x10: processFunctionBlockDiscovery (v); break; } } void processEndpointDiscovery (ump::View v) { const auto filterBitmap = ump::Utils::U8<3>::get (v[1]); if ((filterBitmap & 0x01) != 0) sendEndpointInfoNotification(); if ((filterBitmap & 0x02) != 0) sendDeviceIdentityNotification(); if ((filterBitmap & 0x04) != 0) sendEndpointNameNotification(); if ((filterBitmap & 0x08) != 0) sendProductInstanceIdNotification(); if ((filterBitmap & 0x10) != 0) sendStreamConfigurationNotification(); } void processStreamConfigRequest (ump::View) { // Currently we don't support changing stream configuration, so just reply with // the current configuration. sendStreamConfigurationNotification(); } void processFunctionBlockDiscovery (ump::View v) { const auto blockNumber = ump::Utils::U8<2>::get (v[0]); const auto filterBitmap = ump::Utils::U8<3>::get (v[0]); const auto sendFunctionBlockInfo = [this, filterBitmap] (auto index) { if ((filterBitmap & 0x01) != 0) sendFunctionBlockInfoNotification (index); if ((filterBitmap & 0x02) != 0) sendFunctionBlockNameNotification (index); }; const auto endpoint = port->getEndpoint(); if (! endpoint.has_value()) return; if (blockNumber < endpoint->getBlocks().size()) { sendFunctionBlockInfo (blockNumber); } else if (blockNumber == 0xff) { for (uint8_t i = 0; i < endpoint->getBlocks().size(); ++i) sendFunctionBlockInfo (i); } } void sendFunctionBlockNameNotification (uint8_t index) { const auto endpoint = port->getEndpoint(); if (! endpoint.has_value()) return; if (index >= endpoint->getBlocks().size()) { jassertfalse; return; } ump::Factory::makeFunctionBlockNameNotification (index, endpoint->getBlocks()[index].getName(), [this] (auto v) { const ump::Iterator iterator { v.data(), v.size() }; port->send (iterator, std::next (iterator)); }); } void sendFunctionBlockInfoNotification (uint8_t index) { const auto endpoint = port->getEndpoint(); if (! endpoint.has_value()) return; if (index >= endpoint->getBlocks().size()) { jassertfalse; return; } const auto packet = ump::Factory::makeFunctionBlockInfoNotification (index, endpoint->getBlocks()[index].getInfo()); const ump::Iterator iterator { packet.data(), packet.size() }; port->send (iterator, std::next (iterator)); } void sendEndpointNameNotification() { const auto endpoint = port->getEndpoint(); if (! endpoint.has_value()) return; ump::Factory::makeEndpointNameNotification (endpoint->getName(), [this] (auto v) { const ump::Iterator iterator { v.data(), v.size() }; port->send (iterator, std::next (iterator)); }); } void sendDeviceIdentityNotification() { const auto endpoint = port->getEndpoint(); if (! endpoint.has_value()) return; const auto packet = ump::Factory::makeDeviceIdentityNotification (endpoint->getDeviceInfo()); const ump::Iterator iterator { packet.data(), packet.size() }; port->send (iterator, std::next (iterator)); } void sendEndpointInfoNotification() { const auto ep = port->getEndpoint(); if (! ep.has_value()) return; auto& endpoint = *ep; const auto info = ump::EndpointInfo{}.withMidi1Support (endpoint.hasMidi1Support()) .withMidi2Support (endpoint.hasMidi2Support()) .withNumFunctionBlocks ((uint8_t) endpoint.getBlocks().size()) .withReceiveJRSupport (endpoint.hasReceiveJRSupport()) .withTransmitJRSupport (endpoint.hasTransmitJRSupport()) .withStaticFunctionBlocks (endpoint.hasStaticBlocks()) .withVersion (endpoint.getUMPVersionMajor(), endpoint.getUMPVersionMinor()); const auto packet = ump::Factory::makeEndpointInfoNotification (info); const ump::Iterator iterator { packet.data(), packet.size() }; port->send (iterator, std::next (iterator)); } void sendProductInstanceIdNotification() { const auto endpoint = port->getEndpoint(); if (! endpoint.has_value()) return; ump::Factory::makeProductInstanceIdNotification (endpoint->getProductInstanceId(), [this] (auto v) { const ump::Iterator iterator { v.data(), v.size() }; port->send (iterator, std::next (iterator)); }); } void sendStreamConfigurationNotification() { const auto ep = port->getEndpoint(); if (! ep.has_value()) return; auto& endpoint = *ep; const auto protocol = endpoint.getProtocol(); if (! protocol.has_value()) { jassertfalse; return; } const auto info = ump::StreamConfiguration{}.withProtocol (*protocol) .withReceiveTimestamp (endpoint.isReceiveJREnabled()) .withTransmitTimestamp (endpoint.isTransmitJREnabled()); const auto packet = ump::Factory::makeStreamConfigurationNotification (info); const ump::Iterator iterator { packet.data(), packet.size() }; port->send (iterator, std::next (iterator)); } static ump::Endpoint makeEndpoint (const String& name, const ump::DeviceInfo& info, const String& instanceId, ump::PacketProtocol protocol, Span blocks, ump::BlocksAreStatic blocksAreStatic) { return ump::Endpoint{}.withName (name) .withDeviceInfo (info) .withProductInstanceId (instanceId) .withProtocol (protocol) .withMidi1Support (protocol == ump::PacketProtocol::MIDI_1_0) .withMidi2Support (protocol == ump::PacketProtocol::MIDI_2_0) .withUMPVersion (1, 1) .withBlocks (blocks) .withStaticBlocks (blocksAreStatic == ump::BlocksAreStatic::yes); } std::shared_ptr port; }; class LegacyVirtualPortImplNative : public ump::DisconnectionListener { public: static std::unique_ptr make (std::shared_ptr x) { if (x == nullptr) return {}; return rawToUniquePtr (new LegacyVirtualPortImplNative { x }); } ump::EndpointId getId() const { return port->getEndpointId(); } void disconnected() override { port = nullptr; } private: explicit LegacyVirtualPortImplNative (std::shared_ptr x) : port (std::move (x)) {} std::shared_ptr port; }; struct VirtualMidiPortManagerListener { virtual ~VirtualMidiPortManagerListener() = default; virtual void virtualConnectionAdded() = 0; virtual void virtualConnectionRemoved (std::shared_ptr) = 0; }; class VirtualMidiPortManager { public: static VirtualMidiPortManager& getSingleton() { static VirtualMidiPortManager result; return result; } std::shared_ptr createNativeVirtualEndpoint (const String& name, const ump::DeviceInfo& info, const String& productInstance, ump::PacketProtocol protocol, Span blocks, ump::BlocksAreStatic blocksAreStatic) { if (! isVirtualUmpAvailable()) return {}; if (umpPort == nullptr) { // The virtual port is not available, maybe the service hasn't started yet return {}; } if (umpConnection.lock() != nullptr) { // The virtual port was already claimed, perhaps you called createNativeVirtualEndpoint several times jassertfalse; return {}; } std::shared_ptr result = VirtualEndpointImplNative::make (name, info, productInstance, protocol, blocks, blocksAreStatic, umpPort); umpConnection = result; return result; } std::shared_ptr createLegacyVirtualInput() { if (! isVirtualBytestreamAvailable()) return {}; if (bytestreamPort == nullptr) { // The virtual port is not available, maybe the service hasn't started yet return {}; } if (srcConnection.lock() != nullptr) { // The virtual port was already claimed, perhaps you called createNativeVirtualEndpoint several times jassertfalse; return {}; } std::shared_ptr result = LegacyVirtualPortImplNative::make (bytestreamPort); srcConnection = result; return result; } std::shared_ptr createLegacyVirtualOutput() { if (! isVirtualBytestreamAvailable()) return {}; if (bytestreamPort == nullptr) { // The virtual port is not available, maybe the service hasn't started yet return {}; } if (dstConnection.lock() != nullptr) { // The virtual port was already claimed, perhaps you called createNativeVirtualEndpoint several times jassertfalse; return {}; } std::shared_ptr result = LegacyVirtualPortImplNative::make (bytestreamPort); dstConnection = result; return result; } void addListener (VirtualMidiPortManagerListener& x) { listeners.add (&x); } void removeListener (VirtualMidiPortManagerListener& x) { listeners.remove (&x); } bool isBytestreamPortAvailable() const { return bytestreamPort != nullptr; } bool isUmpPortAvailable() const { return umpPort != nullptr; } std::shared_ptr getBytestreamConnection() const { return bytestreamPort; } std::shared_ptr getUmpConnection() const { return umpPort; } static void setVirtualMidiBytestreamEnabled (bool x) { if (VirtualMidiServices != nullptr) { getEnv()->CallStaticVoidMethod (VirtualMidiServices, VirtualMidiServices.setVirtualMidiBytestreamEnabled, getAppContext().get(), (jboolean) x); } } static void setVirtualMidiUmpEnabled (bool x) { if (VirtualMidiServices != nullptr) { getEnv()->CallStaticVoidMethod (VirtualMidiServices, VirtualMidiServices.setVirtualMidiUmpEnabled, getAppContext().get(), (jboolean) x); } } private: VirtualMidiPortManager() = default; static bool isVirtualBytestreamAvailable() { if (VirtualMidiServices != nullptr) { return getEnv()->CallStaticBooleanMethod (VirtualMidiServices, VirtualMidiServices.isVirtualBytestreamAvailable, getAppContext().get()); } return false; } static bool isVirtualUmpAvailable() { if (VirtualMidiServices != nullptr) { return getEnv()->CallStaticBooleanMethod (VirtualMidiServices, VirtualMidiServices.isVirtualUmpAvailable, getAppContext().get()); } return false; } jobject addPort (JNIEnv* env, jobject newReceiver, jboolean isUmp) { std::shared_ptr port = VirtualPortConnection::make (MidiReceiver { GlobalRef { LocalRef { newReceiver } } }, isUmp); if (port == nullptr) return {}; auto* result = env->NewObject (NativeMidiReceiver, NativeMidiReceiver.constructor, port->getHost()); MidiReceiver key { GlobalRef { LocalRef { env->NewLocalRef (result) } } }; (isUmp ? umpPort : bytestreamPort) = port; (isUmp ? umpReceiver : bytestreamReceiver) = key; if (! isUmp) { StaticDeviceInfo info; info.dst = { "" }; info.src = { "" }; info.transport = TRANSPORT_BYTESTREAM; port->setEndpoint (info.getEquivalentBytestreamEndpoint()); } listeners.call ([&] (auto& x) { x.virtualConnectionAdded(); }); return result; } void removePort (JNIEnv* env, jobject previouslyAdded) { MidiReceiver key { GlobalRef { LocalRef { env->NewLocalRef (previouslyAdded) } } }; const auto connection = std::invoke ([&]() -> std::shared_ptr { if (key == umpReceiver) return umpPort; if (key == bytestreamReceiver) return bytestreamPort; return {}; }); if (connection == nullptr) { jassertfalse; return; } listeners.call ([&] (auto& x) { x.virtualConnectionRemoved (connection); }); if (key == umpReceiver) if (auto strong = umpConnection.lock()) strong->disconnected(); if (key == bytestreamReceiver) { if (auto strong = srcConnection.lock()) strong->disconnected(); if (auto strong = dstConnection.lock()) strong->disconnected(); } for (auto* ptr : { &bytestreamPort, &umpPort }) if (*ptr == connection) ptr->reset(); } std::shared_ptr bytestreamPort, umpPort; MidiReceiver bytestreamReceiver, umpReceiver; std::weak_ptr umpConnection; std::weak_ptr srcConnection; std::weak_ptr dstConnection; ListenerList listeners; static jobject addVirtualPort (JNIEnv* env, jclass, jobject newReceiver, jboolean isUmp) { return getSingleton().addPort (env, newReceiver, isUmp); } static void removeVirtualPort (JNIEnv* env, jclass, jobject previouslyAdded) { getSingleton().removePort (env, previouslyAdded); } #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ CALLBACK (addVirtualPort, "addVirtualPort", "(Landroid/media/midi/MidiReceiver;Z)Landroid/media/midi/MidiReceiver;") \ CALLBACK (removeVirtualPort, "removeVirtualPort", "(Landroid/media/midi/MidiReceiver;)V") \ STATICMETHOD (isVirtualBytestreamAvailable, "isVirtualBytestreamAvailable", "(Landroid/content/Context;)Z") \ STATICMETHOD (isVirtualUmpAvailable, "isVirtualUmpAvailable", "(Landroid/content/Context;)Z") \ STATICMETHOD (setVirtualMidiBytestreamEnabled, "setVirtualMidiBytestreamEnabled", "(Landroid/content/Context;Z)V") \ STATICMETHOD (setVirtualMidiUmpEnabled, "setVirtualMidiUmpEnabled", "(Landroid/content/Context;Z)V") \ DECLARE_OPTIONAL_JNI_CLASS_WITH_BYTECODE (VirtualMidiServices, "com/rmsl/juce/VirtualMidiServices", 24, nullptr, true) #undef JNI_CLASS_MEMBERS }; /* This type holds the bytes of a Universal MIDI Packet in 'string order' */ struct StreamPacketBytes { std::array data; size_t size = 0; static StreamPacketBytes asPlainBytes (ump::View v) { StreamPacketBytes result; for (const auto word : v) { result.data[result.size + 0] = std::byte { ump::Utils::U8<0>::get (word) }; result.data[result.size + 1] = std::byte { ump::Utils::U8<1>::get (word) }; result.data[result.size + 2] = std::byte { ump::Utils::U8<2>::get (word) }; result.data[result.size + 3] = std::byte { ump::Utils::U8<3>::get (word) }; result.size += 4; } return result; } }; /* Endpoint name, product instance ID, and function block name may all be spread across several packets, and this type can reconstitute a UTF-8 string from these packets. */ class StringBuilder { public: StringBuilder() { buffer.reserve (7 * 16); } void add (ump::View v) { if (ump::Utils::getMessageType (v[0]) != ump::Utils::MessageKind::stream) { jassertfalse; return; } const auto status = ump::Utils::U8<1>::get (v[0]); if (status != 0x03 && status != 0x04 && status != 0x12) { jassertfalse; return; } const auto format = (ump::Utils::U8<0>::get (v[0]) >> 2) & 0x3; switch (format) { case 0: { beginString(); append (v); endString(); break; } case 1: { beginString(); append (v); break; } case 2: { jassert (! buffer.empty()); append (v); break; } case 3: { jassert (! buffer.empty()); append (v); endString(); break; } } } std::optional get() const { if (buffer.empty() || buffer.back() == std::byte{}) return String::fromUTF8 (reinterpret_cast (buffer.data()), (int) buffer.size()); return std::nullopt; } private: void beginString() { buffer.clear(); } void append (ump::View v) { const auto status = ump::Utils::U8<1>::get (v[0]); const auto offset = status == 0x12 ? 3 : 2; const auto packetAsBytes = StreamPacketBytes::asPlainBytes (v); buffer.insert (buffer.end(), packetAsBytes.data.begin() + offset, packetAsBytes.data.end()); } void endString() { buffer.push_back ({}); // add null terminator just in case } std::vector buffer; }; struct StreamProcessorQueueDelegate { virtual ~StreamProcessorQueueDelegate() = default; virtual void processStreamPacket (ump::View) = 0; }; /* Filters out stream messages and dispatches them to the main thread, where they are passed to the delegate for further processing. */ class StreamProcessorQueue final : private Timer { public: explicit StreamProcessorQueue (StreamProcessorQueueDelegate& d) : delegate (d) { startTimer (20); } ~StreamProcessorQueue() override { stopTimer(); } /** To be called from the MIDI input thread */ void consume (ump::Iterator b, ump::Iterator e) { for (auto v : makeRange (b, e)) { if (ump::Utils::getMessageType (v[0]) != ump::Utils::MessageKind::stream) continue; size_t i = 0; fifo.write ((int) v.size()).forEach ([&] (auto index) { buffer[(size_t) index] = v[i++]; }); } } private: void timerCallback() override { const auto callback = [this] (ump::View v, double) { delegate.processStreamPacket (v); }; const auto scope = fifo.read (fifo.getNumReady()); dispatcher.dispatch ({ buffer.data() + scope.startIndex1, (size_t) scope.blockSize1 }, 0.0, callback); dispatcher.dispatch ({ buffer.data() + scope.startIndex2, (size_t) scope.blockSize2 }, 0.0, callback); } static constexpr auto queueSize = 4096; StreamProcessorQueueDelegate& delegate; std::vector buffer = std::vector (queueSize); AbstractFifo fifo { queueSize }; ump::Dispatcher dispatcher; }; struct EndpointInfoBuilderDelegate { virtual ~EndpointInfoBuilderDelegate() = default; virtual void sendFunctionBlockDiscovery() = 0; }; /* Parses incoming stream messages to build up a cache of all the information known about a connected endpoint. Waits until all function block information has been received before notifying all EndpointListeners, and then notifies listeners on each subsequent configuration change. */ class EndpointInfoBuilder : public StreamProcessorQueueDelegate { public: EndpointInfoBuilder (const ump::EndpointId& idToUse, EndpointInfoBuilderDelegate& d) : id (idToUse), delegate (d) { } std::optional getEndpoint() const { return endpoint; } void processStreamPacket (ump::View v) override { JUCE_ASSERT_MESSAGE_THREAD const auto status = ump::Utils::U8<1>::get (v[0]); switch (status) { case 0x1: processEndpointInfo (v); break; case 0x2: processDeviceIdentity (v); break; case 0x3: processEndpointName (v); break; case 0x4: processProductInstanceID (v); break; case 0x6: processStreamConfigNotification (v); break; case 0x11: processFunctionBlockInfo (v); break; case 0x12: processFunctionBlockName (v); break; } sendNotificationIfValid(); } void addListener (ump::EndpointsListener& l) { changeListeners.add (&l); } void removeListener (ump::EndpointsListener& l) { changeListeners.remove (&l); } private: void sendNotificationIfValid() { const auto numSpecifiedBlocks = endpoint.has_value() ? endpoint->getBlocks().size() : 0; const auto requiredBitmap = (uint32_t) (((uint64_t) 1 << numSpecifiedBlocks) - 1); if ((specifiedBlocks & requiredBitmap) != requiredBitmap) return; if ((specifiedBlockNames & requiredBitmap) != requiredBitmap) return; changeListeners.call ([] (auto& x) { x.endpointsChanged(); }); } void processEndpointInfo (ump::View v) { const auto prevNumFunctionBlocks = endpoint.has_value() ? endpoint->getBlocks().size() : 0; endpoint = endpoint.value_or (ump::Endpoint{}) .withUMPVersion (ump::Utils::U8<2>::get (v[0]), ump::Utils::U8<3>::get (v[0])) .withMidi1Support (ump::Utils::U8<2>::get (v[1]) & 0x1) .withMidi2Support (ump::Utils::U8<2>::get (v[1]) & 0x2) .withTransmitJRSupport (ump::Utils::U8<3>::get (v[1]) & 0x1) .withReceiveJRSupport (ump::Utils::U8<3>::get (v[1]) & 0x2) .withStaticBlocks (ump::Utils::U8<0>::get (v[1]) & 0x80) .withNumBlocks (ump::Utils::U8<0>::get (v[1]) & 0x7f); if (endpoint->getBlocks().size() > prevNumFunctionBlocks) delegate.sendFunctionBlockDiscovery(); } void processDeviceIdentity (ump::View v) { const auto bytes = StreamPacketBytes::asPlainBytes (v); ump::DeviceInfo info; std::copy (bytes.data.data() + 0x5, bytes.data.data() + 0x8, info.manufacturer.begin()); std::copy (bytes.data.data() + 0x8, bytes.data.data() + 0xa, info.family.begin()); std::copy (bytes.data.data() + 0xa, bytes.data.data() + 0xc, info.modelNumber.begin()); std::copy (bytes.data.data() + 0xc, bytes.data.data() + 0x10, info.revision.begin()); endpoint = endpoint.value_or (ump::Endpoint{}).withDeviceInfo (info); } void processEndpointName (ump::View v) { endpointNameBuilder.add (v); if (auto x = endpointNameBuilder.get()) endpoint = endpoint.value_or (ump::Endpoint{}).withName (*x); } void processProductInstanceID (ump::View v) { productIdBuilder.add (v); if (auto x = productIdBuilder.get()) endpoint = endpoint.value_or (ump::Endpoint{}).withProductInstanceId (*x); } void processStreamConfigNotification (ump::View v) { const auto packetProtocol = std::invoke ([&]() -> std::optional { switch (ump::Utils::U8<2>::get (v[0])) { case 1: return ump::PacketProtocol::MIDI_1_0; case 2: return ump::PacketProtocol::MIDI_2_0; } return {}; }); const auto flags = ump::Utils::U8<3>::get (v[0]); const auto rxjr = flags & 0x2; const auto txjr = flags & 0x1; endpoint = endpoint.value_or (ump::Endpoint{}) .withProtocol (packetProtocol) .withReceiveJREnabled (rxjr) .withTransmitJREnabled (txjr); } void processFunctionBlockInfo (ump::View v) { const auto index = (size_t) (ump::Utils::U8<2>::get (v[0]) & 0x7f); if (! endpoint.has_value() || index >= endpoint->getBlocks().size()) { // Got a block notification for a block that doesn't exist! jassertfalse; return; } endpoint->getBlocks()[index] = ump::Block{}.withEnabled (ump::Utils::U8<2>::get (v[0]) & 0x80) .withUiHint ((ump::BlockUiHint) ((ump::Utils::U8<3>::get (v[0]) >> 4) & 0x3)) .withMIDI1ProxyKind ((ump::BlockMIDI1ProxyKind) ((ump::Utils::U8<3>::get (v[0]) >> 2) & 0x3)) .withDirection ((ump::BlockDirection) ((ump::Utils::U8<3>::get (v[0]) >> 0) & 0x3)) .withFirstGroup (ump::Utils::U8<0>::get (v[1])) .withNumGroups (ump::Utils::U8<1>::get (v[1])) .withMaxSysex8Streams (ump::Utils::U8<3>::get (v[1])); specifiedBlocks |= (1 << index); } void processFunctionBlockName (ump::View v) { const auto blockIndex = ump::Utils::U8<2>::get (v[0]); if (blockIndex >= functionBlockNameBuilders.size()) { // block with this index cannot exist jassertfalse; return; } if (! endpoint.has_value() || blockIndex >= endpoint->getBlocks().size()) { // setting a name for a block that doesn't exist on this endpoint jassertfalse; return; } auto& builder = functionBlockNameBuilders[blockIndex]; builder.add (v); if (auto x = builder.get()) { auto& block = endpoint->getBlocks()[blockIndex]; block = block.withName (*x); specifiedBlockNames |= (1 << blockIndex); } } ump::EndpointId id; EndpointInfoBuilderDelegate& delegate; ListenerList changeListeners; std::optional endpoint; std::array functionBlockNameBuilders; StringBuilder endpointNameBuilder, productIdBuilder; // Toggle each bit on to indicate that we've received information about the block at that index uint32_t specifiedBlocks = 0; uint32_t specifiedBlockNames = 0; }; /* A connection to a UMP device, which is assumed to always have a single input and output port. */ class UmpConnection : public Connection, private ump::EndpointsListener, private EndpointInfoBuilderDelegate, private ump::Consumer { public: ~UmpConnection() override { for (auto& port : ports) port.stop(); } static std::shared_ptr make (const DeviceManager& manager, const StaticDeviceInfo& info, const ump::EndpointId& e) { auto result = rawToUniquePtr (new UmpConnection { e }); result->src = std::make_unique (static_cast (*result), 0, true); if (result->src != nullptr) { if (auto port = manager.openMidiInputPortWithID (info.id, (jint) 0, (jlong) result->src.get())) { port.start(); result->ports.push_back (std::move (port)); } } if (auto port = manager.openMidiOutputPortWithID (info.id, (jint) 0)) { result->dst = MidiDestination::make (MidiReceiver { port.get() }, true); result->ports.push_back (std::move (port)); } if (result->src == nullptr && result->dst == nullptr) { // Unable to open a connection in either direction to this device! jassertfalse; return {}; } result->staticInfo = info; result->sendEndpointDiscovery(); return result; } ump::EndpointId getEndpointId() const override { return endpointId; } std::optional getEndpoint() const override { return infoBuilder.getEndpoint(); } ump::StaticDeviceInfo getStaticDeviceInfo() const override { return staticInfo.getUmpStaticDeviceInfo(); } bool send (ump::Iterator b, ump::Iterator e) override { if (dst == nullptr) return false; return converter.convert (b, e, [&] (auto&&... args) { return dst->send (args...); }); } void addConsumer (ump::Consumer& c) override { consumers.add (c); } void removeConsumer (ump::Consumer& c) override { consumers.remove (c); } void disconnected() override { disconnectListeners.call ([] (auto& x) { x.disconnected(); }); } void addDisconnectionListener (ump::DisconnectionListener& l) override { disconnectListeners.add (&l); } void removeDisconnectionListener (ump::DisconnectionListener& l) override { disconnectListeners.remove (&l); } void addEndpointListener (ump::EndpointsListener& l) { infoBuilder.addListener (l); } void removeEndpointListener (ump::EndpointsListener& l) { infoBuilder.removeListener (l); } private: explicit UmpConnection (const ump::EndpointId& idToUse) : endpointId (idToUse) { infoBuilder.addListener (*this); } /* Called when 'our' endpoint is changed. */ void endpointsChanged() override { const auto newProtocol = std::invoke ([&]() -> std::optional { if (const auto& e = infoBuilder.getEndpoint()) return e->getProtocol(); return std::nullopt; }); converter.setProtocol (newProtocol); } void sendEndpointDiscovery() { // The 'filter bitmap' requests all fields that are defined in the current UMP spec if (dst != nullptr) dst->send (ump::View { ump::Factory::makeEndpointDiscovery (1, 1, std::byte { 0x1f }).data() }); } void sendFunctionBlockDiscovery() override { if (dst != nullptr) dst->send (ump::View { ump::Factory::makeFunctionBlockDiscovery (0xff, std::byte { 0x3 }).data() }); } void consume (ump::Iterator b, ump::Iterator e, double t) override { // Forward to all interested listeners consumers.call ([&] (auto& x) { x.consume (b, e, t); }); // Filter out stream messages for processing on the main thread queue.consume (b, e); } ListenerList disconnectListeners; WaitFreeListeners consumers; StaticDeviceInfo staticInfo; ump::EndpointId endpointId; EndpointInfoBuilder infoBuilder { endpointId, *this }; StreamProcessorQueue queue { infoBuilder }; std::unique_ptr src; std::unique_ptr dst; std::vector ports; ConverterWithOptionalProtocol converter; }; class BytestreamConnection : public Connection, private ump::Consumer { public: ~BytestreamConnection() override { for (auto& port : ports) port.stop(); } static std::shared_ptr make (const DeviceManager& manager, const StaticDeviceInfo& info, const ump::EndpointId& e) { auto result = rawToUniquePtr (new BytestreamConnection); for (size_t i = 0; i < info.src.size(); ++i) { auto item = std::make_unique (static_cast (*result), (int) i, false); auto port = manager.openMidiInputPortWithID (info.id, (jint) i, (jlong) item.get()); if (port == nullptr) continue; result->src.push_back (std::move (item)); port.start(); result->ports.push_back (std::move (port)); } for (size_t i = 0; i < info.dst.size(); ++i) { if (auto port = manager.openMidiOutputPortWithID (info.id, (int) i)) { if (auto dst = MidiDestination::make (MidiReceiver { port.get() }, false)) { result->dst.push_back (std::move (dst)); result->ports.push_back (std::move (port)); } } } result->endpointId = e; result->endpoint = info.getEquivalentBytestreamEndpoint(); result->staticInfo = info; return result; } ump::EndpointId getEndpointId() const override { return endpointId; } std::optional getEndpoint() const override { return endpoint; } ump::StaticDeviceInfo getStaticDeviceInfo() const override { return staticInfo.getUmpStaticDeviceInfo(); } bool send (ump::Iterator b, ump::Iterator e) override { bool success = true; for (const auto v : makeRange (b, e)) { const auto kind = ump::Utils::getMessageType (v[0]); if (ump::Utils::isGroupless (kind)) { success = false; continue; } const auto groupIndex = ump::Utils::getGroup (v[0]); if (groupIndex >= dst.size()) { success = false; continue; } if (auto& x = dst[(size_t) groupIndex]) x->send (v); } return success; } void addConsumer (ump::Consumer& c) override { consumers.add (c); } void removeConsumer (ump::Consumer& c) override { consumers.remove (c); } void disconnected() override { disconnectListeners.call ([] (auto& x) { x.disconnected(); }); } void addDisconnectionListener (ump::DisconnectionListener& l) override { disconnectListeners.add (&l); } void removeDisconnectionListener (ump::DisconnectionListener& l) override { disconnectListeners.remove (&l); } private: void consume (ump::Iterator b, ump::Iterator e, double t) override { consumers.call ([&] (auto& x) { x.consume (b, e, t); }); } ListenerList disconnectListeners; WaitFreeListeners consumers; StaticDeviceInfo staticInfo; ump::EndpointId endpointId; std::optional endpoint; std::vector> src; std::vector> dst; std::vector ports; }; class Client : private DeviceManagerListener, private VirtualMidiPortManagerListener { public: void getEndpoints (std::vector& buffer) const { std::transform (infoForEndpoint.begin(), infoForEndpoint.end(), std::back_inserter (buffer), [] (auto& pair) { return pair.first; }); } std::optional getEndpoint (const ump::EndpointId& id) const { if (const auto iter = infoForEndpoint.find (id); iter != infoForEndpoint.end()) { if (iter->second.transport == TRANSPORT_BYTESTREAM) return iter->second.getEquivalentBytestreamEndpoint(); } if (const auto iter = connections.find (id); iter != connections.end()) { if (auto connection = iter->second.lock()) return connection->getEndpoint(); } return {}; } std::optional getStaticDeviceInfo (const ump::EndpointId& id) const { if (const auto iter = infoForEndpoint.find (id); iter != infoForEndpoint.end()) return iter->second.getUmpStaticDeviceInfo(); if (const auto iter = connections.find (id); iter != connections.end()) { if (auto connection = iter->second.lock()) return connection->getStaticDeviceInfo(); } return {}; } std::shared_ptr findOrMakeConnection (const ump::EndpointId& e) { const auto connection = connections.find (e); if (connection != connections.end()) if (auto strong = connection->second.lock()) return strong; const auto info = infoForEndpoint.find (e); // We don't know anything about this endpoint, maybe it got disconnected. // In any case, we can't connect to it. if (info == infoForEndpoint.end()) return {}; const auto& staticDeviceInfo = info->second; switch (staticDeviceInfo.transport) { case TRANSPORT_BYTESTREAM: { const auto result = BytestreamConnection::make (*deviceManager, staticDeviceInfo, e); connections[e] = result; return result; } case TRANSPORT_UMP: { const auto result = UmpConnection::make (*deviceManager, staticDeviceInfo, e); result->addEndpointListener (listener); connections[e] = result; return result; } } // Unknown transport! jassertfalse; return {}; } static std::unique_ptr make (ump::EndpointsListener& l) { auto result = rawToUniquePtr (new Client { l }); result->deviceManager = DeviceManager::getSingleton(); if (result->deviceManager == nullptr) return {}; result->deviceManager->addListener (*result); return result; } ~Client() override { if (deviceManager != nullptr) deviceManager->removeListener (*this); VirtualMidiPortManager::getSingleton().removeListener (*this); } private: explicit Client (ump::EndpointsListener& l) : listener (l) { // It's possible for the VirtualMidiPortManager to be created early on in the app's // lifecycle, even before the app's main window component peer is constructed. In the // case that the virtual port manager is constructed first, we still need to make a // record of these pre-existing ports in order to allow new connections to those ports. fetchVirtualConnections(); VirtualMidiPortManager::getSingleton().addListener (*this); } void fetchVirtualConnections() { auto& manager = VirtualMidiPortManager::getSingleton(); for (const auto& [port, transport] : { std::tuple (manager.getBytestreamConnection(), TRANSPORT_BYTESTREAM), std::tuple (manager.getUmpConnection(), TRANSPORT_UMP) }) { if (port != nullptr) connections.insert_or_assign (port->getEndpointId(), port); } } void virtualConnectionAdded() override { fetchVirtualConnections(); listener.virtualMidiServiceActiveChanged(); } void virtualConnectionRemoved (std::shared_ptr x) override { listener.virtualMidiServiceActiveChanged(); const auto iter = connections.find (x->getEndpointId()); if (iter != connections.end()) { jassert (iter->second.lock() == x); connections.erase (iter); } } void deviceAdded (const StaticDeviceInfo& info) override { const auto endpointId = findUniqueId (info); endpointIdForDevice.insert_or_assign (info.id, endpointId); infoForEndpoint.insert_or_assign (endpointId, info); listener.endpointsChanged(); } void deviceRemoved (int id) override { const auto iter = endpointIdForDevice.find (id); if (iter == endpointIdForDevice.end()) { // That's weird, a device disconnected, but we had no record of it connecting in the first place! jassertfalse; return; } const auto connection = connections.find (iter->second); if (connection != connections.end()) { if (const auto strong = connection->second.lock()) strong->disconnected(); connections.erase (connection); } infoForEndpoint.erase (iter->second); endpointIdForDevice.erase (iter); listener.endpointsChanged(); } ump::EndpointId findUniqueId (const StaticDeviceInfo& info) const { const auto base = std::invoke ([&] { if (info.serialNumber.isNotEmpty()) return info.serialNumber; if (info.name.isNotEmpty()) return info.name; // The device doesn't have any immediately-available useful info, // so we're giving up on creating a stable ID return String (info.id); }); for (int suffix = 1;; ++suffix) { const auto potential = base + (suffix == 1 ? "" : " (" + String (suffix) + ')'); const auto id = ump::EndpointId::makeSrcDst (potential, potential); if (infoForEndpoint.count (id) == 0) return id; } } ump::EndpointsListener& listener; std::map endpointIdForDevice; std::map infoForEndpoint; std::map> connections; std::shared_ptr deviceManager; }; class InputImplNative : public ump::Input::Impl::Native, private ump::Consumer { public: ~InputImplNative() override { connection->removeDisconnectionListener (disconnectionListener); connection->removeConsumer (*this); } ump::EndpointId getEndpointId() const override { return connection->getEndpointId(); } ump::PacketProtocol getProtocol() const override { return converter.getProtocol(); } static std::unique_ptr make (ump::DisconnectionListener& dl, std::shared_ptr d, ump::PacketProtocol p, ump::Consumer& c) { if (d == nullptr) return {}; return rawToUniquePtr (new InputImplNative (dl, d, p, c)); } private: InputImplNative (ump::DisconnectionListener& dl, std::shared_ptr d, ump::PacketProtocol p, ump::Consumer& c) : disconnectionListener (dl), consumer (c), converter (p), connection (std::move (d)) { connection->addConsumer (*this); connection->addDisconnectionListener (disconnectionListener); } void consume (ump::Iterator b, ump::Iterator e, double time) override { converter.convert (b, e, [this, time] (auto view) { const ump::Iterator begin { view.data(), view.size() }; consumer.consume (begin, std::next (begin), time); }); } ump::DisconnectionListener& disconnectionListener; ump::Consumer& consumer; ump::GenericUMPConverter converter; std::shared_ptr connection; }; class OutputImplNative : public ump::Output::Impl::Native { public: ~OutputImplNative() override { connection->removeDisconnectionListener (disconnectionListener); } ump::EndpointId getEndpointId() const override { return connection->getEndpointId(); } bool send (ump::Iterator b, ump::Iterator e) override { return connection->send (b, e); } static std::unique_ptr make (ump::DisconnectionListener& dl, std::shared_ptr d) { if (d == nullptr) return {}; return rawToUniquePtr (new OutputImplNative (dl, d)); } private: OutputImplNative (ump::DisconnectionListener& dl, std::shared_ptr d) : disconnectionListener (dl), connection (std::move (d)) { connection->addDisconnectionListener (disconnectionListener); } ump::DisconnectionListener& disconnectionListener; std::shared_ptr connection; }; class SessionImplNative : public ump::Session::Impl::Native { public: String getName() const override { return name; } std::unique_ptr connectInput (ump::DisconnectionListener& d, const ump::EndpointId& e, ump::PacketProtocol p, ump::Consumer& c) override { return InputImplNative::make (d, client->findOrMakeConnection (e), p, c); } std::unique_ptr connectOutput (ump::DisconnectionListener& d, const ump::EndpointId& e) override { return OutputImplNative::make (d, client->findOrMakeConnection (e)); } std::unique_ptr createNativeVirtualEndpoint (const String& endpointName, const ump::DeviceInfo& info, const String& productInstance, ump::PacketProtocol protocol, Span blocks, ump::BlocksAreStatic blocksAreStatic) override { const auto impl = VirtualMidiPortManager::getSingleton().createNativeVirtualEndpoint (endpointName, info, productInstance, protocol, blocks, blocksAreStatic); if (impl == nullptr) return {}; struct Result : public ump::VirtualEndpoint::Impl::Native { explicit Result (std::shared_ptr x) : impl (std::move (x)) {} ump::EndpointId getId() const override { return impl->getId(); } bool setBlock (uint8_t index, const juce::universal_midi_packets::Block& block) override { return impl->setBlock (index, block); } bool setName (const juce::String& nameIn) override { return impl->setName (nameIn); } std::shared_ptr impl; }; return rawToUniquePtr (new Result { impl }); } std::unique_ptr createLegacyVirtualInput (const String&) override { const auto impl = VirtualMidiPortManager::getSingleton().createLegacyVirtualInput(); if (impl == nullptr) return {}; struct Result : public ump::LegacyVirtualInput::Impl::Native { explicit Result (std::shared_ptr x) : impl (std::move (x)) {} ump::EndpointId getId() const override { return impl->getId(); } std::shared_ptr impl; }; return rawToUniquePtr (new Result { impl }); } std::unique_ptr createLegacyVirtualOutput (const String&) override { const auto impl = VirtualMidiPortManager::getSingleton().createLegacyVirtualOutput(); if (impl == nullptr) return {}; struct Result : public ump::LegacyVirtualOutput::Impl::Native { explicit Result (std::shared_ptr x) : impl (std::move (x)) {} ump::EndpointId getId() const override { return impl->getId(); } std::shared_ptr impl; }; return rawToUniquePtr (new Result { impl }); } static std::unique_ptr make (std::shared_ptr c, const String& n) { return rawToUniquePtr (new SessionImplNative { std::move (c), n }); } private: explicit SessionImplNative (std::shared_ptr c, const String& n) : client (std::move (c)), name (n) { } std::shared_ptr client; String name; }; class EndpointsImplNative : public ump::Endpoints::Impl::Native { public: ump::Backend getBackend() const override { return ump::Backend::android; } void getEndpoints (std::vector& buffer) const override { client->getEndpoints (buffer); } std::optional getEndpoint (const ump::EndpointId& x) const override { return client->getEndpoint (x); } std::optional getStaticDeviceInfo (const ump::EndpointId& x) const override { return client->getStaticDeviceInfo (x); } std::unique_ptr makeSession (const String& name) override { return SessionImplNative::make (client, name); } bool isVirtualMidiBytestreamServiceActive() const override { return VirtualMidiPortManager::getSingleton().isBytestreamPortAvailable(); } bool isVirtualMidiUmpServiceActive() const override { return VirtualMidiPortManager::getSingleton().isUmpPortAvailable(); } void setVirtualMidiBytestreamServiceActive (bool x) override { VirtualMidiPortManager::setVirtualMidiBytestreamEnabled (x); } void setVirtualMidiUmpServiceActive (bool x) override { VirtualMidiPortManager::setVirtualMidiUmpEnabled (x); } static std::unique_ptr make (ump::EndpointsListener& l) { if (auto c = Client::make (l)) return rawToUniquePtr (new EndpointsImplNative { std::move (c) }); return {}; } private: explicit EndpointsImplNative (std::shared_ptr c) : client (std::move (c)) { } std::shared_ptr client; }; AndroidMidiHelpers() = delete; }; auto ump::Endpoints::Impl::Native::make (EndpointsListener& l) -> std::unique_ptr { return AndroidMidiHelpers::EndpointsImplNative::make (l); } } // namespace juce